slowrx/video.c

408 wiersze
13 KiB
C
Czysty Zwykły widok Historia

2011-07-07 14:09:57 +00:00
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
2011-07-20 17:54:00 +00:00
#include <string.h>
2011-07-07 14:09:57 +00:00
#include <gtk/gtk.h>
2011-07-29 20:09:42 +00:00
#include <alsa/asoundlib.h>
2011-07-07 14:09:57 +00:00
2012-12-25 20:43:26 +00:00
#include <fftw3.h>
2011-07-07 14:09:57 +00:00
#include "common.h"
2011-08-18 00:04:28 +00:00
/* Demodulate the video signal & store all kinds of stuff for later stages
2011-07-07 14:09:57 +00:00
* 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)
2013-01-19 09:51:29 +00:00
* Redraw: FALSE = Apply windowing and FFT to the signal, TRUE = Redraw from cached FFT data
* returns: TRUE when finished, FALSE when aborted
2011-07-07 14:09:57 +00:00
*/
2013-01-19 09:51:29 +00:00
gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) {
2011-08-11 11:48:35 +00:00
guint MaxBin = 0;
guint VideoPlusNoiseBins=0, ReceiverBins=0, NoiseOnlyBins=0;
guint n=0;
2011-08-30 16:21:03 +00:00
guint SyncSampleNum;
guint i=0, j=0;
guint FFTLen=1024, WinLength=0;
guint LopassBin,SyncTargetBin;
int LineNum = 0, SampleNum, Length;
int x = 0, y = 0, prevline=0, tx=0;
2011-08-16 21:29:38 +00:00
double Hann[7][1024] = {{0}};
2013-01-20 00:17:38 +00:00
double t=0, Freq = 0, PrevFreq = 0, InterpFreq = 0, NextPixelTime = 0, NextSNRtime = 0;
2011-08-15 21:03:50 +00:00
double NextSyncTime = 0;
2011-08-12 20:06:23 +00:00
double Praw, Psync;
2011-08-16 21:29:38 +00:00
double Power[1024] = {0};
2011-08-11 11:48:35 +00:00
double Pvideo_plus_noise=0, Pnoise_only=0, Pnoise=0, Psignal=0;
double SNR = 0;
double CurLineTime = 0;
double ChanStart[4] = {0}, ChanLen[4] = {0};
2011-08-20 05:51:27 +00:00
guchar Image[800][616][3] = {{{0}}};
2011-08-30 16:21:03 +00:00
guchar Channel = 0, WinIdx = 0;
2011-08-13 12:26:49 +00:00
2011-07-07 14:09:57 +00:00
// Initialize Hann windows of different lengths
gushort HannLens[7] = { 48, 64, 96, 128, 256, 512, 1024 };
for (j = 0; j < 7; j++)
2011-07-07 14:09:57 +00:00
for (i = 0; i < HannLens[j]; i++)
Hann[j][i] = 0.5 * (1 - cos( (2 * M_PI * i) / (HannLens[j] - 1)) );
2011-07-25 22:41:04 +00:00
// Starting times of video channels on every line, counted from beginning of line
2011-07-07 14:09:57 +00:00
switch (Mode) {
case R36:
case R24:
ChanLen[0] = ModeSpec[Mode].PixelLen * ModeSpec[Mode].ImgWidth * 2;
ChanLen[1] = ChanLen[2] = ModeSpec[Mode].PixelLen * ModeSpec[Mode].ImgWidth;
ChanStart[0] = ModeSpec[Mode].SyncLen + ModeSpec[Mode].PorchLen;
ChanStart[1] = ChanStart[0] + ChanLen[0] + ModeSpec[Mode].SeparatorLen;
ChanStart[2] = ChanStart[1];
break;
2011-07-25 22:41:04 +00:00
case S1:
case S2:
case SDX:
ChanLen[0] = ChanLen[1] = ChanLen[2] = ModeSpec[Mode].PixelLen * ModeSpec[Mode].ImgWidth;
ChanStart[0] = ModeSpec[Mode].SeparatorLen;
ChanStart[1] = ChanStart[0] + ChanLen[0] + ModeSpec[Mode].SeparatorLen;
ChanStart[2] = ChanStart[1] + ChanLen[1] + ModeSpec[Mode].SyncLen + ModeSpec[Mode].PorchLen;
break;
2011-07-07 14:09:57 +00:00
default:
ChanLen[0] = ChanLen[1] = ChanLen[2] = ModeSpec[Mode].PixelLen * ModeSpec[Mode].ImgWidth;
ChanStart[0] = ModeSpec[Mode].SyncLen + ModeSpec[Mode].PorchLen;
ChanStart[1] = ChanStart[0] + ChanLen[0] + ModeSpec[Mode].SeparatorLen;
ChanStart[2] = ChanStart[1] + ChanLen[1] + ModeSpec[Mode].SeparatorLen;
break;
}
2011-08-10 20:43:17 +00:00
// Initialize pixbuffer
2011-07-07 14:09:57 +00:00
if (!Redraw) {
2013-01-13 19:52:31 +00:00
g_object_unref(pixbuf_rx);
2013-01-19 09:51:29 +00:00
pixbuf_rx = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, ModeSpec[Mode].ImgWidth, ModeSpec[Mode].ImgHeight);
2013-01-13 19:52:31 +00:00
gdk_pixbuf_fill(pixbuf_rx, 0);
2011-07-07 14:09:57 +00:00
}
2013-01-13 19:52:31 +00:00
int rowstride = gdk_pixbuf_get_rowstride (pixbuf_rx);
2011-07-07 14:09:57 +00:00
guchar *pixels, *p;
2013-01-13 19:52:31 +00:00
pixels = gdk_pixbuf_get_pixels(pixbuf_rx);
2011-08-20 05:51:27 +00:00
2013-01-13 19:52:31 +00:00
g_object_unref(pixbuf_disp);
pixbuf_disp = gdk_pixbuf_scale_simple(pixbuf_rx, 500,
2011-08-20 05:51:27 +00:00
500.0/ModeSpec[Mode].ImgWidth * ModeSpec[Mode].ImgHeight * ModeSpec[Mode].YScale, GDK_INTERP_BILINEAR);
2011-07-07 14:09:57 +00:00
2011-08-20 05:51:27 +00:00
gdk_threads_enter();
2013-01-13 19:52:31 +00:00
gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp);
2011-08-20 05:51:27 +00:00
gdk_threads_leave();
2011-07-07 14:09:57 +00:00
2011-08-20 05:51:27 +00:00
Length = ModeSpec[Mode].LineLen * ModeSpec[Mode].ImgHeight * 44100;
SyncTargetBin = GetBin(1200+CurrentPic.HedrShift, FFTLen);
LopassBin = GetBin(5000, FFTLen);
2013-01-19 09:51:29 +00:00
Abort = FALSE;
2011-09-11 07:04:46 +00:00
SyncSampleNum = 0;
2011-08-15 21:03:50 +00:00
2011-07-07 14:09:57 +00:00
// Loop through signal
2011-08-30 16:21:03 +00:00
for (SampleNum = 0; SampleNum < Length; SampleNum++) {
2011-07-07 14:09:57 +00:00
2011-08-30 16:21:03 +00:00
t = (SampleNum - Skip) / Rate;
2011-07-07 14:09:57 +00:00
CurLineTime = fmod(t, ModeSpec[Mode].LineLen);
2011-08-18 00:04:28 +00:00
if (!Redraw) {
2011-07-07 14:09:57 +00:00
2011-08-13 12:26:49 +00:00
/*** Read ahead from sound card ***/
if (pcm.WindowPtr == 0 || pcm.WindowPtr >= BUFLEN-1024) readPcm(2048);
2011-08-18 00:04:28 +00:00
2011-07-07 14:09:57 +00:00
2011-08-16 21:29:38 +00:00
/*** Store the sync band for later adjustments ***/
2011-08-12 20:06:23 +00:00
2011-08-15 21:03:50 +00:00
if (t >= NextSyncTime) {
2011-08-12 20:06:23 +00:00
2011-08-13 12:26:49 +00:00
Praw = Psync = 0;
2011-08-16 21:29:38 +00:00
memset(in, 0, sizeof(in[0]) *FFTLen);
memset(out, 0, sizeof(out[0])*FFTLen);
2011-08-17 16:33:04 +00:00
// Hann window
for (i = 0; i < 64; i++) in[i] = pcm.Buffer[pcm.WindowPtr+i-32] / 32768.0 * Hann[1][i];
2011-08-12 20:06:23 +00:00
2011-08-16 21:29:38 +00:00
fftw_execute(Plan1024);
2011-08-15 21:03:50 +00:00
2011-08-12 20:06:23 +00:00
for (i=0;i<LopassBin;i++) {
if (i >= GetBin(1500+CurrentPic.HedrShift, FFTLen) && i <= GetBin(2300+CurrentPic.HedrShift, FFTLen))
Praw += pow(out[i], 2) + pow(out[FFTLen-i], 2);
if (i >= SyncTargetBin-1 && i <= SyncTargetBin+1)
Psync += (pow(out[i], 2) + pow(out[FFTLen-i], 2)) * (1- .5*abs(SyncTargetBin-i));
2011-08-12 20:06:23 +00:00
}
Praw /= (GetBin(2300+CurrentPic.HedrShift, FFTLen) - GetBin(1500+CurrentPic.HedrShift, FFTLen));
Psync /= 2.0;
2011-08-12 20:06:23 +00:00
// 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
2013-01-19 09:51:29 +00:00
if (Psync > 2*Praw) HasSync[SyncSampleNum] = TRUE;
else HasSync[SyncSampleNum] = FALSE;
2011-08-16 21:29:38 +00:00
NextSyncTime += SYNCPIXLEN;
2011-08-30 16:21:03 +00:00
SyncSampleNum ++;
2011-08-12 20:06:23 +00:00
}
2011-08-13 12:26:49 +00:00
/*** Estimate SNR ***/
2011-07-07 14:09:57 +00:00
2011-08-15 21:03:50 +00:00
if (t >= NextSNRtime) {
2011-07-07 14:09:57 +00:00
2011-08-16 21:29:38 +00:00
// Apply Hann window
for (i = 0; i < FFTLen; i++) in[i] = pcm.Buffer[pcm.WindowPtr + i - FFTLen/2] / 32768.0 * Hann[6][i];
2011-07-07 14:09:57 +00:00
2011-08-10 20:43:17 +00:00
// FFT
2011-08-16 21:29:38 +00:00
fftw_execute(Plan1024);
2011-08-15 21:03:50 +00:00
2011-08-10 20:43:17 +00:00
// Calculate video-plus-noise power (1500-2300 Hz)
2011-07-07 14:09:57 +00:00
2011-08-10 20:43:17 +00:00
Pvideo_plus_noise = 0;
for (n = GetBin(1500+CurrentPic.HedrShift, FFTLen); n <= GetBin(2300+CurrentPic.HedrShift, FFTLen); n++)
2011-08-16 21:29:38 +00:00
Pvideo_plus_noise += pow(out[n], 2) + pow(out[FFTLen - n], 2);
2011-07-07 14:09:57 +00:00
2011-08-10 20:43:17 +00:00
// Calculate noise-only power (400-800 Hz + 2700-3400 Hz)
2011-07-07 14:09:57 +00:00
2011-08-10 20:43:17 +00:00
Pnoise_only = 0;
for (n = GetBin(400+CurrentPic.HedrShift, FFTLen); n <= GetBin(800+CurrentPic.HedrShift, FFTLen); n++)
2011-08-16 21:29:38 +00:00
Pnoise_only += pow(out[n], 2) + pow(out[FFTLen - n], 2);
2011-07-07 14:09:57 +00:00
for (n = GetBin(2700+CurrentPic.HedrShift, FFTLen); n <= GetBin(3400+CurrentPic.HedrShift, FFTLen); n++)
2011-08-16 21:29:38 +00:00
Pnoise_only += pow(out[n], 2) + pow(out[FFTLen - n], 2);
2011-07-07 14:09:57 +00:00
2011-08-10 20:43:17 +00:00
// Bandwidths
2011-08-16 21:29:38 +00:00
VideoPlusNoiseBins = GetBin(2300, FFTLen) - GetBin(1500, FFTLen) + 1;
2011-07-25 12:05:03 +00:00
2011-08-16 21:29:38 +00:00
NoiseOnlyBins = GetBin(800, FFTLen) - GetBin(400, FFTLen) + 1 +
GetBin(3400, FFTLen) - GetBin(2700, FFTLen) + 1;
2011-07-25 12:05:03 +00:00
2011-08-16 21:29:38 +00:00
ReceiverBins = GetBin(3400, FFTLen) - GetBin(400, FFTLen);
2011-07-07 14:09:57 +00:00
2011-08-10 20:43:17 +00:00
// Eq 15
Pnoise = Pnoise_only * (1.0 * ReceiverBins / NoiseOnlyBins);
Psignal = Pvideo_plus_noise - Pnoise_only * (1.0 * VideoPlusNoiseBins / NoiseOnlyBins);
2011-07-07 14:09:57 +00:00
2011-08-10 20:43:17 +00:00
// Lower bound to -20 dB
SNR = ((Psignal / Pnoise < .01) ? -20 : 10 * log10(Psignal / Pnoise));
2011-07-07 14:09:57 +00:00
2011-08-15 21:03:50 +00:00
NextSNRtime += 8e-3;
2011-07-07 14:09:57 +00:00
}
2011-08-12 20:06:23 +00:00
/*** FM demodulation ***/
2013-01-20 00:17:38 +00:00
if (t >= NextPixelTime) {
2011-08-15 21:03:50 +00:00
PrevFreq = Freq;
2011-07-07 14:09:57 +00:00
2011-08-10 20:43:17 +00:00
// Adapt window size to SNR
2011-07-07 14:09:57 +00:00
2011-08-30 16:21:03 +00:00
if (!Adaptive) WinIdx = 0;
2013-01-20 00:17:38 +00:00
else if (SNR >= 30) 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;
2011-07-07 14:09:57 +00:00
2011-08-30 16:21:03 +00:00
// Minimum winlength can be doubled for Scottie DX
if (Mode == SDX && WinIdx < 6) WinIdx++;
2011-07-07 14:09:57 +00:00
2011-08-30 16:21:03 +00:00
WinLength = HannLens[WinIdx];
2011-07-07 14:09:57 +00:00
2011-08-16 21:29:38 +00:00
memset(in, 0, sizeof(double)*FFTLen);
2011-07-07 14:09:57 +00:00
2011-08-30 16:21:03 +00:00
// Apply window function
2011-07-07 14:09:57 +00:00
for (i = 0; i < WinLength; i++) in[i] = pcm.Buffer[pcm.WindowPtr + i - WinLength/2] / 32768.0 * Hann[WinIdx][i];
2011-07-07 14:09:57 +00:00
2011-08-16 21:29:38 +00:00
fftw_execute(Plan1024);
2011-08-15 21:03:50 +00:00
2011-07-07 14:09:57 +00:00
MaxBin = 0;
2011-07-25 10:42:27 +00:00
// Find the bin with most power
for (n = GetBin(1500 + CurrentPic.HedrShift, FFTLen) - 1; n <= GetBin(2300 + CurrentPic.HedrShift, FFTLen) + 1; n++) {
2011-07-07 14:09:57 +00:00
2011-07-19 19:29:14 +00:00
Power[n] = pow(out[n],2) + pow(out[FFTLen - n], 2);
2011-07-25 10:42:27 +00:00
if (MaxBin == 0 || Power[n] > Power[MaxBin]) MaxBin = n;
2011-07-07 14:09:57 +00:00
}
2011-08-20 05:51:27 +00:00
// Find the peak frequency by Gaussian interpolation
if (MaxBin > GetBin(1500 + CurrentPic.HedrShift, FFTLen) - 1 && MaxBin < GetBin(2300 + CurrentPic.HedrShift, FFTLen) + 1) {
2011-07-07 14:09:57 +00:00
Freq = MaxBin + (log( Power[MaxBin + 1] / Power[MaxBin - 1] )) /
(2 * log( pow(Power[MaxBin], 2) / (Power[MaxBin + 1] * Power[MaxBin - 1])));
// In Hertz
2011-08-16 21:29:38 +00:00
Freq = Freq / FFTLen * 44100;
2011-08-15 21:03:50 +00:00
InterpFreq = Freq;
2011-07-07 14:09:57 +00:00
} else {
2011-08-15 21:03:50 +00:00
// Clip if out of bounds
Freq = ( (MaxBin > GetBin(1900 + CurrentPic.HedrShift, FFTLen)) ? 2300 : 1500 ) + CurrentPic.HedrShift;
2011-07-07 14:09:57 +00:00
}
}
// Linear interpolation of (chronologically) intermediate frequencies
2013-01-20 00:17:38 +00:00
InterpFreq = PrevFreq + (Freq-PrevFreq) * ((t-(NextPixelTime-ModeSpec[Mode].PixelLen))/ModeSpec[Mode].PixelLen);
2011-08-16 21:29:38 +00:00
2011-08-18 00:04:28 +00:00
// Calculate luminency & store for later use
StoredLum[SampleNum] = clip((InterpFreq - (1500 + CurrentPic.HedrShift)) / 3.1372549);
2011-07-07 14:09:57 +00:00
}
/*** Are we on a video line, and should we sample a pixel? ***/
2013-01-20 00:17:38 +00:00
if ( ((CurLineTime >= ChanStart[0] && CurLineTime < ChanStart[0] + ChanLen[0])
2011-07-07 14:09:57 +00:00
|| (CurLineTime >= ChanStart[1] && CurLineTime < ChanStart[1] + ChanLen[1])
|| (CurLineTime >= ChanStart[2] && CurLineTime < ChanStart[2] + ChanLen[2]) )
2013-01-20 00:17:38 +00:00
&& t >= NextPixelTime
2011-07-07 14:09:57 +00:00
) {
2011-08-11 11:48:35 +00:00
LineNum = t / ModeSpec[Mode].LineLen;
2011-07-07 14:09:57 +00:00
// Which channel is this?
switch(Mode) {
case R24BW:
case R12BW:
case R8BW:
Channel = 0;
break;
case R36:
case R24:
if (CurLineTime >= ChanStart[1]) {
if (LineNum % 2 == 0) Channel = 1;
else Channel = 2;
} else Channel = 0;
break;
case PD50:
case PD90:
case PD120:
case PD160:
case PD180:
case PD240:
case PD290:
2011-07-25 22:41:04 +00:00
if (CurLineTime >= ChanStart[2] + ChanLen[2]) Channel = 3; // ch 0 of even line
2011-07-07 14:09:57 +00:00
else if (CurLineTime >= ChanStart[2]) Channel = 2;
else if (CurLineTime >= ChanStart[1]) Channel = 1;
else Channel = 0;
break;
default:
if (CurLineTime >= ChanStart[2]) Channel = 2;
else if (CurLineTime >= ChanStart[1]) Channel = 1;
else Channel = 0;
break;
}
// X coordinate of this pixel
2011-07-25 22:41:04 +00:00
x = (CurLineTime - ChanStart[Channel]) / ChanLen[Channel] * ModeSpec[Mode].ImgWidth;
2011-07-07 14:09:57 +00:00
// Y coordinate of this pixel
2011-07-25 22:41:04 +00:00
switch(Channel) {
case 3:
y = LineNum + 1;
Channel = 0;
2011-07-07 14:09:57 +00:00
break;
default:
y = LineNum;
break;
}
// Store pixel
if (x >= 0 && y >= 0 && x < ModeSpec[Mode].ImgWidth) {
2011-08-30 16:21:03 +00:00
Image[x][y][Channel] = StoredLum[SampleNum];
2011-07-07 14:09:57 +00:00
// Some modes have R-Y & B-Y channels that are twice the height of the Y channel
if (Channel > 0)
switch(Mode) {
case R36:
case R24:
2011-08-30 16:21:03 +00:00
if (y < ModeSpec[Mode].ImgHeight-1) Image[x][y+1][Channel] = StoredLum[SampleNum];
2011-07-07 14:09:57 +00:00
break;
}
}
2011-08-20 11:49:29 +00:00
if (y > ModeSpec[Mode].ImgHeight-1) break;
2011-07-07 14:09:57 +00:00
2011-08-30 16:21:03 +00:00
// Calculate and draw pixels to pixbuf on line change
2011-08-16 21:29:38 +00:00
if (LineNum != prevline || (LineNum == ModeSpec[Mode].ImgHeight-1 && x == ModeSpec[Mode].ImgWidth-1)) {
2011-07-07 14:09:57 +00:00
for (tx = 0; tx < ModeSpec[Mode].ImgWidth; tx++) {
2011-08-10 20:43:17 +00:00
p = pixels + prevline * rowstride + tx * 3;
switch(ModeSpec[Mode].ColorEnc) {
case RGB:
p[0] = Image[tx][prevline][0];
p[1] = Image[tx][prevline][1];
p[2] = Image[tx][prevline][2];
break;
case GBR:
p[0] = Image[tx][prevline][2];
p[1] = Image[tx][prevline][0];
p[2] = Image[tx][prevline][1];
break;
case YUV:
p[0] = clip((100 * Image[tx][prevline][0] + 140 * Image[tx][prevline][1] - 17850) / 100.0);
p[1] = clip((100 * Image[tx][prevline][0] - 71 * Image[tx][prevline][1] - 33 *
Image[tx][prevline][2] + 13260) / 100.0);
p[2] = clip((100 * Image[tx][prevline][0] + 178 * Image[tx][prevline][2] - 22695) / 100.0);
break;
case BW:
p[0] = p[1] = p[2] = Image[tx][prevline][0];
break;
2011-07-07 14:09:57 +00:00
}
}
if (!Redraw || LineNum % 5 == 0 || LineNum == ModeSpec[Mode].ImgHeight-1) {
2011-07-29 13:01:30 +00:00
// Scale and update image
2013-01-13 19:52:31 +00:00
g_object_unref(pixbuf_disp);
pixbuf_disp = gdk_pixbuf_scale_simple(pixbuf_rx, 500,
2011-08-16 21:29:38 +00:00
500.0/ModeSpec[Mode].ImgWidth * ModeSpec[Mode].ImgHeight * ModeSpec[Mode].YScale, GDK_INTERP_BILINEAR);
2011-07-29 13:01:30 +00:00
2011-07-25 22:41:04 +00:00
gdk_threads_enter();
2013-01-13 19:52:31 +00:00
gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp);
2011-07-25 22:41:04 +00:00
gdk_threads_leave();
2011-07-07 14:09:57 +00:00
}
}
2013-01-20 00:17:38 +00:00
if (LineNum > prevline)
NextPixelTime = t + ModeSpec[Mode].PixelLen/3; // take 3 samples per pixel; to better allow for redraw
else
NextPixelTime += ModeSpec[Mode].PixelLen/3;
2011-07-07 14:09:57 +00:00
prevline = LineNum;
}
2011-08-30 16:21:03 +00:00
if (!Redraw && SampleNum % 8820 == 0) {
setVU(pcm.PeakVal, SNR);
pcm.PeakVal = 0;
2011-07-07 14:09:57 +00:00
}
2013-01-19 09:51:29 +00:00
if (Abort) return FALSE;
2011-08-11 11:48:35 +00:00
pcm.WindowPtr ++;
2011-07-29 13:01:30 +00:00
}
2013-01-19 09:51:29 +00:00
return TRUE;
2011-08-11 11:48:35 +00:00
2011-07-07 14:09:57 +00:00
}