diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index 3f3d2a46..6570de8a 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -36,14 +36,14 @@ jobs: run: 7z x ${{runner.workspace}}/SDRPlay.zip -o"C:/Program Files/" - name: Install vcpkg dependencies - run: vcpkg install fftw3:x64-windows glew:x64-windows glfw3:x64-windows + run: vcpkg install fftw3:x64-windows glew:x64-windows glfw3:x64-windows portaudio:x64-windows - name: Install rtaudio run: git clone https://github.com/thestk/rtaudio ; cd rtaudio ; mkdir build ; cd build ; cmake .. ; cmake --build . --config Release ; cmake --install . - name: Prepare CMake working-directory: ${{runner.workspace}}/build - run: cmake "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON + run: cmake "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON - name: Build working-directory: ${{runner.workspace}}/build @@ -73,7 +73,7 @@ jobs: - name: Prepare CMake working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_AIRSPYHF_SOURCE=OFF -DOPT_BUILD_PLUTOSDR_SOURCE=OFF -DOPT_BUILD_SOAPY_SOURCE=OFF -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON + run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_AIRSPYHF_SOURCE=OFF -DOPT_BUILD_PLUTOSDR_SOURCE=OFF -DOPT_BUILD_SOAPY_SOURCE=OFF -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON - name: Build working-directory: ${{runner.workspace}}/build diff --git a/CMakeLists.txt b/CMakeLists.txt index fb8850f4..492f37dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Depedencies: lib # Sinks option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Depedencies: rtaudio)" ON) option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Depedencies: portaudio)" OFF) +option(OPT_BUILD_NEW_PORTAUDIO_SINK "Build the new PortAudio Sink Module (Depedencies: portaudio)" OFF) # Decoders option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF) @@ -114,6 +115,10 @@ if (OPT_BUILD_PORTAUDIO_SINK) add_subdirectory("portaudio_sink") endif (OPT_BUILD_PORTAUDIO_SINK) +if (OPT_BUILD_NEW_PORTAUDIO_SINK) +add_subdirectory("new_portaudio_sink") +endif (OPT_BUILD_NEW_PORTAUDIO_SINK) + # Decoders if (OPT_BUILD_FALCON9_DECODER) @@ -177,7 +182,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") add_custom_target(do_always ALL cp \"$/libsdrpp_core.dylib\" \"$\") endif () -# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64" +# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" # Install directives install(TARGETS sdrpp DESTINATION bin) diff --git a/core/src/dsp/buffer.h b/core/src/dsp/buffer.h index 8a3f33af..225cd5f4 100644 --- a/core/src/dsp/buffer.h +++ b/core/src/dsp/buffer.h @@ -264,6 +264,11 @@ namespace dsp { generic_block>::tempStart(); } + void flush() { + std::unique_lock lck(bufMtx); + readCur = writeCur; + } + int run() { // Wait for data int count = _in->read(); diff --git a/core/src/gui/main_window.cpp b/core/src/gui/main_window.cpp index c4521265..cc405f90 100644 --- a/core/src/gui/main_window.cpp +++ b/core/src/gui/main_window.cpp @@ -344,18 +344,21 @@ void MainWindow::draw() { if (playing) { ImGui::PushID(ImGui::GetID("sdrpp_stop_btn")); if (ImGui::ImageButton(icons::STOP, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(GLFW_KEY_END, false)) { - sigpath::sourceManager.stop(); playing = false; + onPlayStateChange.emit(false); + sigpath::sourceManager.stop(); + sigpath::signalPath.inputBuffer.flush(); } ImGui::PopID(); } else { // TODO: Might need to check if there even is a device ImGui::PushID(ImGui::GetID("sdrpp_play_btn")); if (ImGui::ImageButton(icons::PLAY, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(GLFW_KEY_END, false)) { + sigpath::signalPath.inputBuffer.flush(); sigpath::sourceManager.start(); - // TODO: tune in module instead sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency()); playing = true; + onPlayStateChange.emit(true); } ImGui::PopID(); } diff --git a/core/src/gui/main_window.h b/core/src/gui/main_window.h index e6ad6765..2e3a9416 100644 --- a/core/src/gui/main_window.h +++ b/core/src/gui/main_window.h @@ -33,6 +33,8 @@ public: bool lockWaterfallControls = false; + Event onPlayStateChange; + private: void generateFFTWindow(int win, int size); static void fftHandler(dsp::complex_t* samples, int count, void* ctx); diff --git a/docker_builds/debian_bullseye/do_build.sh b/docker_builds/debian_bullseye/do_build.sh index 90d56025..3eba9cdd 100644 --- a/docker_builds/debian_bullseye/do_build.sh +++ b/docker_builds/debian_bullseye/do_build.sh @@ -4,7 +4,8 @@ cd /root # Install dependencies and tools apt update -apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget +apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ + libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev # Install SDRPlay libraries wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run @@ -16,7 +17,7 @@ cp inc/* /usr/include/ cd SDRPlusPlus mkdir build cd build -cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON +cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON make -j2 cd .. diff --git a/docker_builds/debian_buster/do_build.sh b/docker_builds/debian_buster/do_build.sh index 7f9e145a..4c378b6f 100644 --- a/docker_builds/debian_buster/do_build.sh +++ b/docker_builds/debian_buster/do_build.sh @@ -4,7 +4,8 @@ cd /root # Install dependencies and tools apt update -apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libglew-dev libvolk1-dev libsoapysdr-dev libairspyhf-dev libairspy-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget +apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libglew-dev libvolk1-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ + libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev # Install SDRPlay libraries wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run @@ -16,7 +17,7 @@ cp inc/* /usr/include/ cd SDRPlusPlus mkdir build cd build -cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON +cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON make -j2 cd .. diff --git a/docker_builds/debian_sid/do_build.sh b/docker_builds/debian_sid/do_build.sh index 90d56025..3eba9cdd 100644 --- a/docker_builds/debian_sid/do_build.sh +++ b/docker_builds/debian_sid/do_build.sh @@ -4,7 +4,8 @@ cd /root # Install dependencies and tools apt update -apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget +apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ + libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev # Install SDRPlay libraries wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run @@ -16,7 +17,7 @@ cp inc/* /usr/include/ cd SDRPlusPlus mkdir build cd build -cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON +cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON make -j2 cd .. diff --git a/docker_builds/ubuntu_focal/do_build.sh b/docker_builds/ubuntu_focal/do_build.sh index 90d56025..3eba9cdd 100644 --- a/docker_builds/ubuntu_focal/do_build.sh +++ b/docker_builds/ubuntu_focal/do_build.sh @@ -4,7 +4,8 @@ cd /root # Install dependencies and tools apt update -apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget +apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ + libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev # Install SDRPlay libraries wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run @@ -16,7 +17,7 @@ cp inc/* /usr/include/ cd SDRPlusPlus mkdir build cd build -cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON +cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON make -j2 cd .. diff --git a/docker_builds/ubuntu_groovy/do_build.sh b/docker_builds/ubuntu_groovy/do_build.sh index 90d56025..c0403b91 100644 --- a/docker_builds/ubuntu_groovy/do_build.sh +++ b/docker_builds/ubuntu_groovy/do_build.sh @@ -4,7 +4,8 @@ cd /root # Install dependencies and tools apt update -apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget +apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ + libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev # Install SDRPlay libraries wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run @@ -16,7 +17,7 @@ cp inc/* /usr/include/ cd SDRPlusPlus mkdir build cd build -cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON +cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON make -j2 cd .. diff --git a/docker_builds/ubuntu_hirsute/do_build.sh b/docker_builds/ubuntu_hirsute/do_build.sh index 90d56025..3eba9cdd 100644 --- a/docker_builds/ubuntu_hirsute/do_build.sh +++ b/docker_builds/ubuntu_hirsute/do_build.sh @@ -4,7 +4,8 @@ cd /root # Install dependencies and tools apt update -apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget +apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \ + libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev # Install SDRPlay libraries wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run @@ -16,7 +17,7 @@ cp inc/* /usr/include/ cd SDRPlusPlus mkdir build cd build -cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON +cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON make -j2 cd .. diff --git a/new_portaudio_sink/CMakeLists.txt b/new_portaudio_sink/CMakeLists.txt new file mode 100644 index 00000000..2105577a --- /dev/null +++ b/new_portaudio_sink/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.13) +project(new_portaudio_sink) + +if (MSVC) + set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") +else() + set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive") +endif (MSVC) + +file(GLOB SRC "src/*.cpp") + +include_directories("src/") + +add_library(new_portaudio_sink SHARED ${SRC}) +target_link_libraries(new_portaudio_sink PRIVATE sdrpp_core) +set_target_properties(new_portaudio_sink PROPERTIES PREFIX "") + +if (MSVC) + find_package(portaudio CONFIG REQUIRED) + target_link_libraries(new_portaudio_sink PUBLIC portaudio) +else (MSVC) + find_package(PkgConfig) + + pkg_check_modules(PORTAUDIO REQUIRED portaudio-2.0) + + target_include_directories(new_portaudio_sink PUBLIC ${PORTAUDIO_INCLUDE_DIRS}) + + target_link_directories(new_portaudio_sink PUBLIC ${PORTAUDIO_LIBRARY_DIRS}) + + target_link_libraries(new_portaudio_sink PUBLIC ${PORTAUDIO_LIBRARIES}) + +endif (MSVC) + +# Install directives +install(TARGETS new_portaudio_sink DESTINATION lib/sdrpp/plugins) \ No newline at end of file diff --git a/new_portaudio_sink/src/main.cpp b/new_portaudio_sink/src/main.cpp new file mode 100644 index 00000000..8e7b1cfd --- /dev/null +++ b/new_portaudio_sink/src/main.cpp @@ -0,0 +1,442 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONCAT(a, b) ((std::string(a) + b).c_str()) + +#define BLOCK_SIZE_DIVIDER 60 +#define AUDIO_LATENCY 1.0 / 60.0 + +SDRPP_MOD_INFO { + /* Name: */ "new_portaudio_sink", + /* Description: */ "Audio sink module for SDR++", + /* Author: */ "Ryzerth;Maxime Biette", + /* Version: */ 0, 1, 0, + /* Max instances */ 1 +}; + +ConfigManager config; + +class AudioSink : SinkManager::Sink { +public: + struct AudioDevice_t { + const PaDeviceInfo* deviceInfo; + const PaHostApiInfo* hostApiInfo; + PaDeviceIndex id; + int defaultSrId; + PaStreamParameters outputParams; + std::vector sampleRates; + std::string sampleRatesTxt; + }; + + AudioSink(SinkManager::Stream* stream, std::string streamName) { + _stream = stream; + _streamName = streamName; + + // Create config if it doesn't exist + config.acquire(); + if (!config.conf.contains(_streamName)) { + config.conf[_streamName]["device"] = ""; + config.conf[_streamName]["devices"] = json::object(); + } + std::string selected = config.conf[_streamName]["device"]; + config.release(true); + + // Register the play state handler + playStateHandler.handler = playStateChangeHandler; + playStateHandler.ctx = this; + gui::mainWindow.onPlayStateChange.bindHandler(&playStateHandler); + + // Initialize DSP blocks + packer.init(_stream->sinkOut, 1024); + s2m.init(&packer.out); + + // Refresh devices and select the one from the config + refreshDevices(); + selectDevByName(selected); + } + + ~AudioSink() { + stop(); + gui::mainWindow.onPlayStateChange.unbindHandler(&playStateHandler); + } + + void start() { + if (running || selectedDevName.empty()) { return; } + + // Get device and samplerate + AudioDevice_t& dev = devices[deviceNames[devId]]; + double sampleRate = dev.sampleRates[srId]; + int blockSize = sampleRate / BLOCK_SIZE_DIVIDER; + + // Set the SDR++ stream sample rate + _stream->setSampleRate(sampleRate); + + // Update the block size on the packer + packer.setSampleCount(blockSize); + + // Clear read stop signals + packer.out.clearReadStop(); + s2m.out.clearReadStop(); + + // Open the stream + PaError err; + if (dev.deviceInfo->maxOutputChannels == 1) { + packer.start(); + s2m.start(); + stereo = false; + err = Pa_OpenStream(&devStream, NULL, &dev.outputParams, sampleRate, blockSize, paNoFlag, _mono_cb, this); + } + else { + packer.start(); + stereo = true; + err = Pa_OpenStream(&devStream, NULL, &dev.outputParams, sampleRate, blockSize, paNoFlag, _stereo_cb, this); + } + + // In case of error, abort + if (err) { + spdlog::error("PortAudio error {0}: {1}", err, Pa_GetErrorText(err)); + return; + } + + spdlog::info("Starting PortAudio stream at {0} S/s", sampleRate); + + // Start stream + Pa_StartStream(devStream); + + running = true; + } + + void stop() { + if (!running || selectedDevName.empty()) { return; } + + // Send stop signal to the streams + packer.out.stopReader(); + s2m.out.stopReader(); + + // Stop DSP + packer.stop(); + s2m.stop(); + + // Stop stream + Pa_AbortStream(devStream); + + // Close the stream + Pa_CloseStream(devStream); + + running = false; + } + + void menuHandler() { + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + // Select device + ImGui::SetNextItemWidth(menuWidth); + if (ImGui::Combo("##audio_sink_dev_sel", &devId, deviceNamesTxt.c_str())) { + selectDevByName(deviceNames[devId]); + stop(); + start(); + if (selectedDevName != "") { + config.acquire(); + config.conf[_streamName]["device"] = selectedDevName; + config.release(true); + } + } + + // Select sample rate + ImGui::SetNextItemWidth(menuWidth); + if (ImGui::Combo("##audio_sink_sr_sel", &srId, selectedDev.sampleRatesTxt.c_str())) { + stop(); + start(); + if (selectedDevName != "") { + config.acquire(); + config.conf[_streamName]["devices"][selectedDevName] = selectedDev.sampleRates[srId]; + config.release(true); + } + } + } + + int devId = 0; + int srId = 0; + bool stereo = false; + +private: + static void playStateChangeHandler(bool newState, void* ctx) { + AudioSink* _this = (AudioSink*)ctx; + + // Wake up reader to send nulls instead of data in preparation for shutoff + if (newState) { + if (_this->stereo) { + _this->packer.out.stopReader(); + } + else { + _this->s2m.out.stopReader(); + } + } + else { + if (_this->stereo) { + _this->packer.out.clearReadStop(); + } + else { + _this->s2m.out.clearReadStop(); + } + } + } + + void refreshDevices() { + // Clear current list + devices.clear(); + deviceNames.clear(); + deviceNamesTxt.clear(); + + // Get number of devices + int devCount = Pa_GetDeviceCount(); + PaStreamParameters outputParams; + char buffer[128]; + + for (int i = 0; i < devCount; i++) { + AudioDevice_t dev; + + // Get device info + dev.deviceInfo = Pa_GetDeviceInfo(i); + dev.hostApiInfo = Pa_GetHostApiInfo(dev.deviceInfo->hostApi); + dev.id = i; + + // Check if device is usable + if (dev.deviceInfo->maxOutputChannels == 0) { continue; } +#ifdef _WIN32 + // On Windows, use only WASAPI + if (dev.hostApiInfo->type == paMME || dev.hostApiInfo->type == paWDMKS) { continue; } +#endif + // Zero out output params + dev.outputParams.device = i; + dev.outputParams.sampleFormat = paFloat32; + dev.outputParams.suggestedLatency = std::min(AUDIO_LATENCY, dev.deviceInfo->defaultLowOutputLatency); + dev.outputParams.channelCount = std::min(dev.deviceInfo->maxOutputChannels, 2); + dev.outputParams.hostApiSpecificStreamInfo = NULL; + + // List available sample rates + for (int sr = 12000; sr < 200000; sr += 12000) { + if (Pa_IsFormatSupported(NULL, &dev.outputParams, sr) != paFormatIsSupported) { continue; } + dev.sampleRates.push_back(sr); + } + for (int sr = 11025; sr < 192000; sr += 11025) { + if (Pa_IsFormatSupported(NULL, &dev.outputParams, sr) != paFormatIsSupported) { continue; } + dev.sampleRates.push_back(sr); + } + + // If no sample rates are supported, cancel adding device + if (dev.sampleRates.empty()) { + continue; + } + + // Sort sample rate list + std::sort(dev.sampleRates.begin(), dev.sampleRates.end(), [](double a, double b) { return (a < b); }); + + // Generate text list for UI + int srId = 0; + int _48kId = -1; + for (auto sr : dev.sampleRates) { + sprintf(buffer, "%d", (int)sr); + dev.sampleRatesTxt += buffer; + dev.sampleRatesTxt += '\0'; + + // Save ID of the default sample rate and 48KHz + if (sr == dev.deviceInfo->defaultSampleRate) { dev.defaultSrId = srId; } + if (sr == 48000.0) { _48kId = srId; } + srId++; + } + + // If a 48KHz option was found, use it instead of the default + if (_48kId >= 0) { dev.defaultSrId = _48kId; } + + std::string apiName = dev.hostApiInfo->name; + +#ifdef _WIN32 + // Shorten the names on windows + if (apiName.rfind("Windows ", 0) == 0) { + apiName = apiName.substr(8); + } +#endif + // Create device name and save to list + sprintf(buffer, "[%s] %s", apiName.c_str(), dev.deviceInfo->name); + devices[buffer] = dev; + deviceNames.push_back(buffer); + deviceNamesTxt += buffer; + deviceNamesTxt += '\0'; + } + } + + void selectDefault() { + if (devices.empty()) { + selectedDevName = ""; + return; + } + + // Search for the default device + PaDeviceIndex defId = Pa_GetDefaultOutputDevice(); + for (auto const& [name, dev] : devices) { + if (dev.id != defId) { continue; } + selectDevByName(name); + return; + } + + // If default not found, select first + selectDevByName(deviceNames[0]); + } + + void selectDevByName(std::string name) { + auto devIt = std::find(deviceNames.begin(), deviceNames.end(), name); + if (devIt == deviceNames.end()) { + selectDefault(); + return; + } + + // Load the device name, device descriptor and device ID + selectedDevName = name; + selectedDev = devices[name]; + devId = std::distance(deviceNames.begin(), devIt); + + // Load config + config.acquire(); + if (!config.conf[_streamName]["devices"].contains(name)) { + config.conf[_streamName]["devices"][name] = selectedDev.sampleRates[selectedDev.defaultSrId]; + } + config.release(true); + + // Find the sample rate ID, if not use default + bool found = false; + double selectedSr = config.conf[_streamName]["devices"][name]; + for (int i = 0; i < selectedDev.sampleRates.size(); i++) { + if (selectedDev.sampleRates[i] != selectedSr) { continue; } + srId = i; + found = true; + break; + } + if (!found) { + srId = selectedDev.defaultSrId; + } + } + + static int _mono_cb(const void *input, void *output, unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { + AudioSink* _this = (AudioSink*)userData; + + // For OSX, mute audio when not playing + if (!gui::mainWindow.isPlaying()) { + memset(output, 0, frameCount*sizeof(float)); + _this->s2m.out.flush(); + return 0; + } + + // Write to buffer + _this->s2m.out.read(); + memcpy(output, _this->s2m.out.readBuf, frameCount * sizeof(float)); + _this->s2m.out.flush(); + return 0; + } + + static int _stereo_cb(const void *input, void *output, unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { + AudioSink* _this = (AudioSink*)userData; + + // For OSX, mute audio when not playing + if (!gui::mainWindow.isPlaying()) { + memset(output, 0, frameCount*sizeof(dsp::stereo_t)); + _this->packer.out.flush(); + return 0; + } + + // Write to buffer + _this->packer.out.read(); + memcpy(output, _this->packer.out.readBuf, frameCount * sizeof(dsp::stereo_t)); + _this->packer.out.flush(); + return 0; + } + + std::string _streamName; + + bool running = false; + std::map devices; + std::vector deviceNames; + std::string deviceNamesTxt; + + AudioDevice_t selectedDev; + std::string selectedDevName; + + SinkManager::Stream* _stream; + dsp::Packer packer; + dsp::StereoToMono s2m; + + PaStream *devStream; + + EventHandler playStateHandler; +}; + +class AudioSinkModule : public ModuleManager::Instance { +public: + AudioSinkModule(std::string name) { + this->name = name; + provider.create = create_sink; + provider.ctx = this; + + Pa_Initialize(); + + sigpath::sinkManager.registerSinkProvider("New Audio", provider); + } + + ~AudioSinkModule() { + Pa_Terminate(); + } + + void enable() { + enabled = true; + } + + void disable() { + enabled = false; + } + + bool isEnabled() { + return enabled; + } + +private: + static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) { + return (SinkManager::Sink*)(new AudioSink(stream, streamName)); + } + + std::string name; + bool enabled = true; + SinkManager::SinkProvider provider; + +}; + +MOD_EXPORT void _INIT_() { + config.setPath(options::opts.root + "/new_audio_sink_config.json"); + config.load(json::object()); + config.enableAutoSave(); +} + +MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) { + AudioSinkModule* instance = new AudioSinkModule(name); + return instance; +} + +MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { + delete (AudioSinkModule*)instance; +} + +MOD_EXPORT void _END_() { + config.disableAutoSave(); + config.save(); +} \ No newline at end of file diff --git a/readme.md b/readme.md index b4203f50..c23ad8d8 100644 --- a/readme.md +++ b/readme.md @@ -284,27 +284,28 @@ Modules in beta are still included in releases for the most part but not enabled ## Sinks -| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default | -|------------------|------------|-------------------|----------------------------|:---------------:|:-----------------------:|:---------------------------:| -| audio_sink | Working | rtaudio | OPT_BUILD_AUDIO_SINK | ✅ | ✅ | ✅ | +| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default | +|--------------------|------------|--------------|------------------------------|:---------------:|:----------------:|:---------------------------:| +| audio_sink | Working | rtaudio | OPT_BUILD_AUDIO_SINK | ✅ | ✅ | ✅ | +| new_portaudio_sink | Beta | portaudio | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ | ## Decoders -| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default | -|---------------------|------------|--------------|-------------------------------|:---------------:|:-----------------------:|:---------------------------:| -| falcon9_decoder | Beta | ffplay | OPT_BUILD_FALCON9_DECODER | ⛔ | ⛔ | ⛔ | -| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ | -| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ | -| weather_sat_decoder | Unfinished | - | OPT_BUILD_WEATHER_SAT_DECODER | ⛔ | ⛔ | ⛔ | +| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default | +|---------------------|------------|--------------|-------------------------------|:---------------:|:----------------:|:---------------------------:| +| falcon9_decoder | Beta | ffplay | OPT_BUILD_FALCON9_DECODER | ⛔ | ⛔ | ⛔ | +| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ | +| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ | +| weather_sat_decoder | Unfinished | - | OPT_BUILD_WEATHER_SAT_DECODER | ⛔ | ⛔ | ⛔ | ## Misc -| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default | -|---------------------|------------|--------------|-------------------------------|:---------------:|:-----------------------:|:---------------------------:| -| discord_integration | Working | - | OPT_BUILD_DISCORD_PRESENCE | ✅ | ✅ | ⛔ | -| frequency_manager | Beta | - | OPT_BUILD_FREQUENCY_MANAGER | ✅ | ✅ | ✅ | -| recorder | Working | - | OPT_BUILD_RECORDER | ✅ | ✅ | ✅ | -| rigctl_server | Unfinished | - | OPT_BUILD_RIGCTL_SERVER | ✅ | ✅ | ⛔ | +| Name | Stage | Dependencies | Option | Built by default | Built in Release | Enabled in SDR++ by default | +|---------------------|------------|--------------|-----------------------------|:----------------:|:----------------:|:---------------------------:| +| discord_integration | Working | - | OPT_BUILD_DISCORD_PRESENCE | ✅ | ✅ | ⛔ | +| frequency_manager | Beta | - | OPT_BUILD_FREQUENCY_MANAGER | ✅ | ✅ | ✅ | +| recorder | Working | - | OPT_BUILD_RECORDER | ✅ | ✅ | ✅ | +| rigctl_server | Unfinished | - | OPT_BUILD_RIGCTL_SERVER | ✅ | ✅ | ⛔ | # Troubleshooting diff --git a/rigctl_server/src/main.cpp b/rigctl_server/src/main.cpp index 33441403..613931ea 100644 --- a/rigctl_server/src/main.cpp +++ b/rigctl_server/src/main.cpp @@ -185,8 +185,8 @@ private: } else if (parts[0] == "LOS") { // TODO: Stop Recorder - core::modComManager.callInterface("Recorder", RECORDER_IFACE_CMD_STOP, NULL, NULL); - resp = "RPRT 0\n"; + core::modComManager.callInterface("Recorder", RECORDER_IFACE_CMD_STOP, NULL, NULL); + resp = "RPRT 0\n"; client->write(resp.size(), (uint8_t*)resp.c_str()); } else if (parts[0] == "q") {