diff --git a/src/media/audio/portaudio/portaudiolayer.cpp b/src/media/audio/portaudio/portaudiolayer.cpp index 104ecd12f8294625ddc40d70d244f5cbe117f240..591eb86d8aa84dfd5bc2657b68eb443b497f7b57 100644 --- a/src/media/audio/portaudio/portaudiolayer.cpp +++ b/src/media/audio/portaudio/portaudiolayer.cpp @@ -2,6 +2,7 @@ * Copyright (C) 2004-2016 Savoir-faire Linux Inc. * * Author: Edric Ladent-Milaret <edric.ladent-milaret@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,49 +26,81 @@ #include "audio/ringbufferpool.h" #include "audio/ringbuffer.h" +#include <portaudio.h> +#include <algorithm> +#include <cmath> + namespace ring { -PortAudioLayer::PortAudioLayer(const AudioPreference &pref) - : AudioLayer(pref) - , indexIn_(pref.getAlsaCardin()) - , indexOut_(pref.getAlsaCardout()) - , indexRing_(pref.getAlsaCardring()) - , playbackBuff_(0, audioFormat_) - , mainRingBuffer_(Manager::instance().getRingBufferPool().getRingBuffer(RingBufferPool::DEFAULT_ID)) -{ - init(); -} +enum Direction {Input=0, Output=1, End=2}; -PortAudioLayer::~PortAudioLayer() +struct PortAudioLayer::PortAudioLayerImpl { - terminate(); -} + PortAudioLayerImpl(PortAudioLayer&, const AudioPreference&); + ~PortAudioLayerImpl(); + + void init(PortAudioLayer&); + void terminate() const; + void initStream(PortAudioLayer&); + + std::vector<std::string> getDeviceByType(bool) const; + + PaDeviceIndex indexIn_; + PaDeviceIndex indexOut_; + PaDeviceIndex indexRing_; + + AudioBuffer playbackBuff_; + + std::shared_ptr<RingBuffer> mainRingBuffer_; + + std::array<PaStream*, static_cast<int>(Direction::End)> streams_; + + int paOutputCallback(PortAudioLayer& parent, + const AudioSample* inputBuffer, + AudioSample* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags); + + int paInputCallback(PortAudioLayer& parent, + const AudioSample* inputBuffer, + AudioSample* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags); +}; + +//################################################################################################## + +PortAudioLayer::PortAudioLayer(const AudioPreference& pref) + : AudioLayer {pref} + , pimpl_ {new PortAudioLayerImpl(*this, pref)} +{} std::vector<std::string> PortAudioLayer::getCaptureDeviceList() const { - return this->getDeviceByType(false); + return pimpl_->getDeviceByType(false); } std::vector<std::string> PortAudioLayer::getPlaybackDeviceList() const { - return this->getDeviceByType(true); + return pimpl_->getDeviceByType(true); } int -PortAudioLayer::getAudioDeviceIndex(const std::string& name, - DeviceType type) const +PortAudioLayer::getAudioDeviceIndex(const std::string& name, DeviceType type) const { int numDevices = 0; (void) type; numDevices = Pa_GetDeviceCount(); - if (numDevices < 0) + if (numDevices < 0) { RING_ERR("PortAudioLayer error : %s", Pa_GetErrorText(numDevices)); - else { - const PaDeviceInfo *deviceInfo; + } else { + const PaDeviceInfo* deviceInfo; for (int i = 0; i < numDevices; i++) { deviceInfo = Pa_GetDeviceInfo(i); if (deviceInfo->name == name) @@ -89,19 +122,19 @@ PortAudioLayer::getAudioDeviceName(int index, DeviceType type) const int PortAudioLayer::getIndexCapture() const { - return this->indexIn_; + return pimpl_->indexIn_; } int PortAudioLayer::getIndexPlayback() const { - return this->indexOut_; + return pimpl_->indexOut_; } int PortAudioLayer::getIndexRingtone() const { - return this->indexRing_; + return pimpl_->indexRing_; } void @@ -113,7 +146,10 @@ PortAudioLayer::startStream() return; status_ = Status::Started; } - this->initStream(); + pimpl_->initStream(*this); + + flushUrgent(); + flushMain(); } void @@ -124,34 +160,33 @@ PortAudioLayer::stopStream() RING_DBG("Stop PortAudio Streams"); - for (int i = 0; i < Direction::End; i++) { - auto err = Pa_StopStream(streams[i]); - if(err != paNoError) + for (auto& st_ptr : pimpl_->streams_) { + auto err = Pa_StopStream(st_ptr); + if (err != paNoError) RING_ERR("Pa_StopStream error : %s", Pa_GetErrorText(err)); - err = Pa_CloseStream(streams[i]); + err = Pa_CloseStream(st_ptr); if (err != paNoError) RING_ERR("Pa_StopStream error : %s", Pa_GetErrorText(err)); } { - std::lock_guard<std::mutex> lock(mutex_); + std::lock_guard<std::mutex> lock {mutex_}; status_ = Status::Idle; } - /* Flush the ring buffers */ + // Flush the ring buffers flushUrgent(); flushMain(); } void -PortAudioLayer::updatePreference(AudioPreference &preference, - int index, DeviceType type) +PortAudioLayer::updatePreference(AudioPreference& preference, int index, DeviceType type) { switch (type) { case DeviceType::PLAYBACK: { - auto playbackList = getDeviceByType(true); + auto playbackList = pimpl_->getDeviceByType(true); if (playbackList.size() > (size_t) index) { auto realIdx = getAudioDeviceIndex(playbackList.at(index), type); preference.setAlsaCardout(realIdx); @@ -161,7 +196,7 @@ PortAudioLayer::updatePreference(AudioPreference &preference, case DeviceType::CAPTURE: { - auto captureList = getDeviceByType(false); + auto captureList = pimpl_->getDeviceByType(false); if (captureList.size() > (size_t) index) { auto realIdx = getAudioDeviceIndex(captureList.at(index), type); preference.setAlsaCardin(realIdx); @@ -178,8 +213,25 @@ PortAudioLayer::updatePreference(AudioPreference &preference, } } +//################################################################################################## + +PortAudioLayer::PortAudioLayerImpl::PortAudioLayerImpl(PortAudioLayer& parent, const AudioPreference& pref) + : indexIn_ {pref.getAlsaCardin()} + , indexOut_ {pref.getAlsaCardout()} + , indexRing_ {pref.getAlsaCardring()} + , playbackBuff_ {0, parent.audioFormat_} + , mainRingBuffer_ {Manager::instance().getRingBufferPool().getRingBuffer(RingBufferPool::DEFAULT_ID)} +{ + init(parent); +} + +PortAudioLayer::PortAudioLayerImpl::~PortAudioLayerImpl() +{ + terminate(); +} + std::vector<std::string> -PortAudioLayer::getDeviceByType(const bool& playback) const +PortAudioLayer::PortAudioLayerImpl::getDeviceByType(bool playback) const { std::vector<std::string> ret; int numDevices = 0; @@ -203,42 +255,35 @@ PortAudioLayer::getDeviceByType(const bool& playback) const } int -PortAudioLayer::paOutputCallback(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData) +PortAudioLayer::PortAudioLayerImpl::paOutputCallback(PortAudioLayer& parent, + const AudioSample* inputBuffer, + AudioSample* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags) { - + // unused arguments (void) inputBuffer; (void) timeInfo; (void) statusFlags; - auto ref = (PortAudioLayer*)userData; - auto out = (AudioSample*)outputBuffer; - - AudioFormat mainBufferAudioFormat = - Manager::instance().getRingBufferPool().getInternalAudioFormat(); - bool resample = - ref->audioFormat_.sample_rate != mainBufferAudioFormat.sample_rate; - auto urgentFramesToGet = - ref->urgentRingBuffer_.availableForGet(RingBufferPool::DEFAULT_ID); + AudioFormat mainBufferAudioFormat = Manager::instance().getRingBufferPool().getInternalAudioFormat(); + bool resample = parent.audioFormat_.sample_rate != mainBufferAudioFormat.sample_rate; + auto urgentFramesToGet = parent.urgentRingBuffer_.availableForGet(RingBufferPool::DEFAULT_ID); if (urgentFramesToGet > 0) { RING_WARN("Getting urgent frames."); - size_t totSample = std::min(framesPerBuffer, - (unsigned long)urgentFramesToGet); + size_t totSample = std::min(framesPerBuffer, (unsigned long)urgentFramesToGet); - ref->playbackBuff_.setFormat(ref->audioFormat_); - ref->playbackBuff_.resize(totSample); - ref->urgentRingBuffer_.get(ref->playbackBuff_, RingBufferPool::DEFAULT_ID); + playbackBuff_.setFormat(parent.audioFormat_); + playbackBuff_.resize(totSample); + parent.urgentRingBuffer_.get(playbackBuff_, RingBufferPool::DEFAULT_ID); - ref->playbackBuff_.applyGain(ref->isPlaybackMuted_ ? 0.0 : ref->playbackGain_); + playbackBuff_.applyGain(parent.isPlaybackMuted_ ? 0.0 : parent.playbackGain_); - ref->playbackBuff_.interleave(out); + playbackBuff_.interleave(outputBuffer); - Manager::instance().getRingBufferPool().discard(totSample, - RingBufferPool::DEFAULT_ID); + Manager::instance().getRingBufferPool().discard(totSample, RingBufferPool::DEFAULT_ID); } unsigned normalFramesToGet = @@ -248,119 +293,107 @@ PortAudioLayer::paOutputCallback(const void *inputBuffer, void *outputBuffer, unsigned readableSamples = framesPerBuffer; if (resample) { - resampleFactor = - static_cast<double>(ref->audioFormat_.sample_rate) - / mainBufferAudioFormat.sample_rate; + resampleFactor = static_cast<double>(parent.audioFormat_.sample_rate) + / mainBufferAudioFormat.sample_rate; readableSamples = std::ceil(framesPerBuffer / resampleFactor); } readableSamples = std::min(readableSamples, normalFramesToGet); - ref->playbackBuff_.setFormat(ref->audioFormat_); - ref->playbackBuff_.resize(readableSamples); - Manager::instance().getRingBufferPool().getData(ref->playbackBuff_, - RingBufferPool::DEFAULT_ID); - ref->playbackBuff_.applyGain(ref->isPlaybackMuted_ ? 0.0 : ref->playbackGain_); + playbackBuff_.setFormat(parent.audioFormat_); + playbackBuff_.resize(readableSamples); + Manager::instance().getRingBufferPool().getData(playbackBuff_, RingBufferPool::DEFAULT_ID); + playbackBuff_.applyGain(parent.isPlaybackMuted_ ? 0.0 : parent.playbackGain_); if (resample) { - AudioBuffer resampledOutput(readableSamples, ref->audioFormat_); - ref->resampler_->resample(ref->playbackBuff_, resampledOutput); + AudioBuffer resampledOutput(readableSamples, parent.audioFormat_); + parent.resampler_->resample(playbackBuff_, resampledOutput); - resampledOutput.interleave(out); + resampledOutput.interleave(outputBuffer); } else { - ref->playbackBuff_.interleave(out); + playbackBuff_.interleave(outputBuffer); } } if (normalFramesToGet <= 0) { auto tone = Manager::instance().getTelephoneTone(); auto file_tone = Manager::instance().getTelephoneFile(); - ref->playbackBuff_.setFormat(ref->audioFormat_); - ref->playbackBuff_.resize(framesPerBuffer); + playbackBuff_.setFormat(parent.audioFormat_); + playbackBuff_.resize(framesPerBuffer); if (tone) { - tone->getNext(ref->playbackBuff_, ref->playbackGain_); + tone->getNext(playbackBuff_, parent.playbackGain_); } else if (file_tone) { - file_tone->getNext(ref->playbackBuff_, ref->playbackGain_); + file_tone->getNext(playbackBuff_, parent.playbackGain_); } else { //RING_WARN("No tone or file_tone!"); - ref->playbackBuff_.reset(); + playbackBuff_.reset(); } - ref->playbackBuff_.interleave(out); + playbackBuff_.interleave(outputBuffer); } return paContinue; } int -PortAudioLayer::paInputCallback(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData) +PortAudioLayer::PortAudioLayerImpl::paInputCallback(PortAudioLayer& parent, + const AudioSample* inputBuffer, + AudioSample* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags) { - + // unused arguments (void) outputBuffer; (void) timeInfo; (void) statusFlags; - auto ref = (PortAudioLayer*)userData; - auto in = (AudioSample*)inputBuffer; - if (framesPerBuffer == 0) { RING_WARN("No frames for input."); return paContinue; } - const auto mainBufferFormat = - Manager::instance().getRingBufferPool().getInternalAudioFormat(); - bool resample = - ref->audioInputFormat_.sample_rate != mainBufferFormat.sample_rate; + const auto mainBufferFormat = Manager::instance().getRingBufferPool().getInternalAudioFormat(); + bool resample = parent.audioInputFormat_.sample_rate != mainBufferFormat.sample_rate; + AudioBuffer inBuff(framesPerBuffer, parent.audioInputFormat_); - AudioBuffer inBuff(framesPerBuffer, ref->audioInputFormat_); + inBuff.deinterleave(inputBuffer, framesPerBuffer, parent.audioInputFormat_.nb_channels); - inBuff.deinterleave(in, framesPerBuffer, ref->audioInputFormat_.nb_channels); - - inBuff.applyGain(ref->isCaptureMuted_ ? 0.0 : ref->captureGain_); + inBuff.applyGain(parent.isCaptureMuted_ ? 0.0 : parent.captureGain_); if (resample) { auto outSamples = framesPerBuffer - / (static_cast<double>(ref->audioInputFormat_.sample_rate) + / (static_cast<double>(parent.audioInputFormat_.sample_rate) / mainBufferFormat.sample_rate); AudioBuffer out(outSamples, mainBufferFormat); - ref->inputResampler_->resample(inBuff, out); - ref->dcblocker_.process(out); - ref->mainRingBuffer_->put(out); + parent.inputResampler_->resample(inBuff, out); + parent.dcblocker_.process(out); + mainRingBuffer_->put(out); } else { - ref->dcblocker_.process(inBuff); - ref->mainRingBuffer_->put(inBuff); + parent.dcblocker_.process(inBuff); + mainRingBuffer_->put(inBuff); } return paContinue; } void -PortAudioLayer::init() +PortAudioLayer::PortAudioLayerImpl::init(PortAudioLayer& parent) { RING_DBG("Init PortAudioLayer"); const auto err = Pa_Initialize(); if (err != paNoError) { RING_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err)); - this->terminate(); + terminate(); } -#ifdef RING_UWP - indexRing_ = indexOut_ = Pa_GetDefaultOutputDevice(); - indexIn_ = Pa_GetDefaultInputDevice(); -#else indexRing_ = indexOut_ = indexOut_ == paNoDevice ? Pa_GetDefaultOutputDevice() : indexOut_; indexIn_ = indexIn_ == paNoDevice ? Pa_GetDefaultInputDevice() : indexIn_; -#endif if (indexOut_ != paNoDevice) { if (const auto outputDeviceInfo = Pa_GetDeviceInfo(indexOut_)) { - audioFormat_.nb_channels = outputDeviceInfo->maxOutputChannels; - audioFormat_.sample_rate = outputDeviceInfo->defaultSampleRate; - hardwareFormatAvailable(audioFormat_); + parent.audioFormat_.nb_channels = outputDeviceInfo->maxOutputChannels; + parent.audioFormat_.sample_rate = outputDeviceInfo->defaultSampleRate; + parent.hardwareFormatAvailable(parent.audioFormat_); } else { indexOut_ = paNoDevice; } @@ -368,20 +401,19 @@ PortAudioLayer::init() if (indexIn_ != paNoDevice) { if (const auto inputDeviceInfo = Pa_GetDeviceInfo(indexIn_)) { - audioInputFormat_.nb_channels = inputDeviceInfo->maxInputChannels; - audioInputFormat_.sample_rate = inputDeviceInfo->defaultSampleRate; - hardwareInputFormatAvailable(audioInputFormat_); + parent.audioInputFormat_.nb_channels = inputDeviceInfo->maxInputChannels; + parent.audioInputFormat_.sample_rate = inputDeviceInfo->defaultSampleRate; + parent.hardwareInputFormatAvailable(parent.audioInputFormat_); } else { indexIn_ = paNoDevice; } } - for (int i = 0; i < Direction::End; i++) - streams[i] = nullptr; + std::fill(std::begin(streams_), std::end(streams_), nullptr); } void -PortAudioLayer::terminate() const +PortAudioLayer::PortAudioLayerImpl::terminate() const { RING_DBG("PortAudioLayer terminate."); const auto err = Pa_Terminate(); @@ -390,9 +422,9 @@ PortAudioLayer::terminate() const } void -PortAudioLayer::initStream() +PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent) { - dcblocker_.reset(); + parent.dcblocker_.reset(); RING_DBG("Open PortAudio Output Stream"); PaStreamParameters outputParameters; @@ -401,23 +433,33 @@ PortAudioLayer::initStream() if (outputParameters.device == paNoDevice) { RING_ERR("Error: No valid output device. There will be no sound."); } else { - const auto outputDeviceInfo = - Pa_GetDeviceInfo(outputParameters.device); - outputParameters.channelCount = - audioFormat_.nb_channels = outputDeviceInfo->maxOutputChannels; + const auto outputDeviceInfo = Pa_GetDeviceInfo(outputParameters.device); + outputParameters.channelCount = parent.audioFormat_.nb_channels = outputDeviceInfo->maxOutputChannels; outputParameters.sampleFormat = paInt16; outputParameters.suggestedLatency = outputDeviceInfo->defaultLowOutputLatency; - outputParameters.hostApiSpecificStreamInfo = NULL; + outputParameters.hostApiSpecificStreamInfo = nullptr; auto err = Pa_OpenStream( - &streams[Direction::Output], - NULL, + &streams_[Direction::Output], + nullptr, &outputParameters, outputDeviceInfo->defaultSampleRate, paFramesPerBufferUnspecified, paNoFlag, - &PortAudioLayer::paOutputCallback, - this); + [](const void* inputBuffer, void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData) -> int { + auto layer = static_cast<PortAudioLayer*>(userData); + return layer->pimpl_->paOutputCallback(*layer, + static_cast<const AudioSample*>(inputBuffer), + static_cast<AudioSample*>(outputBuffer), + framesPerBuffer, + timeInfo, + statusFlags); + }, + &parent); if(err != paNoError) RING_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err)); } @@ -425,40 +467,49 @@ PortAudioLayer::initStream() RING_DBG("Open PortAudio Input Stream"); PaStreamParameters inputParameters; inputParameters.device = indexIn_; + if (inputParameters.device == paNoDevice) { RING_ERR("Error: No valid input device. There will be no mic."); } else { - const auto inputDeviceInfo = - Pa_GetDeviceInfo(inputParameters.device); - inputParameters.channelCount = - audioInputFormat_.nb_channels = inputDeviceInfo->maxInputChannels; + const auto inputDeviceInfo = Pa_GetDeviceInfo(inputParameters.device); + inputParameters.channelCount = parent.audioInputFormat_.nb_channels = inputDeviceInfo->maxInputChannels; inputParameters.sampleFormat = paInt16; inputParameters.suggestedLatency = inputDeviceInfo->defaultLowInputLatency; - inputParameters.hostApiSpecificStreamInfo = NULL; + inputParameters.hostApiSpecificStreamInfo = nullptr; auto err = Pa_OpenStream( - &streams[Direction::Input], + &streams_[Direction::Input], &inputParameters, - NULL, + nullptr, inputDeviceInfo->defaultSampleRate, paFramesPerBufferUnspecified, paNoFlag, - &PortAudioLayer::paInputCallback, - this); - if(err != paNoError) + [](const void* inputBuffer, void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData) -> int { + auto layer = static_cast<PortAudioLayer*>(userData); + return layer->pimpl_->paInputCallback(*layer, + static_cast<const AudioSample*>(inputBuffer), + static_cast<AudioSample*>(outputBuffer), + framesPerBuffer, + timeInfo, + statusFlags); + }, + &parent); + if (err != paNoError) RING_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err)); } RING_DBG("Start PortAudio Streams"); - for (int i = 0; i < Direction::End; i++) { - if (streams[i]) { - auto err = Pa_StartStream(streams[i]); + for (auto& st_ptr : streams_) { + if (st_ptr) { + auto err = Pa_StartStream(st_ptr); if (err != paNoError) RING_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err)); } } - - flushUrgent(); - flushMain(); } + } // namespace ring diff --git a/src/media/audio/portaudio/portaudiolayer.h b/src/media/audio/portaudio/portaudiolayer.h index 8a417ecbc696c8422884de9e6ae308986a7afd77..a333a850074c8395289448d443d57e4321bb5f11 100644 --- a/src/media/audio/portaudio/portaudiolayer.h +++ b/src/media/audio/portaudio/portaudiolayer.h @@ -2,6 +2,7 @@ * Copyright (C) 2004-2016 Savoir-faire Linux Inc. * * Author: Edric Ladent-Milaret <edric.ladent-milaret@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,25 +19,24 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef PORTAUDIO_LAYER_H -#define PORTAUDIO_LAYER_H - -#include <portaudio.h> +#pragma once #include "audio/audiolayer.h" #include "noncopyable.h" +#include <memory> +#include <array> + namespace ring { class PortAudioLayer : public AudioLayer { public: - PortAudioLayer(const AudioPreference &pref); - ~PortAudioLayer(); + PortAudioLayer(const AudioPreference& pref); + virtual ~PortAudioLayer() = default; virtual std::vector<std::string> getCaptureDeviceList() const; virtual std::vector<std::string> getPlaybackDeviceList() const; - virtual int getAudioDeviceIndex(const std::string& name, DeviceType type) const; virtual std::string getAudioDeviceName(int index, DeviceType type) const; virtual int getIndexCapture() const; @@ -55,39 +55,13 @@ public: */ virtual void stopStream(); - virtual void updatePreference(AudioPreference &pref, int index, DeviceType type); + virtual void updatePreference(AudioPreference& pref, int index, DeviceType type); - private: +private: NON_COPYABLE(PortAudioLayer); - void init(void); - void terminate(void) const; - void initStream(void); - std::vector<std::string> getDeviceByType(const bool& playback) const; - - PaDeviceIndex indexIn_; - PaDeviceIndex indexOut_; - PaDeviceIndex indexRing_; - - AudioBuffer playbackBuff_; - - std::shared_ptr<RingBuffer> mainRingBuffer_; - - enum Direction {Input=0, Output=1, End=2}; - PaStream* streams[(int)Direction::End]; - - static int paOutputCallback(const void *inputBuffer, void* outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData); - - static int paInputCallback(const void *inputBuffer, void* outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData); - }; - } + struct PortAudioLayerImpl; + std::unique_ptr<PortAudioLayerImpl> pimpl_; +}; -#endif +} // namespace ring