output/osx: wait-free render callback

Closes https://bugs.musicpd.org/view.php?id=4537 .

Removed the 'cancel' function because it violates 'single producer,
single consumer'.
This commit is contained in:
Jacob Vosmaer 2016-08-05 23:42:25 +02:00
parent afd5b750dc
commit c28cefeeb0

View File

@ -20,7 +20,6 @@
#include "config.h" #include "config.h"
#include "OSXOutputPlugin.hxx" #include "OSXOutputPlugin.hxx"
#include "../OutputAPI.hxx" #include "../OutputAPI.hxx"
#include "util/DynamicFifoBuffer.hxx"
#include "util/Error.hxx" #include "util/Error.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "thread/Mutex.hxx" #include "thread/Mutex.hxx"
@ -32,6 +31,8 @@
#include <AudioUnit/AudioUnit.h> #include <AudioUnit/AudioUnit.h>
#include <CoreServices/CoreServices.h> #include <CoreServices/CoreServices.h>
#include<boost/lockfree/spsc_queue.hpp>
struct OSXOutput { struct OSXOutput {
AudioOutput base; AudioOutput base;
@ -43,10 +44,13 @@ struct OSXOutput {
AudioComponentInstance au; AudioComponentInstance au;
AudioStreamBasicDescription asbd; AudioStreamBasicDescription asbd;
Mutex mutex; Mutex mutex;
Cond condition; Cond condition;
DynamicFifoBuffer<uint8_t> *buffer; boost::lockfree::spsc_queue<uint8_t> *ring_buffer;
size_t render_buffer_size;
uint8_t *render_buffer;
OSXOutput() OSXOutput()
:base(osx_output_plugin) {} :base(osx_output_plugin) {}
@ -378,11 +382,9 @@ osx_render(void *vdata,
AudioBufferList *buffer_list) AudioBufferList *buffer_list)
{ {
AudioBuffer *output_buffer = nullptr; AudioBuffer *output_buffer = nullptr;
size_t output_buffer_frame_size, dest; size_t output_buffer_frame_size;
OSXOutput *od = (OSXOutput *) vdata; OSXOutput *od = (OSXOutput *) vdata;
DynamicFifoBuffer<uint8_t> *input_buffer = od->buffer;
assert(input_buffer != nullptr);
/* /*
By convention when interfacing with audio hardware in CoreAudio, By convention when interfacing with audio hardware in CoreAudio,
@ -403,15 +405,28 @@ osx_render(void *vdata,
size_t input_buffer_frame_size = od->asbd.mBytesPerFrame; size_t input_buffer_frame_size = od->asbd.mBytesPerFrame;
size_t sample_size = input_buffer_frame_size / input_channel_count; size_t sample_size = input_buffer_frame_size / input_channel_count;
// Acquire mutex when accessing input_buffer size_t requested_bytes = in_number_frames * input_buffer_frame_size;
od->mutex.lock(); if (requested_bytes > od->render_buffer_size)
requested_bytes = od->render_buffer_size;
DynamicFifoBuffer<uint8_t>::Range src = input_buffer->Read(); size_t available_bytes = od->ring_buffer->pop(od->render_buffer, requested_bytes);
size_t wraparound_remainder = available_bytes % input_buffer_frame_size;
UInt32 available_frames = src.size / input_buffer_frame_size; /*
// Never write more frames than we were asked Maybe this is paranoid but we have no way of knowing
if (available_frames > in_number_frames) if the 'pop' above ended at a frame boundary. In case
available_frames = in_number_frames; of an incomplete last frame, do a second pop to get
enough bytes to complete the last frame.
*/
if (wraparound_remainder > 0)
available_bytes += od->ring_buffer->pop(
od->render_buffer + available_bytes,
input_buffer_frame_size - wraparound_remainder
);
od->condition.signal(); // We are done consuming from ring_buffer
UInt32 available_frames = available_bytes / input_buffer_frame_size;
/* /*
To de-interleave the data in the input buffer so that it fits in To de-interleave the data in the input buffer so that it fits in
@ -424,10 +439,9 @@ osx_render(void *vdata,
output_buffer_frame_size = output_buffer->mNumberChannels * sample_size; output_buffer_frame_size = output_buffer->mNumberChannels * sample_size;
output_buffer->mDataByteSize = 0; // Record how much data we actually rendered output_buffer->mDataByteSize = 0; // Record how much data we actually rendered
for (UInt32 current_frame = 0; current_frame < available_frames; ++current_frame) { for (UInt32 current_frame = 0; current_frame < available_frames; ++current_frame) {
dest = (size_t) output_buffer->mData + current_frame * output_buffer_frame_size;
memcpy( memcpy(
(void *) dest, (uint8_t *) output_buffer->mData + current_frame * output_buffer_frame_size,
src.data + current_frame * input_buffer_frame_size + sub_frame_offset, od->render_buffer + current_frame * input_buffer_frame_size + sub_frame_offset,
output_buffer_frame_size output_buffer_frame_size
); );
output_buffer->mDataByteSize += output_buffer_frame_size; output_buffer->mDataByteSize += output_buffer_frame_size;
@ -435,11 +449,6 @@ osx_render(void *vdata,
sub_frame_offset += output_buffer_frame_size; sub_frame_offset += output_buffer_frame_size;
} }
input_buffer->Consume(available_frames * input_buffer_frame_size);
od->condition.signal();
od->mutex.unlock();
return noErr; return noErr;
} }
@ -504,15 +513,6 @@ osx_output_disable(AudioOutput *ao)
AudioComponentInstanceDispose(oo->au); AudioComponentInstanceDispose(oo->au);
} }
static void
osx_output_cancel(AudioOutput *ao)
{
OSXOutput *od = (OSXOutput *)ao;
const ScopeLock protect(od->mutex);
od->buffer->Clear();
}
static void static void
osx_output_close(AudioOutput *ao) osx_output_close(AudioOutput *ao)
{ {
@ -521,7 +521,8 @@ osx_output_close(AudioOutput *ao)
AudioOutputUnitStop(od->au); AudioOutputUnitStop(od->au);
AudioUnitUninitialize(od->au); AudioUnitUninitialize(od->au);
delete od->buffer; delete od->ring_buffer;
delete[] od->render_buffer;
} }
static bool static bool
@ -583,9 +584,16 @@ osx_output_open(AudioOutput *ao, AudioFormat &audio_format,
return false; return false;
} }
/* create a buffer of 1s */ /* create a ring buffer of 1s */
od->buffer = new DynamicFifoBuffer<uint8_t>(audio_format.sample_rate * od->ring_buffer = new boost::lockfree::spsc_queue<uint8_t>(audio_format.sample_rate * audio_format.GetFrameSize());
audio_format.GetFrameSize());
/*
od->render_buffer_size is the maximum amount of data we
render in the render callback. Allocate enough space
for 0.1 s of frames.
*/
od->render_buffer_size = (audio_format.sample_rate/10) * audio_format.GetFrameSize();
od->render_buffer = new uint8_t[od->render_buffer_size];
status = AudioOutputUnitStart(od->au); status = AudioOutputUnitStart(od->au);
if (status != 0) { if (status != 0) {
@ -606,25 +614,15 @@ osx_output_play(AudioOutput *ao, const void *chunk, size_t size,
{ {
OSXOutput *od = (OSXOutput *)ao; OSXOutput *od = (OSXOutput *)ao;
const ScopeLock protect(od->mutex);
DynamicFifoBuffer<uint8_t>::Range dest;
while (true) { while (true) {
dest = od->buffer->Write(); if (od->ring_buffer->write_available() > 0)
if (!dest.IsEmpty())
break; break;
/* wait for some free space in the buffer */ /* wait for some free space in the buffer */
od->condition.wait(od->mutex); od->condition.wait(od->mutex);
} }
if (size > dest.size) return od->ring_buffer->push((uint8_t *) chunk, size);
size = dest.size;
memcpy(dest.data, chunk, size);
od->buffer->Append(size);
return size;
} }
const struct AudioOutputPlugin osx_output_plugin = { const struct AudioOutputPlugin osx_output_plugin = {
@ -640,7 +638,7 @@ const struct AudioOutputPlugin osx_output_plugin = {
nullptr, nullptr,
osx_output_play, osx_output_play,
nullptr, nullptr,
osx_output_cancel, nullptr,
nullptr, nullptr,
nullptr, nullptr,
}; };