vis works, sync almost

dev
Oona Räisänen 2015-08-25 20:56:33 +03:00
rodzic 0df5f998a4
commit c34a49f5db
10 zmienionych plików z 537 dodań i 614 usunięć

Wyświetl plik

@ -22,7 +22,7 @@ Requirements
------------
* Linux, OSX, ...
* Portaudio
* PortAudio
* gtkmm 3 (`libgtkmm-3.0-dev`)
* FFTW 3 (`libfftw3-dev`)

Wyświetl plik

@ -1,4 +1,4 @@
bin_PROGRAMS = slowrx
slowrx_CPPFLAGS = -g $(GTKMM_CFLAGS) @SNDFILE_CFLAGS@ $(PORTAUDIO_CFLAGS)
slowrx_LDADD = $(GTKMM_LIBS) @SNDFILE_LIBS@ -lfftw3 $(PORTAUDIO_LIBS)
slowrx_SOURCES = slowrx.cc common.cc modespec.cc gui.cc dsp.cc vis.cc video.cc
slowrx_SOURCES = slowrx.cc common.cc modespec.cc gui.cc dsp.cc vis.cc video.cc sync.cc tests.cc

Wyświetl plik

@ -21,8 +21,7 @@ std::vector<std::thread> threads(2);
PicMeta CurrentPic;
PcmData pcm;
short DSPworker::win_lens_[8] = { 47, 63, 95, 127, 255, 511, 1023, 47 };
double DSPworker::window_[8][1024];
std::vector<std::vector<double> > DSPworker::window_ (16);
// Clip to [0..255]
int clip (double a) {

Wyświetl plik

@ -3,13 +3,17 @@
#define MINSLANT 30
#define MAXSLANT 150
#define CIRBUF_LEN 4096
#define READ_CHUNK_LEN 1024
// moment length only affects length of delay and maximum window size.
#define MOMENT_LEN 1023
#define SYNCPIXLEN 1.5e-3
// moment length only affects length of delay, read interval,
// and maximum window size.
#define READ_CHUNK_LEN 1024
#define MOMENT_LEN 2047
#define FFT_LEN_SMALL 1024
#define FFT_LEN_BIG 2048
#define CIRBUF_LEN_FACTOR 4
#define CIRBUF_LEN ((MOMENT_LEN+1)*CIRBUF_LEN_FACTOR)
#include <iostream>
#include "portaudio.h"
#include "sndfile.hh"
@ -17,14 +21,16 @@
#include "gtkmm.h"
enum WindowType {
WINDOW_HANN47 = 0,
WINDOW_HANN63,
WINDOW_CHEB47 = 0,
WINDOW_HANN95,
WINDOW_HANN127,
WINDOW_HANN255,
WINDOW_HANN511,
WINDOW_HANN1023,
WINDOW_CHEB47
WINDOW_HANN2047,
WINDOW_HANN31,
WINDOW_HANN63,
WINDOW_SQUARE47
};
enum SSTVMode {
@ -48,8 +54,12 @@ enum eSubSamp {
SUBSAMP_444, SUBSAMP_422_YUV, SUBSAMP_420_YUYV, SUBSAMP_440_YUVY
};
enum {
STREAM_FILE, STREAM_PA
enum eStreamType {
STREAM_TYPE_FILE, STREAM_TYPE_PA, STREAM_TYPE_STDIN
};
enum eVISParity {
PARITY_EVEN=0, PARITY_ODD=1
};
extern std::map<int, SSTVMode> vis2mode;
@ -80,23 +90,22 @@ class DSPworker {
DSPworker();
void openAudioFile(std::string);
double forward(unsigned);
double forward();
double forward_ms(double);
void getWindowedMoment(WindowType, double *);
double getPeakFreq (double, double, WindowType);
int getBin (double);
double getFourierPower (fftw_complex coeff);
bool is_still_listening ();
std::vector<double> getBandPowerPerHz(std::vector<std::vector<double> >);
WindowType getBestWindowFor(SSTVMode, double);
WindowType getBestWindowFor(SSTVMode);
void readMore();
void openPortAudio();
void openAudioFile (std::string);
void openPortAudio ();
void readMore ();
double forward (unsigned);
double forward ();
double forward_time (double);
void forward_to_time (double);
static short win_lens_[8];
static double window_[8][1024];
void windowedMoment (WindowType, fftw_complex *);
double peakFreq (double, double, WindowType);
int freq2bin (double, int);
std::vector<double> bandPowerPerHz (std::vector<std::vector<double> >);
WindowType bestWindowFor (SSTVMode, double SNR=99);
bool is_open ();
double get_t ();
private:
@ -107,14 +116,17 @@ class DSPworker {
int cirbuf_fill_count_;
bool please_stop_;
SndfileHandle file_;
int fft_len_;
double *fft_inbuf_;
fftw_complex *fft_inbuf_;
fftw_complex *fft_outbuf_;
fftw_plan fft_plan_;
int samplerate_;
bool is_still_listening_;
fftw_plan fft_plan_small_;
fftw_plan fft_plan_big_;
double samplerate_;
PaStream *pa_stream_;
int stream_type_;
eStreamType stream_type_;
bool is_open_;
double t_;
static std::vector<std::vector<double> > window_;
};
class SlowGUI {
@ -196,6 +208,7 @@ typedef struct ModeSpec {
eColorEnc ColorEnc;
eSyncOrder SyncOrder;
eSubSamp SubSampling;
eVISParity VISParity;
} _ModeSpec;
extern _ModeSpec ModeSpec[];
@ -204,7 +217,6 @@ double power (fftw_complex coeff);
int clip (double a);
void createGUI ();
double deg2rad (double Deg);
double FindSync (SSTVMode Mode, double Rate, int *Skip);
std::string GetFSK ();
bool GetVideo (SSTVMode Mode, DSPworker *dsp);
SSTVMode GetVIS (DSPworker*);
@ -215,6 +227,18 @@ void readPcm (int numsamples);
void saveCurrentPic();
void setVU (double *Power, int FFTLen, int WinIdx, bool ShowWin);
int startGui (int, char**);
void findSyncRansac (SSTVMode, std::vector<bool>);
void findSyncHough (SSTVMode, std::vector<bool>);
std::vector<double> upsampleLanczos (std::vector<double>, int);
std::vector<double> Hann (std::size_t);
std::vector<double> Blackmann (std::size_t);
std::vector<double> Rect (std::size_t);
std::vector<double> Gauss (std::size_t);
void runTest(const char*);
double complexMag (fftw_complex coeff);
guint8 freq2lum(double);
void evt_AbortRx ();
void evt_changeDevices ();

Wyświetl plik

@ -3,13 +3,7 @@
DSPworker::DSPworker() : Mutex(), please_stop_(false) {
fft_len_ = 2048;
for (int j = 0; j < 7; j++)
for (int i = 0; i < win_lens_[j]; i++)
window_[j][i] = 0.5 * (1 - cos( (2 * M_PI * i) / (win_lens_[j] - 1)) );
std::vector<double> cheb = {
std::vector<double> cheb47 = {
0.0004272315,0.0013212953,0.0032312239,0.0067664313,0.0127521667,0.0222058684,
0.0363037629,0.0563165400,0.0835138389,0.1190416120,0.1637810511,0.2182020094,
0.2822270091,0.3551233730,0.4354402894,0.5210045495,0.6089834347,0.6960162864,
@ -19,117 +13,148 @@ DSPworker::DSPworker() : Mutex(), please_stop_(false) {
0.1637810511,0.1190416120,0.0835138389,0.0563165400,0.0363037629,0.0222058684,
0.0127521667,0.0067664313,0.0032312239,0.0013212953,0.0004272315
};
for (int i = 0; i < win_lens_[WINDOW_CHEB47]; i++)
window_[WINDOW_CHEB47][i] = cheb[i];
std::vector<double> sq47 = {
1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1
};
//window_[WINDOW_HANN31] = Hann(31);
//window_[WINDOW_HANN47] = Hann(47);
//window_[WINDOW_HANN63] = Hann(63);
window_[WINDOW_HANN95] = Hann(95);
window_[WINDOW_HANN127] = Hann(127);
window_[WINDOW_HANN255] = Hann(255);
window_[WINDOW_HANN511] = Hann(511);
window_[WINDOW_HANN1023] = Hann(1023);
window_[WINDOW_HANN2047] = Hann(2047);
window_[WINDOW_CHEB47] = cheb47;
//window_[WINDOW_SQUARE47] = sq47;
fft_inbuf_ = (double*) fftw_alloc_real(sizeof(double) * fft_len_);
fft_inbuf_ = (fftw_complex*) fftw_alloc_complex(sizeof(fftw_complex) * FFT_LEN_BIG);
if (fft_inbuf_ == NULL) {
perror("GetVideo: Unable to allocate memory for FFT");
exit(EXIT_FAILURE);
}
fft_outbuf_ = (fftw_complex*) fftw_alloc_complex(sizeof(fftw_complex) * (fft_len_));
fft_outbuf_ = (fftw_complex*) fftw_alloc_complex(sizeof(fftw_complex) * FFT_LEN_BIG);
if (fft_outbuf_ == NULL) {
perror("GetVideo: Unable to allocate memory for FFT");
fftw_free(fft_inbuf_);
exit(EXIT_FAILURE);
}
memset(fft_inbuf_, 0, sizeof(double) * fft_len_);
memset(fft_inbuf_, 0, sizeof(fftw_complex) * FFT_LEN_BIG);
fft_plan_ = fftw_plan_dft_r2c_1d(fft_len_, fft_inbuf_, fft_outbuf_, FFTW_ESTIMATE);
fft_plan_small_ = fftw_plan_dft_1d(FFT_LEN_SMALL, fft_inbuf_, fft_outbuf_, FFTW_FORWARD, FFTW_ESTIMATE);
fft_plan_big_ = fftw_plan_dft_1d(FFT_LEN_BIG, fft_inbuf_, fft_outbuf_, FFTW_FORWARD, FFTW_ESTIMATE);
cirbuf_tail_ = 0;
cirbuf_head_ = 0;
cirbuf_fill_count_ = 0;
is_still_listening_ = true;
printf("DSPworker created\n");
is_open_ = false;
t_ = 0;
}
void DSPworker::openAudioFile (std::string fname) {
file_ = SndfileHandle(fname.c_str()) ;
if (!is_open_) {
printf ("Opened file '%s'\n", fname.c_str()) ;
printf (" Sample rate : %d\n", file_.samplerate ()) ;
printf (" Channels : %d\n", file_.channels ()) ;
fprintf (stderr,"Open '%s'\n", fname.c_str()) ;
file_ = SndfileHandle(fname.c_str()) ;
samplerate_ = file_.samplerate();
if (file_.error()) {
fprintf(stderr,"(sndfile) %s\n", file_.strError());
} else {
fprintf (stderr," Sample rate : %d\n", file_.samplerate ()) ;
fprintf (stderr," Channels : %d\n", file_.channels ()) ;
stream_type_ = STREAM_FILE;
samplerate_ = file_.samplerate();
/* RAII takes care of destroying SndfileHandle object. */
stream_type_ = STREAM_TYPE_FILE;
is_open_ = true;
readMore();
/* RAII takes care of destroying SndfileHandle object. */
}
}
}
void DSPworker::openPortAudio () {
/* -- initialize PortAudio -- */
Pa_Initialize();
if (!is_open_) {
Pa_Initialize();
/* -- setup input and output -- */
PaStreamParameters inputParameters;
inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
inputParameters.channelCount = 1;
inputParameters.sampleFormat = paInt16;
inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency ;
inputParameters.hostApiSpecificStreamInfo = NULL;
PaStreamParameters inputParameters;
inputParameters.device = Pa_GetDefaultInputDevice();
inputParameters.channelCount = 1;
inputParameters.sampleFormat = paInt16;
inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency ;
inputParameters.hostApiSpecificStreamInfo = NULL;
const PaDeviceInfo *info;
info = Pa_GetDeviceInfo(inputParameters.device);
const PaDeviceInfo *devinfo;
devinfo = Pa_GetDeviceInfo(inputParameters.device);
/* -- setup stream -- */
int err = Pa_OpenStream(
&pa_stream_,
&inputParameters,
NULL,
44100,
READ_CHUNK_LEN,
paClipOff, /* we won't output out of range samples so don't bother clipping them */
NULL, /* no callback, use blocking API */
NULL ); /* no callback, so no callback userData */
int err = Pa_OpenStream(
&pa_stream_,
&inputParameters,
NULL,
44100,
READ_CHUNK_LEN,
paClipOff,
NULL, /* no callback, use blocking API */
NULL ); /* no callback, so no callback userData */
if (err == paNoError)
printf("opened %s\n",info->name);
/* -- start stream -- */
err = Pa_StartStream( pa_stream_ );
if (err == paNoError)
printf("stream started\n");
if (err == paNoError)
printf("opened %s\n",devinfo->name);
err = Pa_StartStream( pa_stream_ );
if (err == paNoError)
printf("stream started\n");
samplerate_ = 44100;
const PaStreamInfo *streaminfo;
streaminfo = Pa_GetStreamInfo(pa_stream_);
samplerate_ = streaminfo->sampleRate;
printf("%f\n",samplerate_);
stream_type_ = STREAM_PA;
stream_type_ = STREAM_TYPE_PA;
is_open_ = true;
readMore();
}
}
int DSPworker::getBin (double freq) {
return (freq / samplerate_ * fft_len_);
int DSPworker::freq2bin (double freq, int fft_len) {
return (freq / samplerate_ * fft_len);
}
double DSPworker::getFourierPower (fftw_complex coeff) {
return pow(coeff[0],2) + pow(coeff[1],2);
bool DSPworker::is_open () {
return is_open_;
}
bool DSPworker::is_still_listening () {
return is_still_listening_;
double DSPworker::get_t() {
return t_;
}
void DSPworker::readMore () {
short read_buffer[READ_CHUNK_LEN];
sf_count_t samplesread = 0;
sf_count_t samplesread;
if (stream_type_ == STREAM_FILE) {
if (is_open_) {
if (stream_type_ == STREAM_TYPE_FILE) {
samplesread = file_.read(read_buffer, READ_CHUNK_LEN);
if (samplesread < READ_CHUNK_LEN)
is_still_listening_ = false;
samplesread = file_.read(read_buffer, READ_CHUNK_LEN);
if (samplesread < READ_CHUNK_LEN)
is_open_ = false;
} else {
} else if (stream_type_ == STREAM_TYPE_PA) {
samplesread = READ_CHUNK_LEN;
int err = Pa_ReadStream( pa_stream_, read_buffer, READ_CHUNK_LEN );
if (err != paNoError)
is_still_listening_ = false;
samplesread = READ_CHUNK_LEN;
int err = Pa_ReadStream( pa_stream_, read_buffer, READ_CHUNK_LEN );
if (err != paNoError)
is_open_ = false;
}
}
int cirbuf_fits = std::min(CIRBUF_LEN - cirbuf_head_, (int)samplesread);
@ -159,74 +184,111 @@ double DSPworker::forward (unsigned nsamples) {
readMore();
}
}
return (1.0 * nsamples / samplerate_);
double dt = 1.0 * nsamples / samplerate_;
t_ += dt;
return dt;
}
double DSPworker::forward() {
double DSPworker::forward () {
return forward(1);
}
double DSPworker::forward_ms(double ms) {
return forward(ms / 1000 * samplerate_);
double DSPworker::forward_time(double sec) {
double start_t = t_;
while (t_ < start_t + sec)
forward();
return t_ - start_t;
}
void DSPworker::forward_to_time(double sec) {
while (t_ < sec)
forward();
}
// the current moment, windowed
void DSPworker::getWindowedMoment (WindowType win_type, double *result) {
void DSPworker::windowedMoment (WindowType win_type, fftw_complex *result) {
//double if_phi = 0;
for (int i = 0; i < MOMENT_LEN; i++) {
int win_i = i - MOMENT_LEN/2 + win_lens_[win_type]/2 ;
int win_i = i - MOMENT_LEN/2 + window_[win_type].size()/2 ;
if (win_i >= 0 && win_i < win_lens_[win_type]) {
result[win_i] = cirbuf_[cirbuf_tail_ + i] * window_[win_type][win_i];
if (win_i >= 0 && win_i < window_[win_type].size()) {
double a;
//fftw_complex mixed;
a = cirbuf_[cirbuf_tail_ + i] * window_[win_type][win_i];
/*// mix to IF
mixed[0] = a * cos(if_phi) - a * sin(if_phi);
mixed[1] = a * cos(if_phi) + a * sin(if_phi);
if_phi += 2 * M_PI * 10000 / samplerate_;*/
result[win_i][0] = result[win_i][1] = a;
//printf("%f\n",a);
//std::cout<<result[win_i]<<",";
}
}
// exit(0);
//std::cout<<"\n";
}
double DSPworker::getPeakFreq (double minf, double maxf, WindowType wintype) {
double DSPworker::peakFreq (double minf, double maxf, WindowType wintype) {
double windowed[win_lens_[wintype]];
double Power[fft_len_];
int fft_len = (window_[wintype].size() <= FFT_LEN_SMALL ? FFT_LEN_SMALL : FFT_LEN_BIG);
getWindowedMoment(wintype, windowed);
//for (int i=0;i<win_lens_[wintype];i++)
// printf("g %f\n",windowed[i]);
memset(fft_inbuf_, 0, fft_len_ * sizeof(double));
memcpy(fft_inbuf_, windowed, win_lens_[wintype] * sizeof(double));
fftw_execute(fft_plan_);
// Find the bin with most power
int MaxBin = 0;
for (int i = getBin(minf)-1; i <= getBin(maxf)+1; i++) {
Power[i] = getFourierPower(fft_outbuf_[i]);
if ( (i >= getBin(minf) && i < getBin(maxf)) &&
(MaxBin == 0 || Power[i] > Power[MaxBin]))
MaxBin = i;
fftw_complex windowed[window_[wintype].size()];
double Mag[fft_len/2 + 1];
windowedMoment(wintype, windowed);
memset(fft_inbuf_, 0, fft_len * sizeof(windowed[0]));
memcpy(fft_inbuf_, windowed, window_[wintype].size() * sizeof(windowed[0]));
fftw_execute(fft_len == FFT_LEN_BIG ? fft_plan_big_ : fft_plan_small_);
int peakBin = 0;
int lobin = freq2bin(minf, fft_len);
int hibin = freq2bin(maxf, fft_len);
for (int i = lobin-1; i <= hibin+1; i++) {
Mag[i] = complexMag(fft_outbuf_[i]);
if ( (i >= lobin && i < hibin) &&
(peakBin == 0 || Mag[i] > Mag[peakBin]))
peakBin = i;
}
// Find the peak frequency by Gaussian interpolation
double result = MaxBin + (log( Power[MaxBin + 1] / Power[MaxBin - 1] )) /
(2 * log( pow(Power[MaxBin], 2) / (Power[MaxBin + 1] * Power[MaxBin - 1])));
//double result = MaxBin + (log( Power[MaxBin + 1] / Power[MaxBin - 1] )) /
// (2 * log( pow(Power[MaxBin], 2) / (Power[MaxBin + 1] * Power[MaxBin - 1])));
double y1 = Mag[peakBin-1], y2 = Mag[peakBin], y3 = Mag[peakBin+1];
double result = peakBin + (y3 - y1) / (2 * (2*y2 - y3 - y1));
// In Hertz
result = result / fft_len_ * samplerate_;
result = result / fft_len * samplerate_;
// cheb47 @ 44100 can't resolve <1800 Hz
if (wintype == WINDOW_CHEB47 && result < 1800)
result = peakFreq (minf, maxf, WINDOW_HANN95);
return result;
}
std::vector<double> DSPworker::getBandPowerPerHz(std::vector<std::vector<double> > bands) {
double windowed[win_lens_[WINDOW_HANN1023]];
std::vector<double> DSPworker::bandPowerPerHz(std::vector<std::vector<double> > bands) {
getWindowedMoment(WINDOW_HANN1023, windowed);
memset(fft_inbuf_, 0, fft_len_ * sizeof(double));
memcpy(fft_inbuf_, windowed, win_lens_[WINDOW_HANN1023] * sizeof(double));
fftw_execute(fft_plan_);
int fft_len = FFT_LEN_BIG;
WindowType wintype = WINDOW_HANN2047;
fftw_complex windowed[window_[wintype].size()];
windowedMoment(wintype, windowed);
memset(fft_inbuf_, 0, FFT_LEN_BIG * sizeof(fft_inbuf_[0]));
memcpy(fft_inbuf_, windowed, window_[wintype].size() * sizeof(windowed[0]));
fftw_execute(fft_len == FFT_LEN_BIG ? fft_plan_big_ : fft_plan_small_);
std::vector<double> result;
for (std::vector<double> band : bands) {
double P = 0;
double binwidth = 1.0 * samplerate_ / fft_len_;
double binwidth = 1.0 * samplerate_ / fft_len;
int nbins = 0;
for (int i = getBin(band[0]); i <= getBin(band[1]); i++) {
P += getFourierPower(fft_outbuf_[i]);
for (int i = freq2bin(band[0], fft_len); i <= freq2bin(band[1], fft_len); i++) {
P += pow(complexMag(fft_outbuf_[i]), 2);
nbins++;
}
P = P/(binwidth*nbins);
@ -235,74 +297,26 @@ std::vector<double> DSPworker::getBandPowerPerHz(std::vector<std::vector<double>
return result;
}
WindowType DSPworker::getBestWindowFor(SSTVMode Mode, double SNR) {
WindowType DSPworker::bestWindowFor(SSTVMode Mode, double SNR) {
WindowType WinType;
if (SNR >= 20) WinType = WINDOW_CHEB47;
else if (SNR >= 10) WinType = WINDOW_HANN63;
else if (SNR >= 9) WinType = WINDOW_HANN95;
else if (SNR >= 3) WinType = WINDOW_HANN127;
else if (SNR >= -5) WinType = WINDOW_HANN255;
else if (SNR >= -10) WinType = WINDOW_HANN511;
else WinType = WINDOW_HANN1023;
double samplesInPixel = 1.0 * samplerate_ * ModeSpec[Mode].tScan / ModeSpec[Mode].ScanPixels;
// Minimum winlength can be doubled for Scottie DX
//if (Mode == MODE_SDX && WinType < WINDOW_HANN511) WinType++;
if (SNR >= 23 && Mode != MODE_PD180 && Mode != MODE_SDX) WinType = WINDOW_CHEB47;
else if (SNR >= 12) WinType = WINDOW_HANN95;
else if (SNR >= 8) WinType = WINDOW_HANN127;
else if (SNR >= 5) WinType = WINDOW_HANN255;
else if (SNR >= 4) WinType = WINDOW_HANN511;
else if (SNR >= -7) WinType = WINDOW_HANN1023;
else WinType = WINDOW_HANN2047;
return WinType;
}
WindowType DSPworker::getBestWindowFor(SSTVMode Mode) {
return getBestWindowFor(Mode, 99);
}
/*WindowType DSPworker::bestWindowFor(SSTVMode Mode) {
return bestWindowFor(Mode, 20);
}*/
/*
// Capture fresh PCM data to buffer
void readPcm(int numsamples) {
int samplesread, i;
gint32 tmp[BUFLEN]; // Holds one or two 16-bit channels, will be ANDed to single channel
//samplesread = snd_pcm_readi(pcm.handle, tmp, (pcm.WindowPtr == 0 ? BUFLEN : numsamples));
if (samplesread < numsamples) {
if (samplesread == -EPIPE)
printf("ALSA: buffer overrun\n");
else if (samplesread < 0) {
//printf("ALSA error %d (%s)\n", samplesread, snd_strerror(samplesread));
gtk_widget_set_tooltip_text(gui.image_devstatus, "ALSA error");
Abort = TRUE;
pthread_exit(NULL);
}
else
printf("Can't read %d samples\n", numsamples);
// On first appearance of error, update the status icon
if (!pcm.BufferDrop) {
gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_DIALOG_WARNING,GTK_ICON_SIZE_SMALL_TOOLBAR);
gtk_widget_set_tooltip_text(gui.image_devstatus, "Device is dropping samples");
pcm.BufferDrop = TRUE;
}
}
if (pcm.WindowPtr == 0) {
// Fill buffer on first run
for (i=0; i<BUFLEN; i++)
pcm.Buffer[i] = tmp[i] & 0xffff;
pcm.WindowPtr = BUFLEN/2;
} else {
// Move buffer and push samples
for (i=0; i<BUFLEN-numsamples; i++) pcm.Buffer[i] = pcm.Buffer[i+numsamples];
for (i=BUFLEN-numsamples; i<BUFLEN; i++) pcm.Buffer[i] = tmp[i-(BUFLEN-numsamples)] & 0xffff;
pcm.WindowPtr -= numsamples;
}
}
void populateDeviceList() {
/*void populateDeviceList() {
int card;
char *cardname;
int numcards, row;
@ -333,98 +347,72 @@ void populateDeviceList() {
}*/
// Initialize sound card
// Return value:
// 0 = opened ok
// -1 = opened, but suboptimal
// -2 = couldn't be opened
int initPcmDevice(std::string wanted_dev_name) {
std::vector<double> Hann (std::size_t winlen) {
std::vector<double> result(winlen);
for (std::size_t i=0; i < winlen; i++)
result[i] = 0.5 * (1 - cos( (2 * M_PI * i) / (winlen)) );
return result;
}
//snd_pcm_hw_params_t *hwparams;
void *hwparams;
char pcm_name[30];
unsigned int exact_rate = 44100;
int card;
bool found;
char *cardname;
std::vector<double> Blackmann (std::size_t winlen) {
std::vector<double> result(winlen);
for (std::size_t i=0; i < winlen; i++)
result[i] = 0.42 - 0.5*cos(2*M_PI*i/winlen) - 0.08*cos(4*M_PI*i/winlen);
//pcm.BufferDrop = false;
return result;
}
//snd_pcm_hw_params_alloca(&hwparams);
std::vector<double> Rect (std::size_t winlen) {
std::vector<double> result(winlen);
double sigma = 0.4;
for (std::size_t i=0; i < winlen; i++)
result[i] = exp(-0.5*((i-(winlen-1)/2)/(sigma*(winlen-1)/2)));
card = -1;
found = false;
if (wanted_dev_name.compare("default") == 0) {
found=true;
} else {
do {
//snd_card_next(&card);
if (card != -1) {
//snd_card_get_name(card,&cardname);
if (strcmp(cardname, wanted_dev_name.c_str()) == 0) {
found=true;
break;
}
return result;
}
std::vector<double> Gauss (std::size_t winlen) {
std::vector<double> result(winlen);
for (std::size_t i=0; i < winlen; i++)
result[i] = 1;
return result;
}
double complexMag (fftw_complex coeff) {
return sqrt(pow(coeff[0],2) + pow(coeff[1],2));
}
guint8 freq2lum (double freq) {
return clip((freq - 1500.0) / (2300.0-1500.0) * 255 + .5);
}
std::vector<double> upsampleLanczos(std::vector<double> orig, int factor) {
std::vector<double> result(orig.size()*factor);
int alpha = 4;
int sinclen = (factor*alpha % 2 == 0 ? factor*alpha+1 : factor*alpha);
double middle = 1500;
std::vector<double> sinc(sinclen);
for (int i=0; i<sinclen; i++) {
double x1 = 1.0*i/factor - alpha/2;
double x2 = 2.0*i/sinclen - 1;
sinc[i] = (x1 == 0 ? 1 : sin(M_PI*x1) / (M_PI*x1)) *
(x2 == 0 ? 1 : sin(M_PI*x2) / (M_PI*x2));
}
for (int i=0; i<orig.size(); i++) {
if (orig[i] != 0) {
for (int j=0; j<sinclen; j++) {
int i_new = i*factor + (j-sinclen/2);
if (i_new > 0 && i_new < result.size())
result[i_new] += (orig[i] - middle) * sinc[j];
}
} while (card != -1);
}
if (!found) {
perror("Device disconnected?\n");
return(-2);
}
if (wanted_dev_name.compare("default") == 0) {
sprintf(pcm_name,"default");
} else {
sprintf(pcm_name,"hw:%d",card);
}
/*if (snd_pcm_open(&pcm.handle, pcm_name, SND_PCM_STREAM_CAPTURE, 0) < 0) {
perror("ALSA: Error opening PCM device");
return(-2);
}*/
/* Init hwparams with full configuration space */
/*if (snd_pcm_hw_params_any(pcm.handle, hwparams) < 0) {
perror("ALSA: Can not configure this PCM device.");
return(-2);
}
if (snd_pcm_hw_params_set_access(pcm.handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
perror("ALSA: Error setting interleaved access.");
return(-2);
}
if (snd_pcm_hw_params_set_format(pcm.handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0) {
perror("ALSA: Error setting format S16_LE.");
return(-2);
}
if (snd_pcm_hw_params_set_rate_near(pcm.handle, hwparams, &exact_rate, 0) < 0) {
perror("ALSA: Error setting sample rate.");
return(-2);
}*/
// Try stereo first
/*if (snd_pcm_hw_params_set_channels(pcm.handle, hwparams, 2) < 0) {
// Fall back to mono
if (snd_pcm_hw_params_set_channels(pcm.handle, hwparams, 1) < 0) {
perror("ALSA: Error setting channels.");
return(-2);
}
}
if (snd_pcm_hw_params(pcm.handle, hwparams) < 0) {
perror("ALSA: Error setting HW params.");
return(-2);
}*/
/*pcm.Buffer = calloc( BUFLEN, sizeof(gint16));
memset(pcm.Buffer, 0, BUFLEN);*/
for (int i=0; i<result.size(); i++)
result[i] += middle;
if (exact_rate != 44100) {
fprintf(stderr, "ALSA: Got %d Hz instead of 44100. Expect artifacts.\n", exact_rate);
return(-1);
}
return(0);
return result;
}

Wyświetl plik

@ -19,6 +19,7 @@
* SubSampling Chroma subsampling mode for YUV (SUBSAMP_444, SUBSAMP_2121,
* SUBSAMP_2112, SUBSAMP_211)
* ColorEnc Color format (COLOR_GBR, COLOR_RGB, COLOR_YUV, COLOR_MONO)
* VISParity Parity mode in VIS (normally PARITY_EVEN - with one exception)
*
*
* All timings are in seconds.
@ -50,7 +51,8 @@ _ModeSpec ModeSpec[] = {
.tLine = 446.446e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR },
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_M2] = { // N7CXI, 2000
.Name = "Martin M2",
@ -65,7 +67,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0.572e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR },
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_M3] = { // KB4YZ, 1999
.Name = "Martin M3",
@ -80,7 +83,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0.572e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR },
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_M4] = { // KB4YZ, 1999
.Name = "Martin M4",
@ -95,7 +99,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0.572e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR },
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_S1] = { // N7CXI, 2000
.Name = "Scottie S1",
@ -110,7 +115,8 @@ _ModeSpec ModeSpec[] = {
.tLine = 428.22e-3,
.SyncOrder = SYNC_SCOTTIE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR },
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_S2] = { // N7CXI, 2000
.Name = "Scottie S2",
@ -125,7 +131,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 1.5e-3,
.SyncOrder = SYNC_SCOTTIE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR },
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_SDX] = { // N7CXI, 2000
.Name = "Scottie DX",
@ -140,7 +147,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 1.5e-3,
.SyncOrder = SYNC_SCOTTIE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR },
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_R72] = { // N7CXI, 2000
.Name = "Robot 72",
@ -155,7 +163,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 6e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_422_YUV,
.ColorEnc = COLOR_YUV },
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_R36] = { // N7CXI, 2000
.Name = "Robot 36",
@ -170,7 +179,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 6e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_420_YUYV,
.ColorEnc = COLOR_YUV },
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_R24] = { // KB4YZ, 1999
.Name = "Robot 24",
@ -185,7 +195,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 6e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_420_YUYV,
.ColorEnc = COLOR_YUV },
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_R24BW] = { // KB4YZ, 1999
.Name = "Robot 24 B/W",
@ -200,7 +211,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_MONO },
.ColorEnc = COLOR_MONO,
.VISParity = PARITY_EVEN },
[MODE_R12BW] = { // KB4YZ, 1999
.Name = "Robot 12 B/W",
@ -215,7 +227,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_MONO },
.ColorEnc = COLOR_MONO,
.VISParity = PARITY_ODD },
[MODE_R8BW] = { // KB4YZ, 1999
.Name = "Robot 8 B/W",
@ -230,7 +243,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_MONO },
.ColorEnc = COLOR_MONO,
.VISParity = PARITY_EVEN },
[MODE_W2120] = { // KB4YZ, 1999
.Name = "Wraase SC-2 120",
@ -245,7 +259,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_RGB },
.ColorEnc = COLOR_RGB,
.VISParity = PARITY_EVEN },
[MODE_W2180] = { // N7CXI, 2000
.Name = "Wraase SC-2 180",
@ -260,7 +275,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_RGB },
.ColorEnc = COLOR_RGB,
.VISParity = PARITY_EVEN },
[MODE_PD50] = { // N7CXI, 2000
.Name = "PD-50",
@ -275,7 +291,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV },
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_PD90] = { // N7CXI, 2000
.Name = "PD-90",
@ -290,7 +307,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV },
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_PD120] = { // N7CXI, 2000
.Name = "PD-120",
@ -305,7 +323,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV },
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_PD160] = { // N7CXI, 2000
.Name = "PD-160",
@ -320,7 +339,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV },
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_PD180] = { // N7CXI, 2000
.Name = "PD-180",
@ -335,7 +355,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV },
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_PD240] = { // N7CXI, 2000
.Name = "PD-240",
@ -350,7 +371,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV },
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_PD290] = { // N7CXI, 2000
.Name = "PD-290",
@ -365,7 +387,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV },
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_P3] = { // N7CXI, 2000
.Name = "Pasokon P3",
@ -380,7 +403,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 1.042e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_RGB },
.ColorEnc = COLOR_RGB,
.VISParity = PARITY_EVEN },
[MODE_P5] = { // N7CXI, 2000
.Name = "Pasokon P5",
@ -395,7 +419,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 1.563e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_RGB },
.ColorEnc = COLOR_RGB,
.VISParity = PARITY_EVEN },
[MODE_P7] = { // N7CXI, 2000
.Name = "Pasokon P7",
@ -410,7 +435,8 @@ _ModeSpec ModeSpec[] = {
.tSep = 2.083e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_RGB }
.ColorEnc = COLOR_RGB,
.VISParity = PARITY_EVEN }
};

Wyświetl plik

@ -1,14 +1,32 @@
#include <getopt.h>
#include "common.hh"
int main(int argc, char *argv[]) {
std::string confpath(std::string(getenv("HOME")) + "/.config/slowrx/slowrx.ini");
config.load_from_file(confpath);
DSPworker dsp;
int opt_char;
while ((opt_char = getopt (argc, argv, "t:f:")) != EOF)
switch (opt_char) {
case 't':
runTest(optarg);
return(0);
break;
case 'f':
dsp.openAudioFile(optarg);
break;
}
//dsp.openAudioFile("/Users/windy/Audio/sig/sstv/scottie2-01-noiseonly.wav");
dsp.openPortAudio();
upsampleLanczos({0},10);
if (!dsp.is_open())
dsp.openPortAudio();
GetVideo(GetVIS(&dsp), &dsp);
//SlowGUI gui = SlowGUI();

Wyświetl plik

@ -1,126 +1,52 @@
#include "common.hh"
/* Find the slant angle of the sync singnal and adjust sample rate to cancel it out
* Length: number of PCM samples to process
* Mode: one of M1, M2, S1, S2, R72, R36 ...
* Rate: approximate sampling rate used
* Skip: pointer to variable where the skip amount will be returned
* returns adjusted sample rate
*
*/
double FindSync (SSTVMode Mode, double Rate, int *Skip) {
int LineWidth = ModeSpec[Mode].tLine / ModeSpec[Mode].tSync * 4;
int x,y;
int q, d, qMost, dMost;
gushort xAcc[700] = {0};
gushort lines[600][(MAXSLANT-MINSLANT)*2];
gushort cy, cx, Retries = 0;
bool SyncImg[700][630] = {{FALSE}};
double t=0, slantAngle, s;
double ConvoFilter[8] = { 1,1,1,1,-1,-1,-1,-1 };
double convd, maxconvd=0;
int xmax=0;
// Repeat until slant < 0.5° or until we give up
while (TRUE) {
// Draw the 2D sync signal at current rate
for (y=0; y<ModeSpec[Mode].NumLines; y++) {
for (x=0; x<LineWidth; x++) {
t = (y + 1.0*x/LineWidth) * ModeSpec[Mode].tLine;
SyncImg[x][y] = HasSync[ (int)( t * Rate / 13.0) ];
}
}
/** Linear Hough transform **/
dMost = qMost = 0;
memset(lines, 0, sizeof(lines[0][0]) * (MAXSLANT-MINSLANT)*2 * 600);
// Find white pixels
for (cy = 0; cy < ModeSpec[Mode].NumLines; cy++) {
for (cx = 0; cx < LineWidth; cx++) {
if (SyncImg[cx][cy]) {
// Slant angles to consider
for (q = MINSLANT*2; q < MAXSLANT*2; q ++) {
// Line accumulator
d = LineWidth + round( -cx * sin(deg2rad(q/2.0)) + cy * cos(deg2rad(q/2.0)) );
if (d > 0 && d < LineWidth) {
lines[d][q-MINSLANT*2] ++;
if (lines[d][q-MINSLANT*2] > lines[dMost][qMost-MINSLANT*2]) {
dMost = d;
qMost = q;
}
}
}
}
}
}
if ( qMost == 0) {
printf(" no sync signal; giving up\n");
break;
}
slantAngle = qMost / 2.0;
printf(" %.1f° (d=%d) @ %.1f Hz", slantAngle, dMost, Rate);
// Adjust sample rate
Rate += tan(deg2rad(90 - slantAngle)) / LineWidth * Rate;
if (slantAngle > 89 && slantAngle < 91) {
printf(" slant OK :)\n");
break;
} else if (Retries == 3) {
printf(" still slanted; giving up\n");
Rate = 44100;
printf(" -> 44100\n");
break;
}
printf(" -> %.1f recalculating\n", Rate);
Retries ++;
}
// accumulate a 1-dim array of the position of the sync pulse
memset(xAcc, 0, sizeof(xAcc[0]) * 700);
for (y=0; y<ModeSpec[Mode].NumLines; y++) {
for (x=0; x<700; x++) {
t = y * ModeSpec[Mode].tLine + x/700.0 * ModeSpec[Mode].tLine;
xAcc[x] += HasSync[ (int)(t / (13.0/44100) * Rate/44100) ];
}
}
// find falling edge of the sync pulse by 8-point convolution
for (x=0;x<700-8;x++) {
convd = 0;
for (int i=0;i<8;i++) convd += xAcc[x+i] * ConvoFilter[i];
if (convd > maxconvd) {
maxconvd = convd;
xmax = x+4;
}
}
// If pulse is near the right edge of the image, it just probably slipped
// out the left edge
if (xmax > 350) xmax -= 350;
// Skip until the start of the line
s = xmax / 700.0 * ModeSpec[Mode].tLine - ModeSpec[Mode].tSync;
// (Scottie modes don't start lines with sync)
if (ModeSpec[Mode].SyncOrder == SYNC_SCOTTIE)
s = s - ModeSpec[Mode].tPixel * ModeSpec[Mode].ImgWidth / 2.0
+ ModeSpec[Mode].tPorch * 2;
*Skip = s * Rate;
printf("will return %.2f\n",Rate);
return (Rate);
double findSyncHough (SSTVMode Mode, std::vector<bool> has_sync) {
}
void findSyncRansac(SSTVMode Mode, std::vector<bool> has_sync) {
int line_width = ModeSpec[Mode].NumLines;//ModeSpec[Mode].tLine / ModeSpec[Mode].tSync * 4;
std::vector<std::pair<int,int> > sync_pixels;
for (int y=0; y<ModeSpec[Mode].NumLines; y++) {
for (int x=0; x<line_width; x++) {
if (y+x>0 && has_sync[y*line_width + x] && !has_sync[y*line_width + x - 1]) {
sync_pixels.push_back({x,y});
printf("%d,%d\n",x,y);
}
}
}
std::pair<std::pair<int,int>, std::pair<int,int> > best_line;
double best_dist = -1;
int it_num = 0;
while (++it_num < 300) {
std::random_shuffle(sync_pixels.begin(), sync_pixels.end());
std::pair<std::pair<int,int>, std::pair<int,int> > test_line = {sync_pixels[0], sync_pixels[1]};
int x0 = test_line.first.first;
int y0 = test_line.first.second;
int x1 = test_line.second.first;
int y1 = test_line.second.second;
double total_sq_dist = 0;
int total_good = 0;
for(std::pair<int,int> pixel : sync_pixels) {
int x = pixel.first;
int y = pixel.second;
// Point distance to line
double d = 1.0 * ((y0-y1)*x + (x1-x0)*y + (x0*y1 - x1*y0)) / sqrt(pow(x1-x0,2) + pow(y1-y0,2));
if (fabs(d) < 6 || fabs(d-line_width) < 6) {
total_good ++;//+= sqrt(fabs(d));
}
}
if (best_dist < 0 || total_good > best_dist) {
best_line = test_line;
best_dist = total_good;
printf("(it. %d) for (%d,%d) (%d,%d) total_sq_dist=%f, total_good=%d\n",it_num,x0,y0,x1,y1,total_sq_dist,total_good);
}
}
}

Wyświetl plik

@ -169,13 +169,20 @@ bool GetVideo(SSTVMode Mode, DSPworker* dsp) {
_ModeSpec s = ModeSpec[Mode];
guint8 Image[800][800][3];
guint8 Imagesnr[800][800][3];
Glib::RefPtr<Gtk::Application> app = Gtk::Application::create("com.windytan.slowrx");
// Initialize pixbuffer
Glib::RefPtr<Gdk::Pixbuf> pixbuf_rx;
pixbuf_rx = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, s.ScanPixels, s.NumLines);
pixbuf_rx->fill(0x000000ff);
Glib::RefPtr<Gdk::Pixbuf> pixbuf_snr;
pixbuf_snr = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, s.ScanPixels, s.NumLines);
pixbuf_snr->fill(0x000000ff);
double next_sync_sample_time = 0;
std::vector<bool> has_sync;
/*g_object_unref(pixbuf_disp);
pixbuf_disp = gdk_pixbuf_scale_simple(pixbuf_rx, 500,
@ -192,119 +199,72 @@ bool GetVideo(SSTVMode Mode, DSPworker* dsp) {
double t = 0;
for (int PixelIdx = 0; PixelIdx < PixelGrid.size(); PixelIdx++) {
double Lum = 0;
/*printf("expecting %d (%d,%d,%d)\n",PixelIdx,
PixelGrid[PixelIdx].X,
PixelGrid[PixelIdx].Y,
PixelGrid[PixelIdx].Channel
);*/
while (t < PixelGrid[PixelIdx].Time && dsp->is_still_listening()) {
while (t < PixelGrid[PixelIdx].Time && dsp->is_open()) {
t += dsp->forward();
}
/*if (dsp->is_open()) {
printf("got it\n");
} else {
printf("didn't get it\n");
}*/
/*** Store the sync band for later adjustments ***/
/*if (SampleNum == NextSyncTime) {
if (dsp->get_t() >= next_sync_sample_time) {
Praw = Psync = 0;
int line_width = ModeSpec[Mode].NumLines;
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;
std::vector<double> bands = dsp->bandPowerPerHz({{1150,1250}, {1500,2300}});
// 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);
has_sync.push_back(bands[0] > bands[1]);
NextSyncTime += 13;
SyncSampleNum ++;
}*/
next_sync_sample_time += ModeSpec[Mode].tLine / line_width;
}
/*** Estimate SNR ***/
double SNR;
bool Adaptive = true;
if (PixelIdx == 0 || PixelGrid[PixelIdx].X == s.ScanPixels/2) {
std::vector<double> bands = dsp->getBandPowerPerHz({{400,800}, {1500,2300}, {2700, 3400}});
if (PixelIdx == 0 || (Adaptive && PixelGrid[PixelIdx].X == s.ScanPixels/2)) {
std::vector<double> bands = dsp->bandPowerPerHz({{300,1100}, {1500,2300}, {2500, 2700}});
double Pvideo_plus_noise = bands[1];
double Pnoise_only = (bands[0] + bands[2]) / 2;
double Psignal = Pvideo_plus_noise - Pnoise_only;
SNR = ((Psignal / Pnoise_only < .01) ? -20 : 10 * log10(Psignal / Pnoise_only));
SNR = ((Pnoise_only == 0 || Psignal / Pnoise_only < .01) ? -20 : 10 * log10(Psignal / Pnoise_only));
}
/*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 ***/
//PrevFreq = Freq;
// Adapt window size to SNR
bool Adaptive = true;
WindowType WinType;
if (Adaptive) WinType = dsp->getBestWindowFor(Mode, SNR);
else WinType = dsp->getBestWindowFor(Mode);
if (Adaptive) WinType = dsp->bestWindowFor(Mode, SNR);
else WinType = dsp->bestWindowFor(Mode);
double Freq = dsp->getPeakFreq(1500, 2300, WinType);
double Freq = dsp->peakFreq(1500, 2300, WinType);
// Linear interpolation of (chronologically) intermediate frequencies, for redrawing
//InterpFreq = PrevFreq + (Freq-PrevFreq) * ... // TODO!
// Calculate luminency & store for later use
Lum = clip((Freq - (1500)) / 3.1372549);
guint8 Lum = freq2lum(Freq);
//measured.push_back({t, Lum});
//StoredLum[SampleNum] = clip((Freq - (1500 + CurrentPic.HedrShift)) / 3.1372549);
@ -314,11 +274,15 @@ bool GetVideo(SSTVMode Mode, DSPworker* dsp) {
// Store pixel
Image[x][y][Channel] = Lum;//StoredLum[SampleNum];
Imagesnr[x][y][Channel] = WinType;//StoredLum[SampleNum];
}
// Calculate and draw pixels to pixbuf on line change
/* sync */
findSyncRansac(Mode, has_sync);
toPixbufRGB(Image, pixbuf_rx, Mode);
toPixbufRGB(Imagesnr, pixbuf_snr, Mode);
/*if (!Redraw || y % 5 == 0 || PixelIdx == PixelGrid.size()-1) {
// Scale and update image
g_object_unref(pixbuf_disp);
@ -329,11 +293,8 @@ bool GetVideo(SSTVMode Mode, DSPworker* dsp) {
}*/
/*if (!Redraw && SampleNum % 8820 == 0) {
setVU(Power, FFTLen, WinIdx, true);
}*/
pixbuf_rx->save("testi.png", "png");
pixbuf_snr->save("snr.png", "png");
return true;

Wyświetl plik

@ -3,135 +3,116 @@
/*
*
* Detect VIS & frequency shift
*
* Each bit lasts 30 ms (1323 samples)
* Detect Robot header & VIS
*
*/
SSTVMode GetVIS (DSPworker *dsp) {
double dt = 5e-3;
int bitlen = 30e-3 / dt;
int upsample_factor = 10;
int selmode, ptr=0;
int vis = 0, Parity = 0, HedrPtr = 0;
double HedrBuf[100] = {0}, tone[100] = {0};
int vis = 0, Parity = 0, ReadPtr = 0;
std::vector<double> HedrCirBuf(bitlen*22);
std::vector<double> delta_f(bitlen*22);
std::vector<double> tone(bitlen*22);
bool gotvis = false;
unsigned Bit[8] = {0}, ParityBit = 0;
//for (i = 0; i < FFTLen; i++) fft.in[i] = 0;
// Create 20ms Hann window
//for (i = 0; i < 882; i++) Hann[i] = 0.5 * (1 - cos( (2 * M_PI * (double)i) / 881 ) );
printf("bitlen=%d\n",bitlen);
//ManualActivated = false;
printf("Waiting for header\n");
while ( dsp->is_still_listening() ) {
while ( dsp->is_open() ) {
//if (Abort || ManualResync) return(0);
HedrCirBuf[ReadPtr] = dsp->peakFreq(500, 3300, WINDOW_HANN1023);
HedrBuf[HedrPtr] = dsp->getPeakFreq(500, 3300, WINDOW_HANN1023);
dsp->forward_ms(10);
//printf("%f,%f\n",dsp->get_t(),HedrCirBuf[ReadPtr]);
// Header buffer holds 45 * 10 msec = 450 msec
HedrPtr = (HedrPtr + 1) % 45;
// Frequencies in the last 450 msec
for (int i = 0; i < 45; i++) tone[i] = HedrBuf[(HedrPtr + i) % 45];
delta_f[0] = HedrCirBuf[(ReadPtr+1) % HedrCirBuf.size()];
for (int i = 1; i < HedrCirBuf.size(); i++) {
delta_f[i] = HedrCirBuf[(ReadPtr + 1 + i) % HedrCirBuf.size()] - delta_f[0];
}
// Is there a pattern that looks like (the end of) a calibration header + VIS?
// Tolerance ±25 Hz
CurrentPic.HedrShift = 0;
gotvis = false;
for (int i = 0; i < 3; i++) {
//if (CurrentPic.HedrShift != 0) break;
for (int j = 0; j < 3; j++) {
if ( (tone[1*3+i] > tone[0+j] - 25 && tone[1*3+i] < tone[0+j] + 25) && // 1900 Hz leader
(tone[2*3+i] > tone[0+j] - 25 && tone[2*3+i] < tone[0+j] + 25) && // 1900 Hz leader
(tone[3*3+i] > tone[0+j] - 25 && tone[3*3+i] < tone[0+j] + 25) && // 1900 Hz leader
(tone[4*3+i] > tone[0+j] - 25 && tone[4*3+i] < tone[0+j] + 25) && // 1900 Hz leader
(tone[5*3+i] > tone[0+j] - 725 && tone[5*3+i] < tone[0+j] - 675) && // 1200 Hz start bit
// ...8 VIS bits...
(tone[14*3+i] > tone[0+j] - 725 && tone[14*3+i] < tone[0+j] - 675) // 1200 Hz stop bit
) {
double freq_margin = 25;
// Attempt to read VIS
if ( fabs(delta_f[4*bitlen]) < freq_margin && fabs(delta_f[6*bitlen]) < freq_margin &&
fabs(delta_f[8*bitlen]) < freq_margin && fabs(delta_f[10*bitlen]) < freq_margin &&
fabs(delta_f[12*bitlen]+(1900-1200)) < freq_margin &&
fabs(delta_f[21*bitlen]+(1900-1200)) < freq_margin ) {
gotvis = true;
for (int k = 0; k < 8; k++) {
if (tone[6*3+i+3*k] > tone[0+j] - 625 && tone[6*3+i+3*k] < tone[0+j] - 575) Bit[k] = 0;
else if (tone[6*3+i+3*k] > tone[0+j] - 825 && tone[6*3+i+3*k] < tone[0+j] - 775) Bit[k] = 1;
else { // erroneous bit
gotvis = false;
break;
}
double fshift = ((delta_f[0] - 1900) + delta_f[4*bitlen] + delta_f[6*bitlen] + delta_f[8*bitlen] +
delta_f[10*bitlen]) / 5.0;
printf("header! t=%f, fshift=%+.1f Hz\n",dsp->get_t(),fshift);
// hi-res zero-crossing search
std::vector<double> abs_f(delta_f.size());
for (int i=1; i<abs_f.size(); i++)
abs_f[i] += delta_f[0] + delta_f[i];
std::vector<double> header_interp = upsampleLanczos(abs_f, upsample_factor);
int n_zc = 0;
double t_zc1=0, t_zc2=0;
double s=0,s0=0;
for (int i=0; i<header_interp.size(); i++) {
s = header_interp[i] - fshift - (1200.0+(1900.0-1200.0)/2.0);
double t = dsp->get_t() - (header_interp.size()-1-i)*dt/upsample_factor;
//printf("%f,%f\n",t,s);
if (i*dt/upsample_factor>15e-3 && s * s0 < 0) {
if (n_zc == 1) {
t_zc1 = t - dt + ((-s0)/(s-s0))*dt/upsample_factor;
}
if (gotvis) {
CurrentPic.HedrShift = tone[0+j] - 1900;
vis = Bit[0] + (Bit[1] << 1) + (Bit[2] << 2) + (Bit[3] << 3) + (Bit[4] << 4) +
(Bit[5] << 5) + (Bit[6] << 6);
ParityBit = Bit[7];
printf(" VIS %d (%02Xh) @ %+d Hz\n", vis, vis, CurrentPic.HedrShift);
Parity = Bit[0] ^ Bit[1] ^ Bit[2] ^ Bit[3] ^ Bit[4] ^ Bit[5] ^ Bit[6];
if (vis2mode.find(vis) == vis2mode.end()) {
printf(" Unknown VIS\n");
gotvis = false;
} else if (Parity != ParityBit && vis2mode[vis] != MODE_R12BW) {
printf(" Parity fail\n");
gotvis = false;
} else {
//gtk_combo_box_set_active (GTK_COMBO_BOX(gui.combo_mode), VISmap[VIS]-1);
//gtk_spin_button_set_value (GTK_SPIN_BUTTON(gui.spin_shift), CurrentPic.HedrShift);
break;
}
if (n_zc == 2) {
t_zc2 = t - dt + ((-s0)/(s-s0))*dt/upsample_factor;
break;
}
n_zc++;
}
s0 = s;
}
/*printf ("n_zc=%d, t_zc1=%f, t_zc2=%fi (dur = %.05f ms, error = %.03f%%)\n",n_zc,t_zc1,t_zc2,
(t_zc2-t_zc1)*1e3,
(t_zc2 - t_zc1) / 300e-3 * 100 - 100
);*/
int parity_rx=0;
for (int k=0; k<8; k++) {
if (abs_f[(13+k)*bitlen]+fshift < 1200) {
vis |= (1 << k);
parity_rx ++;
}
}
if (gotvis) break;
}
if (gotvis) break;
vis &= 0x7f;
//if (gotvis)
//if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui.tog_rx))) break;
// Manual start
/*if (ManualActivated) {
gtk_widget_set_sensitive( gui.frame_manual, false );
gtk_widget_set_sensitive( gui.combo_card, false );
selmode = gtk_combo_box_get_active (GTK_COMBO_BOX(gui.combo_mode)) + 1;
CurrentPic.HedrShift = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON(gui.spin_shift));
VIS = 0;
for (i=0; i<0x80; i++) {
if (VISmap[i] == selmode) {
VIS = i;
printf("VIS: %dd (%02Xh) @ %+f Hz\n", vis, vis, fshift);
if (vis2mode.find(vis) == vis2mode.end()) {
printf("Unknown VIS\n");
gotvis = false;
} else {
if ((parity_rx % 2) != ModeSpec[vis2mode[vis]].VISParity) {
printf("Parity fail\n");
gotvis = false;
} else {
printf("%s\n",ModeSpec[vis2mode[vis]].Name.c_str());
double start_of_video = t_zc2 + 10 * 30e-3;
dsp->forward_to_time(start_of_video);
break;
}
}
break;
}
if (++ptr == 10) {
setVU(Power, 2048, 6, false);
ptr = 0;
}*/
ReadPtr = (ReadPtr+1) % HedrCirBuf.size();
dsp->forward_time(dt);
}
// Skip the rest of the stop bit
dsp->forward_ms(12); // TODO hack
if (vis2mode.find(vis) != vis2mode.end()) {
return vis2mode[vis];
}
return vis2mode[vis];
printf(" No VIS found\n");
return MODE_UNKNOWN;
}