kopia lustrzana https://github.com/windytan/slowrx
working sync align, chroma filtering, etc.
rodzic
122a0add97
commit
bc17b64b6d
|
@ -1,4 +1,4 @@
|
|||
bin_PROGRAMS = slowrx
|
||||
slowrx_CPPFLAGS = -g $(GTKMM_CFLAGS) @SNDFILE_CFLAGS@ $(PORTAUDIO_CFLAGS)
|
||||
slowrx_CPPFLAGS = -Wall -Wextra -pedantic -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 header.cc video.cc sync.cc tests.cc
|
||||
|
|
|
@ -3,11 +3,8 @@
|
|||
|
||||
bool Abort = false;
|
||||
bool Adaptive = true;
|
||||
bool *HasSync = NULL;
|
||||
uint16_t HedrShift = 0;
|
||||
bool ManualActivated = false;
|
||||
bool ManualResync = false;
|
||||
int *StoredLum = NULL;
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf_PWR;
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf_SNR;
|
||||
|
@ -18,16 +15,19 @@ Glib::KeyFile config;
|
|||
|
||||
std::vector<std::thread> threads(2);
|
||||
|
||||
PicMeta CurrentPic;
|
||||
|
||||
std::vector<std::vector<double> > DSPworker::window_ (16);
|
||||
|
||||
// Clip to [0..255]
|
||||
int clip (double a) {
|
||||
guint8 clip (double a) {
|
||||
if (a < 0) return 0;
|
||||
else if (a > 255) return 255;
|
||||
return round(a);
|
||||
}
|
||||
double fclip (double a) {
|
||||
if (a < 0.0) return 0.0;
|
||||
else if (a > 1.0) return 1.0;
|
||||
return a;
|
||||
}
|
||||
|
||||
// Convert degrees -> radians
|
||||
double deg2rad (double Deg) {
|
||||
|
@ -97,15 +97,15 @@ void evt_AbortRx() {
|
|||
}
|
||||
|
||||
// Another device selected from list
|
||||
void evt_changeDevices() {
|
||||
void evt_changeDevices() {/*
|
||||
|
||||
int status;
|
||||
|
||||
Abort = true;
|
||||
|
||||
static int init;
|
||||
/*if (init)
|
||||
threads[0].join;*/
|
||||
if (init)
|
||||
threads[0].join;
|
||||
init = 1;
|
||||
|
||||
// if (pcm.handle != NULL) snd_pcm_close(pcm.handle);
|
||||
|
@ -130,7 +130,7 @@ void evt_changeDevices() {
|
|||
|
||||
//config->set_string("slowrx","device",gui.combo_card->get_active_text());
|
||||
|
||||
//threads[0] = thread(Listen);
|
||||
//threads[0] = thread(Listen);*/
|
||||
|
||||
}
|
||||
|
||||
|
@ -145,13 +145,13 @@ void evt_clearPix() {
|
|||
|
||||
// Manual slant adjust
|
||||
void evt_clickimg(Gtk::Widget *widget, GdkEventButton* event, Gdk::WindowEdge edge) {
|
||||
static double prevx=0,prevy=0,newrate;
|
||||
/*static double prevx=0,prevy=0,newrate;
|
||||
static bool secondpress=false;
|
||||
double x,y,dx,dy,xic;
|
||||
|
||||
(void)widget;
|
||||
(void)edge;
|
||||
/*
|
||||
|
||||
if (event->type == Gdk::BUTTON_PRESS && event->button == 1 && gui.tog_setedge->get_active()) {
|
||||
|
||||
x = event->x * (ModeSpec[CurrentPic.Mode].ImgWidth / 500.0);
|
||||
|
|
109
src/common.hh
109
src/common.hh
|
@ -19,13 +19,13 @@
|
|||
struct Point {
|
||||
int x;
|
||||
int y;
|
||||
explicit Point(int x = 0.0, int y=0.0) : x(x), y(y) {}
|
||||
explicit Point(int _x = 0, int _y=0) : x(_x), y(_y) {}
|
||||
};
|
||||
|
||||
struct Tone {
|
||||
double dur;
|
||||
double freq;
|
||||
explicit Tone(double dur = 0.0, double freq=0.0) : dur(dur), freq(freq) {}
|
||||
explicit Tone(double _dur = 0.0, double _freq=0.0) : dur(_dur), freq(_freq) {}
|
||||
};
|
||||
|
||||
using Wave = std::vector<double>;
|
||||
|
@ -75,6 +75,39 @@ enum eVISParity {
|
|||
|
||||
extern std::map<int, SSTVMode> vis2mode;
|
||||
|
||||
typedef struct ModeSpec {
|
||||
std::string Name;
|
||||
std::string ShortName;
|
||||
double tSync;
|
||||
double tPorch;
|
||||
double tSep;
|
||||
double tScan;
|
||||
double tLine;
|
||||
size_t ScanPixels;
|
||||
size_t NumLines;
|
||||
size_t HeaderLines;
|
||||
eColorEnc ColorEnc;
|
||||
eSyncOrder SyncOrder;
|
||||
eSubSamp SubSampling;
|
||||
eVISParity VISParity;
|
||||
} _ModeSpec;
|
||||
|
||||
extern _ModeSpec ModeSpec[];
|
||||
|
||||
|
||||
struct Picture {
|
||||
SSTVMode mode;
|
||||
Wave video_signal;
|
||||
double video_dt;
|
||||
std::vector<bool> sync_signal;
|
||||
double sync_dt;
|
||||
double speed;
|
||||
double starts_at;
|
||||
explicit Picture(SSTVMode _mode)
|
||||
: mode(_mode), video_dt(ModeSpec[_mode].tScan/ModeSpec[_mode].ScanPixels/2),
|
||||
sync_dt(ModeSpec[_mode].tLine / ModeSpec[_mode].ScanPixels/3), speed(1) {}
|
||||
};
|
||||
|
||||
class DSPworker {
|
||||
|
||||
public:
|
||||
|
@ -88,15 +121,20 @@ class DSPworker {
|
|||
double forward ();
|
||||
double forward_time (double);
|
||||
void forward_to_time (double);
|
||||
void set_fshift (double);
|
||||
|
||||
void windowedMoment (WindowType, fftw_complex *);
|
||||
double peakFreq (double, double, WindowType);
|
||||
int freq2bin (double, int);
|
||||
std::vector<double> bandPowerPerHz (std::vector<std::vector<double> >);
|
||||
std::vector<double> bandPowerPerHz (std::vector<std::vector<double> >, WindowType wintype=WINDOW_HANN2047);
|
||||
WindowType bestWindowFor (SSTVMode, double SNR=99);
|
||||
double videoSNR();
|
||||
double lum(SSTVMode, bool is_adaptive=false);
|
||||
|
||||
bool is_open ();
|
||||
bool is_open ();
|
||||
double get_t ();
|
||||
bool isLive();
|
||||
bool hasSync();
|
||||
|
||||
private:
|
||||
|
||||
|
@ -106,16 +144,22 @@ class DSPworker {
|
|||
int cirbuf_tail_;
|
||||
int cirbuf_fill_count_;
|
||||
bool please_stop_;
|
||||
short *read_buffer_;
|
||||
SndfileHandle file_;
|
||||
fftw_complex *fft_inbuf_;
|
||||
fftw_complex *fft_outbuf_;
|
||||
fftw_plan fft_plan_small_;
|
||||
fftw_plan fft_plan_big_;
|
||||
double samplerate_;
|
||||
size_t num_chans_;
|
||||
PaStream *pa_stream_;
|
||||
eStreamType stream_type_;
|
||||
bool is_open_;
|
||||
double t_;
|
||||
double fshift_;
|
||||
double next_snr_time_;
|
||||
double SNR_;
|
||||
WindowType sync_window_;
|
||||
|
||||
static std::vector<std::vector<double> > window_;
|
||||
};
|
||||
|
@ -173,44 +217,22 @@ extern Gtk::ListStore *savedstore;
|
|||
extern Glib::KeyFile config;
|
||||
|
||||
|
||||
typedef struct _PicMeta PicMeta;
|
||||
struct _PicMeta {
|
||||
int HedrShift;
|
||||
int Mode;
|
||||
double Rate;
|
||||
int Skip;
|
||||
Gdk::Pixbuf *thumbbuf;
|
||||
char timestr[40];
|
||||
};
|
||||
extern PicMeta CurrentPic;
|
||||
typedef struct {
|
||||
Point pt;
|
||||
int Channel;
|
||||
double Time;
|
||||
} PixelSample;
|
||||
|
||||
|
||||
typedef struct ModeSpec {
|
||||
std::string Name;
|
||||
std::string ShortName;
|
||||
double tSync;
|
||||
double tPorch;
|
||||
double tSep;
|
||||
double tScan;
|
||||
double tLine;
|
||||
int ScanPixels;
|
||||
int NumLines;
|
||||
int HeaderLines;
|
||||
eColorEnc ColorEnc;
|
||||
eSyncOrder SyncOrder;
|
||||
eSubSamp SubSampling;
|
||||
eVISParity VISParity;
|
||||
} _ModeSpec;
|
||||
|
||||
extern _ModeSpec ModeSpec[];
|
||||
std::vector<PixelSample> getPixelSamplingPoints(SSTVMode mode);
|
||||
|
||||
double power (fftw_complex coeff);
|
||||
int clip (double a);
|
||||
guint8 clip (double a);
|
||||
double fclip (double a);
|
||||
void createGUI ();
|
||||
double deg2rad (double Deg);
|
||||
std::string GetFSK ();
|
||||
bool GetVideo (SSTVMode Mode, DSPworker *dsp);
|
||||
SSTVMode modeFromNextHeader (DSPworker*);
|
||||
bool rxVideo (SSTVMode Mode, DSPworker *dsp);
|
||||
SSTVMode nextHeader (DSPworker*);
|
||||
int initPcmDevice (std::string);
|
||||
void *Listen ();
|
||||
void populateDeviceList ();
|
||||
|
@ -218,25 +240,28 @@ 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>);
|
||||
double findSyncAutocorr (SSTVMode, std::vector<bool>);
|
||||
void resync (Picture* pic);
|
||||
double gaussianPeak (double y1, double y2, double y3);
|
||||
Wave upsampleLanczos (Wave orig, int factor, double middle=1500, int a=3);
|
||||
std::tuple<bool,double,double> findMelody (Wave, Melody, double);
|
||||
std::vector<int> readFSK (DSPworker*, double, double, double, size_t);
|
||||
SSTVMode readVIS (DSPworker*, double fshift=0);
|
||||
Wave upsampleLanczos (Wave orig, int factor, size_t a=3);
|
||||
Wave Hann (std::size_t);
|
||||
Wave Blackmann (std::size_t);
|
||||
Wave Rect (std::size_t);
|
||||
Wave Gauss (std::size_t);
|
||||
|
||||
Wave deriv (Wave);
|
||||
Wave peaks (Wave, int);
|
||||
Wave derivPeaks (Wave, int);
|
||||
Wave peaks (Wave, size_t);
|
||||
Wave derivPeaks (Wave, size_t);
|
||||
Wave rms (Wave, int);
|
||||
void runTest(const char*);
|
||||
|
||||
double complexMag (fftw_complex coeff);
|
||||
guint8 freq2lum(double);
|
||||
|
||||
void renderPixbuf(Picture* pic);
|
||||
|
||||
void printWave(Wave, double);
|
||||
|
||||
void evt_AbortRx ();
|
||||
|
|
255
src/dsp.cc
255
src/dsp.cc
|
@ -49,10 +49,12 @@ DSPworker::DSPworker() : Mutex(), please_stop_(false) {
|
|||
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_head_ = MOMENT_LEN/2;
|
||||
cirbuf_fill_count_ = 0;
|
||||
is_open_ = false;
|
||||
fshift_ = 0;
|
||||
t_ = 0;
|
||||
sync_window_ = WINDOW_HANN511;
|
||||
|
||||
}
|
||||
|
||||
|
@ -60,23 +62,24 @@ void DSPworker::openAudioFile (std::string fname) {
|
|||
|
||||
if (!is_open_) {
|
||||
|
||||
fprintf (stderr,"Open '%s'\n", fname.c_str()) ;
|
||||
fprintf (stderr,"open '%s'\n", fname.c_str()) ;
|
||||
file_ = SndfileHandle(fname.c_str()) ;
|
||||
|
||||
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 ()) ;
|
||||
fprintf (stderr," opened @ %d Hz, %d ch\n", file_.samplerate(), file_.channels()) ;
|
||||
|
||||
samplerate_ = file_.samplerate();
|
||||
|
||||
stream_type_ = STREAM_TYPE_FILE;
|
||||
|
||||
t_ = 0;
|
||||
num_chans_ = file_.channels();
|
||||
read_buffer_ = new short [READ_CHUNK_LEN * num_chans_];
|
||||
is_open_ = true;
|
||||
readMore();
|
||||
|
||||
/* RAII takes care of destroying SndfileHandle object. */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +87,8 @@ void DSPworker::openAudioFile (std::string fname) {
|
|||
void DSPworker::openPortAudio () {
|
||||
|
||||
if (!is_open_) {
|
||||
fprintf(stderr,"open PortAudio\n");
|
||||
|
||||
Pa_Initialize();
|
||||
|
||||
PaStreamParameters inputParameters;
|
||||
|
@ -106,24 +111,33 @@ void DSPworker::openPortAudio () {
|
|||
NULL, /* no callback, use blocking API */
|
||||
NULL ); /* no callback, so no callback userData */
|
||||
|
||||
if (err == paNoError)
|
||||
printf("opened %s\n",devinfo->name);
|
||||
err = Pa_StartStream( pa_stream_ );
|
||||
if (err == paNoError)
|
||||
printf("stream started\n");
|
||||
if (err == paNoError) {
|
||||
err = Pa_StartStream( pa_stream_ );
|
||||
|
||||
const PaStreamInfo *streaminfo;
|
||||
streaminfo = Pa_GetStreamInfo(pa_stream_);
|
||||
samplerate_ = streaminfo->sampleRate;
|
||||
printf("%f\n",samplerate_);
|
||||
const PaStreamInfo *streaminfo;
|
||||
streaminfo = Pa_GetStreamInfo(pa_stream_);
|
||||
samplerate_ = streaminfo->sampleRate;
|
||||
fprintf(stderr," opened '%s' @ %.1f\n",devinfo->name,samplerate_);
|
||||
|
||||
stream_type_ = STREAM_TYPE_PA;
|
||||
stream_type_ = STREAM_TYPE_PA;
|
||||
num_chans_ = 1;
|
||||
read_buffer_ = new short [READ_CHUNK_LEN * num_chans_];
|
||||
|
||||
is_open_ = true;
|
||||
readMore();
|
||||
if (err == paNoError) {
|
||||
is_open_ = true;
|
||||
readMore();
|
||||
} else {
|
||||
fprintf(stderr," error at Pa_StartStream\n");
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr," error\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DSPworker::set_fshift(double fshift) {
|
||||
fshift_ = fshift;
|
||||
}
|
||||
|
||||
int DSPworker::freq2bin (double freq, int fft_len) {
|
||||
return (freq / samplerate_ * fft_len);
|
||||
|
@ -137,28 +151,30 @@ double DSPworker::get_t() {
|
|||
return t_;
|
||||
}
|
||||
|
||||
bool DSPworker::isLive() {
|
||||
return (stream_type_ == STREAM_TYPE_PA);
|
||||
}
|
||||
|
||||
void DSPworker::readMore () {
|
||||
int numchans = file_.channels();
|
||||
short read_buffer[READ_CHUNK_LEN * numchans];
|
||||
sf_count_t samplesread = 0;
|
||||
|
||||
if (is_open_) {
|
||||
if (stream_type_ == STREAM_TYPE_FILE) {
|
||||
|
||||
samplesread = file_.readf(read_buffer, READ_CHUNK_LEN);
|
||||
samplesread = file_.readf(read_buffer_, READ_CHUNK_LEN);
|
||||
if (samplesread < READ_CHUNK_LEN)
|
||||
is_open_ = false;
|
||||
|
||||
if (numchans > 1) {
|
||||
if (num_chans_ > 1) {
|
||||
for (int i=0; i<READ_CHUNK_LEN; i++) {
|
||||
read_buffer[i] = read_buffer[i*numchans];
|
||||
read_buffer_[i] = read_buffer_[i*num_chans_];
|
||||
}
|
||||
}
|
||||
|
||||
} else if (stream_type_ == STREAM_TYPE_PA) {
|
||||
|
||||
samplesread = READ_CHUNK_LEN;
|
||||
int err = Pa_ReadStream( pa_stream_, read_buffer, READ_CHUNK_LEN );
|
||||
int err = Pa_ReadStream( pa_stream_, read_buffer_, READ_CHUNK_LEN );
|
||||
if (err != paNoError)
|
||||
is_open_ = false;
|
||||
}
|
||||
|
@ -166,11 +182,11 @@ void DSPworker::readMore () {
|
|||
|
||||
int cirbuf_fits = std::min(CIRBUF_LEN - cirbuf_head_, (int)samplesread);
|
||||
|
||||
memcpy(&cirbuf_[cirbuf_head_], read_buffer, cirbuf_fits * sizeof(read_buffer[0]));
|
||||
memcpy(&cirbuf_[cirbuf_head_], read_buffer_, cirbuf_fits * sizeof(read_buffer_[0]));
|
||||
|
||||
// wrapped around
|
||||
if (samplesread > cirbuf_fits) {
|
||||
memcpy(&cirbuf_[0], &read_buffer[cirbuf_fits], (samplesread - cirbuf_fits) * sizeof(read_buffer[0]));
|
||||
memcpy(&cirbuf_[0], &read_buffer_[cirbuf_fits], (samplesread - cirbuf_fits) * sizeof(read_buffer_[0]));
|
||||
}
|
||||
|
||||
// mirror
|
||||
|
@ -216,9 +232,9 @@ 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 + window_[win_type].size()/2 ;
|
||||
size_t win_i = i - MOMENT_LEN/2 + window_[win_type].size()/2 ;
|
||||
|
||||
if (win_i >= 0 && win_i < window_[win_type].size()) {
|
||||
if (win_i < window_[win_type].size()) {
|
||||
double a;
|
||||
//fftw_complex mixed;
|
||||
a = cirbuf_[cirbuf_tail_ + i] * window_[win_type][win_i];
|
||||
|
@ -257,24 +273,21 @@ double DSPworker::peakFreq (double minf, double maxf, WindowType wintype) {
|
|||
}
|
||||
|
||||
double result = peakBin + gaussianPeak(Mag[peakBin-1], Mag[peakBin], Mag[peakBin+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_ + fshift_;
|
||||
|
||||
// cheb47 @ 44100 can't resolve <1800 Hz
|
||||
if (result < 1800 && wintype == WINDOW_CHEB47)
|
||||
// cheb47 @ 44100 can't resolve <1700 Hz nominal
|
||||
if (result < 1700 && wintype == WINDOW_CHEB47)
|
||||
result = peakFreq (minf, maxf, WINDOW_HANN95);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
Wave DSPworker::bandPowerPerHz(std::vector<std::vector<double> > bands) {
|
||||
Wave DSPworker::bandPowerPerHz(std::vector<std::vector<double> > bands, WindowType wintype) {
|
||||
|
||||
int fft_len = FFT_LEN_BIG;
|
||||
WindowType wintype = WINDOW_HANN2047;
|
||||
int fft_len = (window_[wintype].size() <= FFT_LEN_SMALL ? FFT_LEN_SMALL : FFT_LEN_BIG);
|
||||
fftw_complex windowed[window_[wintype].size()];
|
||||
|
||||
windowedMoment(wintype, windowed);
|
||||
|
@ -287,7 +300,7 @@ Wave DSPworker::bandPowerPerHz(std::vector<std::vector<double> > bands) {
|
|||
double P = 0;
|
||||
double binwidth = 1.0 * samplerate_ / fft_len;
|
||||
int nbins = 0;
|
||||
for (int i = freq2bin(band[0], fft_len); i <= freq2bin(band[1], fft_len); i++) {
|
||||
for (int i = freq2bin(band[0]+fshift_, fft_len); i <= freq2bin(band[1]+fshift_, fft_len); i++) {
|
||||
P += pow(complexMag(fft_outbuf_[i]), 2);
|
||||
nbins++;
|
||||
}
|
||||
|
@ -300,23 +313,59 @@ Wave DSPworker::bandPowerPerHz(std::vector<std::vector<double> > bands) {
|
|||
WindowType DSPworker::bestWindowFor(SSTVMode Mode, double SNR) {
|
||||
WindowType WinType;
|
||||
|
||||
double samplesInPixel = 1.0 * samplerate_ * ModeSpec[Mode].tScan / ModeSpec[Mode].ScanPixels;
|
||||
//double samplesInPixel = 1.0 * samplerate_ * ModeSpec[Mode].tScan / ModeSpec[Mode].ScanPixels;
|
||||
|
||||
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 if (SNR >= -3) WinType = WINDOW_HANN1023;
|
||||
else WinType = WINDOW_HANN2047;
|
||||
|
||||
return WinType;
|
||||
}
|
||||
|
||||
double DSPworker::videoSNR () {
|
||||
if (t_ >= next_snr_time_) {
|
||||
std::vector<double> bands = bandPowerPerHz({{200,1000}, {1500,2300}, {2700, 2900}});
|
||||
double Pvideo_plus_noise = bands[1];
|
||||
double Pnoise_only = (bands[0] + bands[2]) / 2;
|
||||
double Psignal = Pvideo_plus_noise - Pnoise_only;
|
||||
|
||||
SNR_ = ((Pnoise_only == 0 || Psignal / Pnoise_only < .01) ? -20 : 10 * log10(Psignal / Pnoise_only));
|
||||
|
||||
next_snr_time_ = t_ + 50e-3;
|
||||
}
|
||||
|
||||
return SNR_;
|
||||
}
|
||||
|
||||
bool DSPworker::hasSync () {
|
||||
std::vector<double> bands = bandPowerPerHz({{1150,1250}, {1500,2300}}, sync_window_);
|
||||
|
||||
return (bands[0] > 2 * bands[1]);
|
||||
|
||||
}
|
||||
|
||||
double DSPworker::lum (SSTVMode mode, bool is_adaptive) {
|
||||
WindowType win_type;
|
||||
|
||||
if (is_adaptive) win_type = bestWindowFor(mode, videoSNR());
|
||||
else win_type = bestWindowFor(mode);
|
||||
|
||||
double freq = peakFreq(1500, 2300, win_type);
|
||||
return fclip((freq - 1500.0) / (2300.0-1500.0));
|
||||
}
|
||||
|
||||
// param: y values around peak
|
||||
// return: peak x position (-1 .. 1)
|
||||
double gaussianPeak (double y1, double y2, double y3) {
|
||||
return ((y3 - y1) / (2 * (2*y2 - y3 - y1)));
|
||||
if (2*y2 - y3 - y1 == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return ((y3 - y1) / (2 * (2*y2 - y3 - y1)));
|
||||
}
|
||||
}
|
||||
/*WindowType DSPworker::bestWindowFor(SSTVMode Mode) {
|
||||
return bestWindowFor(Mode, 20);
|
||||
|
@ -389,15 +438,11 @@ 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);
|
||||
}
|
||||
|
||||
double sinc (double x) {
|
||||
return (x == 0 ? 1 : sin(M_PI*x) / (M_PI*x));
|
||||
}
|
||||
|
||||
Wave upsampleLanczos(Wave orig, int factor, double middle_freq, int a) {
|
||||
Wave upsampleLanczos(Wave orig, int factor, size_t a) {
|
||||
Wave result(orig.size()*factor);
|
||||
int kernel_len = factor*a*2 + 1;
|
||||
|
||||
|
@ -410,40 +455,37 @@ Wave upsampleLanczos(Wave orig, int factor, double middle_freq, int a) {
|
|||
}
|
||||
|
||||
// convolution
|
||||
for (int i=-a; i<int(orig.size()+a); i++) {
|
||||
for (int orig_i=-a; orig_i<int(orig.size()+a); orig_i++) {
|
||||
double orig_sample;
|
||||
if (i < 0)
|
||||
if (orig_i < 0)
|
||||
orig_sample = orig[0];
|
||||
else if (i > orig.size()-1)
|
||||
else if (orig_i > int(orig.size()-1))
|
||||
orig_sample = orig[orig.size()-1];
|
||||
else
|
||||
orig_sample = orig[i];
|
||||
orig_sample = orig[orig_i];
|
||||
|
||||
if (orig_sample != 0) {
|
||||
for (int kernel_idx=0; kernel_idx<kernel_len; kernel_idx++) {
|
||||
int i_new = i*factor + (kernel_idx-kernel_len/2);
|
||||
if (i_new >= 0 && i_new <= result.size()-1)
|
||||
result[i_new] += (orig_sample - middle_freq) * lanczos[kernel_idx];
|
||||
int i_new = (orig_i+.5)*factor -kernel_len/2 + kernel_idx;
|
||||
if (i_new >= 0 && i_new <= int(result.size()-1))
|
||||
result[i_new] += orig_sample * lanczos[kernel_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<result.size(); i++)
|
||||
result[i] += middle_freq;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Wave deriv (Wave wave) {
|
||||
Wave result;
|
||||
for (int i=1; i<wave.size(); i++)
|
||||
for (size_t i=1; i<wave.size(); i++)
|
||||
result.push_back(wave[i] - wave[i-1]);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<double> peaks (Wave wave, int n) {
|
||||
std::vector<double> peaks (Wave wave, size_t n) {
|
||||
std::vector<std::pair<double,double> > peaks;
|
||||
for (int i=0; i<wave.size(); i++) {
|
||||
for (size_t i=0; i<wave.size(); i++) {
|
||||
double y1 = (i==0 ? wave[0] : wave[i-1]);
|
||||
double y2 = wave[i];
|
||||
double y3 = (i==wave.size()-1 ? wave[wave.size()-1] : wave[i+1]);
|
||||
|
@ -456,7 +498,7 @@ std::vector<double> peaks (Wave wave, int n) {
|
|||
});
|
||||
|
||||
Wave result;
|
||||
for (int i=0;i<n && i<peaks.size(); i++)
|
||||
for (size_t i=0;i<n && i<peaks.size(); i++)
|
||||
result.push_back(peaks[i].first);
|
||||
|
||||
std::sort(result.begin(), result.end());
|
||||
|
@ -465,9 +507,9 @@ std::vector<double> peaks (Wave wave, int n) {
|
|||
}
|
||||
|
||||
|
||||
std::vector<double> derivPeaks (Wave wave, int n) {
|
||||
std::vector<double> derivPeaks (Wave wave, size_t n) {
|
||||
std::vector<double> result = peaks(deriv(wave), n);
|
||||
for (int i=0; i<result.size(); i++) {
|
||||
for (size_t i=0; i<result.size(); i++) {
|
||||
result[i] += .5;
|
||||
}
|
||||
return result;
|
||||
|
@ -479,7 +521,7 @@ Wave rms(Wave orig, int window_width) {
|
|||
int pool_ptr = 0;
|
||||
double total = 0;
|
||||
|
||||
for (int i=0; i<orig.size(); i++) {
|
||||
for (size_t i=0; i<orig.size(); i++) {
|
||||
total -= pool[pool_ptr];
|
||||
pool[pool_ptr] = pow(orig[i], 2);
|
||||
total += pool[pool_ptr];
|
||||
|
@ -488,3 +530,94 @@ Wave rms(Wave orig, int window_width) {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* returns: vector of bits */
|
||||
std::vector<int> readFSK (DSPworker *dsp, double baud_rate, double cent_freq, double shift, size_t nbits) {
|
||||
std::vector<int> result;
|
||||
double freq_margin = 200;
|
||||
for (size_t i=0; i<nbits; i++) {
|
||||
dsp->forward_time(0.5 / baud_rate);
|
||||
std::vector<double> f = dsp->bandPowerPerHz(
|
||||
{{cent_freq-shift-freq_margin, cent_freq-shift+freq_margin},
|
||||
{cent_freq+shift-freq_margin, cent_freq+shift+freq_margin}}
|
||||
);
|
||||
result.push_back(f[0] > f[1]);
|
||||
dsp->forward_time(0.5 / baud_rate);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* pass: wave FM demodulated signal
|
||||
* melody array of Tones
|
||||
* (zero frequency to accept any)
|
||||
* dt sample delta
|
||||
*
|
||||
* returns: (bool) did we find it
|
||||
* (double) at which frequency shift
|
||||
* (double) started how many seconds before the last sample
|
||||
*/
|
||||
std::tuple<bool,double,double> findMelody (Wave wave, Melody melody, double dt) {
|
||||
bool was_found = true;
|
||||
int start_at = 0;
|
||||
double avg_fdiff = 0;
|
||||
double freq_margin = 25;
|
||||
double tshift = 0;
|
||||
double t = melody[melody.size()-1].dur;
|
||||
std::vector<double> fdiffs;
|
||||
|
||||
for (int i=melody.size()-2; i>=0; i--) {
|
||||
if (melody[i].freq != 0) {
|
||||
double delta_f_ref = melody[i].freq - melody[melody.size()-1].freq;
|
||||
double delta_f = wave[wave.size()-1 - (t/dt)] - wave[wave.size()-1];
|
||||
double fshift = delta_f - delta_f_ref;
|
||||
was_found &= fabs(fshift) < freq_margin;
|
||||
}
|
||||
start_at = wave.size() - (t / dt);
|
||||
t += melody[i].dur;
|
||||
}
|
||||
|
||||
if (was_found) {
|
||||
|
||||
/* refine fshift */
|
||||
int melody_i = 0;
|
||||
double next_tone_t = melody[melody_i].dur;
|
||||
for (size_t i=start_at; i<wave.size(); i++) {
|
||||
double fref = melody[melody_i].freq;
|
||||
double fdiff = (wave[i] - fref);
|
||||
fdiffs.push_back(fdiff);
|
||||
if ( (i-start_at)*dt >= next_tone_t ) {
|
||||
melody_i ++;
|
||||
next_tone_t += melody[melody_i].dur;
|
||||
}
|
||||
}
|
||||
std::sort(fdiffs.begin(), fdiffs.end());
|
||||
avg_fdiff = fdiffs[fdiffs.size()/2];
|
||||
|
||||
/* refine start_at */
|
||||
Wave subwave(wave.begin()+start_at, wave.end());
|
||||
Wave edges_rx = derivPeaks(subwave, melody.size()-1);
|
||||
Wave edges_ref;
|
||||
double t = 0;
|
||||
for (size_t i=0; i<melody.size()-1; i++) {
|
||||
t += melody[i].dur;
|
||||
edges_ref.push_back(t);
|
||||
}
|
||||
|
||||
tshift = 0;
|
||||
if (edges_rx.size() == edges_ref.size()) {
|
||||
for (size_t i=0; i<edges_rx.size(); i++) {
|
||||
tshift += (edges_rx[i]*dt - edges_ref[i]);
|
||||
}
|
||||
tshift = start_at*dt + (tshift / edges_rx.size()) - ((wave.size()-1)*dt);
|
||||
} else {
|
||||
// can't refine
|
||||
tshift = start_at*dt - ((wave.size()-1)*dt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return { was_found, avg_fdiff, tshift };
|
||||
}
|
||||
|
||||
|
||||
|
|
218
src/header.cc
218
src/header.cc
|
@ -1,181 +1,107 @@
|
|||
#include "common.hh"
|
||||
#include <cmath>
|
||||
|
||||
|
||||
/* pass: wave FM demodulated signal
|
||||
* melody array of Tones
|
||||
* (zero frequency to accept any)
|
||||
* dt sample delta
|
||||
*
|
||||
* returns: (bool) did we find it
|
||||
* (double) at which frequency shift
|
||||
* (int) starting at how many dt from beginning of buffer
|
||||
*/
|
||||
std::tuple<bool,double,int> findMelody (Wave wave, Melody melody, double dt) {
|
||||
bool was_found = true;
|
||||
int start = 0;
|
||||
double avg_fdiff = 0;
|
||||
double freq_margin = 25;
|
||||
double t = melody[melody.size()-1].dur;
|
||||
std::vector<double> fdiffs;
|
||||
|
||||
for (int i=melody.size()-2; i>=0; i--) {
|
||||
if (melody[i].freq != 0) {
|
||||
double delta_f_ref = melody[i].freq - melody[melody.size()-1].freq;
|
||||
double delta_f = wave[wave.size()-1 - (t/dt)] - wave[wave.size()-1];
|
||||
double fshift = delta_f - delta_f_ref;
|
||||
was_found &= fabs(fshift) < freq_margin;
|
||||
}
|
||||
start = wave.size() - (t / dt);
|
||||
t += melody[i].dur;
|
||||
}
|
||||
|
||||
if (was_found) {
|
||||
int melody_i = 0;
|
||||
double next_tone_t = melody[melody_i].dur;
|
||||
for (int i=start; i<wave.size(); i++) {
|
||||
double fref = melody[melody_i].freq;
|
||||
double fdiff = (wave[i] - fref);
|
||||
fdiffs.push_back(fdiff);
|
||||
if ( (i-start)*dt >= next_tone_t ) {
|
||||
melody_i ++;
|
||||
next_tone_t += melody[melody_i].dur;
|
||||
}
|
||||
}
|
||||
std::sort(fdiffs.begin(), fdiffs.end());
|
||||
avg_fdiff = fdiffs[fdiffs.size()/2];
|
||||
}
|
||||
|
||||
|
||||
return { was_found, avg_fdiff, start };
|
||||
}
|
||||
|
||||
|
||||
|
||||
SSTVMode modeFromNextHeader (DSPworker *dsp) {
|
||||
SSTVMode nextHeader (DSPworker *dsp) {
|
||||
|
||||
double dt = 5e-3;
|
||||
int bitlen = 30e-3 / dt;
|
||||
int upsample_factor = 10;
|
||||
SSTVMode mode = MODE_UNKNOWN;
|
||||
|
||||
int selmode, ptr=0;
|
||||
int vis = 0, Parity = 0, ReadPtr = 0;
|
||||
Wave HedrCirBuf(1100e-3 / dt);
|
||||
Wave delta_f(1100e-3 / dt);
|
||||
int ptr_read = 0;
|
||||
Wave cirbuf_header(1100e-3 / dt);
|
||||
Wave cirbuf_header_bb(1100e-3 / dt);
|
||||
Wave freq(1100e-3 / dt);
|
||||
Wave tone(1100e-3 / dt);
|
||||
bool gotvis = false;
|
||||
unsigned Bit[8] = {0}, ParityBit = 0;
|
||||
Wave freq_bb(1100e-3 / dt);
|
||||
|
||||
Melody mmsstv_melody = {
|
||||
Melody mmsstv_vox = {
|
||||
Tone(100e-3, 1900), Tone(100e-3, 1500), Tone(100e-3, 1900), Tone(100e-3, 1500),
|
||||
Tone(100e-3, 2300), Tone(100e-3, 1500), Tone(100e-3, 2300), Tone(100e-3, 1500),
|
||||
Tone(300e-3, 1900)
|
||||
};
|
||||
|
||||
Melody robot_melody = {
|
||||
Tone(150e-3, 1900), Tone(150e-3, 1900), Tone(160e-3, 1900), Tone(150e-3, 1900),
|
||||
Tone(30e-3, 1200), Tone(8*30e-3,0), Tone(30e-3, 1200)
|
||||
Melody robot_vox = {
|
||||
Tone(300e-3, 1900), Tone(10e-3, 1200), Tone(300e-3, 1900),
|
||||
Tone(30e-3, 1200)
|
||||
};
|
||||
|
||||
fprintf(stderr,"Waiting for header\n");
|
||||
fprintf(stderr,"wait for header\n");
|
||||
|
||||
while ( dsp->is_open() ) {
|
||||
|
||||
HedrCirBuf[ReadPtr] = dsp->peakFreq(500, 3300, WINDOW_HANN1023);
|
||||
cirbuf_header[ptr_read] = dsp->peakFreq(500, 3300, WINDOW_HANN1023);
|
||||
|
||||
for (int i = 0; i < HedrCirBuf.size(); i++) {
|
||||
freq[i] = HedrCirBuf[(ReadPtr + 1 + i) % HedrCirBuf.size()];
|
||||
for (size_t i = 0; i < cirbuf_header.size(); i++) {
|
||||
freq[i] = cirbuf_header[(ptr_read + 1 + i) % cirbuf_header.size()];
|
||||
}
|
||||
|
||||
double fshift;
|
||||
std::tuple<bool,double,int> has = findMelody(freq, mmsstv_melody, dt);
|
||||
std::tuple<bool,double,double> has = findMelody(freq, mmsstv_vox, dt);
|
||||
if (std::get<0>(has)) {
|
||||
fshift = std::get<1>(has);
|
||||
int start = std::get<2>(has);
|
||||
fprintf(stderr," got MMSSTV header (t=%f s, fshift=%f Hz, start at %d)\n",dsp->get_t(), fshift, start);
|
||||
double tshift = std::get<2>(has);
|
||||
fprintf(stderr," got MMSSTV VOX (t=%.03f s, fshift=%+.0f Hz)\n",
|
||||
dsp->get_t()+tshift, fshift);
|
||||
|
||||
Wave powers = rms(deriv(freq), 6);
|
||||
for (int i=0; i<powers.size(); i++) {
|
||||
dsp->forward_time(tshift + 8*100e-3 + 300e-3 + 10e-3 + 300e-3 + 30e-3);
|
||||
|
||||
}
|
||||
|
||||
Wave header_interp = upsampleLanczos(freq, upsample_factor, 1900);
|
||||
std::vector<double> peaks_pos = derivPeaks(header_interp, 8);
|
||||
mode = readVIS(dsp, fshift);
|
||||
|
||||
printWave(freq, dt);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (false) {
|
||||
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(" got robot header: t=%f, fshift=%+.1f Hz\n",dsp->get_t(),fshift);
|
||||
|
||||
// hi-res zero-crossing search
|
||||
Wave abs_f(delta_f.size());
|
||||
abs_f[0] = delta_f[0];
|
||||
for (int i=1; i<abs_f.size(); i++)
|
||||
abs_f[i] += delta_f[0] + delta_f[i];
|
||||
Wave 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 (n_zc > 1 && t > t_zc1+270e-3 ) {
|
||||
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, speed = %.5f)\n",n_zc,t_zc1,t_zc2,
|
||||
(t_zc2-t_zc1)*1e3,
|
||||
300e-3 / (t_zc2 - t_zc1)
|
||||
);
|
||||
|
||||
int parity_rx=0;
|
||||
for (int k=0; k<8; k++) {
|
||||
if (abs_f[(13+k)*bitlen]+fshift < 1200) {
|
||||
vis |= (1 << k);
|
||||
parity_rx ++;
|
||||
}
|
||||
}
|
||||
vis &= 0x7f;
|
||||
|
||||
printf(" got 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;
|
||||
}
|
||||
if (mode != MODE_UNKNOWN) {
|
||||
dsp->set_fshift(fshift);
|
||||
dsp->forward_time(30e-3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ReadPtr = (ReadPtr+1) % HedrCirBuf.size();
|
||||
|
||||
has = findMelody(freq, robot_vox, dt);
|
||||
if (std::get<0>(has)) {
|
||||
fshift = std::get<1>(has);
|
||||
double tshift = std::get<2>(has);
|
||||
|
||||
fprintf(stderr," got Robot header (t=%f s, fshift=%+.1f Hz)\n",dsp->get_t(),fshift);
|
||||
|
||||
dsp->forward_time(tshift + 300e-3 + 10e-3 + 300e-3 + 30e-3);
|
||||
|
||||
mode = readVIS(dsp, fshift);
|
||||
|
||||
if (mode != MODE_UNKNOWN) {
|
||||
dsp->set_fshift(fshift);
|
||||
dsp->forward_time(30e-3);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
ptr_read = (ptr_read+1) % cirbuf_header.size();
|
||||
dsp->forward_time(dt);
|
||||
}
|
||||
|
||||
return vis2mode[vis];
|
||||
return mode;
|
||||
|
||||
}
|
||||
|
||||
SSTVMode readVIS(DSPworker* dsp, double fshift) {
|
||||
|
||||
int vis = 0;
|
||||
SSTVMode mode = MODE_UNKNOWN;
|
||||
std::vector<int> bits = readFSK(dsp, 33.333, 1200+fshift, 100, 8);
|
||||
int parity_rx=0;
|
||||
for (int i=0; i<8; i++) {
|
||||
vis |= (bits[i] << i);
|
||||
parity_rx += bits[i];
|
||||
}
|
||||
vis &= 0x7F;
|
||||
|
||||
if (vis2mode.find(vis) == vis2mode.end()) {
|
||||
fprintf(stderr,"(unknown mode %dd=%02Xh)\n",vis,vis);
|
||||
} else {
|
||||
if ((parity_rx % 2) != ModeSpec[vis2mode[vis]].VISParity) {
|
||||
fprintf(stderr,"(parity fail)\n");
|
||||
} else {
|
||||
fprintf(stderr," got VIS: %dd / %02Xh (%s)", vis, vis,
|
||||
ModeSpec[vis2mode[vis]].Name.c_str());
|
||||
mode = vis2mode[vis];
|
||||
}
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
|
|
@ -21,13 +21,14 @@ int main(int argc, char *argv[]) {
|
|||
break;
|
||||
}
|
||||
|
||||
upsampleLanczos({0},10);
|
||||
|
||||
|
||||
if (!dsp.is_open())
|
||||
dsp.openPortAudio();
|
||||
|
||||
GetVideo(modeFromNextHeader(&dsp), &dsp);
|
||||
SSTVMode mode = nextHeader(&dsp);
|
||||
if (mode != MODE_UNKNOWN) {
|
||||
rxVideo(mode, &dsp);
|
||||
}
|
||||
|
||||
//SlowGUI gui = SlowGUI();
|
||||
return 0;
|
||||
|
|
101
src/sync.cc
101
src/sync.cc
|
@ -1,93 +1,68 @@
|
|||
#include "common.hh"
|
||||
|
||||
void findSyncHough (SSTVMode Mode, std::vector<bool> has_sync) {
|
||||
// TODO: middle point of sync pulse
|
||||
void resync (Picture* pic) {
|
||||
int line_width = ModeSpec[pic->mode].tLine / pic->sync_dt;
|
||||
|
||||
}
|
||||
|
||||
double findSyncAutocorr (SSTVMode Mode, std::vector<bool> has_sync) {
|
||||
int line_width = ModeSpec[Mode].NumLines;
|
||||
std::vector<int> jakauma;
|
||||
/* speed */
|
||||
std::vector<int> histogram;
|
||||
int peak_speed = 0;
|
||||
int peak_speed_val = 0;
|
||||
int peak_pos = 0;
|
||||
double min_spd = 0.998;
|
||||
double max_spd = 1.002;
|
||||
double spd_step = 0.0002;
|
||||
|
||||
double spd_step = 0.00005;
|
||||
for (double speed = min_spd; speed <= max_spd; speed += spd_step) {
|
||||
std::vector<int> acc(line_width);
|
||||
int peak_x = 0;
|
||||
for (int i=0; i<has_sync.size(); i++) {
|
||||
for (size_t i=1; i<pic->sync_signal.size(); i++) {
|
||||
int x = int(i / speed + .5) % line_width;
|
||||
acc[x] += has_sync[i];
|
||||
acc[x] += pic->sync_signal[i] && !pic->sync_signal[i-1];
|
||||
if (acc[x] > acc[peak_x]) {
|
||||
peak_x = x;
|
||||
}
|
||||
}
|
||||
jakauma.push_back(acc[peak_x]);
|
||||
histogram.push_back(acc[peak_x]);
|
||||
if (acc[peak_x] > peak_speed_val) {
|
||||
peak_speed = jakauma.size()-1;
|
||||
peak_speed = histogram.size()-1;
|
||||
peak_speed_val = acc[peak_x];
|
||||
peak_pos = peak_x;
|
||||
}
|
||||
printf("(%.5f=%.0f) %d\n",1.0/speed,44100.0/speed,acc[peak_x]);
|
||||
|
||||
/*for (int x=0; x<acc.size(); x++) {
|
||||
printf("%4d ",acc[x]);
|
||||
}
|
||||
printf("\n");*/
|
||||
}
|
||||
double peak_refined = peak_speed + gaussianPeak(jakauma[peak_speed-1], jakauma[peak_speed], jakauma[peak_speed+1]);
|
||||
|
||||
double peak_refined = peak_speed +
|
||||
gaussianPeak(histogram[peak_speed-1], histogram[peak_speed], histogram[peak_speed+1]);
|
||||
double spd = 1.0/(min_spd + peak_refined*spd_step);
|
||||
printf("%.5f\n",spd);
|
||||
printf("--> %.1f\n",44100*spd);
|
||||
printf("pos = %d (%.1f ms)\n",peak_pos,1.0*peak_pos/line_width*ModeSpec[Mode].tLine*1000);
|
||||
return spd;
|
||||
}
|
||||
|
||||
void findSyncRansac(SSTVMode Mode, std::vector<bool> has_sync) {
|
||||
int line_width = ModeSpec[Mode].NumLines;//ModeSpec[Mode].tLine / ModeSpec[Mode].tSync * 4;
|
||||
std::vector<Point> 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(Point(x,y));
|
||||
printf("%d,%d\n",x,y);
|
||||
}
|
||||
}
|
||||
/* align */
|
||||
size_t peak_align = 0;
|
||||
std::vector<int> acc(line_width);
|
||||
for (size_t i=1;i<pic->sync_signal.size(); i++) {
|
||||
int x = int(i * spd + .5) % line_width;
|
||||
acc[x] += pic->sync_signal[i] && !pic->sync_signal[i-1];
|
||||
if (acc[x] > acc[peak_align])
|
||||
peak_align = x;
|
||||
}
|
||||
|
||||
std::pair<Point, Point > best_line;
|
||||
double best_dist = -1;
|
||||
int it_num = 0;
|
||||
while (++it_num < 300) {
|
||||
double peak_align_refined = peak_align +
|
||||
(peak_align == 0 || peak_align == acc.size()-1 ? 0 :
|
||||
gaussianPeak(acc[peak_align-1], acc[peak_align], acc[peak_align+1]));
|
||||
|
||||
std::random_shuffle(sync_pixels.begin(), sync_pixels.end());
|
||||
if (ModeSpec[pic->mode].SyncOrder == SYNC_SCOTTIE)
|
||||
peak_align_refined = peak_align_refined -
|
||||
(line_width*(ModeSpec[pic->mode].tSync + ModeSpec[pic->mode].tSep*2 + ModeSpec[pic->mode].tScan*2)/ModeSpec[pic->mode].tLine);
|
||||
|
||||
std::pair<Point, Point > test_line = {sync_pixels[0], sync_pixels[1]};
|
||||
int x0 = test_line.first.x;
|
||||
int y0 = test_line.first.y;
|
||||
int x1 = test_line.second.x;
|
||||
int y1 = test_line.second.y;
|
||||
double total_sq_dist = 0;
|
||||
int total_good = 0;
|
||||
printf("%f\n",peak_align_refined);
|
||||
|
||||
for(Point pixel : sync_pixels) {
|
||||
int x = pixel.x;
|
||||
int y = pixel.y;
|
||||
// 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 (peak_align_refined > line_width/2.0)
|
||||
peak_align_refined -= line_width;
|
||||
|
||||
if (fabs(d) < 6 || fabs(d-line_width) < 6) {
|
||||
total_good ++;//+= sqrt(fabs(d));
|
||||
}
|
||||
fprintf(stderr,"%.5f\n",spd);
|
||||
fprintf(stderr, "--> %.1f\n",44100*spd);
|
||||
fprintf(stderr,"align = %f = %.3f %% (%.3f ms)\n",
|
||||
peak_align_refined,
|
||||
1.0*peak_align_refined/line_width*100,
|
||||
1.0*peak_align_refined/line_width*ModeSpec[pic->mode].tLine*1000);
|
||||
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
pic->speed = spd;
|
||||
pic->starts_at = 1.0*peak_align_refined/line_width*ModeSpec[pic->mode].tLine;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
#include "common.hh"
|
||||
|
||||
void printWave (Wave wave, double dt) {
|
||||
for (int i=0;i<wave.size();i++)
|
||||
printf("%lf,%lf\n",i*dt,wave[i]);
|
||||
}
|
||||
|
||||
double sum (std::vector<double> nums) {
|
||||
double result = 0;
|
||||
for (double d : nums)
|
||||
result += d;
|
||||
return result;
|
||||
}
|
||||
|
||||
double mean (std::vector<double> nums) {
|
||||
return sum(nums) / nums.size();
|
||||
}
|
||||
|
||||
double sdev (std::vector<double> nums) {
|
||||
double result = 0;
|
||||
double _mean = mean(nums);
|
||||
for (double d : nums)
|
||||
result += pow(d - _mean, 2);
|
||||
result = sqrt(result / nums.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
void runTest(const char *sweepsound) {
|
||||
DSPworker dsp;
|
||||
double tone_len = 0.3;
|
||||
dsp.openAudioFile(sweepsound);
|
||||
std::map<double, std::vector<double> > errors;
|
||||
|
||||
while(dsp.is_open()) {
|
||||
double f = dsp.peakFreq(1200,2500,WINDOW_HANN63);
|
||||
double lum_should_be = int(dsp.get_t()/tone_len) % 256;
|
||||
double f_should_be = 1500 + lum_should_be / 255.0 * (2300-1500);
|
||||
|
||||
//printf("f should be %.0f, is %.0f (error %.1f)\n",f_should_be,f,f - f_should_be);
|
||||
|
||||
errors[f_should_be].push_back((f - f_should_be) );
|
||||
|
||||
dsp.forward();
|
||||
}
|
||||
|
||||
typedef std::map<double, std::vector<double> >::iterator it_type;
|
||||
for(it_type iterator = errors.begin(); iterator != errors.end(); iterator++) {
|
||||
double k = iterator->first;
|
||||
std::vector<double> errs = iterator->second;
|
||||
printf("%f,%f\n",k,sdev(errs));
|
||||
}
|
||||
}
|
||||
|
244
src/video.cc
244
src/video.cc
|
@ -1,60 +1,104 @@
|
|||
#include "common.hh"
|
||||
|
||||
typedef struct {
|
||||
Point pt;
|
||||
int Channel;
|
||||
double Time;
|
||||
} PixelSample;
|
||||
|
||||
void renderPixbuf(Picture *pic) {
|
||||
|
||||
int upsample_factor = 4;
|
||||
|
||||
std::vector<PixelSample> pixel_grid = getPixelSamplingPoints(pic->mode);
|
||||
|
||||
guint8 lum[800][800][3];
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf_rx;
|
||||
pixbuf_rx = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, ModeSpec[pic->mode].ScanPixels, ModeSpec[pic->mode].NumLines);
|
||||
pixbuf_rx->fill(0x000000ff);
|
||||
|
||||
// Map to RGB & store in pixbuf
|
||||
void toPixbufRGB(guint8 Image[800][800][3], Glib::RefPtr<Gdk::Pixbuf> pixbuf, SSTVMode Mode) {
|
||||
guint8 *p;
|
||||
guint8 *pixels;
|
||||
pixels = pixbuf->get_pixels();
|
||||
int rowstride = pixbuf->get_rowstride();
|
||||
for (int x = 0; x < ModeSpec[Mode].ScanPixels; x++) {
|
||||
for (int y = 0; y < ModeSpec[Mode].NumLines; y++) {
|
||||
pixels = pixbuf_rx->get_pixels();
|
||||
int rowstride = pixbuf_rx->get_rowstride();
|
||||
Wave signal_up = upsampleLanczos(pic->video_signal, upsample_factor, 3);
|
||||
|
||||
for (size_t pixel_idx = 0; pixel_idx <= pixel_grid.size(); pixel_idx ++) {
|
||||
|
||||
PixelSample px = pixel_grid[pixel_idx];
|
||||
|
||||
double signal_t = (px.Time/pic->speed + pic->starts_at) / pic->video_dt * upsample_factor;
|
||||
double val;
|
||||
if (signal_t < 0 || signal_t >= signal_up.size()-1) {
|
||||
val = 0;
|
||||
} else {
|
||||
double d = signal_t - int(signal_t);
|
||||
val = (1-d) * signal_up[signal_t] +
|
||||
d * signal_up[signal_t+1];
|
||||
}
|
||||
int x = pixel_grid[pixel_idx].pt.x;
|
||||
int y = pixel_grid[pixel_idx].pt.y;
|
||||
int ch = pixel_grid[pixel_idx].Channel;
|
||||
|
||||
lum[x][y][ch] = clip(val*255);
|
||||
}
|
||||
|
||||
if (ModeSpec[pic->mode].SubSampling == SUBSAMP_420_YUYV) {
|
||||
for (size_t x=0; x < ModeSpec[pic->mode].ScanPixels; x++) {
|
||||
std::vector<double> column_u, column_u_filtered;
|
||||
std::vector<double> column_v, column_v_filtered;
|
||||
for (size_t y=0; y < ModeSpec[pic->mode].NumLines; y+=2) {
|
||||
column_u.push_back(lum[x][y][1]);
|
||||
column_v.push_back(lum[x][y][2]);
|
||||
}
|
||||
column_u_filtered = upsampleLanczos(column_u, 2, 2);
|
||||
column_v_filtered = upsampleLanczos(column_v, 2, 2);
|
||||
for (size_t y=0; y < ModeSpec[pic->mode].NumLines; y++) {
|
||||
lum[x][y][1] = column_u_filtered[y];
|
||||
lum[x][y][2] = column_v_filtered[y];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t x = 0; x < ModeSpec[pic->mode].ScanPixels; x++) {
|
||||
for (size_t y = 0; y < ModeSpec[pic->mode].NumLines; y++) {
|
||||
|
||||
p = pixels + y * rowstride + x * 3;
|
||||
|
||||
switch(ModeSpec[Mode].ColorEnc) {
|
||||
switch(ModeSpec[pic->mode].ColorEnc) {
|
||||
|
||||
case COLOR_RGB:
|
||||
p[0] = Image[x][y][0];
|
||||
p[1] = Image[x][y][1];
|
||||
p[2] = Image[x][y][2];
|
||||
p[0] = lum[x][y][0];
|
||||
p[1] = lum[x][y][1];
|
||||
p[2] = lum[x][y][2];
|
||||
break;
|
||||
|
||||
case COLOR_GBR:
|
||||
p[0] = Image[x][y][2];
|
||||
p[1] = Image[x][y][0];
|
||||
p[2] = Image[x][y][1];
|
||||
p[0] = lum[x][y][2];
|
||||
p[1] = lum[x][y][0];
|
||||
p[2] = lum[x][y][1];
|
||||
break;
|
||||
|
||||
case COLOR_YUV:
|
||||
// TODO chroma filtering
|
||||
p[0] = clip((100 * Image[x][y][0] + 140 * Image[x][y][1] - 17850) / 100.0);
|
||||
p[1] = clip((100 * Image[x][y][0] - 71 * Image[x][y][1] - 33 *
|
||||
Image[x][y][2] + 13260) / 100.0);
|
||||
p[2] = clip((100 * Image[x][y][0] + 178 * Image[x][y][2] - 22695) / 100.0);
|
||||
p[0] = clip((100 * lum[x][y][0] + 140 * lum[x][y][1] - 17850) / 100.0);
|
||||
p[1] = clip((100 * lum[x][y][0] - 71 * lum[x][y][1] - 33 *
|
||||
lum[x][y][2] + 13260) / 100.0);
|
||||
p[2] = clip((100 * lum[x][y][0] + 178 * lum[x][y][2] - 22695) / 100.0);
|
||||
break;
|
||||
|
||||
case COLOR_MONO:
|
||||
p[0] = p[1] = p[2] = Image[x][y][0];
|
||||
p[0] = p[1] = p[2] = lum[x][y][0];
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
pixbuf_rx->save("testi.png", "png");
|
||||
}
|
||||
|
||||
// Time instants for all pixels
|
||||
std::vector<PixelSample> getPixelSamplingPoints(SSTVMode Mode) {
|
||||
_ModeSpec s = ModeSpec[Mode];
|
||||
std::vector<PixelSample> PixelGrid;
|
||||
for (int y=0; y<s.NumLines; y++) {
|
||||
for (int x=0; x<s.ScanPixels; x++) {
|
||||
for (int Chan=0; Chan < (s.ColorEnc == COLOR_MONO ? 1 : 3); Chan++) {
|
||||
std::vector<PixelSample> getPixelSamplingPoints(SSTVMode mode) {
|
||||
_ModeSpec s = ModeSpec[mode];
|
||||
std::vector<PixelSample> pixel_grid;
|
||||
for (size_t y=0; y<s.NumLines; y++) {
|
||||
for (size_t x=0; x<s.ScanPixels; x++) {
|
||||
for (size_t Chan=0; Chan < (s.ColorEnc == COLOR_MONO ? 1 : 3); Chan++) {
|
||||
PixelSample px;
|
||||
px.pt = Point(x,y);
|
||||
px.Channel = Chan;
|
||||
|
@ -142,16 +186,16 @@ std::vector<PixelSample> getPixelSamplingPoints(SSTVMode Mode) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
PixelGrid.push_back(px);
|
||||
pixel_grid.push_back(px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(PixelGrid.begin(), PixelGrid.end(), [](PixelSample a, PixelSample b) {
|
||||
std::sort(pixel_grid.begin(), pixel_grid.end(), [](PixelSample a, PixelSample b) {
|
||||
return a.Time < b.Time;
|
||||
});
|
||||
|
||||
return PixelGrid;
|
||||
return pixel_grid;
|
||||
}
|
||||
|
||||
/* Demodulate the video signal & store all kinds of stuff for later stages
|
||||
|
@ -161,127 +205,73 @@ std::vector<PixelSample> getPixelSamplingPoints(SSTVMode Mode) {
|
|||
* Redraw: false = Apply windowing and FFT to the signal, true = Redraw from cached FFT data
|
||||
* returns: true when finished, false when aborted
|
||||
*/
|
||||
bool GetVideo(SSTVMode Mode, DSPworker* dsp) {
|
||||
bool rxVideo(SSTVMode mode, DSPworker* dsp) {
|
||||
|
||||
printf("receive %s\n",ModeSpec[Mode].Name.c_str());
|
||||
printf("receive %s\n",ModeSpec[mode].Name.c_str());
|
||||
|
||||
_ModeSpec s = ModeSpec[Mode];
|
||||
|
||||
guint8 Image[800][800][3];
|
||||
guint8 Imagesnr[800][800][3];
|
||||
_ModeSpec s = ModeSpec[mode];
|
||||
Picture pic(mode);
|
||||
|
||||
Glib::RefPtr<Gtk::Application> app = Gtk::Application::create("com.windytan.slowrx");
|
||||
|
||||
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;
|
||||
double next_video_sample_time = 0;
|
||||
|
||||
/*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);
|
||||
*/
|
||||
//gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp);
|
||||
double t_total = (s.SyncOrder == SYNC_SCOTTIE ? s.tSync : 0) + s.NumLines * s.tLine;
|
||||
size_t idx = 0;
|
||||
|
||||
/*SyncTargetBin = GetBin(1200+CurrentPic.HedrShift, FFTLen);
|
||||
Abort = false;
|
||||
SyncSampleNum = 0;*/
|
||||
for (double t=0; t < t_total && dsp->is_open(); t += dsp->forward()) {
|
||||
|
||||
// Loop through signal
|
||||
std::vector<PixelSample> PixelGrid = getPixelSamplingPoints(Mode);
|
||||
double t = 0;
|
||||
for (int PixelIdx = 0; PixelIdx < PixelGrid.size(); PixelIdx++) {
|
||||
if (t >= next_sync_sample_time) {
|
||||
pic.sync_signal.push_back(dsp->hasSync());
|
||||
|
||||
while (t < PixelGrid[PixelIdx].Time && dsp->is_open()) {
|
||||
t += dsp->forward();
|
||||
next_sync_sample_time += pic.sync_dt;
|
||||
}
|
||||
|
||||
/*** Store the sync band for later adjustments ***/
|
||||
bool is_adaptive = true;
|
||||
|
||||
if (dsp->get_t() >= next_sync_sample_time) {
|
||||
if ( t >= next_video_sample_time ) {
|
||||
|
||||
int line_width = ModeSpec[Mode].NumLines;
|
||||
pic.video_signal.push_back(dsp->lum(pic.mode, is_adaptive));
|
||||
|
||||
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
|
||||
has_sync.push_back(bands[0] > 2 * bands[1]);
|
||||
|
||||
|
||||
next_sync_sample_time += ModeSpec[Mode].tLine / line_width;
|
||||
if ((idx+1) % 1000 == 0) {
|
||||
size_t prog_width = 50;
|
||||
fprintf(stderr," [");
|
||||
double prog = t / t_total;
|
||||
size_t prog_points = prog * prog_width + .5;
|
||||
for (size_t i=0;i<prog_points;i++) {
|
||||
fprintf(stderr,"=");
|
||||
}
|
||||
for (size_t i=prog_points;i<prog_width;i++) {
|
||||
fprintf(stderr," ");
|
||||
}
|
||||
fprintf(stderr,"] %.1f %%\r",prog*100);
|
||||
}
|
||||
|
||||
if (dsp->isLive() && (idx+1) % 10000 == 0) {
|
||||
resync(&pic);
|
||||
renderPixbuf(&pic);
|
||||
}
|
||||
next_video_sample_time += pic.video_dt;
|
||||
idx++;
|
||||
}
|
||||
|
||||
/*** Estimate SNR ***/
|
||||
|
||||
double SNR;
|
||||
bool Adaptive = true;
|
||||
|
||||
if (PixelIdx == 0 || (Adaptive && PixelGrid[PixelIdx].pt.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 = ((Pnoise_only == 0 || Psignal / Pnoise_only < .01) ? -20 : 10 * log10(Psignal / Pnoise_only));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*** FM demodulation ***/
|
||||
|
||||
//PrevFreq = Freq;
|
||||
|
||||
// Adapt window size to SNR
|
||||
WindowType WinType;
|
||||
|
||||
if (Adaptive) WinType = dsp->bestWindowFor(Mode, SNR);
|
||||
else WinType = dsp->bestWindowFor(Mode);
|
||||
|
||||
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
|
||||
guint8 Lum = freq2lum(Freq);
|
||||
//measured.push_back({t, Lum});
|
||||
//StoredLum[SampleNum] = clip((Freq - (1500 + CurrentPic.HedrShift)) / 3.1372549);
|
||||
|
||||
int x = PixelGrid[PixelIdx].pt.x;
|
||||
int y = PixelGrid[PixelIdx].pt.y;
|
||||
int Channel = PixelGrid[PixelIdx].Channel;
|
||||
|
||||
// Store pixel
|
||||
Image[x][y][Channel] = Lum;//StoredLum[SampleNum];
|
||||
Imagesnr[x][y][Channel] = WinType;//StoredLum[SampleNum];
|
||||
|
||||
}
|
||||
|
||||
/* sync */
|
||||
findSyncAutocorr(Mode, has_sync);
|
||||
|
||||
toPixbufRGB(Image, pixbuf_rx, Mode);
|
||||
toPixbufRGB(Imagesnr, pixbuf_snr, Mode);
|
||||
/*if (!Redraw || y % 5 == 0 || PixelIdx == PixelGrid.size()-1) {
|
||||
resync(&pic);
|
||||
renderPixbuf(&pic);
|
||||
|
||||
/*if (!Redraw || y % 5 == 0 || PixelIdx == pixel_grid.size()-1) {
|
||||
// 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);
|
||||
500.0/ModeSpec[mode].ImgWidth * ModeSpec[mode].NumLines * ModeSpec[mode].LineHeight, GDK_INTERP_BILINEAR);
|
||||
|
||||
gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp);
|
||||
}*/
|
||||
|
||||
|
||||
pixbuf_rx->save("testi.png", "png");
|
||||
pixbuf_snr->save("snr.png", "png");
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
return true;
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue