From 99c079b5493d6d07a28987af2a1cf1f63ee4dd5a Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 10 Nov 2020 02:09:21 +0100 Subject: [PATCH] SigMF file input --- CMakeLists.txt | 7 +- cmake/Modules/FindLibSigMF.cmake | 36 + plugins/samplesource/CMakeLists.txt | 6 +- .../sigmffileinput/CMakeLists.txt | 61 + plugins/samplesource/sigmffileinput/readme.md | 168 ++ .../sigmffileinput/recordinfodialog.cpp | 15 + .../sigmffileinput/recordinfodialog.h | 23 + .../sigmffileinput/recordinfodialog.ui | 127 ++ .../sigmffileinput/sigmffileconvert.h | 1554 +++++++++++++++++ .../sigmffileinput/sigmffiledata.h | 200 +++ .../sigmffileinput/sigmffileinput.cpp | 1215 +++++++++++++ .../sigmffileinput/sigmffileinput.h | 501 ++++++ .../sigmffileinput/sigmffileinputgui.cpp | 689 ++++++++ .../sigmffileinput/sigmffileinputgui.h | 116 ++ .../sigmffileinput/sigmffileinputgui.ui | 895 ++++++++++ .../sigmffileinput/sigmffileinputplugin.cpp | 148 ++ .../sigmffileinput/sigmffileinputplugin.h | 55 + .../sigmffileinput/sigmffileinputsettings.cpp | 159 ++ .../sigmffileinput/sigmffileinputsettings.h | 47 + .../sigmffileinputwebapiadapter.cpp | 51 + .../sigmffileinputwebapiadapter.h | 49 + .../sigmffileinput/sigmffileinputworker.cpp | 739 ++++++++ .../sigmffileinput/sigmffileinputworker.h | 130 ++ sdrbase/CMakeLists.txt | 17 + sdrbase/dsp/sigmf_forward.h | 64 + sdrbase/dsp/sigmffilerecord.cpp | 245 +++ sdrbase/dsp/sigmffilerecord.h | 79 + sdrbase/util/sha512.h | 298 ++++ 28 files changed, 7692 insertions(+), 2 deletions(-) create mode 100644 cmake/Modules/FindLibSigMF.cmake create mode 100644 plugins/samplesource/sigmffileinput/CMakeLists.txt create mode 100644 plugins/samplesource/sigmffileinput/readme.md create mode 100644 plugins/samplesource/sigmffileinput/recordinfodialog.cpp create mode 100644 plugins/samplesource/sigmffileinput/recordinfodialog.h create mode 100644 plugins/samplesource/sigmffileinput/recordinfodialog.ui create mode 100644 plugins/samplesource/sigmffileinput/sigmffileconvert.h create mode 100644 plugins/samplesource/sigmffileinput/sigmffiledata.h create mode 100644 plugins/samplesource/sigmffileinput/sigmffileinput.cpp create mode 100644 plugins/samplesource/sigmffileinput/sigmffileinput.h create mode 100644 plugins/samplesource/sigmffileinput/sigmffileinputgui.cpp create mode 100644 plugins/samplesource/sigmffileinput/sigmffileinputgui.h create mode 100644 plugins/samplesource/sigmffileinput/sigmffileinputgui.ui create mode 100644 plugins/samplesource/sigmffileinput/sigmffileinputplugin.cpp create mode 100644 plugins/samplesource/sigmffileinput/sigmffileinputplugin.h create mode 100644 plugins/samplesource/sigmffileinput/sigmffileinputsettings.cpp create mode 100644 plugins/samplesource/sigmffileinput/sigmffileinputsettings.h create mode 100644 plugins/samplesource/sigmffileinput/sigmffileinputwebapiadapter.cpp create mode 100644 plugins/samplesource/sigmffileinput/sigmffileinputwebapiadapter.h create mode 100644 plugins/samplesource/sigmffileinput/sigmffileinputworker.cpp create mode 100644 plugins/samplesource/sigmffileinput/sigmffileinputworker.h create mode 100644 sdrbase/dsp/sigmf_forward.h create mode 100644 sdrbase/dsp/sigmffilerecord.cpp create mode 100644 sdrbase/dsp/sigmffilerecord.h create mode 100644 sdrbase/util/sha512.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c32708778..258e28824 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ project(sdrangel) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules) # disable only when needed -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) @@ -360,6 +360,11 @@ find_package(Boost REQUIRED) find_package(FFTW3F REQUIRED) find_package(LibUSB REQUIRED) # used by so many packages find_package(OpenCV OPTIONAL_COMPONENTS core highgui imgproc imgcodecs videoio) # channeltx/modatv +find_package(LibSigMF) # SigMF recording files support + +if (LIBSIGMF_FOUND) + add_definitions(-DHAS_LIBSIGMF) +endif (LIBSIGMF_FOUND) # macOS compatibility if(APPLE) diff --git a/cmake/Modules/FindLibSigMF.cmake b/cmake/Modules/FindLibSigMF.cmake new file mode 100644 index 000000000..9db6f4855 --- /dev/null +++ b/cmake/Modules/FindLibSigMF.cmake @@ -0,0 +1,36 @@ +# Find libdsdcc + +if (NOT LIBSIGMF_FOUND) + + pkg_check_modules(LIBSIGMF_PKG libsigmf) + + find_path (LIBSIGMF_INCLUDE_DIR + NAMES libsigmf/sigmf_sdrangel_generated.h + HINTS ${LIBSIGMF_DIR}/include + ${LIBSIGMF_PKG_INCLUDE_DIRS} + PATHS /usr/include/libsigmf + /usr/local/include/libsigmf + ) + + find_library (LIBSIGMF_LIBRARIES + NAMES libsigmf + HINTS ${LIBSIGMF_DIR}/lib + ${LIBSIGMF_DIR}/lib64 + ${LIBSIGMF_PKG_LIBRARY_DIRS} + PATHS /usr/lib + /usr/lib64 + /usr/local/lib + /usr/local/lib64 + ) + + if (LIBSIGMF_INCLUDE_DIR AND LIBSIGMF_LIBRARIES) + set(LIBSIGMF_FOUND TRUE CACHE INTERNAL "libsigmf found") + message(STATUS "Found libsigmf: ${LIBSIGMF_INCLUDE_DIR}, ${LIBSIGMF_LIBRARIES}") + else (LIBSIGMF_INCLUDE_DIR AND LIBSIGMF_LIBRARIES) + set(LIBSIGMF_FOUND FALSE CACHE INTERNAL "libdsdcc found") + message(STATUS "libsigmf not found.") + endif (LIBSIGMF_INCLUDE_DIR AND LIBSIGMF_LIBRARIES) + + mark_as_advanced(LIBSIGMF_INCLUDE_DIR LIBSIGMF_LIBRARIES) + +endif (NOT LIBSIGMF_FOUND) diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index 29e40bb00..6c506f97f 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -58,9 +58,13 @@ if(ENABLE_SOAPYSDR AND SOAPYSDR_FOUND) add_subdirectory(soapysdrinput) endif() +if(LIBSIGMF_FOUND) + add_subdirectory(sigmffileinput) +endif() + if(ENABLE_USRP AND UHD_FOUND) add_subdirectory(usrpinput) endif() add_subdirectory(audioinput) -add_subdirectory(kiwisdr) \ No newline at end of file +add_subdirectory(kiwisdr) diff --git a/plugins/samplesource/sigmffileinput/CMakeLists.txt b/plugins/samplesource/sigmffileinput/CMakeLists.txt new file mode 100644 index 000000000..70b1f3df9 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/CMakeLists.txt @@ -0,0 +1,61 @@ +project(sigmffileinput) + +set(sigmffileinput_SOURCES + sigmffileinput.cpp + sigmffileinputplugin.cpp + sigmffileinputworker.cpp + sigmffileinputsettings.cpp + sigmffileinputwebapiadapter.cpp +) + +set(sigmffileinput_HEADERS + sigmffileinput.h + sigmffileinputplugin.h + sigmffileinputworker.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..996eb0383 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/readme.md @@ -0,0 +1,168 @@ +

SigMF file input plugin

+ +

Introduction

+ +This plugin reads a file of samples in [SigMF](https://github.com/gnuradio/SigMF/blob/master/sigmf-spec.md) format. SDRangel [SigMF file sink plugin](../../channelrx/sigmffilesink/readme.md) can save I/Q data in SigMF format. When files are recorded with SDRangel the SDRangel specific SigMF standard extensions are available among which variable sample rate. + +This plugin supports single stream files having one `.sigmf-meta` file and only one `.sigmf-data` file. Thus it does not implement the `multirecordings` extension. + +It adds a dependency to the [libsigmf library](https://github.com/f4exb/libsigmf) more specifically the `f4exb` fork that supports `multirecordings` and `sdrangel` extensions. + +Note: this plugin is officially supported since version 6. + +

Interface

+ +![SigMF File input plugin GUI](../../../doc/img/SigMFFileInput_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 there can be a CRC (SHA512) error or the file size is inconsistent (TOT label appears in red). You may stop and choose another file. + +

2: Frequency

+ +This is the center frequency of reception in Hz of the track (or capture) being played currently. The 3 lower digits appear smaller above the "Hz" unit label. + +

3: Track sample rate

+ +This is the sample rate in S/s of the track being played currently. It is possibly suffixed by a thousands mutiplier ('k' for kHz, 'M' for MHz). Recording SigMF files in SDRangel offers the possibility to change sample rate within the same record creating a new track. + +

4: Open file

+ +Opens a file dialog to select the input file. It expects a default extension of `.sigmf-meta` which points to the meta data file. It expects a file with the `sigmf-data` extension and the same base name for the data file. 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 meta file being read. + +

A: Global meta data

+ +![SigMF File input plugin A GUI](../../../doc/img/SigMFFileInput_pluginA.png) + +This section shows the meta data pertaining to the whole record. It is detailed as follows. + +

1: Recording detailed information

+ +Opens a pop-up display with the complete global information contained in the meta file. + +

2: Recoding summary information

+ +If the recoding was made with SDRangel it will show information about the instance that created the file. + + - GUI (`SDRangel`) or server (`SDRangelSrv`) instance + - Number of sample bits (`16` or `24`) + - Version + +It the recording was not made with SDRangel it will just display `Not recorded with SDRangel` + +

3: Sample rate

+ +This is the sample rate of the track currently being played or the global sample rate if the recording was not made by SDRangel. + +

4: Sample format

+ +This is the sample format of the recording + + - Complex (`c`) or real (`r`). Note that real samples are completed with an imaginary part of zero resulting in aliasing (no Hilbert transform). + - Sample type: + - `i`: signed integer + - `u`: unsigned integer + - `f`: floating point (4 bytes or single precision) + - Sample size in bits followed by `b`. This is the actual range of samples in bits. When record is made with 24 bit samples version of SDRangel it shows `24` although the sample size in the file is actually 32 bits. + +

5: CRC indicator

+ +Indicates if the CRC check (SHA512) has succeeded (green) or failed (red) or is not available in meta data (grey). + +

6: Total number of samples check

+ +Compares the size of the data file with the size data stored in the meta file. If they match the label shows in green else it shows in red. You can still read a file with a failing total samples check however it may be inconsistent and stop before the predicted end or not play all samples. + +

7: Current timestamp

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

B: Tracks (or captures) data

+ +![SigMF File input plugin B GUI](../../../doc/img/SigMFFileInput_pluginB.png) + +This section shows tracks information and is detailed as follows + +

1: Current track

+ +The track being played currently is highlighted. When the playing is paused you can click on the track you want to move to. + +

2: Track number

+ +Track sequence number starting at `1`. + +

3: Track start date and time

+ +Shows the date and time of the start of the track in ISO format. + +

4: Track center frequency

+ +Shows the center frequency of the track in Hz. It is possibly suffixed with a thousands multiplier (`k` for kHz, `M` for MHz, `G` for GHz). + +

5: Track sample rate

+ +If the file was recorded by SDRangel the sample rate can vary between tracks else it is global for the whole record and will show this value for all tracks. Sample rate is in S/s possibly suffixed with a thousands multiplier (`k` for kHz, `M` for MHz). + +

6: Track duration time

+ +This is the track duration in `HH:MM:ss` format + +

6: Current track pointer gauge

+ +This represents the position of the current pointer position in the track being played. It can be used in paused mode to position the current pointer by moving the slider. + +

7: Track loop

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

8: Track play/pause

+ +This is the play/pause button for the current track. In pause mode you may switch to full record play by clicking on the record play/pause button (14). + +

9: Track number

+ +This is the track number of the track currently being played. + +

10: Relative track time

+ +This is the time in `HH:MM:ss.zzz` format of the current pointer since the beginning of the track. + +

11: Track duration

+ +This is the current track duration in `HH:MM:ss` format. + +

12: Current record pointer gauge

+ +This represents the position of the current pointer position in the complete recording. It can be used in paused mode to position the current pointer by moving the slider. + +

13: Record loop

+ +Use this button to read the full record in a loop or read it only once + +

14: Record play/pause

+ +This is the play/pause button for the whole record. In pause mode you may switch to current track record play by clicking on the track play/pause button (8). + +

15: 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. + +

16: Relative timestamp and record length

+ +Relative timestamp of the current pointer from the start of the record in `HH:MM:ss.zzz` format. + +

17: Record length

+ +Total record time in `HH:MM:ss` format. 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..8d0934e52 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffiledata.h @@ -0,0 +1,200 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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; + } +}; + +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; + } +}; + +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; + } +}; + +#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..3230ed5d5 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinput.cpp @@ -0,0 +1,1215 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "SWGSigMFFileInputSettings.h" +#include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGDeviceActions.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 "sigmffileinputworker.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_crcAvailable(false), + m_crcOK(false), + m_recordLengthOK(false), + m_fileInputWorker(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"); + m_crcAvailable = true; + std::string sha512 = sw::sha512::file(dataFileName.toStdString()); + m_crcOK = m_metaInfo.m_sha512 == QString::fromStdString(sha512); + + if (m_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(m_crcOK); + getMessageQueueToGUI()->push(report); + } + + if (!m_crcOK) { + return false; + } + } + else + { + m_crcAvailable = false; + } + + + m_recordLengthOK = (m_metaInfo.m_totalSamples == m_captures.back().m_sampleStart + m_captures.back().m_length); + + if (m_recordLengthOK) { + 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(m_recordLengthOK); + 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)*(m_metaInfo.m_dataType.m_complex ? 2 : 1)); + 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; + double globalSampleRate = metaRecord->global.access().sample_rate; + + if (sdrangelSampleRate == 0) { + m_captures.back().m_sampleRate = globalSampleRate < 0 ? -globalSampleRate : globalSampleRate; + } 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_fileInputWorker = new SigMFFileInputWorker(&m_dataStream, &m_sampleFifo, m_masterTimer, &m_inputMessageQueue); + startWorker(); + m_fileInputWorker->setMetaInformation(&m_metaInfo, &m_captures); + m_fileInputWorker->setAccelerationFactor(m_settings.m_accelerationFactor); + m_fileInputWorker->setTrackIndex(0); + m_fileInputWorker->moveToThread(&m_fileInputWorkerThread); + 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_fileInputWorker) + { + stopWorker(); + delete m_fileInputWorker; + m_fileInputWorker = nullptr; + } + + m_deviceDescription.clear(); + + if (getMessageQueueToGUI()) { + MsgReportStartStop *report = MsgReportStartStop::create(false); + getMessageQueueToGUI()->push(report); + } +} + +void SigMFFileInput::startWorker() +{ + m_fileInputWorker->startWork(); + m_fileInputWorkerThread.start(); +} + +void SigMFFileInput::stopWorker() +{ + m_fileInputWorker->stopWork(); + m_fileInputWorkerThread.quit(); + m_fileInputWorkerThread.wait(); +} + +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_fileInputWorker) + { + bool working = m_fileInputWorker->isRunning(); + + if (working) { + stopWorker(); + } + + m_fileInputWorker->setTrackIndex(m_currentTrackIndex); + m_fileInputWorker->setTotalSamples( + m_trackMode ? + m_captures[m_currentTrackIndex].m_sampleStart + m_captures[m_currentTrackIndex].m_length : + m_metaInfo.m_totalSamples + ); + + if (working) { + startWorker(); + } + } + + return true; + } + else if (MsgConfigureTrackWork::match(message)) + { + MsgConfigureTrackWork& conf = (MsgConfigureTrackWork&) message; + bool working = conf.isWorking(); + m_trackMode = true; + + if (m_fileInputWorker) + { + if (working) + { + m_fileInputWorker->setTotalSamples( + m_captures[m_currentTrackIndex].m_sampleStart + m_captures[m_currentTrackIndex].m_length); + startWorker(); + } + else + { + stopWorker(); + } + } + + return true; + } + else if (MsgConfigureTrackSeek::match(message)) + { + MsgConfigureTrackSeek& conf = (MsgConfigureTrackSeek&) message; + int seekMillis = conf.getMillis(); + seekTrackMillis(seekMillis); + + if (m_fileInputWorker) + { + bool working = m_fileInputWorker->isRunning(); + + if (working) { + stopWorker(); + } + + m_fileInputWorker->setSamplesCount( + m_captures[m_currentTrackIndex].m_sampleStart + ((m_captures[m_currentTrackIndex].m_length*seekMillis)/1000UL)); + + if (working) { + startWorker(); + } + } + + 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_fileInputWorker) + { + bool working = m_fileInputWorker->isRunning(); + + if (working) { + stopWorker(); + } + + m_fileInputWorker->setTrackIndex(m_currentTrackIndex); + m_fileInputWorker->setSamplesCount(sampleCount); + + if (working) { + startWorker(); + } + } + + return true; + } + else if (MsgConfigureFileWork::match(message)) + { + MsgConfigureFileWork& conf = (MsgConfigureFileWork&) message; + bool working = conf.isWorking(); + m_trackMode = false; + + if (m_fileInputWorker) + { + if (working) + { + m_fileInputWorker->setTotalSamples(m_metaInfo.m_totalSamples); + startWorker(); + } + else + { + stopWorker(); + } + } + + return true; + } + else if (MsgConfigureFileInputStreamTiming::match(message)) + { + if (m_fileInputWorker) + { + if (getMessageQueueToGUI()) + { + quint64 totalSamplesCount = m_fileInputWorker->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 (SigMFFileInputWorker::MsgReportEOF::match(message)) // End Of File or end of track + { + qDebug() << "FileInput::handleMessage: MsgReportEOF"; + bool working = m_fileInputWorker->isRunning(); + + if (working) { + stopWorker(); + } + + if (m_trackMode) + { + if (m_settings.m_trackLoop) + { + seekFileStream(m_captures[m_currentTrackIndex].m_sampleStart); + m_fileInputWorker->setTrackIndex(m_currentTrackIndex); + } + } + else + { + if (m_settings.m_fullLoop) + { + seekFileStream(0); + m_fileInputWorker->setTrackIndex(0); + } + } + + if (working) { + startWorker(); + } + + return true; + } + else if (SigMFFileInputWorker::MsgReportTrackChange::match(message)) + { + SigMFFileInputWorker::MsgReportTrackChange& report = (SigMFFileInputWorker::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); + } + + return true; + } + 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_fileInputWorker) + { + 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_fileInputWorker->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_trackLoop != settings.m_fullLoop)) { + reverseAPIKeys.append("fullLoop"); + } + + 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.setSigMfFileInputSettings(new SWGSDRangel::SWGSigMFFileInputSettings()); + response.getSigMfFileInputSettings()->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.getSigMfFileInputSettings()->getFileName(); + } + if (deviceSettingsKeys.contains("accelerationFactor")) { + settings.m_accelerationFactor = response.getSigMfFileInputSettings()->getAccelerationFactor(); + } + if (deviceSettingsKeys.contains("trackLoop")) { + settings.m_trackLoop = response.getSigMfFileInputSettings()->getTrackLoop() != 0; + } + if (deviceSettingsKeys.contains("fullLoop")) { + settings.m_trackLoop = response.getSigMfFileInputSettings()->getFullLoop() != 0; + } + if (deviceSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getSigMfFileInputSettings()->getUseReverseApi() != 0; + } + if (deviceSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getSigMfFileInputSettings()->getReverseApiAddress(); + } + if (deviceSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getSigMfFileInputSettings()->getReverseApiPort(); + } + if (deviceSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIDeviceIndex = response.getSigMfFileInputSettings()->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.setSigMfFileInputReport(new SWGSDRangel::SWGSigMFFileInputReport()); + response.getSigMfFileInputReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +int SigMFFileInput::webapiActionsPost( + const QStringList& deviceActionsKeys, + SWGSDRangel::SWGDeviceActions& query, + QString& errorMessage) +{ + SWGSDRangel::SWGSigMFFileInputActions *swgSigMFFileInputActions = query.getSigMfFileInputActions(); + + if (swgSigMFFileInputActions) + { + if (deviceActionsKeys.contains("playTrack")) + { + bool play = swgSigMFFileInputActions->getPlayTrack() != 0; + MsgConfigureTrackWork * msg = MsgConfigureTrackWork::create(play); + getInputMessageQueue()->push(msg); + + if (getMessageQueueToGUI()) + { + MsgConfigureTrackWork *msgToGUI = MsgConfigureTrackWork::create(play); + getMessageQueueToGUI()->push(msgToGUI); + } + } + else if (deviceActionsKeys.contains("playRecord")) + { + bool play = swgSigMFFileInputActions->getPlayRecord() != 0; + MsgConfigureFileWork * msg = MsgConfigureFileWork::create(play); + getInputMessageQueue()->push(msg); + + if (getMessageQueueToGUI()) + { + MsgConfigureFileWork *msgToGUI = MsgConfigureFileWork::create(play); + getMessageQueueToGUI()->push(msgToGUI); + } + } + else if (deviceActionsKeys.contains("seekTrack")) + { + int trackIndex = swgSigMFFileInputActions->getSeekTrack(); + MsgConfigureTrackIndex *msg = MsgConfigureTrackIndex::create(trackIndex); + getInputMessageQueue()->push(msg); + + if (getMessageQueueToGUI()) + { + MsgConfigureTrackIndex *msgToGUI = MsgConfigureTrackIndex::create(trackIndex); + getMessageQueueToGUI()->push(msgToGUI); + } + } + else if (deviceActionsKeys.contains("seekTrackMillis")) + { + int trackMillis = swgSigMFFileInputActions->getSeekTrackMillis(); + MsgConfigureTrackSeek *msg = MsgConfigureTrackSeek::create(trackMillis); + getInputMessageQueue()->push(msg); + + if (getMessageQueueToGUI()) + { + MsgConfigureTrackSeek *msgToGUI = MsgConfigureTrackSeek::create(trackMillis); + getMessageQueueToGUI()->push(msgToGUI); + } + } + else if (deviceActionsKeys.contains("seekRecordMillis")) + { + int recordMillis = swgSigMFFileInputActions->getSeekRecordMillis(); + MsgConfigureFileSeek *msg = MsgConfigureFileSeek::create(recordMillis); + getInputMessageQueue()->push(msg); + + if (getMessageQueueToGUI()) + { + MsgConfigureFileSeek *msgToGUI = MsgConfigureFileSeek::create(recordMillis); + getMessageQueueToGUI()->push(msgToGUI); + } + } + + return 202; + } + else + { + errorMessage = "Missing AirspyActions in query"; + return 400; + } +} + +void SigMFFileInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SigMFFileInputSettings& settings) +{ + response.getSigMfFileInputSettings()->setFileName(new QString(settings.m_fileName)); + response.getSigMfFileInputSettings()->setAccelerationFactor(settings.m_accelerationFactor); + response.getSigMfFileInputSettings()->setTrackLoop(settings.m_trackLoop ? 1 : 0); + response.getSigMfFileInputSettings()->setFullLoop(settings.m_fullLoop ? 1 : 0); + + response.getSigMfFileInputSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getSigMfFileInputSettings()->getReverseApiAddress()) { + *response.getSigMfFileInputSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getSigMfFileInputSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getSigMfFileInputSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getSigMfFileInputSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); +} + +void SigMFFileInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + if (!m_metaStream.is_open()) { + return; + } + + response.getSigMfFileInputReport()->setSampleSize(m_metaInfo.m_dataType.m_sampleBits); + response.getSigMfFileInputReport()->setSampleBytes(m_sampleBytes); + response.getSigMfFileInputReport()->setSampleFormat(m_metaInfo.m_dataType.m_floatingPoint ? 1 : 0); + response.getSigMfFileInputReport()->setSampleSigned(m_metaInfo.m_dataType.m_signed ? 1 : 0); + response.getSigMfFileInputReport()->setSampleSwapIq(m_metaInfo.m_dataType.m_swapIQ ? 1 : 0); + response.getSigMfFileInputReport()->setCrcStatus(!m_crcAvailable ? 0 : m_crcOK ? 1 : 2); + response.getSigMfFileInputReport()->setTotalBytesStatus(m_recordLengthOK); + response.getSigMfFileInputReport()->setTrackNumber(m_currentTrackIndex); + QList::const_iterator it = m_captures.begin(); + + if (response.getSigMfFileInputReport()->getCaptures()) { + response.getSigMfFileInputReport()->getCaptures()->clear(); + } else { + response.getSigMfFileInputReport()->setCaptures(new QList); + } + + for (; it != m_captures.end(); ++it) + { + response.getSigMfFileInputReport()->getCaptures()->append(new SWGSDRangel::SWGCapture); + response.getSigMfFileInputReport()->getCaptures()->back()->setTsms(it->m_tsms); + response.getSigMfFileInputReport()->getCaptures()->back()->setCenterFrequency(it->m_centerFrequency); + response.getSigMfFileInputReport()->getCaptures()->back()->setSampleRate(it->m_sampleRate); + response.getSigMfFileInputReport()->getCaptures()->back()->setSampleStart(it->m_sampleStart); + response.getSigMfFileInputReport()->getCaptures()->back()->setLength(it->m_length); + response.getSigMfFileInputReport()->getCaptures()->back()->setCumulativeTime(it->m_cumulativeTime); + } + + uint64_t totalSamplesCount = 0; + + if (m_fileInputWorker) { + totalSamplesCount = m_fileInputWorker->getSamplesCount(); + } + + unsigned int sampleRate = m_captures[m_currentTrackIndex].m_sampleRate; + uint64_t trackSamplesCount = totalSamplesCount - m_captures[m_currentTrackIndex].m_sampleStart; + uint64_t trackCumulativeTime = m_captures[m_currentTrackIndex].m_cumulativeTime; + uint64_t startingTimeStampMs = m_captures[m_currentTrackIndex].m_tsms; + + uint64_t t = (trackSamplesCount*1000)/sampleRate; + response.getSigMfFileInputReport()->setElapsedTrackimeMs(t); + t += m_captures[m_currentTrackIndex].m_cumulativeTime; + response.getSigMfFileInputReport()->setElapsedRecordTimeMs(t); + response.getSigMfFileInputReport()->setAbsoluteTimeMs(startingTimeStampMs + ((trackSamplesCount*1000)/sampleRate)); + + float posRatio = (float) trackSamplesCount / (float) m_captures[m_currentTrackIndex].m_length; + response.getSigMfFileInputReport()->setTrackSamplesRatio(posRatio); + + posRatio = (float) totalSamplesCount / (float) m_metaInfo.m_totalSamples; + response.getSigMfFileInputReport()->setRecordSamplesRatio(posRatio); + + if (m_captures.size() > 0 ) + { + uint64_t totalTimeMs = m_captures.back().m_cumulativeTime + ((m_captures.back().m_length * 1000) / m_captures.back().m_sampleRate); + response.getSigMfFileInputReport()->setRecordDurationMs(totalTimeMs); + } + else + { + response.getSigMfFileInputReport()->setRecordDurationMs(0); + } +} + +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->setSigMfFileInputSettings(new SWGSDRangel::SWGSigMFFileInputSettings()); + SWGSDRangel::SWGSigMFFileInputSettings *swgSigMFFileInputSettings = swgDeviceSettings->getSigMfFileInputSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (deviceSettingsKeys.contains("accelerationFactor") || force) { + swgSigMFFileInputSettings->setAccelerationFactor(settings.m_accelerationFactor); + } + if (deviceSettingsKeys.contains("trackLoop") || force) { + swgSigMFFileInputSettings->setTrackLoop(settings.m_trackLoop); + } + if (deviceSettingsKeys.contains("fullLoop") || force) { + swgSigMFFileInputSettings->setFullLoop(settings.m_fullLoop); + } + if (deviceSettingsKeys.contains("fileName") || force) { + swgSigMFFileInputSettings->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..119bdaf8b --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinput.h @@ -0,0 +1,501 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 + +#include "dsp/sigmf_forward.h" + +#include "dsp/devicesamplesource.h" +#include "sigmffileinputsettings.h" +#include "sigmffiledata.h" + +class QNetworkAccessManager; +class QNetworkReply; +class SigMFFileInputWorker; +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 webapiActionsPost( + const QStringList& deviceActionsKeys, + SWGSDRangel::SWGDeviceActions& query, + 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; + bool m_crcAvailable; + bool m_crcOK; + bool m_recordLengthOK; + QString m_recordSummary; + SigMFFileInputWorker* m_fileInputWorker; + QThread m_fileInputWorkerThread; + 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; + + void startWorker(); + void stopWorker(); + 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..d2ad86104 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputgui.cpp @@ -0,0 +1,689 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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) : + DeviceGUI(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(8, 0, pow(10,8)); + + ui->centerFrequencyHz->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->centerFrequencyHz->setValueRange(3, 0, 999U); + + 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::resetToDefaults() +{ + m_settings.resetToDefaults(); + 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); + } + if (m_metaInfo.m_hw.size() > 0) { + 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("Swap I/Q...: %1\n").arg(m_metaInfo.m_dataType.m_swapIQ ? "yes" : "no"); + m_recordInfo += QString("Nb samples.: %1 (%2S)\n").arg(m_metaInfo.m_totalSamples).arg(displayScaled(m_metaInfo.m_totalSamples, 3)); + 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 (%2S)\n").arg(m_metaInfo.m_totalSamples).arg(displayScaled(m_metaInfo.m_totalSamples, 3)); + 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)); + } else { + return tr("%1").arg(QString::number(value, 'e', 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->centerFrequencyHz->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..a76f354f1 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputgui.h @@ -0,0 +1,116 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "device/devicegui.h" +#include "util/messagequeue.h" + +#include "sigmffileinputsettings.h" +#include "sigmffiledata.h" +#include "sigmffileinput.h" + +class DeviceUISet; + +namespace Ui { + class SigMFFileInputGUI; +} + +class SigMFFileInputGUI : public DeviceGUI { + Q_OBJECT + +public: + explicit SigMFFileInputGUI(DeviceUISet *deviceUISet, QWidget* parent = nullptr); + virtual ~SigMFFileInputGUI(); + virtual void destroy(); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + +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); + bool handleMessage(const Message& message); + +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..4c326d7e2 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputgui.ui @@ -0,0 +1,895 @@ + + + SigMFFileInputGUI + + + + 0 + 0 + 360 + 420 + + + + + 0 + 0 + + + + + 360 + 420 + + + + + Liberation Sans + 9 + 50 + false + false + + + + SigMF File Input + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 4 + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + + + + + + 56 + 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 + + + + + + + 6 + + + 6 + + + + + false + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Record center frequency in kHz + + + + + + + Hz + + + + + + + + + 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..045c8ab85 --- /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("6.0.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 +DeviceGUI* SigMFFileInputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + (void) sourceId; + (void) widget; + (void) deviceUISet; + return 0; +} +#else +DeviceGUI* 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..aae460833 --- /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 DeviceGUI* 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..5abdce315 --- /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(4, 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; + } +} 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/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/plugins/samplesource/sigmffileinput/sigmffileinputworker.cpp b/plugins/samplesource/sigmffileinput/sigmffileinputworker.cpp new file mode 100644 index 000000000..b3704d733 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputworker.cpp @@ -0,0 +1,739 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "dsp/filerecord.h" +#include "dsp/samplesinkfifo.h" +#include "util/messagequeue.h" + +#include "sigmffiledata.h" +#include "sigmffileconvert.h" +#include "sigmffileinputsettings.h" +#include "sigmffileinputworker.h" + +MESSAGE_CLASS_DEFINITION(SigMFFileInputWorker::MsgReportEOF, Message) +MESSAGE_CLASS_DEFINITION(SigMFFileInputWorker::MsgReportTrackChange, Message) + +SigMFFileInputWorker::SigMFFileInputWorker(std::ifstream *samplesStream, + SampleSinkFifo* sampleFifo, + const QTimer& timer, + MessageQueue *fileInputMessageQueue, + QObject* parent) : + QObject(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) +{ +} + +SigMFFileInputWorker::~SigMFFileInputWorker() +{ + if (m_running) { + stopWork(); + } + + if (m_fileBuf != 0) { + free(m_fileBuf); + } + + if (m_convertBuf != 0) { + free(m_convertBuf); + } +} + +void SigMFFileInputWorker::startWork() +{ + qDebug() << "SigMFFileInputWorker::startWork: "; + + if (m_ifstream->is_open()) + { + qDebug() << "SigMFFileInputWorker::startWork: file stream open, starting..."; + m_elapsedTimer.start(); + connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); + m_running = true; + } + else + { + qDebug() << "SigMFFileInputWorker::startWork: file stream closed, not starting."; + } +} + +void SigMFFileInputWorker::stopWork() +{ + qDebug() << "SigMFFileInputWorker::stopWork"; + disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); + m_running = false; +} + +void SigMFFileInputWorker::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 SigMFFileInputWorker::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 SigMFFileInputWorker::setAccelerationFactor(int accelerationFactor) +{ + m_accelerationFactor = accelerationFactor; + setSampleRate(); +} + +void SigMFFileInputWorker::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 SigMFFileInputWorker::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 SigMFFileInputWorker::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 SigMFFileInputWorker::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 SigMFFileInputWorker::writeToSampleFifo(const quint8* buf, qint32 nbBytes) +{ + if (!m_sigMFConverter) + { + qDebug("SigMFFileInputWorker::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 SigMFFileInputWorker::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/sigmffileinputworker.h b/plugins/samplesource/sigmffileinput/sigmffileinputworker.h new file mode 100644 index 000000000..843c350c6 --- /dev/null +++ b/plugins/samplesource/sigmffileinput/sigmffileinputworker.h @@ -0,0 +1,130 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_SIGMFFILEINPUTWORK_H +#define INCLUDE_SIGMFFILEINPUTWORK_H + +#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 SigMFFileInputWorker : public QObject { + 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) + { } + }; + + SigMFFileInputWorker(std::ifstream *samplesStream, + SampleSinkFifo* sampleFifo, + const QTimer& timer, + MessageQueue *fileInputMessageQueue, + QObject* parent = NULL); + ~SigMFFileInputWorker(); + + 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: + 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_SIGMFFILEINPUTWORK_H diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 6cd2f9b00..329bc2386 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -44,6 +44,19 @@ if (LIMESUITE_FOUND) set(sdrbase_LIMERFE_LIB ${LIMESUITE_LIBRARY}) endif (LIMESUITE_FOUND) +if (LIBSIGMF_FOUND) + set(sdrbase_SOURCES + ${sdrbase_SOURCES} + dsp/sigmffilerecord.cpp + ) + set(sdrbase_HEADERS + ${sdrbase_HEADERS} + dsp/sigmffilerecord.h + ) + include_directories(${LIBSIGMF_INCLUDE_DIR}) + set(sdrbase_LIBSIGMF_LIB ${LIBSIGMF_LIBRARIES}) +endif (LIBSIGMF_FOUND) + # serialdv now required add_definitions(-DDSD_USE_SERIALDV) include_directories(${LIBSERIALDV_INCLUDE_DIR}) @@ -123,6 +136,7 @@ set(sdrbase_SOURCES dsp/samplesimplefifo.cpp dsp/samplesourcefifo.cpp dsp/samplesourcefifodb.cpp + dsp/sigmffilerecord.cpp dsp/basebandsamplesink.cpp dsp/basebandsamplesource.cpp dsp/nullsink.cpp @@ -291,6 +305,8 @@ set(sdrbase_HEADERS dsp/samplesimplefifo.h dsp/samplesourcefifo.h dsp/samplesourcefifodb.h + dsp/sigmf_forward.h + dsp/sigmffilerecord.h dsp/basebandsamplesink.h dsp/basebandsamplesource.h dsp/nullsink.h @@ -385,6 +401,7 @@ target_link_libraries(sdrbase ${sdrbase_FFTW3F_LIB} ${sdrbase_SERIALDV_LIB} ${sdrbase_LIMERFE_LIB} + ${sdrbase_LIBSIGMF_LIB} Qt5::Core Qt5::Multimedia Qt5::WebSockets diff --git a/sdrbase/dsp/sigmf_forward.h b/sdrbase/dsp/sigmf_forward.h new file mode 100644 index 000000000..e440fa65b --- /dev/null +++ b/sdrbase/dsp/sigmf_forward.h @@ -0,0 +1,64 @@ +/* + * Copyright 2019 DeepSig Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIBSIGMF_SIGMF_FORWARD_H +#define LIBSIGMF_SIGMF_FORWARD_H + +namespace sigmf { + + template + class Global; + + template + class Captures; + + template + class Annotations; + + template + class SigMFVector : public std::vector { + public: + T &create_new() { + T new_element; + this->emplace_back(new_element); + return this->back(); + } + }; + + template + struct SigMF { + GlobalType global; + SigMFVector captures; + SigMFVector annotations; + }; + +} + +// Missing bits... + +namespace core { + class DescrT; +} +namespace sdrangel { + class DescrT; +} + +namespace sigmf { + template class Capture; + template class Annotation; +} + +#endif // LIBSIGMF_SIGMF_FORWARD_H \ No newline at end of file diff --git a/sdrbase/dsp/sigmffilerecord.cpp b/sdrbase/dsp/sigmffilerecord.cpp new file mode 100644 index 000000000..a20812d28 --- /dev/null +++ b/sdrbase/dsp/sigmffilerecord.cpp @@ -0,0 +1,245 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// File recorder in SigMF format single channel for SI plugins // +// // +// 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 "libsigmf/sigmf_core_generated.h" +#include "libsigmf/sigmf_sdrangel_generated.h" +#include "libsigmf/sigmf.h" + +#include "dsp/dspcommands.h" +//#include "util/sha512.h" - SHA512 is skipped because it takes too long + +#include "sigmffilerecord.h" + +SigMFFileRecord::SigMFFileRecord() : + FileRecordInterface(), + m_fileName("test"), + m_sampleRate(0), + m_centerFrequency(0), + m_msShift(0), + m_recordOn(false), + m_recordStart(true), + m_sampleStart(0), + m_sampleCount(0) +{ + qDebug("SigMFFileRecord::SigMFFileRecord: test"); + setObjectName("SigMFFileSink"); + m_metaRecord = new sigmf::SigMF, + sigmf::Capture, + sigmf::Annotation >(); +} + +SigMFFileRecord::SigMFFileRecord(const QString& fileName, const QString& hardwareId) : + FileRecordInterface(), + m_hardwareId(hardwareId), + m_fileName(fileName), + m_sampleRate(0), + m_centerFrequency(0), + m_recordOn(false), + m_recordStart(true), + m_sampleStart(0), + m_sampleCount(0) +{ + qDebug("SigMFFileRecord::SigMFFileRecord: %s", qPrintable(fileName)); + setObjectName("SigMFFileSink"); + m_metaRecord = new sigmf::SigMF, + sigmf::Capture, + sigmf::Annotation >(); +} + +SigMFFileRecord::~SigMFFileRecord() +{ + qDebug("SigMFFileRecord::~SigMFFileRecord"); + + stopRecording(); + + if (m_metaFile.is_open()) { + m_metaFile.close(); + } + + if (m_sampleFile.is_open()) { + m_sampleFile.close(); + } + + delete m_metaRecord; +} + +void SigMFFileRecord::setFileName(const QString& fileName) +{ + if (!m_recordOn) + { + qDebug("SigMFFileRecord::setFileName: %s", qPrintable(fileName)); + + if (m_metaFile.is_open()) { + m_metaFile.close(); + } + + if (m_sampleFile.is_open()) { + m_sampleFile.close(); + } + + m_fileName = fileName; + m_recordStart = true; + } +} + +unsigned int SigMFFileRecord::getNbCaptures() const +{ + return m_metaRecord->captures.size(); +} + +void SigMFFileRecord::startRecording() +{ + + if (m_recordStart) + { + qDebug("SigMFFileRecord::startRecording: new record %s", qPrintable(m_fileName)); + clearMeta(); + m_sampleStart = 0; + m_sampleFileName = m_fileName + ".sigmf-data"; + m_metaFileName = m_fileName + ".sigmf-meta"; + m_sampleFile.open(m_sampleFileName.toStdString().c_str(), std::ios::binary); + m_metaFile.open(m_metaFileName.toStdString().c_str(), std::ofstream::out); + makeHeader(); + m_recordStart = false; + } + else + { + qDebug("SigMFFileRecord::startRecording: start new capture"); + } + + m_captureStartDT = QDateTime::currentDateTimeUtc().addMSecs(m_msShift); + m_recordOn = true; + m_sampleCount = 0; +} + +void SigMFFileRecord::stopRecording() +{ + if (m_recordOn) + { + qDebug("SigMFFileRecord::stopRecording: file previous capture"); + makeCapture(); + m_recordOn = false; + } +} + +void SigMFFileRecord::makeHeader() +{ + m_metaRecord->global.access().author = "SDRangel"; + m_metaRecord->global.access().description = "SDRangel SigMF I/Q recording file"; + m_metaRecord->global.access().sample_rate = m_sampleRate; + m_metaRecord->global.access().hw = m_hardwareId.toStdString(); + m_metaRecord->global.access().recorder = QString(QCoreApplication::applicationName()).toStdString(); + m_metaRecord->global.access().version = "0.0.2"; + m_metaRecord->global.access().version = QString(QCoreApplication::applicationVersion()).toStdString(); + m_metaRecord->global.access().qt_version = QT_VERSION_STR; + m_metaRecord->global.access().rx_bits = SDR_RX_SAMP_SZ; + m_metaRecord->global.access().arch = QString(QSysInfo::currentCpuArchitecture()).toStdString(); + m_metaRecord->global.access().os = QString(QSysInfo::prettyProductName()).toStdString(); + QString endianSuffix = QSysInfo::ByteOrder == QSysInfo::LittleEndian ? "le" : "be"; + int size = 8*sizeof(FixReal); + m_metaRecord->global.access().datatype = QString("ci%1_%2").arg(size).arg(endianSuffix).toStdString(); +} + +void SigMFFileRecord::makeCapture() +{ + if (m_sampleCount) + { + qDebug("SigMFFileRecord::makeCapture: m_sampleStart: %llu m_sampleCount: %llu", m_sampleStart, m_sampleCount); + // Flush samples to disk + m_sampleFile.flush(); + // calculate SHA512 and write it to header + // m_metaRecord->global.access().sha512 = sw::sha512::file(m_sampleFileName.toStdString()); // skip takes too long + // Add new capture + auto recording_capture = sigmf::Capture(); + recording_capture.get().frequency = m_centerFrequency; + recording_capture.get().sample_start = m_sampleStart; + recording_capture.get().length = m_sampleCount; + recording_capture.get().datetime = m_captureStartDT.toString("yyyy-MM-ddTHH:mm:ss.zzzZ").toStdString(); + recording_capture.get().sample_rate = m_sampleRate; + recording_capture.get().tsms = m_captureStartDT.toMSecsSinceEpoch(); + m_metaRecord->captures.emplace_back(recording_capture); + m_sampleStart += m_sampleCount; + // Flush meta to disk + m_metaFile.seekp(0); + std::string jsonRecord = json(*m_metaRecord).dump(2); + m_metaFile << jsonRecord; + m_metaFile.flush(); + } + else + { + qDebug("SigMFFileRecord::makeCapture: skipped because of no samples"); + } +} + +void SigMFFileRecord::clearMeta() +{ + m_metaRecord->captures.clear(); +} + +void SigMFFileRecord::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) +{ + (void) positiveOnly; + // if no recording is active, send the samples to /dev/null + if(!m_recordOn) + return; + + if (begin < end) // if there is something to put out + { + m_sampleFile.write(reinterpret_cast(&*(begin)), (end - begin)*sizeof(Sample)); + m_sampleCount += end - begin; + } +} + +void SigMFFileRecord::start() +{ +} + +void SigMFFileRecord::stop() +{ + stopRecording(); +} + +bool SigMFFileRecord::handleMessage(const Message& message) +{ + if (DSPSignalNotification::match(message)) + { + if (m_recordOn) { + makeCapture(); + m_captureStartDT = QDateTime::currentDateTimeUtc(); + m_sampleCount = 0; + } + + DSPSignalNotification& notif = (DSPSignalNotification&) message; + m_sampleRate = notif.getSampleRate(); + m_centerFrequency = notif.getCenterFrequency(); + qDebug() << "SigMFFileRecord::handleMessage: DSPSignalNotification: " + << " m_inputSampleRate: " << m_sampleRate + << " m_centerFrequency: " << m_centerFrequency; + + return true; + } + else + { + return false; + } +} \ No newline at end of file diff --git a/sdrbase/dsp/sigmffilerecord.h b/sdrbase/dsp/sigmffilerecord.h new file mode 100644 index 000000000..9d31a1709 --- /dev/null +++ b/sdrbase/dsp/sigmffilerecord.h @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// File recorder in SigMF format single channel for SI plugins // +// // +// 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_SIGMF_FILERECORD_H +#define INCLUDE_SIGMF_FILERECORD_H + +#include +#include +#include +#include + +#include + +#include "dsp/sigmf_forward.h" +#include "dsp/filerecordinterface.h" +#include "export.h" + +class Message; + +class SDRBASE_API SigMFFileRecord : public FileRecordInterface { +public: + SigMFFileRecord(); + SigMFFileRecord(const QString& filename, const QString& hardwareId); + virtual ~SigMFFileRecord(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& message); + + virtual void setFileName(const QString& filename); + virtual void startRecording(); + virtual void stopRecording(); + virtual bool isRecording() const { return m_recordOn; } + + void setHardwareId(const QString& hardwareId) { m_hardwareId = hardwareId; } + void setMsShift(qint64 msShift) { m_msShift = msShift; } + unsigned int getNbCaptures() const; + +private: + QString m_hardwareId; + QString m_fileName; + QString m_sampleFileName; + QString m_metaFileName; + quint32 m_sampleRate; + quint64 m_centerFrequency; + qint64 m_msShift; + bool m_recordOn; + bool m_recordStart; + QDateTime m_captureStartDT; + std::ofstream m_metaFile; + std::ofstream m_sampleFile; + quint64 m_sampleStart; + quint64 m_sampleCount; + sigmf::SigMF, + sigmf::Capture, + sigmf::Annotation > *m_metaRecord; + void makeHeader(); + void makeCapture(); + void clearMeta(); +}; + +#endif // INCLUDE_SIGMF_FILERECORD_H diff --git a/sdrbase/util/sha512.h b/sdrbase/util/sha512.h new file mode 100644 index 000000000..7d9327891 --- /dev/null +++ b/sdrbase/util/sha512.h @@ -0,0 +1,298 @@ +/** + * @file sha512.hh + * @author Stefan Wilhelm (stfwi) + * @ccflags + * @ldflags + * @platform linux, bsd, windows + * @standard >= c++98 + * + * SHA512 calculation class template. + * + * ------------------------------------------------------------------------------------- + * +++ BSD license header +++ + * Copyright (c) 2010, 2012, Stefan Wilhelm (stfwi, ) + * All rights reserved. + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: (1) Redistributions + * of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. (2) Redistributions in binary form must reproduce + * the above copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the distribution. + * (3) Neither the name of the project nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ------------------------------------------------------------------------------------- + * 01-2013: (stfwi) Class to template class, reformatting + */ +#ifndef SHA512_HH +#define SHA512_HH + +#if defined(OS_WIN) || defined (_WINDOWS_) || defined(_WIN32) || defined(__MSC_VER) +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +namespace sw { namespace detail { + +/** + * @class basic_sha512 + * @template + */ +template +class basic_sha512 +{ +public: + + /** + * Types + */ + typedef std::basic_string str_t; + +public: + + /** + * Constructor + */ + basic_sha512() + { clear(); } + + /** + * Destructor + */ + ~basic_sha512() + { ; } + +public: + + /** + * Clear/reset all internal buffers and states. + */ + void clear() + { + sum_[0] = 0x6a09e667f3bcc908; sum_[1] = 0xbb67ae8584caa73b; + sum_[2] = 0x3c6ef372fe94f82b; sum_[3] = 0xa54ff53a5f1d36f1; + sum_[4] = 0x510e527fade682d1; sum_[5] = 0x9b05688c2b3e6c1f; + sum_[6] = 0x1f83d9abfb41bd6b; sum_[7] = 0x5be0cd19137e2179; + sz_ = 0; iterations_ = 0; memset(&block_, 0, sizeof(block_)); + } + + /** + * Push new binary data into the internal buf_ and recalculate the checksum. + * @param const void* data + * @param size_t size + */ + void update(const void* data, size_t size) + { + unsigned nb, n, n_tail; + const uint8_t *p; + n = 128 - sz_; + n_tail = size < n ? size : n; + memcpy(&block_[sz_], data, n_tail); + if (sz_ + size < 128) { sz_ += size; return; } + n = size - n_tail; + nb = n >> 7; + p = (const uint8_t*) data + n_tail; + transform(block_, 1); + transform(p, nb); + n_tail = n & 0x7f; + memcpy(block_, &p[nb << 7], n_tail); + sz_ = n_tail; + iterations_ += (nb+1) << 7; + } + + /** + * Finanlise checksum, return hex string. + * @return str_t + */ + str_t final_data() + { + #if (defined (BYTE_ORDER)) && (defined (BIG_ENDIAN)) && ((BYTE_ORDER == BIG_ENDIAN)) + #define U32_B(x,b) *((b)+0)=(uint8_t)((x)); *((b)+1)=(uint8_t)((x)>>8); \ + *((b)+2)=(uint8_t)((x)>>16); *((b)+3)=(uint8_t)((x)>>24); + #else + #define U32_B(x,b) *((b)+3)=(uint8_t)((x)); *((b)+2)=(uint8_t)((x)>>8); \ + *((b)+1)=(uint8_t)((x)>>16); *((b)+0)=(uint8_t)((x)>>24); + #endif + unsigned nb, n; + uint64_t n_total; + nb = 1 + ((0x80-17) < (sz_ & 0x7f)); + n_total = (iterations_ + sz_) << 3; + n = nb << 7; + memset(block_ + sz_, 0, n - sz_); + block_[sz_] = 0x80; + U32_B(n_total, block_ + n-4); + transform(block_, nb); + std::basic_stringstream ss; // hex string + for (unsigned i = 0; i < 8; ++i) { + ss << std::hex << std::setfill('0') << std::setw(16) << (sum_[i]); + } + clear(); + return ss.str(); + #undef U32_B + } + +public: + + /** + * Calculates the SHA256 for a given string. + * @param const str_t & s + * @return str_t + */ + static str_t calculate(const str_t & s) + { + basic_sha512 r; + r.update(s.data(), s.length()); + return r.final_data(); + } + + /** + * Calculates the SHA256 for a given C-string. + * @param const char* s + * @return str_t + */ + static str_t calculate(const void* data, size_t size) + { basic_sha512 r; r.update(data, size); return r.final_data(); } + + /** + * Calculates the SHA256 for a stream. Returns an empty string on error. + * @param std::istream & is + * @return str_t + */ + static str_t calculate(std::istream & is) + { + basic_sha512 r; + char data[64]; + while(is.good() && is.read(data, sizeof(data)).good()) { + r.update(data, sizeof(data)); + } + if(!is.eof()) return str_t(); + if(is.gcount()) r.update(data, is.gcount()); + return r.final_data(); + } + + /** + * Calculates the SHA256 checksum for a given file, either read binary or as text. + * @param const str_t & path + * @param bool binary = true + * @return str_t + */ + static str_t file(const str_t & path, bool binary=true) + { + std::ifstream fs; + fs.open(path.c_str(), binary ? (std::ios::in|std::ios::binary) : (std::ios::in)); + str_t s = calculate(fs); + fs.close(); + return s; + } + +private: + + /** + * Performs the SHA256 transformation on a given block + * @param uint32_t *block + */ + void transform(const uint8_t *data, size_t size) + { + #define SR(x, n) (x >> n) + #define RR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) + #define RL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n))) + #define CH(x, y, z) ((x & y) ^ (~x & z)) + #define MJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) + #define F1(x) (RR(x, 28) ^ RR(x, 34) ^ RR(x, 39)) + #define F2(x) (RR(x, 14) ^ RR(x, 18) ^ RR(x, 41)) + #define F3(x) (RR(x, 1) ^ RR(x, 8) ^ SR(x, 7)) + #define F4(x) (RR(x, 19) ^ RR(x, 61) ^ SR(x, 6)) + #if (defined (BYTE_ORDER)) && (defined (BIG_ENDIAN)) && ((BYTE_ORDER == BIG_ENDIAN)) + #define B_U64(b,x) *(x)=((uint64_t)*((b)+0))|((uint64_t)*((b)+1)<<8)|\ + ((uint64_t)*((b)+2)<<16)|((uint64_t)*((b)+3)<<24)|((uint64_t)*((b)+4)<<32)|\ + ((uint64_t)*((b)+5)<<40)|((uint64_t)*((b)+6)<<48)|((uint64_t)*((b)+7)<<56); + #else + #define B_U64(b,x) *(x)=((uint64_t)*((b)+7))|((uint64_t)*((b)+6)<<8)|\ + ((uint64_t)*((b)+5)<<16)|((uint64_t)*((b)+4)<<24)|((uint64_t)*((b)+3)<<32)|\ + ((uint64_t)*((b)+2)<<40)|((uint64_t)*((b)+1)<<48)|((uint64_t)*((b)+0)<<56); + #endif + uint64_t t, u, v[8], w[80]; + const uint8_t *tblock; + unsigned j; + for(unsigned i = 0; i < size; ++i) { + tblock = data + (i << 7); + for(j = 0; j < 16; ++j) B_U64(&tblock[j<<3], &w[j]); + for(j = 16; j < 80; ++j) w[j] = F4(w[j-2]) + w[j-7] + F3(w[j-15]) + w[j-16]; + for(j = 0; j < 8; ++j) v[j] = sum_[j]; + for(j = 0; j < 80; ++j) { + t = v[7] + F2(v[4]) + CH(v[4], v[5], v[6]) + lut_[j] + w[j]; + u = F1(v[0]) + MJ(v[0], v[1], v[2]); v[7] = v[6]; v[6] = v[5]; v[5] = v[4]; + v[4] = v[3] + t; v[3] = v[2]; v[2] = v[1]; v[1] = v[0]; v[0] = t + u; + } + for(j = 0; j < 8; ++j) sum_[j] += v[j]; + } + #undef SR + #undef RR + #undef RL + #undef CH + #undef MJ + #undef F1 + #undef F2 + #undef F3 + #undef F4 + #undef B_U64 + } + +private: + + uint64_t iterations_; // Number of iterations + uint64_t sum_[8]; // Intermediate checksum buffer + unsigned sz_; // Number of currently stored bytes in the block + uint8_t block_[256]; + static const uint64_t lut_[80]; // Lookup table +}; + +template +const uint64_t basic_sha512::lut_[80] = { + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, + 0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, + 0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694, + 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, + 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70, + 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, + 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, + 0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, + 0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, + 0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, + 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 +}; + +}} + +namespace sw { + typedef detail::basic_sha512<> sha512; +} + +#endif \ No newline at end of file