mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2024-12-24 01:48:28 +01:00
Added a new optional audio sink as a test
This commit is contained in:
parent
45e4c21870
commit
63ae56cb9b
6
.github/workflows/build_all.yml
vendored
6
.github/workflows/build_all.yml
vendored
@ -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
|
||||
|
@ -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 \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
|
||||
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)
|
||||
|
@ -264,6 +264,11 @@ namespace dsp {
|
||||
generic_block<SampleFrameBuffer<T>>::tempStart();
|
||||
}
|
||||
|
||||
void flush() {
|
||||
std::unique_lock lck(bufMtx);
|
||||
readCur = writeCur;
|
||||
}
|
||||
|
||||
int run() {
|
||||
// Wait for data
|
||||
int count = _in->read();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ public:
|
||||
|
||||
bool lockWaterfallControls = false;
|
||||
|
||||
Event<bool> onPlayStateChange;
|
||||
|
||||
private:
|
||||
void generateFFTWindow(int win, int size);
|
||||
static void fftHandler(dsp::complex_t* samples, int count, void* ctx);
|
||||
|
@ -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 ..
|
||||
|
@ -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 ..
|
||||
|
@ -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 ..
|
||||
|
@ -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 ..
|
||||
|
@ -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 ..
|
||||
|
@ -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 ..
|
||||
|
35
new_portaudio_sink/CMakeLists.txt
Normal file
35
new_portaudio_sink/CMakeLists.txt
Normal file
@ -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)
|
442
new_portaudio_sink/src/main.cpp
Normal file
442
new_portaudio_sink/src/main.cpp
Normal file
@ -0,0 +1,442 @@
|
||||
#include <imgui.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <signal_path/sink.h>
|
||||
#include <portaudio.h>
|
||||
#include <dsp/audio.h>
|
||||
#include <dsp/processing.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <config.h>
|
||||
#include <algorithm>
|
||||
#include <options.h>
|
||||
|
||||
#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<double> 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<PaTime>(AUDIO_LATENCY, dev.deviceInfo->defaultLowOutputLatency);
|
||||
dev.outputParams.channelCount = std::min<int>(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<std::string, AudioDevice_t> devices;
|
||||
std::vector<std::string> deviceNames;
|
||||
std::string deviceNamesTxt;
|
||||
|
||||
AudioDevice_t selectedDev;
|
||||
std::string selectedDevName;
|
||||
|
||||
SinkManager::Stream* _stream;
|
||||
dsp::Packer<dsp::stereo_t> packer;
|
||||
dsp::StereoToMono s2m;
|
||||
|
||||
PaStream *devStream;
|
||||
|
||||
EventHandler<bool> 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();
|
||||
}
|
31
readme.md
31
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
|
||||
|
||||
|
@ -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") {
|
||||
|
Loading…
Reference in New Issue
Block a user