Oona Räisänen 2015-09-05 14:39:02 +03:00
rodzic bc17b64b6d
commit 588b3cb9ab
21 zmienionych plików z 1397 dodań i 1185 usunięć

Wyświetl plik

@ -16,13 +16,14 @@ Features
* Adaptive noise reduction
* Decode digital FSK ID
* Save received pictures as PNG
* Written in C++11
* Written in C++/C
Requirements
------------
* Linux, OSX, ...
* PortAudio
* libsndfile
* gtkmm 3 (`libgtkmm-3.0-dev`)
* FFTW 3 (`libfftw3-dev`)
@ -35,7 +36,6 @@ And, obviously:
Compiling
---------
autoreconf --install
./configure
make

Wyświetl plik

@ -1,7 +1,9 @@
AC_INIT([slowrx], [0.1], [oona@kapsi.fi])
AC_INIT([slowrx], [0.7-dev], [oona@kapsi.fi])
AM_INIT_AUTOMAKE([-Wall -Werror foreign])
AX_CXX_COMPILE_STDCXX_11()
AC_PROG_CXX
AC_PROG_CC
AC_PROG_CC_C99
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([
Makefile

Wyświetl plik

@ -1,4 +1,5 @@
bin_PROGRAMS = slowrx
slowrx_CPPFLAGS = -Wall -Wextra -pedantic -g $(GTKMM_CFLAGS) @SNDFILE_CFLAGS@ $(PORTAUDIO_CFLAGS)
slowrx_CPPFLAGS = -Wall -Wextra -pedantic -g $(GTKMM_CFLAGS) @SNDFILE_CFLAGS@ $(PORTAUDIO_CFLAGS) $(DBG_FLAGS)
slowrx_CCFLAGS = -Wall -Wextra -pedantic -g
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
slowrx_SOURCES = slowrx.cc common.cc modespec.cc gui.cc dsp.cc picture.cc video.cc header.cc

Wyświetl plik

@ -1,4 +1,6 @@
#include "common.hh"
#include "common.h"
#include "dsp.h"
#include "gui.h"
#include <thread>
bool Abort = false;
@ -17,6 +19,10 @@ std::vector<std::thread> threads(2);
std::vector<std::vector<double> > DSPworker::window_ (16);
std::string version_string() {
return "0.7-dev";
}
// Clip to [0..255]
guint8 clip (double a) {
if (a < 0) return 0;
@ -39,6 +45,11 @@ double rad2deg (double rad) {
return (180 / M_PI) * rad;
}
size_t maxIndex (std::vector<double> v) {
const int n = sizeof(v) / sizeof(double);
return distance(v.begin(), max_element(v.begin(), v.end()));
}
void ensure_dir_exists(std::string dir) {
struct stat buf;

87
src/common.h 100644
Wyświetl plik

@ -0,0 +1,87 @@
#ifndef _COMMON_H_
#define _COMMON_H_
#include <iostream>
#include <cassert>
#include <vector>
#include "modespec.h"
struct Point {
int x;
int 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) {}
};
class DSPworker;
using Wave = std::vector<double>;
using Melody = std::vector<Tone>;
enum WindowType {
WINDOW_CHEB47 = 0,
WINDOW_HANN95,
WINDOW_HANN127,
WINDOW_HANN255,
WINDOW_HANN511,
WINDOW_HANN1023,
WINDOW_HANN2047,
WINDOW_HANN31,
WINDOW_HANN63,
WINDOW_SQUARE47
};
enum {
KERNEL_LANCZOS2, KERNEL_LANCZOS3, KERNEL_TENT
};
enum eStreamType {
STREAM_TYPE_FILE, STREAM_TYPE_PA, STREAM_TYPE_STDIN
};
typedef struct {
Point pt;
int ch;
double t;
bool exists;
} PixelSample;
std::vector<PixelSample> pixelSamplingPoints(SSTVMode mode);
uint8_t clip (double a);
double fclip (double a);
void createGUI ();
double deg2rad (double Deg);
std::string GetFSK ();
bool rxVideo (SSTVMode Mode, DSPworker *dsp);
SSTVMode nextHeader (DSPworker*);
int initPcmDevice (std::string);
void populateDeviceList ();
void readPcm (int numsamples);
void saveCurrentPic();
void setVU (double *Power, int FFTLen, int WinIdx, bool ShowWin);
int startGui (int, char**);
std::tuple<bool,double,double> findMelody (Wave, Melody, double, double, double);
SSTVMode readVIS (DSPworker*, double fshift=0);
Wave* upsample (Wave orig, size_t factor, int kern_type);
SSTVMode vis2mode (int);
void runTest(const char*);
size_t maxIndex (std::vector<double>);
uint8_t freq2lum(double);
void printWave(Wave, double);
#endif

Wyświetl plik

@ -1,296 +0,0 @@
#ifndef _COMMON_H_
#define _COMMON_H_
// moment length only affects length of global delay, I/O interval,
// and maximum window size.
#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)
#define READ_CHUNK_LEN ((MOMENT_LEN+1)/2)
#include <iostream>
#include "portaudio.h"
#include "sndfile.hh"
#include "fftw3.h"
#include "gtkmm.h"
struct Point {
int x;
int 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) {}
};
using Wave = std::vector<double>;
using Melody = std::vector<Tone>;
enum WindowType {
WINDOW_CHEB47 = 0,
WINDOW_HANN95,
WINDOW_HANN127,
WINDOW_HANN255,
WINDOW_HANN511,
WINDOW_HANN1023,
WINDOW_HANN2047,
WINDOW_HANN31,
WINDOW_HANN63,
WINDOW_SQUARE47
};
enum SSTVMode {
MODE_UNKNOWN=0,
MODE_M1, MODE_M2, MODE_M3, MODE_M4, MODE_S1,
MODE_S2, MODE_SDX, MODE_R72, MODE_R36, MODE_R24,
MODE_R24BW, MODE_R12BW, MODE_R8BW, MODE_PD50, MODE_PD90,
MODE_PD120, MODE_PD160, MODE_PD180, MODE_PD240, MODE_PD290,
MODE_P3, MODE_P5, MODE_P7, MODE_W2120, MODE_W2180
};
enum eColorEnc {
COLOR_GBR, COLOR_RGB, COLOR_YUV, COLOR_MONO
};
enum eSyncOrder {
SYNC_SIMPLE, SYNC_SCOTTIE
};
enum eSubSamp {
SUBSAMP_444, SUBSAMP_422_YUV, SUBSAMP_420_YUYV, SUBSAMP_440_YUVY
};
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;
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:
DSPworker();
void openAudioFile (std::string);
void openPortAudio ();
void readMore ();
double forward (unsigned);
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> >, WindowType wintype=WINDOW_HANN2047);
WindowType bestWindowFor (SSTVMode, double SNR=99);
double videoSNR();
double lum(SSTVMode, bool is_adaptive=false);
bool is_open ();
double get_t ();
bool isLive();
bool hasSync();
private:
mutable Glib::Threads::Mutex Mutex;
short cirbuf_[CIRBUF_LEN * 2];
int cirbuf_head_;
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_;
};
class SlowGUI {
public:
SlowGUI();
void ping();
private:
Gtk::Button *button_abort;
Gtk::Button *button_browse;
Gtk::Button *button_clear;
Gtk::Button *button_start;
Gtk::ComboBoxText *combo_card;
Gtk::ComboBox *combo_mode;
Gtk::Entry *entry_picdir;
Gtk::EventBox *eventbox_img;
Gtk::Frame *frame_manual;
Gtk::Frame *frame_slant;
Gtk::Grid *grid_vu;
Gtk::IconView *iconview;
Gtk::Image *image_devstatus;
Gtk::Image *image_pwr;
Gtk::Image *image_rx;
Gtk::Image *image_snr;
Gtk::Label *label_fskid;
Gtk::Label *label_lastmode;
Gtk::Label *label_utc;
Gtk::MenuItem *menuitem_about;
Gtk::MenuItem *menuitem_quit;
Gtk::SpinButton *spin_shift;
Gtk::Widget *statusbar;
Gtk::ToggleButton *tog_adapt;
Gtk::ToggleButton *tog_fsk;
Gtk::ToggleButton *tog_rx;
Gtk::ToggleButton *tog_save;
Gtk::ToggleButton *tog_setedge;
Gtk::ToggleButton *tog_slant;
Gtk::Window *window_about;
Gtk::Window *window_main;
};
extern Glib::RefPtr<Gdk::Pixbuf> pixbuf_PWR;
extern Glib::RefPtr<Gdk::Pixbuf> pixbuf_SNR;
extern Glib::RefPtr<Gdk::Pixbuf> pixbuf_rx;
extern Glib::RefPtr<Gdk::Pixbuf> pixbuf_disp;
extern Gtk::ListStore *savedstore;
extern Glib::KeyFile config;
typedef struct {
Point pt;
int Channel;
double Time;
} PixelSample;
std::vector<PixelSample> getPixelSamplingPoints(SSTVMode mode);
double power (fftw_complex coeff);
guint8 clip (double a);
double fclip (double a);
void createGUI ();
double deg2rad (double Deg);
std::string GetFSK ();
bool rxVideo (SSTVMode Mode, DSPworker *dsp);
SSTVMode nextHeader (DSPworker*);
int initPcmDevice (std::string);
void *Listen ();
void populateDeviceList ();
void readPcm (int numsamples);
void saveCurrentPic();
void setVU (double *Power, int FFTLen, int WinIdx, bool ShowWin);
int startGui (int, char**);
void resync (Picture* pic);
double gaussianPeak (double y1, double y2, double y3);
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, 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 ();
void evt_changeDevices ();
void evt_chooseDir ();
void evt_clearPix ();
void evt_clickimg (Gtk::Widget*, GdkEventButton*, Gdk::WindowEdge);
void evt_deletewindow ();
void evt_GetAdaptive ();
void evt_ManualStart ();
void evt_show_about ();
class MyPortaudioClass{
int myMemberCallback(const void *input, void *output,
unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags);
static int myPaCallback(
const void *input, void *output,
unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData ) {
return ((MyPortaudioClass*)userData)
->myMemberCallback(input, output, frameCount, timeInfo, statusFlags);
}
};
#endif

Wyświetl plik

@ -1,9 +1,16 @@
#include "common.hh"
#include "portaudio.h"
#include <cmath>
#include "dsp.h"
#include "gui.h"
DSPworker::DSPworker() : Mutex(), please_stop_(false) {
DSPworker::DSPworker() : cirbuf_head_(MOMENT_LEN/2), cirbuf_tail_(0), cirbuf_fill_count_(0), is_open_(false), t_(0), fshift_(0), sync_window_(WINDOW_HANN511) {
Wave cheb47 = {
window_[WINDOW_HANN95] = window::Hann(95);
window_[WINDOW_HANN127] = window::Hann(127);
window_[WINDOW_HANN255] = window::Hann(255);
window_[WINDOW_HANN511] = window::Hann(511);
window_[WINDOW_HANN1023] = window::Hann(1023);
window_[WINDOW_HANN2047] = window::Hann(2047);
window_[WINDOW_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,
@ -13,49 +20,37 @@ 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
};
Wave 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_ = (fftw_complex*) fftw_alloc_complex(sizeof(fftw_complex) * FFT_LEN_BIG);
fft_inbuf_ = new fftw_complex[FFT_LEN_BIG];//fftw_alloc_complex(FFT_LEN_BIG);
if (fft_inbuf_ == NULL) {
perror("GetVideo: Unable to allocate memory for FFT");
perror("unable to allocate memory for FFT");
exit(EXIT_FAILURE);
}
fft_outbuf_ = (fftw_complex*) fftw_alloc_complex(sizeof(fftw_complex) * FFT_LEN_BIG);
fft_outbuf_ = new fftw_complex[FFT_LEN_BIG];//fftw_alloc_complex(FFT_LEN_BIG);
if (fft_outbuf_ == NULL) {
perror("GetVideo: Unable to allocate memory for FFT");
perror("unable to allocate memory for FFT");
fftw_free(fft_inbuf_);
exit(EXIT_FAILURE);
}
memset(fft_inbuf_, 0, sizeof(fftw_complex) * FFT_LEN_BIG);
for (size_t i=0; i<FFT_LEN_BIG; i++) {
fft_inbuf_[i][0] = fft_inbuf_[i][1] = 0;
fft_outbuf_[i][0] = fft_outbuf_[i][1] = 0;
}
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_ = MOMENT_LEN/2;
cirbuf_fill_count_ = 0;
is_open_ = false;
fshift_ = 0;
t_ = 0;
sync_window_ = WINDOW_HANN511;
}
void DSPworker::listenLoop (SlowGUI* caller) {
if (!is_open_)
openPortAudio();
while (is_open_) {
SSTVMode mode = nextHeader(this);
if (mode != MODE_UNKNOWN)
rxVideo(mode, this);
}
}
void DSPworker::openAudioFile (std::string fname) {
@ -74,9 +69,8 @@ void DSPworker::openAudioFile (std::string fname) {
stream_type_ = STREAM_TYPE_FILE;
t_ = 0;
num_chans_ = file_.channels();
read_buffer_ = new short [READ_CHUNK_LEN * num_chans_];
read_buffer_ = new int16_t [READ_CHUNK_LEN * num_chans_];
is_open_ = true;
readMore();
@ -111,7 +105,7 @@ void DSPworker::openPortAudio () {
NULL, /* no callback, use blocking API */
NULL ); /* no callback, so no callback userData */
if (err == paNoError) {
if (!err) {
err = Pa_StartStream( pa_stream_ );
const PaStreamInfo *streaminfo;
@ -121,7 +115,7 @@ void DSPworker::openPortAudio () {
stream_type_ = STREAM_TYPE_PA;
num_chans_ = 1;
read_buffer_ = new short [READ_CHUNK_LEN * num_chans_];
read_buffer_ = new int16_t [READ_CHUNK_LEN * num_chans_]();
if (err == paNoError) {
is_open_ = true;
@ -156,44 +150,55 @@ bool DSPworker::isLive() {
}
void DSPworker::readMore () {
sf_count_t samplesread = 0;
size_t framesread = 0;
if (is_open_) {
if (stream_type_ == STREAM_TYPE_FILE) {
samplesread = file_.readf(read_buffer_, READ_CHUNK_LEN);
if (samplesread < READ_CHUNK_LEN)
sf_count_t fr = file_.readf(read_buffer_, READ_CHUNK_LEN);
if (fr < READ_CHUNK_LEN) {
is_open_ = false;
if (fr < 0) {
framesread = 0;
} else {
framesread = fr;
}
}
if (num_chans_ > 1) {
for (int i=0; i<READ_CHUNK_LEN; i++) {
for (size_t i=0; i<READ_CHUNK_LEN; i++) {
read_buffer_[i] = read_buffer_[i*num_chans_];
}
}
} else if (stream_type_ == STREAM_TYPE_PA) {
samplesread = READ_CHUNK_LEN;
framesread = READ_CHUNK_LEN;
int err = Pa_ReadStream( pa_stream_, read_buffer_, READ_CHUNK_LEN );
if (err != paNoError)
if (err) {
fprintf(stderr,"\nPortAudio: %s\n",Pa_GetErrorText(err));
is_open_ = false;
}
}
}
int cirbuf_fits = std::min(CIRBUF_LEN - cirbuf_head_, (int)samplesread);
size_t cirbuf_fits = std::min(CIRBUF_LEN - cirbuf_head_, (int)framesread);
memcpy(&cirbuf_[cirbuf_head_], read_buffer_, cirbuf_fits * sizeof(read_buffer_[0]));
for (size_t i=0; i<cirbuf_fits; i++)
cirbuf_[cirbuf_head_ + i] = read_buffer_[i];
// wrapped around
if (samplesread > cirbuf_fits) {
memcpy(&cirbuf_[0], &read_buffer_[cirbuf_fits], (samplesread - cirbuf_fits) * sizeof(read_buffer_[0]));
// wrap around
if (framesread > cirbuf_fits) {
for (size_t i=0; i<(framesread - cirbuf_fits); i++)
cirbuf_[i] = read_buffer_[cirbuf_fits + i];
}
// mirror
memcpy(&cirbuf_[CIRBUF_LEN], &cirbuf_[0], CIRBUF_LEN);
for (size_t i=0; i<CIRBUF_LEN; i++)
cirbuf_[CIRBUF_LEN + i] = cirbuf_[i];
cirbuf_head_ = (cirbuf_head_ + samplesread) % CIRBUF_LEN;
cirbuf_fill_count_ += samplesread;
cirbuf_head_ = (cirbuf_head_ + framesread) % CIRBUF_LEN;
cirbuf_fill_count_ += framesread;
cirbuf_fill_count_ = std::min(cirbuf_fill_count_, CIRBUF_LEN);
}
@ -227,7 +232,12 @@ void DSPworker::forward_to_time(double sec) {
// the current moment, windowed
void DSPworker::windowedMoment (WindowType win_type, fftw_complex *result) {
// will be written over buf
// which MUST fit FFT_LEN_BIG * fftw_complex
void DSPworker::windowedMoment (WindowType win_type, fftw_complex* buf) {
for (size_t i=0; i<FFT_LEN_BIG; i++)
buf[i][0] = buf[i][1] = 0;
//double if_phi = 0;
for (int i = 0; i < MOMENT_LEN; i++) {
@ -244,7 +254,7 @@ void DSPworker::windowedMoment (WindowType win_type, fftw_complex *result) {
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;
buf[win_i][0] = buf[win_i][1] = a;
}
}
@ -252,32 +262,32 @@ void DSPworker::windowedMoment (WindowType win_type, fftw_complex *result) {
double DSPworker::peakFreq (double minf, double maxf, WindowType wintype) {
int fft_len = (window_[wintype].size() <= FFT_LEN_SMALL ? FFT_LEN_SMALL : FFT_LEN_BIG);
unsigned fft_len = (window_[wintype].size() <= FFT_LEN_SMALL ? FFT_LEN_SMALL : FFT_LEN_BIG);
fftw_complex windowed[window_[wintype].size()];
double Mag[fft_len/2 + 1];
double* mag;
mag = new double [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]));
windowedMoment(wintype, fft_inbuf_);
//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]))
size_t peakBin = 0;
unsigned lobin = freq2bin(minf, fft_len);
unsigned hibin = freq2bin(maxf, fft_len);
for (size_t i = lobin-1; i <= hibin+1; i++) {
mag[i] = complexMag(fft_outbuf_[i]);
if ( (i >= lobin && i <= hibin && i<(fft_len/2+1) ) &&
(peakBin == 0 || mag[i] > mag[peakBin]))
peakBin = i;
}
double result = peakBin + gaussianPeak(Mag[peakBin-1], Mag[peakBin], Mag[peakBin+1]);
double result = peakBin + gaussianPeak(mag[peakBin-1], mag[peakBin], mag[peakBin+1]);
// In Hertz
result = result / fft_len * samplerate_ + fshift_;
// cheb47 @ 44100 can't resolve <1700 Hz nominal
// cheb47 @ 44100 can't resolve <1700 Hz
if (result < 1700 && wintype == WINDOW_CHEB47)
result = peakFreq (minf, maxf, WINDOW_HANN95);
@ -287,12 +297,13 @@ double DSPworker::peakFreq (double minf, double maxf, WindowType wintype) {
Wave DSPworker::bandPowerPerHz(std::vector<std::vector<double> > bands, WindowType wintype) {
int fft_len = (window_[wintype].size() <= FFT_LEN_SMALL ? FFT_LEN_SMALL : FFT_LEN_BIG);
fftw_complex windowed[window_[wintype].size()];
unsigned fft_len = (window_[wintype].size() <= FFT_LEN_SMALL ? FFT_LEN_SMALL : FFT_LEN_BIG);
//fftw_complex* windowed;
//windowed = new fftw_complex[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]));
windowedMoment(wintype, fft_inbuf_);
//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_);
Wave result;
@ -304,7 +315,7 @@ Wave DSPworker::bandPowerPerHz(std::vector<std::vector<double> > bands, WindowTy
P += pow(complexMag(fft_outbuf_[i]), 2);
nbins++;
}
P = P/(binwidth*nbins);
P = (binwidth*nbins == 0 ? 0 : P/(binwidth*nbins));
result.push_back(P);
}
return result;
@ -320,7 +331,7 @@ WindowType DSPworker::bestWindowFor(SSTVMode Mode, double SNR) {
else if (SNR >= 8) WinType = WINDOW_HANN127;
else if (SNR >= 5) WinType = WINDOW_HANN255;
else if (SNR >= 4) WinType = WINDOW_HANN511;
else if (SNR >= -3) WinType = WINDOW_HANN1023;
else if (SNR >= -7) WinType = WINDOW_HANN1023;
else WinType = WINDOW_HANN2047;
return WinType;
@ -328,7 +339,7 @@ WindowType DSPworker::bestWindowFor(SSTVMode Mode, double SNR) {
double DSPworker::videoSNR () {
if (t_ >= next_snr_time_) {
std::vector<double> bands = bandPowerPerHz({{200,1000}, {1500,2300}, {2700, 2900}});
std::vector<double> bands = bandPowerPerHz({{FREQ_SYNC-1000,FREQ_SYNC-200}, {FREQ_BLACK,FREQ_WHITE}, {FREQ_WHITE+400, FREQ_WHITE+700}});
double Pvideo_plus_noise = bands[1];
double Pnoise_only = (bands[0] + bands[2]) / 2;
double Psignal = Pvideo_plus_noise - Pnoise_only;
@ -341,11 +352,15 @@ double DSPworker::videoSNR () {
return SNR_;
}
bool DSPworker::hasSync () {
std::vector<double> bands = bandPowerPerHz({{1150,1250}, {1500,2300}}, sync_window_);
return (bands[0] > 2 * bands[1]);
double DSPworker::syncPower () {
std::vector<double> bands = bandPowerPerHz({{FREQ_SYNC-50,FREQ_SYNC+50}, {FREQ_BLACK,FREQ_WHITE}}, sync_window_);
double sync;
if (bands[1] == 0.0 || bands[0] > 4 * bands[1]) {
sync = 2.0;
} else {
sync = bands[0] / (2 * bands[1]);
}
return sync;
}
double DSPworker::lum (SSTVMode mode, bool is_adaptive) {
@ -354,8 +369,8 @@ double DSPworker::lum (SSTVMode mode, bool is_adaptive) {
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));
double freq = peakFreq(FREQ_BLACK, FREQ_WHITE, win_type);
return fclip((freq - FREQ_BLACK) / (FREQ_WHITE - FREQ_BLACK));
}
// param: y values around peak
@ -402,80 +417,122 @@ double gaussianPeak (double y1, double y2, double y3) {
}*/
Wave Hann (size_t winlen) {
Wave result(winlen);
for (size_t i=0; i < winlen; i++)
result[i] = 0.5 * (1 - cos( (2 * M_PI * i) / (winlen)) );
return result;
}
namespace window {
Wave Hann (size_t winlen) {
Wave result(winlen);
for (size_t i=0; i < winlen; i++)
result[i] = 0.5 * (1 - cos( (2 * M_PI * i) / (winlen)) );
return result;
}
Wave Blackmann (size_t winlen) {
Wave result(winlen);
for (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);
Wave Blackmann (size_t winlen) {
Wave result(winlen);
for (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);
return result;
}
return result;
}
Wave Rect (size_t winlen) {
Wave result(winlen);
double sigma = 0.4;
for (size_t i=0; i < winlen; i++)
result[i] = exp(-0.5*((i-(winlen-1)/2)/(sigma*(winlen-1)/2)));
Wave Rect (size_t winlen) {
Wave result(winlen);
double sigma = 0.4;
for (size_t i=0; i < winlen; i++)
result[i] = exp(-0.5*((i-(winlen-1)/2)/(sigma*(winlen-1)/2)));
return result;
}
return result;
}
Wave Gauss (size_t winlen) {
Wave result(winlen);
for (size_t i=0; i < winlen; i++)
result[i] = 1;
Wave Gauss (size_t winlen) {
Wave result(winlen);
for (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));
return result;
}
}
double sinc (double x) {
return (x == 0 ? 1 : sin(M_PI*x) / (M_PI*x));
}
Wave upsampleLanczos(Wave orig, int factor, size_t a) {
Wave result(orig.size()*factor);
int kernel_len = factor*a*2 + 1;
// make kernel
Wave lanczos(kernel_len);
for (int i=0; i<kernel_len; i++) {
double x_kern = (1.0*i/(kernel_len-1) - .5)*2*a;
double x_wind = 2.0*i/(kernel_len-1) - 1;
lanczos[i] = sinc(x_kern) * sinc(x_wind);
namespace kernel {
Wave Lanczos (size_t kernel_len, size_t a) {
Wave kern(kernel_len);
for (size_t i=0; i<kernel_len; i++) {
double x_kern = (1.0*i/(kernel_len-1) - .5)*2*a;
double x_wind = 2.0*i/(kernel_len-1) - 1;
kern[i] = sinc(x_kern) * sinc(x_wind);
}
return kern;
}
// convolution
for (int orig_i=-a; orig_i<int(orig.size()+a); orig_i++) {
double orig_sample;
if (orig_i < 0)
orig_sample = orig[0];
else if (orig_i > int(orig.size()-1))
orig_sample = orig[orig.size()-1];
else
orig_sample = orig[orig_i];
Wave Tent (size_t kernel_len) {
Wave kern(kernel_len);
for (size_t i=0; i<kernel_len; i++) {
double x = 1.0*i/(kernel_len-1);
kern[i] = 1-2*fabs(x-0.5);
}
return kern;
}
}
if (orig_sample != 0) {
for (int kernel_idx=0; kernel_idx<kernel_len; kernel_idx++) {
int i_new = (orig_i+.5)*factor -kernel_len/2 + kernel_idx;
double complexMag (fftw_complex coeff) {
return sqrt(pow(coeff[0],2) + pow(coeff[1],2));
}
Wave convolve (Wave sig, Wave kernel, bool wrap_around) {
assert (kernel.size() % 2 == 1);
Wave result(sig.size());
for (size_t i=0; i<sig.size(); i++) {
for (size_t i_kern=0; i_kern<kernel.size(); i_kern++) {
int i_new = i - kernel.size()/2 + i_kern;
if (wrap_around) {
if (i_new < 0)
i_new += result.size();
result[i_new % result.size()] += sig[i] * kernel[i_kern];
} else {
if (i_new >= 0 && i_new <= int(result.size()-1))
result[i_new] += orig_sample * lanczos[kernel_idx];
result[i_new] += sig[i] * kernel[i_kern];
}
}
}
return result;
}
Wave* upsample (Wave orig, size_t factor, int kern_type) {
Wave kern;
if (kern_type == KERNEL_LANCZOS2) {
kern = kernel::Lanczos(factor*2*2 + 1, 2);
} else if (kern_type == KERNEL_LANCZOS3) {
kern = kernel::Lanczos(factor*3*2 + 1, 3);
} else if (kern_type == KERNEL_TENT) {
kern = kernel::Tent(factor*2 + 1);
}
Wave padded(orig.size() * factor);
for (size_t i=0; i<orig.size(); i++) {
padded[i * factor] = orig[i];
}
padded.insert(padded.begin(), factor-1, 0);
padded.insert(padded.begin(), orig[0]);
padded.push_back(orig[orig.size()-1]);
Wave* filtered = new Wave(convolve(padded, kern));
filtered->erase(filtered->begin(), filtered->begin()+factor/2);
filtered->erase(filtered->end()-factor/2, filtered->end());
return filtered;
}
Wave deriv (Wave wave) {
Wave result;
for (size_t i=1; i<wave.size(); i++)
@ -515,22 +572,6 @@ std::vector<double> derivPeaks (Wave wave, size_t n) {
return result;
}
Wave rms(Wave orig, int window_width) {
Wave result(orig.size());
Wave pool(window_width);
int pool_ptr = 0;
double total = 0;
for (size_t i=0; i<orig.size(); i++) {
total -= pool[pool_ptr];
pool[pool_ptr] = pow(orig[i], 2);
total += pool[pool_ptr];
result[i] = sqrt(total / window_width);
pool_ptr = (pool_ptr+1) % 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;
@ -556,7 +597,7 @@ std::vector<int> readFSK (DSPworker *dsp, double baud_rate, double cent_freq, do
* (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) {
std::tuple<bool,double,double> findMelody (Wave wave, Melody melody, double dt, double min_shift, double max_shift) {
bool was_found = true;
int start_at = 0;
double avg_fdiff = 0;
@ -569,8 +610,8 @@ std::tuple<bool,double,double> findMelody (Wave wave, Melody melody, double dt)
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;
double err_f = delta_f - delta_f_ref;
was_found = was_found && (fabs(err_f) < freq_margin);
}
start_at = wave.size() - (t / dt);
t += melody[i].dur;
@ -616,6 +657,9 @@ std::tuple<bool,double,double> findMelody (Wave wave, Melody melody, double dt)
}
if (avg_fdiff < min_shift || avg_fdiff > max_shift)
was_found = false;
return { was_found, avg_fdiff, tshift };
}

119
src/dsp.h 100644
Wyświetl plik

@ -0,0 +1,119 @@
#ifndef DSP_H_
#define DSP_H_
#include "portaudio.h"
#include <sndfile.hh>
#include "fftw3.h"
#include "common.h"
// moment length only affects length of global delay, I/O interval,
// and maximum window size.
#define MOMENT_LEN 2047
#define FFT_LEN_SMALL 1024
#define FFT_LEN_BIG 2048
#define CIRBUF_LEN_FACTOR 8
#define CIRBUF_LEN ((MOMENT_LEN+1)*CIRBUF_LEN_FACTOR)
#define READ_CHUNK_LEN ((MOMENT_LEN+1)/2)
#define FREQ_MIN 500.0
#define FREQ_MAX 3300.0
#define FREQ_BLACK 1500.0
#define FREQ_WHITE 2300.0
#define FREQ_SYNC 1200.0
class SlowGUI;
namespace window {
Wave Hann (size_t);
Wave Blackmann (size_t);
Wave Rect (size_t);
Wave Gauss (size_t);
}
class DSPworker {
public:
DSPworker();
void openAudioFile (std::string);
void openPortAudio ();
void readMore ();
double forward (unsigned);
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> >, WindowType wintype=WINDOW_HANN2047);
WindowType bestWindowFor (SSTVMode, double SNR=99);
double videoSNR();
double lum(SSTVMode, bool is_adaptive=false);
bool is_open ();
double get_t ();
bool isLive ();
double syncPower ();
void listenLoop (SlowGUI* caller);
private:
//mutable Glib::Threads::Mutex Mutex;
int16_t cirbuf_[CIRBUF_LEN * 2];
int cirbuf_head_;
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_;
};
class MyPortaudioClass{
int myMemberCallback(const void *input, void *output,
unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags);
static int myPaCallback(
const void *input, void *output,
unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData ) {
return ((MyPortaudioClass*)userData)
->myMemberCallback(input, output, frameCount, timeInfo, statusFlags);
}
};
Wave convolve (Wave, Wave, bool wrap_around=false);
Wave deriv (Wave);
Wave peaks (Wave, size_t);
Wave derivPeaks (Wave, size_t);
Wave rms (Wave, int);
Wave* upsample (Wave orig, size_t factor, int kern_type);
std::vector<int> readFSK (DSPworker*, double, double, double, size_t);
double gaussianPeak (double y1, double y2, double y3);
double power (fftw_complex coeff);
double complexMag (fftw_complex coeff);
#endif

Wyświetl plik

@ -1,4 +1,4 @@
#include "common.hh"
#include "common.h"
/*
*

Wyświetl plik

@ -1,6 +1,6 @@
#include "gui.hh"
#include "gui.h"
SlowGUI::SlowGUI() {
SlowGUI::SlowGUI() : worker_thread_(nullptr), worker_() {
Glib::RefPtr<Gtk::Application> app =
Gtk::Application::create("com.windytan.slowrx");
@ -75,6 +75,9 @@ SlowGUI::SlowGUI() {
window_main->show_all();
worker_thread_ = Glib::Threads::Thread::create(
sigc::bind(sigc::mem_fun(worker_, &DSPworker::listenLoop), this));
app->run(*window_main);
}

75
src/gui.h 100644
Wyświetl plik

@ -0,0 +1,75 @@
#ifndef GUI_H
#define GUI_H
#include "gtkmm.h"
#include "common.h"
#include "dsp.h"
class SlowGUI {
public:
SlowGUI();
void ping();
private:
Gtk::Button *button_abort;
Gtk::Button *button_browse;
Gtk::Button *button_clear;
Gtk::Button *button_start;
Gtk::ComboBoxText *combo_card;
Gtk::ComboBox *combo_mode;
Gtk::Entry *entry_picdir;
Gtk::EventBox *eventbox_img;
Gtk::Frame *frame_manual;
Gtk::Frame *frame_slant;
Gtk::Grid *grid_vu;
Gtk::IconView *iconview;
Gtk::Image *image_devstatus;
Gtk::Image *image_pwr;
Gtk::Image *image_rx;
Gtk::Image *image_snr;
Gtk::Label *label_fskid;
Gtk::Label *label_lastmode;
Gtk::Label *label_utc;
Gtk::MenuItem *menuitem_about;
Gtk::MenuItem *menuitem_quit;
Gtk::SpinButton *spin_shift;
Gtk::Widget *statusbar;
Gtk::ToggleButton *tog_adapt;
Gtk::ToggleButton *tog_fsk;
Gtk::ToggleButton *tog_rx;
Gtk::ToggleButton *tog_save;
Gtk::ToggleButton *tog_setedge;
Gtk::ToggleButton *tog_slant;
Gtk::Window *window_about;
Gtk::Window *window_main;
Glib::Threads::Thread* worker_thread_;
DSPworker worker_;
};
extern Glib::RefPtr<Gdk::Pixbuf> pixbuf_PWR;
extern Glib::RefPtr<Gdk::Pixbuf> pixbuf_SNR;
extern Glib::RefPtr<Gdk::Pixbuf> pixbuf_rx;
extern Glib::RefPtr<Gdk::Pixbuf> pixbuf_disp;
extern Gtk::ListStore *savedstore;
extern Glib::KeyFile config;
void evt_AbortRx ();
void evt_changeDevices ();
void evt_chooseDir ();
void evt_clearPix ();
void evt_clickimg (Gtk::Widget*, GdkEventButton*, Gdk::WindowEdge);
void evt_deletewindow ();
void evt_GetAdaptive ();
void evt_ManualStart ();
void evt_show_about ();
#endif // GUI_H

Wyświetl plik

@ -1,7 +0,0 @@
#ifndef GUI_H
#define GUI_H
#include <gtkmm.h>
#include "common.hh"
#endif // GUI_H

Wyświetl plik

@ -1,10 +1,11 @@
#include "common.hh"
#include "common.h"
#include "dsp.h"
#include <cmath>
SSTVMode nextHeader (DSPworker *dsp) {
double dt = 5e-3;
int bitlen = 30e-3 / dt;
SSTVMode mode = MODE_UNKNOWN;
int ptr_read = 0;
@ -28,14 +29,14 @@ SSTVMode nextHeader (DSPworker *dsp) {
while ( dsp->is_open() ) {
cirbuf_header[ptr_read] = dsp->peakFreq(500, 3300, WINDOW_HANN1023);
cirbuf_header[ptr_read] = dsp->peakFreq(FREQ_MIN, FREQ_MAX, WINDOW_HANN1023);
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,double> has = findMelody(freq, mmsstv_vox, dt);
std::tuple<bool,double,double> has = findMelody(freq, mmsstv_vox, dt, -800, 800);
if (std::get<0>(has)) {
fshift = std::get<1>(has);
double tshift = std::get<2>(has);
@ -53,7 +54,7 @@ SSTVMode nextHeader (DSPworker *dsp) {
}
}
has = findMelody(freq, robot_vox, dt);
has = findMelody(freq, robot_vox, dt, -800, 800);
if (std::get<0>(has)) {
fshift = std::get<1>(has);
double tshift = std::get<2>(has);
@ -81,7 +82,7 @@ SSTVMode nextHeader (DSPworker *dsp) {
SSTVMode readVIS(DSPworker* dsp, double fshift) {
int vis = 0;
unsigned vis = 0;
SSTVMode mode = MODE_UNKNOWN;
std::vector<int> bits = readFSK(dsp, 33.333, 1200+fshift, 100, 8);
int parity_rx=0;
@ -91,17 +92,19 @@ SSTVMode readVIS(DSPworker* dsp, double fshift) {
}
vis &= 0x7F;
if (vis2mode.find(vis) == vis2mode.end()) {
mode = vis2mode(vis);
if (mode == MODE_UNKNOWN) {
fprintf(stderr,"(unknown mode %dd=%02Xh)\n",vis,vis);
} else {
if ((parity_rx % 2) != ModeSpec[vis2mode[vis]].VISParity) {
if ((parity_rx % 2) != ModeSpec[mode].vis_parity) {
fprintf(stderr,"(parity fail)\n");
} else {
fprintf(stderr," got VIS: %dd / %02Xh (%s)", vis, vis,
ModeSpec[vis2mode[vis]].Name.c_str());
mode = vis2mode[vis];
ModeSpec[mode].name.c_str());
}
}
return mode;
}

Wyświetl plik

@ -1,30 +1,31 @@
#include "common.hh"
#include "modespec.h"
#include <map>
/*
* SSTV mode specifications
* ========================
*
* Name Full human-readable mode identifier
* ShortName Abbreviation to be used in filenames
* NumLines Total number of scanlines
* HeaderLines Number of lines reserved for header, excluded from 4:3 ratio
* ScanPixels Pixel samples per scanline and channel
* tSync Duration of horizontal sync pulse
* tPorch Duration of sync porch pulse
* tSep Duration of channel separator pulse (+ separator porch)
* tScan Duration of visible part of a channel scan (or Y scan if YUV)
* tLine Time from the beginning of a sync pulse to the beginning
* of the next one
* SyncOrder Positioning of sync pulse (SYNC_SIMPLE, SYNC_SCOTTIE)
* 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)
* name Full human-readable mode identifier
* short_name Abbreviation to be used in filenames
* num_lines Total number of scanlines
* header_lines Number of scanlines excluded from 4:3 ratio
* scan_pixels Pixel samples per scanline and channel
* t_sync Duration of horizontal sync pulse
* t_porch Duration of sync porch
* t_sep Duration of channel separator + channel porch
* t_scan Duration of visible part of a channel scan (or Y scan if
* chroma subsampling is used)
* t_period Time from the beginning of a sync pulse to the beginning
* of the next one
* sync_type Positioning of sync pulse (SYNC_SIMPLE, SYNC_SCOTTIE, SYNC_PD)
* subsampling Chroma subsampling and channel order for YUV
* color_enc Color format (COLOR_GBR, COLOR_RGB, COLOR_YUV, COLOR_MONO)
* vis_parity Parity mode in VIS (PARITY_EVEN, PARITY_ODD)
*
*
* All timings are in seconds.
*
* References:
* Sources:
*
* JL Barber N7CXI (2000): "Proposal for SSTV Mode Specifications".
* Presented at the Dayton SSTV forum, 20 May 2000.
@ -34,426 +35,441 @@
*
* Dave Jones KB4YZ (1998): "List of SSTV Modes with VIS Codes".
* <http://www.tima.com/~djones/vis.txt>
*
* Martin Bruchanov OK2MNM (2013): "Image Communication on Short
* Waves". <http://www.sstv-handbook.com>
*/
_ModeSpec ModeSpec[] = {
_ModeSpec ModeSpec[] = {
[MODE_M1] = { // N7CXI, 2000
.Name = "Martin M1",
.ShortName = "M1",
.ScanPixels = 320,
.NumLines = 256,
.HeaderLines = 16,
.tSync = 4.862e-3,
.tPorch = 0.572e-3,
.tSep = 0.572e-3,
.tScan = 146.432e-3,
.tLine = 446.446e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_M1] = { // OK
.name = "Martin M1",
.short_name = "M1",
.scan_pixels = 320,
.num_lines = 256,
.header_lines = 16,
.t_sync = 4.862e-3,
.t_porch = 0.572e-3,
.t_sep = 0.572e-3,
.t_scan = 146.432e-3,
.t_period = 446.446e-3,
.family = MODE_MARTIN,
.color_enc = COLOR_GBR,
.vis = 0x2C,
.vis_parity = PARITY_EVEN },
[MODE_M2] = { // N7CXI, 2000
.Name = "Martin M2",
.ShortName = "M2",
.ScanPixels = 320,
.NumLines = 256,
.HeaderLines = 16,
.tScan = 73.216e-3,
.tLine = 226.7986e-3,
.tSync = 4.862e-3,
.tPorch = 0.572e-3,
.tSep = 0.572e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_M2] = { // OK
.name = "Martin M2",
.short_name = "M2",
.scan_pixels = 320,
.num_lines = 256,
.header_lines = 16,
.t_scan = 73.216e-3,
.t_period = 226.7980e-3,
.t_sync = 4.862e-3,
.t_porch = 0.572e-3,
.t_sep = 0.572e-3,
.family = MODE_MARTIN,
.color_enc = COLOR_GBR,
.vis = 0x28,
.vis_parity = PARITY_EVEN },
[MODE_M3] = { // KB4YZ, 1999
.Name = "Martin M3",
.ShortName = "M3",
.ScanPixels = 320,
.NumLines = 128,
.HeaderLines = 8,
.tScan = 73.216e-3,
.tLine = 446.446e-3,
.tSync = 4.862e-3,
.tPorch = 0.572e-3,
.tSep = 0.572e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_M3] = { // TODO
.name = "Martin M3",
.short_name = "M3",
.scan_pixels = 320,
.num_lines = 128,
.header_lines = 8,
.t_scan = 73.216e-3,
.t_period = 446.446e-3,
.t_sync = 4.862e-3,
.t_porch = 0.572e-3,
.t_sep = 0.572e-3,
.family = MODE_MARTIN,
.color_enc = COLOR_GBR,
.vis = 0x24,
.vis_parity = PARITY_EVEN },
[MODE_M4] = { // KB4YZ, 1999
.Name = "Martin M4",
.ShortName = "M4",
.ScanPixels = 320,
.NumLines = 128,
.HeaderLines = 8,
.tScan = 73.216e-3,
.tLine = 226.7986e-3,
.tSync = 4.862e-3,
.tPorch = 0.572e-3,
.tSep = 0.572e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_M4] = { // TODO
.name = "Martin M4",
.short_name = "M4",
.scan_pixels = 320,
.num_lines = 128,
.header_lines = 8,
.t_scan = 73.216e-3,
.t_period = 226.7986e-3,
.t_sync = 4.862e-3,
.t_porch = 0.572e-3,
.t_sep = 0.572e-3,
.family = MODE_MARTIN,
.color_enc = COLOR_GBR,
.vis = 0x20,
.vis_parity = PARITY_EVEN },
[MODE_S1] = { // N7CXI, 2000
.Name = "Scottie S1",
.ShortName = "S1",
.ScanPixels = 320,
.NumLines = 256,
.HeaderLines = 16,
.tSync = 9e-3,
.tPorch = 1.5e-3,
.tSep = 1.5e-3,
.tScan = 138.24e-3,
.tLine = 428.22e-3,
.SyncOrder = SYNC_SCOTTIE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_S1] = { // OK
.name = "Scottie S1",
.short_name = "S1",
.scan_pixels = 320,
.num_lines = 256,
.header_lines = 16,
.t_sync = 9e-3,
.t_porch = 1.5e-3,
.t_sep = 1.5e-3,
.t_scan = 138.24e-3,
.t_period = 428.22e-3,
.family = MODE_SCOTTIE,
.color_enc = COLOR_GBR,
.vis = 0x3C,
.vis_parity = PARITY_EVEN },
[MODE_S2] = { // N7CXI, 2000
.Name = "Scottie S2",
.ShortName = "S2",
.ScanPixels = 320,
.NumLines = 256,
.HeaderLines = 16,
.tScan = 88.064e-3,
.tLine = 277.692e-3,
.tSync = 9e-3,
.tPorch = 1.5e-3,
.tSep = 1.5e-3,
.SyncOrder = SYNC_SCOTTIE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_S2] = { // OK
.name = "Scottie S2",
.short_name = "S2",
.scan_pixels = 320,
.num_lines = 256,
.header_lines = 16,
.t_scan = 88.064e-3,
.t_period = 277.692e-3,
.t_sync = 9e-3,
.t_porch = 1.5e-3,
.t_sep = 1.5e-3,
.family = MODE_SCOTTIE,
.color_enc = COLOR_GBR,
.vis = 0x38,
.vis_parity = PARITY_EVEN },
[MODE_SDX] = { // N7CXI, 2000
.Name = "Scottie DX",
.ShortName = "SDX",
.ScanPixels = 320,
.NumLines = 256,
.HeaderLines = 16,
.tScan = 345.6e-3,
.tLine = 1050.3e-3,
.tSync = 9e-3,
.tPorch = 1.5e-3,
.tSep = 1.5e-3,
.SyncOrder = SYNC_SCOTTIE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_GBR,
.VISParity = PARITY_EVEN },
[MODE_SDX] = { // OK
.name = "Scottie DX",
.short_name = "SDX",
.scan_pixels = 320,
.num_lines = 256,
.header_lines = 16,
.t_scan = 345.6e-3,
.t_period = 1050.3e-3,
.t_sync = 9e-3,
.t_porch = 1.5e-3,
.t_sep = 1.5e-3,
.family = MODE_SCOTTIE,
.color_enc = COLOR_GBR,
.vis = 0x4C,
.vis_parity = PARITY_EVEN },
[MODE_R72] = { // N7CXI, 2000
.Name = "Robot 72",
.ShortName = "R72",
.ScanPixels = 320,
.NumLines = 240,
.HeaderLines = 0,
.tScan = 138e-3,
.tLine = 300e-3,
.tSync = 9e-3,
.tPorch = 3e-3,
.tSep = 6e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_422_YUV,
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_R72] = { // OK
.name = "Robot 72",
.short_name = "R72",
.scan_pixels = 320,
.num_lines = 240,
.header_lines = 0,
.t_scan = 138e-3,
.t_period = 300e-3,
.t_sync = 9e-3,
.t_porch = 3e-3,
.t_sep = 6e-3,
.family = MODE_ROBOT,
.color_enc = COLOR_YUV,
.vis = 0x0C,
.vis_parity = PARITY_EVEN },
[MODE_R36] = { // N7CXI, 2000
.Name = "Robot 36",
.ShortName = "R36",
.ScanPixels = 320,
.NumLines = 240,
.HeaderLines = 0,
.tScan = 88e-3,
.tLine = 150e-3,
.tSync = 9e-3,
.tPorch = 3e-3,
.tSep = 6e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_420_YUYV,
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_R36] = { // OK
.name = "Robot 36",
.short_name = "R36",
.scan_pixels = 320,
.num_lines = 240,
.header_lines = 0,
.t_scan = 90e-3,
.t_period = 150e-3,
.t_sync = 9e-3,
.t_porch = 1.5e-3,
.t_sep = 4.5e-3,
.family = MODE_ROBOT,
.color_enc = COLOR_YUV,
.vis = 0x08,
.vis_parity = PARITY_EVEN },
[MODE_R24] = { // KB4YZ, 1999
.Name = "Robot 24",
.ShortName = "R24",
.ScanPixels = 320,
.NumLines = 240,
.HeaderLines = 0,
.tScan = 66e-3,
.tLine = 150e-3,
.tSync = 9e-3,
.tPorch = 3e-3,
.tSep = 6e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_420_YUYV,
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_R24] = { // OK
.name = "Robot 24",
.short_name = "R24",
.scan_pixels = 160,
.num_lines = 120,
.header_lines = 0,
.t_scan = 93e-3,
.t_period = 200e-3,
.t_sync = 9e-3,
.t_porch = 0,
.t_sep = 3e-3,
.family = MODE_ROBOT,
.color_enc = COLOR_YUV,
.vis = 0x04,
.vis_parity = PARITY_EVEN },
[MODE_R24BW] = { // KB4YZ, 1999
.Name = "Robot 24 B/W",
.ShortName = "R24BW",
.ScanPixels = 320,
.NumLines = 240,
.HeaderLines = 0,
.tScan = 66e-3,
.tLine = 100e-3,
.tSync = 7e-3,
.tPorch = 0,
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_MONO,
.VISParity = PARITY_EVEN },
[MODE_R24BW] = { // TODO
.name = "Robot 24 B/W",
.short_name = "R24BW",
.scan_pixels = 320,
.num_lines = 240,
.header_lines = 0,
.t_scan = 93e-3,
.t_period = 100e-3,
.t_sync = 7e-3,
.t_porch = 0,
.t_sep = 0,
.family = MODE_ROBOT,
.color_enc = COLOR_MONO,
.vis = 0x0A,
.vis_parity = PARITY_EVEN },
[MODE_R12BW] = { // KB4YZ, 1999
.Name = "Robot 12 B/W",
.ShortName = "R12BW",
.ScanPixels = 320,
.NumLines = 120,
.HeaderLines = 0,
.tScan = 66e-3,
.tLine = 100e-3,
.tSync = 7e-3,
.tPorch = 0,
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_MONO,
.VISParity = PARITY_ODD },
[MODE_R12BW] = { // OK
.name = "Robot 12 B/W",
.short_name = "R12BW",
.scan_pixels = 160,
.num_lines = 120,
.header_lines = 0,
.t_scan = 93e-3,
.t_period = 100e-3,
.t_sync = 9e-3,
.t_porch = 0,
.t_sep = 0,
.family = MODE_ROBOT,
.color_enc = COLOR_MONO,
.vis = 0x06,
.vis_parity = PARITY_ODD },
[MODE_R8BW] = { // KB4YZ, 1999
.Name = "Robot 8 B/W",
.ShortName = "R8BW",
.ScanPixels = 320,
.NumLines = 120,
.HeaderLines = 0,
.tScan = 60e-3,
.tLine = 67e-3,
.tSync = 7e-3,
.tPorch = 0,
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_MONO,
.VISParity = PARITY_EVEN },
[MODE_W2120] = { // KB4YZ, 1999
.Name = "Wraase SC-2 120",
.ShortName = "W2120",
.ScanPixels = 320,
.NumLines = 256,
.HeaderLines = 16,
.tScan = 156.5025e-3,
.tLine = 475.530018e-3,
.tSync = 5.5225e-3,
.tPorch = 0.5e-3,
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_RGB,
.VISParity = PARITY_EVEN },
[MODE_R8BW] = { // OK
.name = "Robot 8 B/W",
.short_name = "R8BW",
.scan_pixels = 160,
.num_lines = 120,
.header_lines = 0,
.t_scan = 59e-3,
.t_period = 67e-3,
.t_sync = 10e-3,
.t_porch = 0,
.t_sep = 0,
.family = MODE_ROBOT,
.color_enc = COLOR_MONO,
.vis = 0x02,
.vis_parity = PARITY_EVEN },
[MODE_W2180] = { // N7CXI, 2000
.Name = "Wraase SC-2 180",
.ShortName = "W2180",
.ScanPixels = 320,
.NumLines = 256,
.HeaderLines = 16,
.tScan = 235e-3,
.tLine = 711.0225e-3,
.tSync = 5.5225e-3,
.tPorch = 0.5e-3,
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_RGB,
.VISParity = PARITY_EVEN },
[MODE_W260] = { // OK
.name = "Wraase SC-2 60",
.short_name = "SC2_60",
.scan_pixels = 320,
.num_lines = 256,
.header_lines = 16,
.t_scan = 78.3e-3,
.t_period = 240.833878e-3,
.t_sync = 5.5225e-3,
.t_porch = 0.5e-3,
.t_sep = 0,
.family = MODE_WRAASE2,
.color_enc = COLOR_RGB,
.vis = 0x3B,
.vis_parity = PARITY_EVEN },
[MODE_PD50] = { // N7CXI, 2000
.Name = "PD-50",
.ShortName = "PD50",
.ScanPixels = 320,
.NumLines = 256,
.HeaderLines = 16,
.tScan = 91.52e-3,
.tLine = 388.16e-3,
.tSync = 20e-3,
.tPorch = 2.08e-3,
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_PD90] = { // N7CXI, 2000
.Name = "PD-90",
.ShortName = "PD90",
.ScanPixels = 320,
.NumLines = 256,
.HeaderLines = 16,
.tScan = 170.240e-3,
.tLine = 703.04e-3,
.tSync = 20e-3,
.tPorch = 2.08e-3,
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_W2120] = { // OK
.name = "Wraase SC-2 120",
.short_name = "SC2_120",
.scan_pixels = 320,
.num_lines = 256,
.header_lines = 16,
.t_scan = 156.5025e-3,
.t_period = 475.52e-3,
.t_sync = 5.5225e-3,
.t_porch = 0.5e-3,
.t_sep = 0,
.family = MODE_WRAASE2,
.color_enc = COLOR_RGB,
.vis = 0x3F,
.vis_parity = PARITY_EVEN },
[MODE_PD120] = { // N7CXI, 2000
.Name = "PD-120",
.ShortName = "PD120",
.ScanPixels = 640,
.NumLines = 496,
.HeaderLines = 16,
.tScan = 121.6e-3,
.tLine = 508.48e-3,
.tSync = 20e-3,
.tPorch = 2.08e-3,
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_W2180] = { // OK
.name = "Wraase SC-2 180",
.short_name = "SC2_180",
.scan_pixels = 320,
.num_lines = 256,
.header_lines = 16,
.t_scan = 235e-3,
.t_period = 711.0437e-3,
.t_sync = 5.5225e-3,
.t_porch = 0.5e-3,
.t_sep = 0,
.family = MODE_WRAASE2,
.color_enc = COLOR_RGB,
.vis = 0x37,
.vis_parity = PARITY_EVEN },
[MODE_PD160] = { // N7CXI, 2000
.Name = "PD-160",
.ShortName = "PD160",
.ScanPixels = 512,
.NumLines = 400,
.HeaderLines = 16,
.tScan = 195.584e-3,
.tLine = 804.416e-3,
.tSync = 20e-3,
.tPorch = 2.08e-3,
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_PD50] = { // OK
.name = "PD-50",
.short_name = "PD50",
.scan_pixels = 320,
.num_lines = 256,
.header_lines = 16,
.t_scan = 91.52e-3,
.t_period = 388.1586e-3,
.t_sync = 20e-3,
.t_porch = 2.08e-3,
.t_sep = 0,
.family = MODE_PD,
.color_enc = COLOR_YUV,
.vis = 0x5D,
.vis_parity = PARITY_EVEN },
[MODE_PD180] = { // N7CXI, 2000
.Name = "PD-180",
.ShortName = "PD180",
.ScanPixels = 640,
.NumLines = 496,
.HeaderLines = 16,
.tScan = 183.04e-3,
.tLine = 754.24e-3,
.tSync = 20e-3,
.tPorch = 2.08e-3,
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_PD90] = { // TODO
.name = "PD-90",
.short_name = "PD90",
.scan_pixels = 320,
.num_lines = 256,
.header_lines = 16,
.t_scan = 170.340e-3,
.t_period = 703.04e-3,
.t_sync = 20e-3,
.t_porch = 2.08e-3,
.t_sep = 0,
.family = MODE_PD,
.color_enc = COLOR_YUV,
.vis = 0x63,
.vis_parity = PARITY_EVEN },
[MODE_PD240] = { // N7CXI, 2000
.Name = "PD-240",
.ShortName = "PD240",
.ScanPixels = 640,
.NumLines = 496,
.HeaderLines = 16,
.tScan = 244.48e-3,
.tLine = 1000e-3,
.tSync = 20e-3,
.tPorch = 2.08e-3,
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_PD120] = { // OK
.name = "PD-120",
.short_name = "PD120",
.scan_pixels = 640,
.num_lines = 496,
.header_lines = 16,
.t_scan = 121.6e-3,
.t_period = 508.48e-3,
.t_sync = 20e-3,
.t_porch = 2.08e-3,
.t_sep = 0,
.family = MODE_PD,
.color_enc = COLOR_YUV,
.vis = 0x5F,
.vis_parity = PARITY_EVEN },
[MODE_PD290] = { // N7CXI, 2000
.Name = "PD-290",
.ShortName = "PD290",
.ScanPixels = 800,
.NumLines = 616,
.HeaderLines = 16,
.tScan = 228.8e-3,
.tLine = 937.28e-3,
.tSync = 20e-3,
.tPorch = 2.08e-3,
.tSep = 0,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_440_YUVY,
.ColorEnc = COLOR_YUV,
.VISParity = PARITY_EVEN },
[MODE_PD160] = { // OK
.name = "PD-160",
.short_name = "PD160",
.scan_pixels = 512,
.num_lines = 400,
.header_lines = 16,
.t_scan = 195.584e-3,
.t_period = 804.416e-3,
.t_sync = 20e-3,
.t_porch = 2.08e-3,
.t_sep = 0,
.family = MODE_PD,
.color_enc = COLOR_YUV,
.vis = 0x62,
.vis_parity = PARITY_EVEN },
[MODE_P3] = { // N7CXI, 2000
.Name = "Pasokon P3",
.ShortName = "P3",
.ScanPixels = 640,
.NumLines = 496,
.HeaderLines = 16,
.tScan = 133.333e-3,
.tLine = 409.375e-3,
.tSync = 5.208e-3,
.tPorch = 1.042e-3,
.tSep = 1.042e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_RGB,
.VISParity = PARITY_EVEN },
[MODE_PD180] = { // OK
.name = "PD-180",
.short_name = "PD180",
.scan_pixels = 640,
.num_lines = 496,
.header_lines = 16,
.t_scan = 183.04e-3,
.t_period = 754.24e-3,
.t_sync = 20e-3,
.t_porch = 2.08e-3,
.t_sep = 0,
.family = MODE_PD,
.color_enc = COLOR_YUV,
.vis = 0x60,
.vis_parity = PARITY_EVEN },
[MODE_P5] = { // N7CXI, 2000
.Name = "Pasokon P5",
.ShortName = "P5",
.ScanPixels = 640,
.NumLines = 496,
.HeaderLines = 16,
.tScan = 200e-3,
.tLine = 614.065e-3,
.tSync = 7.813e-3,
.tPorch = 1.563e-3,
.tSep = 1.563e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_RGB,
.VISParity = PARITY_EVEN },
[MODE_PD240] = { // OK
.name = "PD-240",
.short_name = "PD240",
.scan_pixels = 640,
.num_lines = 496,
.header_lines = 16,
.t_scan = 244.48e-3,
.t_period = 1000e-3,
.t_sync = 20e-3,
.t_porch = 2.08e-3,
.t_sep = 0,
.family = MODE_PD,
.color_enc = COLOR_YUV,
.vis = 0x61,
.vis_parity = PARITY_EVEN },
[MODE_P7] = { // N7CXI, 2000
.Name = "Pasokon P7",
.ShortName = "P7",
.ScanPixels = 640,
.NumLines = 496,
.HeaderLines = 16,
.tScan = 266.666e-3,
.tLine = 818.747e-3,
.tSync = 10.417e-3,
.tPorch = 2.083e-3,
.tSep = 2.083e-3,
.SyncOrder = SYNC_SIMPLE,
.SubSampling = SUBSAMP_444,
.ColorEnc = COLOR_RGB,
.VISParity = PARITY_EVEN }
[MODE_PD290] = { // OK
.name = "PD-290",
.short_name = "PD290",
.scan_pixels = 800,
.num_lines = 616,
.header_lines = 16,
.t_scan = 228.8e-3,
.t_period = 937.28e-3,
.t_sync = 20e-3,
.t_porch = 2.08e-3,
.t_sep = 0,
.family = MODE_PD,
.color_enc = COLOR_YUV,
.vis = 0x5E,
.vis_parity = PARITY_EVEN },
[MODE_P3] = { // OK
.name = "Pasokon P3",
.short_name = "P3",
.scan_pixels = 640,
.num_lines = 496,
.header_lines = 16,
.t_scan = 133.333e-3,
.t_period = 409.3747e-3,
.t_sync = 5.208e-3,
.t_porch = 1.042e-3,
.t_sep = 1.042e-3,
.family = MODE_PASOKON,
.color_enc = COLOR_RGB,
.vis = 0x71,
.vis_parity = PARITY_EVEN },
[MODE_P5] = { // OK
.name = "Pasokon P5",
.short_name = "P5",
.scan_pixels = 640,
.num_lines = 496,
.header_lines = 16,
.t_scan = 200e-3,
.t_period = 614.065e-3,
.t_sync = 7.813e-3,
.t_porch = 1.563e-3,
.t_sep = 1.563e-3,
.family = MODE_PASOKON,
.color_enc = COLOR_RGB,
.vis = 0x72,
.vis_parity = PARITY_EVEN },
[MODE_P7] = { // OK
.name = "Pasokon P7",
.short_name = "P7",
.scan_pixels = 640,
.num_lines = 496,
.header_lines = 16,
.t_scan = 266.666e-3,
.t_period = 818.747e-3,
.t_sync = 10.417e-3,
.t_porch = 2.083e-3,
.t_sep = 2.083e-3,
.family = MODE_PASOKON,
.color_enc = COLOR_RGB,
.vis = 0x73,
.vis_parity = PARITY_EVEN }
};
/*
* Mapping of 7-bit VIS codes to modes
*
* KB4YZ, 1998
*
*/
std::map<int, SSTVMode> vis2mode = {
{0x02,MODE_R8BW}, {0x04,MODE_R24}, {0x06,MODE_R12BW}, {0x08,MODE_R36},
{0x0A,MODE_R24BW},{0x0C,MODE_R72}, {0x20,MODE_M4}, {0x24,MODE_M3},
{0x28,MODE_M2}, {0x2C,MODE_M1}, {0x37,MODE_W2180}, {0x38,MODE_S2},
{0x3C,MODE_S1}, {0x3F,MODE_W2120},{0x4C,MODE_SDX}, {0x5D,MODE_PD50},
{0x5E,MODE_PD290},{0x5F,MODE_PD120},{0x60,MODE_PD180}, {0x61,MODE_PD240},
{0x62,MODE_PD160},{0x63,MODE_PD90}, {0x71,MODE_P3}, {0x72,MODE_P5},
{0x73,MODE_P7}
};
SSTVMode vis2mode (int vis) {
std::map<int, SSTVMode> vismap = {
{0x02,MODE_R8BW}, {0x04,MODE_R24}, {0x06,MODE_R12BW}, {0x08,MODE_R36},
{0x0A,MODE_R24BW},{0x0C,MODE_R72}, {0x20,MODE_M4}, {0x24,MODE_M3},
{0x28,MODE_M2}, {0x2C,MODE_M1}, {0x37,MODE_W2180}, {0x38,MODE_S2},
{0x3C,MODE_S1}, {0x3F,MODE_W2120},{0x4C,MODE_SDX}, {0x5D,MODE_PD50},
{0x5E,MODE_PD290},{0x5F,MODE_PD120},{0x60,MODE_PD180}, {0x61,MODE_PD240},
{0x62,MODE_PD160},{0x63,MODE_PD90}, {0x71,MODE_P3}, {0x72,MODE_P5},
{0x73,MODE_P7}
};
return vismap[vis];
}

48
src/modespec.h 100644
Wyświetl plik

@ -0,0 +1,48 @@
#ifndef MODESPEC_H_
#define MODESPEC_H_
#include <string>
enum SSTVMode {
MODE_UNKNOWN=0,
MODE_M1, MODE_M2, MODE_M3, MODE_M4, MODE_S1,
MODE_S2, MODE_SDX, MODE_R72, MODE_R36, MODE_R24,
MODE_R24BW, MODE_R12BW, MODE_R8BW, MODE_PD50, MODE_PD90,
MODE_PD120, MODE_PD160, MODE_PD180, MODE_PD240, MODE_PD290,
MODE_P3, MODE_P5, MODE_P7, MODE_W260, MODE_W2120,
MODE_W2180
};
enum ModeFamily {
MODE_MARTIN, MODE_SCOTTIE, MODE_ROBOT, MODE_PD, MODE_WRAASE2,
MODE_PASOKON
};
enum eColorEnc {
COLOR_GBR, COLOR_RGB, COLOR_YUV, COLOR_MONO
};
enum eVISParity {
PARITY_EVEN=0, PARITY_ODD=1
};
typedef struct ModeSpec {
std::string name;
std::string short_name;
double t_sync;
double t_porch;
double t_sep;
double t_scan;
double t_period;
unsigned scan_pixels;
unsigned num_lines;
unsigned header_lines;
unsigned vis;
eColorEnc color_enc;
ModeFamily family;
eVISParity vis_parity;
} _ModeSpec;
extern _ModeSpec ModeSpec[];
#endif

248
src/picture.cc 100644
Wyświetl plik

@ -0,0 +1,248 @@
#include "common.h"
#include "gui.h"
#include "dsp.h"
#include "picture.h"
void Picture::pushToSyncSignal(double s) {
sync_signal_.push_back(s);
}
void Picture::pushToVideoSignal(double s) {
video_signal_.push_back(s);
}
SSTVMode Picture::getMode() { return mode_; }
double Picture::getDrift () { return drift_; }
double Picture::getStartsAt () { return starts_at_; }
double Picture::getVideoDt () { return video_dt_; }
double Picture::getSyncDt () { return sync_dt_; }
double Picture::getSyncSignalAt (size_t i) { return sync_signal_[i]; }
double Picture::getVideoSignalAt (size_t i) { return video_signal_[i]; }
void Picture::renderPixbuf(unsigned min_width) {
int upsample_factor = 4;
_ModeSpec m = ModeSpec[mode_];
std::vector<PixelSample> pixel_grid = pixelSamplingPoints(mode_);
std::vector<std::vector<std::vector<uint8_t>>> img(m.scan_pixels);
for (size_t x=0; x < m.scan_pixels; x++) {
img[x] = std::vector<std::vector<uint8_t>>(m.num_lines);
for (size_t y=0; y < m.num_lines; y++) {
img[x][y] = std::vector<uint8_t>(m.color_enc == COLOR_MONO ? 1 : 3);
}
}
Glib::RefPtr<Gdk::Pixbuf> pixbuf_rx;
pixbuf_rx = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, m.scan_pixels, m.num_lines);
pixbuf_rx->fill(0x000000ff);
guint8 *p;
guint8 *pixels;
pixels = pixbuf_rx->get_pixels();
int rowstride = pixbuf_rx->get_rowstride();
Wave* signal_up = upsample(video_signal_, upsample_factor, KERNEL_TENT);
for (size_t pixel_idx = 0; pixel_idx < pixel_grid.size(); pixel_idx ++) {
PixelSample px = pixel_grid[pixel_idx];
if (px.exists) {
double signal_t = (px.t/drift_ + starts_at_) / video_dt_ * upsample_factor;
double val;
if (signal_t < 0 || signal_t >= signal_up->size()-1) {
val = 0;
} else {
val = signal_up->at(signal_t);
}
int x = pixel_grid[pixel_idx].pt.x;
int y = pixel_grid[pixel_idx].pt.y;
int ch = pixel_grid[pixel_idx].ch;
img[x][y][ch] = clip(round(val*255));
}
}
if (mode_ == MODE_R36 || m.family == MODE_PD) {
for (size_t x=0; x < m.scan_pixels; x++) {
Wave column_u, column_v;
Wave* column_u_filtered;
Wave* column_v_filtered;
for (size_t y=0; y < m.num_lines; y+=2) {
column_u.push_back(img[x][y][1]);
column_v.push_back(img[x][y][2]);
}
column_u_filtered = upsample(column_u, 2, KERNEL_TENT);
column_v_filtered = upsample(column_v, 2, KERNEL_TENT);
for (size_t y=0; y < m.num_lines; y++) {
img[x][y][1] = column_u_filtered->at(y+1);
img[x][y][2] = column_v_filtered->at(y+1);
}
}
}
for (size_t x = 0; x < m.scan_pixels; x++) {
for (size_t y = 0; y < m.num_lines; y++) {
p = pixels + y * rowstride + x * 3;
#ifdef RGBONLY
p[0] = lum[x][y][0];
p[1] = lum[x][y][1];
p[2] = lum[x][y][2];
#else
switch(m.color_enc) {
case COLOR_RGB: {
p[0] = img[x][y][0];
p[1] = img[x][y][1];
p[2] = img[x][y][2];
break;
}
case COLOR_GBR: {
p[0] = img[x][y][2];
p[1] = img[x][y][0];
p[2] = img[x][y][1];
break;
}
case COLOR_YUV: {
double r = (298.082/256)*img[x][y][0] + (408.583/256) * img[x][y][1] - 222.921;
double g = (298.082/256)*img[x][y][0] - (100.291/256) * img[x][y][2] - (208.120/256) * img[x][y][1] + 135.576;
double b = (298.082/256)*img[x][y][0] + (516.412/256) * img[x][y][2] - 276.836;
p[0] = clip(r);
p[1] = clip(g);
p[2] = clip(b);
break;
}
case COLOR_MONO: {
p[0] = p[1] = p[2] = img[x][y][0];
break;
}
}
#endif
}
}
unsigned img_width = std::max(min_width, m.scan_pixels);
double scale = 1.0*img_width/m.scan_pixels;
unsigned img_height = round(m.num_lines * scale);
Glib::RefPtr<Gdk::Pixbuf> pixbuf_scaled;
pixbuf_scaled = pixbuf_rx->scale_simple(img_width, img_height, Gdk::INTERP_BILINEAR);
pixbuf_scaled->save("testi.png", "png");
}
void Picture::saveSync () {
_ModeSpec m = ModeSpec[mode_];
int line_width = m.t_period / sync_dt_;
int numlines = 240;
int upsample_factor = 2;
Glib::RefPtr<Gdk::Pixbuf> pixbuf_rx;
pixbuf_rx = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, line_width, numlines);
pixbuf_rx->fill(0x000000ff);
guint8 *p;
guint8 *pixels;
pixels = pixbuf_rx->get_pixels();
int rowstride = pixbuf_rx->get_rowstride();
Wave* big_sync = upsample(sync_signal_, 2, KERNEL_TENT);
for (size_t i=1; i<sync_signal_.size(); i++) {
int x = i % line_width;
int y = i / line_width;
int signal_idx = i * upsample_factor / drift_ + .5;
if (y < numlines) {
uint8_t val = clip((big_sync->at(signal_idx))*127);
p = pixels + y * rowstride + x * 3;
p[0] = p[1] = p[2] = val;
}
}
pixbuf_rx->save("sync.png", "png");
}
void Picture::resync () {
#ifdef NOSYNC
drift_ = 1.0;
starts_at_ = 0.0;
#else
_ModeSpec m = ModeSpec[mode_];
int line_width = m.t_period / sync_dt_;
size_t upsample_factor = 2;
Wave* sync_up = upsample(sync_signal_, upsample_factor, KERNEL_TENT);
/* slant */
std::vector<double> histogram;
int peak_drift_at = 0;
double peak_drift_val = 0;
double min_drift = 0.998;
double max_drift = 1.002;
double drift_step = 0.00005;
for (double d = min_drift; d <= max_drift; d += drift_step) {
std::vector<double> acc(line_width);
int peak_x = 0;
for (size_t i=1; i<sync_signal_.size(); i++) {
int x = i % line_width;
size_t signal_idx = round(i * upsample_factor / d);
double delta = (signal_idx < sync_up->size() ? sync_up->at(signal_idx) - sync_up->at(signal_idx-1) : 0);
if (delta >= 0.0)
acc[x] += delta;
if (acc[x] > acc[peak_x]) {
peak_x = x;
}
}
histogram.push_back(acc[peak_x]);
if (acc[peak_x] > peak_drift_val) {
peak_drift_at = histogram.size()-1;
peak_drift_val = acc[peak_x];
}
}
double peak_refined = peak_drift_at +
gaussianPeak(histogram[peak_drift_at-1], histogram[peak_drift_at], histogram[peak_drift_at+1]);
double drift = min_drift + peak_refined*drift_step;
/* align */
std::vector<double> acc(line_width);
for (size_t i=1;i<sync_signal_.size()-1; i++) {
int x = i % line_width;
size_t signal_idx = round(i * upsample_factor / drift);
acc[x] += (signal_idx < sync_up->size() ? sync_up->at(signal_idx) : 0);
}
int kernel_len = round(m.t_sync / m.t_period * line_width);
kernel_len += 1-(kernel_len%2);
Wave sync_kernel(kernel_len, 1);
Wave sc = convolve(acc, sync_kernel, true);
size_t m_i = maxIndex(sc);
double peak_align = m_i + gaussianPeak(sc[m_i-1], sc[m_i], sc[m_i+1]);
printf("peak_align = %f\n",peak_align);
double align_time = peak_align/line_width*m.t_period - m.t_sync*0.5;
if (m.family == MODE_SCOTTIE)
align_time -= m.t_sync + m.t_sep*2 + m.t_scan * 2;
fprintf(stderr,"drift = %.5f\n",drift);
delete sync_up;
drift_ = drift;
starts_at_ = align_time;
#endif
//saveSync(pic);
}

42
src/picture.h 100644
Wyświetl plik

@ -0,0 +1,42 @@
#ifndef PICTURE_H_
#define PICTURE_H_
#include "common.h"
class Picture {
public:
Picture(SSTVMode _mode)
: mode_(_mode), video_signal_(),
video_dt_(ModeSpec[_mode].t_scan/ModeSpec[_mode].scan_pixels/2), sync_signal_(),
sync_dt_(ModeSpec[_mode].t_period / ModeSpec[_mode].scan_pixels/3), drift_(1.0),
starts_at_(0.0) {}
void pushToSyncSignal (double s);
void pushToVideoSignal (double s);
SSTVMode getMode();
double getDrift ();
double getStartsAt ();
double getVideoDt ();
double getSyncDt ();
double getSyncSignalAt(size_t i);
double getVideoSignalAt(size_t i);
void renderPixbuf(unsigned min_width=320);
void resync();
void saveSync();
private:
SSTVMode mode_;
Wave video_signal_;
double video_dt_;
Wave sync_signal_;
double sync_dt_;
double drift_;
double starts_at_;
};
#endif

Wyświetl plik

@ -1,37 +1,25 @@
#include <getopt.h>
#include "common.hh"
#include "common.h"
#include "gui.h"
#include "dsp.h"
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);
//dsp.openAudioFile(optarg);
break;
}
SlowGUI gui = SlowGUI();
if (!dsp.is_open())
dsp.openPortAudio();
SSTVMode mode = nextHeader(&dsp);
if (mode != MODE_UNKNOWN) {
rxVideo(mode, &dsp);
}
//SlowGUI gui = SlowGUI();
return 0;
}

Wyświetl plik

@ -1,68 +0,0 @@
#include "common.hh"
// TODO: middle point of sync pulse
void resync (Picture* pic) {
int line_width = ModeSpec[pic->mode].tLine / pic->sync_dt;
/* speed */
std::vector<int> histogram;
int peak_speed = 0;
int peak_speed_val = 0;
double min_spd = 0.998;
double max_spd = 1.002;
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 (size_t i=1; i<pic->sync_signal.size(); i++) {
int x = int(i / speed + .5) % line_width;
acc[x] += pic->sync_signal[i] && !pic->sync_signal[i-1];
if (acc[x] > acc[peak_x]) {
peak_x = x;
}
}
histogram.push_back(acc[peak_x]);
if (acc[peak_x] > peak_speed_val) {
peak_speed = histogram.size()-1;
peak_speed_val = acc[peak_x];
}
}
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);
/* 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;
}
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]));
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);
printf("%f\n",peak_align_refined);
if (peak_align_refined > line_width/2.0)
peak_align_refined -= line_width;
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);
pic->speed = spd;
pic->starts_at = 1.0*peak_align_refined/line_width*ModeSpec[pic->mode].tLine;
}

Wyświetl plik

@ -1,4 +1,5 @@
#include "common.hh"
#include "common.h"
#include "dsp.h"
void printWave (Wave wave, double dt) {
for (int i=0;i<wave.size();i++)

Wyświetl plik

@ -1,198 +1,96 @@
#include "common.hh"
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);
guint8 *p;
guint8 *pixels;
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[pic->mode].ColorEnc) {
case COLOR_RGB:
p[0] = lum[x][y][0];
p[1] = lum[x][y][1];
p[2] = lum[x][y][2];
break;
case COLOR_GBR:
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 * 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] = lum[x][y][0];
break;
}
}
}
pixbuf_rx->save("testi.png", "png");
}
#include "common.h"
#include "dsp.h"
#include "gui.h"
#include "picture.h"
// Time instants for all pixels
std::vector<PixelSample> getPixelSamplingPoints(SSTVMode mode) {
_ModeSpec s = ModeSpec[mode];
std::vector<PixelSample> pixelSamplingPoints(SSTVMode mode) {
_ModeSpec m = 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++) {
for (size_t y=0; y<m.num_lines; y++) {
for (size_t x=0; x<m.scan_pixels; x++) {
for (size_t ch=0; ch < (m.color_enc == COLOR_MONO ? 1 : 3); ch++) {
PixelSample px;
px.pt = Point(x,y);
px.Channel = Chan;
switch(s.SubSampling) {
px.ch = ch;
px.exists = true;
case (SUBSAMP_444):
px.Time = y*(s.tLine) + (x+0.5)/s.ScanPixels * s.tScan;
switch (s.SyncOrder) {
case (SYNC_SIMPLE):
px.Time += s.tSync + s.tPorch + Chan*(s.tScan + s.tSep);
break;
case (SYNC_SCOTTIE):
px.Time += s.tSync + (Chan+1) * s.tSep + Chan*s.tScan +
(Chan == 2 ? s.tSync : 0);
break;
}
break;
case (SUBSAMP_420_YUYV):
switch (Chan) {
case (0):
px.Time = y*(s.tLine) + s.tSync + s.tPorch +
(x+.5)/s.ScanPixels * s.tScan;
break;
case (1):
px.Time = (y-(y % 2)) * (s.tLine) + s.tSync + s.tPorch +
s.tScan + s.tSep + 0.5*(x+0.5)/s.ScanPixels * s.tScan;
break;
case (2):
px.Time = (y+1-(y % 2)) * (s.tLine) + s.tSync + s.tPorch +
s.tScan + s.tSep + 0.5*(x+0.5)/s.ScanPixels * s.tScan;
break;
}
break;
case (SUBSAMP_422_YUV):
switch (Chan) {
case (0):
px.Time = y*(s.tLine) + s.tSync + s.tPorch +
(x+.5)/s.ScanPixels * s.tScan;
break;
case (1):
px.Time = y*(s.tLine) + s.tSync + s.tPorch + s.tScan
+ s.tSep + (x+.5)/s.ScanPixels * s.tScan/2;
break;
case (2):
px.Time = y*(s.tLine) + s.tSync + s.tPorch + s.tScan
+ s.tSep + s.tScan/2 + s.tSep + (x+.5)/s.ScanPixels *
s.tScan/2;
break;
}
break;
case (SUBSAMP_440_YUVY):
switch (Chan) {
case (0):
px.Time = (y/2)*(s.tLine) + s.tSync + s.tPorch +
((y%2 == 1 ? s.ScanPixels*3 : 0)+x+.5)/s.ScanPixels *
s.tScan;
break;
case (1):
px.Time = (y/2)*(s.tLine) + s.tSync + s.tPorch +
(s.ScanPixels+x+.5)/s.ScanPixels * s.tScan;
break;
case (2):
px.Time = (y/2)*(s.tLine) + s.tSync + s.tPorch +
(s.ScanPixels*2+x+.5)/s.ScanPixels * s.tScan;
break;
}
break;
if (m.family == MODE_MARTIN) {
px.t = y*(m.t_period) + m.t_sync + m.t_porch + ch*(m.t_scan + m.t_sep) +
(x+0.5)/m.scan_pixels * m.t_scan;
}
else if (m.family == MODE_SCOTTIE) {
px.t = y*(m.t_period) + (x+0.5)/m.scan_pixels * m.t_scan +
m.t_sync + (ch+1) * m.t_sep + ch*m.t_scan +
(ch == 2 ? m.t_sync : 0);
}
else if (m.family == MODE_PD) {
double line_video_start = (y/2)*(m.t_period) + m.t_sync + m.t_porch;
if (ch == 0) {
px.t = line_video_start + (y%2 == 1 ? 3*m.t_scan : 0) +
(x+.5)/m.scan_pixels * m.t_scan;
} else if (ch == 1 && (y%2) == 0) {
px.t = line_video_start + m.t_scan +
(x+.5)/m.scan_pixels * m.t_scan;
} else if (ch == 2 && (y%2) == 0) {
px.t = line_video_start + 2*m.t_scan +
(x+.5)/m.scan_pixels * m.t_scan;
} else if ((ch==1 || ch==2) && (y%2)==1) {
px.exists = false;
}
}
else if (m.family == MODE_PASOKON || m.family == MODE_WRAASE2) {
px.t = y*(m.t_period) + m.t_sync + m.t_porch + ch*(m.t_sep+m.t_scan) +
(x+0.5)/m.scan_pixels * m.t_scan;
}
else if (mode == MODE_R72 || mode == MODE_R24) {
double line_video_start = y*(m.t_period) + m.t_sync + m.t_porch;
switch (ch) {
case (0):
px.t = line_video_start + (x+.5) / m.scan_pixels * m.t_scan;
break;
case (1):
px.t = line_video_start + m.t_scan + m.t_sep +
(x+.5) / m.scan_pixels * m.t_scan / 2;
break;
case(2):
px.t = line_video_start + 1.5*m.t_scan + 2*m.t_sep +
(x+.5) / m.scan_pixels * m.t_scan / 2;
break;
}
}
else if (mode == MODE_R36) {
double line_video_start = y*(m.t_period) + m.t_sync + m.t_porch;
if (ch == 0) {
px.t = line_video_start + (x+.5) / m.scan_pixels * m.t_scan;
} else if (ch == 1 && (y % 2) == 0) {
px.t = line_video_start + m.t_scan + m.t_sep +
(x+.5) / m.scan_pixels * m.t_scan / 2;
} else if (ch == 2 && (y % 2) == 0) {
px.t = (y+1)*(m.t_period) + m.t_sync + m.t_porch + m.t_scan + m.t_sep +
(x+.5) / m.scan_pixels * m.t_scan / 2;
} else if ((ch==1 || ch==2) && (y%2) == 1) {
px.exists = false;
}
}
else if (mode == MODE_R8BW || mode == MODE_R12BW) {
px.t = y*(m.t_period) + m.t_sync + m.t_porch +
(x+.5) / m.scan_pixels * m.t_scan;
}
pixel_grid.push_back(px);
}
}
}
std::sort(pixel_grid.begin(), pixel_grid.end(), [](PixelSample a, PixelSample b) {
return a.Time < b.Time;
return a.t < b.t;
});
return pixel_grid;
@ -207,32 +105,39 @@ std::vector<PixelSample> getPixelSamplingPoints(SSTVMode mode) {
*/
bool rxVideo(SSTVMode mode, DSPworker* dsp) {
printf("receive %s\n",ModeSpec[mode].Name.c_str());
fprintf(stderr,"receive %s\n",ModeSpec[mode].name.c_str());
_ModeSpec s = ModeSpec[mode];
Picture pic(mode);
_ModeSpec m = ModeSpec[mode];
Picture* pic = new Picture(mode);
Glib::RefPtr<Gtk::Application> app = Gtk::Application::create("com.windytan.slowrx");
//Glib::RefPtr<Gtk::Application> app = Gtk::Application::create("com.windytan.slowrx");
double next_sync_sample_time = 0;
double next_video_sample_time = 0;
double t_total = (s.SyncOrder == SYNC_SCOTTIE ? s.tSync : 0) + s.NumLines * s.tLine;
double t_total;
if (m.family == MODE_SCOTTIE) {
t_total = m.t_sync + m.num_lines * m.t_period;
} else if (m.family == MODE_PD) {
t_total = m.num_lines * m.t_period / 2;
} else {
t_total = m.num_lines * m.t_period;
}
size_t idx = 0;
for (double t=0; t < t_total && dsp->is_open(); t += dsp->forward()) {
if (t >= next_sync_sample_time) {
pic.sync_signal.push_back(dsp->hasSync());
next_sync_sample_time += pic.sync_dt;
double p = dsp->syncPower();
pic->pushToSyncSignal(p);
next_sync_sample_time += pic->getSyncDt();
}
bool is_adaptive = true;
if ( t >= next_video_sample_time ) {
pic.video_signal.push_back(dsp->lum(pic.mode, is_adaptive));
pic->pushToVideoSignal(dsp->lum(pic->getMode(), is_adaptive));
if ((idx+1) % 1000 == 0) {
size_t prog_width = 50;
@ -245,31 +150,21 @@ bool rxVideo(SSTVMode mode, DSPworker* dsp) {
for (size_t i=prog_points;i<prog_width;i++) {
fprintf(stderr," ");
}
fprintf(stderr,"] %.1f %%\r",prog*100);
fprintf(stderr,"] %.1f %% (%.1f s / %.1f s)\r",prog*100,t,t_total);
}
if (dsp->isLive() && (idx+1) % 10000 == 0) {
resync(&pic);
renderPixbuf(&pic);
}
next_video_sample_time += pic.video_dt;
/*if (dsp->isLive() && (idx+1) % 10000 == 0) {
fprintf(stderr,"\n(resync)\n");
pic->resync();
pic->renderPixbuf();
}*/
next_video_sample_time += pic->getVideoDt();
idx++;
}
}
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);
gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp);
}*/
pic->resync();
pic->renderPixbuf();
fprintf(stderr, "\n");