summaryrefslogtreecommitdiffstats
path: root/audio/out
diff options
context:
space:
mode:
Diffstat (limited to 'audio/out')
-rw-r--r--audio/out/ao.c6
-rw-r--r--audio/out/ao_alsa.c3
-rw-r--r--audio/out/ao_audiotrack.c361
-rw-r--r--audio/out/ao_avfoundation.m372
-rw-r--r--audio/out/ao_coreaudio.c138
-rw-r--r--audio/out/ao_coreaudio_chmap.c221
-rw-r--r--audio/out/ao_coreaudio_chmap.h9
-rw-r--r--audio/out/ao_coreaudio_exclusive.c6
-rw-r--r--audio/out/ao_coreaudio_properties.h5
-rw-r--r--audio/out/ao_coreaudio_utils.c11
-rw-r--r--audio/out/ao_coreaudio_utils.h7
-rw-r--r--audio/out/ao_lavc.c1
-rw-r--r--audio/out/ao_null.c3
-rw-r--r--audio/out/ao_oss.c5
-rw-r--r--audio/out/ao_pipewire.c107
-rw-r--r--audio/out/ao_pulse.c5
-rw-r--r--audio/out/ao_sndio.c6
-rw-r--r--audio/out/ao_wasapi.c73
-rw-r--r--audio/out/ao_wasapi.h5
-rw-r--r--audio/out/ao_wasapi_utils.c77
-rw-r--r--audio/out/buffer.c61
-rw-r--r--audio/out/internal.h10
22 files changed, 1140 insertions, 352 deletions
diff --git a/audio/out/ao.c b/audio/out/ao.c
index a5aa3a9..75fcbac 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -40,6 +40,7 @@ extern const struct ao_driver audio_out_audiotrack;
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_avfoundation;
extern const struct ao_driver audio_out_rsound;
extern const struct ao_driver audio_out_pipewire;
extern const struct ao_driver audio_out_sndio;
@@ -65,6 +66,9 @@ static const struct ao_driver * const audio_out_drivers[] = {
#if HAVE_COREAUDIO
&audio_out_coreaudio,
#endif
+#if HAVE_AVFOUNDATION
+ &audio_out_avfoundation,
+#endif
#if HAVE_PIPEWIRE
&audio_out_pipewire,
#endif
@@ -612,7 +616,7 @@ void ao_set_gain(struct ao *ao, float gain)
#define MUL_GAIN_f(d, num_samples, gain) \
for (int n = 0; n < (num_samples); n++) \
- (d)[n] = MPCLAMP(((d)[n]) * (gain), -1.0, 1.0)
+ (d)[n] = (d)[n] * (gain)
static void process_plane(struct ao *ao, void *data, int num_samples)
{
diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c
index 75eda3b..92ea0db 100644
--- a/audio/out/ao_alsa.c
+++ b/audio/out/ao_alsa.c
@@ -623,7 +623,8 @@ static void uninit(struct ao *ao)
CHECK_ALSA_ERROR("pcm close error");
}
-alsa_error: ;
+alsa_error:
+ snd_config_update_free_global();
}
#define INIT_DEVICE_ERR_GENERIC -1
diff --git a/audio/out/ao_audiotrack.c b/audio/out/ao_audiotrack.c
index 1392699..db1da9c 100644
--- a/audio/out/ao_audiotrack.c
+++ b/audio/out/ao_audiotrack.c
@@ -57,9 +57,6 @@ struct priv {
bool cfg_pcm_float;
int cfg_session_id;
- bool needs_timestamp_offset;
- int64_t timestamp_offset;
-
bool thread_terminate;
bool thread_created;
mp_thread thread;
@@ -67,19 +64,19 @@ struct priv {
mp_cond wakeup;
};
-struct JNIByteBuffer {
+static struct JNIByteBuffer {
jclass clazz;
jmethodID clear;
- struct MPJniField mapping[];
-} ByteBuffer = {.mapping = {
- #define OFFSET(member) offsetof(struct JNIByteBuffer, member)
- {"java/nio/ByteBuffer", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
- {"java/nio/ByteBuffer", "clear", "()Ljava/nio/Buffer;", MP_JNI_METHOD, OFFSET(clear), 1},
+} ByteBuffer;
+#define OFFSET(member) offsetof(struct JNIByteBuffer, member)
+static const struct MPJniField ByteBuffer_mapping[] = {
+ {"java/nio/ByteBuffer", NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
+ {"clear", "()Ljava/nio/Buffer;", MP_JNI_METHOD, OFFSET(clear), 1},
{0},
- #undef OFFSET
-}};
+};
+#undef OFFSET
-struct JNIAudioTrack {
+static struct JNIAudioTrack {
jclass clazz;
jmethodID ctor;
jmethodID ctorV21;
@@ -110,78 +107,78 @@ struct JNIAudioTrack {
jint ERROR_INVALID_OPERATION;
jint WRITE_BLOCKING;
jint WRITE_NON_BLOCKING;
- struct MPJniField mapping[];
-} AudioTrack = {.mapping = {
- #define OFFSET(member) offsetof(struct JNIAudioTrack, member)
- {"android/media/AudioTrack", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
- {"android/media/AudioTrack", "<init>", "(IIIIIII)V", MP_JNI_METHOD, OFFSET(ctor), 1},
- {"android/media/AudioTrack", "<init>", "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;III)V", MP_JNI_METHOD, OFFSET(ctorV21), 0},
- {"android/media/AudioTrack", "release", "()V", MP_JNI_METHOD, OFFSET(release), 1},
- {"android/media/AudioTrack", "getState", "()I", MP_JNI_METHOD, OFFSET(getState), 1},
- {"android/media/AudioTrack", "getPlayState", "()I", MP_JNI_METHOD, OFFSET(getPlayState), 1},
- {"android/media/AudioTrack", "play", "()V", MP_JNI_METHOD, OFFSET(play), 1},
- {"android/media/AudioTrack", "stop", "()V", MP_JNI_METHOD, OFFSET(stop), 1},
- {"android/media/AudioTrack", "flush", "()V", MP_JNI_METHOD, OFFSET(flush), 1},
- {"android/media/AudioTrack", "pause", "()V", MP_JNI_METHOD, OFFSET(pause), 1},
- {"android/media/AudioTrack", "write", "([BII)I", MP_JNI_METHOD, OFFSET(write), 1},
- {"android/media/AudioTrack", "write", "([FIII)I", MP_JNI_METHOD, OFFSET(writeFloat), 1},
- {"android/media/AudioTrack", "write", "([SIII)I", MP_JNI_METHOD, OFFSET(writeShortV23), 0},
- {"android/media/AudioTrack", "write", "(Ljava/nio/ByteBuffer;II)I", MP_JNI_METHOD, OFFSET(writeBufferV21), 1},
- {"android/media/AudioTrack", "getBufferSizeInFrames", "()I", MP_JNI_METHOD, OFFSET(getBufferSizeInFramesV23), 0},
- {"android/media/AudioTrack", "getTimestamp", "(Landroid/media/AudioTimestamp;)Z", MP_JNI_METHOD, OFFSET(getTimestamp), 1},
- {"android/media/AudioTrack", "getPlaybackHeadPosition", "()I", MP_JNI_METHOD, OFFSET(getPlaybackHeadPosition), 1},
- {"android/media/AudioTrack", "getLatency", "()I", MP_JNI_METHOD, OFFSET(getLatency), 1},
- {"android/media/AudioTrack", "getMinBufferSize", "(III)I", MP_JNI_STATIC_METHOD, OFFSET(getMinBufferSize), 1},
- {"android/media/AudioTrack", "getNativeOutputSampleRate", "(I)I", MP_JNI_STATIC_METHOD, OFFSET(getNativeOutputSampleRate), 1},
- {"android/media/AudioTrack", "WRITE_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_BLOCKING), 0},
- {"android/media/AudioTrack", "WRITE_NON_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_NON_BLOCKING), 0},
- {"android/media/AudioTrack", "STATE_INITIALIZED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STATE_INITIALIZED), 1},
- {"android/media/AudioTrack", "PLAYSTATE_STOPPED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_STOPPED), 1},
- {"android/media/AudioTrack", "PLAYSTATE_PAUSED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PAUSED), 1},
- {"android/media/AudioTrack", "PLAYSTATE_PLAYING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PLAYING), 1},
- {"android/media/AudioTrack", "MODE_STREAM", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(MODE_STREAM), 1},
- {"android/media/AudioTrack", "ERROR", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR), 1},
- {"android/media/AudioTrack", "ERROR_BAD_VALUE", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_BAD_VALUE), 1},
- {"android/media/AudioTrack", "ERROR_INVALID_OPERATION", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_INVALID_OPERATION), 1},
+} AudioTrack;
+#define OFFSET(member) offsetof(struct JNIAudioTrack, member)
+static const struct MPJniField AudioTrack_mapping[] = {
+ {"android/media/AudioTrack", NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
+ {"<init>", "(IIIIIII)V", MP_JNI_METHOD, OFFSET(ctor), 1},
+ {"<init>", "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;III)V", MP_JNI_METHOD, OFFSET(ctorV21), 0},
+ {"release", "()V", MP_JNI_METHOD, OFFSET(release), 1},
+ {"getState", "()I", MP_JNI_METHOD, OFFSET(getState), 1},
+ {"getPlayState", "()I", MP_JNI_METHOD, OFFSET(getPlayState), 1},
+ {"play", "()V", MP_JNI_METHOD, OFFSET(play), 1},
+ {"stop", "()V", MP_JNI_METHOD, OFFSET(stop), 1},
+ {"flush", "()V", MP_JNI_METHOD, OFFSET(flush), 1},
+ {"pause", "()V", MP_JNI_METHOD, OFFSET(pause), 1},
+ {"write", "([BII)I", MP_JNI_METHOD, OFFSET(write), 1},
+ {"write", "([FIII)I", MP_JNI_METHOD, OFFSET(writeFloat), 1},
+ {"write", "([SIII)I", MP_JNI_METHOD, OFFSET(writeShortV23), 0},
+ {"write", "(Ljava/nio/ByteBuffer;II)I", MP_JNI_METHOD, OFFSET(writeBufferV21), 1},
+ {"getBufferSizeInFrames", "()I", MP_JNI_METHOD, OFFSET(getBufferSizeInFramesV23), 0},
+ {"getTimestamp", "(Landroid/media/AudioTimestamp;)Z", MP_JNI_METHOD, OFFSET(getTimestamp), 1},
+ {"getPlaybackHeadPosition", "()I", MP_JNI_METHOD, OFFSET(getPlaybackHeadPosition), 1},
+ {"getLatency", "()I", MP_JNI_METHOD, OFFSET(getLatency), 1},
+ {"getMinBufferSize", "(III)I", MP_JNI_STATIC_METHOD, OFFSET(getMinBufferSize), 1},
+ {"getNativeOutputSampleRate", "(I)I", MP_JNI_STATIC_METHOD, OFFSET(getNativeOutputSampleRate), 1},
+ {"WRITE_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_BLOCKING), 0},
+ {"WRITE_NON_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_NON_BLOCKING), 0},
+ {"STATE_INITIALIZED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STATE_INITIALIZED), 1},
+ {"PLAYSTATE_STOPPED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_STOPPED), 1},
+ {"PLAYSTATE_PAUSED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PAUSED), 1},
+ {"PLAYSTATE_PLAYING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PLAYING), 1},
+ {"MODE_STREAM", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(MODE_STREAM), 1},
+ {"ERROR", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR), 1},
+ {"ERROR_BAD_VALUE", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_BAD_VALUE), 1},
+ {"ERROR_INVALID_OPERATION", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_INVALID_OPERATION), 1},
{0}
- #undef OFFSET
-}};
+};
+#undef OFFSET
-struct JNIAudioAttributes {
+static struct JNIAudioAttributes {
jclass clazz;
jint CONTENT_TYPE_MOVIE;
jint CONTENT_TYPE_MUSIC;
jint USAGE_MEDIA;
- struct MPJniField mapping[];
-} AudioAttributes = {.mapping = {
- #define OFFSET(member) offsetof(struct JNIAudioAttributes, member)
- {"android/media/AudioAttributes", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 0},
- {"android/media/AudioAttributes", "CONTENT_TYPE_MOVIE", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CONTENT_TYPE_MOVIE), 0},
- {"android/media/AudioAttributes", "CONTENT_TYPE_MUSIC", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CONTENT_TYPE_MUSIC), 0},
- {"android/media/AudioAttributes", "USAGE_MEDIA", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(USAGE_MEDIA), 0},
+} AudioAttributes;
+#define OFFSET(member) offsetof(struct JNIAudioAttributes, member)
+static const struct MPJniField AudioAttributes_mapping[] = {
+ {"android/media/AudioAttributes", NULL, MP_JNI_CLASS, OFFSET(clazz), 0},
+ {"CONTENT_TYPE_MOVIE", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CONTENT_TYPE_MOVIE), 0},
+ {"CONTENT_TYPE_MUSIC", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CONTENT_TYPE_MUSIC), 0},
+ {"USAGE_MEDIA", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(USAGE_MEDIA), 0},
{0}
- #undef OFFSET
-}};
+};
+#undef OFFSET
-struct JNIAudioAttributesBuilder {
+static struct JNIAudioAttributesBuilder {
jclass clazz;
jmethodID ctor;
jmethodID setUsage;
jmethodID setContentType;
jmethodID build;
- struct MPJniField mapping[];
-} AudioAttributesBuilder = {.mapping = {
- #define OFFSET(member) offsetof(struct JNIAudioAttributesBuilder, member)
- {"android/media/AudioAttributes$Builder", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 0},
- {"android/media/AudioAttributes$Builder", "<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 0},
- {"android/media/AudioAttributes$Builder", "setUsage", "(I)Landroid/media/AudioAttributes$Builder;", MP_JNI_METHOD, OFFSET(setUsage), 0},
- {"android/media/AudioAttributes$Builder", "setContentType", "(I)Landroid/media/AudioAttributes$Builder;", MP_JNI_METHOD, OFFSET(setContentType), 0},
- {"android/media/AudioAttributes$Builder", "build", "()Landroid/media/AudioAttributes;", MP_JNI_METHOD, OFFSET(build), 0},
+} AudioAttributesBuilder;
+#define OFFSET(member) offsetof(struct JNIAudioAttributesBuilder, member)
+static const struct MPJniField AudioAttributesBuilder_mapping[] = {
+ {"android/media/AudioAttributes$Builder", NULL, MP_JNI_CLASS, OFFSET(clazz), 0},
+ {"<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 0},
+ {"setUsage", "(I)Landroid/media/AudioAttributes$Builder;", MP_JNI_METHOD, OFFSET(setUsage), 0},
+ {"setContentType", "(I)Landroid/media/AudioAttributes$Builder;", MP_JNI_METHOD, OFFSET(setContentType), 0},
+ {"build", "()Landroid/media/AudioAttributes;", MP_JNI_METHOD, OFFSET(build), 0},
{0}
- #undef OFFSET
-}};
+};
+#undef OFFSET
-struct JNIAudioFormat {
+static struct JNIAudioFormat {
jclass clazz;
jint ENCODING_PCM_8BIT;
jint ENCODING_PCM_16BIT;
@@ -194,77 +191,90 @@ struct JNIAudioFormat {
jint CHANNEL_OUT_5POINT1;
jint CHANNEL_OUT_BACK_CENTER;
jint CHANNEL_OUT_7POINT1_SURROUND;
- struct MPJniField mapping[];
-} AudioFormat = {.mapping = {
- #define OFFSET(member) offsetof(struct JNIAudioFormat, member)
- {"android/media/AudioFormat", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
- {"android/media/AudioFormat", "ENCODING_PCM_8BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_8BIT), 1},
- {"android/media/AudioFormat", "ENCODING_PCM_16BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_16BIT), 1},
- {"android/media/AudioFormat", "ENCODING_PCM_FLOAT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_FLOAT), 1},
- {"android/media/AudioFormat", "ENCODING_IEC61937", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_IEC61937), 0},
- {"android/media/AudioFormat", "CHANNEL_OUT_MONO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_MONO), 1},
- {"android/media/AudioFormat", "CHANNEL_OUT_STEREO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_STEREO), 1},
- {"android/media/AudioFormat", "CHANNEL_OUT_FRONT_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_CENTER), 1},
- {"android/media/AudioFormat", "CHANNEL_OUT_QUAD", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_QUAD), 1},
- {"android/media/AudioFormat", "CHANNEL_OUT_5POINT1", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_5POINT1), 1},
- {"android/media/AudioFormat", "CHANNEL_OUT_BACK_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_CENTER), 1},
- {"android/media/AudioFormat", "CHANNEL_OUT_7POINT1_SURROUND", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_7POINT1_SURROUND), 0},
+} AudioFormat;
+#define OFFSET(member) offsetof(struct JNIAudioFormat, member)
+static const struct MPJniField AudioFormat_mapping[] = {
+ {"android/media/AudioFormat", NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
+ {"ENCODING_PCM_8BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_8BIT), 1},
+ {"ENCODING_PCM_16BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_16BIT), 1},
+ {"ENCODING_PCM_FLOAT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_FLOAT), 1},
+ {"ENCODING_IEC61937", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_IEC61937), 0},
+ {"CHANNEL_OUT_MONO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_MONO), 1},
+ {"CHANNEL_OUT_STEREO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_STEREO), 1},
+ {"CHANNEL_OUT_FRONT_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_CENTER), 1},
+ {"CHANNEL_OUT_QUAD", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_QUAD), 1},
+ {"CHANNEL_OUT_5POINT1", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_5POINT1), 1},
+ {"CHANNEL_OUT_BACK_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_CENTER), 1},
+ {"CHANNEL_OUT_7POINT1_SURROUND", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_7POINT1_SURROUND), 0},
{0}
- #undef OFFSET
-}};
+};
+#undef OFFSET
-struct JNIAudioFormatBuilder {
+static struct JNIAudioFormatBuilder {
jclass clazz;
jmethodID ctor;
jmethodID setEncoding;
jmethodID setSampleRate;
jmethodID setChannelMask;
jmethodID build;
- struct MPJniField mapping[];
-} AudioFormatBuilder = {.mapping = {
- #define OFFSET(member) offsetof(struct JNIAudioFormatBuilder, member)
- {"android/media/AudioFormat$Builder", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 0},
- {"android/media/AudioFormat$Builder", "<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 0},
- {"android/media/AudioFormat$Builder", "setEncoding", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setEncoding), 0},
- {"android/media/AudioFormat$Builder", "setSampleRate", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setSampleRate), 0},
- {"android/media/AudioFormat$Builder", "setChannelMask", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setChannelMask), 0},
- {"android/media/AudioFormat$Builder", "build", "()Landroid/media/AudioFormat;", MP_JNI_METHOD, OFFSET(build), 0},
+} AudioFormatBuilder;
+#define OFFSET(member) offsetof(struct JNIAudioFormatBuilder, member)
+static const struct MPJniField AudioFormatBuilder_mapping[] = {
+ {"android/media/AudioFormat$Builder", NULL, MP_JNI_CLASS, OFFSET(clazz), 0},
+ {"<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 0},
+ {"setEncoding", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setEncoding), 0},
+ {"setSampleRate", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setSampleRate), 0},
+ {"setChannelMask", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setChannelMask), 0},
+ {"build", "()Landroid/media/AudioFormat;", MP_JNI_METHOD, OFFSET(build), 0},
{0}
- #undef OFFSET
-}};
-
+};
+#undef OFFSET
-struct JNIAudioManager {
+static struct JNIAudioManager {
jclass clazz;
jint ERROR_DEAD_OBJECT;
jint STREAM_MUSIC;
- struct MPJniField mapping[];
-} AudioManager = {.mapping = {
- #define OFFSET(member) offsetof(struct JNIAudioManager, member)
- {"android/media/AudioManager", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
- {"android/media/AudioManager", "STREAM_MUSIC", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STREAM_MUSIC), 1},
- {"android/media/AudioManager", "ERROR_DEAD_OBJECT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_DEAD_OBJECT), 0},
+} AudioManager;
+#define OFFSET(member) offsetof(struct JNIAudioManager, member)
+static const struct MPJniField AudioManager_mapping[] = {
+ {"android/media/AudioManager", NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
+ {"STREAM_MUSIC", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STREAM_MUSIC), 1},
+ {"ERROR_DEAD_OBJECT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_DEAD_OBJECT), 0},
{0}
- #undef OFFSET
-}};
+};
+#undef OFFSET
-struct JNIAudioTimestamp {
+static struct JNIAudioTimestamp {
jclass clazz;
jmethodID ctor;
jfieldID framePosition;
jfieldID nanoTime;
- struct MPJniField mapping[];
-} AudioTimestamp = {.mapping = {
- #define OFFSET(member) offsetof(struct JNIAudioTimestamp, member)
- {"android/media/AudioTimestamp", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
- {"android/media/AudioTimestamp", "<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 1},
- {"android/media/AudioTimestamp", "framePosition", "J", MP_JNI_FIELD, OFFSET(framePosition), 1},
- {"android/media/AudioTimestamp", "nanoTime", "J", MP_JNI_FIELD, OFFSET(nanoTime), 1},
+} AudioTimestamp;
+#define OFFSET(member) offsetof(struct JNIAudioTimestamp, member)
+static const struct MPJniField AudioTimestamp_mapping[] = {
+ {"android/media/AudioTimestamp", NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
+ {"<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 1},
+ {"framePosition", "J", MP_JNI_FIELD, OFFSET(framePosition), 1},
+ {"nanoTime", "J", MP_JNI_FIELD, OFFSET(nanoTime), 1},
{0}
- #undef OFFSET
-}};
-
-#define MP_JNI_DELETELOCAL(o) (*env)->DeleteLocalRef(env, o)
+};
+#undef OFFSET
+
+#define ENTRY(name) { &name, name ## _mapping }
+static const struct {
+ void *fields;
+ const struct MPJniField *mapping;
+} jclass_list[] = {
+ ENTRY(ByteBuffer),
+ ENTRY(AudioTrack),
+ ENTRY(AudioAttributes),
+ ENTRY(AudioAttributesBuilder),
+ ENTRY(AudioFormat),
+ ENTRY(AudioFormatBuilder),
+ ENTRY(AudioManager),
+ ENTRY(AudioTimestamp),
+};
+#undef ENTRY
static int AudioTrack_New(struct ao *ao)
{
@@ -279,24 +289,24 @@ static int AudioTrack_New(struct ao *ao)
jobject format_builder = MP_JNI_NEW(AudioFormatBuilder.clazz, AudioFormatBuilder.ctor);
MP_JNI_EXCEPTION_LOG(ao);
tmp = MP_JNI_CALL_OBJECT(format_builder, AudioFormatBuilder.setEncoding, p->format);
- MP_JNI_DELETELOCAL(tmp);
+ MP_JNI_LOCAL_FREEP(&tmp);
tmp = MP_JNI_CALL_OBJECT(format_builder, AudioFormatBuilder.setSampleRate, p->samplerate);
- MP_JNI_DELETELOCAL(tmp);
+ MP_JNI_LOCAL_FREEP(&tmp);
tmp = MP_JNI_CALL_OBJECT(format_builder, AudioFormatBuilder.setChannelMask, p->channel_config);
- MP_JNI_DELETELOCAL(tmp);
+ MP_JNI_LOCAL_FREEP(&tmp);
jobject format = MP_JNI_CALL_OBJECT(format_builder, AudioFormatBuilder.build);
- MP_JNI_DELETELOCAL(format_builder);
+ MP_JNI_LOCAL_FREEP(&format_builder);
jobject attr_builder = MP_JNI_NEW(AudioAttributesBuilder.clazz, AudioAttributesBuilder.ctor);
MP_JNI_EXCEPTION_LOG(ao);
tmp = MP_JNI_CALL_OBJECT(attr_builder, AudioAttributesBuilder.setUsage, AudioAttributes.USAGE_MEDIA);
- MP_JNI_DELETELOCAL(tmp);
+ MP_JNI_LOCAL_FREEP(&tmp);
jint content_type = (ao->init_flags & AO_INIT_MEDIA_ROLE_MUSIC) ?
AudioAttributes.CONTENT_TYPE_MUSIC : AudioAttributes.CONTENT_TYPE_MOVIE;
tmp = MP_JNI_CALL_OBJECT(attr_builder, AudioAttributesBuilder.setContentType, content_type);
- MP_JNI_DELETELOCAL(tmp);
+ MP_JNI_LOCAL_FREEP(&tmp);
jobject attr = MP_JNI_CALL_OBJECT(attr_builder, AudioAttributesBuilder.build);
- MP_JNI_DELETELOCAL(attr_builder);
+ MP_JNI_LOCAL_FREEP(&attr_builder);
audiotrack = MP_JNI_NEW(
AudioTrack.clazz,
@@ -308,8 +318,8 @@ static int AudioTrack_New(struct ao *ao)
p->cfg_session_id
);
- MP_JNI_DELETELOCAL(format);
- MP_JNI_DELETELOCAL(attr);
+ MP_JNI_LOCAL_FREEP(&format);
+ MP_JNI_LOCAL_FREEP(&attr);
} else {
MP_VERBOSE(ao, "Using legacy initializer\n");
audiotrack = MP_JNI_NEW(
@@ -332,7 +342,7 @@ static int AudioTrack_New(struct ao *ao)
if (MP_JNI_CALL_INT(audiotrack, AudioTrack.getState) != AudioTrack.STATE_INITIALIZED) {
MP_JNI_CALL_VOID(audiotrack, AudioTrack.release);
MP_JNI_EXCEPTION_LOG(ao);
- (*env)->DeleteLocalRef(env, audiotrack);
+ MP_JNI_LOCAL_FREEP(&audiotrack);
MP_ERR(ao, "AudioTrack.getState failed\n");
return -1;
}
@@ -346,7 +356,7 @@ static int AudioTrack_New(struct ao *ao)
}
p->audiotrack = (*env)->NewGlobalRef(env, audiotrack);
- (*env)->DeleteLocalRef(env, audiotrack);
+ MP_JNI_LOCAL_FREEP(&audiotrack);
if (!p->audiotrack)
return -1;
@@ -360,8 +370,7 @@ static int AudioTrack_Recreate(struct ao *ao)
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.release);
MP_JNI_EXCEPTION_LOG(ao);
- (*env)->DeleteGlobalRef(env, p->audiotrack);
- p->audiotrack = NULL;
+ MP_JNI_GLOBAL_FREEP(&p->audiotrack);
return AudioTrack_New(ao);
}
@@ -407,11 +416,6 @@ static uint32_t AudioTrack_getPlaybackHeadPosition(struct ao *ao)
int64_t time = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime);
if (time == 0)
fpos = pos = 0;
- if (p->needs_timestamp_offset) {
- if (time != 0 && !p->timestamp_offset)
- p->timestamp_offset = now - time;
- time += p->timestamp_offset;
- }
if (fpos != 0 && time != 0 && state == AudioTrack.PLAYSTATE_PLAYING) {
double diff = (double)(now - time) / 1e9;
pos += diff * ao->samplerate;
@@ -497,7 +501,7 @@ static int AudioTrack_write(struct ao *ao, int len)
// reset positions for reading
jobject bbuf = MP_JNI_CALL_OBJECT(p->bbuf, ByteBuffer.clear);
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
- (*env)->DeleteLocalRef(env, bbuf);
+ MP_JNI_LOCAL_FREEP(&bbuf);
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.writeBufferV21, p->bbuf, len, AudioTrack.WRITE_BLOCKING);
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
@@ -521,35 +525,29 @@ static int AudioTrack_write(struct ao *ao, int len)
static void uninit_jni(struct ao *ao)
{
JNIEnv *env = MP_JNI_GET_ENV(ao);
- mp_jni_reset_jfields(env, &AudioTrack, AudioTrack.mapping, 1, ao->log);
- mp_jni_reset_jfields(env, &AudioTimestamp, AudioTimestamp.mapping, 1, ao->log);
- mp_jni_reset_jfields(env, &AudioManager, AudioManager.mapping, 1, ao->log);
- mp_jni_reset_jfields(env, &AudioFormat, AudioFormat.mapping, 1, ao->log);
- mp_jni_reset_jfields(env, &AudioFormatBuilder, AudioFormatBuilder.mapping, 1, ao->log);
- mp_jni_reset_jfields(env, &AudioAttributes, AudioAttributes.mapping, 1, ao->log);
- mp_jni_reset_jfields(env, &AudioAttributesBuilder, AudioAttributesBuilder.mapping, 1, ao->log);
- mp_jni_reset_jfields(env, &ByteBuffer, ByteBuffer.mapping, 1, ao->log);
+ for (int i = 0; i < MP_ARRAY_SIZE(jclass_list); i++) {
+ mp_jni_reset_jfields(env, jclass_list[i].fields,
+ jclass_list[i].mapping, 1, ao->log);
+ }
}
static int init_jni(struct ao *ao)
{
JNIEnv *env = MP_JNI_GET_ENV(ao);
- if (mp_jni_init_jfields(env, &AudioTrack, AudioTrack.mapping, 1, ao->log) < 0 ||
- mp_jni_init_jfields(env, &ByteBuffer, ByteBuffer.mapping, 1, ao->log) < 0 ||
- mp_jni_init_jfields(env, &AudioTimestamp, AudioTimestamp.mapping, 1, ao->log) < 0 ||
- mp_jni_init_jfields(env, &AudioManager, AudioManager.mapping, 1, ao->log) < 0 ||
- mp_jni_init_jfields(env, &AudioAttributes, AudioAttributes.mapping, 1, ao->log) < 0 ||
- mp_jni_init_jfields(env, &AudioAttributesBuilder, AudioAttributesBuilder.mapping, 1, ao->log) < 0 ||
- mp_jni_init_jfields(env, &AudioFormatBuilder, AudioFormatBuilder.mapping, 1, ao->log) < 0 ||
- mp_jni_init_jfields(env, &AudioFormat, AudioFormat.mapping, 1, ao->log) < 0) {
- uninit_jni(ao);
- return -1;
+ for (int i = 0; i < MP_ARRAY_SIZE(jclass_list); i++) {
+ if (mp_jni_init_jfields(env, jclass_list[i].fields,
+ jclass_list[i].mapping, 1, ao->log) < 0) {
+ goto error;
+ }
}
-
return 0;
+
+error:
+ uninit_jni(ao);
+ return -1;
}
-static MP_THREAD_VOID playthread(void *arg)
+static MP_THREAD_VOID ao_thread(void *arg)
{
struct ao *ao = arg;
struct priv *p = ao->priv;
@@ -608,34 +606,18 @@ static void uninit(struct ao *ao)
if (p->audiotrack) {
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.release);
MP_JNI_EXCEPTION_LOG(ao);
- (*env)->DeleteGlobalRef(env, p->audiotrack);
- p->audiotrack = NULL;
+ MP_JNI_GLOBAL_FREEP(&p->audiotrack);
}
- if (p->bytearray) {
- (*env)->DeleteGlobalRef(env, p->bytearray);
- p->bytearray = NULL;
- }
+ MP_JNI_GLOBAL_FREEP(&p->bytearray);
- if (p->shortarray) {
- (*env)->DeleteGlobalRef(env, p->shortarray);
- p->shortarray = NULL;
- }
+ MP_JNI_GLOBAL_FREEP(&p->shortarray);
- if (p->floatarray) {
- (*env)->DeleteGlobalRef(env, p->floatarray);
- p->floatarray = NULL;
- }
+ MP_JNI_GLOBAL_FREEP(&p->floatarray);
- if (p->bbuf) {
- (*env)->DeleteGlobalRef(env, p->bbuf);
- p->bbuf = NULL;
- }
+ MP_JNI_GLOBAL_FREEP(&p->bbuf);
- if (p->timestamp) {
- (*env)->DeleteGlobalRef(env, p->timestamp);
- p->timestamp = NULL;
- }
+ MP_JNI_GLOBAL_FREEP(&p->timestamp);
mp_cond_destroy(&p->wakeup);
mp_mutex_destroy(&p->lock);
@@ -658,6 +640,10 @@ static int init(struct ao *ao)
if (af_fmt_is_spdif(ao->format)) {
p->format = AudioFormat.ENCODING_IEC61937;
+ if (!p->format || !AudioTrack.writeShortV23) {
+ MP_ERR(ao, "spdif passthrough not supported by API\n");
+ return -1;
+ }
} else if (ao->format == AF_FORMAT_U8) {
p->format = AudioFormat.ENCODING_PCM_8BIT;
} else if (p->cfg_pcm_float && af_fmt_is_float(ao->format)) {
@@ -752,26 +738,26 @@ static int init(struct ao *ao)
return -1;
}
p->timestamp = (*env)->NewGlobalRef(env, timestamp);
- (*env)->DeleteLocalRef(env, timestamp);
+ MP_JNI_LOCAL_FREEP(&timestamp);
// decide and create buffer of right type
if (p->format == AudioFormat.ENCODING_IEC61937) {
jshortArray shortarray = (*env)->NewShortArray(env, p->chunksize / 2);
p->shortarray = (*env)->NewGlobalRef(env, shortarray);
- (*env)->DeleteLocalRef(env, shortarray);
+ MP_JNI_LOCAL_FREEP(&shortarray);
} else if (AudioTrack.writeBufferV21) {
MP_VERBOSE(ao, "Using NIO ByteBuffer\n");
jobject bbuf = (*env)->NewDirectByteBuffer(env, p->chunk, p->chunksize);
p->bbuf = (*env)->NewGlobalRef(env, bbuf);
- (*env)->DeleteLocalRef(env, bbuf);
+ MP_JNI_LOCAL_FREEP(&bbuf);
} else if (p->format == AudioFormat.ENCODING_PCM_FLOAT) {
jfloatArray floatarray = (*env)->NewFloatArray(env, p->chunksize / sizeof(float));
p->floatarray = (*env)->NewGlobalRef(env, floatarray);
- (*env)->DeleteLocalRef(env, floatarray);
+ MP_JNI_LOCAL_FREEP(&floatarray);
} else {
jbyteArray bytearray = (*env)->NewByteArray(env, p->chunksize);
p->bytearray = (*env)->NewGlobalRef(env, bytearray);
- (*env)->DeleteLocalRef(env, bytearray);
+ MP_JNI_LOCAL_FREEP(&bytearray);
}
/* create AudioTrack object */
@@ -780,7 +766,7 @@ static int init(struct ao *ao)
goto error;
}
- if (mp_thread_create(&p->thread, playthread, ao)) {
+ if (mp_thread_create(&p->thread, ao_thread, ao)) {
MP_ERR(ao, "pthread creation failed\n");
goto error;
}
@@ -812,7 +798,6 @@ static void stop(struct ao *ao)
p->written_frames = 0;
p->timestamp_fetched = 0;
p->timestamp_set = false;
- p->timestamp_offset = 0;
}
static void start(struct ao *ao)
diff --git a/audio/out/ao_avfoundation.m b/audio/out/ao_avfoundation.m
new file mode 100644
index 0000000..7654916
--- /dev/null
+++ b/audio/out/ao_avfoundation.m
@@ -0,0 +1,372 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ao.h"
+#include "audio/format.h"
+#include "audio/out/ao_coreaudio_chmap.h"
+#include "audio/out/ao_coreaudio_utils.h"
+#include "common/common.h"
+#include "common/msg.h"
+#include "internal.h"
+#include "osdep/timer.h"
+#include "ta/ta_talloc.h"
+
+#import <AVFoundation/AVFoundation.h>
+#import <Foundation/Foundation.h>
+#import <CoreAudioTypes/CoreAudioTypes.h>
+#import <CoreFoundation/CoreFoundation.h>
+#import <CoreMedia/CoreMedia.h>
+
+
+@interface AVObserver : NSObject {
+ struct ao *ao;
+}
+- (void)handleRestartNotification:(NSNotification*)notification;
+@end
+
+struct priv {
+ AVSampleBufferAudioRenderer *renderer;
+ AVSampleBufferRenderSynchronizer *synchronizer;
+ dispatch_queue_t queue;
+ CMAudioFormatDescriptionRef format_description;
+ AVObserver *observer;
+ int64_t end_time_av;
+};
+
+static int64_t CMTimeGetNanoseconds(CMTime time)
+{
+ time = CMTimeConvertScale(time, 1000000000, kCMTimeRoundingMethod_Default);
+ return time.value;
+}
+
+static CMTime CMTimeFromNanoseconds(int64_t time)
+{
+ return CMTimeMake(time, 1000000000);
+}
+
+static void feed(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+ int samplerate = ao->samplerate;
+ int sstride = ao->sstride;
+
+ CMBlockBufferRef block_buffer = NULL;
+ CMSampleBufferRef sample_buffer = NULL;
+ OSStatus err;
+
+ int request_sample_count = samplerate / 10;
+ int buffer_size = request_sample_count * sstride;
+ void *data[] = {CFAllocatorAllocate(NULL, buffer_size, 0)};
+
+ int64_t cur_time_av = CMTimeGetNanoseconds([p->synchronizer currentTime]);
+ int64_t cur_time_mp = mp_time_ns();
+ int64_t end_time_av = MPMAX(p->end_time_av, cur_time_av);
+ int64_t time_delta = CMTimeGetNanoseconds(CMTimeMake(request_sample_count, samplerate));
+ int real_sample_count = ao_read_data_nonblocking(ao, data, request_sample_count, end_time_av - cur_time_av + cur_time_mp + time_delta);
+ if (real_sample_count == 0) {
+ // avoid spinning by blocking the thread
+ mp_sleep_ns(10000000);
+ goto finish;
+ }
+
+ if ((err = CMBlockBufferCreateWithMemoryBlock(
+ NULL,
+ data[0],
+ buffer_size,
+ NULL,
+ NULL,
+ 0,
+ real_sample_count * sstride,
+ 0,
+ &block_buffer
+ )) != noErr) {
+ MP_FATAL(ao, "failed to create block buffer\n");
+ MP_VERBOSE(ao, "CMBlockBufferCreateWithMemoryBlock returned %d\n", err);
+ goto error;
+ }
+ data[0] = NULL;
+
+ CMSampleTimingInfo sample_timing_into[] = {(CMSampleTimingInfo) {
+ .duration = CMTimeMake(1, samplerate),
+ .presentationTimeStamp = CMTimeFromNanoseconds(end_time_av),
+ .decodeTimeStamp = kCMTimeInvalid
+ }};
+ size_t sample_size_array[] = {sstride};
+ if ((err = CMSampleBufferCreateReady(
+ NULL,
+ block_buffer,
+ p->format_description,
+ real_sample_count,
+ 1,
+ sample_timing_into,
+ 1,
+ sample_size_array,
+ &sample_buffer
+ )) != noErr) {
+ MP_FATAL(ao, "failed to create sample buffer\n");
+ MP_VERBOSE(ao, "CMSampleBufferCreateReady returned %d\n", err);
+ goto error;
+ }
+
+ [p->renderer enqueueSampleBuffer:sample_buffer];
+
+ time_delta = CMTimeGetNanoseconds(CMTimeMake(real_sample_count, samplerate));
+ p->end_time_av = end_time_av + time_delta;
+
+ goto finish;
+
+error:
+ ao_request_reload(ao);
+finish:
+ if (data[0]) CFAllocatorDeallocate(NULL, data[0]);
+ if (block_buffer) CFRelease(block_buffer);
+ if (sample_buffer) CFRelease(sample_buffer);
+}
+
+static void start(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+
+ p->end_time_av = -1;
+ [p->synchronizer setRate:1];
+ [p->renderer requestMediaDataWhenReadyOnQueue:p->queue usingBlock:^{
+ feed(ao);
+ }];
+}
+
+static void stop(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+
+ dispatch_sync(p->queue, ^{
+ [p->renderer stopRequestingMediaData];
+ [p->renderer flush];
+ [p->synchronizer setRate:0];
+ });
+}
+
+static bool set_pause(struct ao *ao, bool paused)
+{
+ struct priv *p = ao->priv;
+
+ if (paused) {
+ [p->synchronizer setRate:0];
+ } else {
+ [p->synchronizer setRate:1];
+ }
+
+ return true;
+}
+
+static int control(struct ao *ao, enum aocontrol cmd, void *arg)
+{
+ struct priv *p = ao->priv;
+
+ switch (cmd) {
+ case AOCONTROL_GET_MUTE:
+ *(bool*)arg = [p->renderer isMuted];
+ return CONTROL_OK;
+ case AOCONTROL_GET_VOLUME:
+ *(float*)arg = [p->renderer volume] * 100;
+ return CONTROL_OK;
+ case AOCONTROL_SET_MUTE:
+ [p->renderer setMuted:*(bool*)arg];
+ return CONTROL_OK;
+ case AOCONTROL_SET_VOLUME:
+ [p->renderer setVolume:*(float*)arg / 100];
+ return CONTROL_OK;
+ default:
+ return CONTROL_UNKNOWN;
+ }
+}
+
+@implementation AVObserver
+- (instancetype)initWithAO:(struct ao*)_ao {
+ self = [super init];
+ if (self) {
+ ao = _ao;
+ }
+ return self;
+}
+- (void)handleRestartNotification:(NSNotification*)notification {
+ char *name = cfstr_get_cstr((CFStringRef)notification.name);
+ MP_WARN(ao, "restarting due to system notification; this will cause desync\n");
+ MP_VERBOSE(ao, "notification name: %s\n", name);
+ talloc_free(name);
+ stop(ao);
+ start(ao);
+}
+@end
+
+static int init(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+ AudioChannelLayout *layout = NULL;
+
+#if TARGET_OS_IPHONE
+ AVAudioSession *instance = AVAudioSession.sharedInstance;
+ 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];
+#endif
+
+ if ((p->renderer = [[AVSampleBufferAudioRenderer alloc] init]) == nil) {
+ MP_FATAL(ao, "failed to create audio renderer\n");
+ MP_VERBOSE(ao, "AVSampleBufferAudioRenderer failed to initialize\n");
+ goto error;
+ }
+ if ((p->synchronizer = [[AVSampleBufferRenderSynchronizer alloc] init]) == nil) {
+ MP_FATAL(ao, "failed to create rendering synchronizer\n");
+ MP_VERBOSE(ao, "AVSampleBufferRenderSynchronizer failed to initialize\n");
+ goto error;
+ }
+ if ((p->queue = dispatch_queue_create(
+ "avfoundation event",
+ dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0)
+ )) == NULL) {
+ MP_FATAL(ao, "failed to create dispatch queue\n");
+ MP_VERBOSE(ao, "dispatch_queue_create failed\n");
+ goto error;
+ }
+
+ if (ao->device && ao->device[0]) {
+ [p->renderer setAudioOutputDeviceUniqueID:(NSString*)cfstr_from_cstr(ao->device)];
+ }
+
+ [p->synchronizer addRenderer:p->renderer];
+ if (@available(tvOS 14.5, iOS 14.5, macOS 11.3, *)) {
+ [p->synchronizer setDelaysRateChangeUntilHasSufficientMediaData:NO];
+ }
+
+ if (af_fmt_is_spdif(ao->format)) {
+ MP_FATAL(ao, "avfoundation does not support SPDIF\n");
+#if HAVE_COREAUDIO
+ MP_FATAL(ao, "please use coreaudio_exclusive instead\n");
+#endif
+ goto error;
+ }
+
+ // AVSampleBufferAudioRenderer only supports interleaved formats
+ ao->format = af_fmt_from_planar(ao->format);
+ if (af_fmt_is_planar(ao->format)) {
+ MP_FATAL(ao, "planar audio formats are unsupported\n");
+ goto error;
+ }
+
+ AudioStreamBasicDescription asbd;
+ ca_fill_asbd(ao, &asbd);
+ size_t layout_size = sizeof(AudioChannelLayout)
+ + (ao->channels.num - 1) * sizeof(AudioChannelDescription);
+ layout = talloc_size(ao, layout_size);
+ layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
+ layout->mNumberChannelDescriptions = ao->channels.num;
+ for (int i = 0; i < ao->channels.num; ++i) {
+ AudioChannelDescription *desc = layout->mChannelDescriptions + i;
+ desc->mChannelFlags = kAudioChannelFlags_AllOff;
+ desc->mChannelLabel = mp_speaker_id_to_ca_label(ao->channels.speaker[i]);
+ }
+
+ void *talloc_ctx = talloc_new(NULL);
+ AudioChannelLayout *std_layout = ca_find_standard_layout(talloc_ctx, layout);
+ memmove(layout, std_layout, sizeof(AudioChannelLayout));
+ talloc_free(talloc_ctx);
+ ca_log_layout(ao, MSGL_V, layout);
+
+ OSStatus err;
+ if ((err = CMAudioFormatDescriptionCreate(
+ NULL,
+ &asbd,
+ layout_size,
+ layout,
+ 0,
+ NULL,
+ NULL,
+ &p->format_description
+ )) != noErr) {
+ MP_FATAL(ao, "failed to create audio format description\n");
+ MP_VERBOSE(ao, "CMAudioFormatDescriptionCreate returned %d\n", err);
+ goto error;
+ }
+ talloc_free(layout);
+ layout = NULL;
+
+ // AVSampleBufferAudioRenderer read ahead aggressively
+ ao->device_buffer = ao->samplerate * 2;
+
+ p->observer = [[AVObserver alloc] initWithAO:ao];
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+ [center addObserver:p->observer selector:@selector(handleRestartNotification:) name:AVSampleBufferAudioRendererOutputConfigurationDidChangeNotification object:p->renderer];
+ [center addObserver:p->observer selector:@selector(handleRestartNotification:) name:AVSampleBufferAudioRendererWasFlushedAutomaticallyNotification object:p->renderer];
+
+ return CONTROL_OK;
+
+error:
+ talloc_free(layout);
+ if (p->renderer) [p->renderer release];
+ if (p->synchronizer) [p->synchronizer release];
+ if (p->queue) dispatch_release(p->queue);
+ if (p->format_description) CFRelease(p->format_description);
+
+#if TARGET_OS_IPHONE
+ [AVAudioSession.sharedInstance setActive:NO
+ withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
+ error:nil
+ ];
+#endif
+
+ return CONTROL_ERROR;
+}
+
+static void uninit(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+
+ stop(ao);
+
+ [p->renderer release];
+ [p->synchronizer release];
+ dispatch_release(p->queue);
+ CFRelease(p->format_description);
+
+ [[NSNotificationCenter defaultCenter] removeObserver:p->observer];
+ [p->observer release];
+
+#if TARGET_OS_IPHONE
+ [AVAudioSession.sharedInstance setActive:NO
+ withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
+ error:nil
+ ];
+#endif
+}
+
+#define OPT_BASE_STRUCT struct priv
+
+const struct ao_driver audio_out_avfoundation = {
+ .description = "AVFoundation AVSampleBufferAudioRenderer",
+ .name = "avfoundation",
+ .uninit = uninit,
+ .init = init,
+ .control = control,
+ .reset = stop,
+ .start = start,
+ .set_pause = set_pause,
+ .list_devs = ca_get_device_list,
+ .priv_size = sizeof(struct priv),
+};
diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c
index 37f1313..ae743c9 100644
--- a/audio/out/ao_coreaudio.c
+++ b/audio/out/ao_coreaudio.c
@@ -27,6 +27,11 @@
#include "ao_coreaudio_properties.h"
#include "ao_coreaudio_utils.h"
+// The timeout for stopping the audio unit after being reset. This allows the
+// device to sleep after playback paused. The duration is chosen to match the
+// behavior of AVFoundation.
+#define IDLE_TIME 7 * NSEC_PER_SEC
+
struct priv {
AudioDeviceID device;
AudioUnit audio_unit;
@@ -37,6 +42,12 @@ struct priv {
AudioStreamID original_asbd_stream;
bool change_physical_format;
+
+ // Block that is executed after `IDLE_TIME` to stop audio output unit.
+ dispatch_block_t idle_work;
+ dispatch_queue_t queue;
+
+ int hotplug_cb_registration_times;
};
static int64_t ca_get_hardware_latency(struct ao *ao) {
@@ -78,7 +89,7 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
int64_t end = mp_time_ns();
end += p->hw_latency_ns + ca_get_latency(ts) + ca_frames_to_ns(ao, frames);
- int samples = ao_read_data_nonblocking(ao, planes, frames, end);
+ int samples = ao_read_data(ao, planes, frames, end);
if (samples == 0)
*aflags |= kAudioUnitRenderAction_OutputIsSilence;
@@ -128,6 +139,9 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd);
static void init_physical_format(struct ao *ao);
+static void reinit_latency(struct ao *ao);
+static bool register_hotplug_cb(struct ao *ao);
+static void unregister_hotplug_cb(struct ao *ao);
static bool reinit_device(struct ao *ao) {
struct priv *p = ao->priv;
@@ -154,6 +168,9 @@ static int init(struct ao *ao)
if (!reinit_device(ao))
goto coreaudio_error;
+ if (!register_hotplug_cb(ao))
+ goto coreaudio_error;
+
if (p->change_physical_format)
init_physical_format(ao);
@@ -166,6 +183,11 @@ static int init(struct ao *ao)
if (!init_audiounit(ao, asbd))
goto coreaudio_error;
+ reinit_latency(ao);
+
+ p->queue = dispatch_queue_create("io.mpv.coreaudio_stop_during_idle",
+ DISPATCH_QUEUE_SERIAL);
+
return CONTROL_OK;
coreaudio_error:
@@ -295,8 +317,6 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
"can't link audio unit to selected device");
- p->hw_latency_ns = ca_get_hardware_latency(ao);
-
AURenderCallbackStruct render_cb = (AURenderCallbackStruct) {
.inputProc = render_cb_lpcm,
.inputProcRefCon = ao,
@@ -320,24 +340,96 @@ coreaudio_error:
return false;
}
-static void reset(struct ao *ao)
+static void reinit_latency(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+
+ p->hw_latency_ns = ca_get_hardware_latency(ao);
+}
+
+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 cancel_and_release_idle_work(struct priv *p)
+{
+ if (!p->idle_work)
+ return;
+
+ dispatch_block_cancel(p->idle_work);
+ Block_release(p->idle_work);
+ p->idle_work = NULL;
+}
+
+static void stop_after_idle_time(struct ao *ao)
{
struct priv *p = ao->priv;
+
+ cancel_and_release_idle_work(p);
+
+ p->idle_work = dispatch_block_create(0, ^{
+ MP_VERBOSE(ao, "Stopping audio unit due to idle timeout\n");
+ stop(ao);
+ });
+
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, IDLE_TIME),
+ p->queue, p->idle_work);
+}
+
+static void _reset(void *_ao)
+{
+ struct ao *ao = (struct ao *)_ao;
+ struct priv *p = ao->priv;
OSStatus err = AudioUnitReset(p->audio_unit, kAudioUnitScope_Global, 0);
CHECK_CA_WARN("can't reset audio unit");
+
+ // Until the audio unit is stopped the macOS daemon coreaudiod continues to
+ // consume CPU and prevent macOS from sleeping. Immediately stopping the
+ // audio unit would be disruptive for short pause/resume cycles as
+ // restarting the audio unit takes a noticeable amount of time when a
+ // wireless audio device is being used. Instead the audio unit is stopped
+ // after a delay if it remains idle.
+ stop_after_idle_time(ao);
}
-static void start(struct ao *ao)
+static void reset(struct ao *ao)
{
struct priv *p = ao->priv;
+ // Must dispatch to serialize reset, start and stop operations.
+ dispatch_sync_f(p->queue, ao, &_reset);
+}
+
+static void _start(void *_ao)
+{
+ struct ao *ao = (struct ao *)_ao;
+ struct priv *p = ao->priv;
+
+ if (p->idle_work)
+ dispatch_block_cancel(p->idle_work);
+
OSStatus err = AudioOutputUnitStart(p->audio_unit);
CHECK_CA_WARN("can't start audio unit");
}
+static void start(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+ // Must dispatch to serialize reset, start and stop operations.
+ dispatch_sync_f(p->queue, ao, &_start);
+}
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;
+
+ dispatch_sync(p->queue, ^{
+ cancel_and_release_idle_work(p);
+ });
+ dispatch_release(p->queue);
+
AudioOutputUnitStop(p->audio_unit);
AudioUnitUninitialize(p->audio_unit);
AudioComponentInstanceDispose(p->audio_unit);
@@ -348,6 +440,8 @@ static void uninit(struct ao *ao)
&p->original_asbd);
CHECK_CA_WARN("could not restore physical stream format");
}
+
+ unregister_hotplug_cb(ao);
}
static OSStatus hotplug_cb(AudioObjectID id, UInt32 naddr,
@@ -355,8 +449,11 @@ static OSStatus hotplug_cb(AudioObjectID id, UInt32 naddr,
void *ctx)
{
struct ao *ao = ctx;
+ struct priv *p = ao->priv;
MP_VERBOSE(ao, "Handling potential hotplug event...\n");
reinit_device(ao);
+ if (p->audio_unit)
+ reinit_latency(ao);
ao_hotplug_event(ao);
return noErr;
}
@@ -369,7 +466,25 @@ static uint32_t hotplug_properties[] = {
static int hotplug_init(struct ao *ao)
{
if (!reinit_device(ao))
- goto coreaudio_error;
+ return -1;
+
+ if (!register_hotplug_cb(ao))
+ return -1;
+
+ return 0;
+}
+
+static void hotplug_uninit(struct ao *ao)
+{
+ unregister_hotplug_cb(ao);
+}
+
+static bool register_hotplug_cb(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+
+ if (p->hotplug_cb_registration_times++)
+ return true;
OSStatus err = noErr;
for (int i = 0; i < MP_ARRAY_SIZE(hotplug_properties); i++) {
@@ -388,14 +503,19 @@ static int hotplug_init(struct ao *ao)
}
}
- return 0;
+ return true;
coreaudio_error:
- return -1;
+ return false;
}
-static void hotplug_uninit(struct ao *ao)
+static void unregister_hotplug_cb(struct ao *ao)
{
+ struct priv *p = ao->priv;
+
+ if (--p->hotplug_cb_registration_times)
+ return;
+
OSStatus err = noErr;
for (int i = 0; i < MP_ARRAY_SIZE(hotplug_properties); i++) {
AudioObjectPropertyAddress addr = {
diff --git a/audio/out/ao_coreaudio_chmap.c b/audio/out/ao_coreaudio_chmap.c
index 3fd9550..5a129c4 100644
--- a/audio/out/ao_coreaudio_chmap.c
+++ b/audio/out/ao_coreaudio_chmap.c
@@ -22,6 +22,7 @@
#include "ao_coreaudio_utils.h"
#include "ao_coreaudio_chmap.h"
+#include <CoreAudioTypes/CoreAudioTypes.h>
static const int speaker_map[][2] = {
{ kAudioChannelLabel_Left, MP_SPEAKER_ID_FL },
@@ -65,6 +66,119 @@ static const int speaker_map[][2] = {
{ 0, -1 },
};
+static const AudioChannelLayoutTag std_layouts[] = {
+ (100U<<16) | 1, // kAudioChannelLayoutTag_Mono
+ (101U<<16) | 2, // kAudioChannelLayoutTag_Stereo
+ (102U<<16) | 2, // kAudioChannelLayoutTag_StereoHeadphones
+ (103U<<16) | 2, // kAudioChannelLayoutTag_MatrixStereo
+ (104U<<16) | 2, // kAudioChannelLayoutTag_MidSide
+ (105U<<16) | 2, // kAudioChannelLayoutTag_XY
+ (106U<<16) | 2, // kAudioChannelLayoutTag_Binaural
+ (107U<<16) | 4, // kAudioChannelLayoutTag_Ambisonic_B_Format
+ (108U<<16) | 4, // kAudioChannelLayoutTag_Quadraphonic
+ (109U<<16) | 5, // kAudioChannelLayoutTag_Pentagonal
+ (110U<<16) | 6, // kAudioChannelLayoutTag_Hexagonal
+ (111U<<16) | 8, // kAudioChannelLayoutTag_Octagonal
+ (112U<<16) | 8, // kAudioChannelLayoutTag_Cube
+ (113U<<16) | 3, // kAudioChannelLayoutTag_MPEG_3_0_A
+ (114U<<16) | 3, // kAudioChannelLayoutTag_MPEG_3_0_B
+ (115U<<16) | 4, // kAudioChannelLayoutTag_MPEG_4_0_A
+ (116U<<16) | 4, // kAudioChannelLayoutTag_MPEG_4_0_B
+ (117U<<16) | 5, // kAudioChannelLayoutTag_MPEG_5_0_A
+ (118U<<16) | 5, // kAudioChannelLayoutTag_MPEG_5_0_B
+ (119U<<16) | 5, // kAudioChannelLayoutTag_MPEG_5_0_C
+ (120U<<16) | 5, // kAudioChannelLayoutTag_MPEG_5_0_D
+ (121U<<16) | 6, // kAudioChannelLayoutTag_MPEG_5_1_A
+ (122U<<16) | 6, // kAudioChannelLayoutTag_MPEG_5_1_B
+ (123U<<16) | 6, // kAudioChannelLayoutTag_MPEG_5_1_C
+ (124U<<16) | 6, // kAudioChannelLayoutTag_MPEG_5_1_D
+ (125U<<16) | 7, // kAudioChannelLayoutTag_MPEG_6_1_A
+ (126U<<16) | 8, // kAudioChannelLayoutTag_MPEG_7_1_A
+ (127U<<16) | 8, // kAudioChannelLayoutTag_MPEG_7_1_B
+ (128U<<16) | 8, // kAudioChannelLayoutTag_MPEG_7_1_C
+ (129U<<16) | 8, // kAudioChannelLayoutTag_Emagic_Default_7_1
+ (130U<<16) | 8, // kAudioChannelLayoutTag_SMPTE_DTV
+ (131U<<16) | 3, // kAudioChannelLayoutTag_ITU_2_1
+ (132U<<16) | 4, // kAudioChannelLayoutTag_ITU_2_2
+ (133U<<16) | 3, // kAudioChannelLayoutTag_DVD_4
+ (134U<<16) | 4, // kAudioChannelLayoutTag_DVD_5
+ (135U<<16) | 5, // kAudioChannelLayoutTag_DVD_6
+ (136U<<16) | 4, // kAudioChannelLayoutTag_DVD_10
+ (137U<<16) | 5, // kAudioChannelLayoutTag_DVD_11
+ (138U<<16) | 5, // kAudioChannelLayoutTag_DVD_18
+ (139U<<16) | 6, // kAudioChannelLayoutTag_AudioUnit_6_0
+ (140U<<16) | 7, // kAudioChannelLayoutTag_AudioUnit_7_0
+ (148U<<16) | 7, // kAudioChannelLayoutTag_AudioUnit_7_0_Front
+ (141U<<16) | 6, // kAudioChannelLayoutTag_AAC_6_0
+ (142U<<16) | 7, // kAudioChannelLayoutTag_AAC_6_1
+ (143U<<16) | 7, // kAudioChannelLayoutTag_AAC_7_0
+ (183U<<16) | 8, // kAudioChannelLayoutTag_AAC_7_1_B
+ (184U<<16) | 8, // kAudioChannelLayoutTag_AAC_7_1_C
+ (144U<<16) | 8, // kAudioChannelLayoutTag_AAC_Octagonal
+ (145U<<16) | 16, // kAudioChannelLayoutTag_TMH_10_2_std
+ (146U<<16) | 21, // kAudioChannelLayoutTag_TMH_10_2_full
+ (149U<<16) | 2, // kAudioChannelLayoutTag_AC3_1_0_1
+ (150U<<16) | 3, // kAudioChannelLayoutTag_AC3_3_0
+ (151U<<16) | 4, // kAudioChannelLayoutTag_AC3_3_1
+ (152U<<16) | 4, // kAudioChannelLayoutTag_AC3_3_0_1
+ (153U<<16) | 4, // kAudioChannelLayoutTag_AC3_2_1_1
+ (154U<<16) | 5, // kAudioChannelLayoutTag_AC3_3_1_1
+ (155U<<16) | 6, // kAudioChannelLayoutTag_EAC_6_0_A
+ (156U<<16) | 7, // kAudioChannelLayoutTag_EAC_7_0_A
+ (157U<<16) | 7, // kAudioChannelLayoutTag_EAC3_6_1_A
+ (158U<<16) | 7, // kAudioChannelLayoutTag_EAC3_6_1_B
+ (159U<<16) | 7, // kAudioChannelLayoutTag_EAC3_6_1_C
+ (160U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_A
+ (161U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_B
+ (162U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_C
+ (163U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_D
+ (164U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_E
+ (165U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_F
+ (166U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_G
+ (167U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_H
+ (168U<<16) | 4, // kAudioChannelLayoutTag_DTS_3_1
+ (169U<<16) | 5, // kAudioChannelLayoutTag_DTS_4_1
+ (170U<<16) | 6, // kAudioChannelLayoutTag_DTS_6_0_A
+ (171U<<16) | 6, // kAudioChannelLayoutTag_DTS_6_0_B
+ (172U<<16) | 6, // kAudioChannelLayoutTag_DTS_6_0_C
+ (173U<<16) | 7, // kAudioChannelLayoutTag_DTS_6_1_A
+ (174U<<16) | 7, // kAudioChannelLayoutTag_DTS_6_1_B
+ (175U<<16) | 7, // kAudioChannelLayoutTag_DTS_6_1_C
+ (176U<<16) | 7, // kAudioChannelLayoutTag_DTS_7_0
+ (177U<<16) | 8, // kAudioChannelLayoutTag_DTS_7_1
+ (178U<<16) | 8, // kAudioChannelLayoutTag_DTS_8_0_A
+ (179U<<16) | 8, // kAudioChannelLayoutTag_DTS_8_0_B
+ (180U<<16) | 9, // kAudioChannelLayoutTag_DTS_8_1_A
+ (181U<<16) | 9, // kAudioChannelLayoutTag_DTS_8_1_B
+ (182U<<16) | 7, // kAudioChannelLayoutTag_DTS_6_1_D
+ (185U<<16) | 4, // kAudioChannelLayoutTag_WAVE_4_0_B
+ (186U<<16) | 5, // kAudioChannelLayoutTag_WAVE_5_0_B
+ (187U<<16) | 6, // kAudioChannelLayoutTag_WAVE_5_1_B
+ (188U<<16) | 7, // kAudioChannelLayoutTag_WAVE_6_1
+ (189U<<16) | 8, // kAudioChannelLayoutTag_WAVE_7_1
+ (194U<<16) | 8, // kAudioChannelLayoutTag_Atmos_5_1_2
+ (195U<<16) | 10, // kAudioChannelLayoutTag_Atmos_5_1_4
+ (196U<<16) | 10, // kAudioChannelLayoutTag_Atmos_7_1_2
+ (192U<<16) | 12, // kAudioChannelLayoutTag_Atmos_7_1_4
+ (193U<<16) | 16, // kAudioChannelLayoutTag_Atmos_9_1_6
+ (197U<<16) | 4, // kAudioChannelLayoutTag_Logic_4_0_C
+ (198U<<16) | 6, // kAudioChannelLayoutTag_Logic_6_0_B
+ (199U<<16) | 7, // kAudioChannelLayoutTag_Logic_6_1_B
+ (200U<<16) | 7, // kAudioChannelLayoutTag_Logic_6_1_D
+ (201U<<16) | 8, // kAudioChannelLayoutTag_Logic_7_1_B
+ (202U<<16) | 12, // kAudioChannelLayoutTag_Logic_Atmos_7_1_4_B
+ (203U<<16) | 14, // kAudioChannelLayoutTag_Logic_Atmos_7_1_6
+ (204U<<16) | 24, // kAudioChannelLayoutTag_CICP_13
+ (205U<<16) | 8, // kAudioChannelLayoutTag_CICP_14
+ (206U<<16) | 12, // kAudioChannelLayoutTag_CICP_15
+ (207U<<16) | 10, // kAudioChannelLayoutTag_CICP_16
+ (208U<<16) | 12, // kAudioChannelLayoutTag_CICP_17
+ (209U<<16) | 14, // kAudioChannelLayoutTag_CICP_18
+ (210U<<16) | 12, // kAudioChannelLayoutTag_CICP_19
+ (211U<<16) | 14, // kAudioChannelLayoutTag_CICP_20
+ kAudioChannelLayoutTag_Unknown
+};
+
int ca_label_to_mp_speaker_id(AudioChannelLabel label)
{
for (int i = 0; speaker_map[i][1] >= 0; i++)
@@ -73,30 +187,48 @@ int ca_label_to_mp_speaker_id(AudioChannelLabel label)
return -1;
}
+AudioChannelLabel mp_speaker_id_to_ca_label(int speaker_id)
+{
+ for (int i = 0; speaker_map[i][1] >= 0; i++)
+ if (speaker_map[i][1] == speaker_id)
+ return speaker_map[i][0];
+ return -1; // kAudioChannelLabel_Unknown
+}
+
#if HAVE_COREAUDIO
-static void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout)
+void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout)
{
if (!mp_msg_test(ao->log, l))
return;
- AudioChannelDescription *descs = layout->mChannelDescriptions;
-
- mp_msg(ao->log, l, "layout: tag: <%u>, bitmap: <%u>, "
- "descriptions <%u>\n",
- (unsigned) layout->mChannelLayoutTag,
- (unsigned) layout->mChannelBitmap,
- (unsigned) layout->mNumberChannelDescriptions);
-
- for (int i = 0; i < layout->mNumberChannelDescriptions; i++) {
- AudioChannelDescription d = descs[i];
- mp_msg(ao->log, l, " - description %d: label <%u, %u>, "
- " flags: <%u>, coords: <%f, %f, %f>\n", i,
- (unsigned) d.mChannelLabel,
- (unsigned) ca_label_to_mp_speaker_id(d.mChannelLabel),
- (unsigned) d.mChannelFlags,
- d.mCoordinates[0],
- d.mCoordinates[1],
- d.mCoordinates[2]);
+ AudioChannelLayoutTag tag = layout->mChannelLayoutTag;
+ mp_msg(ao->log, l, "audio channel layout: tag: <%u>", tag);
+
+ if (tag == kAudioChannelLayoutTag_UseChannelDescriptions) {
+ AudioChannelDescription *descs = layout->mChannelDescriptions;
+ mp_msg(ao->log, l, ", descriptions <%u>\n",
+ (unsigned) layout->mNumberChannelDescriptions);
+
+ for (int i = 0; i < layout->mNumberChannelDescriptions; i++) {
+ AudioChannelDescription d = descs[i];
+ mp_msg(ao->log, l, " - description %d: label <%u, %u>, flags: <%u>",
+ i,
+ (unsigned) d.mChannelLabel,
+ (unsigned) ca_label_to_mp_speaker_id(d.mChannelLabel),
+ (unsigned) d.mChannelFlags);
+ if (d.mChannelFlags != kAudioChannelFlags_AllOff) {
+ mp_msg(ao->log, l, ", coords: <%f, %f, %f>\n",
+ d.mCoordinates[0],
+ d.mCoordinates[1],
+ d.mCoordinates[2]);
+ } else {
+ mp_msg(ao->log, l, "\n");
+ }
+ }
+ } else if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
+ mp_msg(ao->log, l, ", bitmap <%u>\n", layout->mChannelBitmap);
+ } else {
+ mp_msg(ao->log, l, "\n");
}
}
@@ -117,7 +249,7 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
kAudioFormatProperty_ChannelLayoutForBitmap,
sizeof(uint32_t), &l->mChannelBitmap, &psize);
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (info)");
- r = talloc_size(NULL, psize);
+ r = talloc_size(talloc_ctx, psize);
err = AudioFormatGetProperty(
kAudioFormatProperty_ChannelLayoutForBitmap,
sizeof(uint32_t), &l->mChannelBitmap, &psize, r);
@@ -127,7 +259,7 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
err = AudioFormatGetPropertyInfo(
kAudioFormatProperty_ChannelLayoutForTag,
sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize);
- r = talloc_size(NULL, psize);
+ r = talloc_size(talloc_ctx, psize);
CHECK_CA_ERROR("failed to convert channel tag to descriptions (info)");
err = AudioFormatGetProperty(
kAudioFormatProperty_ChannelLayoutForTag,
@@ -135,14 +267,53 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
CHECK_CA_ERROR("failed to convert channel tag to descriptions (get)");
}
- MP_VERBOSE(ao, "converted input channel layout:\n");
- ca_log_layout(ao, MSGL_V, l);
+ if (ao) {
+ MP_VERBOSE(ao, "converted input channel layout:\n");
+ ca_log_layout(ao, MSGL_V, l);
+ }
return r;
coreaudio_error:
return NULL;
}
+AudioChannelLayout *ca_find_standard_layout(void *talloc_ctx, AudioChannelLayout *l)
+{
+ if (l->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions)
+ return l;
+
+ AudioChannelLayout *s = talloc_size(talloc_ctx, sizeof(AudioChannelLayout));
+
+ for (int i = 0; ; ++i) {
+ if ((s->mChannelLayoutTag = std_layouts[i]) == kAudioChannelLayoutTag_Unknown) {
+ s = NULL;
+ break;
+ }
+
+ AudioChannelLayout *r = ca_layout_to_custom_layout(NULL, talloc_ctx, s);
+
+ if (!r)
+ goto mismatch;
+ if (l->mNumberChannelDescriptions != r->mNumberChannelDescriptions)
+ goto mismatch;
+
+ for (int i = 0; i < l->mNumberChannelDescriptions; ++i) {
+ AudioChannelDescription *ld = l->mChannelDescriptions + i;
+ AudioChannelDescription *rd = r->mChannelDescriptions + i;
+ if (ld->mChannelLabel == rd->mChannelLabel)
+ continue;
+ // XXX: we cannot handle channels with coordinates
+ goto mismatch;
+ }
+
+ break;
+
+mismatch:;
+ }
+
+ return s ? s : l;
+}
+
#define CHMAP(n, ...) &(struct mp_chmap) MP_CONCAT(MP_CHMAP, n) (__VA_ARGS__)
@@ -241,8 +412,8 @@ static AudioChannelLayout* ca_query_stereo_layout(struct ao *ao,
void *talloc_ctx)
{
OSStatus err;
- const int nch = 2;
- uint32_t channels[nch];
+ uint32_t channels[2];
+ const int nch = MP_ARRAY_SIZE(channels);
AudioChannelLayout *r = NULL;
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
diff --git a/audio/out/ao_coreaudio_chmap.h b/audio/out/ao_coreaudio_chmap.h
index b6d160c..0b21e83 100644
--- a/audio/out/ao_coreaudio_chmap.h
+++ b/audio/out/ao_coreaudio_chmap.h
@@ -18,15 +18,22 @@
#ifndef MPV_COREAUDIO_CHMAP_H
#define MPV_COREAUDIO_CHMAP_H
+#include "config.h"
#include <AudioToolbox/AudioToolbox.h>
-#include "config.h"
+#if HAVE_AVFOUNDATION || HAVE_AUDIOUNIT
+#undef HAVE_COREAUDIO
+#define HAVE_COREAUDIO 1
+#endif
struct mp_chmap;
int ca_label_to_mp_speaker_id(AudioChannelLabel label);
+AudioChannelLabel mp_speaker_id_to_ca_label(int speaker_id);
#if HAVE_COREAUDIO
+AudioChannelLayout *ca_find_standard_layout(void *talloc_ctx, AudioChannelLayout *l);
+void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout);
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);
diff --git a/audio/out/ao_coreaudio_exclusive.c b/audio/out/ao_coreaudio_exclusive.c
index e24f791..5e0ec3b 100644
--- a/audio/out/ao_coreaudio_exclusive.c
+++ b/audio/out/ao_coreaudio_exclusive.c
@@ -1,5 +1,5 @@
/*
- * CoreAudio audio output driver for Mac OS X
+ * CoreAudio audio output driver for macOS
*
* original copyright (C) Timothy J. Wood - Aug 2000
* ported to MPlayer libao2 by Dan Christiansen
@@ -28,7 +28,7 @@
*/
/*
- * The MacOS X CoreAudio framework doesn't mesh as simply as some
+ * The macOS CoreAudio framework doesn't mesh as simply as some
* simpler frameworks do. This is due to the fact that CoreAudio pulls
* audio samples rather than having them pushed at it (which is nice
* when you are wanting to do good buffering of audio).
@@ -114,7 +114,7 @@ static OSStatus enable_property_listener(struct ao *ao, bool enabled)
kAudioHardwarePropertyDevices};
AudioDeviceID devs[] = {p->device,
kAudioObjectSystemObject};
- assert(MP_ARRAY_SIZE(selectors) == MP_ARRAY_SIZE(devs));
+ static_assert(MP_ARRAY_SIZE(selectors) == MP_ARRAY_SIZE(devs), "");
OSStatus status = noErr;
for (int n = 0; n < MP_ARRAY_SIZE(devs); n++) {
diff --git a/audio/out/ao_coreaudio_properties.h b/audio/out/ao_coreaudio_properties.h
index f293968..2c9c565 100644
--- a/audio/out/ao_coreaudio_properties.h
+++ b/audio/out/ao_coreaudio_properties.h
@@ -23,6 +23,11 @@
#include "internal.h"
+#if HAVE_AVFOUNDATION || HAVE_AUDIOUNIT
+#undef HAVE_COREAUDIO
+#define HAVE_COREAUDIO 1
+#endif
+
// CoreAudio names are way too verbose
#define ca_sel AudioObjectPropertySelector
#define ca_scope AudioObjectPropertyScope
diff --git a/audio/out/ao_coreaudio_utils.c b/audio/out/ao_coreaudio_utils.c
index 14db8e3..e74092a 100644
--- a/audio/out/ao_coreaudio_utils.c
+++ b/audio/out/ao_coreaudio_utils.c
@@ -138,7 +138,8 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
{
if (code == noErr) return true;
- mp_msg(ao->log, level, "%s (%s/%d)\n", message, mp_tag_str(code), (int)code);
+ if (ao)
+ mp_msg(ao->log, level, "%s (%s/%d)\n", message, mp_tag_str(code), (int)code);
return false;
}
@@ -470,11 +471,9 @@ bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
ca_print_asbd(ao, "setting stream physical format:", &change_format);
- sem_t wakeup;
- if (mp_sem_init(&wakeup, 0, 0)) {
- MP_WARN(ao, "OOM\n");
- return false;
- }
+ mp_sem_t wakeup;
+ if (mp_sem_init(&wakeup, 0, 0))
+ MP_HANDLE_OOM(0);
AudioStreamBasicDescription prev_format;
err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &prev_format);
diff --git a/audio/out/ao_coreaudio_utils.h b/audio/out/ao_coreaudio_utils.h
index 0e2b8b1..699ffde 100644
--- a/audio/out/ao_coreaudio_utils.h
+++ b/audio/out/ao_coreaudio_utils.h
@@ -27,7 +27,12 @@
#include "common/msg.h"
#include "audio/out/ao.h"
#include "internal.h"
-#include "osdep/apple_utils.h"
+#include "osdep/utils-mac.h"
+
+#if HAVE_AVFOUNDATION || HAVE_AUDIOUNIT
+#undef HAVE_COREAUDIO
+#define HAVE_COREAUDIO 1
+#endif
bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message);
diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c
index 163fdca..4bae438 100644
--- a/audio/out/ao_lavc.c
+++ b/audio/out/ao_lavc.c
@@ -26,6 +26,7 @@
#include <limits.h>
#include <libavutil/common.h>
+#include <libavutil/samplefmt.h>
#include "config.h"
#include "options/options.h"
diff --git a/audio/out/ao_null.c b/audio/out/ao_null.c
index fcb61d2..0cda8d9 100644
--- a/audio/out/ao_null.c
+++ b/audio/out/ao_null.c
@@ -116,10 +116,11 @@ static void uninit(struct ao *ao)
{
}
-// stop playing and empty buffers (for seeking/pause)
+// stop playing and empty buffers (for seeking)
static void reset(struct ao *ao)
{
struct priv *priv = ao->priv;
+ priv->paused = false;
priv->buffered = 0;
priv->playing = false;
}
diff --git a/audio/out/ao_oss.c b/audio/out/ao_oss.c
index 5c0b8c9..afe5839 100644
--- a/audio/out/ao_oss.c
+++ b/audio/out/ao_oss.c
@@ -36,6 +36,7 @@
#include <sys/types.h>
#include "audio/format.h"
+#include "common/common.h"
#include "common/msg.h"
#include "options/options.h"
#include "osdep/endian.h"
@@ -89,7 +90,7 @@ static const int format_table[][2] = {
#define MP_WARN_IOCTL_ERR(__ao) \
MP_WARN((__ao), "%s: ioctl() fail, err = %i: %s\n", \
- __FUNCTION__, errno, strerror(errno))
+ __FUNCTION__, errno, mp_strerror(errno))
static void uninit(struct ao *ao);
@@ -329,7 +330,7 @@ static bool audio_write(struct ao *ao, void **data, int samples)
if (errno == EINTR)
continue;
MP_WARN(ao, "audio_write: write() fail, err = %i: %s.\n",
- errno, strerror(errno));
+ errno, mp_strerror(errno));
return false;
}
if ((size_t)rc != size) {
diff --git a/audio/out/ao_pipewire.c b/audio/out/ao_pipewire.c
index 3fbcbf6..94d393a 100644
--- a/audio/out/ao_pipewire.c
+++ b/audio/out/ao_pipewire.c
@@ -27,6 +27,7 @@
#include <spa/utils/result.h>
#include <math.h>
+#include "common/common.h"
#include "common/msg.h"
#include "options/m_config.h"
#include "options/m_option.h"
@@ -46,6 +47,15 @@ static inline int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time
#define spa_hook_remove(hook) if ((hook)->link.prev) spa_hook_remove(hook)
#endif
+#if !PW_CHECK_VERSION(1, 0, 4)
+static uint64_t pw_stream_get_nsec(struct pw_stream *stream)
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return SPA_TIMESPEC_TO_NSEC(&ts);
+}
+#endif
+
enum init_state {
INIT_STATE_NONE,
INIT_STATE_SUCCESS,
@@ -86,7 +96,7 @@ struct id_list {
struct spa_list node;
};
-static enum spa_audio_format af_fmt_to_pw(struct ao *ao, enum af_format format)
+static enum spa_audio_format af_fmt_to_pw(enum af_format format)
{
switch (format) {
case AF_FORMAT_U8: return SPA_AUDIO_FORMAT_U8;
@@ -99,9 +109,21 @@ static enum spa_audio_format af_fmt_to_pw(struct ao *ao, enum af_format format)
case AF_FORMAT_S32P: return SPA_AUDIO_FORMAT_S32P;
case AF_FORMAT_FLOATP: return SPA_AUDIO_FORMAT_F32P;
case AF_FORMAT_DOUBLEP: return SPA_AUDIO_FORMAT_F64P;
- default:
- MP_WARN(ao, "Unhandled format %d\n", format);
- return SPA_AUDIO_FORMAT_UNKNOWN;
+ default: return SPA_AUDIO_FORMAT_UNKNOWN;
+ }
+}
+
+static enum spa_audio_iec958_codec af_fmt_to_codec(enum af_format format)
+{
+ switch (format) {
+ case AF_FORMAT_S_AAC: return SPA_AUDIO_IEC958_CODEC_MPEG2_AAC;
+ case AF_FORMAT_S_AC3: return SPA_AUDIO_IEC958_CODEC_AC3;
+ case AF_FORMAT_S_DTS: return SPA_AUDIO_IEC958_CODEC_DTS;
+ case AF_FORMAT_S_DTSHD: return SPA_AUDIO_IEC958_CODEC_DTSHD;
+ case AF_FORMAT_S_EAC3: return SPA_AUDIO_IEC958_CODEC_EAC3;
+ case AF_FORMAT_S_MP3: return SPA_AUDIO_IEC958_CODEC_MPEG;
+ case AF_FORMAT_S_TRUEHD: return SPA_AUDIO_IEC958_CODEC_TRUEHD;
+ default: return SPA_AUDIO_IEC958_CODEC_UNKNOWN;
}
}
@@ -154,14 +176,13 @@ static void on_process(void *userdata)
void *data[MP_NUM_CHANNELS];
if ((b = pw_stream_dequeue_buffer(p->stream)) == NULL) {
- MP_WARN(ao, "out of buffers: %s\n", strerror(errno));
+ MP_WARN(ao, "out of buffers: %s\n", mp_strerror(errno));
return;
}
struct spa_buffer *buf = b->buffer;
- int bytes_per_channel = buf->datas[0].maxsize / ao->channels.num;
- int nframes = bytes_per_channel / ao->sstride;
+ int nframes = buf->datas[0].maxsize / ao->sstride;
#if PW_CHECK_VERSION(0, 3, 49)
if (b->requested != 0)
nframes = MPMIN(b->requested, nframes);
@@ -177,9 +198,13 @@ static void on_process(void *userdata)
time.rate.num = 1;
int64_t end_time = mp_time_ns();
- /* time.queued is always going to be 0, so we don't need to care */
- end_time += (nframes * 1e9 / ao->samplerate) +
- ((double) time.delay * SPA_NSEC_PER_SEC * time.rate.num / time.rate.denom);
+ end_time += MP_TIME_S_TO_NS(nframes) / ao->samplerate;
+ end_time += MP_TIME_S_TO_NS(time.delay) * time.rate.num / time.rate.denom;
+ end_time += MP_TIME_S_TO_NS(time.queued) / ao->samplerate;
+#if PW_CHECK_VERSION(0, 3, 50)
+ end_time += MP_TIME_S_TO_NS(time.buffered) / ao->samplerate;
+#endif
+ end_time -= pw_stream_get_nsec(p->stream) - time.now;
int samples = ao_read_data_nonblocking(ao, data, nframes, end_time);
b->size = samples;
@@ -214,7 +239,7 @@ static void on_param_changed(void *userdata, uint32_t id, const struct spa_pod *
if (param == NULL || id != SPA_PARAM_Format)
return;
- int buffer_size = ao->device_buffer * af_fmt_to_bytes(ao->format) * ao->channels.num;
+ int buffer_size = ao->device_buffer * ao->sstride;
params[0] = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
@@ -506,7 +531,7 @@ static int pipewire_init_boilerplate(struct ao *ao)
if (!p->core) {
MP_MSG(ao, ao->probing ? MSGL_V : MSGL_ERR,
"Could not connect to context '%s': %s\n",
- p->options.remote, strerror(errno));
+ p->options.remote, mp_strerror(errno));
pw_context_destroy(context);
goto error;
}
@@ -580,24 +605,41 @@ static int init(struct ao *ao)
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", ao->samplerate);
- enum spa_audio_format spa_format = af_fmt_to_pw(ao, ao->format);
- if (spa_format == SPA_AUDIO_FORMAT_UNKNOWN) {
- ao->format = AF_FORMAT_FLOATP;
- spa_format = SPA_AUDIO_FORMAT_F32P;
- }
+ if (af_fmt_is_spdif(ao->format)) {
+ enum spa_audio_iec958_codec spa_codec = af_fmt_to_codec(ao->format);
+ if (spa_codec == SPA_AUDIO_IEC958_CODEC_UNKNOWN) {
+ MP_ERR(ao, "Unhandled codec %d\n", ao->format);
+ goto error_props;
+ }
- struct spa_audio_info_raw audio_info = {
- .format = spa_format,
- .rate = ao->samplerate,
- .channels = ao->channels.num,
- };
+ struct spa_audio_info_iec958 audio_info = {
+ .codec = spa_codec,
+ .rate = ao->samplerate,
+ };
- for (int i = 0; i < ao->channels.num; i++)
- audio_info.position[i] = mp_speaker_id_to_spa(ao, ao->channels.speaker[i]);
+ params[0] = spa_format_audio_iec958_build(&b, SPA_PARAM_EnumFormat, &audio_info);
+ if (!params[0])
+ goto error_props;
+ } else {
+ enum spa_audio_format spa_format = af_fmt_to_pw(ao->format);
+ if (spa_format == SPA_AUDIO_FORMAT_UNKNOWN) {
+ MP_ERR(ao, "Unhandled format %d\n", ao->format);
+ goto error_props;
+ }
- params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &audio_info);
- if (!params[0])
- goto error_props;
+ struct spa_audio_info_raw audio_info = {
+ .format = spa_format,
+ .rate = ao->samplerate,
+ .channels = ao->channels.num,
+ };
+
+ for (int i = 0; i < ao->channels.num; i++)
+ audio_info.position[i] = mp_speaker_id_to_spa(ao, ao->channels.speaker[i]);
+
+ params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &audio_info);
+ if (!params[0])
+ goto error_props;
+ }
if (af_fmt_is_planar(ao->format)) {
ao->num_planes = ao->channels.num;
@@ -664,6 +706,15 @@ static void start(struct ao *ao)
pw_thread_loop_unlock(p->loop);
}
+static bool set_pause(struct ao *ao, bool paused)
+{
+ struct priv *p = ao->priv;
+ pw_thread_loop_lock(p->loop);
+ pw_stream_set_active(p->stream, !paused);
+ pw_thread_loop_unlock(p->loop);
+ return true;
+}
+
#define CONTROL_RET(r) (!r ? CONTROL_OK : CONTROL_ERROR)
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
@@ -855,7 +906,7 @@ const struct ao_driver audio_out_pipewire = {
.uninit = uninit,
.reset = reset,
.start = start,
-
+ .set_pause = set_pause,
.control = control,
.hotplug_init = hotplug_init,
diff --git a/audio/out/ao_pulse.c b/audio/out/ao_pulse.c
index 3b29b1a..5c86855 100644
--- a/audio/out/ao_pulse.c
+++ b/audio/out/ao_pulse.c
@@ -118,7 +118,7 @@ static void stream_request_cb(pa_stream *s, size_t length, void *userdata)
{
struct ao *ao = userdata;
struct priv *priv = ao->priv;
- ao_wakeup_playthread(ao);
+ ao_wakeup(ao);
pa_threaded_mainloop_signal(priv->mainloop, 0);
}
@@ -135,7 +135,7 @@ static void underflow_cb(pa_stream *s, void *userdata)
struct priv *priv = ao->priv;
priv->playing = false;
priv->underrun_signalled = true;
- ao_wakeup_playthread(ao);
+ ao_wakeup(ao);
pa_threaded_mainloop_signal(priv->mainloop, 0);
}
@@ -804,6 +804,7 @@ const struct ao_driver audio_out_pulse = {
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.cfg_buffer = 100,
+ .cfg_latency_hacks = true,
},
.options = (const struct m_option[]) {
{"host", OPT_STRING(cfg_host)},
diff --git a/audio/out/ao_sndio.c b/audio/out/ao_sndio.c
index fce7139..309ea4a 100644
--- a/audio/out/ao_sndio.c
+++ b/audio/out/ao_sndio.c
@@ -22,6 +22,8 @@
#include <errno.h>
#include <sndio.h>
+#include "config.h"
+
#include "options/m_option.h"
#include "common/msg.h"
@@ -292,7 +294,7 @@ static void get_state(struct ao *ao, struct mp_pcm_state *state)
state->delay = p->delay / (double)p->par.rate;
/* report unexpected EOF / underrun */
- if ((state->queued_samples && state->queued_samples &&
+ if ((state->queued_samples &&
(state->queued_samples < state->free_samples) &&
p->playing) || sio_eof(p->hdl))
{
@@ -301,7 +303,7 @@ static void get_state(struct ao *ao, struct mp_pcm_state *state)
state->free_samples, state->queued_samples, state->delay);
p->playing = false;
state->playing = p->playing;
- ao_wakeup_playthread(ao);
+ ao_wakeup(ao);
} else {
state->playing = p->playing;
}
diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c
index b201f26..d986d80 100644
--- a/audio/out/ao_wasapi.c
+++ b/audio/out/ao_wasapi.c
@@ -150,14 +150,32 @@ exit_label:
return false;
}
+static void thread_pause(struct ao *ao)
+{
+ struct wasapi_state *state = ao->priv;
+ MP_DBG(state, "Thread Pause\n");
+ HRESULT hr = IAudioClient_Stop(state->pAudioClient);
+ if (FAILED(hr))
+ MP_ERR(state, "IAudioClient_Stop returned: %s\n", mp_HRESULT_to_str(hr));
+}
+
+static void thread_unpause(struct ao *ao)
+{
+ struct wasapi_state *state = ao->priv;
+ MP_DBG(state, "Thread Unpause\n");
+ HRESULT hr = IAudioClient_Start(state->pAudioClient);
+ if (FAILED(hr)) {
+ MP_ERR(state, "IAudioClient_Start returned %s\n",
+ mp_HRESULT_to_str(hr));
+ }
+}
+
static void thread_reset(struct ao *ao)
{
struct wasapi_state *state = ao->priv;
HRESULT hr;
MP_DBG(state, "Thread Reset\n");
- hr = IAudioClient_Stop(state->pAudioClient);
- if (FAILED(hr))
- MP_ERR(state, "IAudioClient_Stop returned: %s\n", mp_HRESULT_to_str(hr));
+ thread_pause(ao);
hr = IAudioClient_Reset(state->pAudioClient);
if (FAILED(hr))
@@ -172,27 +190,20 @@ static void thread_resume(struct ao *ao)
MP_DBG(state, "Thread Resume\n");
thread_reset(ao);
thread_feed(ao);
-
- HRESULT hr = IAudioClient_Start(state->pAudioClient);
- if (FAILED(hr)) {
- MP_ERR(state, "IAudioClient_Start returned %s\n",
- mp_HRESULT_to_str(hr));
- }
+ thread_unpause(ao);
}
-static void thread_wakeup(void *ptr)
+static void set_state_and_wakeup_thread(struct ao *ao,
+ enum wasapi_thread_state thread_state)
{
- struct ao *ao = ptr;
struct wasapi_state *state = ao->priv;
+ atomic_store(&state->thread_state, thread_state);
SetEvent(state->hWake);
}
-static void set_thread_state(struct ao *ao,
- enum wasapi_thread_state thread_state)
+static void thread_process_dispatch(void *ptr)
{
- struct wasapi_state *state = ao->priv;
- atomic_store(&state->thread_state, thread_state);
- thread_wakeup(ao);
+ set_state_and_wakeup_thread(ptr, WASAPI_THREAD_DISPATCH);
}
static DWORD __stdcall AudioThread(void *lpParameter)
@@ -212,8 +223,6 @@ static DWORD __stdcall AudioThread(void *lpParameter)
if (WaitForSingleObject(state->hWake, INFINITE) != WAIT_OBJECT_0)
MP_ERR(ao, "Unexpected return value from WaitForSingleObject\n");
- mp_dispatch_queue_process(state->dispatch, 0);
-
int thread_state = atomic_load(&state->thread_state);
switch (thread_state) {
case WASAPI_THREAD_FEED:
@@ -221,6 +230,9 @@ static DWORD __stdcall AudioThread(void *lpParameter)
if (thread_feed(ao) && thread_feed(ao))
MP_ERR(ao, "Unable to fill buffer fast enough\n");
break;
+ case WASAPI_THREAD_DISPATCH:
+ mp_dispatch_queue_process(state->dispatch, 0);
+ break;
case WASAPI_THREAD_RESET:
thread_reset(ao);
break;
@@ -230,6 +242,12 @@ static DWORD __stdcall AudioThread(void *lpParameter)
case WASAPI_THREAD_SHUTDOWN:
thread_reset(ao);
goto exit_label;
+ case WASAPI_THREAD_PAUSE:
+ thread_pause(ao);
+ break;
+ case WASAPI_THREAD_UNPAUSE:
+ thread_unpause(ao);
+ break;
default:
MP_ERR(ao, "Unhandled thread state: %d\n", thread_state);
}
@@ -250,7 +268,7 @@ static void uninit(struct ao *ao)
MP_DBG(ao, "Uninit wasapi\n");
struct wasapi_state *state = ao->priv;
if (state->hWake)
- set_thread_state(ao, WASAPI_THREAD_SHUTDOWN);
+ set_state_and_wakeup_thread(ao, WASAPI_THREAD_SHUTDOWN);
if (state->hAudioThread &&
WaitForSingleObject(state->hAudioThread, INFINITE) != WAIT_OBJECT_0)
@@ -301,7 +319,7 @@ static int init(struct ao *ao)
}
state->dispatch = mp_dispatch_create(state);
- mp_dispatch_set_wakeup_fn(state->dispatch, thread_wakeup, ao);
+ mp_dispatch_set_wakeup_fn(state->dispatch, thread_process_dispatch, ao);
state->init_ok = false;
state->hAudioThread = CreateThread(NULL, 0, &AudioThread, ao, 0, NULL);
@@ -349,7 +367,7 @@ static int thread_control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg
case AOCONTROL_GET_VOLUME:
IAudioEndpointVolume_GetMasterVolumeLevelScalar(
state->pEndpointVolume, &volume);
- *(float *)arg = volume;
+ *(float *)arg = volume * 100.f;
return CONTROL_OK;
case AOCONTROL_SET_VOLUME:
volume = (*(float *)arg) / 100.f;
@@ -379,7 +397,7 @@ static int thread_control_shared(struct ao *ao, enum aocontrol cmd, void *arg)
switch(cmd) {
case AOCONTROL_GET_VOLUME:
ISimpleAudioVolume_GetMasterVolume(state->pAudioVolume, &volume);
- *(float *)arg = volume;
+ *(float *)arg = volume * 100.f;
return CONTROL_OK;
case AOCONTROL_SET_VOLUME:
volume = (*(float *)arg) / 100.f;
@@ -456,12 +474,18 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
static void audio_reset(struct ao *ao)
{
- set_thread_state(ao, WASAPI_THREAD_RESET);
+ set_state_and_wakeup_thread(ao, WASAPI_THREAD_RESET);
}
static void audio_resume(struct ao *ao)
{
- set_thread_state(ao, WASAPI_THREAD_RESUME);
+ set_state_and_wakeup_thread(ao, WASAPI_THREAD_RESUME);
+}
+
+static bool audio_set_pause(struct ao *ao, bool paused)
+{
+ set_state_and_wakeup_thread(ao, paused ? WASAPI_THREAD_PAUSE : WASAPI_THREAD_UNPAUSE);
+ return true;
}
static void hotplug_uninit(struct ao *ao)
@@ -497,6 +521,7 @@ const struct ao_driver audio_out_wasapi = {
.control = control,
.reset = audio_reset,
.start = audio_resume,
+ .set_pause = audio_set_pause,
.list_devs = wasapi_list_devs,
.hotplug_init = hotplug_init,
.hotplug_uninit = hotplug_uninit,
diff --git a/audio/out/ao_wasapi.h b/audio/out/ao_wasapi.h
index 17b8f7a..4e5e9c8 100644
--- a/audio/out/ao_wasapi.h
+++ b/audio/out/ao_wasapi.h
@@ -48,9 +48,12 @@ void wasapi_change_uninit(struct ao* ao);
enum wasapi_thread_state {
WASAPI_THREAD_FEED = 0,
+ WASAPI_THREAD_DISPATCH,
WASAPI_THREAD_RESUME,
WASAPI_THREAD_RESET,
- WASAPI_THREAD_SHUTDOWN
+ WASAPI_THREAD_SHUTDOWN,
+ WASAPI_THREAD_PAUSE,
+ WASAPI_THREAD_UNPAUSE,
};
typedef struct wasapi_state {
diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c
index 731fe8a..7e85f75 100644
--- a/audio/out/ao_wasapi_utils.c
+++ b/audio/out/ao_wasapi_utils.c
@@ -18,12 +18,15 @@
*/
#include <math.h>
-#include <wchar.h>
+
#include <windows.h>
-#include <errors.h>
+#include <mmsystem.h>
+#include <mmreg.h>
#include <ksguid.h>
#include <ksmedia.h>
#include <avrt.h>
+#include <propsys.h>
+#include <functiondiscoverykeys_devpkey.h>
#include "audio/format.h"
#include "osdep/timer.h"
@@ -31,36 +34,47 @@
#include "osdep/strnlen.h"
#include "ao_wasapi.h"
-DEFINE_PROPERTYKEY(mp_PKEY_Device_FriendlyName,
- 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20,
- 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
-DEFINE_PROPERTYKEY(mp_PKEY_Device_DeviceDesc,
- 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20,
- 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2);
-// CEA 861 subformats
-// should work on vista
-DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS,
- 0x00000008, 0x0000, 0x0010, 0x80, 0x00,
+#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DTS
+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DTS,
+ WAVE_FORMAT_DTS, 0x0000, 0x0010, 0x80, 0x00,
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
-DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL,
- 0x00000092, 0x0000, 0x0010, 0x80, 0x00,
+#endif
+
+#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL
+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL,
+ WAVE_FORMAT_DOLBY_AC3_SPDIF, 0x0000, 0x0010, 0x80, 0x00,
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
-// might require 7+
-DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_AAC,
+#endif
+
+#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_AAC
+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_AAC,
0x00000006, 0x0cea, 0x0010, 0x80, 0x00,
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
-DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3,
- 0x00000004, 0x0cea, 0x0010, 0x80, 0x00,
+#endif
+
+#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3
+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3,
+ 0x00000005, 0x0cea, 0x0010, 0x80, 0x00,
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
-DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS,
+#endif
+
+#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS
+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS,
0x0000000a, 0x0cea, 0x0010, 0x80, 0x00,
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
-DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD,
+#endif
+
+#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD
+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD,
0x0000000b, 0x0cea, 0x0010, 0x80, 0x00,
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
-DEFINE_GUID(mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP,
+#endif
+
+#ifndef KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP
+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP,
0x0000000c, 0x0cea, 0x0010, 0x80, 0x00,
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
+#endif
struct wasapi_sample_fmt {
int mp_format; // AF_FORMAT_*
@@ -82,13 +96,13 @@ static const struct wasapi_sample_fmt wasapi_formats[] = {
// aka S24 (with conversion on output)
{AF_FORMAT_S32, 24, 24, &KSDATAFORMAT_SUBTYPE_PCM},
{AF_FORMAT_FLOAT, 32, 32, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT},
- {AF_FORMAT_S_AC3, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL},
- {AF_FORMAT_S_DTS, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS},
- {AF_FORMAT_S_AAC, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_AAC},
- {AF_FORMAT_S_MP3, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3},
- {AF_FORMAT_S_TRUEHD, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP},
- {AF_FORMAT_S_EAC3, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS},
- {AF_FORMAT_S_DTSHD, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD},
+ {AF_FORMAT_S_AC3, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL},
+ {AF_FORMAT_S_DTS, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DTS},
+ {AF_FORMAT_S_AAC, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_AAC},
+ {AF_FORMAT_S_MP3, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_MPEG3},
+ {AF_FORMAT_S_TRUEHD, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP},
+ {AF_FORMAT_S_EAC3, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS},
+ {AF_FORMAT_S_DTSHD, 16, 16, &KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD},
{0},
};
@@ -562,9 +576,10 @@ static void init_session_display(struct wasapi_state *state, const char *name) {
(void **)&state->pSessionControl);
EXIT_ON_ERROR(hr);
- wchar_t path[MAX_PATH] = {0};
- GetModuleFileNameW(NULL, path, MAX_PATH);
+ wchar_t *path = talloc_array(NULL, wchar_t, MP_PATH_MAX);
+ GetModuleFileNameW(NULL, path, MP_PATH_MAX);
hr = IAudioSessionControl_SetIconPath(state->pSessionControl, path, NULL);
+ talloc_free(path);
if (FAILED(hr)) {
// don't goto exit_label here since SetDisplayName might still work
MP_WARN(state, "Error setting audio session icon: %s\n",
@@ -718,7 +733,7 @@ static char* get_device_name(struct mp_log *l, void *talloc_ctx, IMMDevice *pDev
HRESULT hr = IMMDevice_OpenPropertyStore(pDevice, STGM_READ, &pProps);
EXIT_ON_ERROR(hr);
- hr = IPropertyStore_GetValue(pProps, &mp_PKEY_Device_FriendlyName,
+ hr = IPropertyStore_GetValue(pProps, &PKEY_Device_FriendlyName,
&devname);
EXIT_ON_ERROR(hr);
diff --git a/audio/out/buffer.c b/audio/out/buffer.c
index 5b8b523..97f7ea1 100644
--- a/audio/out/buffer.c
+++ b/audio/out/buffer.c
@@ -41,7 +41,7 @@ struct buffer_state {
mp_mutex lock;
mp_cond wakeup;
- // Playthread sleep
+ // AO thread sleep
mp_mutex pt_lock;
mp_cond pt_wakeup;
@@ -62,6 +62,11 @@ struct buffer_state {
bool paused; // logically paused
int64_t end_time_ns; // absolute output time of last played sample
+ int64_t queued_time_ns; // duration of samples that have been queued to
+ // the device but have not been played.
+ // This field is only set in ao_set_paused(),
+ // and is considered as a temporary solution;
+ // DO NOT USE IT IN OTHER PLACES.
bool initial_unblocked;
@@ -78,9 +83,9 @@ struct buffer_state {
bool terminate; // exit thread
};
-static MP_THREAD_VOID playthread(void *arg);
+static MP_THREAD_VOID ao_thread(void *arg);
-void ao_wakeup_playthread(struct ao *ao)
+void ao_wakeup(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
mp_mutex_lock(&p->pt_lock);
@@ -173,8 +178,8 @@ static int read_buffer(struct ao *ao, void **data, int samples, bool *eof,
return pos;
}
-static int ao_read_data_unlocked(struct ao *ao, void **data, int samples,
- int64_t out_time_ns, bool pad_silence)
+static int ao_read_data_locked(struct ao *ao, void **data, int samples,
+ int64_t out_time_ns, bool pad_silence)
{
struct buffer_state *p = ao->buffer_state;
assert(!ao->driver->write);
@@ -208,7 +213,7 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_ns)
mp_mutex_lock(&p->lock);
- int pos = ao_read_data_unlocked(ao, data, samples, out_time_ns, true);
+ int pos = ao_read_data_locked(ao, data, samples, out_time_ns, true);
mp_mutex_unlock(&p->lock);
@@ -224,7 +229,7 @@ int ao_read_data_nonblocking(struct ao *ao, void **data, int samples, int64_t ou
if (mp_mutex_trylock(&p->lock))
return 0;
- int pos = ao_read_data_unlocked(ao, data, samples, out_time_ns, false);
+ int pos = ao_read_data_locked(ao, data, samples, out_time_ns, false);
mp_mutex_unlock(&p->lock);
@@ -347,7 +352,7 @@ void ao_reset(struct ao *ao)
ao->driver->reset(ao);
if (wakeup)
- ao_wakeup_playthread(ao);
+ ao_wakeup(ao);
}
// Initiate playback. This moves from the stop/underrun state to actually
@@ -374,14 +379,14 @@ void ao_start(struct ao *ao)
if (do_start)
ao->driver->start(ao);
- ao_wakeup_playthread(ao);
+ ao_wakeup(ao);
}
void ao_set_paused(struct ao *ao, bool paused, bool eof)
{
struct buffer_state *p = ao->buffer_state;
bool wakeup = false;
- bool do_reset = false, do_start = false;
+ bool do_change_state = false;
// If we are going to pause on eof and ao is still playing,
// be sure to drain the ao first for gapless.
@@ -402,9 +407,9 @@ void ao_set_paused(struct ao *ao, bool paused, bool eof)
p->streaming = false;
p->recover_pause = !ao->untimed;
}
- } else if (ao->driver->reset) {
+ } else if (ao->driver->reset || ao->driver->set_pause) {
// See ao_reset() why this is done outside of the lock.
- do_reset = true;
+ do_change_state = true;
p->streaming = false;
}
}
@@ -416,7 +421,7 @@ void ao_set_paused(struct ao *ao, bool paused, bool eof)
p->hw_paused = false;
} else {
if (!p->streaming)
- do_start = true;
+ do_change_state = true;
p->streaming = true;
}
wakeup = true;
@@ -425,13 +430,25 @@ void ao_set_paused(struct ao *ao, bool paused, bool eof)
mp_mutex_unlock(&p->lock);
- if (do_reset)
- ao->driver->reset(ao);
- if (do_start)
- ao->driver->start(ao);
+ if (do_change_state) {
+ if (ao->driver->set_pause) {
+ if (paused) {
+ ao->driver->set_pause(ao, true);
+ p->queued_time_ns = p->end_time_ns - mp_time_ns();
+ } else {
+ p->end_time_ns = p->queued_time_ns + mp_time_ns();
+ ao->driver->set_pause(ao, false);
+ }
+ } else {
+ if (paused)
+ ao->driver->reset(ao);
+ else
+ ao->driver->start(ao);
+ }
+ }
if (wakeup)
- ao_wakeup_playthread(ao);
+ ao_wakeup(ao);
}
// Whether audio is playing. This means that there is still data in the buffers,
@@ -486,7 +503,7 @@ void ao_drain(struct ao *ao)
static void wakeup_filters(void *ctx)
{
struct ao *ao = ctx;
- ao_wakeup_playthread(ao);
+ ao_wakeup(ao);
}
void ao_uninit(struct ao *ao)
@@ -561,7 +578,7 @@ bool init_buffer_post(struct ao *ao)
mp_filter_graph_set_wakeup_cb(p->filter_root, wakeup_filters, ao);
p->thread_valid = true;
- if (mp_thread_create(&p->thread, playthread, ao)) {
+ if (mp_thread_create(&p->thread, ao_thread, ao)) {
p->thread_valid = false;
return false;
}
@@ -684,7 +701,7 @@ eof:
return true;
}
-static MP_THREAD_VOID playthread(void *arg)
+static MP_THREAD_VOID ao_thread(void *arg)
{
struct ao *ao = arg;
struct buffer_state *p = ao->buffer_state;
@@ -731,6 +748,6 @@ void ao_unblock(struct ao *ao)
mp_mutex_lock(&p->lock);
p->initial_unblocked = true;
mp_mutex_unlock(&p->lock);
- ao_wakeup_playthread(ao);
+ ao_wakeup(ao);
}
}
diff --git a/audio/out/internal.h b/audio/out/internal.h
index 7951b38..51429b9 100644
--- a/audio/out/internal.h
+++ b/audio/out/internal.h
@@ -108,6 +108,7 @@ struct mp_pcm_state {
* start
* Optional for both types:
* control
+ * set_pause
* a) ->write is called to queue audio. push.c creates a thread to regularly
* refill audio device buffers with ->write, but all driver functions are
* always called under an exclusive lock.
@@ -115,8 +116,6 @@ struct mp_pcm_state {
* reset
* write
* get_state
- * Optional:
- * set_pause
* b) ->write must be NULL. ->start must be provided, and should make the
* audio API start calling the audio callback. Your audio callback should
* in turn call ao_read_data() to get audio data. Most functions are
@@ -149,6 +148,9 @@ struct ao_driver {
// Stop all audio playback, clear buffers, back to state after init().
// Optional for pull AOs.
void (*reset)(struct ao *ao);
+ // pull based: set pause state. Only called after start() and before reset().
+ // The return value is ignored.
+ // The pausing state is also cleared by reset().
// push based: set pause state. Only called after start() and before reset().
// returns success (this is intended for paused=true; if it
// returns false, playback continues, and the core emulates via
@@ -157,7 +159,7 @@ struct ao_driver {
bool (*set_pause)(struct ao *ao, bool paused);
// pull based: start the audio callback
// push based: start playing queued data
- // AO should call ao_wakeup_playthread() if a period boundary
+ // AO should call ao_wakeup() if a period boundary
// is crossed, or playback stops due to external reasons
// (including underruns or device removal)
// must set mp_pcm_state.playing; unset on error/underrun/end
@@ -229,7 +231,7 @@ bool ao_can_convert_inplace(struct ao_convert_fmt *fmt);
bool ao_need_conversion(struct ao_convert_fmt *fmt);
void ao_convert_inplace(struct ao_convert_fmt *fmt, void **data, int num_samples);
-void ao_wakeup_playthread(struct ao *ao);
+void ao_wakeup(struct ao *ao);
int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
void **data, int samples, int64_t out_time_ns);