# Portaudio device

**URL:** <https://forum.juce.com/t/portaudio-device/8465>\
**Category:** Useful Tools and Components\
**Created:** [April 12, 2012, 10:26pm UTC](https://forum.juce.com/t/portaudio-device/8465 "2012-04-12T22:26:11Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![jpo](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/jpo/32/503_2.png) [@jpo](https://forum.juce.com/u/jpo)\
**Post date:** [April 12, 2012, 10:26pm UTC](https://forum.juce.com/t/portaudio-device/8465/1 "2012-04-12T22:26:11Z")

</div>

I have written an AudioIODeviceType implementation for portaudio devices. My motivation was to test portaudio’s WDM/KS implementation. It may also be useful for testing other hostapis and compare them with the juce implementation – for example portaudio has WASAPI in exclusive mode, and also ALSA soft-devices . The problem with portaudio is that it does not provide a list of buffersizes, and does not provides the current buffer size outside of audiocallbacks. Although the code should support audio input, I have only tested audio output, so it is quite likely that there are bugs with audio input. The code is public domain, you can do whatever you want with it:

[code]#include \<portaudio.h\>  
#include \<juce.h\>

namespace {

struct DeviceInfo {  
PaDeviceIndex index;  
const PaDeviceInfo \*pa\_info;  
DeviceInfo() : index(-1), pa\_info(0) {}  
bool empty() const { return pa\_info == 0; }  
// the PA devs have decided to use uft8 inside all strings – this is at least the case for the wdmks stuff.  
String name() const { return pa\_info ? String::fromUTF8(pa\_info-\>name) : String::empty; }  
};

}

class PAAudioIODevice : public AudioIODevice {  
const PaHostApiInfo \*hostapi\_info;  
DeviceInfo input\_di, output\_di;

Array sample\_rates, buffer\_sizes;  
int default\_buffer\_size;  
int default\_sample\_rate;

int current\_sample\_rate;  
int current\_buffer\_size;

BigInteger channels\_in, channels\_out;

String last\_error;  
PaStream _pa\_stream;  
AudioIODeviceCallback_ callback;  
bool started;  
public:  
StringArray getOutputChannelNames() {  
StringArray outChannels;  
if (output\_di.pa\_info \>= 0) {  
for (int i = 1; i \<= output\_di.pa\_info-\>maxOutputChannels; ++i)  
outChannels.add("Output channel " + String (i));  
}  
return outChannels;  
}

StringArray getInputChannelNames() {  
StringArray inChannels;  
if (input\_di.pa\_info) {  
for (int i = 1; i \<= input\_di.pa\_info-\>maxInputChannels; ++i)  
inChannels.add ("Input channel " + String (i));  
}  
return inChannels;  
}

int getNumSampleRates() { return sample\_rates.size(); }  
double getSampleRate (int index) { return sample\_rates[index]; }  
int getNumBufferSizesAvailable() { return buffer\_sizes.size(); }  
int getBufferSizeSamples (int index) { return buffer\_sizes[index]; }  
int getDefaultBufferSize() { return default\_buffer\_size; }

int getCurrentBufferSizeSamples() { return current\_buffer\_size; }  
double getCurrentSampleRate() { return current\_sample\_rate; }  
int getCurrentBitDepth() { return 32; }  
int getOutputLatencyInSamples() { return 0; }  
int getInputLatencyInSamples() { return 0; }  
BigInteger getActiveOutputChannels() const { return channels\_out; }  
BigInteger getActiveInputChannels() const { return channels\_in; }  
String getLastError() { return last\_error; }

PAAudioIODevice(const String &device\_name, const String &type\_name,  
const PaHostApiInfo \*info, const DeviceInfo \*p\_input\_di, const DeviceInfo \*p\_output\_di)  
: AudioIODevice(device\_name, type\_name)  
{  
hostapi\_info = info;  
if (p\_input\_di)  
input\_di = \*p\_input\_di;  
if (p\_output\_di)  
output\_di = \*p\_output\_di;  
default\_buffer\_size = 0;  
current\_sample\_rate = 0;  
current\_buffer\_size = 0;  
pa\_stream = 0;  
callback = 0;  
started = false;  
}

~PAAudioIODevice() {  
close();  
}

void initPaStreamParameters(PaStreamParameters &in, PaStreamParameters &out) {  
memset(&in, 0, sizeof in); memset(&out, 0, sizeof out);

```
if (input_di.pa_info) {
  in.device = input_di.index;
  in.channelCount = input_di.pa_info->maxInputChannels;
  in.sampleFormat = paFloat32 | paNonInterleaved;
  in.suggestedLatency = input_di.pa_info->defaultLowOutputLatency;
  in.hostApiSpecificStreamInfo = 0;
}

if (output_di.pa_info) {
  out.device = output_di.index;
  out.channelCount = output_di.pa_info->maxOutputChannels;
  out.sampleFormat = paFloat32 | paNonInterleaved;
  out.suggestedLatency = output_di.pa_info->defaultLowOutputLatency;
  out.hostApiSpecificStreamInfo = 0;
}

```

}

String open (const BigInteger& inputChannels, const BigInteger& outputChannels,  
double sampleRate, int bufferSizeSamples) {  
close();  
last\_error = String::empty;

```
if (sample_rates.size() == 0 && input_di.pa_info && output_di.pa_info) {
  last_error = "The input and output devices don't share a common sample rate!";
  return last_error;
}

current_buffer_size = bufferSizeSamples <= 0 ? default_buffer_size : jmax (bufferSizeSamples, buffer_sizes[0]);
current_sample_rate = sampleRate > 0 ? sampleRate : default_sample_rate;

PaStreamParameters in, out;
initPaStreamParameters(in, out);
if (input_di.pa_info && !inputChannels.isZero()) {
  in.channelCount = jmin(inputChannels.getHighestBit() + 1, in.channelCount);
  channels_in = inputChannels; 
  channels_in.setRange(in.channelCount, channels_in.getHighestBit() + 1 - in.channelCount, false);
  in.suggestedLatency = current_buffer_size / (double)jmax(current_sample_rate, 1);
} else channels_in = 0;

if (output_di.pa_info && !outputChannels.isZero()) {
  out.channelCount = jmin(outputChannels.getHighestBit() + 1, out.channelCount);
  channels_out = outputChannels; 
  channels_out.setRange(out.channelCount, channels_out.getHighestBit() + 1 - out.channelCount, false);
  out.suggestedLatency = current_buffer_size / (double)jmax(current_sample_rate, 1);
} else channels_out = 0;

PaError err;

jassert(pa_stream == 0);
err = Pa_OpenStream(&pa_stream, 
                    (channels_in.isZero() ? 0 : &in), 
                    (channels_out.isZero() ? 0 : &out), 
                    current_sample_rate, 0 /*current_buffer_size*/, paNoFlag,
                    &streamCallback_static, this);
if (err != paNoError) {
  last_error = Pa_GetErrorText(err);
  return last_error;
}

const PaStreamInfo *stream_info = Pa_GetStreamInfo(pa_stream);
if (stream_info) {
  current_sample_rate = stream_info->sampleRate;
  if (!sample_rates.contains(current_sample_rate))
    sample_rates.addUsingDefaultSort(current_sample_rate);

  // for this part to be really correct, it would require that portaudio uses only the 'framesPerBuffer'
  // stuff when computing the latency. Unfortunately it is not always the case, for example the wdm/ks 
  // driver adds hwFifoLatency to framesPerBuffer when computing outputLatency ...
  int buffer_size = stream_info->outputLatency * stream_info->sampleRate;
  if (buffer_size > 0 && buffer_size < 65536 && buffer_size != current_buffer_size) {
    current_buffer_size = buffer_size;
    if (!buffer_sizes.contains(current_buffer_size))
      buffer_sizes.addUsingDefaultSort(current_buffer_size);
  }

}

return last_error;

```

}

void close() {  
if (pa\_stream) {  
stop();  
PaError err = Pa\_CloseStream(pa\_stream);  
if (err != paNoError) {  
last\_error = Pa\_GetErrorText(err);  
}  
pa\_stream = 0;  
}  
}

bool isOpen() { return pa\_stream != 0; }  
bool isPlaying() { return pa\_stream != 0 && Pa\_IsStreamActive(pa\_stream); }

void start(AudioIODeviceCallback\* call) {  
if (isOpen() && call != nullptr && !Pa\_IsStreamActive(pa\_stream)) {  
callback = call;  
callback-\>audioDeviceAboutToStart(this);  
started = true;  
PaError err = Pa\_StartStream(pa\_stream);  
if (err != paNoError) {  
last\_error = Pa\_GetErrorText(err);  
callback-\>audioDeviceStopped();  
callback = 0;  
}  
}  
}

void stop() {  
if (Pa\_IsStreamActive(pa\_stream)) {  
PaError err = Pa\_StopStream(pa\_stream);  
started = false;  
if (err != paNoError) {  
last\_error = Pa\_GetErrorText(err);  
}  
if (callback) { callback-\>audioDeviceStopped(); }  
callback = 0;  
}  
}

bool initialise() {  
updateSupportedBufferSizes();  
updateSupportedSampleRates();  
return sample\_rates.size() && buffer\_sizes.size();  
}

private:  
void updateSupportedSampleRates() {  
sample\_rates.clear();  
bool default\_sample\_rate\_found = false;  
DeviceInfo &di = (input\_di.pa\_info ? input\_di : output\_di);  
jassert(di.pa\_info);  
default\_sample\_rate = di.pa\_info-\>defaultSampleRate;  
float freqs[] = { 44100, 48000, 88200, 96000, 176400, 192000, 0 };  
for (size\_t i=0; freqs[i]; ++i) {  
double f = freqs[i];  
PaStreamParameters in, out;  
initPaStreamParameters(in, out);  
PaError err = Pa\_IsFormatSupported(input\_di.pa\_info ? &in : 0,  
output\_di.pa\_info ? &out : 0, f);  
if (err == paFormatIsSupported) {  
sample\_rates.add(f);  
if (std::abs(default\_sample\_rate - f) \< 1.0)  
default\_sample\_rate\_found = true;  
}  
}  
if (!default\_sample\_rate\_found)  
sample\_rates.add(default\_sample\_rate);  
DefaultElementComparator cmp;  
sample\_rates.sort(cmp);  
xassert(sample\_rates.size());  
}

void updateSupportedBufferSizes() {  
buffer\_sizes.clear();  
buffer\_sizes.add(32);  
buffer\_sizes.add(64);  
buffer\_sizes.add(96);  
buffer\_sizes.add(128);  
buffer\_sizes.add(192);  
buffer\_sizes.add(256);  
buffer\_sizes.add(384);  
buffer\_sizes.add(512);  
buffer\_sizes.add(768);  
buffer\_sizes.add(1024);  
default\_buffer\_size = 256;  
}

static int streamCallback\_static(const void _input, void output,  
unsigned long frameCount,  
const PaStreamCallbackTimeInfo timeInfo,  
PaStreamCallbackFlags statusFlags,  
void userData) {  
return ((PAAudioIODevice)userData)-\>streamCallback((const float_\*)input, (float\*\*)output,  
frameCount, timeInfo, statusFlags);  
}

// data was requested with the paNonInterleaved flag =\> it is given as an array of pointers.  
int streamCallback(const float \*\*input, float \*\*output,  
unsigned long frame\_count,  
const PaStreamCallbackTimeInfo \*,  
PaStreamCallbackFlags) {  
if (callback) {  
jassert(started);  
int num\_input\_buffers = 0, num\_output\_buffers = 0;  
const float \*in[64];  
float \*out[64];  
if (!channels\_in.isZero() && input != 0) {  
for (int i=0; i \<= channels\_in.getHighestBit() && num\_input\_buffers \< 64; ++i) {  
if (channels\_in[i]) in[num\_input\_buffers++] = input[i];  
}  
xassert(num\_input\_buffers == 64 || num\_input\_buffers == channels\_in.countNumberOfSetBits());  
}

```
  if (!channels_out.isZero() && output != 0) {
    for (int i=0; i <= channels_out.getHighestBit() && num_output_buffers < 64; ++i) {
      if (channels_out[i]) out[num_output_buffers++] = output[i];
    }
    xassert(num_output_buffers == 64 || num_output_buffers == channels_out.countNumberOfSetBits());
  }
  callback->audioDeviceIOCallback (in, num_input_buffers, out, num_output_buffers, frame_count);
}
return paContinue;

```

}

};

class PAAudioIODeviceType : public AudioIODeviceType  
{  
PaHostApiTypeId hostapi\_type;  
PaHostApiIndex hostapi\_index;  
bool pa\_initialized;  
const PaHostApiInfo \*hostapi\_info;  
std::vector devices;  
StringArray output\_devices\_names, input\_devices\_names;  
public:  
PAAudioIODeviceType(const String &hostapi\_id, const String &hostapi\_name) :  
AudioIODeviceType(hostapi\_name)  
{  
pa\_initialized = (Pa\_Initialize() == paNoError);

```
if (hostapi_id == "WASAPI") hostapi_type = paWASAPI;
else if (hostapi_id == "WDMKS") hostapi_type = paWDMKS;
else if (hostapi_id == "MME") hostapi_type = paMME;
else if (hostapi_id == "ASIO") hostapi_type = paASIO;
else if (hostapi_id == "DS") hostapi_type = paDirectSound;
else if (hostapi_id == "ALSA") hostapi_type = paALSA;
else if (hostapi_id == "OSS") hostapi_type = paOSS;
else xassert(0);

hostapi_index = -1;  
hostapi_info = 0; // init by scanForDevices

if (pa_initialized) {
  // if hostapi_index <= 0 , that means portaudio has not been built with support for that hostapi.
  hostapi_index = Pa_HostApiTypeIdToHostApiIndex(hostapi_type);
}

```

}

~PAAudioIODeviceType() {  
if (pa\_initialized) {  
Pa\_Terminate();  
}  
}

bool isHostApiAvailable() const { return hostapi\_index \>= 0; }

void scanForDevices() {  
if (hostapi\_info == 0 && pa\_initialized) {  
if (hostapi\_index \>= 0) {  
// pointer returned is owned by PA, valid until Pa\_Terminate is called  
hostapi\_info = Pa\_GetHostApiInfo(hostapi\_index);  
if (hostapi\_info) xassert(hostapi\_info-\>type == hostapi\_type);  
}  
}  
devices.clear();  
output\_devices\_names.clear();  
input\_devices\_names.clear();  
if (hostapi\_info) {  
for (int idx = 0; idx \< hostapi\_info-\>deviceCount; ++idx) {  
DeviceInfo di;  
di.index = Pa\_HostApiDeviceIndexToDeviceIndex(hostapi\_index, idx);  
if (di.index \< 0) continue; // there was an error…

```
    di.pa_info = Pa_GetDeviceInfo(di.index);
    if (di.pa_info) {
      devices.push_back(di);
      if (di.pa_info->maxOutputChannels > 0) {
        output_devices_names.add(di.name());
      }
      if (di.pa_info->maxInputChannels > 0) {
        input_devices_names.add(di.name());
      }
    }
  }
}

```

}

StringArray getDeviceNames (bool wantInputNames) const {  
return wantInputNames ? input\_devices\_names : output\_devices\_names;  
}

int getDefaultDeviceIndex(bool forInput) const {  
if (hostapi\_info) {  
for (size\_t i=0; i \< devices.size(); ++i) {  
if (forInput) {  
if (devices[i].index == hostapi\_info-\>defaultInputDevice) {  
return input\_devices\_names.indexOf(devices[i].name());  
}  
} else {  
if (devices[i].index == hostapi\_info-\>defaultOutputDevice) {  
return output\_devices\_names.indexOf(devices[i].name());  
}  
}  
}  
}  
return 0;  
}

int getIndexOfDevice (AudioIODevice\* device, bool asInput) const  
{  
PAAudioIODevice\* const d = dynamic\_cast \<PAAudioIODevice\*\> (device);  
return d == nullptr ? -1 : (asInput ? input\_devices\_names.indexOf(device-\>getName())  
: output\_devices\_names.indexOf(device-\>getName()));  
}

bool hasSeparateInputsAndOutputs() const { return true; }

DeviceInfo \*findDeviceInfo(const String &name) {  
for (size\_t i=0; i \< devices.size(); ++i) {  
DeviceInfo \*d = &devices[i];  
if (!d-\>empty()) {  
if (name == d-\>name()) return d;  
}  
}  
return 0;  
}

AudioIODevice \*createDevice(const String &outputDeviceName, const String &inputDeviceName) {  
ScopedPointer device;

```
DeviceInfo *in = findDeviceInfo(inputDeviceName);
DeviceInfo *out = findDeviceInfo(outputDeviceName);

if (in && in->pa_info->maxInputChannels == 0) in = 0;
if (out && out->pa_info->maxOutputChannels == 0) out = 0;
if (in == 0 && out == 0) return 0;

device = new PAAudioIODevice(out ? outputDeviceName : inputDeviceName, AudioIODeviceType::getTypeName(), hostapi_info, in, out);
if (! device->initialise()) {
  device = nullptr;
}
return device.release();

```

}

};

AudioIODeviceType\* createAudioIODeviceType\_PortAudio(const String &hostapi, const String &hostapi\_name)  
{  
PAAudioIODeviceType \*p = new PAAudioIODeviceType(hostapi, hostapi\_name);  
if (!p-\>isHostApiAvailable()) deleteAndZero§;  
return p;  
}  
[/code]

In order to use it, subclass the juce AudioDeviceManager, and override its createAudioDevices(OwnedArray\< AudioIODeviceType \> &list) method, with, for example:

```auto
    AudioDeviceManager::createAudioDeviceTypes(list);
    addIfNotNull(list, createAudioIODeviceType_PortAudio("ALSA", "Portaudio ALSA"));
    addIfNotNull(list, createAudioIODeviceType_PortAudio("WDMKS", "WDM/KS"));
    addIfNotNull(list, createAudioIODeviceType_PortAudio("MME", "Windows Multimedia"));
```

---

<div class="post-metadata">

**Author:** ![sonic59](https://avatars.discourse-cdn.com/v4/letter/s/a8b319/32.png) [@sonic59](https://forum.juce.com/u/sonic59)\
**Post date:** [June 7, 2012, 9:45pm UTC](https://forum.juce.com/t/portaudio-device/8465/2 "2012-06-07T21:45:08Z")

</div>

Was wondering what the results of your test were.  
Did you find WDM KS to be superior to Juce’s existing non-exclusive WASAPI?

---

<div class="post-metadata">

**Author:** ![jpo](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/jpo/32/503_2.png) [@jpo](https://forum.juce.com/u/jpo)\
**Post date:** [June 7, 2012, 10:38pm UTC](https://forum.juce.com/t/portaudio-device/8465/3 "2012-06-07T22:38:14Z")

</div>

Yes it was, the latency was almost as low as with asio (with less choices of buffer sizes). However I felt that ASIO4ALL was still a bit better at avoiding cracklings with low buffer sizes and high cpu loads. I had also one tester that had an old xp computer where the wdm/ks initialisation code was crashing. I have been too lazy to try to identify the issue, and gave up on wdm/ks

---

<div class="post-metadata">

**Author:** ![Mike](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/mike/32/629_2.png) [@Mike](https://forum.juce.com/u/Mike)\
**Post date:** [December 1, 2012, 9:07pm UTC](https://forum.juce.com/t/portaudio-device/8465/4 "2012-12-01T21:07:22Z")

</div>

I tried your class for fun, but I can’t really get it to work properly.

I tried WDMKS, WASAPI, ASIO, but I Couldn’t really get the buffer sizes to stick or apply.  
Have you done any more work on the class? I would be quite interested in testing it.

---

<div class="post-metadata">

**Author:** ![jpo](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/jpo/32/503_2.png) [@jpo](https://forum.juce.com/u/jpo)\
**Post date:** [December 2, 2012, 10:11am UTC](https://forum.juce.com/t/portaudio-device/8465/5 "2012-12-02T10:11:54Z")

</div>

No, since I gave up on wdmks I have not worked further on it. However it seemed to work well. If you’re not getting the actual buffer size that you are requesting, I’m not surprised, this is the way portaudio works: it expects an “output latency” and it picks a buffer size given this latency but there is no simple formula that maps latency to actual buffer size
