mirror of https://github.com/mpv-player/mpv
audio/out: add AudioUnit output driver for iOS
This commit is contained in:
parent
0dc1fe3269
commit
3f5b41dfa3
|
@ -35,6 +35,7 @@
|
|||
#include "common/global.h"
|
||||
|
||||
extern const struct ao_driver audio_out_oss;
|
||||
extern const struct ao_driver audio_out_audiounit;
|
||||
extern const struct ao_driver audio_out_coreaudio;
|
||||
extern const struct ao_driver audio_out_coreaudio_exclusive;
|
||||
extern const struct ao_driver audio_out_rsound;
|
||||
|
@ -52,6 +53,9 @@ extern const struct ao_driver audio_out_sdl;
|
|||
|
||||
static const struct ao_driver * const audio_out_drivers[] = {
|
||||
// native:
|
||||
#if HAVE_AUDIOUNIT
|
||||
&audio_out_audiounit,
|
||||
#endif
|
||||
#if HAVE_COREAUDIO
|
||||
&audio_out_coreaudio,
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv 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.
|
||||
*
|
||||
* mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "options/m_option.h"
|
||||
#include "misc/ring.h"
|
||||
#include "common/msg.h"
|
||||
#include "ao_coreaudio_utils.h"
|
||||
#include "ao_coreaudio_chmap.h"
|
||||
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <CoreAudio/CoreAudioTypes.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <mach/mach_time.h>
|
||||
|
||||
struct priv {
|
||||
AudioUnit audio_unit;
|
||||
double device_latency;
|
||||
};
|
||||
|
||||
static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||
const AudioTimeStamp *ts, UInt32 bus,
|
||||
UInt32 frames, AudioBufferList *buffer_list)
|
||||
{
|
||||
struct ao *ao = ctx;
|
||||
struct priv *p = ao->priv;
|
||||
void *planes[MP_NUM_CHANNELS] = {0};
|
||||
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
planes[n] = buffer_list->mBuffers[n].mData;
|
||||
|
||||
int64_t end = mp_time_us();
|
||||
end += ca_frames_to_us(ao, frames);
|
||||
end += p->device_latency * 1e6;
|
||||
ao_read_data(ao, planes, frames, end);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static bool init_audiounit(struct ao *ao)
|
||||
{
|
||||
AudioStreamBasicDescription asbd;
|
||||
OSStatus err;
|
||||
uint32_t size;
|
||||
struct priv *p = ao->priv;
|
||||
AVAudioSession *instance = AVAudioSession.sharedInstance;
|
||||
AVAudioSessionPortDescription *port = nil;
|
||||
NSInteger maxChannels = instance.maximumOutputNumberOfChannels;
|
||||
NSInteger prefChannels = MIN(maxChannels, ao->channels.num);
|
||||
|
||||
[instance setCategory:AVAudioSessionCategoryPlayback error:nil];
|
||||
[instance setMode:AVAudioSessionModeMoviePlayback error:nil];
|
||||
[instance setActive:YES error:nil];
|
||||
[instance setPreferredOutputNumberOfChannels:prefChannels error:nil];
|
||||
|
||||
AudioComponentDescription desc = (AudioComponentDescription) {
|
||||
.componentType = kAudioUnitType_Output,
|
||||
.componentSubType = kAudioUnitSubType_RemoteIO,
|
||||
.componentManufacturer = kAudioUnitManufacturer_Apple,
|
||||
.componentFlags = 0,
|
||||
.componentFlagsMask = 0,
|
||||
};
|
||||
|
||||
AudioComponent comp = AudioComponentFindNext(NULL, &desc);
|
||||
if (comp == NULL) {
|
||||
MP_ERR(ao, "unable to find audio component\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
err = AudioComponentInstanceNew(comp, &(p->audio_unit));
|
||||
CHECK_CA_ERROR("unable to open audio component");
|
||||
|
||||
err = AudioUnitInitialize(p->audio_unit);
|
||||
CHECK_CA_ERROR_L(coreaudio_error_component,
|
||||
"unable to initialize audio unit");
|
||||
|
||||
if (af_fmt_is_spdif(ao->format) || instance.outputNumberOfChannels <= 2) {
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP_INIT_STEREO;
|
||||
} else {
|
||||
port = instance.currentRoute.outputs.firstObject;
|
||||
if (port.channels.count == 2 &&
|
||||
port.channels[0].channelLabel == kAudioChannelLabel_Unknown) {
|
||||
// Special case when using an HDMI adapter. The iOS device will
|
||||
// perform SPDIF conversion for us, so send all available channels
|
||||
// using the AC3 mapping.
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP6(FL, FC, FR, SL, SR, LFE);
|
||||
} else {
|
||||
ao->channels.num = (uint8_t)port.channels.count;
|
||||
for (AVAudioSessionChannelDescription *ch in port.channels) {
|
||||
ao->channels.speaker[ch.channelNumber - 1] =
|
||||
ca_label_to_mp_speaker_id(ch.channelLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
size = sizeof(AudioStreamBasicDescription);
|
||||
err = AudioUnitSetProperty(p->audio_unit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &asbd, size);
|
||||
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"unable to set the input format on the audio unit");
|
||||
|
||||
AURenderCallbackStruct render_cb = (AURenderCallbackStruct) {
|
||||
.inputProc = render_cb_lpcm,
|
||||
.inputProcRefCon = ao,
|
||||
};
|
||||
|
||||
err = AudioUnitSetProperty(p->audio_unit,
|
||||
kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &render_cb,
|
||||
sizeof(AURenderCallbackStruct));
|
||||
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"unable to set render callback on audio unit");
|
||||
|
||||
return true;
|
||||
|
||||
coreaudio_error_audiounit:
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
coreaudio_error_component:
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
static void stop(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = AudioOutputUnitStop(p->audio_unit);
|
||||
CHECK_CA_WARN("can't stop audio unit");
|
||||
}
|
||||
|
||||
static void start(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
AVAudioSession *instance = AVAudioSession.sharedInstance;
|
||||
|
||||
p->device_latency = [instance outputLatency];
|
||||
|
||||
OSStatus err = AudioOutputUnitStart(p->audio_unit);
|
||||
CHECK_CA_WARN("can't start audio unit");
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
AudioOutputUnitStop(p->audio_unit);
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
|
||||
[AVAudioSession.sharedInstance
|
||||
setActive:NO
|
||||
withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
|
||||
error:nil];
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
if (!init_audiounit(ao))
|
||||
goto coreaudio_error;
|
||||
|
||||
return CONTROL_OK;
|
||||
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_audiounit = {
|
||||
.description = "AudioUnit (iOS)",
|
||||
.name = "audiounit",
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.pause = stop,
|
||||
.resume = start,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]){
|
||||
{0}
|
||||
},
|
||||
};
|
|
@ -56,7 +56,7 @@ static const int speaker_map[][2] = {
|
|||
{ 0, -1 },
|
||||
};
|
||||
|
||||
static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
{
|
||||
for (int i = 0; speaker_map[i][1] >= 0; i++)
|
||||
if (speaker_map[i][0] == label)
|
||||
|
@ -64,6 +64,7 @@ static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
|||
return -1;
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
static void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout)
|
||||
{
|
||||
if (!mp_msg_test(ao->log, l))
|
||||
|
@ -327,3 +328,4 @@ void ca_get_active_chmap(struct ao *ao, AudioDeviceID device, int channel_count,
|
|||
MP_WARN(ao, "mismatching channels - falling back to %s\n",
|
||||
mp_chmap_to_str(out_map));
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -22,8 +22,12 @@
|
|||
|
||||
struct mp_chmap;
|
||||
|
||||
int ca_label_to_mp_speaker_id(AudioChannelLabel label);
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
bool ca_init_chmap(struct ao *ao, AudioDeviceID device);
|
||||
void ca_get_active_chmap(struct ao *ao, AudioDeviceID device, int channel_count,
|
||||
struct mp_chmap *out_map);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -22,15 +22,17 @@
|
|||
* on CoreAudio but not the AUHAL (such as using AudioQueue services).
|
||||
*/
|
||||
|
||||
#include <CoreAudio/HostTime.h>
|
||||
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/endian.h"
|
||||
#include "osdep/semaphore.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include <CoreAudio/HostTime.h>
|
||||
#endif
|
||||
|
||||
CFStringRef cfstr_from_cstr(char *str)
|
||||
{
|
||||
return CFStringCreateWithCString(NULL, str, CA_CFSTR_ENCODING);
|
||||
|
@ -46,6 +48,7 @@ char *cfstr_get_cstr(CFStringRef cfstr)
|
|||
return buffer;
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
static bool ca_is_output_device(struct ao *ao, AudioDeviceID dev)
|
||||
{
|
||||
size_t n_buffers;
|
||||
|
@ -142,6 +145,7 @@ OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
|
|||
coreaudio_error:
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
|
||||
{
|
||||
|
@ -306,6 +310,7 @@ int64_t ca_frames_to_us(struct ao *ao, uint32_t frames)
|
|||
return frames / (float) ao->samplerate * 1e6;
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts)
|
||||
{
|
||||
uint64_t out = AudioConvertHostTimeToNanos(ts->mHostTime);
|
||||
|
@ -522,4 +527,4 @@ coreaudio_error:
|
|||
sem_destroy(&wakeup);
|
||||
return format_set;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -49,7 +49,9 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message);
|
|||
} while (0)
|
||||
|
||||
void ca_get_device_list(struct ao *ao, struct ao_device_list *list);
|
||||
#if HAVE_COREAUDIO
|
||||
OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device);
|
||||
#endif
|
||||
|
||||
bool ca_formatid_is_compressed(uint32_t formatid);
|
||||
void ca_fill_asbd(struct ao *ao, AudioStreamBasicDescription *asbd);
|
||||
|
@ -63,8 +65,11 @@ bool ca_asbd_is_better(AudioStreamBasicDescription *req,
|
|||
AudioStreamBasicDescription *new);
|
||||
|
||||
int64_t ca_frames_to_us(struct ao *ao, uint32_t frames);
|
||||
#if HAVE_COREAUDIO
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts);
|
||||
#endif
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream);
|
||||
OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid);
|
||||
OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid);
|
||||
|
@ -73,5 +78,6 @@ OSStatus ca_enable_mixing(struct ao *ao, AudioDeviceID device, bool changed);
|
|||
int64_t ca_get_device_latency_us(struct ao *ao, AudioDeviceID device);
|
||||
bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
AudioStreamBasicDescription change_format);
|
||||
#endif
|
||||
|
||||
#endif /* MPV_COREAUDIO_UTILS_H */
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <CoreAudio/CoreAudioTypes.h>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
AudioComponentDescription desc = (AudioComponentDescription) {
|
||||
.componentType = kAudioUnitType_Output,
|
||||
.componentSubType = kAudioUnitSubType_RemoteIO,
|
||||
.componentManufacturer = kAudioUnitManufacturer_Apple,
|
||||
};
|
||||
|
||||
AudioComponentFindNext(NULL, &desc);
|
||||
return 0;
|
||||
}
|
7
wscript
7
wscript
|
@ -606,6 +606,13 @@ audio_output_features = [
|
|||
'func': check_cc(
|
||||
fragment=load_fragment('coreaudio.c'),
|
||||
framework_name=['CoreFoundation', 'CoreAudio', 'AudioUnit', 'AudioToolbox'])
|
||||
}, {
|
||||
'name': '--audiounit',
|
||||
'desc': 'AudioUnit output for iOS',
|
||||
'deps': ['atomics'],
|
||||
'func': check_cc(
|
||||
fragment=load_fragment('audiounit.c'),
|
||||
framework_name=['Foundation', 'AudioToolbox'])
|
||||
}, {
|
||||
'name': '--wasapi',
|
||||
'desc': 'WASAPI audio output',
|
||||
|
|
|
@ -127,6 +127,9 @@ def build(ctx):
|
|||
( "audio/filter/tools.c" ),
|
||||
( "audio/out/ao.c" ),
|
||||
( "audio/out/ao_alsa.c", "alsa" ),
|
||||
( "audio/out/ao_audiounit.m", "audiounit" ),
|
||||
( "audio/out/ao_coreaudio_chmap.c", "audiounit" ),
|
||||
( "audio/out/ao_coreaudio_utils.c", "audiounit" ),
|
||||
( "audio/out/ao_coreaudio.c", "coreaudio" ),
|
||||
( "audio/out/ao_coreaudio_chmap.c", "coreaudio" ),
|
||||
( "audio/out/ao_coreaudio_exclusive.c", "coreaudio" ),
|
||||
|
|
Loading…
Reference in New Issue