coreaudio: Handle output device change
An output device change can occur when plugging or unplugging an earphone. Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com> Message-Id: <20210311151512.22096-3-akihiko.odaki@gmail.com> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
This commit is contained in:
parent
7d6948cd98
commit
3ba6e3f688
@ -41,19 +41,21 @@ typedef struct coreaudioVoiceOut {
|
|||||||
UInt32 audioDevicePropertyBufferFrameSize;
|
UInt32 audioDevicePropertyBufferFrameSize;
|
||||||
AudioStreamBasicDescription outputStreamBasicDescription;
|
AudioStreamBasicDescription outputStreamBasicDescription;
|
||||||
AudioDeviceIOProcID ioprocid;
|
AudioDeviceIOProcID ioprocid;
|
||||||
|
bool enabled;
|
||||||
} coreaudioVoiceOut;
|
} coreaudioVoiceOut;
|
||||||
|
|
||||||
static OSStatus coreaudio_get_voice(AudioDeviceID *id)
|
static const AudioObjectPropertyAddress voice_addr = {
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*id);
|
|
||||||
AudioObjectPropertyAddress addr = {
|
|
||||||
kAudioHardwarePropertyDefaultOutputDevice,
|
kAudioHardwarePropertyDefaultOutputDevice,
|
||||||
kAudioObjectPropertyScopeGlobal,
|
kAudioObjectPropertyScopeGlobal,
|
||||||
kAudioObjectPropertyElementMaster
|
kAudioObjectPropertyElementMaster
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static OSStatus coreaudio_get_voice(AudioDeviceID *id)
|
||||||
|
{
|
||||||
|
UInt32 size = sizeof(*id);
|
||||||
|
|
||||||
return AudioObjectGetPropertyData(kAudioObjectSystemObject,
|
return AudioObjectGetPropertyData(kAudioObjectSystemObject,
|
||||||
&addr,
|
&voice_addr,
|
||||||
0,
|
0,
|
||||||
NULL,
|
NULL,
|
||||||
&size,
|
&size,
|
||||||
@ -258,18 +260,6 @@ static void GCC_FMT_ATTR (3, 4) coreaudio_logerr2 (
|
|||||||
#define coreaudio_playback_logerr(status, ...) \
|
#define coreaudio_playback_logerr(status, ...) \
|
||||||
coreaudio_logerr2(status, "playback", __VA_ARGS__)
|
coreaudio_logerr2(status, "playback", __VA_ARGS__)
|
||||||
|
|
||||||
static inline UInt32 isPlaying (AudioDeviceID outputDeviceID)
|
|
||||||
{
|
|
||||||
OSStatus status;
|
|
||||||
UInt32 result = 0;
|
|
||||||
status = coreaudio_get_isrunning(outputDeviceID, &result);
|
|
||||||
if (status != kAudioHardwareNoError) {
|
|
||||||
coreaudio_logerr(status,
|
|
||||||
"Could not determine whether Device is playing\n");
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coreaudio_lock (coreaudioVoiceOut *core, const char *fn_name)
|
static int coreaudio_lock (coreaudioVoiceOut *core, const char *fn_name)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
@ -341,6 +331,11 @@ static OSStatus audioDeviceIOProc(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inDevice != core->outputDeviceID) {
|
||||||
|
coreaudio_unlock (core, "audioDeviceIOProc(old device)");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
frameCount = core->audioDevicePropertyBufferFrameSize;
|
frameCount = core->audioDevicePropertyBufferFrameSize;
|
||||||
pending_frames = hw->pending_emul / hw->info.bytes_per_frame;
|
pending_frames = hw->pending_emul / hw->info.bytes_per_frame;
|
||||||
|
|
||||||
@ -392,6 +387,9 @@ static OSStatus init_out_device(coreaudioVoiceOut *core)
|
|||||||
/* get minimum and maximum buffer frame sizes */
|
/* get minimum and maximum buffer frame sizes */
|
||||||
status = coreaudio_get_framesizerange(core->outputDeviceID,
|
status = coreaudio_get_framesizerange(core->outputDeviceID,
|
||||||
&frameRange);
|
&frameRange);
|
||||||
|
if (status == kAudioHardwareBadObjectError) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_playback_logerr (status,
|
coreaudio_playback_logerr (status,
|
||||||
"Could not get device buffer frame range\n");
|
"Could not get device buffer frame range\n");
|
||||||
@ -411,6 +409,9 @@ static OSStatus init_out_device(coreaudioVoiceOut *core)
|
|||||||
/* set Buffer Frame Size */
|
/* set Buffer Frame Size */
|
||||||
status = coreaudio_set_framesize(core->outputDeviceID,
|
status = coreaudio_set_framesize(core->outputDeviceID,
|
||||||
&core->audioDevicePropertyBufferFrameSize);
|
&core->audioDevicePropertyBufferFrameSize);
|
||||||
|
if (status == kAudioHardwareBadObjectError) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_playback_logerr (status,
|
coreaudio_playback_logerr (status,
|
||||||
"Could not set device buffer frame size %" PRIu32 "\n",
|
"Could not set device buffer frame size %" PRIu32 "\n",
|
||||||
@ -421,6 +422,9 @@ static OSStatus init_out_device(coreaudioVoiceOut *core)
|
|||||||
/* get Buffer Frame Size */
|
/* get Buffer Frame Size */
|
||||||
status = coreaudio_get_framesize(core->outputDeviceID,
|
status = coreaudio_get_framesize(core->outputDeviceID,
|
||||||
&core->audioDevicePropertyBufferFrameSize);
|
&core->audioDevicePropertyBufferFrameSize);
|
||||||
|
if (status == kAudioHardwareBadObjectError) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_playback_logerr (status,
|
coreaudio_playback_logerr (status,
|
||||||
"Could not get device buffer frame size\n");
|
"Could not get device buffer frame size\n");
|
||||||
@ -431,6 +435,9 @@ static OSStatus init_out_device(coreaudioVoiceOut *core)
|
|||||||
/* get StreamFormat */
|
/* get StreamFormat */
|
||||||
status = coreaudio_get_streamformat(core->outputDeviceID,
|
status = coreaudio_get_streamformat(core->outputDeviceID,
|
||||||
&core->outputStreamBasicDescription);
|
&core->outputStreamBasicDescription);
|
||||||
|
if (status == kAudioHardwareBadObjectError) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_playback_logerr (status,
|
coreaudio_playback_logerr (status,
|
||||||
"Could not get Device Stream properties\n");
|
"Could not get Device Stream properties\n");
|
||||||
@ -441,6 +448,9 @@ static OSStatus init_out_device(coreaudioVoiceOut *core)
|
|||||||
/* set Samplerate */
|
/* set Samplerate */
|
||||||
status = coreaudio_set_streamformat(core->outputDeviceID,
|
status = coreaudio_set_streamformat(core->outputDeviceID,
|
||||||
&core->outputStreamBasicDescription);
|
&core->outputStreamBasicDescription);
|
||||||
|
if (status == kAudioHardwareBadObjectError) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_playback_logerr (status,
|
coreaudio_playback_logerr (status,
|
||||||
"Could not set samplerate %lf\n",
|
"Could not set samplerate %lf\n",
|
||||||
@ -455,6 +465,9 @@ static OSStatus init_out_device(coreaudioVoiceOut *core)
|
|||||||
audioDeviceIOProc,
|
audioDeviceIOProc,
|
||||||
&core->hw,
|
&core->hw,
|
||||||
&core->ioprocid);
|
&core->ioprocid);
|
||||||
|
if (status == kAudioHardwareBadDeviceError) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (status != kAudioHardwareNoError || core->ioprocid == NULL) {
|
if (status != kAudioHardwareNoError || core->ioprocid == NULL) {
|
||||||
coreaudio_playback_logerr (status, "Could not set IOProc\n");
|
coreaudio_playback_logerr (status, "Could not set IOProc\n");
|
||||||
core->outputDeviceID = kAudioDeviceUnknown;
|
core->outputDeviceID = kAudioDeviceUnknown;
|
||||||
@ -467,24 +480,94 @@ static OSStatus init_out_device(coreaudioVoiceOut *core)
|
|||||||
static void fini_out_device(coreaudioVoiceOut *core)
|
static void fini_out_device(coreaudioVoiceOut *core)
|
||||||
{
|
{
|
||||||
OSStatus status;
|
OSStatus status;
|
||||||
|
UInt32 isrunning;
|
||||||
|
|
||||||
/* stop playback */
|
/* stop playback */
|
||||||
if (isPlaying(core->outputDeviceID)) {
|
status = coreaudio_get_isrunning(core->outputDeviceID, &isrunning);
|
||||||
status = AudioDeviceStop(core->outputDeviceID, core->ioprocid);
|
if (status != kAudioHardwareBadObjectError) {
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
|
coreaudio_logerr(status,
|
||||||
|
"Could not determine whether Device is playing\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isrunning) {
|
||||||
|
status = AudioDeviceStop(core->outputDeviceID, core->ioprocid);
|
||||||
|
if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr(status, "Could not stop playback\n");
|
coreaudio_logerr(status, "Could not stop playback\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* remove callback */
|
/* remove callback */
|
||||||
status = AudioDeviceDestroyIOProcID(core->outputDeviceID,
|
status = AudioDeviceDestroyIOProcID(core->outputDeviceID,
|
||||||
core->ioprocid);
|
core->ioprocid);
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr(status, "Could not remove IOProc\n");
|
coreaudio_logerr(status, "Could not remove IOProc\n");
|
||||||
}
|
}
|
||||||
core->outputDeviceID = kAudioDeviceUnknown;
|
core->outputDeviceID = kAudioDeviceUnknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void update_device_playback_state(coreaudioVoiceOut *core)
|
||||||
|
{
|
||||||
|
OSStatus status;
|
||||||
|
UInt32 isrunning;
|
||||||
|
|
||||||
|
status = coreaudio_get_isrunning(core->outputDeviceID, &isrunning);
|
||||||
|
if (status != kAudioHardwareNoError) {
|
||||||
|
if (status != kAudioHardwareBadObjectError) {
|
||||||
|
coreaudio_logerr(status,
|
||||||
|
"Could not determine whether Device is playing\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (core->enabled) {
|
||||||
|
/* start playback */
|
||||||
|
if (!isrunning) {
|
||||||
|
status = AudioDeviceStart(core->outputDeviceID, core->ioprocid);
|
||||||
|
if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) {
|
||||||
|
coreaudio_logerr (status, "Could not resume playback\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* stop playback */
|
||||||
|
if (isrunning) {
|
||||||
|
status = AudioDeviceStop(core->outputDeviceID,
|
||||||
|
core->ioprocid);
|
||||||
|
if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) {
|
||||||
|
coreaudio_logerr(status, "Could not pause playback\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static OSStatus handle_voice_change(
|
||||||
|
AudioObjectID in_object_id,
|
||||||
|
UInt32 in_number_addresses,
|
||||||
|
const AudioObjectPropertyAddress *in_addresses,
|
||||||
|
void *in_client_data)
|
||||||
|
{
|
||||||
|
OSStatus status;
|
||||||
|
coreaudioVoiceOut *core = in_client_data;
|
||||||
|
|
||||||
|
if (coreaudio_lock(core, __func__)) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (core->outputDeviceID) {
|
||||||
|
fini_out_device(core);
|
||||||
|
}
|
||||||
|
|
||||||
|
status = init_out_device(core);
|
||||||
|
if (!status) {
|
||||||
|
update_device_playback_state(core);
|
||||||
|
}
|
||||||
|
|
||||||
|
coreaudio_unlock (core, __func__);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
|
static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
|
||||||
void *drv_opaque)
|
void *drv_opaque)
|
||||||
{
|
{
|
||||||
@ -499,7 +582,11 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
err = pthread_mutex_init(&core->mutex, NULL);
|
err = pthread_mutex_init(&core->mutex, NULL);
|
||||||
if (err) {
|
if (err) {
|
||||||
dolog("Could not create mutex\nReason: %s\n", strerror (err));
|
dolog("Could not create mutex\nReason: %s\n", strerror (err));
|
||||||
return -1;
|
goto mutex_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coreaudio_lock(core, __func__)) {
|
||||||
|
goto lock_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
obt_as = *as;
|
obt_as = *as;
|
||||||
@ -513,11 +600,43 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
core->bufferCount = cpdo->has_buffer_count ? cpdo->buffer_count : 4;
|
core->bufferCount = cpdo->has_buffer_count ? cpdo->buffer_count : 4;
|
||||||
core->outputStreamBasicDescription.mSampleRate = (Float64) as->freq;
|
core->outputStreamBasicDescription.mSampleRate = (Float64) as->freq;
|
||||||
|
|
||||||
if (init_out_device(core)) {
|
status = AudioObjectAddPropertyListener(kAudioObjectSystemObject,
|
||||||
return -1;
|
&voice_addr, handle_voice_change,
|
||||||
|
core);
|
||||||
|
if (status != kAudioHardwareNoError) {
|
||||||
|
coreaudio_playback_logerr (status,
|
||||||
|
"Could not listen to voice property change\n");
|
||||||
|
goto listener_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (init_out_device(core)) {
|
||||||
|
goto device_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
coreaudio_unlock(core, __func__);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
device_error:
|
||||||
|
status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject,
|
||||||
|
&voice_addr,
|
||||||
|
handle_voice_change,
|
||||||
|
core);
|
||||||
|
if (status != kAudioHardwareNoError) {
|
||||||
|
coreaudio_playback_logerr(status,
|
||||||
|
"Could not remove voice property change listener\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
listener_error:
|
||||||
|
coreaudio_unlock(core, __func__);
|
||||||
|
|
||||||
|
lock_error:
|
||||||
|
err = pthread_mutex_destroy(&core->mutex);
|
||||||
|
if (err) {
|
||||||
|
dolog("Could not destroy mutex\nReason: %s\n", strerror (err));
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_error:
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void coreaudio_fini_out (HWVoiceOut *hw)
|
static void coreaudio_fini_out (HWVoiceOut *hw)
|
||||||
@ -526,8 +645,22 @@ static void coreaudio_fini_out (HWVoiceOut *hw)
|
|||||||
int err;
|
int err;
|
||||||
coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
|
coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
|
||||||
|
|
||||||
|
if (coreaudio_lock(core, __func__)) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject,
|
||||||
|
&voice_addr,
|
||||||
|
handle_voice_change,
|
||||||
|
core);
|
||||||
|
if (status != kAudioHardwareNoError) {
|
||||||
|
coreaudio_logerr(status, "Could not remove voice property change listener\n");
|
||||||
|
}
|
||||||
|
|
||||||
fini_out_device(core);
|
fini_out_device(core);
|
||||||
|
|
||||||
|
coreaudio_unlock(core, __func__);
|
||||||
|
|
||||||
/* destroy mutex */
|
/* destroy mutex */
|
||||||
err = pthread_mutex_destroy(&core->mutex);
|
err = pthread_mutex_destroy(&core->mutex);
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -537,27 +670,16 @@ static void coreaudio_fini_out (HWVoiceOut *hw)
|
|||||||
|
|
||||||
static void coreaudio_enable_out(HWVoiceOut *hw, bool enable)
|
static void coreaudio_enable_out(HWVoiceOut *hw, bool enable)
|
||||||
{
|
{
|
||||||
OSStatus status;
|
|
||||||
coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
|
coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
|
||||||
|
|
||||||
if (enable) {
|
if (coreaudio_lock(core, __func__)) {
|
||||||
/* start playback */
|
abort();
|
||||||
if (!isPlaying(core->outputDeviceID)) {
|
|
||||||
status = AudioDeviceStart(core->outputDeviceID, core->ioprocid);
|
|
||||||
if (status != kAudioHardwareNoError) {
|
|
||||||
coreaudio_logerr (status, "Could not resume playback\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* stop playback */
|
|
||||||
if (isPlaying(core->outputDeviceID)) {
|
|
||||||
status = AudioDeviceStop(core->outputDeviceID,
|
|
||||||
core->ioprocid);
|
|
||||||
if (status != kAudioHardwareNoError) {
|
|
||||||
coreaudio_logerr(status, "Could not pause playback\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core->enabled = enable;
|
||||||
|
update_device_playback_state(core);
|
||||||
|
|
||||||
|
coreaudio_unlock(core, __func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *coreaudio_audio_init(Audiodev *dev)
|
static void *coreaudio_audio_init(Audiodev *dev)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user