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:
parent
afd5b750dc
commit
c28cefeeb0
@ -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,
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user