Merge branch 'v0.21.x'

This commit is contained in:
Max Kellermann 2020-07-02 15:33:13 +02:00
commit 86823af685
8 changed files with 201 additions and 90 deletions

1
NEWS
View File

@ -51,6 +51,7 @@ ver 0.21.25 (not yet released)
- opus: fix memory leak - opus: fix memory leak
* output * output
- osx: improve sample rate selection - osx: improve sample rate selection
- osx: fix noise while stopping
* Windows/Android: * Windows/Android:
- fix Boost detection after breaking change in Meson 0.54 - fix Boost detection after breaking change in Meson 0.54

View File

@ -350,6 +350,8 @@ subdir('src/thread')
subdir('src/net') subdir('src/net')
subdir('src/event') subdir('src/event')
subdir('src/apple')
subdir('src/lib/dbus') subdir('src/lib/dbus')
subdir('src/lib/icu') subdir('src/lib/icu')
subdir('src/lib/smbclient') subdir('src/lib/smbclient')

40
src/apple/AudioObject.cxx Normal file
View File

@ -0,0 +1,40 @@
/*
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "AudioObject.hxx"
#include "StringRef.hxx"
Apple::StringRef
AudioObjectGetStringProperty(AudioObjectID inObjectID,
const AudioObjectPropertyAddress &inAddress)
{
auto s = AudioObjectGetPropertyDataT<CFStringRef>(inObjectID,
inAddress);
return Apple::StringRef(s);
}

View File

@ -37,7 +37,11 @@
#include <cstddef> #include <cstddef>
std::size_t namespace Apple {
class StringRef;
}
inline std::size_t
AudioObjectGetPropertyDataSize(AudioObjectID inObjectID, AudioObjectGetPropertyDataSize(AudioObjectID inObjectID,
const AudioObjectPropertyAddress &inAddress) const AudioObjectPropertyAddress &inAddress)
{ {
@ -69,6 +73,10 @@ AudioObjectGetPropertyDataT(AudioObjectID inObjectID,
return value; return value;
} }
Apple::StringRef
AudioObjectGetStringProperty(AudioObjectID inObjectID,
const AudioObjectPropertyAddress &inAddress);
template<typename T> template<typename T>
AllocatedArray<T> AllocatedArray<T>
AudioObjectGetPropertyDataArray(AudioObjectID inObjectID, AudioObjectGetPropertyDataArray(AudioObjectID inObjectID,

View File

@ -52,4 +52,60 @@ AudioUnitGetPropertyT(AudioUnit inUnit, AudioUnitPropertyID inID,
return value; return value;
} }
template<typename T>
void
AudioUnitSetPropertyT(AudioUnit inUnit, AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement,
const T &value)
{
OSStatus status = AudioUnitSetProperty(inUnit, inID, inScope,
inElement,
&value, sizeof(value));
if (status != noErr)
Apple::ThrowOSStatus(status);
}
inline void
AudioUnitSetCurrentDevice(AudioUnit inUnit, const AudioDeviceID &value)
{
AudioUnitSetPropertyT(inUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0,
value);
}
inline void
AudioUnitSetInputStreamFormat(AudioUnit inUnit,
const AudioStreamBasicDescription &value)
{
AudioUnitSetPropertyT(inUnit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
value);
}
inline void
AudioUnitSetInputRenderCallback(AudioUnit inUnit,
const AURenderCallbackStruct &value)
{
AudioUnitSetPropertyT(inUnit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0,
value);
}
inline UInt32
AudioUnitGetBufferFrameSize(AudioUnit inUnit)
{
return AudioUnitGetPropertyT<UInt32>(inUnit,
kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global, 0);
}
inline void
AudioUnitSetBufferFrameSize(AudioUnit inUnit, const UInt32 &value)
{
AudioUnitSetPropertyT(inUnit, kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global, 0,
value);
}
#endif #endif

28
src/apple/meson.build Normal file
View File

@ -0,0 +1,28 @@
if not is_darwin
apple_dep = dependency('', required: false)
subdir_done()
endif
audiounit_dep = declare_dependency(
link_args: ['-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreServices'],
dependencies: [
boost_dep,
],
)
apple = static_library(
'apple',
'AudioObject.cxx',
'Throw.cxx',
include_directories: inc,
dependencies: [
audiounit_dep,
],
)
apple_dep = declare_dependency(
link_with: apple,
dependencies: [
audiounit_dep,
],
)

View File

@ -73,7 +73,14 @@ struct OSXOutput final : AudioOutput {
const char *device_name; const char *device_name;
const char *const channel_map; const char *const channel_map;
const bool hog_device; const bool hog_device;
bool pause; bool pause;
/**
* Is the audio unit "started", i.e. was AudioOutputUnitStart() called?
*/
bool started;
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
/** /**
* Enable DSD over PCM according to the DoP standard? * Enable DSD over PCM according to the DoP standard?
@ -450,24 +457,14 @@ osx_output_set_buffer_size(AudioUnit au, AudioStreamBasicDescription desc)
kAudioUnitScope_Global, kAudioUnitScope_Global,
0); 0);
UInt32 buffer_frame_size = value_range.mMaximum; try {
OSStatus err; AudioUnitSetBufferFrameSize(au, value_range.mMaximum);
err = AudioUnitSetProperty(au, } catch (...) {
kAudioDevicePropertyBufferFrameSize, LogError(std::current_exception(),
kAudioUnitScope_Global, "Failed to set maximum buffer size");
0, }
&buffer_frame_size,
sizeof(buffer_frame_size));
if (err != noErr)
FormatWarning(osx_output_domain,
"Failed to set maximum buffer size: %d",
err);
buffer_frame_size = AudioUnitGetPropertyT<UInt32>(au,
kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global,
0);
auto buffer_frame_size = AudioUnitGetBufferFrameSize(au);
buffer_frame_size *= desc.mBytesPerFrame; buffer_frame_size *= desc.mBytesPerFrame;
// We set the frame size to a power of two integer that // We set the frame size to a power of two integer that
@ -535,19 +532,15 @@ IsAudioDeviceName(AudioDeviceID id, const char *expected_name) noexcept
kAudioObjectPropertyElementMaster, kAudioObjectPropertyElementMaster,
}; };
CFStringRef cfname;
UInt32 size = sizeof(cfname);
if (AudioObjectGetPropertyData(id, &aopa_name,
0, nullptr,
&size, &cfname) != noErr)
return false;
const Apple::StringRef cfname_(cfname);
char actual_name[256]; char actual_name[256];
if (!cfname_.GetCString(actual_name, sizeof(actual_name)))
try {
auto cfname = AudioObjectGetStringProperty(id, aopa_name);
if (!cfname.GetCString(actual_name, sizeof(actual_name)))
return false; return false;
} catch (...) {
return false;
}
return StringIsEqual(actual_name, expected_name); return StringIsEqual(actual_name, expected_name);
} }
@ -587,15 +580,7 @@ osx_output_set_device(OSXOutput *oo)
"found matching device: ID=%u, name=%s", "found matching device: ID=%u, name=%s",
(unsigned)id, oo->device_name); (unsigned)id, oo->device_name);
OSStatus status; AudioUnitSetCurrentDevice(oo->au, id);
status = AudioUnitSetProperty(oo->au,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
0,
&id, sizeof(id));
if (status != noErr)
Apple::ThrowOSStatus(status,
"Unable to set OS X audio output device");
oo->dev_id = id; oo->dev_id = id;
FormatDebug(osx_output_domain, FormatDebug(osx_output_domain,
@ -682,6 +667,7 @@ OSXOutput::Disable() noexcept
void void
OSXOutput::Close() noexcept OSXOutput::Close() noexcept
{ {
if (started)
AudioOutputUnitStop(au); AudioOutputUnitStop(au);
AudioUnitUninitialize(au); AudioUnitUninitialize(au);
delete ring_buffer; delete ring_buffer;
@ -745,29 +731,15 @@ OSXOutput::Open(AudioFormat &audio_format)
dop_enabled = params.dsd_mode == PcmExport::DsdMode::DOP; dop_enabled = params.dsd_mode == PcmExport::DsdMode::DOP;
#endif #endif
OSStatus status = AudioUnitSetInputStreamFormat(au, asbd);
AudioUnitSetProperty(au, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
&asbd,
sizeof(asbd));
if (status != noErr)
throw std::runtime_error("Unable to set format on OS X device");
AURenderCallbackStruct callback; AURenderCallbackStruct callback;
callback.inputProc = osx_render; callback.inputProc = osx_render;
callback.inputProcRefCon = this; callback.inputProcRefCon = this;
status = AudioUnitSetInputRenderCallback(au, callback);
AudioUnitSetProperty(au,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0,
&callback, sizeof(callback));
if (status != noErr) {
AudioComponentInstanceDispose(au);
throw std::runtime_error("Unable to set callback for OS X audio unit");
}
status = AudioUnitInitialize(au); OSStatus status = AudioUnitInitialize(au);
if (status != noErr) if (status != noErr)
Apple::ThrowOSStatus(status, "Unable to initialize OS X audio unit"); Apple::ThrowOSStatus(status, "Unable to initialize OS X audio unit");
@ -785,36 +757,43 @@ OSXOutput::Open(AudioFormat &audio_format)
#endif #endif
ring_buffer = new boost::lockfree::spsc_queue<uint8_t>(ring_buffer_size); ring_buffer = new boost::lockfree::spsc_queue<uint8_t>(ring_buffer_size);
status = AudioOutputUnitStart(au);
if (status != 0)
Apple::ThrowOSStatus(status, "Unable to start audio output");
pause = false; pause = false;
started = false;
} }
size_t size_t
OSXOutput::Play(const void *chunk, size_t size) OSXOutput::Play(const void *chunk, size_t size)
{ {
assert(size > 0); assert(size > 0);
if (pause) {
pause = false; pause = false;
OSStatus status = AudioOutputUnitStart(au);
if (status != 0) { ConstBuffer<uint8_t> input((const uint8_t *)chunk, size);
AudioUnitUninitialize(au);
throw std::runtime_error("Unable to restart audio output after pause");
}
}
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
if (dop_enabled) { if (dop_enabled) {
const auto e = pcm_export->Export({chunk, size}); input = ConstBuffer<uint8_t>::FromVoid(pcm_export->Export(input.ToVoid()));
if (e.empty()) if (input.empty())
return size; return size;
size_t bytes_written = ring_buffer->push((const uint8_t *)e.data, e.size);
return pcm_export->CalcInputSize(bytes_written);
} }
#endif #endif
return ring_buffer->push((const uint8_t *)chunk, size);
size_t bytes_written = ring_buffer->push(input.data, input.size);
if (!started) {
OSStatus status = AudioOutputUnitStart(au);
if (status != noErr)
throw std::runtime_error("Unable to restart audio output after pause");
started = true;
}
#ifdef ENABLE_DSD
if (dop_enabled)
bytes_written = pcm_export->CalcInputSize(bytes_written);
#endif
return bytes_written;
} }
std::chrono::steady_clock::duration std::chrono::steady_clock::duration
@ -827,22 +806,30 @@ OSXOutput::Delay() const noexcept
bool OSXOutput::Pause() bool OSXOutput::Pause()
{ {
if (!pause) {
pause = true; pause = true;
if (started) {
AudioOutputUnitStop(au); AudioOutputUnitStop(au);
started = false;
} }
return true; return true;
} }
void void
OSXOutput::Cancel() noexcept OSXOutput::Cancel() noexcept
{ {
if (started) {
AudioOutputUnitStop(au); AudioOutputUnitStop(au);
started = false;
}
ring_buffer->reset(); ring_buffer->reset();
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
pcm_export->Reset(); pcm_export->Reset();
#endif #endif
AudioOutputUnitStart(au);
/* the AudioUnit will be restarted by the next Play() call */
} }
int int

View File

@ -76,18 +76,7 @@ endif
if is_darwin if is_darwin
output_plugins_sources += [ output_plugins_sources += [
'OSXOutputPlugin.cxx', 'OSXOutputPlugin.cxx',
'../../apple/Throw.cxx',
] ]
audiounit_dep = declare_dependency(
link_args: [
'-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreServices',
],
dependencies: [
boost_dep,
],
)
else
audiounit_dep = dependency('', required: false)
endif endif
output_features.set('HAVE_OSX', is_darwin) output_features.set('HAVE_OSX', is_darwin)
@ -163,7 +152,7 @@ output_plugins = static_library(
include_directories: inc, include_directories: inc,
dependencies: [ dependencies: [
alsa_dep, alsa_dep,
audiounit_dep, apple_dep,
libao_dep, libao_dep,
libjack_dep, libjack_dep,
pulse_dep, pulse_dep,