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

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.

@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’s 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
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 force-pushed the agent/avfoundation-pause-cpu branch from 4df03a9 to 4e616c3 Compare September 11, 2026 18:41
@GeiserX

GeiserX commented Sep 25, 2026

Copy link
Copy Markdown

I can reproduce this on macOS 27.0 arm64 with libmpv 0.41.0, and this PR fixes it.

I hit it through Supersonic, a Subsonic client that bundles libmpv. It sat at 91-100% CPU while paused, and a sample of the process showed the same stack: avfoundation event → -[AVMediaDataRequester _requestMediaDataIfReady] → __start_block_invoke → feed().

My reproducer uses libmpv with ao=avfoundation, video=no and mute=yes. A local HTTP server sends the first 6 s of a 48 kHz s16 WAV, then keeps the connection open and sends nothing more. Play it, pause at 10 s when the renderer queue has drained, and measure process CPU over the next 6 s.

libmpv paused CPU, two runs
v0.41.0 + c5d391a 99.5%, 99.4%
same + this PR 0.1%, 0.0%

Pausing with audio still queued doesn't trigger it on either build. Neither does starting paused, seeking while paused, adding and removing an aggregate device while paused, or ao-reload while paused. It takes the empty queue, which matches the root cause in the description. With the patch, three pause/resume cycles keep time-pos right. It holds while paused and advances on resume.

The harness is about 60 lines of C plus a small Python server. Happy to share it.

Disclosure: I used an AI coding assistant for the diagnosis and the test harness. I ran the tests and checked the results myself.

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.

5 participants