Line data Source code
1 : /* 2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc. 3 : * 4 : * This program is free software: you can redistribute it and/or modify 5 : * it under the terms of the GNU General Public License as published by 6 : * the Free Software Foundation, either version 3 of the License, or 7 : * (at your option) any later version. 8 : * 9 : * This program is distributed in the hope that it will be useful, 10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 : * GNU General Public License for more details. 13 : * 14 : * You should have received a copy of the GNU General Public License 15 : * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 : */ 17 : 18 : #pragma once 19 : 20 : #include <pulse/pulseaudio.h> 21 : #include <pulse/thread-mainloop.h> 22 : #include <pulse/error.h> 23 : #include <string> 24 : #include <functional> 25 : #include <vector> 26 : #include <iostream> 27 : #include <thread> 28 : #include <mutex> 29 : #include <atomic> 30 : #include <cstring> 31 : #include <unistd.h> 32 : 33 : class PulseLoopbackCapture 34 : { 35 : public: 36 : using AudioFrameCallback = std::function<void(const void* data, size_t length)>; 37 : 38 : PulseLoopbackCapture(); 39 : ~PulseLoopbackCapture(); 40 : 41 : bool startCaptureAsync(AudioFrameCallback callback); 42 : void stopCapture(); 43 : 44 0 : bool isRunning() const { return running_.load(); } 45 0 : uint32_t sampleRate() const { return SAMPLE_SPEC.rate; } 46 0 : uint8_t channels() const { return SAMPLE_SPEC.channels; } 47 : 48 : private: 49 : // PulseAudio callbacks 50 : static void contextStateCallback(pa_context* c, void* userdata); 51 : static void serverInfoCallback(pa_context* c, const pa_server_info* i, void* userdata); 52 : static void moduleLoadedCallback(pa_context* c, uint32_t idx, void* userdata); 53 : static void subscribeCallback(pa_context* c, pa_subscription_event_type_t t, uint32_t idx, void* userdata); 54 : static void sinkInputInfoCallback(pa_context* c, const pa_sink_input_info* i, int eol, void* userdata); 55 : static void streamReadCallback(pa_stream* s, size_t length, void* userdata); 56 : 57 : // Internal logic 58 : void runMainLoop(); 59 : void moveStreamIfNeeded(uint32_t streamIdx, pid_t streamPid, uint32_t ownerModuleIdx, const char* streamName); 60 : void setupModules(const std::string& default_sink); 61 : void startRecordingStream(); 62 : void unloadModulesAndQuit(); 63 : 64 : // Configuration 65 : std::string uniqueSinkName_; 66 : const pa_sample_spec SAMPLE_SPEC = {PA_SAMPLE_S16LE, 48000, 2}; 67 : 68 : // State 69 : pa_threaded_mainloop* mainloop_ = nullptr; 70 : pa_mainloop_api* mainloopApi_ = nullptr; 71 : pa_context* context_ = nullptr; 72 : pa_stream* recordStream_ = nullptr; 73 : 74 : std::atomic<bool> running_ {false}; 75 : 76 : AudioFrameCallback dataCallback_; 77 : 78 : pid_t myPid_; 79 : std::string defaultSinkName_; 80 : uint32_t nullSinkModuleIdx_ = PA_INVALID_INDEX; 81 : uint32_t loopbackModuleIdx_ = PA_INVALID_INDEX; 82 : };