Skip to content

ao_avfoundation: stop requesting media while paused - #18390

Open
aliceisjustplaying wants to merge 1 commit into
mpv-player:masterfrom
aliceisjustplaying:agent/avfoundation-pause-cpu
Open

ao_avfoundation: stop requesting media while paused#18390
aliceisjustplaying wants to merge 1 commit into
mpv-player:masterfrom
aliceisjustplaying:agent/avfoundation-pause-cpu

Conversation

@aliceisjustplaying

@aliceisjustplaying aliceisjustplaying commented Aug 19, 2026

Copy link
Copy Markdown

Summary

Stop AVSampleBufferAudioRenderer from requesting media data while mpv is paused, then register the request callback again when playback resumes.

Root cause

set_pause set the render synchronizer rate to zero but left the media-data request active. If the renderer had no queued audio, ao_read_data returned no samples while paused. AVFoundation still considered the renderer ready for more data and immediately invoked the callback again, creating a busy loop on the avfoundation event queue.

The callback request now stops before the rate changes to zero. Resuming sets the rate back to one and registers a new callback, keeping the request and stop lifecycle balanced.

Observed failure

A paused mpv 0.41.0 process consistently consumed one full CPU core while its playback, decoder, demuxer, video-output, and Lua threads slept. A process sample showed the active thread in this stack:

DispatchQueue: avfoundation event
AVMediaDataRequester::_requestMediaDataIfReady
__start_block_invoke (mpv)

The process-level signal stayed consistent across repeated measurements:

cpu=98.8% state=R
cpu=100.0% state=R
cpu=99.1% state=R

Verification

  • meson compile -C build
  • git diff --check origin/master...HEAD
  • Played the same remote E-AC-3 stream with ao=avfoundation, completed five pause/resume cycles, and left the player paused
  • Repeated the pause/resume check with a generated 48 kHz audio source
  • Sampled the patched process while paused:
pause_property=true
paused_cpu=0.2
hot_callback=no

Additional macOS 26 runtime verification:

  • Built and ran the exact unpatched parent (e7191f2a65) and PR commit (4df03a9b22) on macOS 26.6.2 (25G83), arm64
  • Served a locally generated 48 kHz 5.1 E-AC-3 stream over HTTP, stopped the producer to reproduce a stalled remote stream, then paused mpv
  • Unpatched: paused_cpu=77.4; a three-second sample recorded 2,266 samples on DispatchQueue: avfoundation event, including 1,610 in -[AVMediaDataRequester _requestMediaDataIfReady]
  • Patched: paused_cpu=0.1; requestMediaDataIfReady was absent from the sample; resume and re-pause measured 0.0% CPU

This build configuration defines no automated tests. The behavior depends on the macOS AVFoundation runtime, so the verification exercises the real audio output.

AI/LLM disclosure

OpenAI Codex with GPT-5.6 Sol (high) assisted with diagnosis, implementation, testing, and drafting this pull request. I understand the change and take full responsibility for it, including its licensing under the file's existing LGPLv2.1+ terms. I will participate in review with human-written responses.

The AVSampleBufferAudioRenderer callback remained registered when the
synchronizer rate was set to zero. If its queue was empty when playback
was paused, AVFoundation immediately invoked the callback again after
ao_read_data returned no samples, consuming a full CPU core.

Stop requesting media data before pausing and register the callback again
when playback resumes. This balances the request/stop lifecycle and
prevents the empty renderer from busy-looping.
@aliceisjustplaying
aliceisjustplaying marked this pull request as ready for review August 19, 2026 13:54
@llyyr

llyyr commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

AVFoundation still considered the renderer ready for more data and immediately invoked the callback again

Why should it do that?
https://developer.apple.com/documentation/avfoundation/avsamplebufferrendersynchronizer/setrate(_:time:) says:

A rate value of 0.0 means that playback has stopped

Busylooping any time the player is paused would've resulted in far more noise about it, considering we haven't heard about this issue at all yet, is this a MacOS regression?

struct priv *p = ao->priv;

if (paused) {
[p->renderer stopRequestingMediaData];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we use this property elsewhere in the AO too, but this is deprecated https://developer.apple.com/documentation/avfoundation/avsamplebufferaudiorenderer/stoprequestingmediadata()

It can't be the case that avoiding busylooping necessitates the use of a deprecated property

@aliceisjustplaying

Copy link
Copy Markdown
Author

I also find it strange that there is no noise about it, but I tested this both on the macOS 27 beta but also on macOS 26 now and I can reproduce it, Updated the PR description. Looking into the deprecated property.

@aliceisjustplaying

Copy link
Copy Markdown
Author

@llyyr it's deprecated for swift but not objc, and as far as i can tell apple's website is just simply wrong about this

Xcode 27 SDK, AVQueuedSampleBufferRendering.h:

#if defined(__swift__)
API_DEPRECATED("Cancel the receiver's Task instead", macos(10.13, 27.0), ...)
#else
API_AVAILABLE(macos(10.13), ...)
#endif
  • Objective-C compiled with -Werror=deprecated-declarations: exit 0.
  • The identical Swift call with warnings as errors fails as deprecated.
  • Apple鈥檚 DocC data sets the Objective-C variant to "deprecated": false and clears deprecationSummary.
  • The SDK requires every requestMediaDataWhenReadyOnQueue call to be paired with stopRequestingMediaData; otherwise behavior is undefined.

@kasper93 kasper93 added this to the Release v0.42.0 milestone Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants