diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index a39c819c3..524ff5fc3 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -58,4 +58,8 @@ if(ENABLE_SOAPYSDR AND SOAPYSDR_FOUND) add_subdirectory(soapysdrinput) endif() +if(LIBSIGMF_FOUND) + add_subdirectory(sigmffileinput) +endif() + add_subdirectory(kiwisdr) \ No newline at end of file diff --git a/plugins/samplesource/sigmffileinput/CMakeLists.txt b/plugins/samplesource/sigmffileinput/CMakeLists.txt new file mode 100644 index 000000000..aa142c5b7 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/CMakeLists.txt @@ -0,0 +1,61 @@ +project(sigmffileinput) + +set(sigmffileinput_SOURCES + sigmffileinput.cpp + sigmffileinputplugin.cpp + sigmffileinputthread.cpp + sigmffileinputsettings.cpp + sigmffileinputwebapiadapter.cpp +) + +set(sigmffileinput_HEADERS + sigmffileinput.h + sigmffileinputplugin.h + sigmffileinputthread.h + sigmffileinputsettings.h + sigmffileinputwebapiadapter.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBSIGMF_INCLUDE_DIR} +) + +if(NOT SERVER_MODE) + set(sigmffileinput_SOURCES + ${sigmffileinput_SOURCES} + sigmffileinputgui.cpp + recordinfodialog.cpp + sigmffileinputgui.ui + recordinfodialog.ui + ) + set(sigmffileinput_HEADERS + ${sigmffileinput_HEADERS} + sigmffileinputgui.h + recordinfodialog.h + ) + set(TARGET_NAME inputsigmffileinput) + set(TARGET_LIB "Qt5::Widgets") + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME inputsigmffileinputsrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${sigmffileinput_SOURCES} +) + +target_link_libraries(${TARGET_NAME} + Qt5::Core + ${TARGET_LIB} + sdrbase + ${TARGET_LIB_GUI} + swagger + ${LIBSIGMF_LIBRARIES} +) + +install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) diff --git a/plugins/samplesource/sigmffileinput/readme.md b/plugins/samplesource/sigmffileinput/readme.md new file mode 100644 index 000000000..ac674f36b --- /dev/null +++ b/plugins/samplesource/sigmffileinput/readme.md @@ -0,0 +1,113 @@ +

File input plugin

+ +

Introduction

+ +This plugin reads a file of I/Q samples that have been previously saved with the file record button of other sampling source devices. The file starts with a 32 byte header of all unsigned integer of various sizes containing meta data: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Displ.BytesDescription
04Sample rate in S/s
48Center frequency in Hz
128Unix epoch (timestamp) of start
204Sample size (16 or 24 bits)
244Filler with zeroes
284CRC32 of the previous 28 bytes
+ +The header takes an integer number of 16 (4 bytes) or 24 (8 bytes) bits samples. To calculate CRC it is assumed that bytes are in little endian order. + +

Interface

+ +![File input plugin GUI](../../../doc/img/FileInput_plugin.png) + +

1: Start/Stop

+ +Device start / stop button. + + - Blue triangle icon: ready to be started + - Green square icon: currently running and can be stopped + - Magenta (or pink) square icon: an error occurred. The file may not be found or this can be a header CRC error or the file is too small (less than the header length). You may stop and choose another file. + +

2: Stream sample rate

+ +Baseband I/Q sample rate in kS/s. This is the sample rate present in the header. + +

3: Frequency

+ +This is the center frequency of reception in kHz when the record was taken and written in the header. + +

4: Open file

+ +Opens a file dialog to select the input file. It expects a default extension of `.sdriq`. This button is disabled when the stream is running. You need to pause (button 11) to make it active and thus be able to select another file. + +

5: File path

+ +Absolute path of the file being read + +

6: File recorded sample rate

+ +Sample rate of the record in kS/s as written in the header. The reading process is based on this sample rate. + +

7: Sample size

+ +This is the sample size in bits as written in the header. The reading process is based on this sample size. + +

8: CRC indicator

+ +Indicates if the header block CRC check has succeeded (green) or failed (red) or undetermined yet (grey). If the header is corrupted you may try to reconstruct a valid header using the `rescuesdriq` utility in the folder with the same name. See the [readme](../../../rescuesdriq/readme.md) for details. + +

9: Current timestamp

+ +This is the timestamp of the current pointer in the file based on the start time, number of samples read and sample rate. + +

10: Loop

+ +Use this button to read in a loop or read only once + +

11: Play/pause

+ +This is the play/pause button + +

12: Playback acceleration

+ +Use this combo to select play back acceleration to values of 1 (no acceleration), 2, 5, 10, 20, 50, 100, 200, 500, 1k (1000) times. This is useful on long recordings used in conjunction with the spectrum "Max" averaging mode in order to see the waterfall over a long period. Thus the waterfall will be filled much faster. + +☞ Note that this control is enabled only in paused mode. + +⚠ The result when using channel plugins with acceleration is unpredictable. Use this tool to locate your signal of interest then play at normal speed to get proper demodulation or decoding. + +

13: Relative timestamp and record length

+ +Left is the relative timestamp of the current pointer from the start of the record. Right is the total record time. + +

14: Current pointer gauge

+ +This represents the position of the current pointer position in the complete recording. It can be used it paused mode to position the current pointer by moving the slider. diff --git a/plugins/samplesource/sigmffileinput/recordinfodialog.cpp b/plugins/samplesource/sigmffileinput/recordinfodialog.cpp new file mode 100644 index 000000000..76efae0b0 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/recordinfodialog.cpp @@ -0,0 +1,15 @@ +#include "recordinfodialog.h" +#include "ui_recordinfodialog.h" + +RecordInfoDialog::RecordInfoDialog(const QString& text, QWidget* parent) : + QDialog(parent), + ui(new Ui::RecordInfoDialog) +{ + ui->setupUi(this); + ui->infoText->setText(text); +} + +RecordInfoDialog::~RecordInfoDialog() +{ + delete ui; +} diff --git a/plugins/samplesource/sigmffileinput/recordinfodialog.h b/plugins/samplesource/sigmffileinput/recordinfodialog.h new file mode 100644 index 000000000..1f72bc43c --- /dev/null +++ b/plugins/samplesource/sigmffileinput/recordinfodialog.h @@ -0,0 +1,23 @@ +#ifndef INCLUDE_ABOUTDIALOG_H +#define INCLUDE_ABOUTDIALOG_H + +#include + +#include "export.h" + +namespace Ui { + class RecordInfoDialog; +} + +class SDRGUI_API RecordInfoDialog : public QDialog { + Q_OBJECT + +public: + explicit RecordInfoDialog(const QString& text, QWidget* parent = nullptr); + ~RecordInfoDialog(); + +private: + Ui::RecordInfoDialog* ui; +}; + +#endif // INCLUDE_ABOUTDIALOG_H diff --git a/plugins/samplesource/sigmffileinput/recordinfodialog.ui b/plugins/samplesource/sigmffileinput/recordinfodialog.ui new file mode 100644 index 000000000..11aa7da36 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/recordinfodialog.ui @@ -0,0 +1,127 @@ + + + RecordInfoDialog + + + + 0 + 0 + 404 + 300 + + + + + 0 + 0 + + + + + 404 + 300 + + + + + Liberation Sans + 9 + + + + File information + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + SigMF file information (meta data) + + + + + + + + 398 + 0 + + + + + Liberation Mono + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + true + + + + + + + + + + + buttonBox + accepted() + RecordInfoDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + RecordInfoDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/plugins/samplesource/sigmffileinput/sigmffileconvert.h b/plugins/samplesource/sigmffileinput/sigmffileconvert.h new file mode 100644 index 000000000..664ff9214 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileconvert.h @@ -0,0 +1,1554 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SIGMFFILECONVERT_H +#define INCLUDE_SIGMFFILECONVERT_H + +#include "dsp/dsptypes.h" + +// Convert from Little Endian + +template +T sigMFFromLE(const T in) { + return in; // default assumes LE -> LE and is unused anyway +} + +template<> +float sigMFFromLE(const float in) +{ +#if defined(__WINDOWS__) || (BYTE_ORDER == LITTLE_ENDIAN) + return in; +#else + float retVal; + char *toConvert = ( char* ) & in; + char *converted = ( char* ) & retVal; + + // swap the bytes into a temporary buffer + converted[0] = toConvert[3]; + converted[1] = toConvert[2]; + converted[2] = toConvert[1]; + converted[3] = toConvert[0]; + + return retVal; +#endif +} + +template<> +int16_t sigMFFromLE(const int16_t in) +{ +#if defined(__WINDOWS__) || (BYTE_ORDER == LITTLE_ENDIAN) + return in; +#else + int16_t retVal; + char *toConvert = ( char* ) & in; + char *converted = ( char* ) & retVal; + + // swap the bytes into a temporary buffer + converted[0] = toConvert[1]; + converted[1] = toConvert[0]; + + return retVal; +#endif +} + +template<> +uint16_t sigMFFromLE(const uint16_t in) +{ +#if defined(__WINDOWS__) || (BYTE_ORDER == LITTLE_ENDIAN) + return in; +#else + uint16_t retVal; + char *toConvert = ( char* ) & in; + char *converted = ( char* ) & retVal; + + // swap the bytes into a temporary buffer + converted[0] = toConvert[1]; + converted[1] = toConvert[0]; + + return retVal; +#endif +} + +template<> +int32_t sigMFFromLE(const int32_t in) +{ +#if defined(__WINDOWS__) || (BYTE_ORDER == LITTLE_ENDIAN) + return in; +#else + int32_t retVal; + char *toConvert = ( char* ) & in; + char *converted = ( char* ) & retVal; + + // swap the bytes into a temporary buffer + converted[0] = toConvert[3]; + converted[1] = toConvert[2]; + converted[2] = toConvert[1]; + converted[3] = toConvert[0]; + + return retVal; +#endif +} + +template<> +uint32_t sigMFFromLE(const uint32_t in) +{ +#if defined(__WINDOWS__) || (BYTE_ORDER == LITTLE_ENDIAN) + return in; +#else + uint32_t retVal; + char *toConvert = ( char* ) & in; + char *converted = ( char* ) & retVal; + + // swap the bytes into a temporary buffer + converted[0] = toConvert[3]; + converted[1] = toConvert[2]; + converted[2] = toConvert[1]; + converted[3] = toConvert[0]; + + return retVal; +#endif +} + +// Convert from Big Endian + +template +T sigMFFromBE(const T in) { + return in; // default assumes BE -> BE and is unused anyway +} + +template<> +float sigMFFromBE(const float in) +{ +#if defined(__WINDOWS__) || (BYTE_ORDER == LITTLE_ENDIAN) + float retVal; + char *toConvert = ( char* ) & in; + char *converted = ( char* ) & retVal; + + // swap the bytes into a temporary buffer + converted[0] = toConvert[3]; + converted[1] = toConvert[2]; + converted[2] = toConvert[1]; + converted[3] = toConvert[0]; + + return retVal; +#else + return in; +#endif +} + +template<> +int16_t sigMFFromBE(const int16_t in) +{ +#if defined(__WINDOWS__) || (BYTE_ORDER == LITTLE_ENDIAN) + int16_t retVal; + char *toConvert = ( char* ) & in; + char *converted = ( char* ) & retVal; + + // swap the bytes into a temporary buffer + converted[0] = toConvert[1]; + converted[1] = toConvert[0]; + + return retVal; +#else + return in; +#endif +} + +template<> +uint16_t sigMFFromBE(const uint16_t in) +{ +#if defined(__WINDOWS__) || (BYTE_ORDER == LITTLE_ENDIAN) + uint16_t retVal; + char *toConvert = ( char* ) & in; + char *converted = ( char* ) & retVal; + + // swap the bytes into a temporary buffer + converted[0] = toConvert[1]; + converted[1] = toConvert[0]; + + return retVal; +#else + return in; +#endif +} + +template<> +int32_t sigMFFromBE(const int32_t in) +{ +#if defined(__WINDOWS__) || (BYTE_ORDER == LITTLE_ENDIAN) + int32_t retVal; + char *toConvert = ( char* ) & in; + char *converted = ( char* ) & retVal; + + // swap the bytes into a temporary buffer + converted[0] = toConvert[3]; + converted[1] = toConvert[2]; + converted[2] = toConvert[1]; + converted[3] = toConvert[0]; + + return retVal; +#else + return in; +#endif +} + +template<> +uint32_t sigMFFromBE(const uint32_t in) +{ +#if defined(__WINDOWS__) || (BYTE_ORDER == LITTLE_ENDIAN) + uint32_t retVal; + char *toConvert = ( char* ) & in; + char *converted = ( char* ) & retVal; + + // swap the bytes into a temporary buffer + converted[0] = toConvert[3]; + converted[1] = toConvert[2]; + converted[2] = toConvert[1]; + converted[3] = toConvert[0]; + + return retVal; +#else + return in; +#endif +} + +// Sample conversions + +class SigMFConverterInterface +{ +public: + virtual int convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) = 0; +}; + +template +class SigMFConverter : public SigMFConverterInterface +{ +public: + virtual int convert(FixReal *convertBuffer, const quint8* buf, int nbBytes); +}; + +template +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const SigMFT *sigMFBuf = (SigMFT *) buf; + int nbSamples = nbBytes / ((IsComplex ? 2 : 1) * sizeof(SigMFT)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFBuf[2*is]; + convertBuffer[2*is+1] = sigMFBuf[2*is+1]; + } + + return nbSamples; +} + +// Specialized templates + +// ================= +// float input type +// ================= + +// float complex LE IQ => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const float *sigMFBuf = (float *) buf; + int nbSamples = nbBytes / (2*sizeof(float)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]) * 32768.0f; + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is+1]) * 32768.0f; + } + + return nbSamples; +} + +// float complex LE IQ => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const float *sigMFBuf = (float *) buf; + int nbSamples = nbBytes / (2*sizeof(float)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]) * 8388608.0f; + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is+1]) * 8388608.0f; + } + + return nbSamples; +} + +// float complex LE QI => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const float *sigMFBuf = (float *) buf; + int nbSamples = nbBytes / (2*sizeof(float)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is+1]) * 32768.0f; + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is]) * 32768.0f; + } + + return nbSamples; +} + +// float complex LE QI => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const float *sigMFBuf = (float *) buf; + int nbSamples = nbBytes / (2*sizeof(float)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is+1]) * 8388608.0f; + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is]) * 8388608.0f; + } + + return nbSamples; +} + +// float real LE => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const float *sigMFBuf = (float *) buf; + int nbSamples = nbBytes / sizeof(float); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]) * 32768.0f; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// float real LE => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const float *sigMFBuf = (float *) buf; + int nbSamples = nbBytes / sizeof(float); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]) * 8388608.0f; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// float complex BE IQ => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const float *sigMFBuf = (float *) buf; + int nbSamples = nbBytes / (2*sizeof(float)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]) * 32768.0f; + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is+1]) * 32768.0f; + } + + return nbSamples; +} + +// float complex BE IQ => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const float *sigMFBuf = (float *) buf; + int nbSamples = nbBytes / (2*sizeof(float)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]) * 8388608.0f; + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is+1]) * 8388608.0f; + } + + return nbSamples; +} + +// float complex BE QI => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const float *sigMFBuf = (float *) buf; + int nbSamples = nbBytes / (2*sizeof(float)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is+1]) * 32768.0f; + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is]) * 32768.0f; + } + + return nbSamples; +} + +// float complex BE QI => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const float *sigMFBuf = (float *) buf; + int nbSamples = nbBytes / (2*sizeof(float)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is+1]) * 8388608.0f; + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is]) * 8388608.0f; + } + + return nbSamples; +} + +// float real BE => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const float *sigMFBuf = (float *) buf; + int nbSamples = nbBytes / sizeof(float); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]) * 32768.0f; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// float real BE => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const float *sigMFBuf = (float *) buf; + int nbSamples = nbBytes / sizeof(float); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]) * 8388608.0f; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// ================================ +// 8 bit signed integer input type +// ================================ + +// s8 complex IQ => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int8_t *sigMFBuf = (int8_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int8_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFBuf[2*is] << 8; + convertBuffer[2*is+1] = sigMFBuf[2*is+1] << 8; + } + + return nbSamples; +} + +// s8 complex IQ => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int8_t *sigMFBuf = (int8_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int8_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFBuf[2*is] << 16; + convertBuffer[2*is+1] = sigMFBuf[2*is+1] << 16; + } + + return nbSamples; +} + +// s8 complex QI => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int8_t *sigMFBuf = (int8_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int8_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFBuf[2*is+1] << 8; + convertBuffer[2*is+1] = sigMFBuf[2*is] << 8; + } + + return nbSamples; +} + +// s8 complex QI => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int8_t *sigMFBuf = (int8_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int8_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFBuf[2*is+1] << 16; + convertBuffer[2*is+1] = sigMFBuf[2*is] << 16; + } + + return nbSamples; +} + +// s8 real => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int8_t *sigMFBuf = (int8_t *) buf; + int nbSamples = nbBytes / sizeof(int8_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFBuf[2*is] << 8; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// s8 real => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int8_t *sigMFBuf = (int8_t *) buf; + int nbSamples = nbBytes / sizeof(int8_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFBuf[2*is] << 16; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// ================================== +// 8 bit unsigned integer input type +// ================================== + +// u8 complex IQ => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* sigMFBuf, int nbBytes) +{ + int nbSamples = nbBytes / (2*sizeof(uint8_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFBuf[2*is] - 128) << 8; + convertBuffer[2*is+1] = (sigMFBuf[2*is+1] - 128) << 8; + } + + return nbSamples; +} + +// u8 complex IQ => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* sigMFBuf, int nbBytes) +{ + int nbSamples = nbBytes / (2*sizeof(uint8_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFBuf[2*is] - 128) << 16; + convertBuffer[2*is+1] = (sigMFBuf[2*is+1] - 128) << 16; + } + + return nbSamples; +} + +// u8 complex QI => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* sigMFBuf, int nbBytes) +{ + int nbSamples = nbBytes / (2*sizeof(uint8_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFBuf[2*is+1] - 128) << 8; + convertBuffer[2*is+1] = (sigMFBuf[2*is] - 128) << 8; + } + + return nbSamples; +} + +// u8 complex QI => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* sigMFBuf, int nbBytes) +{ + int nbSamples = nbBytes / (2*sizeof(uint8_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFBuf[2*is+1] - 128) << 16; + convertBuffer[2*is+1] = (sigMFBuf[2*is] - 128) << 16; + } + + return nbSamples; +} + +// u8 real => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* sigMFBuf, int nbBytes) +{ + int nbSamples = nbBytes / sizeof(uint8_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFBuf[2*is] - 128) << 8; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// u8 real => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* sigMFBuf, int nbBytes) +{ + int nbSamples = nbBytes / sizeof(uint8_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFBuf[2*is] - 128) << 16; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// ================================= +// 16 bit signed integer input type +// ================================= + +// i16 complex LE IQ => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int16_t *sigMFBuf = (int16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int16_t)); +#if defined(__WINDOWS__) || (BYTE_ORDER == LITTLE_ENDIAN) + std::copy(sigMFBuf, sigMFBuf + 2*nbSamples, convertBuffer); + return nbSamples; +#else + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]); + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is+1]); + } + + return nbSamples; +#endif +} + +// i16 complex LE IQ => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int16_t *sigMFBuf = (int16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]) << 8; + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is+1]) << 8; + } + + return nbSamples; +} + +// i16 complex LE QI => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int16_t *sigMFBuf = (int16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is+1]); + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is]); + } + + return nbSamples; +} + +// i16 complex LE QI => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int16_t *sigMFBuf = (int16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is+1] << 8); + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is] << 8); + } + + return nbSamples; +} + +// i16 real LE => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int16_t *sigMFBuf = (int16_t *) buf; + int nbSamples = nbBytes / sizeof(int16_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]); + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// i16 real LE => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int16_t *sigMFBuf = (int16_t *) buf; + int nbSamples = nbBytes / sizeof(int16_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is] << 8); + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// i16 complex BE IQ => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int16_t *sigMFBuf = (int16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]); + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is+1]); + } + + return nbSamples; +} + +// i16 complex BE IQ => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int16_t *sigMFBuf = (int16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]) << 8; + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is+1]) << 8; + } + + return nbSamples; +} + +// i16 complex BE QI => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int16_t *sigMFBuf = (int16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is+1]); + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is]); + } + + return nbSamples; +} + +// i16 complex BE QI => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int16_t *sigMFBuf = (int16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is+1]) << 8; + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is]) << 8; + } + + return nbSamples; +} + +// i16 real BE => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int16_t *sigMFBuf = (int16_t *) buf; + int nbSamples = nbBytes / sizeof(int16_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]); + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// i16 real BE => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int16_t *sigMFBuf = (int16_t *) buf; + int nbSamples = nbBytes / sizeof(int16_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]) << 8; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// =================================== +// 16 bit unsigned integer input type +// =================================== + +// u16 complex LE IQ => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint16_t *sigMFBuf = (uint16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]) - 32768; + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is+1]) -32768; + } + + return nbSamples; +} + +// u16 complex LE IQ => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint16_t *sigMFBuf = (uint16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromLE(sigMFBuf[2*is]) - 32768) << 8; + convertBuffer[2*is+1] = (sigMFFromLE(sigMFBuf[2*is+1]) - 32768) << 8; + convertBuffer[2*is+1] <<= 8; + } + + return nbSamples; +} + +// u16 complex LE QI => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint16_t *sigMFBuf = (uint16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is+1]) - 32768; + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is]) -32768; + } + + return nbSamples; +} + +// u16 complex LE QI => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint16_t *sigMFBuf = (uint16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromLE(sigMFBuf[2*is+1]) - 32768) << 8; + convertBuffer[2*is+1] = (sigMFFromLE(sigMFBuf[2*is]) - 32768) << 8; + } + + return nbSamples; +} + +// u16 real LE => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint16_t *sigMFBuf = (uint16_t *) buf; + int nbSamples = nbBytes / sizeof(uint16_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]) - 32768; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// u16 real LE => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint16_t *sigMFBuf = (uint16_t *) buf; + int nbSamples = nbBytes / sizeof(uint16_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromLE(sigMFBuf[2*is]) - 32768) << 8; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// u16 complex BE IQ => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint16_t *sigMFBuf = (uint16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]) - 32768; + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is+1]) -32768; + } + + return nbSamples; +} + +// u16 complex BE IQ => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint16_t *sigMFBuf = (uint16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromBE(sigMFBuf[2*is]) - 32768) << 8; + convertBuffer[2*is+1] = (sigMFFromBE(sigMFBuf[2*is+1]) -32768) << 8; + } + + return nbSamples; +} + +// u16 complex BE QI => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint16_t *sigMFBuf = (uint16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is+1]) - 32768; + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is]) -32768; + } + + return nbSamples; +} + +// u16 complex BE QI => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint16_t *sigMFBuf = (uint16_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint16_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromBE(sigMFBuf[2*is+1]) - 32768) << 8; + convertBuffer[2*is+1] = (sigMFFromBE(sigMFBuf[2*is]) -32768) << 8; + } + + return nbSamples; +} + +// u16 real BE => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint16_t *sigMFBuf = (uint16_t *) buf; + int nbSamples = nbBytes / sizeof(uint16_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]) - 32768; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// u16 real BE => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint16_t *sigMFBuf = (uint16_t *) buf; + int nbSamples = nbBytes / sizeof(uint16_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromBE(sigMFBuf[2*is]) - 32768) << 8; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// ====================================================== +// 24 bit signed integer input type - SDRangel exclusive +// ====================================================== + +// s24 complex LE IQ => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]) >> 8; + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is+1]) >> 8; + } + + return nbSamples; +} + +// s24 complex LE IQ => FixReal 24 bits - SDRangel only + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int32_t)); +#if defined(__WINDOWS__) || (BYTE_ORDER == LITTLE_ENDIAN) + std::copy(sigMFBuf, sigMFBuf + 2*nbSamples, convertBuffer); + return nbSamples; +#else + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]); + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is+1]); + } + + return nbSamples; +#endif +} + +// ================================= +// 32 bit signed integer input type +// ================================= + +// s32 complex LE IQ => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]) >> 16; + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is+1]) >> 16; + } + + return nbSamples; +} + +// s32 complex LE IQ => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]) >> 8; + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is+1]) >> 8; + } + + return nbSamples; +} + +// s32 complex LE QI => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is+1]) >> 16; + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is]) >> 16; + } + + return nbSamples; +} + +// s32 complex LE QI => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is+1]) >> 8; + convertBuffer[2*is+1] = sigMFFromLE(sigMFBuf[2*is]) >> 8; + } + + return nbSamples; +} + +// s32 real LE => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / sizeof(int32_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]) >> 16; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// s32 real LE => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / sizeof(int32_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromLE(sigMFBuf[2*is]) >> 8; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// s32 complex BE IQ => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]) >> 16; + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is+1]) >> 16; + } + + return nbSamples; +} + +// s32 complex BE IQ => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]) >> 8; + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is+1]) >> 8; + } + + return nbSamples; +} + +// s32 complex BE QI => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is+1]) >> 16; + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is]) >> 16; + } + + return nbSamples; +} + +// s32 complex BE QI => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(int32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is+1]) >> 8; + convertBuffer[2*is+1] = sigMFFromBE(sigMFBuf[2*is]) >> 8; + } + + return nbSamples; +} + +// s32 real BE => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / sizeof(int32_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]) >> 16; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// s32 real BE => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const int32_t *sigMFBuf = (int32_t *) buf; + int nbSamples = nbBytes / sizeof(int32_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = sigMFFromBE(sigMFBuf[2*is]) >> 8; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// =================================== +// 32 bit unsigned integer input type +// =================================== + +// u32 complex LE IQ => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint32_t *sigMFBuf = (uint32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromLE(sigMFBuf[2*is]) >> 16) - 32768; + convertBuffer[2*is+1] = (sigMFFromLE(sigMFBuf[2*is+1]) >> 16) - 32768; + } + + return nbSamples; +} + +// u32 complex LE IQ => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint32_t *sigMFBuf = (uint32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromLE(sigMFBuf[2*is]) >> 8) - 8388608; + convertBuffer[2*is+1] = (sigMFFromLE(sigMFBuf[2*is+1]) >> 8) - 8388608; + } + + return nbSamples; +} + +// u32 complex LE QI => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint32_t *sigMFBuf = (uint32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromLE(sigMFBuf[2*is+1]) >> 16) - 32768; + convertBuffer[2*is+1] = (sigMFFromLE(sigMFBuf[2*is]) >> 16) - 32768; + } + + return nbSamples; +} + +// u32 complex LE QI => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint32_t *sigMFBuf = (uint32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromLE(sigMFBuf[2*is+1]) >> 8) - 8388608; + convertBuffer[2*is+1] = (sigMFFromLE(sigMFBuf[2*is]) >> 8) - 8388608; + } + + return nbSamples; +} + +// u32 real LE => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint32_t *sigMFBuf = (uint32_t *) buf; + int nbSamples = nbBytes / sizeof(uint32_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromLE(sigMFBuf[2*is]) >> 16) - 32768; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// u32 real LE => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint32_t *sigMFBuf = (uint32_t *) buf; + int nbSamples = nbBytes / sizeof(uint32_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromLE(sigMFBuf[2*is]) >> 8) - 8388608; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// u32 complex BE IQ => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint32_t *sigMFBuf = (uint32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromBE(sigMFBuf[2*is]) >> 16) - 32768; + convertBuffer[2*is+1] = (sigMFFromBE(sigMFBuf[2*is+1]) >> 16) - 32768; + } + + return nbSamples; +} + +// u32 complex BE IQ => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint32_t *sigMFBuf = (uint32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromBE(sigMFBuf[2*is]) >> 8) - 8388608; + convertBuffer[2*is+1] = (sigMFFromBE(sigMFBuf[2*is+1]) >> 8) - 8388608; + } + + return nbSamples; +} + +// u32 complex BE QI => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint32_t *sigMFBuf = (uint32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromBE(sigMFBuf[2*is+1]) >> 16) - 32768; + convertBuffer[2*is+1] = (sigMFFromBE(sigMFBuf[2*is]) >> 16) - 32768; + } + + return nbSamples; +} + +// u32 complex BE QI => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint32_t *sigMFBuf = (uint32_t *) buf; + int nbSamples = nbBytes / (2*sizeof(uint32_t)); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromBE(sigMFBuf[2*is+1]) >> 8) - 8388608; + convertBuffer[2*is+1] = (sigMFFromBE(sigMFBuf[2*is]) >> 8) - 8388608; + } + + return nbSamples; +} + +// u32 real BE => FixReal 16 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint32_t *sigMFBuf = (uint32_t *) buf; + int nbSamples = nbBytes / sizeof(uint32_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromBE(sigMFBuf[2*is]) >> 16) - 32768; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +// u32 real BE => FixReal 24 bits + +template<> +int SigMFConverter::convert(FixReal *convertBuffer, const quint8* buf, int nbBytes) +{ + const uint32_t *sigMFBuf = (uint32_t *) buf; + int nbSamples = nbBytes / sizeof(uint32_t); + + for (int is = 0; is < nbSamples; is++) + { + convertBuffer[2*is] = (sigMFFromBE(sigMFBuf[2*is]) >> 8) - 8388608; + convertBuffer[2*is+1] = 0; + } + + return nbSamples; +} + +#endif // INCLUDE_SIGMFFILEDATA_H \ No newline at end of file diff --git a/plugins/samplesource/sigmffileinput/sigmffiledata.h b/plugins/samplesource/sigmffileinput/sigmffiledata.h new file mode 100644 index 000000000..31d646beb --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffiledata.h @@ -0,0 +1,216 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SIGMFFILEDATA_H +#define INCLUDE_SIGMFFILEDATA_H + +#include + +struct SigMFFileDataType +{ + bool m_complex; + bool m_floatingPoint; + bool m_signed; + bool m_bigEndian; + bool m_swapIQ; + int m_sampleBits; + + SigMFFileDataType() : + m_complex(true), + m_floatingPoint(false), + m_signed(true), + m_bigEndian(false), + m_swapIQ(false), + m_sampleBits(32) + {} + + SigMFFileDataType(const SigMFFileDataType& other) : + m_complex(other.m_complex), + m_floatingPoint(other.m_floatingPoint), + m_signed(other.m_signed), + m_bigEndian(other.m_bigEndian), + m_swapIQ(other.m_swapIQ), + m_sampleBits(other.m_sampleBits) + {} + + SigMFFileDataType& operator=(const SigMFFileDataType& t) + { + // Check for self assignment + if (this != &t) + { + m_complex = t.m_complex; + m_floatingPoint = t.m_floatingPoint; + m_signed = t.m_signed; + m_bigEndian = t.m_bigEndian; + m_swapIQ = t.m_swapIQ; + m_sampleBits = t.m_sampleBits; + } + + return *this; + } + + explicit operator SigMFFileDataType() const { + return SigMFFileDataType{static_cast(*this)}; + } +}; + +struct SigMFFileMetaInfo +{ + // core + QString m_dataTypeStr; + SigMFFileDataType m_dataType; + uint64_t m_totalSamples; + uint64_t m_totalTimeMs; + double m_coreSampleRate; + QString m_sigMFVersion; + QString m_sha512; + unsigned int m_offset; + QString m_description; + QString m_author; + QString m_metaDOI; + QString m_dataDOI; + QString m_recorder; + QString m_license; + QString m_hw; + // sdrangel + QString m_sdrAngelVersion; + QString m_qtVersion; + int m_rxBits; + QString m_arch; + QString m_os; + // lists + unsigned int m_nbCaptures; + unsigned int m_nbAnnotations; + + SigMFFileMetaInfo() + {} + + SigMFFileMetaInfo(const SigMFFileMetaInfo& other) : + m_dataTypeStr(other.m_dataTypeStr), + m_dataType(other.m_dataType), + m_totalSamples(other.m_totalSamples), + m_totalTimeMs(other.m_totalTimeMs), + m_coreSampleRate(other.m_coreSampleRate), + m_sigMFVersion(other.m_sigMFVersion), + m_sha512(other.m_sha512), + m_offset(other.m_offset), + m_description(other.m_description), + m_author(other.m_author), + m_metaDOI(other.m_metaDOI), + m_dataDOI(other.m_dataDOI), + m_recorder(other.m_recorder), + m_license(other.m_license), + m_hw(other.m_hw), + m_sdrAngelVersion(other.m_sdrAngelVersion), + m_qtVersion(other.m_qtVersion), + m_rxBits(other.m_rxBits), + m_arch(other.m_arch), + m_os(other.m_os), + m_nbCaptures(other.m_nbCaptures), + m_nbAnnotations(other.m_nbAnnotations) + {} + + SigMFFileMetaInfo& operator=(const SigMFFileMetaInfo& t) + { + // Check for self assignment + if (this != &t) + { + m_dataTypeStr = t.m_dataTypeStr; + m_dataType = t.m_dataType; + m_totalSamples = t.m_totalSamples; + m_totalTimeMs = t.m_totalTimeMs; + m_coreSampleRate = t.m_coreSampleRate; + m_sigMFVersion = t.m_sigMFVersion; + m_sha512 = t.m_sha512; + m_offset = t.m_offset; + m_description = t.m_description; + m_author = t.m_author; + m_metaDOI = t.m_metaDOI; + m_dataDOI = t.m_dataDOI; + m_recorder = t.m_recorder; + m_license = t.m_license; + m_hw = t.m_hw; + m_sdrAngelVersion = t.m_sdrAngelVersion; + m_qtVersion = t.m_qtVersion; + m_rxBits = t.m_rxBits; + m_arch = t.m_arch; + m_os = t.m_os; + m_nbCaptures = t.m_nbCaptures; + m_nbAnnotations = t.m_nbAnnotations; + } + + return *this; + } + + explicit operator SigMFFileMetaInfo() const { + return SigMFFileMetaInfo{static_cast(*this)}; + } +}; + +struct SigMFFileCapture +{ + uint64_t m_tsms; //!< Unix timestamp in milliseconds + uint64_t m_centerFrequency; //!< Center frequency in Hz + uint64_t m_sampleStart; //!< Sample index at which capture start + uint64_t m_length; //!< Length of capture in samples + uint64_t m_cumulativeTime; //!< Time since beginning of record (millisecond timestamp) at start + unsigned int m_sampleRate; //!< sdrangel extension - sample rate for this capture + + SigMFFileCapture() : + m_tsms(0), + m_centerFrequency(0), + m_sampleStart(0), + m_length(0), + m_cumulativeTime(0), + m_sampleRate(1) + {} + + SigMFFileCapture(const SigMFFileCapture& other) : + m_tsms(other.m_tsms), + m_centerFrequency(other.m_centerFrequency), + m_sampleStart(other.m_sampleStart), + m_length(other.m_length), + m_cumulativeTime(other.m_cumulativeTime), + m_sampleRate(other.m_sampleRate) + {} + + SigMFFileCapture& operator=(const SigMFFileCapture& t) + { + // Check for self assignment + if (this != &t) + { + m_tsms = t.m_tsms; + m_centerFrequency = t.m_centerFrequency; + m_sampleStart = t.m_sampleStart; + m_length = t.m_length; + m_cumulativeTime = t.m_cumulativeTime; + m_sampleRate = t.m_sampleRate; + } + + return *this; + } + + explicit operator SigMFFileCapture() const { + return SigMFFileCapture{static_cast(*this)}; + } + + explicit operator SigMFFileCapture() { + return SigMFFileCapture{static_cast(*this)}; + } +}; + +#endif // INCLUDE_SIGMFFILEDATA_H \ No newline at end of file diff --git a/plugins/samplesource/sigmffileinput/sigmffileinput.cpp b/plugins/samplesource/sigmffileinput/sigmffileinput.cpp new file mode 100644 index 000000000..dc6366f4d --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinput.cpp @@ -0,0 +1,1063 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libsigmf/sigmf_core_generated.h" +#include "libsigmf/sigmf_sdrangel_generated.h" +#include "libsigmf/sigmf.h" + +#include "SWGDeviceSettings.h" +#include "SWGFileInputSettings.h" +#include "SWGDeviceState.h" +#include "SWGDeviceReport.h" + +#include "dsp/dspcommands.h" +#include "dsp/dspdevicesourceengine.h" +#include "dsp/dspengine.h" +#include "device/deviceapi.h" +#include "util/sha512.h" + +#include "sigmffileinput.h" +#include "sigmffileinputthread.h" + +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgConfigureSigMFFileInput, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgConfigureTrackWork, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgConfigureTrackIndex, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgConfigureTrackSeek, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgConfigureFileSeek, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgConfigureFileWork, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgConfigureFileInputStreamTiming, Message) + +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgReportStartStop, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgReportMetaData, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgReportTrackChange, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgReportFileInputStreamTiming, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgReportCRC, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInput::MsgReportTotalSamplesCheck, Message) + +SigMFFileInput::SigMFFileInput(DeviceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_settings(), + m_trackMode(false), + m_currentTrackIndex(0), + m_recordOpen(false), + m_fileInputThread(nullptr), + m_deviceDescription(), + m_sampleRate(48000), + m_sampleBytes(1), + m_centerFrequency(0), + m_recordLength(0), + m_startingTimeStamp(0) +{ + m_deviceAPI->setNbSourceStreams(1); + qDebug("SigMFFileInput::SigMFFileInput: device source engine: %p", m_deviceAPI->getDeviceSourceEngine()); + qDebug("SigMFFileInput::SigMFFileInput: device source engine message queue: %p", m_deviceAPI->getDeviceEngineInputMessageQueue()); + qDebug("SigMFFileInput::SigMFFileInput: device source: %p", m_deviceAPI->getDeviceSourceEngine()->getSource()); + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + m_masterTimer.setTimerType(Qt::PreciseTimer); + m_masterTimer.start(50); +} + +SigMFFileInput::~SigMFFileInput() +{ + m_masterTimer.stop(); + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + delete m_networkManager; + + stop(); +} + +void SigMFFileInput::destroy() +{ + delete this; +} + +bool SigMFFileInput::openFileStreams(const QString& fileName) +{ + if (m_metaStream.is_open()) { + m_metaStream.close(); + } + + if (m_dataStream.is_open()) { + m_dataStream.close(); + } + + QString metaFileName = fileName + ".sigmf-meta"; + QString dataFileName = fileName + ".sigmf-data"; + +#ifdef Q_OS_WIN + m_metaStream.open(metaFileName.toStdWString().c_str()); +#else + m_metaStream.open(metaFileName.toStdString().c_str()); +#endif + +#ifdef Q_OS_WIN + m_dataStream.open(dataFileName.toStdWString().c_str(), std::ios::binary | std::ios::ate); +#else + m_dataStream.open(dataFileName.toStdString().c_str(), std::ios::binary | std::ios::ate); +#endif + + if (!m_dataStream.is_open()) + { + qCritical("SigMFFileInput::openFileStreams: error opening data file %s", qPrintable(dataFileName)); + return false; + } + + uint64_t dataFileSize = m_dataStream.tellg(); + + sigmf::SigMF, + sigmf::Capture, + sigmf::Annotation > metaRecord; + + std::ostringstream meta_buffer; + meta_buffer << m_metaStream.rdbuf(); + from_json(json::parse(meta_buffer.str()), metaRecord); + extractMeta(&metaRecord, dataFileSize); + extractCaptures(&metaRecord); + m_metaInfo.m_totalTimeMs = m_captures.back().m_cumulativeTime + ((m_captures.back().m_length * 1000)/m_captures.back().m_sampleRate); + + uint64_t centerFrequency = (m_captures.size() > 0) ? m_captures.at(0).m_centerFrequency : 0; + DSPSignalNotification *notif = new DSPSignalNotification(m_metaInfo.m_coreSampleRate, centerFrequency); + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); + + if (getMessageQueueToGUI()) + { + MsgReportMetaData *report = MsgReportMetaData::create(m_metaInfo, m_captures); + getMessageQueueToGUI()->push(report); + } + + if (m_metaInfo.m_sha512.size() != 0) + { + qDebug("SigMFFileInput::openFileStreams: compute SHA512"); + std::string sha512 = sw::sha512::file(dataFileName.toStdString()); + bool crcOK = m_metaInfo.m_sha512 == QString::fromStdString(sha512); + + if (crcOK) { + qDebug("SigMFFileInput::openFileStreams: SHA512 OK: %s", sha512.c_str()); + } else { + qCritical("SigMFFileInput::openFileStreams: bad SHA512: %s expected: %s", sha512.c_str(), qPrintable(m_metaInfo.m_sha512)); + } + + if (getMessageQueueToGUI()) + { + MsgReportCRC *report = MsgReportCRC::create(crcOK); + getMessageQueueToGUI()->push(report); + } + + if (!crcOK) { + return false; + } + } + + bool totalSamplesCheck = (m_metaInfo.m_totalSamples == m_captures.back().m_sampleStart + m_captures.back().m_length); + + if (totalSamplesCheck) { + qDebug("SigMFFileInput::openFileStreams: total samples OK"); + } else { + qCritical("SigMFFileInput::openFileStreams: invalid total samples: meta: %lu data: %lu", + m_captures.back().m_sampleStart + m_captures.back().m_length, m_metaInfo.m_totalSamples); + } + + if (getMessageQueueToGUI()) + { + MsgReportTotalSamplesCheck *report = MsgReportTotalSamplesCheck::create(totalSamplesCheck); + getMessageQueueToGUI()->push(report); + } + + return true; +} + +void SigMFFileInput::extractMeta( + sigmf::SigMF, + sigmf::Capture, + sigmf::Annotation >* metaRecord, + uint64_t dataFileSize +) +{ + // core + m_metaInfo.m_dataTypeStr = QString::fromStdString(metaRecord->global.access().datatype); + analyzeDataType(m_metaInfo.m_dataTypeStr.toStdString(), m_metaInfo.m_dataType); + m_sampleBytes = SigMFFileInputSettings::bitsToBytes(m_metaInfo.m_dataType.m_sampleBits); + m_metaInfo.m_totalSamples = dataFileSize / (SigMFFileInputSettings::bitsToBytes(m_metaInfo.m_dataType.m_sampleBits)*2); + m_metaInfo.m_coreSampleRate = metaRecord->global.access().sample_rate; + m_metaInfo.m_sigMFVersion = QString::fromStdString(metaRecord->global.access().version); + m_metaInfo.m_sha512 = QString::fromStdString(metaRecord->global.access().sha512); + m_metaInfo.m_offset = metaRecord->global.access().offset; + m_metaInfo.m_description = QString::fromStdString(metaRecord->global.access().description); + m_metaInfo.m_author = QString::fromStdString(metaRecord->global.access().author); + m_metaInfo.m_metaDOI = QString::fromStdString(metaRecord->global.access().meta_doi); + m_metaInfo.m_dataDOI = QString::fromStdString(metaRecord->global.access().meta_doi); + m_metaInfo.m_recorder = QString::fromStdString(metaRecord->global.access().recorder); + m_metaInfo.m_license = QString::fromStdString(metaRecord->global.access().license); + m_metaInfo.m_hw = QString::fromStdString(metaRecord->global.access().hw); + // sdrangel + m_metaInfo.m_sdrAngelVersion = QString::fromStdString(metaRecord->global.access().version); + m_metaInfo.m_qtVersion = QString::fromStdString(metaRecord->global.access().qt_version); + m_metaInfo.m_rxBits = metaRecord->global.access().rx_bits; + m_metaInfo.m_arch = QString::fromStdString(metaRecord->global.access().arch); + m_metaInfo.m_os = QString::fromStdString(metaRecord->global.access().os); + // lists + m_metaInfo.m_nbCaptures = metaRecord->captures.size(); + m_metaInfo.m_nbAnnotations = metaRecord->annotations.size(); + // correct sample bits if sdrangel + if (m_metaInfo.m_sdrAngelVersion.size() > 0) + { + if (m_metaInfo.m_dataType.m_sampleBits == 32) { + m_metaInfo.m_dataType.m_sampleBits = 24; + } + } + // negative sample rate means inversion + m_metaInfo.m_dataType.m_swapIQ = m_metaInfo.m_coreSampleRate < 0; + if (m_metaInfo.m_coreSampleRate < 0) { + m_metaInfo.m_coreSampleRate = -m_metaInfo.m_coreSampleRate; + } +} + +void SigMFFileInput::extractCaptures( + sigmf::SigMF, + sigmf::Capture, + sigmf::Annotation >* metaRecord +) +{ + m_captures.clear(); + std::regex datetime_reg("(\\d{4})-(\\d\\d)-(\\d\\d)T(\\d\\d):(\\d\\d):(\\d\\d)(\\.\\d+)?(([+-]\\d\\d:\\d\\d)|Z)?"); + std::smatch datetime_match; + + sigmf::SigMFVector>::iterator it = + metaRecord->captures.begin(); + uint64_t lastSampleStart = 0; + unsigned int i = 0; + uint64_t cumulativeTime = 0; + + for (; it != metaRecord->captures.end(); ++it, i++) + { + m_captures.push_back(SigMFFileCapture()); + m_captures.back().m_centerFrequency = it->get().frequency; + m_captures.back().m_sampleStart = it->get().sample_start; + m_captureStarts.push_back(m_captures.back().m_sampleStart); + m_captures.back().m_cumulativeTime = cumulativeTime; + int sdrangelSampleRate = it->get().sample_rate; + + if (sdrangelSampleRate == 0) { + m_captures.back().m_sampleRate = metaRecord->global.access().sample_rate; + } else { + m_captures.back().m_sampleRate = sdrangelSampleRate; + } + + uint64_t tsms = it->get().tsms; + + if (tsms) + { + m_captures.back().m_tsms = tsms; + } + else + { + std::regex_search(it->get().datetime, datetime_match, datetime_reg); + QString dateTimeString; + QDateTime dateTime; + + if (datetime_match.size() > 6) { + dateTimeString = QString("%1-%2-%3T%4:%5:%6") + .arg(QString::fromStdString(datetime_match[1])) // year + .arg(QString::fromStdString(datetime_match[2])) // month + .arg(QString::fromStdString(datetime_match[3])) // day + .arg(QString::fromStdString(datetime_match[4])) // hour + .arg(QString::fromStdString(datetime_match[5])) // minute + .arg(QString::fromStdString(datetime_match[6])); // second + dateTime = QDateTime::fromString(dateTimeString, "yyyy-MM-ddThh:mm:ss"); + + // skip timezone calculation - assume UTC + if (dateTime.isValid()) { + dateTime.setTimeZone(QTimeZone::utc()); + } else { + dateTime = QDateTime::currentDateTimeUtc(); + } + } + else + { + dateTime = QDateTime::currentDateTimeUtc(); + } + + double seconds = dateTime.toSecsSinceEpoch(); + // the subsecond part can be milli (strict ISO-8601) or micro or nano (RFC-3339). This will take any width + if (datetime_match.size() > 7) + { + try + { + double fractionalSecs = boost::lexical_cast(datetime_match[7]); + seconds += fractionalSecs; + } + catch (const boost::bad_lexical_cast &e) + { + qDebug("SigMFFileInput::extractCaptures: invalid fractional seconds"); + } + } + + m_captures.back().m_tsms = seconds * 1000.0; + } + + m_captures.back().m_length = it->get().length; + + if ((i != 0) && (m_captures.at(i-1).m_length == 0)) + { + m_captures[i-1].m_length = m_captures.at(i).m_sampleStart - lastSampleStart; + lastSampleStart = m_captures.at(i).m_sampleStart; + } + + cumulativeTime += (m_captures.back().m_length * 1000) / m_captures.back().m_sampleRate; + } + + if (m_captures.back().m_length == 0) { + m_captures.back().m_length = m_metaInfo.m_totalSamples - m_captures.back().m_sampleStart; + } +} + +void SigMFFileInput::analyzeDataType(const std::string& dataTypeString, SigMFFileDataType& dataType) +{ + std::regex dataType_reg("(\\w)(\\w)(\\d+)(_\\w\\w)?"); + std::smatch dataType_match; + std::regex_search(dataTypeString, dataType_match, dataType_reg); + + if (dataType_match.size() > 3) + { + dataType.m_complex = (dataType_match[1] == "c"); + + if (dataType_match[2] == "f") + { + dataType.m_floatingPoint = true; + dataType.m_signed = true; + } + else if (dataType_match[2] == "i") + { + dataType.m_floatingPoint = false; + dataType.m_signed = true; + } + else + { + dataType.m_floatingPoint = false; + dataType.m_signed = false; + } + + try + { + dataType.m_sampleBits = boost::lexical_cast(dataType_match[3]); + } + catch(const boost::bad_lexical_cast &e) + { + qDebug("SigMFFileInput::analyzeDataType: invalid sample bits. Assume 32"); + dataType.m_sampleBits = 32; + } + } + + if (dataType_match.size() > 4) { + dataType.m_bigEndian = (dataType_match[4] == "_be"); + } +} + +uint64_t SigMFFileInput::getTrackSampleStart(int trackIndex) +{ + if (trackIndex < m_captureStarts.size()) { + return m_captureStarts[trackIndex]; + } else { + return m_metaInfo.m_totalSamples; + } +} + +int SigMFFileInput::getTrackIndex(uint64_t sampleIndex) +{ + auto it = std::upper_bound(m_captureStarts.begin(), m_captureStarts.end(), sampleIndex); + return (it - m_captureStarts.begin()) - 1; +} + +void SigMFFileInput::seekFileStream(uint64_t sampleIndex) +{ + QMutexLocker mutexLocker(&m_mutex); + + if (m_dataStream.is_open()) + { + uint64_t seekPoint = sampleIndex*m_sampleBytes*2; + m_dataStream.clear(); + m_dataStream.seekg(seekPoint, std::ios::beg); + } +} + +void SigMFFileInput::seekTrackMillis(int seekMillis) +{ + seekFileStream(m_captures[m_currentTrackIndex].m_sampleStart + ((m_captures[m_currentTrackIndex].m_length*seekMillis)/1000UL)); +} + +void SigMFFileInput::seekFileMillis(int seekMillis) +{ + seekFileStream((m_metaInfo.m_totalSamples*seekMillis)/1000UL); +} + +void SigMFFileInput::init() +{ + DSPSignalNotification *notif = new DSPSignalNotification(m_sampleRate, m_centerFrequency); + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); +} + +bool SigMFFileInput::start() +{ + if (!m_dataStream.is_open()) + { + qWarning("SigMFFileInput::start: file not open. not starting"); + return false; + } + + QMutexLocker mutexLocker(&m_mutex); + qDebug() << "SigMFFileInput::start"; + + if (m_dataStream.tellg() != (std::streampos) 0) { + m_dataStream.clear(); + m_dataStream.seekg(0, std::ios::beg); + } + + if(!m_sampleFifo.setSize(m_settings.m_accelerationFactor * m_sampleRate * sizeof(Sample))) { + qCritical("Could not allocate SampleFifo"); + return false; + } + + m_fileInputThread = new SigMFFileInputThread(&m_dataStream, &m_sampleFifo, m_masterTimer, &m_inputMessageQueue); + m_fileInputThread->setMetaInformation(&m_metaInfo, &m_captures); + m_fileInputThread->setAccelerationFactor(m_settings.m_accelerationFactor); + m_fileInputThread->setTrackIndex(0); + m_fileInputThread->startWork(); + m_deviceDescription = "SigMFFileInput"; + + mutexLocker.unlock(); + qDebug("SigMFFileInput::startInput: started"); + + if (getMessageQueueToGUI()) { + MsgReportStartStop *report = MsgReportStartStop::create(true); + getMessageQueueToGUI()->push(report); + } + + return true; +} + +void SigMFFileInput::stop() +{ + qDebug() << "SigMFFileInput::stop"; + QMutexLocker mutexLocker(&m_mutex); + + if (m_fileInputThread) + { + m_fileInputThread->stopWork(); + delete m_fileInputThread; + m_fileInputThread = nullptr; + } + + m_deviceDescription.clear(); + + if (getMessageQueueToGUI()) { + MsgReportStartStop *report = MsgReportStartStop::create(false); + getMessageQueueToGUI()->push(report); + } +} + +QByteArray SigMFFileInput::serialize() const +{ + return m_settings.serialize(); +} + +bool SigMFFileInput::deserialize(const QByteArray& data) +{ + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureSigMFFileInput* message = MsgConfigureSigMFFileInput::create(m_settings, true); + m_inputMessageQueue.push(message); + + if (getMessageQueueToGUI()) + { + MsgConfigureSigMFFileInput* messageToGUI = MsgConfigureSigMFFileInput::create(m_settings, true); + getMessageQueueToGUI()->push(messageToGUI); + } + + return success; +} + +const QString& SigMFFileInput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int SigMFFileInput::getSampleRate() const +{ + return m_sampleRate; +} + +quint64 SigMFFileInput::getCenterFrequency() const +{ + return m_centerFrequency; +} + +void SigMFFileInput::setCenterFrequency(qint64 centerFrequency) +{ + SigMFFileInputSettings settings = m_settings; + m_centerFrequency = centerFrequency; + + MsgConfigureSigMFFileInput* message = MsgConfigureSigMFFileInput::create(m_settings, false); + m_inputMessageQueue.push(message); + + if (getMessageQueueToGUI()) + { + MsgConfigureSigMFFileInput* messageToGUI = MsgConfigureSigMFFileInput::create(m_settings, false); + getMessageQueueToGUI()->push(messageToGUI); + } +} + +quint64 SigMFFileInput::getStartingTimeStamp() const +{ + return m_startingTimeStamp; +} + +bool SigMFFileInput::handleMessage(const Message& message) +{ + if (MsgConfigureSigMFFileInput::match(message)) + { + MsgConfigureSigMFFileInput& conf = (MsgConfigureSigMFFileInput&) message; + SigMFFileInputSettings settings = conf.getSettings(); + applySettings(settings); + return true; + } + else if (MsgConfigureTrackIndex::match(message)) + { + MsgConfigureTrackIndex& conf = (MsgConfigureTrackIndex&) message; + m_currentTrackIndex = conf.getTrackIndex(); + qDebug("SigMFFileInput::handleMessage MsgConfigureTrackIndex: m_currentTrackIndex: %d", m_currentTrackIndex); + seekTrackMillis(0); + + if (m_fileInputThread) + { + bool working = m_fileInputThread->isRunning(); + + if (working) { + m_fileInputThread->stopWork(); + } + + m_fileInputThread->setTrackIndex(m_currentTrackIndex); + m_fileInputThread->setTotalSamples( + m_trackMode ? + m_captures[m_currentTrackIndex].m_sampleStart + m_captures[m_currentTrackIndex].m_length : + m_metaInfo.m_totalSamples + ); + + if (working) { + m_fileInputThread->startWork(); + } + } + } + else if (MsgConfigureTrackWork::match(message)) + { + MsgConfigureTrackWork& conf = (MsgConfigureTrackWork&) message; + bool working = conf.isWorking(); + m_trackMode = true; + + if (m_fileInputThread) + { + if (working) + { + m_fileInputThread->setTotalSamples( + m_captures[m_currentTrackIndex].m_sampleStart + m_captures[m_currentTrackIndex].m_length); + m_fileInputThread->startWork(); + } + else + { + m_fileInputThread->stopWork(); + } + } + + return true; + } + else if (MsgConfigureTrackSeek::match(message)) + { + MsgConfigureTrackSeek& conf = (MsgConfigureTrackSeek&) message; + int seekMillis = conf.getMillis(); + seekTrackMillis(seekMillis); + + if (m_fileInputThread) + { + bool working = m_fileInputThread->isRunning(); + + if (working) { + m_fileInputThread->stopWork(); + } + + m_fileInputThread->setSamplesCount( + m_captures[m_currentTrackIndex].m_sampleStart + ((m_captures[m_currentTrackIndex].m_length*seekMillis)/1000UL)); + + if (working) { + m_fileInputThread->startWork(); + } + } + + return true; + } + else if (MsgConfigureFileSeek::match(message)) + { + MsgConfigureFileSeek& conf = (MsgConfigureFileSeek&) message; + int seekMillis = conf.getMillis(); + seekFileStream(seekMillis); + uint64_t sampleCount = (m_metaInfo.m_totalSamples*seekMillis)/1000UL; + m_currentTrackIndex = getTrackIndex(sampleCount); + + if (m_fileInputThread) + { + bool working = m_fileInputThread->isRunning(); + + if (working) { + m_fileInputThread->stopWork(); + } + + m_fileInputThread->setTrackIndex(m_currentTrackIndex); + m_fileInputThread->setSamplesCount(sampleCount); + + if (working) { + m_fileInputThread->startWork(); + } + } + + return true; + } + else if (MsgConfigureFileWork::match(message)) + { + MsgConfigureFileWork& conf = (MsgConfigureFileWork&) message; + bool working = conf.isWorking(); + m_trackMode = false; + + if (m_fileInputThread) + { + if (working) + { + m_fileInputThread->setTotalSamples(m_metaInfo.m_totalSamples); + m_fileInputThread->startWork(); + } + else + { + m_fileInputThread->stopWork(); + } + } + + return true; + } + else if (MsgConfigureFileInputStreamTiming::match(message)) + { + if (m_fileInputThread) + { + if (getMessageQueueToGUI()) + { + quint64 totalSamplesCount = m_fileInputThread->getSamplesCount(); + quint64 trackSamplesCount = totalSamplesCount - m_captures[m_currentTrackIndex].m_sampleStart; + MsgReportFileInputStreamTiming *report = MsgReportFileInputStreamTiming::create( + totalSamplesCount, + trackSamplesCount, + m_captures[m_currentTrackIndex].m_cumulativeTime, + m_currentTrackIndex + ); + getMessageQueueToGUI()->push(report); + } + } + + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "FileInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initDeviceEngine()) { + m_deviceAPI->startDeviceEngine(); + } + } + else + { + m_deviceAPI->stopDeviceEngine(); + } + + if (m_settings.m_useReverseAPI) { + webapiReverseSendStartStop(cmd.getStartStop()); + } + + return true; + } + else if (SigMFFileInputThread::MsgReportEOF::match(message)) // End Of File or end of track + { + qDebug() << "FileInput::handleMessage: MsgReportEOF"; + bool working = m_fileInputThread->isRunning(); + + if (working) { + m_fileInputThread->stopWork(); + } + + if (m_trackMode) + { + if (m_settings.m_trackLoop) + { + seekFileStream(m_captures[m_currentTrackIndex].m_sampleStart); + m_fileInputThread->setTrackIndex(m_currentTrackIndex); + } + } + else + { + if (m_settings.m_fullLoop) + { + seekFileStream(0); + m_fileInputThread->setTrackIndex(0); + } + } + + if (working) { + m_fileInputThread->startWork(); + } + + return true; + } + else if (SigMFFileInputThread::MsgReportTrackChange::match(message)) + { + SigMFFileInputThread::MsgReportTrackChange& report = (SigMFFileInputThread::MsgReportTrackChange&) message; + m_currentTrackIndex = report.getTrackIndex(); + qDebug("SigMFFileInput::handleMessage MsgReportTrackChange: m_currentTrackIndex: %d", m_currentTrackIndex); + int sampleRate = m_captures.at(m_currentTrackIndex).m_sampleRate; + uint64_t centerFrequency = m_captures.at(m_currentTrackIndex).m_centerFrequency; + + if ((m_sampleRate != sampleRate) || (m_centerFrequency != centerFrequency)) + { + DSPSignalNotification *notif = new DSPSignalNotification(sampleRate, centerFrequency); + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); + + m_sampleRate = sampleRate; + m_centerFrequency = centerFrequency; + } + + if (getMessageQueueToGUI()) + { + MsgReportTrackChange *msgToGUI = MsgReportTrackChange::create(m_currentTrackIndex); + getMessageQueueToGUI()->push(msgToGUI); + } + } + else + { + return false; + } +} + +bool SigMFFileInput::applySettings(const SigMFFileInputSettings& settings, bool force) +{ + QList reverseAPIKeys; + + if ((m_settings.m_accelerationFactor != settings.m_accelerationFactor) || force) + { + reverseAPIKeys.append("accelerationFactor"); + + if (m_fileInputThread) + { + QMutexLocker mutexLocker(&m_mutex); + if (!m_sampleFifo.setSize(m_settings.m_accelerationFactor * m_sampleRate * sizeof(Sample))) { + qCritical("SigMFFileInput::applySettings: could not reallocate sample FIFO size to %lu", + m_settings.m_accelerationFactor * m_sampleRate * sizeof(Sample)); + } + m_fileInputThread->setAccelerationFactor(settings.m_accelerationFactor); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed + } + } + + if ((m_settings.m_trackLoop != settings.m_trackLoop)) { + reverseAPIKeys.append("trackLoop"); + } + + if ((m_settings.m_fileName != settings.m_fileName)) + { + reverseAPIKeys.append("fileName"); + openFileStreams(settings.m_fileName); + } + + if (settings.m_useReverseAPI) + { + bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || + (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || + (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || + (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex); + webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); + } + + m_settings = settings; + return true; +} + +int SigMFFileInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setFileInputSettings(new SWGSDRangel::SWGFileInputSettings()); + response.getFileInputSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int SigMFFileInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage) +{ + (void) errorMessage; + SigMFFileInputSettings settings = m_settings; + webapiUpdateDeviceSettings(settings, deviceSettingsKeys, response); + + MsgConfigureSigMFFileInput *msg = MsgConfigureSigMFFileInput::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSigMFFileInput *msgToGUI = MsgConfigureSigMFFileInput::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void SigMFFileInput::webapiUpdateDeviceSettings( + SigMFFileInputSettings& settings, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response) +{ + if (deviceSettingsKeys.contains("fileName")) { + settings.m_fileName = *response.getFileInputSettings()->getFileName(); + } + if (deviceSettingsKeys.contains("accelerationFactor")) { + settings.m_accelerationFactor = response.getFileInputSettings()->getAccelerationFactor(); + } + if (deviceSettingsKeys.contains("trackLoop")) { + settings.m_trackLoop = response.getFileInputSettings()->getLoop() != 0; + } + if (deviceSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getFileInputSettings()->getUseReverseApi() != 0; + } + if (deviceSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getFileInputSettings()->getReverseApiAddress(); + } + if (deviceSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getFileInputSettings()->getReverseApiPort(); + } + if (deviceSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIDeviceIndex = response.getFileInputSettings()->getReverseApiDeviceIndex(); + } +} + +int SigMFFileInput::webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) +{ + (void) errorMessage; + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + return 200; +} + +int SigMFFileInput::webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) +{ + (void) errorMessage; + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + MsgStartStop *message = MsgStartStop::create(run); + m_inputMessageQueue.push(message); + + if (getMessageQueueToGUI()) // forward to GUI if any + { + MsgStartStop *msgToGUI = MsgStartStop::create(run); + getMessageQueueToGUI()->push(msgToGUI); + } + + return 200; +} + +int SigMFFileInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setFileInputReport(new SWGSDRangel::SWGFileInputReport()); + response.getFileInputReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void SigMFFileInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SigMFFileInputSettings& settings) +{ + response.getFileInputSettings()->setFileName(new QString(settings.m_fileName)); + response.getFileInputSettings()->setAccelerationFactor(settings.m_accelerationFactor); + response.getFileInputSettings()->setLoop(settings.m_trackLoop ? 1 : 0); + + response.getFileInputSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getFileInputSettings()->getReverseApiAddress()) { + *response.getFileInputSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getFileInputSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getFileInputSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getFileInputSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); +} + +void SigMFFileInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + qint64 t_sec = 0; + qint64 t_msec = 0; + quint64 samplesCount = 0; + + if (m_fileInputThread) { + samplesCount = m_fileInputThread->getSamplesCount(); + } + + if (m_sampleRate > 0) + { + t_sec = samplesCount / m_sampleRate; + t_msec = (samplesCount - (t_sec * m_sampleRate)) * 1000 / m_sampleRate; + } + + QTime t(0, 0, 0, 0); + t = t.addSecs(t_sec); + t = t.addMSecs(t_msec); + response.getFileInputReport()->setElapsedTime(new QString(t.toString("HH:mm:ss.zzz"))); + + qint64 startingTimeStampMsec = m_startingTimeStamp * 1000LL; + QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); + dt = dt.addSecs(t_sec); + dt = dt.addMSecs(t_msec); + response.getFileInputReport()->setAbsoluteTime(new QString(dt.toString("yyyy-MM-dd HH:mm:ss.zzz"))); + + QTime recordLength(0, 0, 0, 0); + recordLength = recordLength.addSecs(m_recordLength); + response.getFileInputReport()->setDurationTime(new QString(recordLength.toString("HH:mm:ss"))); + + response.getFileInputReport()->setSampleRate(m_sampleRate); + response.getFileInputReport()->setSampleSize(m_sampleBytes); +} + +void SigMFFileInput::webapiReverseSendSettings(QList& deviceSettingsKeys, const SigMFFileInputSettings& settings, bool force) +{ + SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings(); + swgDeviceSettings->setDirection(0); // single Rx + swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex()); + swgDeviceSettings->setDeviceHwType(new QString("SigMFFileInput")); + swgDeviceSettings->setFileInputSettings(new SWGSDRangel::SWGFileInputSettings()); + SWGSDRangel::SWGFileInputSettings *swgFileInputSettings = swgDeviceSettings->getFileInputSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (deviceSettingsKeys.contains("accelerationFactor") || force) { + swgFileInputSettings->setAccelerationFactor(settings.m_accelerationFactor); + } + if (deviceSettingsKeys.contains("loop") || force) { + swgFileInputSettings->setLoop(settings.m_trackLoop); + } + if (deviceSettingsKeys.contains("fileName") || force) { + swgFileInputSettings->setFileName(new QString(settings.m_fileName)); + } + + QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/settings") + .arg(settings.m_reverseAPIAddress) + .arg(settings.m_reverseAPIPort) + .arg(settings.m_reverseAPIDeviceIndex); + m_networkRequest.setUrl(QUrl(deviceSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgDeviceSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + buffer->setParent(reply); + + delete swgDeviceSettings; +} + +void SigMFFileInput::webapiReverseSendStartStop(bool start) +{ + SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings(); + swgDeviceSettings->setDirection(0); // single Rx + swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex()); + swgDeviceSettings->setDeviceHwType(new QString("SigMFFileInput")); + + QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/run") + .arg(m_settings.m_reverseAPIAddress) + .arg(m_settings.m_reverseAPIPort) + .arg(m_settings.m_reverseAPIDeviceIndex); + m_networkRequest.setUrl(QUrl(deviceSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgDeviceSettings->asJson().toUtf8()); + buffer->seek(0); + QNetworkReply *reply; + + if (start) { + reply = m_networkManager->sendCustomRequest(m_networkRequest, "POST", buffer); + } else { + reply = m_networkManager->sendCustomRequest(m_networkRequest, "DELETE", buffer); + } + + buffer->setParent(reply); + delete swgDeviceSettings; +} + +void SigMFFileInput::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "SigMFFileInput::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("SigMFFileInput::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} diff --git a/plugins/samplesource/sigmffileinput/sigmffileinput.h b/plugins/samplesource/sigmffileinput/sigmffileinput.h new file mode 100644 index 000000000..cdd14ca10 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinput.h @@ -0,0 +1,489 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SIGMFFILEINPUT_H +#define INCLUDE_SIGMFFILEINPUT_H + +#include +#include +#include + +#include +#include +#include +#include + +#include "dsp/sigmf_forward.h" + +#include "dsp/devicesamplesource.h" +#include "sigmffileinputsettings.h" +#include "sigmffiledata.h" + +class QNetworkAccessManager; +class QNetworkReply; +class SigMFFileInputThread; +class DeviceAPI; + +class SigMFFileInput : public DeviceSampleSource { + Q_OBJECT +public: + /** + * Communicate settings + */ + class MsgConfigureSigMFFileInput : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SigMFFileInputSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureSigMFFileInput* create(const SigMFFileInputSettings& settings, bool force) + { + return new MsgConfigureSigMFFileInput(settings, force); + } + + private: + SigMFFileInputSettings m_settings; + bool m_force; + + MsgConfigureSigMFFileInput(const SigMFFileInputSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + /** + * Start/stop track play + */ + class MsgConfigureTrackWork : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool isWorking() const { return m_working; } + + static MsgConfigureTrackWork* create(bool working) { + return new MsgConfigureTrackWork(working); + } + + private: + bool m_working; + + MsgConfigureTrackWork(bool working) : + Message(), + m_working(working) + { } + }; + + + /** + * Start/stop full file play + */ + class MsgConfigureFileWork : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool isWorking() const { return m_working; } + + static MsgConfigureFileWork* create(bool working) { + return new MsgConfigureFileWork(working); + } + + private: + bool m_working; + + MsgConfigureFileWork(bool working) : + Message(), + m_working(working) + { } + }; + + /** + * Move to track + */ + class MsgConfigureTrackIndex : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getTrackIndex() const { return m_trackIndex; } + + static MsgConfigureTrackIndex* create(int trackIndex) + { + return new MsgConfigureTrackIndex(trackIndex); + } + + private: + int m_trackIndex; + + MsgConfigureTrackIndex(int trackIndex) : + Message(), + m_trackIndex(trackIndex) + { } + }; + + /** + * Seek position in track + */ + class MsgConfigureTrackSeek : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getMillis() const { return m_seekMillis; } + + static MsgConfigureTrackSeek* create(int seekMillis) + { + return new MsgConfigureTrackSeek(seekMillis); + } + + protected: + int m_seekMillis; //!< millis of seek position from the beginning 0..1000 + + MsgConfigureTrackSeek(int seekMillis) : + Message(), + m_seekMillis(seekMillis) + { } + }; + + /** + * Seek position in full file + */ + class MsgConfigureFileSeek : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getMillis() const { return m_seekMillis; } + + static MsgConfigureFileSeek* create(int seekMillis) + { + return new MsgConfigureFileSeek(seekMillis); + } + + protected: + int m_seekMillis; //!< millis of seek position from the beginning 0..1000 + + MsgConfigureFileSeek(int seekMillis) : + Message(), + m_seekMillis(seekMillis) + { } + }; + + /** + * Pull stram timing information + */ + class MsgConfigureFileInputStreamTiming : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgConfigureFileInputStreamTiming* create() + { + return new MsgConfigureFileInputStreamTiming(); + } + + private: + + MsgConfigureFileInputStreamTiming() : + Message() + { } + }; + + /** + * Start/stop plugin + */ + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + /** + * Push start/stop information + */ + class MsgReportStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgReportStartStop* create(bool startStop) + { + return new MsgReportStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgReportStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + /** + * Push meta data information + */ + class MsgReportMetaData : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SigMFFileMetaInfo& getMetaInfo() const { return m_metaInfo; } + const QList& getCaptures() { return m_captures; } + + static MsgReportMetaData* create(const SigMFFileMetaInfo& metaInfo, const QList& captures) { + return new MsgReportMetaData(metaInfo, captures); + } + + protected: + SigMFFileMetaInfo m_metaInfo; + QList m_captures; + + MsgReportMetaData(const SigMFFileMetaInfo& metaInfo, const QList& captures) : + Message(), + m_metaInfo(metaInfo), + m_captures(captures) + {} + }; + + /** + * Push track change + */ + class MsgReportTrackChange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getTrackIndex() const { return m_trackIndex; } + static MsgReportTrackChange* create(int trackIndex) { + return new MsgReportTrackChange(trackIndex); + } + + private: + int m_trackIndex; + MsgReportTrackChange(int trackIndex) : + Message(), + m_trackIndex(trackIndex) + { } + }; + + /** + * Push stream timing information + */ + class MsgReportFileInputStreamTiming : public Message { + MESSAGE_CLASS_DECLARATION + + public: + quint64 getSamplesCount() const { return m_samplesCount; } + quint64 getTrackSamplesCount() const { return m_trackSamplesCount; } + quint64 getTrackTimeStart() const { return m_trackTimeStart; } + int getTrackNumber() const { return m_trackNumber; } + + static MsgReportFileInputStreamTiming* create( + quint64 samplesCount, + quint64 trackSamplesCount, + quint64 trackTimeStart, + int trackNumber + ) + { + return new MsgReportFileInputStreamTiming( + samplesCount, + trackSamplesCount, + trackTimeStart, + trackNumber + ); + } + + protected: + quint64 m_samplesCount; + quint64 m_trackSamplesCount; + quint64 m_trackTimeStart; + int m_trackNumber; + + MsgReportFileInputStreamTiming( + quint64 samplesCount, + quint64 trackSamplesCount, + quint64 trackTimeStart, + int trackNumber + ) : + Message(), + m_samplesCount(samplesCount), + m_trackSamplesCount(trackSamplesCount), + m_trackTimeStart(trackTimeStart), + m_trackNumber(trackNumber) + { } + }; + + /** + * Push CRC (SHA512) information + */ + class MsgReportCRC : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool isOK() const { return m_ok; } + + static MsgReportCRC* create(bool ok) { + return new MsgReportCRC(ok); + } + + protected: + bool m_ok; + + MsgReportCRC(bool ok) : + Message(), + m_ok(ok) + { } + }; + + /** + * Push record total check information + */ + class MsgReportTotalSamplesCheck : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool isOK() const { return m_ok; } + + static MsgReportTotalSamplesCheck* create(bool ok) { + return new MsgReportTotalSamplesCheck(ok); + } + + protected: + bool m_ok; + + MsgReportTotalSamplesCheck(bool ok) : + Message(), + m_ok(ok) + { } + }; + + SigMFFileInput(DeviceAPI *deviceAPI); + virtual ~SigMFFileInput(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual void setSampleRate(int sampleRate) { (void) sampleRate; } + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + quint64 getStartingTimeStamp() const; + + virtual bool handleMessage(const Message& message); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + + static void webapiFormatDeviceSettings( + SWGSDRangel::SWGDeviceSettings& response, + const SigMFFileInputSettings& settings); + + static void webapiUpdateDeviceSettings( + SigMFFileInputSettings& settings, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response); + +private: + DeviceAPI *m_deviceAPI; + QMutex m_mutex; + SigMFFileInputSettings m_settings; + std::ifstream m_metaStream; + std::ifstream m_dataStream; + SigMFFileMetaInfo m_metaInfo; + QList m_captures; + std::vector m_captureStarts; + bool m_trackMode; + int m_currentTrackIndex; + bool m_recordOpen; + QString m_recordSummary; + SigMFFileInputThread* m_fileInputThread; + QString m_deviceDescription; + int m_sampleRate; + unsigned int m_sampleBytes; + quint64 m_centerFrequency; + quint64 m_recordLength; //!< record length in seconds computed from file size + quint64 m_startingTimeStamp; + QTimer m_masterTimer; + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + bool openFileStreams(const QString& fileName); + void extractMeta( + sigmf::SigMF, + sigmf::Capture, + sigmf::Annotation >* metaRecord, + uint64_t dataFileSize + ); + void extractCaptures( + sigmf::SigMF, + sigmf::Capture, + sigmf::Annotation >* metaRecord + ); + static void analyzeDataType(const std::string& dataTypeString, SigMFFileDataType& dataType); + uint64_t getTrackSampleStart(int trackIndex); + int getTrackIndex(uint64_t sampleIndex); + void seekFileStream(uint64_t sampleIndex); + void seekTrackMillis(int seekMillis); + void seekFileMillis(int seekMillis); + bool applySettings(const SigMFFileInputSettings& settings, bool force = false); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); + void webapiReverseSendSettings(QList& deviceSettingsKeys, const SigMFFileInputSettings& settings, bool force); + void webapiReverseSendStartStop(bool start); + +private slots: + void networkManagerFinished(QNetworkReply *reply); +}; + +#endif // INCLUDE_SIGMFFILEINPUT_H diff --git a/plugins/samplesource/sigmffileinput/sigmffileinputgui.cpp b/plugins/samplesource/sigmffileinput/sigmffileinputgui.cpp new file mode 100644 index 000000000..39a6ba1d8 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputgui.cpp @@ -0,0 +1,699 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include +#include + +#include "ui_sigmffileinputgui.h" +#include "plugin/pluginapi.h" +#include "gui/colormapper.h" +#include "gui/glspectrum.h" +#include "gui/crightclickenabler.h" +#include "gui/basicdevicesettingsdialog.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "dsp/filerecordinterface.h" +#include "device/deviceapi.h" +#include "device/deviceuiset.h" + +#include "mainwindow.h" + +#include "recordinfodialog.h" +#include "sigmffileinputgui.h" + +SigMFFileInputGUI::SigMFFileInputGUI(DeviceUISet *deviceUISet, QWidget* parent) : + QWidget(parent), + ui(new Ui::SigMFFileInputGUI), + m_deviceUISet(deviceUISet), + m_settings(), + m_currentTrackIndex(0), + m_doApplySettings(true), + m_sampleSource(0), + m_startStop(false), + m_trackMode(false), + m_metaFileName("..."), + m_sampleRate(48000), + m_centerFrequency(0), + m_recordLength(0), + m_startingTimeStamp(0), + m_samplesCount(0), + m_tickCount(0), + m_enableTrackNavTime(false), + m_enableFullNavTime(false), + m_lastEngineState(DeviceAPI::StNotStarted) +{ + ui->setupUi(this); + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->centerFrequency->setValueRange(7, 0, pow(10,7)); + ui->fileNameText->setText(m_metaFileName); + ui->crcLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + ui->captureTable->setSelectionMode(QAbstractItemView::NoSelection); + + connect(&(m_deviceUISet->m_deviceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(500); + + CRightClickEnabler *startStopRightClickEnabler = new CRightClickEnabler(ui->startStop); + connect(startStopRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(openDeviceSettingsDialog(const QPoint &))); + + setAccelerationCombo(); + displaySettings(); + updateStartStop(); + + ui->trackNavTimeSlider->setEnabled(false); + ui->fullNavTimeSlider->setEnabled(false); + ui->acceleration->setEnabled(false); + ui->playFull->setEnabled(false); + ui->playFull->setChecked(false); + ui->playTrack->setEnabled(false); + ui->playTrack->setChecked(false); + + m_sampleSource = m_deviceUISet->m_deviceAPI->getSampleSource(); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); +} + +SigMFFileInputGUI::~SigMFFileInputGUI() +{ + delete ui; +} + +void SigMFFileInputGUI::destroy() +{ + delete this; +} + +void SigMFFileInputGUI::setName(const QString& name) +{ + setObjectName(name); +} + +QString SigMFFileInputGUI::getName() const +{ + return objectName(); +} + +void SigMFFileInputGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +qint64 SigMFFileInputGUI::getCenterFrequency() const +{ + return m_centerFrequency; +} + +void SigMFFileInputGUI::setCenterFrequency(qint64 centerFrequency) +{ + m_centerFrequency = centerFrequency; + displaySettings(); + sendSettings(); +} + +QByteArray SigMFFileInputGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool SigMFFileInputGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +void SigMFFileInputGUI::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (DSPSignalNotification::match(*message)) + { + DSPSignalNotification* notif = (DSPSignalNotification*) message; + m_deviceSampleRate = notif->getSampleRate(); + m_deviceCenterFrequency = notif->getCenterFrequency(); + qDebug("SigMFFileInputGUI::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); + updateSampleRateAndFrequency(); + + delete message; + } + else + { + if (handleMessage(*message)) + { + delete message; + } + } + } +} + +bool SigMFFileInputGUI::handleMessage(const Message& message) +{ + if (SigMFFileInput::MsgConfigureSigMFFileInput::match(message)) + { + const SigMFFileInput::MsgConfigureSigMFFileInput& cfg = (SigMFFileInput::MsgConfigureSigMFFileInput&) message; + m_settings = cfg.getSettings(); + displaySettings(); + return true; + } + else if (SigMFFileInput::MsgReportStartStop::match(message)) + { + SigMFFileInput::MsgReportStartStop& report = (SigMFFileInput::MsgReportStartStop&) message; + m_startStop = report.getStartStop(); + updateStartStop(); + return true; + } + else if (SigMFFileInput::MsgReportMetaData::match(message)) + { + SigMFFileInput::MsgReportMetaData& report = (SigMFFileInput::MsgReportMetaData&) message; + m_metaInfo = report.getMetaInfo(); + + m_recordInfo = QString("Meta file..: %1\n").arg(m_metaFileName); + + if (m_metaInfo.m_sdrAngelVersion.size() == 0) + { + if (m_metaInfo.m_description.size() > 0) { + m_recordInfo += QString("Description: %1\n").arg(m_metaInfo.m_description); + } + if (m_metaInfo.m_author.size() > 0) { + m_recordInfo += QString("Author.....: %1\n").arg(m_metaInfo.m_author); + } + if (m_metaInfo.m_license.size() > 0) { + m_recordInfo += QString("License....: %1\n").arg(m_metaInfo.m_license); + } + if (m_metaInfo.m_sigMFVersion.size() > 0) { + m_recordInfo += QString("Version....: %1\n").arg(m_metaInfo.m_sigMFVersion); + } + + m_recordInfo += QString("Data type..: %1\n").arg(m_metaInfo.m_dataTypeStr); + m_recordInfo += QString("Nb samples.: %1\n").arg(m_metaInfo.m_totalSamples); + m_recordInfo += QString("Nb captures: %1\n").arg(m_metaInfo.m_nbCaptures); + m_recordInfo += QString("Nb annot...: %1\n").arg(m_metaInfo.m_nbAnnotations); + + ui->infoSummaryText->setText("Not recorded with SDRangel"); + } + else + { + m_recordInfo += QString("Recorder...: %1\n").arg(m_metaInfo.m_recorder); + m_recordInfo += QString("Hardware...: %1\n").arg(m_metaInfo.m_hw); + m_recordInfo += QString("Data type..: %1\n").arg(m_metaInfo.m_dataTypeStr); + m_recordInfo += QString("Core SRate.: %1 S/s\n").arg(m_metaInfo.m_coreSampleRate); + m_recordInfo += QString("Nb samples.: %1\n").arg(m_metaInfo.m_totalSamples); + m_recordInfo += QString("Nb captures: %1\n").arg(m_metaInfo.m_nbCaptures); + m_recordInfo += QString("SDRangel application info:\n"); + m_recordInfo += QString("Version....: v%1\n").arg(m_metaInfo.m_sdrAngelVersion); + m_recordInfo += QString("Qt version.: %1\n").arg(m_metaInfo.m_qtVersion); + m_recordInfo += QString("Rx bits....: %1 bits\n").arg(m_metaInfo.m_rxBits); + m_recordInfo += QString("Arch.......: %1\n").arg(m_metaInfo.m_arch); + m_recordInfo += QString("O/S........: %1\n").arg(m_metaInfo.m_os); + + ui->infoSummaryText->setText(QString("%1 Rx %2 bits v%3") + .arg(m_metaInfo.m_recorder) + .arg(m_metaInfo.m_rxBits) + .arg(m_metaInfo.m_sdrAngelVersion)); + } + + m_captures = report.getCaptures(); + addCaptures(m_captures); + m_centerFrequency = m_captures.size() > 0 ? m_captures.at(0).m_centerFrequency : 0; + m_recordLength = m_captures.size() > 0 ? m_captures.at(0).m_length : m_metaInfo.m_totalSamples; + m_startingTimeStamp = m_captures.size() > 0 ? m_captures.at(0).m_tsms : 0; + m_sampleRate = m_metaInfo.m_coreSampleRate; + m_sampleSize = m_metaInfo.m_dataType.m_sampleBits; + + QTime recordLength(0, 0, 0, 0); + recordLength = recordLength.addMSecs(m_metaInfo.m_totalTimeMs); + QString s_time = recordLength.toString("HH:mm:ss"); + ui->fullRecordLengthText->setText(s_time); + m_trackSamplesCount = 0; + m_trackTimeStart = 0; + + ui->sampleSizeText->setText(tr("%1%2%3b") + .arg(m_metaInfo.m_dataType.m_complex ? "c" : "r") + .arg(m_metaInfo.m_dataType.m_floatingPoint ? "f" : m_metaInfo.m_dataType.m_signed ? "i" : "u") + .arg(m_sampleSize)); + + updateWithStreamData(); + + return true; + } + else if (SigMFFileInput::MsgReportTrackChange::match(message)) + { + SigMFFileInput::MsgReportTrackChange& report = (SigMFFileInput::MsgReportTrackChange&) message; + m_currentTrackIndex = report.getTrackIndex(); + qDebug("SigMFFileInputGUI::handleMessage MsgReportTrackChange: m_currentTrackIndex: %d", m_currentTrackIndex); + m_centerFrequency = m_captures.at(m_currentTrackIndex).m_centerFrequency; + m_sampleRate = m_captures.at(m_currentTrackIndex).m_sampleRate; + m_recordLength = m_captures.at(m_currentTrackIndex).m_length; + m_startingTimeStamp = m_captures.at(m_currentTrackIndex).m_tsms; + m_samplesCount = m_captures.at(m_currentTrackIndex).m_sampleStart; + m_trackSamplesCount = 0; + updateWithStreamData(); + + return true; + } + else if (SigMFFileInput::MsgReportFileInputStreamTiming::match(message)) + { + SigMFFileInput::MsgReportFileInputStreamTiming& report = (SigMFFileInput::MsgReportFileInputStreamTiming&) message; + m_samplesCount = report.getSamplesCount(); + m_trackSamplesCount = report.getTrackSamplesCount(); + m_trackTimeStart = report.getTrackTimeStart(); + m_trackNumber = report.getTrackNumber(); + updateWithStreamTime(); + return true; + } + else if (SigMFFileInput::MsgStartStop::match(message)) + { + SigMFFileInput::MsgStartStop& notif = (SigMFFileInput::MsgStartStop&) message; + blockApplySettings(true); + ui->startStop->setChecked(notif.getStartStop()); + blockApplySettings(false); + + return true; + } + else if (SigMFFileInput::MsgReportCRC::match(message)) + { + SigMFFileInput::MsgReportCRC& notif = (SigMFFileInput::MsgReportCRC&) message; + + if (notif.isOK()) { + ui->crcLabel->setStyleSheet("QLabel { background-color : green; }"); + } else { + ui->crcLabel->setStyleSheet("QLabel { background-color : red; }"); + } + + return true; + } + else if (SigMFFileInput::MsgReportTotalSamplesCheck::match(message)) + { + SigMFFileInput::MsgReportTotalSamplesCheck& notif = (SigMFFileInput::MsgReportTotalSamplesCheck&) message; + + if (notif.isOK()) { + ui->totalLabel->setStyleSheet("QLabel { background-color : green; }"); + } else { + ui->totalLabel->setStyleSheet("QLabel { background-color : red; }"); + } + + return true; + } + else + { + return false; + } +} + +void SigMFFileInputGUI::updateSampleRateAndFrequency() +{ + m_deviceUISet->getSpectrum()->setSampleRate(m_deviceSampleRate); + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + ui->deviceRateText->setText(tr("%1k").arg((float)m_deviceSampleRate / 1000)); +} + +void SigMFFileInputGUI::displaySettings() +{ + blockApplySettings(true); + ui->playTrackLoop->setChecked(m_settings.m_trackLoop); + ui->playFullLoop->setChecked(m_settings.m_fullLoop); + ui->acceleration->setCurrentIndex(SigMFFileInputSettings::getAccelerationIndex(m_settings.m_accelerationFactor)); + blockApplySettings(false); +} + +QString SigMFFileInputGUI::displayScaled(uint64_t value, int precision) +{ + if (value < 1000) { + return tr("%1").arg(QString::number(value, 'f', precision)); + } else if (value < 1000000) { + return tr("%1k").arg(QString::number(value / 1000.0, 'f', precision)); + } else if (value < 1000000000) { + return tr("%1M").arg(QString::number(value / 1000000.0, 'f', precision)); + } else if (value < 1000000000000) { + return tr("%1G").arg(QString::number(value / 1000000000.0, 'f', precision)); + } +} + +void SigMFFileInputGUI::addCaptures(const QList& captures) +{ + ui->captureTable->setRowCount(captures.size()); + QList::const_iterator it = captures.begin(); + + for (int i = 0; i < captures.size(); i++) + { + QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(captures.at(i).m_tsms); + unsigned int sampleRate = captures.at(i).m_sampleRate; + ui->captureTable->setItem(i, 0, new QTableWidgetItem(dateTime.toString("yyyy-MM-ddTHH:mm:ss"))); + ui->captureTable->setItem(i, 1, new QTableWidgetItem(displayScaled(captures.at(i).m_centerFrequency, 5))); + ui->captureTable->setItem(i, 2, new QTableWidgetItem(displayScaled(sampleRate, 2))); + unsigned int milliseconds = (captures.at(i).m_length * 1000) / sampleRate; + QTime t = QTime::fromMSecsSinceStartOfDay(milliseconds); + ui->captureTable->setItem(i, 3, new QTableWidgetItem(t.toString("HH:mm:ss"))); + + for (int j = 0; j < 4; j++) + { + ui->captureTable->item(i, j)->setFlags(ui->captureTable->item(i, j)->flags() & ~Qt::ItemIsEditable); + ui->captureTable->item(i, j)->setTextAlignment(Qt::AlignRight); + } + } + + ui->captureTable->resizeRowsToContents(); + ui->captureTable->resizeColumnsToContents(); +} + +void SigMFFileInputGUI::sendSettings() +{ + if (m_doApplySettings) + { + SigMFFileInput::MsgConfigureSigMFFileInput *message = SigMFFileInput::MsgConfigureSigMFFileInput::create(m_settings, false); + m_sampleSource->getInputMessageQueue()->push(message); + } +} + +void SigMFFileInputGUI::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + SigMFFileInput::MsgStartStop *message = SigMFFileInput::MsgStartStop::create(checked); + m_sampleSource->getInputMessageQueue()->push(message); + } +} + +void SigMFFileInputGUI::on_infoDetails_clicked(bool checked) +{ + RecordInfoDialog infoDialog(m_recordInfo, this); + infoDialog.exec(); +} + +void SigMFFileInputGUI::on_captureTable_itemSelectionChanged() +{ + QList selectedItems = ui->captureTable->selectedItems(); + + if (selectedItems.size() == 0) + { + qDebug("SigMFFileInputGUI::on_captureTable_itemSelectionChanged: no selection"); + } + else + { + int trackIndex = selectedItems.front()->row(); + qDebug("SigMFFileInputGUI::on_captureTable_itemSelectionChanged: row: %d", trackIndex); + SigMFFileInput::MsgConfigureTrackIndex *message = SigMFFileInput::MsgConfigureTrackIndex::create(trackIndex); + m_sampleSource->getInputMessageQueue()->push(message); + + ui->trackNavTimeSlider->setValue(0); + float posRatio = (float) m_captures[trackIndex].m_sampleStart / (float) m_metaInfo.m_totalSamples; + ui->fullNavTimeSlider->setValue((int) (posRatio * 1000.0)); + } +} + +void SigMFFileInputGUI::on_trackNavTimeSlider_valueChanged(int value) +{ + if (m_enableTrackNavTime && ((value >= 0) && (value <= 1000))) + { + SigMFFileInput::MsgConfigureTrackSeek* message = SigMFFileInput::MsgConfigureTrackSeek::create(value); + m_sampleSource->getInputMessageQueue()->push(message); + } +} + +void SigMFFileInputGUI::on_fullNavTimeSlider_valueChanged(int value) +{ + if (m_enableFullNavTime && ((value >= 0) && (value <= 1000))) + { + SigMFFileInput::MsgConfigureFileSeek* message = SigMFFileInput::MsgConfigureFileSeek::create(value); + m_sampleSource->getInputMessageQueue()->push(message); + } +} + +void SigMFFileInputGUI::on_playTrackLoop_toggled(bool checked) +{ + m_settings.m_trackLoop = checked; + sendSettings(); +} + +void SigMFFileInputGUI::on_playTrack_toggled(bool checked) +{ + SigMFFileInput::MsgConfigureTrackWork* message = SigMFFileInput::MsgConfigureTrackWork::create(checked); + m_sampleSource->getInputMessageQueue()->push(message); + ui->trackNavTimeSlider->setEnabled(!checked); + ui->fullNavTimeSlider->setEnabled(!checked); + ui->acceleration->setEnabled(!checked); + m_enableTrackNavTime = !checked; + m_enableFullNavTime = !checked; + ui->playFull->setEnabled(!checked); + ui->playFull->setChecked(false); + ui->captureTable->setSelectionMode(checked ? QAbstractItemView::NoSelection : QAbstractItemView::ExtendedSelection); +} + +void SigMFFileInputGUI::on_playFullLoop_toggled(bool checked) +{ + m_settings.m_fullLoop = checked; + sendSettings(); +} + +void SigMFFileInputGUI::on_playFull_toggled(bool checked) +{ + SigMFFileInput::MsgConfigureFileWork* message = SigMFFileInput::MsgConfigureFileWork::create(checked); + m_sampleSource->getInputMessageQueue()->push(message); + ui->trackNavTimeSlider->setEnabled(!checked); + ui->fullNavTimeSlider->setEnabled(!checked); + ui->acceleration->setEnabled(!checked); + m_enableTrackNavTime = !checked; + m_enableFullNavTime = !checked; + ui->playTrack->setEnabled(!checked); + ui->playTrack->setChecked(false); + ui->captureTable->setSelectionMode(checked ? QAbstractItemView::NoSelection : QAbstractItemView::ExtendedSelection); +} + +void SigMFFileInputGUI::on_acceleration_currentIndexChanged(int index) +{ + m_settings.m_accelerationFactor = SigMFFileInputSettings::getAccelerationValue(index); + sendSettings(); +} + +void SigMFFileInputGUI::updateStatus() +{ + int state = m_deviceUISet->m_deviceAPI->state(); + + if(m_lastEngineState != state) + { + switch(state) + { + case DeviceAPI::StNotStarted: + ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case DeviceAPI::StIdle: + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + break; + case DeviceAPI::StRunning: + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + break; + case DeviceAPI::StError: + ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceAPI->errorMessage()); + break; + default: + break; + } + + m_lastEngineState = state; + } +} + +void SigMFFileInputGUI::on_showFileDialog_clicked(bool checked) +{ + (void) checked; + QString fileName = QFileDialog::getOpenFileName( + this, + tr("Open SigMF I/Q record file"), + ".", + tr("SigMF Files (*.sigmf-meta)"), + nullptr, + QFileDialog::DontUseNativeDialog + ); + + if (fileName != "") + { + m_metaFileName = fileName; + ui->fileNameText->setText(m_metaFileName); + ui->crcLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + configureFileName(); + } +} + +void SigMFFileInputGUI::configureFileName() +{ + qDebug() << "SigMFFileInputGUI::configureFileName: " << m_metaFileName.toStdString().c_str(); + + QString fileBase; + FileRecordInterface::RecordType recordType = FileRecordInterface::guessTypeFromFileName(m_metaFileName, fileBase); + + if (recordType == FileRecordInterface::RecordTypeSigMF) + { + m_settings.m_fileName = fileBase; + sendSettings(); + } +} + +void SigMFFileInputGUI::updateStartStop() +{ + qDebug("SigMFFileInputGUI::updateStartStop: %s", m_startStop ? "start" : "stop"); + // always start in file mode + ui->playFull->setEnabled(m_startStop); + ui->playFull->setChecked(m_startStop); + ui->playTrack->setEnabled(false); + ui->playTrack->setChecked(false); + ui->trackNavTimeSlider->setEnabled(false); + ui->fullNavTimeSlider->setEnabled(false); + ui->showFileDialog->setEnabled(!m_startStop); + ui->captureTable->setSelectionMode(QAbstractItemView::NoSelection); +} + +void SigMFFileInputGUI::updateWithStreamData() +{ + ui->captureTable->blockSignals(true); + ui->captureTable->setRangeSelected( + QTableWidgetSelectionRange(0, 0, ui->captureTable->rowCount() - 1, ui->captureTable->columnCount() - 1), false); + ui->captureTable->setRangeSelected( + QTableWidgetSelectionRange(m_currentTrackIndex, 0, m_currentTrackIndex, ui->captureTable->columnCount() - 1), true); + ui->captureTable->blockSignals(false); + + ui->trackNumberText->setText(tr("%1").arg(m_currentTrackIndex + 1, 3, 10, QChar('0'))); + ui->centerFrequency->setValue(m_centerFrequency/1000); + ui->sampleRateText->setText(tr("%1k").arg((float)m_sampleRate / 1000)); + QTime recordLength(0, 0, 0, 0); + recordLength = recordLength.addSecs(m_recordLength / m_sampleRate); + QString s_time = recordLength.toString("HH:mm:ss"); + ui->trackRecordLengthText->setText(s_time); + + updateWithStreamTime(); +} + +void SigMFFileInputGUI::updateWithStreamTime() +{ + qint64 track_sec = 0; + qint64 track_msec = 0; + + if (m_sampleRate > 0) + { + track_sec = m_trackSamplesCount / m_sampleRate; + track_msec = (m_trackSamplesCount - (track_sec * m_sampleRate)) * 1000LL / m_sampleRate; + } + + QTime t(0, 0, 0, 0); + t = t.addSecs(track_sec); + t = t.addMSecs(track_msec); + QString s_timems = t.toString("HH:mm:ss.zzz"); + ui->trackRelTimeText->setText(s_timems); + + t = t.addMSecs(m_trackTimeStart); + s_timems = t.toString("HH:mm:ss.zzz"); + ui->fullRelTimeText->setText(s_timems); + + QDateTime dt = QDateTime::fromMSecsSinceEpoch(m_startingTimeStamp); + dt = dt.addSecs(track_sec); + dt = dt.addMSecs(track_msec); + QString s_date = dt.toString("yyyy-MM-dd HH:mm:ss.zzz"); + ui->absTimeText->setText(s_date); + + if (!ui->trackNavTimeSlider->isEnabled()) + { + float posRatio = (float) m_trackSamplesCount / (float) m_recordLength; + ui->trackNavTimeSlider->setValue((int) (posRatio * 1000.0)); + } + + if (!ui->fullNavTimeSlider->isEnabled()) + { + float posRatio = (float) m_samplesCount / (float) m_metaInfo.m_totalSamples; + ui->fullNavTimeSlider->setValue((int) (posRatio * 1000.0)); + } +} + +void SigMFFileInputGUI::tick() +{ + if ((++m_tickCount & 0xf) == 0) { + SigMFFileInput::MsgConfigureFileInputStreamTiming* message = SigMFFileInput::MsgConfigureFileInputStreamTiming::create(); + m_sampleSource->getInputMessageQueue()->push(message); + } +} + +void SigMFFileInputGUI::setAccelerationCombo() +{ + ui->acceleration->blockSignals(true); + ui->acceleration->clear(); + ui->acceleration->addItem(QString("1")); + + for (unsigned int i = 0; i <= SigMFFileInputSettings::m_accelerationMaxScale; i++) + { + QString s; + int m = pow(10.0, i); + int x = 2*m; + setNumberStr(x, s); + ui->acceleration->addItem(s); + x = 5*m; + setNumberStr(x, s); + ui->acceleration->addItem(s); + x = 10*m; + setNumberStr(x, s); + ui->acceleration->addItem(s); + } + + ui->acceleration->blockSignals(false); +} + +void SigMFFileInputGUI::setNumberStr(int n, QString& s) +{ + if (n < 1000) { + s = tr("%1").arg(n); + } else if (n < 100000) { + s = tr("%1k").arg(n/1000); + } else if (n < 1000000) { + s = tr("%1e5").arg(n/100000); + } else if (n < 1000000000) { + s = tr("%1M").arg(n/1000000); + } else { + s = tr("%1G").arg(n/1000000000); + } +} + +void SigMFFileInputGUI::openDeviceSettingsDialog(const QPoint& p) +{ + BasicDeviceSettingsDialog dialog(this); + dialog.setUseReverseAPI(m_settings.m_useReverseAPI); + dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); + dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); + dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); + + dialog.move(p); + dialog.exec(); + + m_settings.m_useReverseAPI = dialog.useReverseAPI(); + m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); + m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); + m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); + + sendSettings(); +} diff --git a/plugins/samplesource/sigmffileinput/sigmffileinputgui.h b/plugins/samplesource/sigmffileinput/sigmffileinputgui.h new file mode 100644 index 000000000..7908e2970 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputgui.h @@ -0,0 +1,121 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SIGMFFILEINPUTGUI_H +#define INCLUDE_SIGMFFILEINPUTGUI_H + +#include +#include + +#include "plugin/plugininstancegui.h" +#include "util/messagequeue.h" + +#include "sigmffileinputsettings.h" +#include "sigmffiledata.h" +#include "sigmffileinput.h" + +class DeviceUISet; + +namespace Ui { + class SigMFFileInputGUI; +} + +class SigMFFileInputGUI : public QWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + explicit SigMFFileInputGUI(DeviceUISet *deviceUISet, QWidget* parent = nullptr); + virtual ~SigMFFileInputGUI(); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + + void resetToDefaults(); + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + Ui::SigMFFileInputGUI* ui; + + DeviceUISet* m_deviceUISet; + SigMFFileInputSettings m_settings; + int m_currentTrackIndex; + bool m_doApplySettings; + QTimer m_statusTimer; + std::vector m_gains; + DeviceSampleSource* m_sampleSource; + bool m_startStop; + bool m_trackMode; + QString m_metaFileName; + QString m_recordInfo; + int m_sampleRate; + quint32 m_sampleSize; + quint64 m_centerFrequency; + quint64 m_recordLength; + quint64 m_startingTimeStamp; + quint64 m_samplesCount; + quint64 m_trackSamplesCount; + quint64 m_trackTimeStart; + int m_trackNumber; + std::size_t m_tickCount; + bool m_enableTrackNavTime; + bool m_enableFullNavTime; + int m_deviceSampleRate; + quint64 m_deviceCenterFrequency; //!< Center frequency in device + int m_lastEngineState; + MessageQueue m_inputMessageQueue; + SigMFFileMetaInfo m_metaInfo; + QList m_captures; + + void blockApplySettings(bool block) { m_doApplySettings = !block; } + void displaySettings(); + void displayTime(); + QString displayScaled(uint64_t value, int precision); + void addCaptures(const QList& captures); + void sendSettings(); + void updateSampleRateAndFrequency(); + void configureFileName(); + void updateStartStop(); + void updateWithStreamData(); + void updateWithStreamTime(); + void setAccelerationCombo(); + void setNumberStr(int n, QString& s); + +private slots: + void handleInputMessages(); + void on_startStop_toggled(bool checked); + void on_infoDetails_clicked(bool checked); + void on_captureTable_itemSelectionChanged(); + void on_trackNavTimeSlider_valueChanged(int value); + void on_playTrack_toggled(bool checked); + void on_playTrackLoop_toggled(bool checked); + void on_fullNavTimeSlider_valueChanged(int value); + void on_playFull_toggled(bool checked); + void on_playFullLoop_toggled(bool checked); + void on_showFileDialog_clicked(bool checked); + void on_acceleration_currentIndexChanged(int index); + void updateStatus(); + void tick(); + void openDeviceSettingsDialog(const QPoint& p); +}; + +#endif // INCLUDE_SIGMFFILEINPUTGUI_H diff --git a/plugins/samplesource/sigmffileinput/sigmffileinputgui.ui b/plugins/samplesource/sigmffileinput/sigmffileinputgui.ui new file mode 100644 index 000000000..6bf04b69e --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputgui.ui @@ -0,0 +1,851 @@ + + + SigMFFileInputGUI + + + + 0 + 0 + 303 + 447 + + + + + 0 + 0 + + + + + 250 + 360 + + + + + Liberation Sans + 9 + 50 + false + false + + + + SigMF File Input + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 4 + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + + + + + + 50 + 0 + + + + I/Q sample rate kS/s + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + false + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 20 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Record center frequency in kHz + + + + + + + kHz + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + Qt::Horizontal + + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + Open file + + + + + + + :/preset-load.png:/preset-load.png + + + + + + + false + + + File currently opened + + + ... + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + + + + 0 + 0 + + + + + 24 + 16777215 + + + + Record detailed information + + + + + + + :/info.png:/info.png + + + + 16 + 16 + + + + + + + + + 0 + 0 + + + + Record summary information + + + ... + + + + + + + + + + + + 40 + 0 + + + + + 8 + + + + Record sample rate (kS/s) + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + + 22 + 0 + + + + + 8 + + + + Record sample size (bits) + + + 00b + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + + 8 + + + + CRC status: Green: OK Red: KO Grey: undefined + + + CRC + + + + + + + + 8 + + + + Total samples check: Green: OK Red: KO Grey: undefined + + + TOT + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + + + + false + + + + 160 + 0 + + + + Record absolute time + + + 2015-01-01 00:00:00.000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + QAbstractItemView::SelectRows + + + + Datetime + + + + + F(Hz) + + + + + SR(Hz) + + + + + Time + + + + + + + + + + Track time navigator + + + 1000 + + + 1 + + + Qt::Horizontal + + + + + + + + + + + Play track in a loop + + + + + + + :/playloop.png:/playloop.png + + + + 16 + 16 + + + + true + + + + + + + Track Play / Pause + + + + + + + :/play.png + :/pause.png + :/stop.png + :/stop.png + :/play.png + :/pause.png + :/play.png + :/pause.png:/play.png + + + + 16 + 16 + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Track number + + + 000 + + + + + + + Qt::Vertical + + + + + + + false + + + + 90 + 0 + + + + Track record time from start + + + 00:00:00.000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + false + + + + 60 + 0 + + + + Track total record time + + + 00:00:00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Full time navigator + + + 1000 + + + 1 + + + Qt::Horizontal + + + + + + + + + + + Play record in a loop + + + + + + + :/playloop.png:/playloop.png + + + + + + + Record Play / Pause + + + + + + + :/play.png + :/pause.png + :/stop.png + :/stop.png + :/play.png + :/pause.png + :/play.png + :/pause.png:/play.png + + + true + + + + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + Acceleration factor + + + + 1 + + + + + 2 + + + + + 5 + + + + + 10 + + + + + 20 + + + + + 50 + + + + + 100 + + + + + 200 + + + + + 500 + + + + + 1k + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + 90 + 0 + + + + Full record time from start + + + 00:00:00.000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + false + + + + 60 + 0 + + + + Full record time + + + 00:00:00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+
+ + + + +
diff --git a/plugins/samplesource/sigmffileinput/sigmffileinputplugin.cpp b/plugins/samplesource/sigmffileinput/sigmffileinputplugin.cpp new file mode 100644 index 000000000..1ff839c19 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputplugin.cpp @@ -0,0 +1,148 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" + +#ifdef SERVER_MODE +#include "sigmffileinput.h" +#else +#include "sigmffileinputgui.h" +#endif +#include "sigmffileinputplugin.h" +#include "sigmffileinputwebapiadapter.h" + +const PluginDescriptor SigMFFileInputPlugin::m_pluginDescriptor = { + QString("SigMFFileInput"), + QString("File device input (SigMF)"), + QString("5.7.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString SigMFFileInputPlugin::m_hardwareID = "SigMFFileInput"; +const QString SigMFFileInputPlugin::m_deviceTypeID = FILEINPUT_DEVICE_TYPE_ID; + +SigMFFileInputPlugin::SigMFFileInputPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& SigMFFileInputPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void SigMFFileInputPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleSource(m_deviceTypeID, this); +} + +void SigMFFileInputPlugin::enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices) +{ + if (listedHwIds.contains(m_hardwareID)) { // check if it was done + return; + } + + originDevices.append(OriginDevice( + "SigMFFileInput", + m_hardwareID, + QString(), + 0, + 1, // nb Rx + 0 // nb Tx + )); + + listedHwIds.append(m_hardwareID); +} + +PluginInterface::SamplingDevices SigMFFileInputPlugin::enumSampleSources(const OriginDevices& originDevices) +{ + SamplingDevices result; + + for (OriginDevices::const_iterator it = originDevices.begin(); it != originDevices.end(); ++it) + { + if (it->hardwareId == m_hardwareID) + { + result.append(SamplingDevice( + it->displayableName, + m_hardwareID, + m_deviceTypeID, + it->serial, + it->sequence, + PluginInterface::SamplingDevice::BuiltInDevice, + PluginInterface::SamplingDevice::StreamSingleRx, + 1, + 0 + )); + } + } + + return result; +} + +#ifdef SERVER_MODE +PluginInstanceGUI* SigMFFileInputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + (void) sourceId; + (void) widget; + (void) deviceUISet; + return 0; +} +#else +PluginInstanceGUI* SigMFFileInputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if(sourceId == m_deviceTypeID) + { + SigMFFileInputGUI* gui = new SigMFFileInputGUI(deviceUISet); + *widget = gui; + return gui; + } + else + { + return 0; + } +} +#endif + +DeviceSampleSource *SigMFFileInputPlugin::createSampleSourcePluginInstance(const QString& sourceId, DeviceAPI *deviceAPI) +{ + if (sourceId == m_deviceTypeID) + { + SigMFFileInput* input = new SigMFFileInput(deviceAPI); + return input; + } + else + { + return 0; + } +} + +DeviceWebAPIAdapter *SigMFFileInputPlugin::createDeviceWebAPIAdapter() const +{ + return new SigMFFileInputWebAPIAdapter(); +} diff --git a/plugins/samplesource/sigmffileinput/sigmffileinputplugin.h b/plugins/samplesource/sigmffileinput/sigmffileinputplugin.h new file mode 100644 index 000000000..14ce45826 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputplugin.h @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SIGMFFILEINPUTPLUGIN_H +#define INCLUDE_SIGMFFILEINPUTPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +#define FILEINPUT_DEVICE_TYPE_ID "sdrangel.samplesource.sigmffileinput" + +class PluginAPI; + +class SigMFFileInputPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID FILEINPUT_DEVICE_TYPE_ID) + +public: + explicit SigMFFileInputPlugin(QObject* parent = nullptr); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual void enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices); + virtual SamplingDevices enumSampleSources(const OriginDevices& originDevices); + virtual PluginInstanceGUI* createSampleSourcePluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet); + virtual DeviceSampleSource* createSampleSourcePluginInstance(const QString& sourceId, DeviceAPI *deviceAPI); + virtual DeviceWebAPIAdapter* createDeviceWebAPIAdapter() const; + + static const QString m_hardwareID; + static const QString m_deviceTypeID; + +private: + static const PluginDescriptor m_pluginDescriptor; +}; + +#endif // INCLUDE_FILESOURCEPLUGIN_H diff --git a/plugins/samplesource/sigmffileinput/sigmffileinputsettings.cpp b/plugins/samplesource/sigmffileinput/sigmffileinputsettings.cpp new file mode 100644 index 000000000..dbc21d95b --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputsettings.cpp @@ -0,0 +1,159 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "util/simpleserializer.h" + +#include "sigmffileinputsettings.h" + +const unsigned int SigMFFileInputSettings::m_accelerationMaxScale = 2; + +SigMFFileInputSettings::SigMFFileInputSettings() +{ + resetToDefaults(); +} + +void SigMFFileInputSettings::resetToDefaults() +{ + m_fileName = "./test.sdriq"; + m_accelerationFactor = 1; + m_trackLoop = false; + m_fullLoop = true; + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIDeviceIndex = 0; +} + +QByteArray SigMFFileInputSettings::serialize() const +{ + SimpleSerializer s(1); + s.writeString(1, m_fileName); + s.writeU32(2, m_accelerationFactor); + s.writeBool(3, m_trackLoop); + s.writeBool(3, m_fullLoop); + s.writeBool(5, m_useReverseAPI); + s.writeString(6, m_reverseAPIAddress); + s.writeU32(7, m_reverseAPIPort); + s.writeU32(8, m_reverseAPIDeviceIndex); + + return s.final(); +} + +bool SigMFFileInputSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + uint32_t uintval; + + d.readString(1, &m_fileName, "./test.sdriq"); + d.readU32(2, &m_accelerationFactor, 1); + d.readBool(3, &m_trackLoop, false); + d.readBool(4, &m_fullLoop, true); + d.readBool(5, &m_useReverseAPI, false); + d.readString(6, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(7, &uintval, 0); + + if ((uintval > 1023) && (uintval < 65535)) { + m_reverseAPIPort = uintval; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(8, &uintval, 0); + m_reverseAPIDeviceIndex = uintval > 99 ? 99 : uintval; + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +int SigMFFileInputSettings::getAccelerationIndex(int accelerationValue) +{ + if (accelerationValue <= 1) { + return 0; + } + + int v = accelerationValue; + int j = 0; + + for (int i = 0; i <= accelerationValue; i++) + { + if (v < 20) + { + if (v < 2) { + j = 0; + } else if (v < 5) { + j = 1; + } else if (v < 10) { + j = 2; + } else { + j = 3; + } + + return 3*i + j; + } + + v /= 10; + } + + return 3*m_accelerationMaxScale + 3; +} + +int SigMFFileInputSettings::getAccelerationValue(int accelerationIndex) +{ + if (accelerationIndex <= 0) { + return 1; + } + + unsigned int v = accelerationIndex - 1; + int m = pow(10.0, v/3 > m_accelerationMaxScale ? m_accelerationMaxScale : v/3); + int x = 1; + + if (v % 3 == 0) { + x = 2; + } else if (v % 3 == 1) { + x = 5; + } else if (v % 3 == 2) { + x = 10; + } + + return x * m; +} + +int SigMFFileInputSettings::bitsToBytes(int bits) +{ + if (bits <= 8) { + return 1; + } else if (bits <= 16) { + return 2; + } else if (bits <= 32) { + return 4; + } else { + return 8; + } +} \ No newline at end of file diff --git a/plugins/samplesource/sigmffileinput/sigmffileinputsettings.h b/plugins/samplesource/sigmffileinput/sigmffileinputsettings.h new file mode 100644 index 000000000..9e365494f --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputsettings.h @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_SIGMFFILEINPUT_SIGMFFILEINPUTSETTINGS_H_ +#define PLUGINS_SAMPLESOURCE_SIGMFFILEINPUT_SIGMFFILEINPUTSETTINGS_H_ + +#include +#include + +struct SigMFFileInputSettings { + QString m_fileName; + quint32 m_accelerationFactor; + bool m_trackLoop; + bool m_fullLoop; + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIDeviceIndex; + + static const unsigned int m_accelerationMaxScale; //!< Max power of 10 multiplier to 2,5,10 base ex: 2 -> 2,5,10,20,50,100,200,500,1000 + + SigMFFileInputSettings(); + ~SigMFFileInputSettings() {} + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + static int getAccelerationIndex(int averaging); + static int getAccelerationValue(int averagingIndex); + static int bitsToBytes(int bits); +}; + +#endif /* PLUGINS_SAMPLESOURCE_SIGMFFILEINPUT_SIGMFFILEINPUTSETTINGS_H_ */ diff --git a/plugins/samplesource/sigmffileinput/sigmffileinputthread.cpp b/plugins/samplesource/sigmffileinput/sigmffileinputthread.cpp new file mode 100644 index 000000000..6ef68a262 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputthread.cpp @@ -0,0 +1,762 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "dsp/filerecord.h" +#include "dsp/samplesinkfifo.h" +#include "util/messagequeue.h" + +#include "sigmffiledata.h" +#include "sigmffileconvert.h" +#include "sigmffileinputsettings.h" +#include "sigmffileinputthread.h" + +MESSAGE_CLASS_DEFINITION(SigMFFileInputThread::MsgReportEOF, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInputThread::MsgReportTrackChange, Message) + +SigMFFileInputThread::SigMFFileInputThread(std::ifstream *samplesStream, + SampleSinkFifo* sampleFifo, + const QTimer& timer, + MessageQueue *fileInputMessageQueue, + QObject* parent) : + QThread(parent), + m_running(false), + m_currentTrackIndex(0), + m_ifstream(samplesStream), + m_fileBuf(0), + m_convertBuf(0), + m_bufsize(0), + m_chunksize(0), + m_sampleFifo(sampleFifo), + m_samplesCount(0), + m_timer(timer), + m_fileInputMessageQueue(fileInputMessageQueue), + m_samplerate(48000), + m_accelerationFactor(1), + m_samplesize(16), + m_samplebytes(2), + m_throttlems(FILESOURCE_THROTTLE_MS), + m_throttleToggle(false), + m_sigMFConverter(nullptr) +{ + assert(m_ifstream != 0); +} + +SigMFFileInputThread::~SigMFFileInputThread() +{ + if (m_running) { + stopWork(); + } + + if (m_fileBuf != 0) { + free(m_fileBuf); + } + + if (m_convertBuf != 0) { + free(m_convertBuf); + } +} + +void SigMFFileInputThread::startWork() +{ + qDebug() << "SigMFFileInputThread::startWork: "; + + if (m_ifstream->is_open()) + { + qDebug() << "SigMFFileInputThread::startWork: file stream open, starting..."; + m_startWaitMutex.lock(); + m_elapsedTimer.start(); + start(); + + while(!m_running) { + m_startWaiter.wait(&m_startWaitMutex, 100); + } + + m_startWaitMutex.unlock(); + connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); + } + else + { + qDebug() << "SigMFFileInputThread::startWork: file stream closed, not starting."; + } +} + +void SigMFFileInputThread::stopWork() +{ + qDebug() << "SigMFFileInputThread::stopWork"; + disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); + m_running = false; + wait(); +} + +void SigMFFileInputThread::setMetaInformation(const SigMFFileMetaInfo *metaInfo, const QList *captures) +{ + m_metaInfo = metaInfo; + m_captures = captures; + m_samplerate = m_metaInfo->m_coreSampleRate; + m_samplesize = m_metaInfo->m_dataType.m_sampleBits; + setConverter(); + setSampleRate(); +} + +void SigMFFileInputThread::setTrackIndex(int trackIndex) +{ + m_currentTrackIndex = trackIndex; + m_samplesCount = m_captures->at(m_currentTrackIndex).m_sampleStart; + unsigned int sampleRate = m_captures->at(m_currentTrackIndex).m_sampleRate; + + if (sampleRate != m_samplerate) + { + m_samplerate = sampleRate; + setSampleRate(); + } + + MsgReportTrackChange *message = MsgReportTrackChange::create(m_currentTrackIndex); + m_fileInputMessageQueue->push(message); +} + +void SigMFFileInputThread::setAccelerationFactor(int accelerationFactor) +{ + m_accelerationFactor = accelerationFactor; + setSampleRate(); +} + +void SigMFFileInputThread::setSampleRate() +{ + bool running = m_running; + + if (running) { + stopWork(); + } + + m_samplebytes = SigMFFileInputSettings::bitsToBytes(m_samplesize); + m_chunksize = (m_accelerationFactor * m_samplerate * 2 * m_samplebytes * m_throttlems) / 1000; + + setBuffers(m_chunksize); + + if (running) { + startWork(); + } +} + +void SigMFFileInputThread::setBuffers(std::size_t chunksize) +{ + if (chunksize > m_bufsize) + { + m_bufsize = chunksize; + int nbSamples = m_bufsize/(2 * m_samplebytes); + + if (m_fileBuf == 0) + { + qDebug() << "FileInputThread::setBuffers: Allocate file buffer"; + m_fileBuf = (quint8*) malloc(m_bufsize); + } + else + { + qDebug() << "FileInputThread::setBuffers: Re-allocate file buffer"; + quint8 *buf = m_fileBuf; + m_fileBuf = (quint8*) realloc((void*) m_fileBuf, m_bufsize); + if (!m_fileBuf) free(buf); + } + + if (m_convertBuf == 0) + { + qDebug() << "FileInputThread::setBuffers: Allocate conversion buffer"; + m_convertBuf = (quint8*) malloc(nbSamples*sizeof(Sample)); + } + else + { + qDebug() << "FileInputThread::setBuffers: Re-allocate conversion buffer"; + quint8 *buf = m_convertBuf; + m_convertBuf = (quint8*) realloc((void*) m_convertBuf, nbSamples*sizeof(Sample)); + if (!m_convertBuf) free(buf); + } + + qDebug() << "FileInputThread::setBuffers: size: " << m_bufsize + << " #samples: " << nbSamples; + } +} + +void SigMFFileInputThread::run() +{ + m_running = true; + m_startWaiter.wakeAll(); + + while(m_running) // actual work is in the tick() function + { + sleep(1); + } + + m_running = false; +} + +void SigMFFileInputThread::tick() +{ + if (m_running) + { + qint64 throttlems = m_elapsedTimer.restart(); + + if (throttlems != m_throttlems) + { + m_throttlems = throttlems; + m_chunksize = 2 * m_samplebytes * ((m_samplerate * (m_throttlems+(m_throttleToggle ? 1 : 0))) / 1000); + m_throttleToggle = !m_throttleToggle; + setBuffers(m_chunksize); + } + + // read samples directly feeding the SampleFifo (no callback) + + if (m_samplesCount + m_chunksize > m_totalSamples) { + m_ifstream->read(reinterpret_cast(m_fileBuf), m_totalSamples - m_samplesCount); + } else { + m_ifstream->read(reinterpret_cast(m_fileBuf), m_chunksize); + } + + if ((m_samplesCount + m_chunksize > m_totalSamples) || m_ifstream->eof()) + { + writeToSampleFifo(m_fileBuf, (qint32) m_ifstream->gcount()); // take what has been read + MsgReportEOF *message = MsgReportEOF::create(); + m_fileInputMessageQueue->push(message); + } + else + { + writeToSampleFifo(m_fileBuf, (qint32) m_chunksize); + m_samplesCount += m_chunksize / (2 * m_samplebytes); + + if ((m_currentTrackIndex + 1 < m_captures->size()) + && (m_samplesCount > m_captures->at(m_currentTrackIndex+1).m_sampleStart)) + { + m_currentTrackIndex++; + unsigned int sampleRate = m_captures->at(m_currentTrackIndex).m_sampleRate; + + if (sampleRate != m_samplerate) + { + m_samplerate = sampleRate; + setSampleRate(); + } + + MsgReportTrackChange *message = MsgReportTrackChange::create(m_currentTrackIndex); + m_fileInputMessageQueue->push(message); + } + } + } +} + +void SigMFFileInputThread::setConverter() +{ + if (m_metaInfo->m_dataType.m_floatingPoint) // float + { + if (m_metaInfo->m_dataType.m_complex) + { + if (m_metaInfo->m_dataType.m_bigEndian) + { + if (m_metaInfo->m_dataType.m_swapIQ) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + else + { + if (m_metaInfo->m_dataType.m_swapIQ) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + } + else + { + if (m_metaInfo->m_dataType.m_bigEndian) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + } + else if ((m_metaInfo->m_dataType.m_signed) && (m_samplesize == 8)) // i8 + { + if (m_metaInfo->m_dataType.m_complex) + { + if (m_metaInfo->m_dataType.m_swapIQ) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + else + { + m_sigMFConverter = new SigMFConverter(); + } + } + else if ((!m_metaInfo->m_dataType.m_signed) && (m_samplesize == 8)) // u8 + { + if (m_metaInfo->m_dataType.m_complex) + { + if (m_metaInfo->m_dataType.m_swapIQ) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + else + { + m_sigMFConverter = new SigMFConverter(); + } + } + else if ((m_metaInfo->m_dataType.m_signed) && (m_samplesize == 16)) // i16 + { + if (m_metaInfo->m_dataType.m_complex) + { + if (m_metaInfo->m_dataType.m_bigEndian) + { + if (m_metaInfo->m_dataType.m_swapIQ) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + else + { + if (m_metaInfo->m_dataType.m_swapIQ) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + } + else + { + if (m_metaInfo->m_dataType.m_bigEndian) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + } + else if ((!m_metaInfo->m_dataType.m_signed) && (m_samplesize == 16)) // u16 + { + if (m_metaInfo->m_dataType.m_complex) + { + if (m_metaInfo->m_dataType.m_bigEndian) + { + if (m_metaInfo->m_dataType.m_swapIQ) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + else + { + if (m_metaInfo->m_dataType.m_swapIQ) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + } + else + { + if (m_metaInfo->m_dataType.m_bigEndian) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + } + else if ((m_metaInfo->m_dataType.m_signed) && (m_samplesize == 24)) // i24 (SDRangel special) + { + m_sigMFConverter = new SigMFConverter(); + } + else if ((m_metaInfo->m_dataType.m_signed) && (m_samplesize == 32)) // i32 + { + if (m_metaInfo->m_dataType.m_complex) + { + if (m_metaInfo->m_dataType.m_bigEndian) + { + if (m_metaInfo->m_dataType.m_swapIQ) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + else + { + if (m_metaInfo->m_dataType.m_swapIQ) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + } + else + { + if (m_metaInfo->m_dataType.m_bigEndian) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + } + else if ((!m_metaInfo->m_dataType.m_signed) && (m_samplesize == 32)) // u32 + { + if (m_metaInfo->m_dataType.m_complex) + { + if (m_metaInfo->m_dataType.m_bigEndian) + { + if (m_metaInfo->m_dataType.m_swapIQ) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + else + { + if (m_metaInfo->m_dataType.m_swapIQ) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + } + else + { + if (m_metaInfo->m_dataType.m_bigEndian) { + m_sigMFConverter = new SigMFConverter(); + } else { + m_sigMFConverter = new SigMFConverter(); + } + } + } +} + +void SigMFFileInputThread::writeToSampleFifo(const quint8* buf, qint32 nbBytes) +{ + if (!m_sigMFConverter) + { + qDebug("SigMFFileInputThread::writeToSampleFifo: no converter - probably sample format is not supported"); + return; + } + +#if defined(__WINDOWS__) || (BYTE_ORDER == LITTLE_ENDIAN) + if ((m_metaInfo->m_dataType.m_complex) && (!m_metaInfo->m_dataType.m_bigEndian) && (!m_metaInfo->m_dataType.m_swapIQ)) + { + if ((m_samplesize == 16) && (SDR_RX_SAMP_SZ == 16)) + { + m_sampleFifo->write(buf, nbBytes); + return; + } + if ((m_samplesize == 24) && (SDR_RX_SAMP_SZ == 24)) + { + m_sampleFifo->write(buf, nbBytes); + return; + } + } +#endif + int nbSamples = m_sigMFConverter->convert((FixReal *) m_convertBuf, buf, nbBytes); + m_sampleFifo->write(m_convertBuf, nbSamples*sizeof(Sample)); +} + +void SigMFFileInputThread::writeToSampleFifoBAK(const quint8* buf, qint32 nbBytes) +{ + if (m_metaInfo->m_dataType.m_floatingPoint) // FP assumes 32 bit floats (float) always + { + FixReal *convertBuf = (FixReal *) m_convertBuf; + const float *fileBuf = (float *) buf; + int nbSamples; + + if (m_metaInfo->m_dataType.m_complex) + { + nbSamples = nbBytes / (2 * m_samplebytes); + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[2*is] * SDR_RX_SCALEF; + convertBuf[2*is+1] = fileBuf[2*is+1] * SDR_RX_SCALEF; + } + } + else + { + nbSamples = nbBytes / m_samplebytes; + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[is] * SDR_RX_SCALEF; + convertBuf[2*is+1] = 0; + } + } + + m_sampleFifo->write((quint8*) convertBuf, nbSamples*sizeof(Sample)); + } + else if (m_metaInfo->m_dataType.m_signed) // signed integers + { + if (m_samplesize == 8) + { + FixReal *convertBuf = (FixReal *) m_convertBuf; + const int8_t *fileBuf = (int8_t *) buf; + int nbSamples; + + if (m_metaInfo->m_dataType.m_complex) + { + nbSamples = nbBytes / (2 * m_samplebytes); + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[2*is]; + convertBuf[2*is] <<= (SDR_RX_SAMP_SZ == 16) ? 8 : 16; + convertBuf[2*is+1] = fileBuf[2*is+1]; + convertBuf[2*is+1] <<= (SDR_RX_SAMP_SZ == 16) ? 8 : 16; + } + } + else + { + nbSamples = nbBytes / m_samplebytes; + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[is]; + convertBuf[2*is] <<= (SDR_RX_SAMP_SZ == 16) ? 8 : 16; + convertBuf[2*is+1] = 0; + } + } + + m_sampleFifo->write((quint8*) convertBuf, nbSamples*sizeof(Sample)); + } + else if (m_samplesize == 16) + { + if (SDR_RX_SAMP_SZ == 16) + { + if (m_metaInfo->m_dataType.m_complex) + { + m_sampleFifo->write(buf, nbBytes); + } + else + { + FixReal *convertBuf = (FixReal *) m_convertBuf; + const int16_t *fileBuf = (int16_t *) buf; + int nbSamples = nbBytes / m_samplebytes; + + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[2*is]; + convertBuf[2*is+1] = 0; + } + + m_sampleFifo->write((quint8*) convertBuf, nbSamples*sizeof(Sample)); + } + } + else if (SDR_RX_SAMP_SZ == 24) + { + FixReal *convertBuf = (FixReal *) m_convertBuf; + const int16_t *fileBuf = (int16_t *) buf; + int nbSamples; + + if (m_metaInfo->m_dataType.m_complex) + { + nbSamples = nbBytes / (2 * m_samplebytes); + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[2*is] << 8; + convertBuf[2*is+1] = fileBuf[2*is+1] << 8; + } + } + else + { + nbSamples = nbBytes / m_samplebytes; + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[is] << 8; + convertBuf[2*is+1] = 0; + } + } + + m_sampleFifo->write((quint8*) convertBuf, nbSamples*sizeof(Sample)); + } + } + else if (m_samplesize == 24) + { + if (SDR_RX_SAMP_SZ == 24) + { + if (m_metaInfo->m_dataType.m_complex) + { + m_sampleFifo->write(buf, nbBytes); + } + else + { + FixReal *convertBuf = (FixReal *) m_convertBuf; + const int32_t *fileBuf = (int32_t *) buf; + int nbSamples = nbBytes / m_samplebytes; + + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[2*is]; + convertBuf[2*is+1] = 0; + } + + m_sampleFifo->write((quint8*) convertBuf, nbSamples*sizeof(Sample)); + } + } + else if (SDR_RX_SAMP_SZ == 16) + { + FixReal *convertBuf = (FixReal *) m_convertBuf; + const int32_t *fileBuf = (int32_t *) buf; + int nbSamples; + + if (m_metaInfo->m_dataType.m_complex) + { + nbSamples = nbBytes / (2 * m_samplebytes); + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[2*is] >> 8; + convertBuf[2*is+1] = fileBuf[2*is+1] >> 8; + } + } + else + { + nbSamples = nbBytes / m_samplebytes; + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[is] >> 8; + convertBuf[2*is+1] = 0; + } + } + + m_sampleFifo->write((quint8*) convertBuf, nbSamples*sizeof(Sample)); + } + } + if (m_samplesize == 32) + { + FixReal *convertBuf = (FixReal *) m_convertBuf; + const int32_t *fileBuf = (int32_t *) buf; + int nbSamples; + + if (m_metaInfo->m_dataType.m_complex) + { + nbSamples = nbBytes / (2 * m_samplebytes); + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[2*is] >> (SDR_RX_SAMP_SZ == 24) ? 8 : 16; + convertBuf[2*is+1] = fileBuf[2*is+1] >> (SDR_RX_SAMP_SZ == 24) ? 8 : 16; + } + } + else + { + nbSamples = nbBytes / m_samplebytes; + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[is] >> (SDR_RX_SAMP_SZ == 24) ? 8 : 16; + convertBuf[2*is+1] = 0; + } + } + + m_sampleFifo->write((quint8*) convertBuf, nbSamples*sizeof(Sample)); + } + } + else // unsigned integers + { + if (m_samplesize == 8) + { + FixReal *convertBuf = (FixReal *) m_convertBuf; + const uint8_t *fileBuf = (uint8_t *) buf; + int nbSamples; + + if (m_metaInfo->m_dataType.m_complex) + { + nbSamples = nbBytes / (2 * m_samplebytes); + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[2*is] - 128; + convertBuf[2*is] <<= (SDR_RX_SAMP_SZ == 16) ? 8 : 16; + convertBuf[2*is+1] = fileBuf[2*is+1] - 128; + convertBuf[2*is+1] <<= (SDR_RX_SAMP_SZ == 16) ? 8 : 16; + } + } + else + { + nbSamples = nbBytes / m_samplebytes; + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[is] - 128; + convertBuf[2*is] <<= (SDR_RX_SAMP_SZ == 16) ? 8 : 16; + convertBuf[2*is+1] = 0; + } + } + + m_sampleFifo->write((quint8*) convertBuf, nbSamples*sizeof(Sample)); + } + else if (m_samplesize == 16) + { + FixReal *convertBuf = (FixReal *) m_convertBuf; + const uint16_t *fileBuf = (uint16_t *) buf; + int nbSamples; + + if (m_metaInfo->m_dataType.m_complex) + { + nbSamples = nbBytes / (2 * m_samplebytes); + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[2*is] - 32768; + convertBuf[2*is] <<= (SDR_RX_SAMP_SZ == 16) ? 0 : 8; + convertBuf[2*is+1] = fileBuf[2*is+1] - 32768; + convertBuf[2*is+1] <<= (SDR_RX_SAMP_SZ == 16) ? 0 : 8; + } + } + else + { + nbSamples = nbBytes / m_samplebytes; + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = fileBuf[is] - 32768; + convertBuf[2*is] <<= (SDR_RX_SAMP_SZ == 16) ? 0 : 8; + convertBuf[2*is+1] = 0; + } + } + + m_sampleFifo->write((quint8*) convertBuf, nbSamples*sizeof(Sample)); + } + else if (m_samplesize == 32) + { + FixReal *convertBuf = (FixReal *) m_convertBuf; + const uint32_t *fileBuf = (uint32_t *) buf; + int nbSamples; + + if (m_metaInfo->m_dataType.m_complex) + { + nbSamples = nbBytes / (2 * m_samplebytes); + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = (fileBuf[2*is] >> (SDR_RX_SAMP_SZ == 24) ? 8 : 16) + - ((SDR_RX_SAMP_SZ == 24) ? (1<<23) : (1<<15)); + convertBuf[2*is+1] = (fileBuf[2*is+1] >> (SDR_RX_SAMP_SZ == 24) ? 8 : 16) + - ((SDR_RX_SAMP_SZ == 24) ? (1<<23) : (1<<15));; + } + } + else + { + nbSamples = nbBytes / m_samplebytes; + for (int is = 0; is < nbSamples; is++) + { + convertBuf[2*is] = (fileBuf[is] >> (SDR_RX_SAMP_SZ == 24) ? 8 : 16) + - ((SDR_RX_SAMP_SZ == 24) ? (1<<23) : (1<<15)); + convertBuf[2*is+1] = 0; + } + } + + m_sampleFifo->write((quint8*) convertBuf, nbSamples*sizeof(Sample)); + } + } +} diff --git a/plugins/samplesource/sigmffileinput/sigmffileinputthread.h b/plugins/samplesource/sigmffileinput/sigmffileinputthread.h new file mode 100644 index 000000000..15d2d0af2 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputthread.h @@ -0,0 +1,134 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SIGMFFILEINPUTTHREAD_H +#define INCLUDE_SIGMFFILEINPUTTHREAD_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dsp/inthalfbandfilter.h" +#include "util/message.h" + +#define FILESOURCE_THROTTLE_MS 50 + +class SampleSinkFifo; +class MessageQueue; +class SigMFFileCapture; +class SigMFFileMetaInfo; +class SigMFConverterInterface; + +class SigMFFileInputThread : public QThread { + Q_OBJECT + +public: + class MsgReportEOF : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgReportEOF* create() { + return new MsgReportEOF(); + } + + private: + MsgReportEOF() : + Message() + { } + }; + + class MsgReportTrackChange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getTrackIndex() const { return m_trackIndex; } + static MsgReportTrackChange* create(int trackIndex) { + return new MsgReportTrackChange(trackIndex); + } + + private: + int m_trackIndex; + MsgReportTrackChange(int trackIndex) : + Message(), + m_trackIndex(trackIndex) + { } + }; + + SigMFFileInputThread(std::ifstream *samplesStream, + SampleSinkFifo* sampleFifo, + const QTimer& timer, + MessageQueue *fileInputMessageQueue, + QObject* parent = NULL); + ~SigMFFileInputThread(); + + void startWork(); + void stopWork(); + void setBuffers(std::size_t chunksize); + bool isRunning() const { return m_running; } + quint64 getSamplesCount() const { return m_samplesCount; } + void setSamplesCount(uint64_t samplesCount) { m_samplesCount = samplesCount; } + void setTotalSamples(uint64_t totalSamples) { m_totalSamples = totalSamples; } + void setMetaInformation(const SigMFFileMetaInfo *metaInfo, const QList *captures); + void setAccelerationFactor(int accelerationFactor); + void setTrackIndex(int trackIndex); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + volatile bool m_running; + + const SigMFFileMetaInfo *m_metaInfo; + const QList *m_captures; + int m_currentTrackIndex; + std::ifstream* m_ifstream; + quint8 *m_fileBuf; + quint8 *m_convertBuf; + std::size_t m_bufsize; + qint64 m_chunksize; + SampleSinkFifo* m_sampleFifo; + uint64_t m_samplesCount; + uint64_t m_totalSamples; + const QTimer& m_timer; + MessageQueue *m_fileInputMessageQueue; + + int m_samplerate; //!< File I/Q stream original sample rate + int m_accelerationFactor; + quint64 m_samplesize; //!< File effective sample size in bits (I or Q). Ex: 16, 24. + quint64 m_samplebytes; //!< Number of bytes used to store a I or Q sample. Ex: 2. 4. + qint64 m_throttlems; + QElapsedTimer m_elapsedTimer; + bool m_throttleToggle; + + SigMFConverterInterface *m_sigMFConverter; + + void run(); + //void decimate1(SampleVector::iterator* it, const qint16* buf, qint32 len); + void setSampleRate(); + void setConverter(); + void writeToSampleFifo(const quint8* buf, qint32 nbBytes); + void writeToSampleFifoBAK(const quint8* buf, qint32 nbBytes); + +private slots: + void tick(); +}; + +#endif // INCLUDE_SIGMFFILEINPUTTHREAD_H diff --git a/plugins/samplesource/sigmffileinput/sigmffileinputwebapiadapter.cpp b/plugins/samplesource/sigmffileinput/sigmffileinputwebapiadapter.cpp new file mode 100644 index 000000000..dfab72d1c --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputwebapiadapter.cpp @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// Implementation of static web API adapters used for preset serialization and // +// deserialization // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "SWGDeviceSettings.h" +#include "sigmffileinput.h" +#include "sigmffileinputwebapiadapter.h" + +SigMFFileInputWebAPIAdapter::SigMFFileInputWebAPIAdapter() +{} + +SigMFFileInputWebAPIAdapter::~SigMFFileInputWebAPIAdapter() +{} + +int SigMFFileInputWebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setAirspyHfSettings(new SWGSDRangel::SWGAirspyHFSettings()); + response.getAirspyHfSettings()->init(); + SigMFFileInput::webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int SigMFFileInputWebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage) +{ + (void) errorMessage; + SigMFFileInput::webapiUpdateDeviceSettings(m_settings, deviceSettingsKeys, response); + return 200; +} diff --git a/plugins/samplesource/sigmffileinput/sigmffileinputwebapiadapter.h b/plugins/samplesource/sigmffileinput/sigmffileinputwebapiadapter.h new file mode 100644 index 000000000..512ff080a --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputwebapiadapter.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// Implementation of static web API adapters used for preset serialization and // +// deserialization // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SIGMFFILEWEBAPIADAPTER_H +#define INCLUDE_SIGMFFILEWEBAPIADAPTER_H + +#include "device/devicewebapiadapter.h" +#include "sigmffileinputsettings.h" + +class SigMFFileInputWebAPIAdapter : public DeviceWebAPIAdapter +{ +public: + SigMFFileInputWebAPIAdapter(); + virtual ~SigMFFileInputWebAPIAdapter(); + virtual QByteArray serialize() { return m_settings.serialize(); } + virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + +private: + SigMFFileInputSettings m_settings; +}; + +#endif // INCLUDE_SIGMFFILEWEBAPIADAPTER_H diff --git a/sdrgui/resources/info.png b/sdrgui/resources/info.png new file mode 100644 index 000000000..a6f56dd4d Binary files /dev/null and b/sdrgui/resources/info.png differ diff --git a/sdrgui/resources/res.qrc b/sdrgui/resources/res.qrc index b703d3858..33755c3da 100644 --- a/sdrgui/resources/res.qrc +++ b/sdrgui/resources/res.qrc @@ -1,5 +1,6 @@ + info.png gridpolar.png gridrect.png double_arrow_up.png