diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a342391..fb8850f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependen option(OPT_BUILD_DISCORD_PRESENCE "Build the Discord Rich Presence module" ON) option(OPT_BUILD_FREQUENCY_MANAGER "Build the Frequency Manager module" ON) option(OPT_BUILD_RECORDER "Audio and baseband recorder" ON) +option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON) # Compiler arguments for each platform if (MSVC) @@ -145,6 +146,10 @@ if (OPT_BUILD_RECORDER) add_subdirectory("recorder") endif (OPT_BUILD_RECORDER) +if (OPT_BUILD_RIGCTL_SERVER) +add_subdirectory("rigctl_server") +endif (OPT_BUILD_RIGCTL_SERVER) + add_executable(sdrpp "src/main.cpp" "win32/resources.rc") target_link_libraries(sdrpp PRIVATE sdrpp_core) diff --git a/contributing.md b/contributing.md index 35e88a43..ac078cdf 100644 --- a/contributing.md +++ b/contributing.md @@ -121,5 +121,5 @@ Please follow this guide to properly format the JSON files for custom color maps # Best Practices * All additions and/or bug fixes to the core must not add additional dependencies. - -* Use VSCode for development, VS seems to cause issues. \ No newline at end of file +* Use VSCode for development, VS seems to cause issues. +* DO NOT use libboost for any code meant for this repository \ No newline at end of file diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 693f4628..5895bc02 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -48,6 +48,9 @@ if (MSVC) find_package(FFTW3f CONFIG REQUIRED) target_link_libraries(sdrpp_core PUBLIC FFTW3::fftw3f) + # WinSock2 + target_link_libraries(sdrpp_core PUBLIC wsock32 ws2_32) + else() find_package(PkgConfig) find_package(OpenGL REQUIRED) diff --git a/core/src/utils/networking.cpp b/core/src/utils/networking.cpp index efd1f31b..658affa5 100644 --- a/core/src/utils/networking.cpp +++ b/core/src/utils/networking.cpp @@ -1,2 +1,381 @@ +#pragma once #include +#include +namespace net { + +#ifdef _WIN32 + extern bool winsock_init = false; +#endif + + ConnClass::ConnClass(Socket sock) { + _sock = sock; + connectionOpen = true; + readWorkerThread = std::thread(&ConnClass::readWorker, this); + writeWorkerThread = std::thread(&ConnClass::writeWorker, this); + } + + ConnClass::~ConnClass() { + ConnClass::close(); + } + + void ConnClass::close() { + std::lock_guard lck(closeMtx); + // Set stopWorkers to true + { + std::lock_guard lck1(readQueueMtx); + std::lock_guard lck2(writeQueueMtx); + stopWorkers = true; + } + + // Notify the workers of the change + readQueueCnd.notify_all(); + writeQueueCnd.notify_all(); + + if (connectionOpen) { +#ifdef _WIN32 + closesocket(_sock); +#else + ::close(_sock); +#endif + } + + // Wait for the theads to terminate + if (readWorkerThread.joinable()) { readWorkerThread.join(); } + if (writeWorkerThread.joinable()) { writeWorkerThread.join(); } + + { + std::lock_guard lck(connectionOpenMtx); + connectionOpen = false; + } + connectionOpenCnd.notify_all(); + } + + bool ConnClass::isOpen() { + return connectionOpen; + } + + void ConnClass::waitForEnd() { + std::unique_lock lck(readQueueMtx); + connectionOpenCnd.wait(lck, [this](){ return !connectionOpen; }); + } + + int ConnClass::read(int count, uint8_t* buf) { + assert(connectionOpen); + std::lock_guard lck(readMtx); +#ifdef _WIN32 + int ret = recv(_sock, (char*)buf, count, 0); +#else + int ret = ::read(_sock, buf, count); +#endif + if (ret <= 0) { + { + std::lock_guard lck(connectionOpenMtx); + connectionOpen = false; + } + connectionOpenCnd.notify_all(); + } + return ret; + } + + bool ConnClass::write(int count, uint8_t* buf) { + assert(connectionOpen); + std::lock_guard lck(writeMtx); +#ifdef _WIN32 + int ret = send(_sock, (char*)buf, count, 0); +#else + int ret = ::write(_sock, buf, count); +#endif + if (ret <= 0) { + { + std::lock_guard lck(connectionOpenMtx); + connectionOpen = false; + } + connectionOpenCnd.notify_all(); + } + return (ret > 0); + } + + void ConnClass::readAsync(int count, uint8_t* buf, void (*handler)(int count, uint8_t* buf, void* ctx), void* ctx) { + assert(connectionOpen); + // Create entry + ConnReadEntry entry; + entry.count = count; + entry.buf = buf; + entry.handler = handler; + entry.ctx = ctx; + + // Add entry to queue + { + std::lock_guard lck(readQueueMtx); + readQueue.push_back(entry); + } + + // Notify read worker + readQueueCnd.notify_all(); + } + + void ConnClass::writeAsync(int count, uint8_t* buf) { + assert(connectionOpen); + // Create entry + ConnWriteEntry entry; + entry.count = count; + entry.buf = buf; + + // Add entry to queue + { + std::lock_guard lck(writeQueueMtx); + writeQueue.push_back(entry); + } + + // Notify write worker + writeQueueCnd.notify_all(); + } + + void ConnClass::readWorker() { + while (true) { + // Wait for wakeup and exit if it's for terminating the thread + std::unique_lock lck(readQueueMtx); + readQueueCnd.wait(lck, [this](){ return (readQueue.size() > 0 || stopWorkers); }); + if (stopWorkers || !connectionOpen) { return; } + + // Pop first element off the list + ConnReadEntry entry = readQueue[0]; + readQueue.erase(readQueue.begin()); + lck.unlock(); + + // Read from socket and send data to the handler + int ret = read(entry.count, entry.buf); + if (ret <= 0) { + { + std::lock_guard lck(connectionOpenMtx); + connectionOpen = false; + } + connectionOpenCnd.notify_all(); + return; + } + entry.handler(ret, entry.buf, entry.ctx); + } + } + + void ConnClass::writeWorker() { + while (true) { + // Wait for wakeup and exit if it's for terminating the thread + std::unique_lock lck(writeQueueMtx); + writeQueueCnd.wait(lck, [this](){ return (writeQueue.size() > 0 || stopWorkers); }); + if (stopWorkers || !connectionOpen) { return; } + + // Pop first element off the list + ConnWriteEntry entry = writeQueue[0]; + writeQueue.erase(writeQueue.begin()); + lck.unlock(); + + // Write to socket + if (!write(entry.count, entry.buf)) { + { + std::lock_guard lck(connectionOpenMtx); + connectionOpen = false; + } + connectionOpenCnd.notify_all(); + return; + } + } + } + + + ListenerClass::ListenerClass(Socket listenSock) { + sock = listenSock; + listening = true; + acceptWorkerThread = std::thread(&ListenerClass::worker, this); + } + + ListenerClass::~ListenerClass() { + close(); + } + + Conn ListenerClass::accept() { + assert(listening); + std::lock_guard lck(acceptMtx); + Socket _sock; + + // Accept socket + _sock = ::accept(sock, NULL, NULL); + if (_sock < 0) { + listening = false; + throw std::runtime_error("Could not bind socket"); + return NULL; + } + + return Conn(new ConnClass(_sock)); + } + + void ListenerClass::acceptAsync(void (*handler)(Conn conn, void* ctx), void* ctx) { + assert(listening); + // Create entry + ListenerAcceptEntry entry; + entry.handler = handler; + entry.ctx = ctx; + + // Add entry to queue + { + std::lock_guard lck(acceptQueueMtx); + acceptQueue.push_back(entry); + } + + // Notify write worker + acceptQueueCnd.notify_all(); + } + + void ListenerClass::close() { + { + std::lock_guard lck(acceptQueueMtx); + stopWorker = true; + } + + if (listening) { +#ifdef _WIN32 + closesocket(sock); +#else + ::close(sock); +#endif + } + + acceptQueueCnd.notify_all(); + if (acceptWorkerThread.joinable()) { acceptWorkerThread.join(); } + + + + listening = false; + } + + bool ListenerClass::isListening() { + return listening; + } + + void ListenerClass::worker() { + while (true) { + // Wait for wakeup and exit if it's for terminating the thread + std::unique_lock lck(acceptQueueMtx); + acceptQueueCnd.wait(lck, [this](){ return (acceptQueue.size() > 0 || stopWorker); }); + if (stopWorker || !listening) { return; } + + // Pop first element off the list + ListenerAcceptEntry entry = acceptQueue[0]; + acceptQueue.erase(acceptQueue.begin()); + lck.unlock(); + + // Read from socket and send data to the handler + try { + Conn client = accept(); + if (!client) { + listening = false; + return; + } + entry.handler(std::move(client), entry.ctx); + } + catch (std::exception e) { + listening = false; + return; + } + } + } + + + Conn connect(Protocol proto, std::string host, uint16_t port) { + Socket sock; + +#ifdef _WIN32 + // Initilize WinSock2 + if (!winsock_init) { + WSADATA wsa; + if (WSAStartup(MAKEWORD(2,2),&wsa)) { + throw std::runtime_error("Could not initialize WinSock2"); + return NULL; + } + winsock_init = true; + } + assert(winsock_init); +#endif + + // Create a socket + sock = socket(AF_INET, SOCK_STREAM, (proto == PROTO_TCP) ? IPPROTO_TCP : IPPROTO_UDP); + if (sock < 0) { + throw std::runtime_error("Could not create socket"); + return NULL; + } + + // Get address from hostname/ip + hostent* remoteHost = gethostbyname(host.c_str()); + if (remoteHost == NULL || remoteHost->h_addr_list[0] == NULL) { + throw std::runtime_error("Could get address from host"); + return NULL; + } + uint32_t* naddr = (uint32_t*)remoteHost->h_addr_list[0]; + + // Create host address + struct sockaddr_in addr; + addr.sin_addr.s_addr = *naddr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + // Connect to host + if (::connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + throw std::runtime_error("Could not connect to host"); + return NULL; + } + + return Conn(new ConnClass(sock)); + } + + Listener listen(Protocol proto, std::string host, uint16_t port) { + Socket listenSock; + +#ifdef _WIN32 + // Initilize WinSock2 + if (!winsock_init) { + WSADATA wsa; + if (WSAStartup(MAKEWORD(2,2),&wsa)) { + throw std::runtime_error("Could not initialize WinSock2"); + return NULL; + } + winsock_init = true; + } + assert(winsock_init); +#endif + + // Create a socket + listenSock = socket(AF_INET, SOCK_STREAM, (proto == PROTO_TCP) ? IPPROTO_TCP : IPPROTO_UDP); + if (listenSock < 0) { + throw std::runtime_error("Could not create socket"); + return NULL; + } + + // Get address from hostname/ip + hostent* remoteHost = gethostbyname(host.c_str()); + if (remoteHost == NULL || remoteHost->h_addr_list[0] == NULL) { + throw std::runtime_error("Could get address from host"); + return NULL; + } + uint32_t* naddr = (uint32_t*)remoteHost->h_addr_list[0]; + + // Create host address + struct sockaddr_in addr; + addr.sin_addr.s_addr = *naddr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + // Bind socket + if (bind(listenSock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + throw std::runtime_error("Could not bind socket"); + return NULL; + } + + // Listen + if (::listen(listenSock, SOMAXCONN) != 0) { + throw std::runtime_error("Could not listen"); + return NULL; + } + + return Listener(new ListenerClass(listenSock)); + } +} \ No newline at end of file diff --git a/core/src/utils/networking.h b/core/src/utils/networking.h index b7486d6d..e4f61a09 100644 --- a/core/src/utils/networking.h +++ b/core/src/utils/networking.h @@ -1,66 +1,128 @@ #pragma once +#include #include #include -#include -#include #include +#include +#include +#include +#include -struct TCPAsyncRead { - int timeout; - int count; - void (*handler)(int count, char* data, void* ctx); - void* ctx; -}; +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#include +#endif -struct TCPAsyncWrite { - int timeoutMs; - int count; - char* data; -}; +namespace net { +#ifdef _WIN32 + typedef SOCKET Socket; +#else + typedef int Socket; +#endif -enum { - NET_ERR_OK = 0, - NET_ERR_TIMEOUT = -1, - NET_ERR_SYSTEM = -2 -}; + enum Protocol { + PROTO_TCP, + PROTO_UDP + }; -class TCPClient { -public: - TCPClient(); - ~TCPClient(); + struct ConnReadEntry { + int count; + uint8_t* buf; + void (*handler)(int count, uint8_t* buf, void* ctx); + void* ctx; + }; - bool connect(std::string host, int port); - bool disconnect(); + struct ConnWriteEntry { + int count; + uint8_t* buf; + }; - int enableAsync(); - int disableAsync(); + class ConnClass { + public: + ConnClass(Socket sock); + ~ConnClass(); - bool isAsync(); + void close(); + bool isOpen(); + void waitForEnd(); - int read(char* data, int count, int timeout = -1); - int write(char* data, int count, int timeout = -1); + int read(int count, uint8_t* buf); + bool write(int count, uint8_t* buf); + void readAsync(int count, uint8_t* buf, void (*handler)(int count, uint8_t* buf, void* ctx), void* ctx); + void writeAsync(int count, uint8_t* buf); - int asyncRead(int count, void (*handler)(int count, char* data, void* ctx), int timeout = -1); - int asyncWrite(char* data, int count, int timeout = -1); + private: + void readWorker(); + void writeWorker(); - bool isOpen(); + bool stopWorkers = false; + bool connectionOpen = false; - int close(); + std::mutex readMtx; + std::mutex writeMtx; + std::mutex readQueueMtx; + std::mutex writeQueueMtx; + std::mutex connectionOpenMtx; + std::mutex closeMtx; + std::condition_variable readQueueCnd; + std::condition_variable writeQueueCnd; + std::condition_variable connectionOpenCnd; + std::vector readQueue; + std::vector writeQueue; + std::thread readWorkerThread; + std::thread writeWorkerThread; -private: - void readWorker(); - void writeWorker(); + Socket _sock; - std::mutex readQueueMtx; - std::vector readQueue; + }; - std::mutex writeQueueMtx; - std::vector writeQueue; + typedef std::unique_ptr Conn; - std::thread readWorkerThread; - std::thread writeWorkerThread; + struct ListenerAcceptEntry { + void (*handler)(Conn conn, void* ctx); + void* ctx; + }; - bool open = false; - bool async = false; + class ListenerClass { + public: + ListenerClass(Socket listenSock); + ~ListenerClass(); -}; \ No newline at end of file + Conn accept(); + void acceptAsync(void (*handler)(Conn conn, void* ctx), void* ctx); + + void close(); + bool isListening(); + + private: + void worker(); + + bool listening = false; + bool stopWorker = false; + + std::mutex acceptMtx; + std::mutex acceptQueueMtx; + std::condition_variable acceptQueueCnd; + std::vector acceptQueue; + std::thread acceptWorkerThread; + + Socket sock; + + }; + + typedef std::unique_ptr Listener; + + Conn connect(Protocol proto, std::string host, uint16_t port); + Listener listen(Protocol proto, std::string host, uint16_t port); + +#ifdef _WIN32 + extern bool winsock_init; +#endif +} \ No newline at end of file diff --git a/demo_module/CMakeLists.txt b/demo_module/CMakeLists.txt index c03009f9..f3e17c57 100644 --- a/demo_module/CMakeLists.txt +++ b/demo_module/CMakeLists.txt @@ -4,15 +4,17 @@ project(demo) if (MSVC) set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup -fPIC") + set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup") else () - set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fPIC") + set(CMAKE_CXX_FLAGS "-O3 -std=c++17") endif () file(GLOB SRC "src/*.cpp") + include_directories("src/") add_library(demo SHARED ${SRC}) +target_link_libraries(demo PRIVATE sdrpp_core) set_target_properties(demo PROPERTIES PREFIX "") # Install directives diff --git a/demo_module/src/main.cpp b/demo_module/src/main.cpp index 6a7f803a..daf10a31 100644 --- a/demo_module/src/main.cpp +++ b/demo_module/src/main.cpp @@ -18,7 +18,7 @@ public: } ~DemoModule() { - + gui::menu.removeEntry(name); } void enable() { diff --git a/recorder/src/main.cpp b/recorder/src/main.cpp index 873e7c36..69a5b9a5 100644 --- a/recorder/src/main.cpp +++ b/recorder/src/main.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #define CONCAT(a, b) ((std::string(a) + b).c_str()) SDRPP_MOD_INFO { @@ -55,7 +57,7 @@ public: // Create config if it doesn't exist if (!config.conf.contains(name)) { - config.conf[name]["mode"] = 1; + config.conf[name]["mode"] = RECORDER_MODE_AUDIO; config.conf[name]["recPath"] = "%ROOT%/recordings"; config.conf[name]["audioStream"] = "Radio"; created = true; @@ -85,12 +87,15 @@ public: refreshStreams(); gui::menu.registerEntry(name, menuHandler, this); + core::modComManager.registerInterface("recorder", name, moduleInterfaceHandler, this); } ~RecorderModule() { + core::modComManager.unregisterInterface(name); + // Stop recording if (recording) { - if (recMode == 1) { + if (recMode == RECORDER_MODE_AUDIO) { audioSplit.unbindStream(&audioHandlerStream); audioHandler.stop(); audioWriter->close(); @@ -171,15 +176,15 @@ private: if (_this->recording) { style::beginDisabled(); } ImGui::BeginGroup(); ImGui::Columns(2, CONCAT("AirspyGainModeColumns##_", _this->name), false); - if (ImGui::RadioButton(CONCAT("Baseband##_recmode_", _this->name), _this->recMode == 0)) { - _this->recMode = 0; + if (ImGui::RadioButton(CONCAT("Baseband##_recmode_", _this->name), _this->recMode == RECORDER_MODE_BASEBAND)) { + _this->recMode = RECORDER_MODE_BASEBAND; config.acquire(); config.conf[_this->name]["mode"] = _this->recMode; config.release(true); } ImGui::NextColumn(); - if (ImGui::RadioButton(CONCAT("Audio##_recmode_", _this->name), _this->recMode == 1)) { - _this->recMode = 1; + if (ImGui::RadioButton(CONCAT("Audio##_recmode_", _this->name), _this->recMode == RECORDER_MODE_AUDIO)) { + _this->recMode = RECORDER_MODE_AUDIO; config.acquire(); config.conf[_this->name]["mode"] = _this->recMode; config.release(true); @@ -198,7 +203,7 @@ private: } // Mode specific menu - if (_this->recMode == 1) { + if (_this->recMode == RECORDER_MODE_AUDIO) { _this->audioMenu(menuColumnWidth); } else { @@ -210,29 +215,15 @@ private: if (!folderSelect.pathIsValid()) { style::beginDisabled(); } if (!recording) { if (ImGui::Button(CONCAT("Record##_recorder_rec_", name), ImVec2(menuColumnWidth, 0))) { - samplesWritten = 0; - std::string expandedPath = expandString(folderSelect.path + genFileName("/baseband_", false)); - sampleRate = sigpath::signalPath.getSampleRate(); - basebandWriter = new WavWriter(expandedPath, 16, 2, sigpath::signalPath.getSampleRate()); - if (basebandWriter->isOpen()) { - basebandHandler.start(); - sigpath::signalPath.bindIQStream(&basebandStream); - recording = true; - spdlog::info("Recording to '{0}'", expandedPath); - } - else { - spdlog::error("Could not create '{0}'", expandedPath); - } + std::lock_guard lck(recMtx); + startRecording(); } ImGui::TextColored(ImGui::GetStyleColorVec4(ImGuiCol_Text), "Idle --:--:--"); } else { if (ImGui::Button(CONCAT("Stop##_recorder_rec_", name), ImVec2(menuColumnWidth, 0))) { - recording = false; - sigpath::signalPath.unbindIQStream(&basebandStream); - basebandHandler.stop(); - basebandWriter->close(); - delete basebandWriter; + std::lock_guard lck(recMtx); + stopRecording(); } uint64_t seconds = samplesWritten / (uint64_t)sampleRate; time_t diff = seconds; @@ -279,29 +270,15 @@ private: if (!folderSelect.pathIsValid() || selectedStreamName == "") { style::beginDisabled(); } if (!recording) { if (ImGui::Button(CONCAT("Record##_recorder_rec_", name), ImVec2(menuColumnWidth, 0))) { - samplesWritten = 0; - std::string expandedPath = expandString(folderSelect.path + genFileName("/audio_", true, selectedStreamName)); - sampleRate = sigpath::sinkManager.getStreamSampleRate(selectedStreamName); - audioWriter = new WavWriter(expandedPath, 16, 2, sigpath::sinkManager.getStreamSampleRate(selectedStreamName)); - if (audioWriter->isOpen()) { - recording = true; - audioHandler.start(); - audioSplit.bindStream(&audioHandlerStream); - spdlog::info("Recording to '{0}'", expandedPath); - } - else { - spdlog::error("Could not create '{0}'", expandedPath); - } + std::lock_guard lck(recMtx); + startRecording(); } ImGui::TextColored(ImGui::GetStyleColorVec4(ImGuiCol_Text), "Idle --:--:--"); } else { if (ImGui::Button(CONCAT("Stop##_recorder_rec_", name), ImVec2(menuColumnWidth, 0))) { - recording = false; - audioSplit.unbindStream(&audioHandlerStream); - audioHandler.stop(); - audioWriter->close(); - delete audioWriter; + std::lock_guard lck(recMtx); + stopRecording(); } uint64_t seconds = samplesWritten / (uint64_t)sampleRate; time_t diff = seconds; @@ -325,6 +302,76 @@ private: _this->samplesWritten += count; } + static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) { + RecorderModule* _this = (RecorderModule*)ctx; + std::lock_guard lck(_this->recMtx); + if (code == RECORDER_IFACE_CMD_GET_MODE) { + int* _out = (int*)out; + *_out = _this->recMode; + } + else if (code == RECORDER_IFACE_CMD_SET_MODE) { + if (_this->recording) { return; } + int* _in = (int*)in; + _this->recMode = std::clamp(*_in, 0, 1); + } + else if (code == RECORDER_IFACE_CMD_START) { + if (!_this->recording) { _this->startRecording(); } + } + else if (code == RECORDER_IFACE_CMD_STOP) { + if (_this->recording) { _this->stopRecording(); } + } + } + + void startRecording() { + if (recMode == RECORDER_MODE_BASEBAND) { + samplesWritten = 0; + std::string expandedPath = expandString(folderSelect.path + genFileName("/baseband_", false)); + sampleRate = sigpath::signalPath.getSampleRate(); + basebandWriter = new WavWriter(expandedPath, 16, 2, sigpath::signalPath.getSampleRate()); + if (basebandWriter->isOpen()) { + basebandHandler.start(); + sigpath::signalPath.bindIQStream(&basebandStream); + recording = true; + spdlog::info("Recording to '{0}'", expandedPath); + } + else { + spdlog::error("Could not create '{0}'", expandedPath); + } + } + else if (recMode == RECORDER_MODE_AUDIO) { + samplesWritten = 0; + std::string expandedPath = expandString(folderSelect.path + genFileName("/audio_", true, selectedStreamName)); + sampleRate = sigpath::sinkManager.getStreamSampleRate(selectedStreamName); + audioWriter = new WavWriter(expandedPath, 16, 2, sigpath::sinkManager.getStreamSampleRate(selectedStreamName)); + if (audioWriter->isOpen()) { + recording = true; + audioHandler.start(); + audioSplit.bindStream(&audioHandlerStream); + spdlog::info("Recording to '{0}'", expandedPath); + } + else { + spdlog::error("Could not create '{0}'", expandedPath); + } + } + } + + void stopRecording() { + if (recMode == 0) { + recording = false; + sigpath::signalPath.unbindIQStream(&basebandStream); + basebandHandler.stop(); + basebandWriter->close(); + delete basebandWriter; + } + else if (recMode == 1) { + recording = false; + audioSplit.unbindStream(&audioHandlerStream); + audioHandler.stop(); + audioWriter->close(); + delete audioWriter; + } + } + std::string name; bool enabled = true; @@ -340,6 +387,8 @@ private: float lvlR = -90.0f; dsp::stream dummyStream; + + std::mutex recMtx; FolderSelect folderSelect;