kopia lustrzana https://github.com/windytan/slowrx
dev
rodzic
bc17b64b6d
commit
588b3cb9ab
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
296
src/common.hh
296
src/common.hh
|
@ -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
|
354
src/dsp.cc
354
src/dsp.cc
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
#include "common.hh"
|
||||
#include "common.h"
|
||||
|
||||
/*
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -1,7 +0,0 @@
|
|||
#ifndef GUI_H
|
||||
#define GUI_H
|
||||
|
||||
#include <gtkmm.h>
|
||||
#include "common.hh"
|
||||
|
||||
#endif // GUI_H
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
840
src/modespec.cc
840
src/modespec.cc
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
68
src/sync.cc
68
src/sync.cc
|
@ -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;
|
||||
|
||||
}
|
|
@ -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++)
|
||||
|
|
315
src/video.cc
315
src/video.cc
|
@ -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");
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue