985 lines
29 KiB
C
985 lines
29 KiB
C
/*
|
|
* Copyright (C) 2015 Yue Wang <yuleopen@gmail.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
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdatomic.h>
|
|
#include <AudioUnit/AudioUnit.h>
|
|
#include <CoreAudio/CoreAudio.h>
|
|
|
|
#include "../debug.h"
|
|
#include "../op.h"
|
|
#include "../mixer.h"
|
|
#include "../sf.h"
|
|
#include "../utils.h"
|
|
#include "../xmalloc.h"
|
|
|
|
#if !defined(MAC_OS_VERSION_12_0) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_12_0
|
|
#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Ring buffer utility from the PortAudio project.
|
|
// Original licence information is listed below.
|
|
|
|
/*
|
|
* Portable Audio I/O Library
|
|
* Ring Buffer utility.
|
|
*
|
|
* Author: Phil Burk, http://www.softsynth.com
|
|
* modified for SMP safety on Mac OS X by Bjorn Roche
|
|
* modified for SMP safety on Linux by Leland Lucius
|
|
* also, allowed for const where possible
|
|
* Note that this is safe only for a single-thread reader and a
|
|
* single-thread writer.
|
|
*
|
|
* This program uses the PortAudio Portable Audio Library.
|
|
* For more information see: http://www.portaudio.com
|
|
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files
|
|
* (the "Software"), to deal in the Software without restriction,
|
|
* including without limitation the rights to use, copy, modify, merge,
|
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
* and to permit persons to whom the Software is furnished to do so,
|
|
* subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
|
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
/*
|
|
* The text above constitutes the entire PortAudio license; however,
|
|
* the PortAudio community also makes the following non-binding requests:
|
|
*
|
|
* Any person wishing to distribute modifications to the Software is
|
|
* requested to send the modifications to the original developer so that
|
|
* they can be incorporated into the canonical version. It is also
|
|
* requested that these non-binding requests be included along with the
|
|
* license above.
|
|
*/
|
|
|
|
typedef struct coreaudio_ring_buffer_t {
|
|
size_t buffer_size;
|
|
size_t write_index;
|
|
size_t read_index;
|
|
size_t big_mask;
|
|
size_t small_mask;
|
|
char *buffer;
|
|
} coreaudio_ring_buffer_t;
|
|
|
|
static int coreaudio_ring_buffer_init(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes);
|
|
static void coreaudio_ring_buffer_destroy(coreaudio_ring_buffer_t *rbuf);
|
|
static void coreaudio_ring_buffer_flush(coreaudio_ring_buffer_t *rbuf);
|
|
static size_t coreaudio_ring_buffer_write_available(coreaudio_ring_buffer_t *rbuf);
|
|
static size_t coreaudio_ring_buffer_read_available(coreaudio_ring_buffer_t *rbuf);
|
|
static size_t coreaudio_ring_buffer_write(coreaudio_ring_buffer_t *rbuf, const char *data, size_t num_of_bytes);
|
|
static size_t coreaudio_ring_buffer_read(coreaudio_ring_buffer_t *rbuf, char *data, size_t num_of_bytes);
|
|
static size_t coreaudio_ring_buffer_write_regions(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes, char **data_ptr1, size_t *size_ptr1, char **data_ptr2, size_t *size_ptr2);
|
|
static size_t coreaudio_ring_buffer_advance_write_index(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes);
|
|
static size_t coreaudio_ring_buffer_read_regions(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes, char **data_ptr1, size_t *size_ptr1, char **data_ptr2, size_t *size_ptr2);
|
|
static size_t coreaudio_ring_buffer_advance_read_index(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes);
|
|
|
|
static int coreaudio_ring_buffer_init(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes)
|
|
{
|
|
if (((num_of_bytes - 1) & num_of_bytes) != 0)
|
|
return -1; /*Not Power of two. */
|
|
rbuf->buffer_size = num_of_bytes;
|
|
rbuf->buffer = (char *)xmalloc(num_of_bytes);
|
|
coreaudio_ring_buffer_flush(rbuf);
|
|
rbuf->big_mask = (num_of_bytes *2) - 1;
|
|
rbuf->small_mask = (num_of_bytes) - 1;
|
|
return 0;
|
|
}
|
|
|
|
static void coreaudio_ring_buffer_destroy(coreaudio_ring_buffer_t *rbuf)
|
|
{
|
|
if (rbuf->buffer)
|
|
free(rbuf->buffer);
|
|
rbuf->buffer = NULL;
|
|
rbuf->buffer_size = 0;
|
|
rbuf->write_index = 0;
|
|
rbuf->read_index = 0;
|
|
rbuf->big_mask = 0;
|
|
rbuf->small_mask = 0;
|
|
}
|
|
|
|
static size_t coreaudio_ring_buffer_read_available(coreaudio_ring_buffer_t *rbuf)
|
|
{
|
|
atomic_thread_fence(memory_order_seq_cst);
|
|
return ((rbuf->write_index - rbuf->read_index) & rbuf->big_mask);
|
|
}
|
|
|
|
static size_t coreaudio_ring_buffer_write_available(coreaudio_ring_buffer_t *rbuf)
|
|
{
|
|
return (rbuf->buffer_size - coreaudio_ring_buffer_read_available(rbuf));
|
|
}
|
|
|
|
static void coreaudio_ring_buffer_flush(coreaudio_ring_buffer_t *rbuf)
|
|
{
|
|
rbuf->write_index = rbuf->read_index = 0;
|
|
}
|
|
|
|
static size_t coreaudio_ring_buffer_write_regions(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes, char **data_ptr1, size_t *size_ptr1, char **data_ptr2, size_t *size_ptr2)
|
|
{
|
|
size_t index;
|
|
size_t available = coreaudio_ring_buffer_write_available(rbuf);
|
|
if (num_of_bytes > available)
|
|
num_of_bytes = available;
|
|
index = rbuf->write_index & rbuf->small_mask;
|
|
if ((index + num_of_bytes) > rbuf->buffer_size) {
|
|
size_t first_half = rbuf->buffer_size - index;
|
|
*data_ptr1 = &rbuf->buffer[index];
|
|
*size_ptr1 = first_half;
|
|
*data_ptr2 = &rbuf->buffer[0];
|
|
*size_ptr2 = num_of_bytes - first_half;
|
|
} else {
|
|
*data_ptr1 = &rbuf->buffer[index];
|
|
*size_ptr1 = num_of_bytes;
|
|
*data_ptr2 = NULL;
|
|
*size_ptr2 = 0;
|
|
}
|
|
return num_of_bytes;
|
|
}
|
|
|
|
static size_t coreaudio_ring_buffer_advance_write_index(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes)
|
|
{
|
|
atomic_thread_fence(memory_order_seq_cst);
|
|
return rbuf->write_index = (rbuf->write_index + num_of_bytes) & rbuf->big_mask;
|
|
}
|
|
|
|
static size_t coreaudio_ring_buffer_read_regions(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes, char **data_ptr1, size_t *size_ptr1, char **data_ptr2, size_t *size_ptr2)
|
|
{
|
|
size_t index;
|
|
size_t available = coreaudio_ring_buffer_read_available(rbuf);
|
|
if (num_of_bytes > available)
|
|
num_of_bytes = available;
|
|
index = rbuf->read_index & rbuf->small_mask;
|
|
if ((index + num_of_bytes) > rbuf->buffer_size) {
|
|
size_t first_half = rbuf->buffer_size - index;
|
|
*data_ptr1 = &rbuf->buffer[index];
|
|
*size_ptr1 = first_half;
|
|
*data_ptr2 = &rbuf->buffer[0];
|
|
*size_ptr2 = num_of_bytes - first_half;
|
|
} else {
|
|
*data_ptr1 = &rbuf->buffer[index];
|
|
*size_ptr1 = num_of_bytes;
|
|
*data_ptr2 = NULL;
|
|
*size_ptr2 = 0;
|
|
}
|
|
return num_of_bytes;
|
|
}
|
|
|
|
static size_t coreaudio_ring_buffer_advance_read_index(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes)
|
|
{
|
|
atomic_thread_fence(memory_order_seq_cst);
|
|
return rbuf->read_index = (rbuf->read_index + num_of_bytes) & rbuf->big_mask;
|
|
}
|
|
|
|
static size_t coreaudio_ring_buffer_write(coreaudio_ring_buffer_t *rbuf, const char *data, size_t num_of_bytes)
|
|
{
|
|
size_t size1, size2, num_write;
|
|
char *data1, *data2;
|
|
num_write = coreaudio_ring_buffer_write_regions(rbuf, num_of_bytes, &data1, &size1, &data2, &size2);
|
|
if (size2 > 0) {
|
|
memcpy(data1, data, size1);
|
|
data = ((char *) data) + size1;
|
|
memcpy(data2, data, size2);
|
|
} else {
|
|
memcpy(data1, data, size1);
|
|
}
|
|
coreaudio_ring_buffer_advance_write_index(rbuf, num_write);
|
|
return num_write;
|
|
}
|
|
|
|
static size_t coreaudio_ring_buffer_read(coreaudio_ring_buffer_t *rbuf, char *data, size_t num_of_bytes)
|
|
{
|
|
size_t size1, size2, num_read;
|
|
char *data1, *data2;
|
|
num_read = coreaudio_ring_buffer_read_regions(rbuf, num_of_bytes, &data1, &size1, &data2, &size2);
|
|
if (size2 > 0) {
|
|
memcpy(data, data1, size1);
|
|
data = ((char *) data) + size1;
|
|
memcpy(data, data2, size2);
|
|
} else {
|
|
memcpy(data, data1, size1);
|
|
}
|
|
coreaudio_ring_buffer_advance_read_index(rbuf, num_read);
|
|
return num_read;
|
|
}
|
|
|
|
// End of ring buffer utility from the PortAudio project.
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static char *coreaudio_opt_device_name = NULL;
|
|
static bool coreaudio_opt_enable_hog_mode = false;
|
|
static bool coreaudio_opt_sync_rate = false;
|
|
|
|
static int coreaudio_max_volume = 100;
|
|
static AudioDeviceID coreaudio_device_id = kAudioDeviceUnknown;
|
|
static AudioStreamBasicDescription coreaudio_format_description;
|
|
static AudioUnit coreaudio_audio_unit = NULL;
|
|
static UInt32 coreaudio_buffer_frame_size = 1;
|
|
static coreaudio_ring_buffer_t coreaudio_ring_buffer = {0, 0, 0, 0, 0, NULL};
|
|
static UInt32 coreaudio_stereo_channels[2];
|
|
static int coreaudio_mixer_pipe_in = 0;
|
|
static int coreaudio_mixer_pipe_out = 0;
|
|
|
|
static OSStatus coreaudio_device_volume_change_listener(AudioObjectID inObjectID,
|
|
UInt32 inNumberAddresses,
|
|
const AudioObjectPropertyAddress inAddresses[],
|
|
void *inClientData)
|
|
{
|
|
notify_via_pipe(coreaudio_mixer_pipe_in);
|
|
return noErr;
|
|
}
|
|
|
|
static OSStatus coreaudio_play_callback(void *user_data,
|
|
AudioUnitRenderActionFlags *flags,
|
|
const AudioTimeStamp *ts,
|
|
UInt32 busnum,
|
|
UInt32 nframes,
|
|
AudioBufferList *buflist)
|
|
{
|
|
int count = nframes * coreaudio_format_description.mBytesPerFrame;
|
|
buflist->mBuffers[0].mDataByteSize = coreaudio_ring_buffer_read(&coreaudio_ring_buffer,
|
|
buflist->mBuffers[0].mData,
|
|
count);
|
|
return noErr;
|
|
}
|
|
|
|
static AudioDeviceID coreaudio_get_default_device()
|
|
{
|
|
AudioObjectPropertyAddress aopa = {
|
|
kAudioHardwarePropertyDefaultOutputDevice,
|
|
kAudioObjectPropertyScopeOutput,
|
|
kAudioObjectPropertyElementMain
|
|
};
|
|
|
|
AudioDeviceID dev_id = kAudioDeviceUnknown;
|
|
UInt32 dev_id_size = sizeof(dev_id);
|
|
AudioObjectGetPropertyData(kAudioObjectSystemObject,
|
|
&aopa,
|
|
0,
|
|
NULL,
|
|
&dev_id_size,
|
|
&dev_id);
|
|
return dev_id;
|
|
}
|
|
|
|
static AudioDeviceID coreaudio_find_device(const char *dev_name)
|
|
{
|
|
AudioObjectPropertyAddress aopa = {
|
|
kAudioHardwarePropertyDevices,
|
|
kAudioObjectPropertyScopeOutput,
|
|
kAudioObjectPropertyElementMain
|
|
};
|
|
|
|
UInt32 property_size = 0;
|
|
OSStatus err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
|
|
&aopa,
|
|
0,
|
|
NULL,
|
|
&property_size);
|
|
if (err != noErr)
|
|
return kAudioDeviceUnknown;
|
|
|
|
aopa.mSelector = kAudioHardwarePropertyDevices;
|
|
int device_count = property_size / sizeof(AudioDeviceID);
|
|
AudioDeviceID devices[device_count];
|
|
property_size = sizeof(devices);
|
|
|
|
err = AudioObjectGetPropertyData(kAudioObjectSystemObject,
|
|
&aopa,
|
|
0,
|
|
NULL,
|
|
&property_size,
|
|
devices);
|
|
if (err != noErr)
|
|
return kAudioDeviceUnknown;
|
|
|
|
aopa.mSelector = kAudioDevicePropertyDeviceName;
|
|
for (int i = 0; i < device_count; i++) {
|
|
char name[256] = {0};
|
|
property_size = sizeof(name);
|
|
err = AudioObjectGetPropertyData(devices[i],
|
|
&aopa,
|
|
0,
|
|
NULL,
|
|
&property_size,
|
|
name);
|
|
if (err == noErr && strcmp(name, dev_name) == 0) {
|
|
return devices[i];
|
|
}
|
|
}
|
|
|
|
return kAudioDeviceUnknown;
|
|
}
|
|
|
|
static const struct {
|
|
channel_position_t pos;
|
|
const AudioChannelLabel label;
|
|
} coreaudio_channel_mapping[] = {
|
|
{ CHANNEL_POSITION_LEFT, kAudioChannelLabel_Left },
|
|
{ CHANNEL_POSITION_RIGHT, kAudioChannelLabel_Right },
|
|
{ CHANNEL_POSITION_CENTER, kAudioChannelLabel_Center },
|
|
{ CHANNEL_POSITION_LFE, kAudioChannelLabel_LFEScreen },
|
|
{ CHANNEL_POSITION_SIDE_LEFT, kAudioChannelLabel_LeftSurround },
|
|
{ CHANNEL_POSITION_SIDE_RIGHT, kAudioChannelLabel_RightSurround },
|
|
{ CHANNEL_POSITION_MONO, kAudioChannelLabel_Mono },
|
|
{ CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, kAudioChannelLabel_LeftCenter },
|
|
{ CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, kAudioChannelLabel_RightCenter },
|
|
{ CHANNEL_POSITION_REAR_LEFT, kAudioChannelLabel_LeftSurroundDirect },
|
|
{ CHANNEL_POSITION_REAR_RIGHT, kAudioChannelLabel_RightSurroundDirect },
|
|
{ CHANNEL_POSITION_REAR_CENTER, kAudioChannelLabel_CenterSurround },
|
|
{ CHANNEL_POSITION_INVALID, kAudioChannelLabel_Unknown },
|
|
};
|
|
|
|
static void coreaudio_set_channel_position(AudioDeviceID dev_id,
|
|
int channels,
|
|
const channel_position_t *map)
|
|
{
|
|
AudioObjectPropertyAddress aopa = {
|
|
kAudioDevicePropertyPreferredChannelLayout,
|
|
kAudioObjectPropertyScopeOutput,
|
|
kAudioObjectPropertyElementMain
|
|
};
|
|
AudioChannelLayout *layout = NULL;
|
|
size_t layout_size = (size_t) &layout->mChannelDescriptions[channels];
|
|
layout = (AudioChannelLayout*)xmalloc(layout_size);
|
|
layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions ;
|
|
layout->mChannelBitmap = 0;
|
|
layout->mNumberChannelDescriptions = channels;
|
|
AudioChannelDescription *descriptions = layout->mChannelDescriptions;
|
|
for (int i = 0; i < channels; i++) {
|
|
const channel_position_t pos = map[i];
|
|
AudioChannelLabel label = kAudioChannelLabel_Mono;
|
|
for (int j = 0; j < N_ELEMENTS(coreaudio_channel_mapping); j++) {
|
|
if (pos == coreaudio_channel_mapping[j].pos) {
|
|
label = coreaudio_channel_mapping[j].label;
|
|
break;
|
|
}
|
|
}
|
|
descriptions[channels - 1 - i].mChannelLabel = label;
|
|
descriptions[i].mChannelFlags = kAudioChannelFlags_AllOff;
|
|
descriptions[i].mCoordinates[0] = 0;
|
|
descriptions[i].mCoordinates[1] = 0;
|
|
descriptions[i].mCoordinates[2] = 0;
|
|
}
|
|
OSStatus err =
|
|
AudioObjectSetPropertyData(dev_id,
|
|
&aopa,
|
|
0, NULL, layout_size, layout);
|
|
if (err != noErr)
|
|
d_print("Cannot set the channel layout successfully.\n");
|
|
free(layout);
|
|
}
|
|
|
|
|
|
static AudioStreamBasicDescription coreaudio_fill_format_description(sample_format_t sf)
|
|
{
|
|
AudioStreamBasicDescription desc = {
|
|
.mSampleRate = (Float64)sf_get_rate(sf),
|
|
.mFormatID = kAudioFormatLinearPCM,
|
|
.mFormatFlags = kAudioFormatFlagIsPacked,
|
|
.mBytesPerPacket = sf_get_frame_size(sf),
|
|
.mFramesPerPacket = 1,
|
|
.mChannelsPerFrame = sf_get_channels(sf),
|
|
.mBitsPerChannel = sf_get_bits(sf),
|
|
.mBytesPerFrame = sf_get_frame_size(sf),
|
|
};
|
|
|
|
d_print("Bits:%d\n", sf_get_bits(sf));
|
|
if (sf_get_bigendian(sf))
|
|
desc.mFormatFlags |= kAudioFormatFlagIsBigEndian;
|
|
if (sf_get_signed(sf))
|
|
desc.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
|
|
|
|
return desc;
|
|
}
|
|
|
|
static void coreaudio_sync_device_sample_rate(AudioDeviceID dev_id, AudioStreamBasicDescription desc)
|
|
{
|
|
AudioObjectPropertyAddress aopa = {
|
|
kAudioDevicePropertyAvailableNominalSampleRates,
|
|
kAudioObjectPropertyScopeOutput,
|
|
kAudioObjectPropertyElementMain
|
|
};
|
|
|
|
UInt32 property_size;
|
|
OSStatus err = AudioObjectGetPropertyDataSize(dev_id,
|
|
&aopa,
|
|
0,
|
|
NULL,
|
|
&property_size);
|
|
|
|
int count = property_size/sizeof(AudioValueRange);
|
|
AudioValueRange ranges[count];
|
|
property_size = sizeof(ranges);
|
|
err = AudioObjectGetPropertyData(dev_id,
|
|
&aopa,
|
|
0,
|
|
NULL,
|
|
&property_size,
|
|
&ranges);
|
|
// Get the maximum sample rate as fallback.
|
|
Float64 sample_rate = .0;
|
|
for (int i = 0; i < count; i++) {
|
|
if (ranges[i].mMaximum > sample_rate)
|
|
sample_rate = ranges[i].mMaximum;
|
|
}
|
|
|
|
// Now try to see if the device support our format sample rate.
|
|
// For some high quality media samples, the frame rate may exceed
|
|
// device capability. In this case, we let CoreAudio downsample
|
|
// by decimation with an integer factor ranging from 1 to 4.
|
|
for (int f = 4; f > 0; f--) {
|
|
Float64 rate = desc.mSampleRate / f;
|
|
for (int i = 0; i < count; i++) {
|
|
if (ranges[i].mMinimum <= rate
|
|
&& rate <= ranges[i].mMaximum) {
|
|
sample_rate = rate;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
aopa.mSelector = kAudioDevicePropertyNominalSampleRate,
|
|
|
|
err = AudioObjectSetPropertyData(dev_id,
|
|
&aopa,
|
|
0,
|
|
NULL,
|
|
sizeof(&desc.mSampleRate),
|
|
&sample_rate);
|
|
if (err != noErr)
|
|
d_print("Failed to synchronize the sample rate: %d\n", err);
|
|
}
|
|
|
|
static void coreaudio_hog_device(AudioDeviceID dev_id, bool hog)
|
|
{
|
|
pid_t hog_pid;
|
|
AudioObjectPropertyAddress aopa = {
|
|
kAudioDevicePropertyHogMode,
|
|
kAudioObjectPropertyScopeOutput,
|
|
kAudioObjectPropertyElementMain
|
|
};
|
|
UInt32 size = sizeof(hog_pid);
|
|
OSStatus err = AudioObjectGetPropertyData(dev_id,
|
|
&aopa,
|
|
0,
|
|
NULL,
|
|
&size,
|
|
&hog_pid);
|
|
if (err != noErr) {
|
|
d_print("Cannot get hog information: %d\n", err);
|
|
return;
|
|
}
|
|
if (hog) {
|
|
if (hog_pid != -1) {
|
|
d_print("Device is already hogged.\n");
|
|
return;
|
|
}
|
|
} else {
|
|
if (hog_pid != getpid()) {
|
|
d_print("Device is not owned by this process.\n");
|
|
return;
|
|
}
|
|
}
|
|
hog_pid = hog ? getpid() : -1;
|
|
size = sizeof(hog_pid);
|
|
err = AudioObjectSetPropertyData(dev_id,
|
|
&aopa,
|
|
0,
|
|
NULL,
|
|
size,
|
|
&hog_pid);
|
|
if (err != noErr)
|
|
d_print("Cannot hog the device: %d\n", err);
|
|
}
|
|
|
|
static OSStatus coreaudio_set_buffer_size(AudioUnit au, AudioStreamBasicDescription desc, int *frame_size)
|
|
{
|
|
AudioValueRange value_range = {0, 0};
|
|
UInt32 property_size = sizeof(AudioValueRange);
|
|
OSStatus err = AudioUnitGetProperty(au,
|
|
kAudioDevicePropertyBufferFrameSizeRange,
|
|
kAudioUnitScope_Global,
|
|
0,
|
|
&value_range,
|
|
&property_size);
|
|
if (err != noErr)
|
|
return err;
|
|
|
|
UInt32 buffer_frame_size = value_range.mMaximum;
|
|
err = AudioUnitSetProperty(au,
|
|
kAudioDevicePropertyBufferFrameSize,
|
|
kAudioUnitScope_Global,
|
|
0,
|
|
&buffer_frame_size,
|
|
sizeof(buffer_frame_size));
|
|
if (err != noErr)
|
|
d_print("Failed to set maximum buffer size: %d\n", err);
|
|
|
|
property_size = sizeof(buffer_frame_size);
|
|
err = AudioUnitGetProperty(au,
|
|
kAudioDevicePropertyBufferFrameSize,
|
|
kAudioUnitScope_Global,
|
|
0,
|
|
&buffer_frame_size,
|
|
&property_size);
|
|
if (err != noErr) {
|
|
d_print("Cannot get the buffer frame size: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
buffer_frame_size *= desc.mBytesPerFrame;
|
|
|
|
// We set the frame size to a power of two integer that
|
|
// is larger than buffer_frame_size.
|
|
while (*frame_size < buffer_frame_size + 1) {
|
|
*frame_size <<= 1;
|
|
}
|
|
|
|
return noErr;
|
|
}
|
|
|
|
static OSStatus coreaudio_init_audio_unit(AudioUnit *au,
|
|
OSType os_type,
|
|
AudioDeviceID dev_id)
|
|
{
|
|
OSStatus err;
|
|
AudioComponentDescription comp_desc = {
|
|
kAudioUnitType_Output,
|
|
os_type,
|
|
kAudioUnitManufacturer_Apple,
|
|
0,
|
|
0
|
|
};
|
|
|
|
AudioComponent comp = AudioComponentFindNext(0, &comp_desc);
|
|
if (!comp) {
|
|
return -1;
|
|
}
|
|
|
|
err = AudioComponentInstanceNew(comp, au);
|
|
if (err != noErr)
|
|
return err;
|
|
|
|
if (os_type == kAudioUnitSubType_HALOutput) {
|
|
err = AudioUnitSetProperty(*au,
|
|
kAudioOutputUnitProperty_CurrentDevice,
|
|
kAudioUnitScope_Global,
|
|
0,
|
|
&dev_id,
|
|
sizeof(dev_id));
|
|
if (err != noErr)
|
|
return err;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static OSStatus coreaudio_start_audio_unit(AudioUnit *au,
|
|
int *frame_size,
|
|
AudioStreamBasicDescription desc)
|
|
{
|
|
|
|
OSStatus err;
|
|
err = AudioUnitSetProperty(*au,
|
|
kAudioUnitProperty_StreamFormat,
|
|
kAudioUnitScope_Input,
|
|
0,
|
|
&desc,
|
|
sizeof(desc));
|
|
if (err != noErr)
|
|
return err;
|
|
|
|
AURenderCallbackStruct cb = {
|
|
.inputProc = coreaudio_play_callback,
|
|
.inputProcRefCon = NULL,
|
|
};
|
|
err = AudioUnitSetProperty(*au,
|
|
kAudioUnitProperty_SetRenderCallback,
|
|
kAudioUnitScope_Input,
|
|
0,
|
|
&cb,
|
|
sizeof(cb));
|
|
if (err != noErr)
|
|
return err;
|
|
|
|
err = AudioUnitInitialize(*au);
|
|
if (err != noErr)
|
|
return err;
|
|
|
|
err = coreaudio_set_buffer_size(*au, desc, frame_size);
|
|
if (err != noErr)
|
|
return err;
|
|
|
|
return AudioOutputUnitStart(*au);
|
|
}
|
|
|
|
static int coreaudio_init(void)
|
|
{
|
|
AudioDeviceID default_dev_id = coreaudio_get_default_device();
|
|
if (default_dev_id == kAudioDeviceUnknown) {
|
|
errno = ENODEV;
|
|
return -OP_ERROR_ERRNO;
|
|
}
|
|
|
|
AudioDeviceID named_dev_id = kAudioDeviceUnknown;
|
|
if (coreaudio_opt_device_name)
|
|
named_dev_id = coreaudio_find_device(coreaudio_opt_device_name);
|
|
|
|
coreaudio_device_id = named_dev_id != kAudioDeviceUnknown ? named_dev_id : default_dev_id;
|
|
|
|
if (named_dev_id != kAudioDeviceUnknown && coreaudio_opt_enable_hog_mode)
|
|
coreaudio_hog_device(coreaudio_device_id, true);
|
|
|
|
OSType unit_subtype = named_dev_id != kAudioDeviceUnknown ?
|
|
kAudioUnitSubType_HALOutput :
|
|
kAudioUnitSubType_DefaultOutput;
|
|
OSStatus err = coreaudio_init_audio_unit(&coreaudio_audio_unit,
|
|
unit_subtype,
|
|
coreaudio_device_id);
|
|
if (err != noErr) {
|
|
errno = ENODEV;
|
|
return -OP_ERROR_ERRNO;
|
|
}
|
|
return OP_ERROR_SUCCESS;
|
|
}
|
|
|
|
static int coreaudio_exit(void)
|
|
{
|
|
AudioComponentInstanceDispose(coreaudio_audio_unit);
|
|
coreaudio_audio_unit = NULL;
|
|
coreaudio_hog_device(coreaudio_device_id, false);
|
|
AudioHardwareUnload();
|
|
coreaudio_device_id = kAudioDeviceUnknown;
|
|
return OP_ERROR_SUCCESS;
|
|
}
|
|
|
|
static int coreaudio_open(sample_format_t sf, const channel_position_t *channel_map)
|
|
{
|
|
|
|
coreaudio_format_description = coreaudio_fill_format_description(sf);
|
|
if (coreaudio_opt_sync_rate)
|
|
coreaudio_sync_device_sample_rate(coreaudio_device_id, coreaudio_format_description);
|
|
if (channel_map)
|
|
coreaudio_set_channel_position(coreaudio_device_id,
|
|
coreaudio_format_description.mChannelsPerFrame,
|
|
channel_map);
|
|
OSStatus err = coreaudio_start_audio_unit(&coreaudio_audio_unit,
|
|
&coreaudio_buffer_frame_size,
|
|
coreaudio_format_description);
|
|
if (err)
|
|
return -OP_ERROR_SAMPLE_FORMAT;
|
|
coreaudio_ring_buffer_init(&coreaudio_ring_buffer, coreaudio_buffer_frame_size);
|
|
return OP_ERROR_SUCCESS;
|
|
}
|
|
|
|
static int coreaudio_close(void)
|
|
{
|
|
AudioOutputUnitStop(coreaudio_audio_unit);
|
|
AudioUnitUninitialize(coreaudio_audio_unit);
|
|
coreaudio_ring_buffer_destroy(&coreaudio_ring_buffer);
|
|
return OP_ERROR_SUCCESS;
|
|
}
|
|
|
|
static int coreaudio_drop(void)
|
|
{
|
|
coreaudio_ring_buffer_flush(&coreaudio_ring_buffer);
|
|
return OP_ERROR_SUCCESS;
|
|
}
|
|
|
|
static int coreaudio_write(const char *buf, int cnt)
|
|
{
|
|
return coreaudio_ring_buffer_write(&coreaudio_ring_buffer, buf, cnt);
|
|
}
|
|
|
|
static OSStatus coreaudio_get_device_stereo_channels(AudioDeviceID dev_id, UInt32 *channels) {
|
|
AudioObjectPropertyAddress aopa = {
|
|
kAudioDevicePropertyPreferredChannelsForStereo,
|
|
kAudioObjectPropertyScopeOutput,
|
|
kAudioObjectPropertyElementMain
|
|
};
|
|
UInt32 size = sizeof(UInt32[2]);
|
|
OSStatus err = AudioObjectGetPropertyData(dev_id,
|
|
&aopa,
|
|
0,
|
|
NULL,
|
|
&size,
|
|
channels);
|
|
return err;
|
|
}
|
|
|
|
static int coreaudio_mixer_set_volume(int l, int r)
|
|
{
|
|
Float32 vol[2];
|
|
OSStatus err = 0;
|
|
for (int i = 0; i < 2; i++) {
|
|
vol[i] = (i == 0 ? l : r) * 1.0f / coreaudio_max_volume;
|
|
if (vol[i] > 1.0f)
|
|
vol[i] = 1.0f;
|
|
if (vol[i] < 0.0f)
|
|
vol[i] = 0.0f;
|
|
AudioObjectPropertyAddress aopa = {
|
|
.mSelector = kAudioDevicePropertyVolumeScalar,
|
|
.mScope = kAudioObjectPropertyScopeOutput,
|
|
.mElement = coreaudio_stereo_channels[i]
|
|
};
|
|
|
|
UInt32 size = sizeof(vol[i]);
|
|
err |= AudioObjectSetPropertyData(coreaudio_device_id,
|
|
&aopa,
|
|
0,
|
|
NULL,
|
|
size,
|
|
vol + i);
|
|
}
|
|
if (err != noErr) {
|
|
errno = ENODEV;
|
|
return -OP_ERROR_ERRNO;
|
|
}
|
|
return OP_ERROR_SUCCESS;
|
|
}
|
|
|
|
static int coreaudio_mixer_get_volume(int *l, int *r)
|
|
{
|
|
clear_pipe(coreaudio_mixer_pipe_out, -1);
|
|
Float32 vol[2] = {.0, .0};
|
|
OSStatus err = 0;
|
|
for (int i = 0; i < 2; i++) {
|
|
AudioObjectPropertyAddress aopa = {
|
|
.mSelector = kAudioDevicePropertyVolumeScalar,
|
|
.mScope = kAudioObjectPropertyScopeOutput,
|
|
.mElement = coreaudio_stereo_channels[i]
|
|
};
|
|
UInt32 size = sizeof(vol[i]);
|
|
err |= AudioObjectGetPropertyData(coreaudio_device_id,
|
|
&aopa,
|
|
0,
|
|
NULL,
|
|
&size,
|
|
vol + i);
|
|
int volume = vol[i] * coreaudio_max_volume;
|
|
if (volume > coreaudio_max_volume)
|
|
volume = coreaudio_max_volume;
|
|
if (volume < 0)
|
|
volume = 0;
|
|
if (i == 0) {
|
|
*l = volume;
|
|
} else {
|
|
*r = volume;
|
|
}
|
|
}
|
|
if (err != noErr) {
|
|
errno = ENODEV;
|
|
return -OP_ERROR_ERRNO;
|
|
}
|
|
return OP_ERROR_SUCCESS;
|
|
}
|
|
|
|
static int coreaudio_mixer_open(int *volume_max)
|
|
{
|
|
*volume_max = coreaudio_max_volume;
|
|
OSStatus err = coreaudio_get_device_stereo_channels(coreaudio_device_id, coreaudio_stereo_channels);
|
|
if (err != noErr) {
|
|
d_print("Cannot get channel information: %d\n", err);
|
|
errno = ENODEV;
|
|
return -OP_ERROR_ERRNO;
|
|
}
|
|
for (int i = 0; i < 2; i++) {
|
|
AudioObjectPropertyAddress aopa = {
|
|
.mSelector = kAudioDevicePropertyVolumeScalar,
|
|
.mScope = kAudioObjectPropertyScopeOutput,
|
|
.mElement = coreaudio_stereo_channels[i]
|
|
};
|
|
err |= AudioObjectAddPropertyListener(coreaudio_device_id,
|
|
&aopa,
|
|
coreaudio_device_volume_change_listener,
|
|
NULL);
|
|
}
|
|
if (err != noErr) {
|
|
d_print("Cannot add property listener: %d\n", err);
|
|
errno = ENODEV;
|
|
return -OP_ERROR_ERRNO;
|
|
}
|
|
init_pipes(&coreaudio_mixer_pipe_out, &coreaudio_mixer_pipe_in);
|
|
return OP_ERROR_SUCCESS;
|
|
}
|
|
|
|
static int coreaudio_mixer_close(void)
|
|
{
|
|
OSStatus err = noErr;
|
|
for (int i = 0; i < 2; i++) {
|
|
AudioObjectPropertyAddress aopa = {
|
|
.mSelector = kAudioDevicePropertyVolumeScalar,
|
|
.mScope = kAudioObjectPropertyScopeOutput,
|
|
.mElement = coreaudio_stereo_channels[i]
|
|
};
|
|
|
|
err |= AudioObjectRemovePropertyListener(coreaudio_device_id,
|
|
&aopa,
|
|
coreaudio_device_volume_change_listener,
|
|
NULL);
|
|
}
|
|
if (err != noErr) {
|
|
d_print("Cannot remove property listener: %d\n", err);
|
|
errno = ENODEV;
|
|
return -OP_ERROR_ERRNO;
|
|
}
|
|
close(coreaudio_mixer_pipe_out);
|
|
close(coreaudio_mixer_pipe_in);
|
|
return OP_ERROR_SUCCESS;
|
|
}
|
|
|
|
static int coreaudio_mixer_dummy(void)
|
|
{
|
|
return OP_ERROR_SUCCESS;
|
|
}
|
|
|
|
static int coreaudio_mixer_get_fds(int what, int *fds)
|
|
{
|
|
switch (what) {
|
|
case MIXER_FDS_VOLUME:
|
|
fds[0] = coreaudio_mixer_pipe_out;
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int coreaudio_pause(void)
|
|
{
|
|
OSStatus err = AudioOutputUnitStop(coreaudio_audio_unit);
|
|
if (err != noErr) {
|
|
errno = ENODEV;
|
|
return -OP_ERROR_ERRNO;
|
|
}
|
|
return OP_ERROR_SUCCESS;
|
|
}
|
|
|
|
static int coreaudio_unpause(void)
|
|
{
|
|
OSStatus err = AudioOutputUnitStart(coreaudio_audio_unit);
|
|
if (err != noErr) {
|
|
errno = ENODEV;
|
|
return -OP_ERROR_ERRNO;
|
|
}
|
|
return OP_ERROR_SUCCESS;
|
|
}
|
|
|
|
static int coreaudio_buffer_space(void)
|
|
{
|
|
return coreaudio_ring_buffer_write_available(&coreaudio_ring_buffer);
|
|
}
|
|
|
|
static int coreaudio_set_sync_sample_rate(const char *val)
|
|
{
|
|
coreaudio_opt_sync_rate = strcmp(val, "true") ? false : true;
|
|
if (coreaudio_opt_sync_rate)
|
|
coreaudio_sync_device_sample_rate(coreaudio_device_id, coreaudio_format_description);
|
|
return 0;
|
|
}
|
|
|
|
static int coreaudio_get_sync_sample_rate(char **val)
|
|
{
|
|
*val = xstrdup(coreaudio_opt_sync_rate ? "true" : "false");
|
|
return 0;
|
|
}
|
|
|
|
static int coreaudio_set_enable_hog_mode(const char *val)
|
|
{
|
|
coreaudio_opt_enable_hog_mode = strcmp(val, "true") ? false : true;
|
|
coreaudio_hog_device(coreaudio_device_id, coreaudio_opt_enable_hog_mode);
|
|
return 0;
|
|
}
|
|
|
|
static int coreaudio_get_enable_hog_mode(char **val)
|
|
{
|
|
*val = xstrdup(coreaudio_opt_enable_hog_mode ? "true" : "false");
|
|
return 0;
|
|
}
|
|
|
|
static int coreaudio_set_device(const char *val)
|
|
{
|
|
free(coreaudio_opt_device_name);
|
|
coreaudio_opt_device_name = NULL;
|
|
if (val[0])
|
|
coreaudio_opt_device_name = xstrdup(val);
|
|
return 0;
|
|
}
|
|
|
|
static int coreaudio_get_device(char **val)
|
|
{
|
|
if (coreaudio_opt_device_name)
|
|
*val = xstrdup(coreaudio_opt_device_name);
|
|
return 0;
|
|
}
|
|
|
|
const struct output_plugin_ops op_pcm_ops = {
|
|
.init = coreaudio_init,
|
|
.exit = coreaudio_exit,
|
|
.open = coreaudio_open,
|
|
.close = coreaudio_close,
|
|
.drop = coreaudio_drop,
|
|
.write = coreaudio_write,
|
|
.pause = coreaudio_pause,
|
|
.unpause = coreaudio_unpause,
|
|
.buffer_space = coreaudio_buffer_space,
|
|
};
|
|
|
|
|
|
const struct mixer_plugin_ops op_mixer_ops = {
|
|
.init = coreaudio_mixer_dummy,
|
|
.exit = coreaudio_mixer_dummy,
|
|
.open = coreaudio_mixer_open,
|
|
.close = coreaudio_mixer_close,
|
|
.get_fds.abi_2 = coreaudio_mixer_get_fds,
|
|
.set_volume = coreaudio_mixer_set_volume,
|
|
.get_volume = coreaudio_mixer_get_volume,
|
|
};
|
|
|
|
const struct output_plugin_opt op_pcm_options[] = {
|
|
OPT(coreaudio, device),
|
|
OPT(coreaudio, enable_hog_mode),
|
|
OPT(coreaudio, sync_sample_rate),
|
|
{ NULL },
|
|
};
|
|
|
|
const struct mixer_plugin_opt op_mixer_options[] = {
|
|
{ NULL },
|
|
};
|
|
|
|
const int op_priority = 1;
|
|
const unsigned op_abi_version = OP_ABI_VERSION;
|