diff --git a/include/mpv/client.h b/include/mpv/client.h index 85cff63..669a2e1 100644 --- a/include/mpv/client.h +++ b/include/mpv/client.h @@ -1902,6 +1902,35 @@ MPV_EXPORT int mpv_get_wakeup_pipe(mpv_handle *ctx); #endif +/** + * Conflux extension (not part of upstream mpv): decoded audio tap. + * + * The callback receives every block of decoded and filtered audio right before + * it is queued to the audio output. That is after --af, but before volume and + * mute are applied, so muting playback does not silence the tap. + * + * Samples are always float32, interleaved, in the channel count and sample rate + * of the stream as it leaves the filter chain. pts is the playback timestamp of + * the first sample in seconds, or a negative value if unknown. Passthrough + * (spdif) audio is not delivered. + * + * The callback runs on an internal playback thread. It must return quickly, + * must not block on other threads, and must not call any mpv API function. The + * samples pointer is only valid for the duration of the call. + * + * Only one tap exists per mpv core; setting a new one replaces the previous + * one. Pass cb=NULL to remove it. Once this function returns, the previous + * callback is guaranteed not to be running and will not be called again, so + * its userdata may be released. Remove the tap before destroying the handle. + * + * @return error code (0 on success) + */ +typedef void (*mpv_audio_tap_fn)(void *userdata, const float *samples, + int frames, int channels, int samplerate, + double pts); +MPV_EXPORT int mpv_set_audio_tap(mpv_handle *ctx, mpv_audio_tap_fn cb, + void *userdata); + /** * Defining MPV_CPLUGIN_DYNAMIC_SYM during plugin compilation will replace mpv_* * functions with function pointers. Those pointer will be initialized when diff --git a/player/audio.c b/player/audio.c index b3e975d..ef70928 100644 --- a/player/audio.c +++ b/player/audio.c @@ -645,6 +645,68 @@ static void update_throttle(struct MPContext *mpctx) } } +// Conflux audio tap: hand the frame to the mpv_set_audio_tap() callback as +// interleaved float32. The frame itself is not modified. +void audio_tap_feed(struct MPContext *mpctx, struct mp_aframe *af) +{ + mp_mutex_lock(&mpctx->audio_tap_lock); + if (!mpctx->audio_tap_cb) + goto done; + + int format = mp_aframe_get_format(af); + int channels = mp_aframe_get_channels(af); + int rate = mp_aframe_get_rate(af); + int frames = mp_aframe_get_size(af); + if (af_fmt_is_spdif(format) || channels < 1 || rate < 1 || frames < 1) + goto done; + + bool planar = af_fmt_is_planar(format); + int base = af_fmt_from_planar(format); + uint8_t **data = mp_aframe_get_data_ro(af); + if (!data) + goto done; + + MP_TARRAY_GROW(mpctx, mpctx->audio_tap_buf, frames * channels); + float *out = mpctx->audio_tap_buf; + for (int i = 0; i < frames; i++) { + for (int c = 0; c < channels; c++) { + const uint8_t *plane = data[planar ? c : 0]; + size_t idx = planar ? i : (size_t)i * channels + c; + float v; + switch (base) { + case AF_FORMAT_U8: + v = (((const uint8_t *)plane)[idx] - 128) / 128.0f; + break; + case AF_FORMAT_S16: + v = ((const int16_t *)plane)[idx] / 32768.0f; + break; + case AF_FORMAT_S32: + v = ((const int32_t *)plane)[idx] / 2147483648.0f; + break; + case AF_FORMAT_S64: + v = ((const int64_t *)plane)[idx] / 9223372036854775808.0f; + break; + case AF_FORMAT_FLOAT: + v = ((const float *)plane)[idx]; + break; + case AF_FORMAT_DOUBLE: + v = ((const double *)plane)[idx]; + break; + default: + goto done; + } + out[(size_t)i * channels + c] = v; + } + } + + double pts = mp_aframe_get_pts(af); + mpctx->audio_tap_cb(mpctx->audio_tap_ctx, out, frames, channels, rate, + pts == MP_NOPTS_VALUE ? -1 : pts); + +done: + mp_mutex_unlock(&mpctx->audio_tap_lock); +} + static void ao_process(struct mp_filter *f) { struct ao_chain *ao_c = f->priv; @@ -748,6 +810,7 @@ static void ao_process(struct mp_filter *f) MP_VERBOSE(mpctx, "previous audio still playing; continuing\n"); } + audio_tap_feed(mpctx, af); mp_pin_in_write(ao_c->queue_filter->pins[0], frame); } else if (frame.type == MP_FRAME_EOF) { MP_VERBOSE(mpctx, "audio filter EOF\n"); diff --git a/player/client.c b/player/client.c index e23b98b..b8bc915 100644 --- a/player/client.c +++ b/player/client.c @@ -379,6 +379,16 @@ void mpv_set_wakeup_callback(mpv_handle *ctx, void (*cb)(void *d), void *d) mp_mutex_unlock(&ctx->wakeup_lock); } +int mpv_set_audio_tap(mpv_handle *ctx, mpv_audio_tap_fn cb, void *userdata) +{ + struct MPContext *mpctx = ctx->mpctx; + mp_mutex_lock(&mpctx->audio_tap_lock); + mpctx->audio_tap_cb = cb; + mpctx->audio_tap_ctx = cb ? userdata : NULL; + mp_mutex_unlock(&mpctx->audio_tap_lock); + return 0; +} + static void lock_core(mpv_handle *ctx) { mp_dispatch_lock(ctx->mpctx->dispatch); diff --git a/player/core.h b/player/core.h index eb7f508..c8f7af6 100644 --- a/player/core.h +++ b/player/core.h @@ -447,6 +447,14 @@ typedef struct MPContext { int64_t builtin_script_ids[9]; + // Conflux audio tap (mpv_set_audio_tap). All fields protected by + // audio_tap_lock; the lock is held while the callback runs. + mp_mutex audio_tap_lock; + void (*audio_tap_cb)(void *userdata, const float *samples, int frames, + int channels, int samplerate, double pts); + void *audio_tap_ctx; + float *audio_tap_buf; + mp_mutex abort_lock; // --- The following fields are protected by abort_lock @@ -510,6 +518,7 @@ void reinit_audio_chain_src(struct MPContext *mpctx, struct track *track); float audio_get_gain(struct MPContext *mpctx); void audio_update_volume(struct MPContext *mpctx); void reload_audio_output(struct MPContext *mpctx); +void audio_tap_feed(struct MPContext *mpctx, struct mp_aframe *af); void audio_start_ao(struct MPContext *mpctx); // configfiles.c diff --git a/player/main.c b/player/main.c index 37eb6b1..b3073bc 100644 --- a/player/main.c +++ b/player/main.c @@ -210,6 +210,7 @@ void mp_destroy(struct MPContext *mpctx) mp_assert(!mpctx->num_abort_list); talloc_free(mpctx->abort_list); mp_mutex_destroy(&mpctx->abort_lock); + mp_mutex_destroy(&mpctx->audio_tap_lock); talloc_free(mpctx->mconfig); // destroy before dispatch talloc_free(mpctx); } @@ -283,6 +284,7 @@ struct MPContext *mp_create(void) }; mp_mutex_init(&mpctx->abort_lock); + mp_mutex_init(&mpctx->audio_tap_lock); mpctx->global = talloc_zero(mpctx, struct mpv_global);