#include #include #include #include #include #include #include #include "common.h" /* Demodulate the video signal & store all kinds of stuff for later stages * Mode: M1, M2, S1, S2, R72, R36... * Rate: exact sampling rate used * Skip: number of PCM samples to skip at the beginning (for sync phase adjustment) * Redraw: FALSE = Apply windowing and FFT to the signal, TRUE = Redraw from cached FFT data * returns: TRUE when finished, FALSE when aborted */ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { guint MaxBin = 0; guint VideoPlusNoiseBins=0, ReceiverBins=0, NoiseOnlyBins=0; guint n=0; guint SyncSampleNum; guint i=0, j=0; guint FFTLen=1024, WinLength=0; guint SyncTargetBin; int SampleNum, Length, NumChans; int x = 0, y = 0, tx=0, k=0; double Hann[7][1024] = {{0}}; double Freq = 0, PrevFreq = 0, InterpFreq = 0; int NextSNRtime = 0, NextSyncTime = 0; double Praw, Psync; double Power[1024] = {0}; double Pvideo_plus_noise=0, Pnoise_only=0, Pnoise=0, Psignal=0; double SNR = 0; double ChanStart[4] = {0}, ChanLen[4] = {0}; guchar Image[800][616][3] = {{{0}}}; guchar Channel = 0, WinIdx = 0; typedef struct { int X; int Y; int Time; guchar Channel; gboolean Last; } _PixelGrid; _PixelGrid *PixelGrid; PixelGrid = calloc( ModeSpec[Mode].ImgWidth * ModeSpec[Mode].NumLines * 3, sizeof(_PixelGrid) ); // Initialize Hann windows of different lengths gushort HannLens[7] = { 48, 64, 96, 128, 256, 512, 1024 }; for (j = 0; j < 7; j++) for (i = 0; i < HannLens[j]; i++) Hann[j][i] = 0.5 * (1 - cos( (2 * M_PI * i) / (HannLens[j] - 1)) ); // Starting times of video channels on every line, counted from beginning of line switch (Mode) { case R36: case R24: ChanLen[0] = ModeSpec[Mode].PixelTime * ModeSpec[Mode].ImgWidth * 2; ChanLen[1] = ChanLen[2] = ModeSpec[Mode].PixelTime * ModeSpec[Mode].ImgWidth; ChanStart[0] = ModeSpec[Mode].SyncTime + ModeSpec[Mode].PorchTime; ChanStart[1] = ChanStart[0] + ChanLen[0] + ModeSpec[Mode].SeptrTime; ChanStart[2] = ChanStart[1]; break; case S1: case S2: case SDX: ChanLen[0] = ChanLen[1] = ChanLen[2] = ModeSpec[Mode].PixelTime * ModeSpec[Mode].ImgWidth; ChanStart[0] = ModeSpec[Mode].SeptrTime; ChanStart[1] = ChanStart[0] + ChanLen[0] + ModeSpec[Mode].SeptrTime; ChanStart[2] = ChanStart[1] + ChanLen[1] + ModeSpec[Mode].SyncTime + ModeSpec[Mode].PorchTime; break; case PD50: case PD90: case PD120: case PD160: case PD180: case PD240: case PD290: ChanLen[0] = ChanLen[1] = ChanLen[2] = ChanLen[3] = ModeSpec[Mode].PixelTime * ModeSpec[Mode].ImgWidth; ChanStart[0] = ModeSpec[Mode].SyncTime + ModeSpec[Mode].PorchTime; ChanStart[1] = ChanStart[0] + ChanLen[0] + ModeSpec[Mode].SeptrTime; ChanStart[2] = ChanStart[1] + ChanLen[1] + ModeSpec[Mode].SeptrTime; ChanStart[3] = ChanStart[2] + ChanLen[2] + ModeSpec[Mode].SeptrTime; break; default: ChanLen[0] = ChanLen[1] = ChanLen[2] = ModeSpec[Mode].PixelTime * ModeSpec[Mode].ImgWidth; ChanStart[0] = ModeSpec[Mode].SyncTime + ModeSpec[Mode].PorchTime; ChanStart[1] = ChanStart[0] + ChanLen[0] + ModeSpec[Mode].SeptrTime; ChanStart[2] = ChanStart[1] + ChanLen[1] + ModeSpec[Mode].SeptrTime; break; } // Number of channels per line switch(Mode) { case R24BW: case R12BW: case R8BW: NumChans = 1; break; case R24: case R36: NumChans = 2; break; //In PD* modes, each radio frame encodes //4 channels, two luminance and two chroma case PD50: case PD90: case PD120: case PD160: case PD180: case PD240: case PD290: NumChans = 4; break; default: NumChans = 3; break; } // Plan ahead the time instants (in samples) at which to take pixels out int PixelIdx = 0; if (NumChans == 4){ //Woking on PD* mode //Each radio frame encodes two image lines for (y = 0; y < ModeSpec[Mode].NumLines; y += 2){ for (Channel = 0; Channel < NumChans; Channel++){ for (x = 0; x < ModeSpec[Mode].ImgWidth; x++){ PixelGrid[PixelIdx].Time = (int)round(Rate * ( y/2 * ModeSpec[Mode].LineTime + ChanStart[Channel] + ModeSpec[Mode].PixelTime * 1.0 * (x + 0.5))) + Skip; if (Channel == 0) { PixelGrid[PixelIdx].X = x; PixelGrid[PixelIdx].Y = y; PixelGrid[PixelIdx].Channel = Channel; PixelGrid[PixelIdx].Last = FALSE; PixelIdx++; } else if (Channel == 1 || Channel == 2) { PixelGrid[PixelIdx].X = x; PixelGrid[PixelIdx].Y = y; PixelGrid[PixelIdx].Channel = Channel; PixelGrid[PixelIdx].Last = FALSE; PixelIdx++; PixelGrid[PixelIdx].Time = PixelGrid[PixelIdx - 1].Time; PixelGrid[PixelIdx].X = x; PixelGrid[PixelIdx].Y = y + 1; PixelGrid[PixelIdx].Channel = Channel; PixelGrid[PixelIdx].Last = FALSE; PixelIdx++; } else if (Channel == 3) { PixelGrid[PixelIdx].X = x; PixelGrid[PixelIdx].Y = y + 1; PixelGrid[PixelIdx].Channel = 0; PixelGrid[PixelIdx].Last = FALSE; PixelIdx++; } } } } PixelGrid[PixelIdx - 1].Last = TRUE; } else { for (y = 0; y < ModeSpec[Mode].NumLines; y++) { for (Channel = 0; Channel < NumChans; Channel++) { for (x = 0; x < ModeSpec[Mode].ImgWidth; x++) { if (Mode == R36 || Mode == R24) { if (Channel == 1) { if (y % 2 == 0) PixelGrid[PixelIdx].Channel = 1; else PixelGrid[PixelIdx].Channel = 2; } else PixelGrid[PixelIdx].Channel = 0; } else{ PixelGrid[PixelIdx].Channel = Channel; } PixelGrid[PixelIdx].Time = (int)round(Rate * (y * ModeSpec[Mode].LineTime + ChanStart[Channel] + (1.0 * (x - .5) / ModeSpec[Mode].ImgWidth * ChanLen[PixelGrid[PixelIdx].Channel]))) + Skip; PixelGrid[PixelIdx].X = x; PixelGrid[PixelIdx].Y = y; PixelGrid[PixelIdx].Last = FALSE; PixelIdx++; } } } PixelGrid[PixelIdx - 1].Last = TRUE; } for (k = 0; k < PixelIdx; k++) { if (PixelGrid[k].Time >= 0) { PixelIdx = k; break; } } /*case PD50: case PD90: case PD120: case PD160: case PD180: case PD240: case PD290: if (CurLineTime >= ChanStart[2] + ChanLen[2]) Channel = 3; // ch 0 of even line else if (CurLineTime >= ChanStart[2]) Channel = 2; else if (CurLineTime >= ChanStart[1]) Channel = 1; else Channel = 0; break;*/ // Initialize pixbuffer if (!Redraw) { g_object_unref(pixbuf_rx); pixbuf_rx = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, ModeSpec[Mode].ImgWidth, ModeSpec[Mode].NumLines); gdk_pixbuf_fill(pixbuf_rx, 0); } int rowstride = gdk_pixbuf_get_rowstride (pixbuf_rx); guchar *pixels, *p; pixels = gdk_pixbuf_get_pixels(pixbuf_rx); g_object_unref(pixbuf_disp); pixbuf_disp = gdk_pixbuf_scale_simple(pixbuf_rx, 500, 500.0/ModeSpec[Mode].ImgWidth * ModeSpec[Mode].NumLines * ModeSpec[Mode].LineHeight, GDK_INTERP_BILINEAR); gdk_threads_enter(); gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp); gdk_threads_leave(); if(NumChans == 4) //In PD* modes, each radio frame encodes two image lines Length = ModeSpec[Mode].LineTime * ModeSpec[Mode].NumLines/2 * 44100; else Length = ModeSpec[Mode].LineTime * ModeSpec[Mode].NumLines * 44100; SyncTargetBin = GetBin(1200 + CurrentPic.HedrShift, FFTLen); Abort = FALSE; SyncSampleNum = 0; // Loop through signal for (SampleNum = 0; SampleNum < Length; SampleNum++) { if (!Redraw) { /*** Read ahead from sound card ***/ if (pcm.WindowPtr == 0 || pcm.WindowPtr >= BUFLEN-1024) readPcm(2048); /*** Store the sync band for later adjustments ***/ if (SampleNum == NextSyncTime) { Praw = Psync = 0; memset(fft.in, 0, sizeof(double)*FFTLen); // Hann window for (i = 0; i < 64; i++) fft.in[i] = pcm.Buffer[pcm.WindowPtr+i-32] / 32768.0 * Hann[1][i]; fftw_execute(fft.Plan1024); for (i=GetBin(1500+CurrentPic.HedrShift,FFTLen); i<=GetBin(2300+CurrentPic.HedrShift, FFTLen); i++) Praw += power(fft.out[i]); for (i=SyncTargetBin-1; i<=SyncTargetBin+1; i++) Psync += power(fft.out[i]) * (1- .5*abs(SyncTargetBin-i)); Praw /= (GetBin(2300+CurrentPic.HedrShift, FFTLen) - GetBin(1500+CurrentPic.HedrShift, FFTLen)); Psync /= 2.0; // If there is more than twice the amount of power per Hz in the // sync band than in the video band, we have a sync signal here HasSync[SyncSampleNum] = (Psync > 2*Praw); NextSyncTime += 13; SyncSampleNum ++; } /*** Estimate SNR ***/ if (SampleNum == NextSNRtime) { memset(fft.in, 0, sizeof(double)*FFTLen); // Apply Hann window for (i = 0; i < FFTLen; i++) fft.in[i] = pcm.Buffer[pcm.WindowPtr + i - FFTLen/2] / 32768.0 * Hann[6][i]; fftw_execute(fft.Plan1024); // Calculate video-plus-noise power (1500-2300 Hz) Pvideo_plus_noise = 0; for (n = GetBin(1500+CurrentPic.HedrShift, FFTLen); n <= GetBin(2300+CurrentPic.HedrShift, FFTLen); n++) Pvideo_plus_noise += power(fft.out[n]); // Calculate noise-only power (400-800 Hz + 2700-3400 Hz) Pnoise_only = 0; for (n = GetBin(400+CurrentPic.HedrShift, FFTLen); n <= GetBin(800+CurrentPic.HedrShift, FFTLen); n++) Pnoise_only += power(fft.out[n]); for (n = GetBin(2700+CurrentPic.HedrShift, FFTLen); n <= GetBin(3400+CurrentPic.HedrShift, FFTLen); n++) Pnoise_only += power(fft.out[n]); // Bandwidths VideoPlusNoiseBins = GetBin(2300, FFTLen) - GetBin(1500, FFTLen) + 1; NoiseOnlyBins = GetBin(800, FFTLen) - GetBin(400, FFTLen) + 1 + GetBin(3400, FFTLen) - GetBin(2700, FFTLen) + 1; ReceiverBins = GetBin(3400, FFTLen) - GetBin(400, FFTLen); // Eq 15 Pnoise = Pnoise_only * (1.0 * ReceiverBins / NoiseOnlyBins); Psignal = Pvideo_plus_noise - Pnoise_only * (1.0 * VideoPlusNoiseBins / NoiseOnlyBins); // Lower bound to -20 dB SNR = ((Psignal / Pnoise < .01) ? -20 : 10 * log10(Psignal / Pnoise)); NextSNRtime += 256; } /*** FM demodulation ***/ if (SampleNum % 6 == 0) { // Take FFT every 6 samples PrevFreq = Freq; // Adapt window size to SNR if (!Adaptive) WinIdx = 0; else if (SNR >= 20) WinIdx = 0; else if (SNR >= 10) WinIdx = 1; else if (SNR >= 9) WinIdx = 2; else if (SNR >= 3) WinIdx = 3; else if (SNR >= -5) WinIdx = 4; else if (SNR >= -10) WinIdx = 5; else WinIdx = 6; // Minimum winlength can be doubled for Scottie DX if (Mode == SDX && WinIdx < 6) WinIdx++; memset(fft.in, 0, sizeof(double)*FFTLen); memset(Power, 0, sizeof(double)*1024); // Apply window function WinLength = HannLens[WinIdx]; for (i = 0; i < WinLength; i++) fft.in[i] = pcm.Buffer[pcm.WindowPtr + i - WinLength/2] / 32768.0 * Hann[WinIdx][i]; fftw_execute(fft.Plan1024); MaxBin = 0; // Find the bin with most power for (n = GetBin(1500 + CurrentPic.HedrShift, FFTLen) - 1; n <= GetBin(2300 + CurrentPic.HedrShift, FFTLen) + 1; n++) { Power[n] = power(fft.out[n]); if (MaxBin == 0 || Power[n] > Power[MaxBin]) MaxBin = n; } // Find the peak frequency by Gaussian interpolation if (MaxBin > GetBin(1500 + CurrentPic.HedrShift, FFTLen) - 1 && MaxBin < GetBin(2300 + CurrentPic.HedrShift, FFTLen) + 1) { Freq = MaxBin + (log( Power[MaxBin + 1] / Power[MaxBin - 1] )) / (2 * log( pow(Power[MaxBin], 2) / (Power[MaxBin + 1] * Power[MaxBin - 1]))); // In Hertz Freq = Freq / FFTLen * 44100; } else { // Clip if out of bounds Freq = ( (MaxBin > GetBin(1900 + CurrentPic.HedrShift, FFTLen)) ? 2300 : 1500 ) + CurrentPic.HedrShift; } } /* endif (SampleNum == PixelGrid[PixelIdx].Time) */ // Linear interpolation of (chronologically) intermediate frequencies, for redrawing //InterpFreq = PrevFreq + (Freq-PrevFreq) * ... // TODO! // Calculate luminency & store for later use StoredLum[SampleNum] = clip((Freq - (1500 + CurrentPic.HedrShift)) / 3.1372549); } /* endif (!Redraw) */ if (SampleNum == PixelGrid[PixelIdx].Time) { //In PD* modes, two pixels need data from the same sample //Can't move on from SampleNum, until all are processed while (SampleNum == PixelGrid[PixelIdx].Time) { x = PixelGrid[PixelIdx].X; y = PixelGrid[PixelIdx].Y; Channel = PixelGrid[PixelIdx].Channel; // Store pixel Image[x][y][Channel] = StoredLum[SampleNum]; // Some modes have R-Y & B-Y channels that are twice the height of the Y channel if (Channel > 0 && (Mode == R36 || Mode == R24)) Image[x][y+1][Channel] = StoredLum[SampleNum]; // Calculate and draw pixels to pixbuf on line change if (x == ModeSpec[Mode].ImgWidth - 1 || PixelGrid[PixelIdx].Last) { for (tx = 0; tx < ModeSpec[Mode].ImgWidth; tx++) { p = pixels + y * rowstride + tx * 3; switch(ModeSpec[Mode].ColorEnc) { case RGB: p[0] = Image[tx][y][0]; p[1] = Image[tx][y][1]; p[2] = Image[tx][y][2]; break; case GBR: p[0] = Image[tx][y][2]; p[1] = Image[tx][y][0]; p[2] = Image[tx][y][1]; break; case YUV: p[0] = clip((100 * Image[tx][y][0] + 140 * Image[tx][y][1] - 17850) / 100.0); p[1] = clip((100 * Image[tx][y][0] - 71 * Image[tx][y][1] - 33 * Image[tx][y][2] + 13260) / 100.0); p[2] = clip((100 * Image[tx][y][0] + 178 * Image[tx][y][2] - 22695) / 100.0); break; case BW: p[0] = p[1] = p[2] = Image[tx][y][0]; break; } } if (!Redraw || y % 5 == 0 || PixelGrid[PixelIdx].Last) { // Scale and update image g_object_unref(pixbuf_disp); pixbuf_disp = gdk_pixbuf_scale_simple(pixbuf_rx, 500, 500.0 / ModeSpec[Mode].ImgWidth * ModeSpec[Mode].NumLines * ModeSpec[Mode].LineHeight, GDK_INTERP_BILINEAR); gdk_threads_enter(); gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp); gdk_threads_leave(); } } PixelIdx++; } } /* endif (SampleNum == PixelGrid[PixelIdx].Time) */ if (!Redraw && SampleNum % 8820 == 0) { setVU(Power, FFTLen, WinIdx, TRUE); } if (Abort) { free(PixelGrid); return FALSE; } pcm.WindowPtr ++; } free(PixelGrid); return TRUE; }