mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-11-04 10:49:11 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			349 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			349 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#pragma once
 | 
						|
#include <thread>
 | 
						|
#include <dsp/stream.h>
 | 
						|
#include <dsp/types.h>
 | 
						|
#include <fstream>
 | 
						|
#include <portaudio.h>
 | 
						|
#include <spdlog/spdlog.h>
 | 
						|
 | 
						|
namespace io {
 | 
						|
    class AudioSink {
 | 
						|
    public:
 | 
						|
        enum {
 | 
						|
            MONO,
 | 
						|
            STEREO,
 | 
						|
            _TYPE_COUNT
 | 
						|
        };
 | 
						|
 | 
						|
        struct AudioDevice_t {
 | 
						|
            std::string name;
 | 
						|
            int index;
 | 
						|
            int channels;
 | 
						|
            std::vector<float> sampleRates;
 | 
						|
            std::string txtSampleRates;
 | 
						|
        };
 | 
						|
 | 
						|
        AudioSink() {
 | 
						|
            
 | 
						|
        }
 | 
						|
 | 
						|
        AudioSink(int bufferSize) {
 | 
						|
            _bufferSize = bufferSize;
 | 
						|
            monoBuffer = new float[_bufferSize];
 | 
						|
            stereoBuffer = new dsp::StereoFloat_t[_bufferSize];
 | 
						|
            _volume = 1.0f;
 | 
						|
            Pa_Initialize();
 | 
						|
 | 
						|
            devTxtList = "";
 | 
						|
            int devCount = Pa_GetDeviceCount();
 | 
						|
            devIndex = Pa_GetDefaultOutputDevice();
 | 
						|
            const PaDeviceInfo *deviceInfo;
 | 
						|
            PaStreamParameters outputParams;
 | 
						|
            outputParams.sampleFormat = paFloat32;
 | 
						|
            outputParams.hostApiSpecificStreamInfo = NULL;
 | 
						|
            for(int i = 0; i < devCount; i++) {
 | 
						|
                deviceInfo = Pa_GetDeviceInfo(i);
 | 
						|
                if (deviceInfo->maxOutputChannels < 1) {
 | 
						|
                    continue;
 | 
						|
                }
 | 
						|
                AudioDevice_t dev;
 | 
						|
                dev.name = deviceInfo->name;
 | 
						|
                dev.index = i;
 | 
						|
                dev.channels = std::min<int>(deviceInfo->maxOutputChannels, 2);
 | 
						|
                dev.sampleRates.clear();
 | 
						|
                dev.txtSampleRates = "";
 | 
						|
                for (int j = 0; j < 6; j++) {
 | 
						|
                    outputParams.channelCount = dev.channels;
 | 
						|
                    outputParams.device = dev.index;
 | 
						|
                    outputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency;
 | 
						|
                    PaError err = Pa_IsFormatSupported(NULL, &outputParams, POSSIBLE_SAMP_RATE[j]);
 | 
						|
                    if (err != paFormatIsSupported) {
 | 
						|
                        continue;
 | 
						|
                    }
 | 
						|
                    dev.sampleRates.push_back(POSSIBLE_SAMP_RATE[j]);
 | 
						|
                    dev.txtSampleRates += std::to_string((int)POSSIBLE_SAMP_RATE[j]);
 | 
						|
                    dev.txtSampleRates += '\0';
 | 
						|
                }
 | 
						|
                if (dev.sampleRates.size() == 0) {
 | 
						|
                    continue;
 | 
						|
                }
 | 
						|
                if (i == devIndex) {
 | 
						|
                    devListIndex = devices.size();
 | 
						|
                    defaultDev = devListIndex;
 | 
						|
                }
 | 
						|
                devices.push_back(dev);
 | 
						|
                deviceNames.push_back(deviceInfo->name);
 | 
						|
                devTxtList += deviceInfo->name;
 | 
						|
                devTxtList += '\0';
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        void init(int bufferSize) {
 | 
						|
            _bufferSize = bufferSize;
 | 
						|
            monoBuffer = new float[_bufferSize];
 | 
						|
            stereoBuffer = new dsp::StereoFloat_t[_bufferSize];
 | 
						|
            _volume = 1.0f;
 | 
						|
            Pa_Initialize();
 | 
						|
 | 
						|
            devTxtList = "";
 | 
						|
            int devCount = Pa_GetDeviceCount();
 | 
						|
            devIndex = Pa_GetDefaultOutputDevice();
 | 
						|
            const PaDeviceInfo *deviceInfo;
 | 
						|
            PaStreamParameters outputParams;
 | 
						|
            outputParams.sampleFormat = paFloat32;
 | 
						|
            outputParams.hostApiSpecificStreamInfo = NULL;
 | 
						|
            for(int i = 0; i < devCount; i++) {
 | 
						|
                deviceInfo = Pa_GetDeviceInfo(i);
 | 
						|
                if (deviceInfo->maxOutputChannels < 1) {
 | 
						|
                    continue;
 | 
						|
                }
 | 
						|
                AudioDevice_t dev;
 | 
						|
                dev.name = deviceInfo->name;
 | 
						|
                dev.index = i;
 | 
						|
                dev.channels = std::min<int>(deviceInfo->maxOutputChannels, 2);
 | 
						|
                dev.sampleRates.clear();
 | 
						|
                dev.txtSampleRates = "";
 | 
						|
                for (int j = 0; j < 6; j++) {
 | 
						|
                    outputParams.channelCount = dev.channels;
 | 
						|
                    outputParams.device = dev.index;
 | 
						|
                    outputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency;
 | 
						|
                    PaError err = Pa_IsFormatSupported(NULL, &outputParams, POSSIBLE_SAMP_RATE[j]);
 | 
						|
                    if (err != paFormatIsSupported) {
 | 
						|
                        continue;
 | 
						|
                    }
 | 
						|
                    dev.sampleRates.push_back(POSSIBLE_SAMP_RATE[j]);
 | 
						|
                    dev.txtSampleRates += std::to_string((int)POSSIBLE_SAMP_RATE[j]);
 | 
						|
                    dev.txtSampleRates += '\0';
 | 
						|
                }
 | 
						|
                if (dev.sampleRates.size() == 0) {
 | 
						|
                    continue;
 | 
						|
                }
 | 
						|
                if (i == devIndex) {
 | 
						|
                    devListIndex = devices.size();
 | 
						|
                    defaultDev = devListIndex;
 | 
						|
                }
 | 
						|
                devices.push_back(dev);
 | 
						|
                deviceNames.push_back(deviceInfo->name);
 | 
						|
                devTxtList += deviceInfo->name;
 | 
						|
                devTxtList += '\0';
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        void setMonoInput(dsp::stream<float>* input) {
 | 
						|
            _monoInput = input;
 | 
						|
        }
 | 
						|
 | 
						|
        void setStereoInput(dsp::stream<dsp::StereoFloat_t>* input) {
 | 
						|
            _stereoInput = input;
 | 
						|
        }
 | 
						|
 | 
						|
        void setVolume(float volume) {
 | 
						|
            _volume = volume;
 | 
						|
        }
 | 
						|
 | 
						|
        void start() {
 | 
						|
            if (running) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            const PaDeviceInfo *deviceInfo;
 | 
						|
            AudioDevice_t dev = devices[devListIndex];
 | 
						|
            PaStreamParameters outputParams;
 | 
						|
            deviceInfo = Pa_GetDeviceInfo(dev.index);
 | 
						|
            outputParams.channelCount = 2;
 | 
						|
            outputParams.sampleFormat = paFloat32;
 | 
						|
            outputParams.hostApiSpecificStreamInfo = NULL;
 | 
						|
            outputParams.device = dev.index;
 | 
						|
            outputParams.suggestedLatency = Pa_GetDeviceInfo(outputParams.device)->defaultLowOutputLatency;
 | 
						|
            PaError err;
 | 
						|
            if (streamType == MONO) {
 | 
						|
                err = Pa_OpenStream(&stream, NULL, &outputParams, _sampleRate, _bufferSize, 0,
 | 
						|
                                (dev.channels == 2) ? _mono_to_stereo_callback : _mono_to_mono_callback, this);
 | 
						|
            }
 | 
						|
            else {
 | 
						|
                err = Pa_OpenStream(&stream, NULL, &outputParams, _sampleRate, _bufferSize, 0,
 | 
						|
                                (dev.channels == 2) ? _stereo_to_stereo_callback : _stereo_to_mono_callback, this);
 | 
						|
            }
 | 
						|
            
 | 
						|
            if (err != 0) {
 | 
						|
                spdlog::error("Error while opening audio stream: ({0}) => {1}", err, Pa_GetErrorText(err));
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            err = Pa_StartStream(stream);
 | 
						|
            if (err != 0) {
 | 
						|
                spdlog::error("Error while starting audio stream: ({0}) => {1}", err, Pa_GetErrorText(err));
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            spdlog::info("Audio device open.");
 | 
						|
            running = true;
 | 
						|
        }
 | 
						|
 | 
						|
        void stop() {
 | 
						|
            if (!running) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            if (streamType == MONO) {
 | 
						|
                _monoInput->stopReader();
 | 
						|
            }
 | 
						|
            else {
 | 
						|
                _stereoInput->stopReader();
 | 
						|
            }
 | 
						|
            Pa_StopStream(stream);
 | 
						|
            Pa_CloseStream(stream);
 | 
						|
            if (streamType == MONO) {
 | 
						|
                _monoInput->clearReadStop();
 | 
						|
            }
 | 
						|
            else {
 | 
						|
                _stereoInput->clearReadStop();
 | 
						|
            }
 | 
						|
            running = false;
 | 
						|
        }
 | 
						|
 | 
						|
        void setBlockSize(int blockSize) {
 | 
						|
            if (running) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            _bufferSize = blockSize;
 | 
						|
            delete[] monoBuffer;
 | 
						|
            delete[] stereoBuffer;
 | 
						|
            monoBuffer = new float[_bufferSize];
 | 
						|
            stereoBuffer = new dsp::StereoFloat_t[_bufferSize];
 | 
						|
        }
 | 
						|
 | 
						|
        void setSampleRate(float sampleRate) {
 | 
						|
            _sampleRate = sampleRate;
 | 
						|
        }
 | 
						|
 | 
						|
        void setDevice(int id) {
 | 
						|
            if (devListIndex == id) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            if (running) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            devListIndex = id;
 | 
						|
            devIndex = devices[id].index;
 | 
						|
        }
 | 
						|
 | 
						|
        void setToDefault() {
 | 
						|
            setDevice(defaultDev);
 | 
						|
        }
 | 
						|
 | 
						|
        int getDeviceId() {
 | 
						|
            return devListIndex;
 | 
						|
        }
 | 
						|
 | 
						|
        void setStreamType(int type) {
 | 
						|
            streamType = type;
 | 
						|
        }
 | 
						|
 | 
						|
        std::string devTxtList;
 | 
						|
        std::vector<AudioDevice_t> devices;
 | 
						|
        std::vector<std::string> deviceNames;
 | 
						|
 | 
						|
    private:
 | 
						|
        static int _mono_to_mono_callback(const void *input,
 | 
						|
                                      void *output,
 | 
						|
                                      unsigned long frameCount,
 | 
						|
                                      const PaStreamCallbackTimeInfo* timeInfo, 
 | 
						|
                                      PaStreamCallbackFlags statusFlags, void *userData ) {
 | 
						|
            AudioSink* _this = (AudioSink*)userData;
 | 
						|
            float* outbuf = (float*)output;
 | 
						|
            if (_this->_monoInput->read(_this->monoBuffer, frameCount) < 0) {
 | 
						|
                memset(outbuf, 0, sizeof(float) * frameCount);
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
            
 | 
						|
            float vol = powf(_this->_volume, 2);
 | 
						|
            for (int i = 0; i < frameCount; i++) {
 | 
						|
                outbuf[i] = _this->monoBuffer[i] * vol;
 | 
						|
            }
 | 
						|
            return 0;
 | 
						|
        }
 | 
						|
 | 
						|
        static int _stereo_to_stereo_callback(const void *input,
 | 
						|
                                      void *output,
 | 
						|
                                      unsigned long frameCount,
 | 
						|
                                      const PaStreamCallbackTimeInfo* timeInfo, 
 | 
						|
                                      PaStreamCallbackFlags statusFlags, void *userData ) {
 | 
						|
            AudioSink* _this = (AudioSink*)userData;
 | 
						|
            dsp::StereoFloat_t* outbuf = (dsp::StereoFloat_t*)output;
 | 
						|
            if (_this->_stereoInput->read(_this->stereoBuffer, frameCount) < 0) {
 | 
						|
                memset(outbuf, 0, sizeof(dsp::StereoFloat_t) * frameCount);
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
 | 
						|
            // Note: Calculate the power in the UI instead of here
 | 
						|
            
 | 
						|
            float vol = powf(_this->_volume, 2);
 | 
						|
            for (int i = 0; i < frameCount; i++) {
 | 
						|
                outbuf[i].l = _this->stereoBuffer[i].l * vol;
 | 
						|
                outbuf[i].r = _this->stereoBuffer[i].r * vol;
 | 
						|
            }
 | 
						|
            return 0;
 | 
						|
        }
 | 
						|
 | 
						|
        static int _mono_to_stereo_callback(const void *input,
 | 
						|
                                      void *output,
 | 
						|
                                      unsigned long frameCount,
 | 
						|
                                      const PaStreamCallbackTimeInfo* timeInfo, 
 | 
						|
                                      PaStreamCallbackFlags statusFlags, void *userData ) {
 | 
						|
            AudioSink* _this = (AudioSink*)userData;
 | 
						|
            dsp::StereoFloat_t* outbuf = (dsp::StereoFloat_t*)output;
 | 
						|
            if (_this->_monoInput->read(_this->monoBuffer, frameCount) < 0) {
 | 
						|
                memset(outbuf, 0, sizeof(dsp::StereoFloat_t) * frameCount);
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
            
 | 
						|
            float vol = powf(_this->_volume, 2);
 | 
						|
            for (int i = 0; i < frameCount; i++) {
 | 
						|
                outbuf[i].l = _this->monoBuffer[i] * vol;
 | 
						|
                outbuf[i].r = _this->monoBuffer[i] * vol;
 | 
						|
            }
 | 
						|
            return 0;
 | 
						|
        }
 | 
						|
 | 
						|
        static int _stereo_to_mono_callback(const void *input,
 | 
						|
                                      void *output,
 | 
						|
                                      unsigned long frameCount,
 | 
						|
                                      const PaStreamCallbackTimeInfo* timeInfo, 
 | 
						|
                                      PaStreamCallbackFlags statusFlags, void *userData ) {
 | 
						|
            AudioSink* _this = (AudioSink*)userData;
 | 
						|
            float* outbuf = (float*)output;
 | 
						|
            if (_this->_stereoInput->read(_this->stereoBuffer, frameCount) < 0) {
 | 
						|
                memset(outbuf, 0, sizeof(float) * frameCount);
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
 | 
						|
            // Note: Calculate the power in the UI instead of here
 | 
						|
            
 | 
						|
            float vol = powf(_this->_volume, 2);
 | 
						|
            for (int i = 0; i < frameCount; i++) {
 | 
						|
                outbuf[i] = ((_this->stereoBuffer[i].l + _this->stereoBuffer[i].r) / 2.0f) * vol;
 | 
						|
            }
 | 
						|
            return 0;
 | 
						|
        }
 | 
						|
 | 
						|
        float POSSIBLE_SAMP_RATE[6] = {
 | 
						|
            48000.0f,
 | 
						|
            44100.0f,
 | 
						|
            24000.0f,
 | 
						|
            22050.0f,
 | 
						|
            12000.0f,
 | 
						|
            11025.0f
 | 
						|
        };
 | 
						|
 | 
						|
        int streamType;
 | 
						|
        int devIndex;
 | 
						|
        int devListIndex;
 | 
						|
        int defaultDev;
 | 
						|
        float _sampleRate;
 | 
						|
        int _bufferSize;
 | 
						|
        dsp::stream<float>* _monoInput;
 | 
						|
        dsp::stream<dsp::StereoFloat_t>* _stereoInput;
 | 
						|
        float* monoBuffer;
 | 
						|
        dsp::StereoFloat_t* stereoBuffer;
 | 
						|
        float _volume = 1.0f;
 | 
						|
        PaStream *stream;
 | 
						|
        bool running = false;
 | 
						|
    };
 | 
						|
}; |