Compare commits

...
351 Commits
Author SHA1 Message Date
BoatingCloud c49631d857 docs: record diagnostic metadata progress 2026-09-02 21:12:35 +08:00
BoatingCloud ecaad47c23 docs: record mpv9 emby a1 progress 2026-09-02 20:36:33 +08:00
BoatingCloud 1b24aebe77 fix: pass emby tls ca bundle to core 2026-08-31 23:34:32 +08:00
BoatingCloud 67ed15e5d4 docs: record emby runtime validation 2026-08-31 04:12:26 +08:00
BoatingCloud 6064d0e05a docs: record alpha interaction validation 2026-08-31 02:46:50 +08:00
BoatingCloud 246653e2e1 docs: record alpha resume and settings validation 2026-08-31 01:39:26 +08:00
BoatingCloud 5d411509a5 docs: record alpha diagnostics validation 2026-08-31 01:01:54 +08:00
BoatingCloud 7afc6919ce docs: record Emby alpha foundation 2026-08-30 23:47:27 +08:00
BoatingCloud cfa8fa3cef docs: record Emby credential validation 2026-08-30 22:32:00 +08:00
BoatingCloud c8b8e76705 docs: record Emby phase 8.1 foundation 2026-08-30 21:31:28 +08:00
BoatingCloud f67cf6ce00 docs: record Emby foundation build 2026-08-30 19:53:48 +08:00
BoatingCloud 6e06941fec docs: record Emby API inventory phase 2026-08-30 19:47:18 +08:00
BoatingCloud afad6a40fe docs: record http playback and release packaging 2026-08-30 17:50:58 +08:00
BoatingCloud f51544026b docs: record csr format correction 2026-08-30 16:19:46 +08:00
BoatingCloud 471c9dbb6c docs: record detail ui and csr 2026-08-30 16:03:32 +08:00
BoatingCloud a06e098787 docs: preserve daily tmdb history 2026-08-30 15:24:03 +08:00
BoatingCloud c3a4f9469a docs: record tmdb live emulator verification 2026-08-30 15:19:38 +08:00
BoatingCloud 8cae735e3c docs: record tmdb detail completeness 2026-08-30 14:05:35 +08:00
BoatingCloud 82325ddfa9 docs: record search pagination 2026-08-30 13:57:28 +08:00
BoatingCloud 9c8e679c94 docs: record tmdb edge fixes 2026-08-30 13:53:10 +08:00
BoatingCloud fe1935c3b3 docs: record tmdb data integration 2026-08-30 13:40:56 +08:00
BoatingCloud 8f58fad5cd docs: record scene continuity redesign 2026-08-30 08:51:00 +08:00
BoatingCloud 3bcdcfd2a8 docs: clarify navigation ambient tint 2026-08-30 08:35:40 +08:00
BoatingCloud e2f4fbd831 docs: record navigation contrast adjustment 2026-08-30 08:32:37 +08:00
BoatingCloud 29cd001796 docs: record immersive home redesign 2026-08-30 08:05:41 +08:00
BoatingCloud e34a718c48 docs: record fixed home actions 2026-08-30 07:53:36 +08:00
BoatingCloud f1f0484eb4 docs: record vui1 completion 2026-08-30 07:31:18 +08:00
BoatingCloud 32422c4e06 docs: record phase8 ui foundation status 2026-08-29 21:31:02 +08:00
BoatingCloud 7f3d2e0747 docs: record phase7 sdk integration status 2026-08-29 19:55:47 +08:00
BoatingCloud 14149854b8 feat: package core sdk and integrate velix player app 2026-08-29 19:52:38 +08:00
BoatingCloud e594f66c35 perf: complete phase6 playback benchmark baseline 2026-08-29 18:56:22 +08:00
BoatingCloud c6f267d6dd feat: complete phase5 playback network contract 2026-08-29 16:49:38 +08:00
BoatingCloud 49b078737b docs: record phase5 remote push 2026-08-29 15:42:58 +08:00
BoatingCloud f9d1481fb8 feat: unify phase5 playback lab with stable core api 2026-08-29 15:42:26 +08:00
BoatingCloud 0db086a1e9 docs: refresh phase5 artifact record 2026-08-29 14:57:48 +08:00
BoatingCloud d7cf273432 fix: close phase5 playback contract gaps 2026-08-29 14:55:57 +08:00
BoatingCloud 8a30e06621 feat: add phase5 playback and network capability 2026-08-29 14:34:50 +08:00
BoatingCloud e4bc586d77 docs: pin final FFmpeg-Harmony branch 2026-08-29 13:43:44 +08:00
BoatingCloud 5a50baf26a docs: pin final FFmpeg-Harmony tip 2026-08-29 13:41:36 +08:00
BoatingCloud d84fd47f9b docs: pin current FFmpeg-Harmony tip 2026-08-29 13:40:31 +08:00
BoatingCloud 09de481b03 build: isolate FFmpeg-Harmony MPV output prefix 2026-08-29 13:38:17 +08:00
BoatingCloud 329728ce56 docs: record Phase 4.5 fork status 2026-08-29 13:35:35 +08:00
BoatingCloud 46582ed8bd build: add FFmpeg-Harmony prefix switch 2026-08-29 13:32:57 +08:00
BoatingCloud e5f6c0ced2 docs: normalize Phase 4 status 2026-08-29 12:43:49 +08:00
BoatingCloud e1bc8cc52f docs: add Phase 4 OHCodec status 2026-08-29 12:43:14 +08:00
BoatingCloud c239b1c186 docs: record Phase 4 OHCodec validation 2026-08-29 12:41:34 +08:00
BoatingCloud 1f1a6e5962 docs: record OHCodec interop boundaries 2026-08-29 12:36:33 +08:00
BoatingCloud 37df87fc56 feat: surface OHCodec diagnostics in Playback Lab 2026-08-29 12:36:20 +08:00
BoatingCloud 3f6132637b feat: add Harmony OHCodec hwdec adapter 2026-08-29 12:36:14 +08:00
BoatingCloud 9fdf08073e docs(harmony-audio): record implementation and validation 2026-08-29 10:07:29 +08:00
BoatingCloud dfda10b6d8 harmony: playback-lab: expose audio diagnostics 2026-08-29 10:07:25 +08:00
BoatingCloud 9941ad1da9 harmony: meson: enable OHAudio backend on OHOS 2026-08-29 10:07:19 +08:00
BoatingCloud 7021f883cb harmony: audio: add OHAudio output backend 2026-08-29 10:07:13 +08:00
BoatingCloud c650a4214d docs(playback-lab): record emulator verification 2026-08-29 09:06:04 +08:00
BoatingCloud d4c84601fa feat(playback-lab): add EGL and libmpv playback bridge 2026-08-29 09:05:47 +08:00
BoatingCloud 2356e2f67b feat(playback-lab): add isolated HarmonyOS app shell 2026-08-29 09:05:42 +08:00
BoatingCloud 75ed9dd2f4 build: complete HarmonyOS arm64 libmpv phase 1 2026-08-28 19:08:53 +08:00
BoatingCloud f178d38ce6 docs: record Velix remote repository 2026-08-28 18:36:36 +08:00
BoatingCloud 2c469d4299 docs: complete HarmonyOS phase 0 research 2026-08-28 15:47:20 +08:00
Kacper Michajłow 182fa6ca49 player/discnav: allow window dragging on menu
lint / commit-msg (push) Has been cancelled
lint / swift (push) Has been cancelled
lint / lua (push) Has been cancelled
lint / ruff (push) Has been cancelled
lint / mypy (push) Has been cancelled
lint / editorconfig (push) Has been cancelled
lint / pre-commit (push) Has been cancelled
build / mingw (i686, wine32) (push) Has been cancelled
build / mingw (x86_64, wine64) (push) Has been cancelled
build / win32 (arm64, windows-11-arm, aarch64-pc-windows-msvc) (push) Has been cancelled
build / win32 (x64, windows-2025-vs2026, x86_64-pc-windows-msvc) (push) Has been cancelled
build / macos (arm, macos-14, Xcode_15.0) (push) Has been cancelled
build / macos (arm, macos-15) (push) Has been cancelled
build / macos (arm, macos-26) (push) Has been cancelled
build / macos (macos-15-intel) (push) Has been cancelled
build / macos (test, macos-26, -target arm64-apple-macosx10.15, 10.15) (push) Has been cancelled
build / linux (map[args:-Db_lto=true -Db_lto_mode=thin cc:clang cxx:clang++ ld:lld]) (push) Has been cancelled
build / linux (map[args:-Db_ndebug=true cc:gcc cxx:g++]) (push) Has been cancelled
build / linux-fuzz (push) Has been cancelled
build / linux-ffmpeg-6-1 (push) Has been cancelled
build / openbsd (push) Has been cancelled
build / freebsd (push) Has been cancelled
build / msys2 (pattern, windows-2025-vs2026, clang64) (push) Has been cancelled
build / msys2 (true, windows-2025-vs2026, clang64) (push) Has been cancelled
build / msys2 (uninitialized, windows-2025-vs2026, clang64) (push) Has been cancelled
build / msys2 (windows-11-arm, clangarm64) (push) Has been cancelled
build / msys2 (windows-2025-vs2026, mingw64) (push) Has been cancelled
build / msys2 (windows-2025-vs2026, ucrt64) (push) Has been cancelled
build / msys2 (zero, windows-2025-vs2026, clang64) (push) Has been cancelled
build / Publish git release (push) Has been cancelled
2026-08-26 18:04:17 +02:00
Kacper Michajłow c318236b88 player: drop the enhancement-layer pairing when the media has no EL 2026-08-26 01:47:41 +02:00
Kacper Michajłow e1cf77c0e9 stream_dvdnav: clamp number of buttons to array size
libdvdread doesn't sanitize this value, so we do for corrupted discs.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 93451ef64f demux_disc: mark streams the current program does not carry
The outer streams live for as long as the disc does, but each program
brings its own set. Playing a BD playlist without the Dolby Vision
enhancement layer left the outer EL stream selected with no slave
stream mapped to it.

Normally when there is no EL there is no track either, but since it was
exposed in PMT we still have the track, while VM may decide to unmap it.
This is bit of workaround, but harmless one.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 795cd15438 demux: let a demuxer mark a stream as absent
Media can stop providing a stream mid-playback, for example a disc
program that does not carry the Dolby Vision enhancement layer the
previous one had. Since mpv always try to keep at least 1 packet
buffered in all tracks, it can keep reading it till EOF.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 96b9d84b60 f_output_chain: restore the chain ends manual connections on relink
mp_pin_connect() clears manual_connection on the pins it links, so a
filter that stops being the first or the last one leaves the chain
unable to drive its own ends. Nothing has changed the ends at runtime
so far, so this went unnoticed.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 79186bd167 discnav: poll the disc VM while playback idles
The BD-J VM progresses only on reads. With playback at EOF or on a
held still nothing reads anymore, applet playlist switches never
surface and menus freeze, libbluray documents this polling requirement
for idle states. Drive one nav read per tick and keep the core awake.
The slave demuxer latches its EOF and stops touching the stream, so
the read path peeks the stream itself to run its event loop. Poll the
same way while paused in a menu, highlight redraws would otherwise
wait for the next core wakeup. dvdnav stills release on user input,
not reads, and the pump bypasses the cache limits, so no pumping
outside these states.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 6f6bedec72 demux_disc: reset the slave demuxer in place on BD time seeks
A seek repositions the disc under the live slave demuxer, and mpegts
spliced its buffered PES state together with data of the new position
into broken packets, an AU worth of decode errors per seek. mpegts
already flushes all per-PID packet state when it sees the avio
position jump, so restart the byte position counters on the seek and
resync the avio position to the stream in drop_buffers.
2026-08-26 01:47:41 +02:00
Kacper Michajłow a47452aac0 stream_bluray: treat unannounced repositions as jumps
BD-J applets seek their player and restart playlists without any event
reaching the read loop, menu loop points do this constantly. Compare
bd_tell against the end of the previous read and treat a mismatch as a
jump. Checking before the read keeps the new position's head out of
the old slave demuxer, the forward-only stream cannot re-read it. A
read delivered together with a jump event can already carry data of
the new position, the same check drops it.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 0c87e46c83 demux_disc: gate slave reopens on buffered stream data
While the disc is between positions the open only probes an EOF and
fails, and retrying at the read rate logged a warning every attempt.
Peek before opening, this pumps the stream's event loop and the peeked
bytes stay buffered for the probe.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 15750e1212 stream_bluray: drain queued events before holding a still
The release of a still can already sit in the event queue behind the
event that announced it. Holding at first sight would miss it and
freeze the menu until user input. STILL_TIME is re-emitted on every
read while the still holds and does not count as progress.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 16896f33c0 stream_dvdnav: fix seek positioning
Menu-domain seeks: menu PGCs sequence intro/transition segments and
the looping button segment as programs of one PGC, and the menu
subpicture is a one-shot packet at a cell start. dvdnav's time search
lands mid-cell (erratically so in menu VOBs, which often lack usable
time maps), skipping the subpicture and leaving the menu without
visible buttons until its next loop. Map menu seeks to program skips.

Title seeks: dvdnav_time_search() interpolates the target from cell
durations. On VBR content the landing can fall further short than the
seek step, so repeated relative seeks converge to a fixed point and
never advance. Use dvdnav_jump_to_sector_by_time(), which resolves
through the VTS time maps, with dvdnav_time_search() as the fallback for
titles without one. Accurate landings also let the hr-seek margin
shrink.
2026-08-26 01:47:41 +02:00
Kacper Michajłow dd18d1eabc stream_dvdnav: reset a stale selected button on menu changes
A menu change keeps the VM's selected-button register unless the link
command carried a button number or the new button set forces one via
fosl_btnn. Select the first button in that case.
2026-08-26 01:47:41 +02:00
Kacper Michajłow abbbffe0dd demux_disc: hold BD jump boundaries for the player's resync seek
The resync seek that follows a disc-initiated jump must not reposition
the VM. On BD the jump is handled out-of-band by reopening the slave
demuxer on the packet-read path, and the VM already sits at the jump
destination. Repositioning it with the (stale) seek target time would
hop to the wrong place. Adopting the discontinuity counter here would
leave the old slave demuxer parsing the new playlist's data with the
wrong codec context. Flush our side only, and keep the counter stale so
the read path still performs the reopen.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 4a110f39cf demux_disc: map timestamps with per-stream timeline generations
Discs restart the in-stream timestamps at cell/segment boundaries. The
single global base_dts/last_dts pair rebased on any |diff| >= 5s between
consecutive packets of any stream, which broke down in several ways:

- Backward resets smaller than the threshold (e.g. a 1.5s menu
  transition cell followed by the looping button cell) were not detected
  at all, producing non-monotonic playback timestamps.
- The global last_dts ping-ponged between streams that cross a boundary
  at different packet positions (interleave skew), rebasing on nearly
  every packet and spamming the log.
- Menu subpictures timestamped for the next cell were mapped with the
  previous cell's offset and displayed at the wrong time.

Map each segment as a timeline "generation" with its own raw->playback
offset instead. Boundaries are detected per stream, as a backward step
beyond muxing jitter or a forward gap that would stall playback (sparse
still-image streams exempt). The previous generation stays valid until
every stream has moved past the boundary, and the new generation is
placed so the triggering stream continues seamlessly.

DVD subpictures are muxed around cell boundaries and their raw
timestamps alone cannot tell whether they continue the current segment
or belong to the next one. Defer each by one a/v packet (or EOF) and
let the packet that follows settle which generation it belongs to.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 02ec593037 stream: auto-detect .iso DVD-Video, DVD-Audio and Blu-ray images
Passing a disc image directly (e.g. `mpv disc.iso`) previously fell
through to FFmpeg. Route it to the matching disc backend instead,
like the VIDEO_TS.IFO/AUDIO_TS.IFO/BDMV redirectors already do.
2026-08-26 01:47:41 +02:00
Kacper Michajłow fee9b14d46 demux_disc: handle slideshow DVD titles
Audio DVDs commonly author each song as a chapter whose video is a
single (or more) still frame, with only the audio really interleaved.
Detect this at runtime and mark the video still_image. The player then
treats it as sparse, and the starved stream stops forcing eager reads
that would scan through the source hunting for the next still.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 4109805d0c demux_disc: follow the disc's initial audio stream selection
The player's default track selection picks the first probed audio
track, but the substream the disc's current PGC actually plays is
decided by the DVD VM and may be a different one. On discs whose menu
domain plays LPCM while the mux also carries an (unreferenced) AC3
substream, mpv selected the AC3 track, which has no data. We have to
keep track of what VM wants to output and what LAVF expects.
2026-08-26 01:47:41 +02:00
Kacper Michajłow c855d13170 stream_dvdnav: resync disc-initiated jumps through the seek path
When the disc VM jumps (menu activation, end of title), the demuxer
and player have to flush and reinitialize exactly at the boundary.

Hold an EOF at the boundary until the demuxer acks it, and resync
the player with a regular seek, which already orders flush, decoder
reset and restart. d_seek recognizes the held boundary and releases
it instead of repositioning the VM.

Also log the VM domain in nav event messages.
2026-08-26 01:47:41 +02:00
Kacper Michajłow daaab42166 player: rejoin the restart for a video frame after a transient EOF
A sparse video stream can report a transient EOF during a restart. The
DVD-Audio still track is read lazily and its first frame may arrive a
moment after the video chain already drained. Treat a frame arriving
while the restart is in progress as the restart's first frame.
2026-08-26 01:47:41 +02:00
Kacper Michajłow b3e3d1774e player: fix timing of sparse still-image video streams
Sparse video with continuous audio broke playback timing in several
ways:

- The first frame after a seek/restart was always shown immediately.
  With sparse video the frame at the seek target may not exist, and the
  next still can lie minutes ahead. Showing it early displays the wrong
  image.
- hr-seeks dropped frames with PTS below the target. For sparse video
  the nearest preceding still is the correct frame to show.
- Frame scheduling used the incremental A/V delay accounting, which is
  unusable across the video-EOF gaps between stills. Schedule sparse
  frames directly against the audio clock instead.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 34c649b864 demux: allow demuxers to update still_image during demuxing
Whether a DVD title's video track consists of sparse still images (a
slideshow) is only observable from the packet timestamps after
streaming started, and demux_disc wants to flip sh->still_image
accordingly, so that the stream stops being read eagerly.
2026-08-26 01:47:41 +02:00
Kacper Michajłow a2448f3b81 stream_dvda: expose DVD-Audio still images as a cover-art video track
DVD-Audio discs carry an Audio Still Video Set (ASVS). An MPEG-2 still
images (cover art, booklet/lyric pages) that the disc associates with
tracks.

A new STREAM_CTRL_GET_STILL returns the still for a given playback time.
demux_disc exposes it as a selectable mpeg2video track and injects the
still as a keyframe at the track's playback time. It changes as playback
crosses track boundaries (and follows onset times when a disc encodes
them).

--dvda-page (with the new STREAM_CTRL_SET_STILL_PAGE) forces a specific
still page instead of the scheduled one. The still state is guarded by
a mutex, runtime page changes arrive from the player thread while the
demuxer thread extracts stills.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 263bea5261 demux: read sparse still-image streams lazily
still_image streams (AV_DISPOSITION_STILL_IMAGE) consist of sparse
frames that can be minutes apart. When such a stream was selected
alongside an eager audio/video stream, its empty packet queue forced
the demuxer to keep reading ahead, buffering the whole file trying to
chase min_secs of "video". Read them lazily when other eager streams
exist, so playback drives read-ahead instead.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 8c6680a868 osdep/w32_register: add a DVD-Audio AutoPlay handler
Register mpv for the PlayDVDAudioOnArrival event, like the existing
CD-Audio/DVD/Blu-ray handlers.
2026-08-26 01:47:41 +02:00
Kacper Michajłow be04215d3f stream_dvda: add DVD-Audio playback support
Add a dvda:// protocol backed by libdvdread. Audio title sets are
enumerated from the ATS IFOs. Each ATS title is exposed as an mpv
title/edition and its tracks as chapters.

Menus are not supported, they would need full VM implementation, and
libdvdnav currently doesn't support DVD-Audio.

Still images (ASVS) may be supported in the future.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 76c0c5de03 stream_dvdnav: lock event processing
The menu/highlight state is filled by event processing on the demuxer
thread and read by nav commands and controls on the player thread. Add
a mutex around it.
2026-08-26 01:47:41 +02:00
Kacper Michajłow bb19b0fc36 stream_bluray: remove BD_EVENT_POPUP and menu-event tracking
BD_EVENT_POPUP was tracked into has_popup, which never grew a
consumer. BD_EVENT_MENU's menu_event_active only fed the menu_active
condition, where it was subsumed by the visible-overlay check. Remove
all three.
2026-08-26 01:47:41 +02:00
Kacper Michajłow c9422ff88f stream: turn the highlight fields into mp_dvdnav_highlight
The highlight rectangle and palette were passed around as loose fields
in three places. Bundle them into a struct, pass the whole nav state to
the sd_lavc control.
2026-08-26 01:47:41 +02:00
Kacper Michajłow c7859fe5b6 sub/osd_state: rename external2 to image_overlay 2026-08-26 01:47:41 +02:00
Kacper Michajłow b17b4371f8 stream_bluray: route logs through mp_log
This is awkward, because libbluray has a single global log callback.
libmpv can spawn multiple players in a single process, and the
instance created last takes over the callback (the previous instance's
log stays alive until its stream closes, so a racing callback never
sees a dangling pointer). This mirrors how ffmpeg logging works.

The callback delivers only a string without severity, so everything is
printed as MSGL_DEBUG, with the libbluray debug mask derived from our
log level.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 15f8a74aa4 stream_dvdnav: route logs from libdvdnav through mp_log
Use dvdnav_open2() with a logger callback instead of letting libdvdnav
print to stderr. This bumps the libdvdnav requirement to 6.1.0. 6.1.0 is
from 2019, it's resonable to request it by now.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 7c13c58ffa stream_dvdnav: keep cached duration in native time base
Store the duration in 90 kHz ticks instead of converting back and
forth. This also fixes STREAM_CTRL_GET_CURRENT_TIME's failure check,
which previously compared the already-converted value against -1 and
could never trigger.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 61416bd8b5 stream_bluray: rename BD_TIME_TO_MP to BD_TIME_TO_S 2026-08-26 01:47:41 +02:00
Kacper Michajłow b48fb6c86c demux_lavf: seek avfc only on seekable streams in drop_buffers
Disc streams are only seekable through the higher-level stream
controls, a byte seek on the slave can only fail and log errors.
avformat_flush() below already resets the demuxer state.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 2c8d954d21 demux_disc: only add the menu edition for streams with menu support 2026-08-26 01:47:41 +02:00
Kacper Michajłow 8356c7f674 demux_disc: keep one-shot DVD menu subpictures deliverable
Keep DVD SPU streams always enabled at the slave level and retain the
last subpicture per substream. Re-deliver the retained copy when a sub
track is (re)selected and when the player resets playback state.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 47a53910a5 demux: parent chapter metadata to the chapter list
demux_disc replaces the chapter list at runtime. Parent each chapter's
tags to the list that contains them, so a replaced list keeps owning
its metadata instead of accumulating it on the demuxer.
2026-08-26 01:47:41 +02:00
Kacper Michajłow b0c4e8cab6 demux_disc: soft-reload the slave demuxer on DVD discontinuities
A full slave reopen makes libavformat re-probe the stream, which eats
packets around the jump (losing the one-shot menu SPU) and re-bases the
timestamps. DVD cannot change codecs across a jump, so flushing the
slave and refreshing duration/chapters/edition is enough. BD keeps the
full reopen because codecs can change across titles.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 582810ac1d stream_dvdnav: emit a one-shot EOF at source jump boundaries
Menu activations, HOP_CHANNEL and VTS changes jump the source position.
On this strictly forward stream the demuxer cannot tell where the old
content ends, so buffered pre-jump data would mix with post-jump data.
Return a single EOF at the boundary so the demuxer drains and resets
before any new content flows.
2026-08-26 01:47:41 +02:00
Kacper Michajłow dbe496e6be demux_lavf: clear sticky avio EOF in drop_buffers
A stream can report a one-shot EOF and then continue producing data
(disc-nav jump boundaries). Also clear a sticky read error, the
underlying source is being reused.
2026-08-26 01:47:41 +02:00
Kacper Michajłow af53a9dc4b stream_bluray: rework fill_buffer around bd_read_ext
Menu support added bd_read_ext for events but kept the old bd_read
path, leaving two read loops. Use bd_read_ext everywhere and handle
all events in one place.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 4084e7609f stream_bluray: add support for still frames
Track BD_EVENT_STILL/STILL_TIME. Hold playback on indefinite stills and
skip timed stills only. Activations on a held still bump the
discontinuity counter, the disc VM will jump.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 3897b3a579 stream_dvdnav: add support for still frames
DVDNAV_STILL_FRAME means the VM is holding a frame indefinitely (menu
pages, some logos) or for a timed duration. Report the indefinite case
via nav state so the player holds playback at EOF instead of ending
the file, and skip timed stills, which would otherwise stall playback.
2026-08-26 01:47:41 +02:00
Kacper Michajłow fbcced5bf0 stream_bluray: lock all controls that read cached disc state
We cache some state like title properties, in some cases during deinit
player thread may actually try to read it during demuxer thead read.
Just add a lock, it shouldn't be called frequently to cause too much
synchronization.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 5f19209953 stream_bluray: use 1-based angle, same as in DVD
Internally the angle stays 0-based (matching the libbluray API), only
the user-visible option/property values are 1-based. BD_EVENT_ANGLE
delivers a 1-based value and is converted on arrival.
2026-08-26 01:47:41 +02:00
Kacper Michajłow f0bc30aaf1 demux: adapt caching to interactive disc navigation
The disc VM advances when the stream is read, so in nav mode the cache
must not run ahead of what the user is seeing. Never read ahead (a
looping menu is endless). Never satisfy seeks from the cache either.

This applies to the whole disc session, not only while a menu is on
screen. The VM can jump at any time (BD-J timers, end of title), so
the cache must never run ahead of playback.

Activations still must be able to drive the VM while nothing is
consumed. demux_drive_nav() forces a single read for them.
2026-08-26 01:47:41 +02:00
Kacper Michajłow c625405ddc stream_bluray: add BD-J menu support
Adds support for the ARGB overlay callback from libbluray. libbluray
does not convert between its overlay formats, BD-J planes arrive as
ARGB while HDMV planes arrive RLE-encoded, so both callbacks have to
be supported.

When the Java VM is not available, fall back to non-menu playback.

Also bump libbluray requirement to 1.0.2, it was released in 2017.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 70174945a4 discnav: sync track selection state with VM
The disc VM selects audio and subpicture streams itself (menu SPU
highlights, title-authored stream changes on jumps). Follow the VM's
selection on every hop, and turn subtitles on/off with the disc's
sub_visible flag.

The sync only applies while the corresponding track selection is on
auto. An explicitly chosen track (--aid/--sid or runtime cycling) is
never overridden.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 4725c2e492 demux_disc: set partially_seekable to true
This suppresses refresh seeks (e.g. on track switches). A refresh seek
would reposition the disc VM, which breaks menus and can hop to a
different position entirely. Track switching relies on the always-on
readahead of the disc demuxer instead.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 216e26c871 player/command: add discnav command and bind default keys
User-facing entry point for in-disc menu navigation. The command
forwards an action (up/down/left/right/select/menu/title-menu/popup/
prev/mouse-move/mouse-click) to the current disc stream handler.

The new discnav input section allows binding controls that are only
active while a disc menu is on screen.

Setting the edition property on a disc jumps to the title through the
stream directly, so selecting the currently-playing edition can still
return to it from a menu.
2026-08-26 01:47:41 +02:00
Kacper Michajłow a5682eb2ad player/command: add disc-menu-active property 2026-08-26 01:47:41 +02:00
Kacper Michajłow 5713c30160 player/loadfile: react to runtime chapter/edition list updates
On DEMUX_EVENT_LISTS, re-copy the chapter list and notify the
chapter/edition properties, so a disc title change is reflected in
observed properties and the OSC.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 816eca1fef player/loadfile: auto-select default tracks added after initial open
play_current_file picks default tracks once, then leaves them alone.
That leaves later-surfacing streams without a default selection. This
applies to all demuxers with late-added streams, not only discs.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 652ca81af9 player: add disc-menu state holder and overlay support
The new player/discnav.c owns the player-side state for the in-disc
menu UI, shared between stream_bluray and stream_dvdnav. Once per
playloop iteration it pulls the stream's nav state and

- pushes the BD menu plane to the OSD (OSDTYPE_DISC_MENU) and the DVD
  highlight state to the dvd_subtitle decoders,
- resyncs playback when the disc VM jumped on its own, detected
  through the stream's discontinuity counter,
- mirrors the disc's current title into demuxer->edition,
- force-selects a dvd_subtitle track while a DVD menu is up, so the
  menu graphics decoded by sd_lavc reach the OSD, and
- enables the {discnav} input section while a menu is interactive.

The per-file state is reset on every new playlist entry.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 8d36904e05 sub/osd: add OSDTYPE_DISC_MENU
A dedicated OSD object for the disc menu overlay. It renders above the
subtitle tracks and below mpv's own OSD.
2026-08-26 01:47:41 +02:00
Kacper Michajłow e204c1cfd9 demux_disc: add a synthetic "Disc Menu" edition
Expose the disc menu in the edition list, after the titles, so it can
be reached like any title.
2026-08-26 01:47:41 +02:00
Kacper Michajłow af9a11a8e5 demux_disc: reopen slave demuxer on disc-nav hops
Playing a disc through the menu means crossing playlist boundaries at
runtime: menu -> title (and back), sub-menu -> sub-menu, title -> title.
Each new playlist can have a different stream count and/or a different
codec mix. The previous slave demuxer was probed once at open and never
refreshed, so after a hop the player either kept decoding stale streams
or saw nothing at all. Tear down and re-open the slave on every nav
discontinuity.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 3a8b6995e9 stream_bluray: implement HDMV menu navigation
Add HDMV menu support to stream_bluray, activated by opening with
"bd://menu/..." or the global --disc-menu option.

The IG overlay plane arrives through the libbluray YUV overlay
callback. Decode the palettized RLE into a BGRA plane (premultiplied
alpha) and hand it to the player via STREAM_CTRL_GET_NAV_OVERLAY.
STREAM_CTRL_NAV_CMD maps arrow/select/menu/popup/mouse actions onto
bd_user_input() and bd_mouse_select(). Events relevant to the menu
state (title, playlist, menu on/off) are tracked so
STREAM_CTRL_GET_NAV_STATE can report an active menu, and playlist
changes bump discontinuity_id so the player flushes stale packets.
2026-08-26 01:47:41 +02:00
Kacper Michajłow b17feb1f79 stream_dvdnav: implement DVD menu navigation
Drive libdvdnav's menu state through the new disc-nav protocol so the
player can show and interact with VMGM and VTSM menus.

STREAM_CTRL_NAV_CMD maps the nav actions onto libdvdnav.

STREAM_CTRL_GET_NAV_STATE reports whether a menu with buttons is on
screen, the highlight rectangle and palette of the focused button, and
the ids of the disc-selected audio/SPU streams.

Menu mode is entered by opening with dvd://menu or --disc-menu, which
starts at the disc's root menu instead of a title.
2026-08-26 01:47:41 +02:00
Kacper Michajłow fbd0e047b1 options: add disc-menu option 2026-08-26 01:47:41 +02:00
Kacper Michajłow e2b7a983fa stream: add disc-nav actions and states
Define three new stream controls so disc backends (libdvdnav, libbluray)
can expose their in-disc menu/highlight state to the player.

Not used yet, the next commits add the backends and the player side.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 5db1db2852 demux: allow updating duration, chapters and editions during playback
Disc demuxers switch titles and playlists at runtime, which changes the
duration and replaces the chapter/edition lists. Add
demux_set_duration() and demux_lists_changed() for demuxers to publish
such updates, and DEMUX_EVENT_LISTS for the player to react.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 3ba4f76096 demux_disc: drop "PTS discontinuity" logs to verbose
Discs restart timestamps on every title or menu transition, so these
are expected in normal operation, not a sign of broken input.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 8b583e9a0d sd_lavc: add support for DVD sub highlight
This is connected to standard DVD subs renderer, with additional state
passed for highlight color.
2026-08-26 01:47:41 +02:00
Kacper Michajłow fad4b3f321 stream_dvdnav: read VTS event payload from the right buffer
While here, drop the message to MP_VERBOSE and fix its wording.
"switched to title" is misleading because this is a VTS-change event,
not a title change.
2026-08-26 01:47:41 +02:00
Kacper Michajłow b573edaaa1 sub/osd: change osd_set_external2 into generic osd_set_bitmaps
Let the caller pick the OSD object instead of hardcoding EXTERNAL2, so
other bitmap overlay sources can reuse the same path. No functional
change, EXTERNAL2 is still the only user.
2026-08-26 01:47:41 +02:00
Kacper Michajłow fbca538bfe stream_dvdnav: remove unused mousex/mousey 2026-08-26 01:47:41 +02:00
Kacper Michajłow f73dd13376 demux_lavf: parent mp_codec_params members to it
The members were parented to the demuxer or sh_stream, while they really
commute better with mp_codec_params.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 57b0a0a695 stream_dvdnav,demux_disc: clean up log messages 2026-08-26 01:47:41 +02:00
Kacper Michajłow 654e9382c0 command: add colorspace and luminance arguments to overlay-add
Add primaries, transfer, max_luma and video_colorspace arguments.
Allows tagging the colorspace of the overlay bitmap instead of assuming
sRGB. video_colorspace interprets the bitmap in the video's colorspace,
so it is tone mapped exactly like the video. max_luma overrides the
luminance peak of the bitmap. Currently only vo_gpu_next honors these
parameters. vo_gpu keeps its sRGB assumption regardless of the tag.

Fixes #10678
2026-08-23 18:09:02 +02:00
Kacper Michajłow 1c2ff7d56b vo_gpu_next: support per-part overlay colorspace
Split the parts into runs of consecutive parts that share a colorspace
and render each run as a separate overlay referencing the same texture.
2026-08-23 18:09:02 +02:00
Kacper Michajłow 66ff2efbc7 sub/sd_lavc: zero-initialize sub_bitmap entries 2026-08-23 18:09:02 +02:00
posi 345e783643 vo_gpu_next: use video colorspace for overlays only when requested
Bitmap subtitles muxed in media file are generally the same colorspace
as video. Generic overlays may not be.
2026-08-23 18:09:02 +02:00
Kacper Michajłow e89a491052 sub/packer: preserve video_color_space when packing to RGBA
The flag was lost when replacing the result with the packed image.
2026-08-23 18:09:02 +02:00
Kacper Michajłow 413153fd4e vo_gpu_next: split update_overlays into helper functions
No functional changes.
2026-08-23 18:09:02 +02:00
Kacper Michajłow 65d138fd57 vo_gpu_next: don't read libass.color for non-libass parts
The union member is only valid for SUBBITMAP_LIBASS. The resulting data
is only read for PL_OVERLAY_MONOCHROME, so there was no risk of using
garbage data, but we can make it explicit.
2026-08-23 18:09:02 +02:00
verygoodlee 9f9f8c4dd4 select.lua: fix some incorrect boolean values in the property menu
`false` value in the `value or mp.get_property_native(property)`
expression will trigger `mp.get_property_native`, but some
sub-properties cannot be accessed directly, the obtained value is nil.
2026-08-23 10:45:18 +02:00
Kacper Michajłow b955aa28f3 f_enhancement_pair: keep retained frames within the hw surface budget
Reduce the queue to 8 frames and reserve that many extra surfaces on
both the BL and EL decoders.
2026-08-22 21:00:11 +02:00
Kacper Michajłow 228f3109fd f_decoder_wrapper: add mp_decoder_wrapper_set_extra_hw_frames()
Player-side consumers can now retain decoded frames beyond what the
decoder and VO account for, e.g. the enhancement-layer pairing queue.
2026-08-22 21:00:11 +02:00
Kacper Michajłow 3b4caf0f8b f_enhancement_pair: don't emit BL-only frames before the EL warms up
The queue-full fallback could fire during startup (and after seeks),
before the enhancement-layer decoder produced its first frame.

Gate the fallback on the EL pipeline having delivered at least one
frame since the last reset. Until then a full queue only means the EL
decoder is still spinning up.
2026-08-22 21:00:11 +02:00
Kacper Michajłow 49418246f3 ci: port BSD jobs to new cross-platform-actions syntax
The run input is deprecated. Split the monolithic scripts into the same
step layout as the other jobs. Sync the sources once at VM start and
skip the per-step file synchronization, since everything runs inside the
VM anyway.

This will make reading logs easier.
2026-08-21 13:36:49 +02:00
Kate Markson a87a678573 osdep/subprocess-win: terminate environment block
Terminate the block of environment variables with NUL. Previously the block was left unterminated which could cause subprocess spawn failures depending on the array contents after the last item.
2026-08-21 12:45:11 +02:00
Kacper Michajłow e7191f2a65 demux_lavf: select initial edition only by the program's variant_bitrate
The per-stream variant_bitrate is not a reliable selection key. FFmpeg
attributes the whole variant's bandwidth to individual streams, and for
streams shared by multiple variants the value was bogus or missing
depending on the number of variants sharing the stream. The program
metadata always carries the exact HLS variant BANDWIDTH, so use it as
the only key and drop the per-stream fallback.

Note that with `--flatten-editions` tracks are selected by their own
tags, so they may still use bogus values.

For context, there is currently a bug in lavf where, depending on the
number of variants sharing a stream, the stream may or may not get
`variant_bitrate`. This will be fixed in a future FFmpeg release.
2026-08-17 19:57:13 +02:00
sharpchen b6d3434493 script_format_time: fix typo in comment 2026-08-17 19:52:11 +02:00
dependabot[bot] e034d612cf ci: bump cross-platform-actions/action from 1.3.0 to 1.4.0
Bumps [cross-platform-actions/action](https://github.com/cross-platform-actions/action) from 1.3.0 to 1.4.0.
- [Release notes](https://github.com/cross-platform-actions/action/releases)
- [Changelog](https://github.com/cross-platform-actions/action/blob/master/changelog.md)
- [Commits](https://github.com/cross-platform-actions/action/compare/v1.3.0...v1.4.0)

---
updated-dependencies:
- dependency-name: cross-platform-actions/action
  dependency-version: 1.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-16 13:43:18 +02:00
Kacper Michajłow e167836802 demux/ebml: interpret zero-length elements as their default value
An EBML element is permitted to have a zero-length data payload. It's
then interpreted as the element's RFC declared default value, or zero.

Before this change all zero-length elements were initialized to empty or
rejected in case of uint.

Fixes: #18351
2026-08-14 23:24:50 +02:00
Kacper Michajłow 52bb166f30 TOOLS/matroska.py: use named attributes for optional element fields
Will be useful for next commit.
2026-08-14 23:24:50 +02:00
Philip Langdale 7b8915bc1d ra_pl: add x2bgr10/x2rgb10 special RA formats
x2bgr10/x2rgb10 are packed formats that do not automatically get mapped by the
existing ra_pl logic. They are more useful than other non-byte-aligned packed
formats because they show up when dealing with > 8bit RGB content, which is
more common in an HDR context.

In practice, x2bgr10 is probably the only one that's going to get used, but
I figured there's no harm in supporting both.
2026-08-13 21:03:11 +02:00
llyyr f4d13e1c2c wayland: handle null output in wl_surface.enter event
This is seemingly possible: https://wayland.freedesktop.org/docs/book/Message_XML.html#allow-nulltrue--false
2026-08-10 04:39:39 +00:00
llyyr 513d3407d4 wayland: wait for initial surface configure before attaching buffer 2026-08-09 04:09:06 +00:00
subotac dd5d17d328 osc.lua: ignore seek ranges without usable duration
seekRangesF() accepts a zero duration and produces NaN/Inf coordinates,
which assdraw cannot format. Ignore seek-range metadata when the native
duration property is unavailable or zero.

Fixes #18344
2026-08-07 22:41:57 +02:00
zhanghongyuan 21277b0ccf bash-completion: fix '=' for *-clr/*-help and --help=
The special-options regex decided which options are completed without
a trailing '=', but it only covered Print options and "not in config
files" flags. As a result, two groups of no-argument options still got
'=' appended:

  (1) *-clr / *-help sub-options (e.g. --af-clr, --vo-help) which are
      mpv list-type sub-options taking no argument.
  (2) --help/--h which is String-typed in --list-options but actually
      accepts a substring filter to narrow down the option listing.

Extend the regex to cover (1). For (2), keep '=' appended (it does take
an argument) and route completion through a new Help branch that mirrors
the zsh help-options state: complete bare option names (no "--"
prefix), excluding no-* negations and list-type sub-options
(-add/-append/-clr/-pre/-set/-remove/-toggle). Bare names are needed
because --help=<substring> matches within the option name body.
2026-08-05 20:01:49 -07:00
Kacper Michajłow 1d15686142 hwdec_d3d11egl: fix EGL_EXT_device_query availability check
After ANGLE commit 56663dbfa780 EGL_EXT_device_query is listed in the
client extension string instead of the display one. The display string
check always failed, silently disabling native d3d11va interop.

This fixes `hwdec` with `gpu-context=angle`.
2026-07-31 18:01:08 +02:00
Kacper Michajłow 55146ecd14 ci/cleanup_caches: prune setup-msys2 package caches
setup-msys2 saves the pacman package cache under a key ending in a hash
of its contents. So with each update it saves more caches, what's more
important it saves it per each PR ref. We don't want that. Keep only the
msys2 cache on main branch and only the latest one per each
configuration. If PR saves new one, it will be dropped, we prefer master
branch case that can be used by all PR, rather then per PR cache of
this. The few packages that will be refetched are ok.

This change is an atempt to keep our cache under 10GB limit, to avoid
evicting more important ccache caches.
2026-07-31 17:58:16 +02:00
Kacper Michajłow 612bf5b6e8 ci/cleanup_caches: rewrite the selection as a single jq program
The nested jq invocations were hard to extend and left no trace of what
they decided. Delete by id rather than by key, since a key is only
unique per ref, and log each deletion.

No change in which caches are selected.
2026-07-31 17:58:16 +02:00
Kacper Michajłow 22c09166d7 ci/cleanup_caches: group all cleanup runs together
A cleanup is not tied to the build that triggered it, it always operates
on the whole cache state. Just cancel them and let the newest one
finish.
2026-07-31 17:58:16 +02:00
sfan5 63ada87ec7 ci/mingw: add second download URL for iconv and freetype
It appears this time GNU broke their ftpmirror, while the primary one is working again.

download.savannah.gnu.org also seems to be having issues.
Never thought I'd see the day where sourceforge is more reliable.
2026-07-31 15:56:00 +02:00
Dudemanguy 56b6200986 build: use --output option for rst2man
Apparently, the separate destination argument was deprecated years ago
and will be removed soon*. Just switch to the "new" way of specifiying
the output. Fixes #18283.

https://sourceforge.net/p/docutils/feature-requests/36/#0709
2026-07-31 13:12:03 +00:00
Nathan Lucas 74356c0fc6 ad_spdif: only warn for required codec parameters
determine_codec_params() warns when it cannot determine a codec profile.
Some passthrough formats use fixed output parameters and do not require
the probed profile or sample rate making the warning misleading.

Warn only when a value used to configure the selected passthrough format
is missing: the profile for DTS-HD, or the sample rate for core DTS and
AC-3.

Signed-off-by: Nathan Lucas <nlucasgit@gmail.com>
2026-07-29 18:18:37 +02:00
Nathan Lucas 1388a45395 ad_spdif: recreate spdif muxer on reset
The spdif decoder uses avformat/spdifenc. Some spdifenc paths keep
muxer state across packets, such as partial TrueHD MAT frames. After a
seek, that state belongs to the old stream position and can cause the
next packet to be muxed with stale timing or buffering state.

On decoder reset, discard the current spdif muxer context. The next
usable input packet will recreate the muxer through the existing
initialization path.

Cache codec parameters from the initial probe so recreating the muxer
does not probe a partial packet after a seek or replace known values
with fallbacks.

A fresh TrueHD spdif muxer needs a TrueHD major sync before it can begin
muxing the stream. Startup or post-seek playback may begin on a TrueHD
access unit that does not have major sync, so drop TrueHD packets until
a major sync access unit is seen. This also gives the downstream decoder
a clean restart point after seeks. Warn periodically if no major sync
has been found.

Partially addresses: https://trac.ffmpeg.org/ticket/9569
Signed-off-by: Nathan Lucas <nlucasgit@gmail.com>
2026-07-29 18:18:37 +02:00
Kacper Michajłow 24c1cc52a3 vd_lavc: derive hwdec surface pool size from VO requirements
Change --hwdec-extra-frames default to auto, which sizes fixed hw frame
pools from the number of frames the VO declares to reference, instead of
a static guess. Fixes pool exhaustion with e.g. --interpolation and a
tscale filter with larger radius.

Fixes: #18205
2026-07-28 15:08:36 +02:00
Kacper Michajłow 7f72f64b7f vo: add number of referenced frames to queue params
VOs can hold more frames than the requested lookahead. vo_gpu_next's
pl_queue retains past frames for the interpolation mixing window. Let
VOs declare the total number of frames they may reference at the same
time, so fixed hwdec surface pools can be sized accordingly.
2026-07-28 15:08:36 +02:00
Kacper Michajłow 48e6c35c0e demux: interrupt blocking reads whose result would be discarded
Add demux_read_interrupted(), true when the demuxer is being canceled,
its thread is terminating, or a seek is queued. Poll it from
demux_lavf's AVIO interrupt callback so blocking network I/O (e.g. the
HLS demuxer waiting on a live playlist reload) doesn't stall queued
seeks or shutdown for multiple seconds. The aborted read's result is
discarded in all of these cases anyway.

In normal operation demuxer reads are fast enough to not notice the
issue. However, in the HLS/DASH demuxers, FFmpeg blocks read() calls
on the playlist reload timeout. This is expected, but can mean multiple
seconds of dummy wait before we can actually proceed. This change
significantly speeds up seeking in HLS playback when it hits a playlist
reload.
2026-07-26 18:15:18 +02:00
Philip Langdale 390ebe2eac hwdec_vulkan: propagate the queue creation flags to ffmpeg
Up until now, we have not bothered to propagate queue creation flags when
setting up the ffmpeg device context, but with libplacebo setting flags like
VK_DEVICE_QUEUE_CREATE_INTERNALLY_SYNCHRONIZED_BIT_KHR and newer nvidia drivers
actually supporting this feature, I've now managed to run into this. If the
flags aren't put into the context, ffmpeg code can end up calling
vkGetDeviceQueue2 with the wrong flags, and the driver will return a NULL
queue instead.

I suspect we got this far without hitting this because the only filter that
can currently end up calling vkGetDeviceQueue2 is vf_libplacebo and then only
with the new `inherit_device=1` where the device we set up _from_ libplacebo
goes back to libplacebo. The oroborus is complete.
2026-07-26 06:05:47 -07:00
Kacper Michajłow b27573a239 demux: fail probing when the stream cannot be rewound to the start
Each probe attempt assumes it reads from position 0. If the rewind
fails, the demuxer would silently misparse the stream from a wrong
position.

On non-seekable streams this may fail when the probe size > buffer size,
which would cause us to drop the buffered data and not allow to rewind
the stream. This can be improved in the future.
2026-07-26 12:59:51 +02:00
Kacper Michajłow 762c5999c2 stream_curl: only offer compression for full unbounded requests
The requested start/end is in file bytes, we cannot use compression in
this case.
2026-07-26 12:59:51 +02:00
Kacper Michajłow 5910e9c2ba stream_curl: validate that the server honored a requested start offset
Error out like lavf/http.c instead of serving bytes at wrong offsets when
the server ignores the range request.
2026-07-26 12:59:51 +02:00
Kacper Michajłow 8849c4963f stream_curl: fix racy read of request_start when setting initial pos
request_start is producer state, a fast transfer can complete and roll it
forward before curl_open gets to read it, mislabeling all stream offsets.
Store the requested offset separately and use it for the initial pos.

Fixes: 289723b41a
2026-07-26 12:59:51 +02:00
nanahi 5659a9a9c8 DOCS/man/options: clarify --playlist expansion behavior
It's not recursive except for directories.
2026-07-26 11:50:05 +02:00
nanahi f76519ebd8 DOCS/man/options: clarify how --shuffle option works
There are many misconceptions about how this option work.
Make it more clear.
2026-07-26 11:50:05 +02:00
nanahi 05d45f4955 DOCS/man: mention when options are marked as file-local 2026-07-26 11:49:30 +02:00
nanahi 42d50061f6 DOCS/man/input: use connect terminology for file-local options
The file-local-options property sets options that are reset when file
change occurs. This is different from per-file options which are set with
--{ commandline arguments and options parameter of the loadfile command.
Per-file options use file-local option mechanism for setting options when
the file is loaded, but they are persistent and stay with the playlist
entries, unlike file-local-options set at runtime.

This corrects the on_load hook documentation about the effect of setting
file-local-options.
2026-07-26 11:49:30 +02:00
nanahi 731229bd2b options: add --playlist-inherit-options option
This allows controlling whether the files in a playlist inherit the
per-file options of the playlist file itself. For playlists created
by --autocreate-playlist, --playlist-inherit-options=current allows
inheriting only for the item the playlist is created from.
2026-07-26 11:49:16 +02:00
nanahi 19215efe61 player/loadfile: inherit file-local options when resolving playlist file
Currently, if a playlist file is played and replaced by its contents,
the per-file options of the playlist file are ignored.

This commit makes these options inherited for its contents so they work
as expected.
2026-07-26 11:49:16 +02:00
nanahi 97db44f89f common/playlist: add playlist_set_params function
Helper function to set file local options for all entries. Will be used
in the next commit.
2026-07-26 11:49:16 +02:00
llyyr 8c67647b50 wayland: don't leak wl_fixes on uninit
I fixed this locally but forgot to push it before the original commit
got merged, oh well

Fixes: 6f6c29b0b4
2026-07-24 12:53:05 +00:00
diniamo 0fb136f685 input/ipc-win: allow Windows handles for --input-ipc-client
Previously, you could only provide a CRT FD, which works great for the
most part, but when using CreateProcess on Windows, it requires the use
of the reserved fields of STARTUPINFO to inherit the FD to a child mpv
process. This becomes a problem for example if you use Go's os/exec API,
which does not expose a way to set those reserved fields. Exposing a way
to allow passing handles directly fixes the mentioned scenario, and
makes --input-ipc-client more flexible at no cost.
2026-07-23 22:06:04 +02:00
llyyr 6f6c29b0b4 wayland: use wl_fixes.ack_global_remove on global_remove
Notifies the compositor that the removed global will not be bound, so
the compositor can destroy it.

Ref: https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/533
2026-07-23 12:29:49 +00:00
Guido Cella 1c4adc9819 console.lua: limit how many completions are printed to the terminal
This prevents mpv from freezing e.g. when completing very large
directories.
2026-07-23 13:04:55 +02:00
Kacper Michajłow 11a9054b1d player/lua: pre-allocate tables of known size 2026-07-23 12:16:59 +02:00
Kacper Michajłow 7ff087a6b0 options/m_property: avoid repacking list nodes if we can just steal it
Removes one redundant deep-copy. For example this makes playlist with
300_000 elements conversion to node from 800 ms to 470 ms. Not a big
improvement, but still it's free to do.
2026-07-23 12:16:59 +02:00
Kacper Michajłow e61d819bf6 console.lua: add coroutine to filter selectable items asynchronously
Make the filtering and user input render asynchronously, such that there
is no latency penalty for user typing.

A single keystroke with hundreds of thousands of selectable items blocks
the script for significant time. Especially filtering by single letter
which would produce a lot of matches. This could be fixed by delaying
filtering by the time user "stops" typing, but this adds artificial
delay on top of the filtering delay itself. Instead we start the
filtering always, but have a deadline of 5 ms, at which point the
coroutine is paused and script is allowed to process user events again.
If user typed or closed the dialog, we simply abort the filtering and
start new one. Instead of waiting for full filtering to finish when the
result is already stale.

The first slice runs synchronously, so anything but huge item lists is
still filtered within the keypress handler exactly as before.

Fixes: #18268
2026-07-23 12:16:59 +02:00
Kacper Michajłow 893ffae982 console.lua: speed up filtering of selectable items
Store just the indices of the matching items in matches. The text is
looked up in selectable_items, and the positions to highlight are
computed in get_matches_to_print() only for the rendered matches, using
the query that produced the current matches. This reduces useless work
of computing highlight positions when only handful of items can be
displayed at the time on screen in practice.

Sort matches without lua comparator, this significantly speeds up
sorting of big lists, where C->Lua round-trip was problematic.

Overall it's 10x-20x times faster in my testcase.
2026-07-23 12:16:59 +02:00
Kacper Michajłow 0583bb892e fzy.lua: use lookup table for precompute_bonus
Replace those conditions with precomputed lookup. It's not huge win, but
it's free, so we can do it.

JIT:
* score() x 44875 matches: 35 -> 25 ms
* filter_range 'p': 50 -> 25 ms
* filter_range 'pf': 61 -> 37 ms

interpreter:
* score() x 44875 matches: 295 -> 254 ms
* filter_range 'p': 412 -> 284 ms
* filter_range 'pf': 531 -> 388 ms

The lookup is dense, to avoid hash tables, which makes things slower,
but in fact the tables are reused, so it's low on memory.
2026-07-23 12:16:59 +02:00
Kacper Michajłow 181090e70d fzy.lua: add filter_range() for interactive filtering of large lists
Add fzy.filter_range() which computes only scores, appends to
caller-provided arrays instead of allocating tables per match, takes
an inclusive range so callers can process a large list in slices while
handling pending input in between, and accepts optional pre-lowercased
haystacks so repeated calls don't allocate a lowercase copy of the whole
list each time.
2026-07-23 12:16:59 +02:00
Kacper Michajłow e2eaf31ce2 fzy.lua: optimize main compute() a little
There could be a lot more done if we target luajit only. Or if we port
this to C. But for now let's keep it this way. It's 2-3x faster than
before, and the issue is mostly only on very big inputs, which are not
that common in practice.

Previously computing scores allocated two matrices of lua tables per
call. With hundreds of thousands of items this made fuzzy matching in
console.lua GC-bound.

When only the score is needed, keep two reused rolling matrix rows,
making repeated scoring allocation-free. positions() still keeps all
rows since tracing back the matched positions requires them.

fzy.filter() of 300000 items drops from 1013 ms to 356 ms (on my setup)
for a worst-case single-character needle, and a standalone score() call
no longer allocates at all.
2026-07-23 12:16:59 +02:00
Kacper Michajłow e4abaaa26b fzy.lua: do byte comparision instead string ones
This are char comparisions, but in lua they are expressed as full string
compare. Compare as bytes instead, this is significantly faster,
especially with luajit compiled code.
2026-07-23 12:16:59 +02:00
Kacper Michajłow 23d39a6048 vo_wlshm: fix leak on in flight buffer on unint 2026-07-23 10:17:00 +02:00
Kacper Michajłow 11e6a50025 vo/context_wayland: fix memory leaks on failed init 2026-07-23 10:17:00 +02:00
Kacper Michajłow 15de3e6193 vo/opengl/context: fix memory leaks on failed init 2026-07-23 10:17:00 +02:00
idontwannagoo 8ab3f8b66d options: add secondary-sub-scale
This allows scaling secondary subtitles independently from primary subtitles. The option defaults to 1 like --sub-scale and follows the other secondary-sub-* options by not inheriting from the primary subtitle setting.

Fixes #14989.
2026-07-22 19:32:01 +00:00
llyyr 25451bcc11 player/command: don't refresh sub stream on UPDATE_SUB option updates
Back when reselect_demux_stream call was added in this path in 700f72f,
sub_redecode_cached_packets only retained the last two packets. The
subtitle decoder cache was changed in 0a4b098 to retain the whole packet
sequence. Therefore sub_redecode_cached_packets by itself can rebuild
the new decoder state entirely from packets held in memory, and we no
longer need the demuxer to refresh the track.

This fixes expensive track refresh for network streams, where changing
some sub-* options during runtime of network streams would cause the
cache to be dropped
2026-07-22 20:51:55 +02:00
Dudemanguy 94335ab87a ci/build-mingw64: remove --enable-libshaderc flag
This was removed upstream.

https://code.ffmpeg.org/FFmpeg/FFmpeg/commit/92f2e6374dfbe9bf1c8e4d879f8810deafff3710
2026-07-14 19:13:03 +00:00
dengbo e5486b96d7 ao_pipewire: treat init timeout as failure instead of success
When wait_for_init_done times out, init_state remains INIT_STATE_NONE.
The original check only handled INIT_STATE_ERROR, allowing init() to
return 0 (success) even though the stream was never properly initialized.
This could lead to an infinite reload loop when the partially initialized
stream later enters an error state.

Fix by checking for INIT_STATE_SUCCESS instead of INIT_STATE_ERROR,
so that any state other than SUCCESS (including NONE due to timeout)
is correctly treated as a failure.
2026-07-10 00:22:51 +02:00
Kacper Michajłow 115b87b521 demux_mkv: init ff-index for backward compatibility
lavf basically always provide tracks in order as they appear, so this
will match it. There is no hard guarantee, but this is documented.

`ff-index` is now optional, but both demux_mkv and demux_lavf set it, so
it should cover most cases where it's used. In all other cases it would
be wrong anyway.
2026-07-09 05:04:11 +02:00
Kacper Michajłow 5b087dfb18 demux: remove falsification of ff-index
The `ff-index` is just a index of in the track order as seen by lavf.
However, there are many cases where tracks are not related to lavf, when
it's not used or there are virtual tracks addded like DOVI EL track or
merged image tile grid. Don't conjure `ff-index` out of nothing.

Some scripts may be confused when this index does not much ffmpeg. Even
though it has always stated in docs.

Fixes #18244
2026-07-09 05:04:11 +02:00
Kacper Michajłow e517fd6043 player/command: make track-list/N/ff-index optional
The `ff-index` is just a index of in the track order, it doesn't carry
much meaning and may be wrong or unavailable when other than lavf
demuxer is used. Additionally some tracks may be virtual like DOVI EL
track or merged image tile grid. In which case those track are not
related to lavf at all.

No deprecation period. It was never guaranteed to match FFmpeg anyway,
so anyone who has a hard dependency on this property should reconsider.
It's not really possible to provide backward compatibility given the
ambiguity of this index. It's better to make it unavailable than making
mismatch. For user it's clear when it's not available, but not possible
to know when it's wrong.
2026-07-09 05:04:11 +02:00
Kacper Michajłow 68387ea859 video/vdpau: throttle reinit in all cases
If `is_preempted` flag is set during init we need to try again, but if
we do it too fast, we never let the init to finish and constantly gets
the flag re-set.

Fix this by throttling the same as error path. It's not perfect, because
we rely on init timing, but was confirmed working and this restores the
previous status quo.

Fixes: d9f27bd0f1
Fixes: #18161
2026-07-08 21:02:26 +02:00
Kacper Michajłow cc763d17dc osc.lua: fix duration when it becomes available
After recent changes OSC become defunct when duration is updated during
playback.

Fixes: 02491a3949
2026-07-08 20:43:09 +02:00
Kacper Michajłow cef62fbc39 player/command: reset playback state of edition changes
When changing MPEG-TS program (edition) it may have completelly
different time base and duration, on live, non-seekable streams we would
just hang trying to resync to revious PTS, which is not valid. Reset the
state on edition change.
2026-07-08 20:42:25 +02:00
Kacper Michajłow 7f93b06797 demux_lavf: sort AVProgram array by id
AVProgram array contains programs in order as they were discovered by
lavf. For MPEG-TS depending on the point where we start demuxing it is
effectivelly random. Sort by the id, so we have consistent order of our
editions, regardless of probing.
2026-07-08 20:42:25 +02:00
Kacper Michajłow 02baec9701 demux_lavf: change demuxer-lavf-{probesize,buffersize} to OPT_BYTE_SIZE
It's way easier to type 10M instead of typing all those zeros.

Our C API uses int64_t for properties and string for setting options, so
it's safe to bump this option type.
2026-07-08 20:42:25 +02:00
Kacper Michajłow ab34ec35d2 vo_gpu_next: use pl_color_transfer_is_hdr consistently
pl_color_space_is_hdr checks also luminance, but we don't do that in
other places, and unlikely that we want it.
2026-07-08 20:20:07 +02:00
Kacper Michajłow 4c220ffd95 vo_gpu_next: disable auto reference white on onlder libplacebo version
To avoid forcing by default values that may be problematic in some
cases, especially with ICC profiles.
2026-07-07 00:00:05 +02:00
Kacper Michajłow 6a0ef86e00 vo_gpu_next: make subtitles / osd also follow system reference white 2026-07-07 00:00:05 +02:00
Kacper Michajłow ba92eb63c4 vo_gpu_next: ignore reference white for screenshots 2026-07-07 00:00:05 +02:00
llyyr 8e88661257 video/out/hwdec_cuda: use mp_msg for ffnvcodec logging
ffnvcodec falls back to fprintf(stderr) unless FFNV_LOG_FUNC and
FFNV_DEBUG_LOG_FUNC are defined by the user
(https://github.com/FFmpeg/nv-codec-headers/blob/master/include/ffnvcodec/dynlink_loader.h#L80-L84).
Define these callbacks so CUDA loader messages go through mpv's normal
logging instead.
2026-07-06 18:26:36 +02:00
der richter 28a7932599 osxbundle: fix vulkan loader search path check
the old check stopped at the first occurrence of a vulkan loader path.
though it's possible different vulkan loaders are installed in different
paths, leading to not found vulkan loaders.

to prevent this, also check if the needed vulkan loader is actually
installed to that path.

specific case is when installing MoltenVK and KosmicKrisp at the same
time. the former is installed in the homebrew prefix under etc/ the
latter under share/. the latter wasn't found previous, since the former
was found first.
2026-07-06 18:25:28 +02:00
der richter ed9c05d06e osxbundle: fix a wrong vulkan search path
the paths in the array are meant to be base paths to the vulkan path.
remove vulkan from it so it won't end up as /usr/share/vulkan/vulkan.
2026-07-06 18:25:28 +02:00
nanahi 9281bccfdf osdep/compiler: don't use __has_builtin for __builtin_mul_overflow
The feature test macro is nonstandard and not available on older GCC
version, which causes build failure despite the compiler does support
__builtin_mul_overflow.

Since there are no fallback when __builtin_mul_overflow is not
available, there is no need to check, so simply remove it.
If __builtin_mul_overflow is not supported compilation will still fail
just like currently.
2026-07-06 18:25:12 +02:00
llyyr c8c7d91a8e ci/freebsd: bump freebsd version to 15.1 2026-07-06 13:17:01 +02:00
Kacper Michajłow 7bf2d9cee6 vo/opengl/context_drm_egl: init fd to -1
Instead of using zero initialized value. Otherwise an early init failure
calls close(0), a descriptor we don't own, breaking whatever else
happens to hold it.

This is bit defensive, but I want to init those just after allocation,
so we don't have to trace whether they are initalized correctly or not.
2026-07-06 13:02:16 +02:00
Kacper Michajłow 318e4e21fb vo/drm_common: init fd to -1
Instead of using zero initialized value. Otherwise an early init failure
calls close(0), a descriptor we don't own, breaking whatever else
happens to hold it.

Also add missing check before close() call.
2026-07-06 13:02:16 +02:00
Kacper Michajłow f6df7dfd8b vo/vulkan/context_display: init fd to -1
Instead of using zero initialized value. Otherwise an early init failure
calls close(0), a descriptor we don't own, breaking whatever else
happens to hold it.

This is bit defensive, but I want to init those just after allocation,
so we don't have to trace whether they are initalized correctly or not.
2026-07-06 13:02:16 +02:00
Dudemanguy 33111f3212 DOCS/man/vo: remove mentions of 'experimental' for vo_dmabuf_wayland
It's fair to say that it's generally a mature and well-working VO at
this point.
2026-07-03 14:00:15 +00:00
Dudemanguy 7a94ec5719 vo_libmpv: use the VO_CAP of the renderer backend instead of the VO
The software renderer backend is not capable of using any of the VO
capabilities set globally by vo_libmpv. This can lead to assertion
errors when trying to render a video with 90 degree rotation metadata.
Instead use the ctx->renderer->driver_caps value so the source frame
doesn't have its params rotated when it's not desired.
2026-07-03 13:59:58 +00:00
Mohammad AlSaleh 7aa8580cab options: store audio/sub delay in doubles instead of floats
An `--audio-file` could be sourced from a stream. A stream could be
 using timestamps that match unix time in their segments. In that
 case, setting `audio-delay` will lead to a big unexpected A/V desync
 due to the limited precision of floats.

 This commit changes `--audio-delay` (and --sub-delay while at it) to be
 stored in `double` instead of `float`.

 `audio_delay` was already up-casted to `double` wherever it was
 evaluated. Same for `sub-delay`, except its own value was getting
 adjusted based on `double` values in one place (bad!):

 player/command.c:6297

    mpctx->opts->subs_shared->sub_delay[track_ind] -= a[0] - refpts;

 A showcase:

    mkfifo /tmp/mpv
    date=$(date +'%s')

 Before:

    mpv --input-ipc-server=/tmp/mpv -
    echo 'print-text '"${date}" | socat - /tmp/mpv # 1782890841
    echo 'set audio-delay '"${date}" | socat - /tmp/mpv
    echo 'set sub-delay '"${date}" | socat - /tmp/mpv
    echo 'print-text ${=audio-delay}' | socat - /tmp/mpv # 1782890880.000000
    echo 'print-text ${=sub-delay}' | socat - /tmp/mpv # 1782890880.000000

 After:

    mpv --input-ipc-server=/tmp/mpv -
    echo 'print-text '"${date}" | socat - /tmp/mpv # 1782890841
    echo 'set audio-delay '"${date}" | socat - /tmp/mpv
    echo 'set sub-delay '"${date}" | socat - /tmp/mpv
    echo 'print-text ${=audio-delay}' | socat - /tmp/mpv # 1782890841.000000
    echo 'print-text ${=sub-delay}' | socat - /tmp/mpv # 1782890841.000000

Signed-off-by: Mohammad AlSaleh <CE.Mohammad.AlSaleh@gmail.com>
2026-07-02 17:38:51 +00:00
Kacper Michajłow c02751a0bf vo: prefer dmabuf_wayland over other fallback VOs 2026-07-02 17:38:28 +00:00
Mohammad AlSaleh dc8101574d audio/filter: implement lavfi-tempo leveraging lavfi's atempo/ascale
Implement `lavfi-tempo` audio filter, which uses `atempo` or `ascale`
 form libavfilter for setting the tempo.

 Users (myself included) seem to be always looking for alternative tempo
 scalers to try, and this provides a faster implementation (or two), and
 may prove useful beyond performance/speed.

 `atempo` is picked as a default, and as a fall-back if `ascale` is not
 available.

Signed-off-by: Mohammad AlSaleh <CE.Mohammad.AlSaleh@gmail.com>
2026-07-02 17:38:07 +00:00
Emmanuel Gil Peyrot 0279c0f07d path-unix: only accept absolute paths
The XDG Base Directory specification says “All paths set in these
environment variables must be absolute. If an implementation encounters
a relative path in any of these variables it should consider the path
invalid and ignore it.”
2026-07-02 17:36:50 +00:00
Thomas Weißschuh c22cac669e stream_env: add new stream to read from environment variables 2026-07-02 17:36:31 +00:00
Thomas Weißschuh 6d64f2867c stream_memory: split out bstr logic
This logic can then be reused by other streams that work on static data.
2026-07-02 17:36:31 +00:00
Thomas Weißschuh 1c467ad4f9 stream: drop unneeded include fcntl.h 2026-07-02 17:36:31 +00:00
Nicolas F a3d4b59197 command: plug another slop faucet
In the 2020s, now known as "the twilight years of modern
civilization", Silicon Valley's two most morbidly obese failsons
finally found their niche: a product that lets complete imbeciles
pretend to be software developers. This was, at least for a
while, a smash hit. Hordes of claudecucked vibechuds began
spamming free software projects with bogus "security bugs" and
associated even worse fixes, both of which they did not know how
to reason about, and definitely did not care to learn how to
reason about. The entire exercise, after all, was to get one's
name into the list of contributors, to get ahead in a highly
competitive job market of economies with equally torturous
education systems.

To the disturbingly moist-looking tech oligarchs recording
Goldman Sachs jerk-off instructons on their podcasts, this was a
profound shift in software development. Now, they no longer had
to pay 6 figures to some idiot who doesn't know how to program so
chooses React, and instead they can pay 6 figures to some
application that doesn't know how to program so chooses React.

For everyone else, it just added to the torment that is life in a
modern globalised service economy, where the fruits of their
labour are siphoned off and directed towards building ever
grander and more opulent paedophile island resorts.

But back to the code. Someone submitted a slop issue about how
there's UB if you run an input command with bogus parameters.
This is hardly a bug, since the commands are trusted, so the fix
is "don't do dumb shit". However, contextless vibechuddies saw
this as an opportunity to fire up their deeply subsidized
planet-burners and shit out some code they had no idea about what
it did.

LLMs, failing to have any real reasoning, tried twice, and failed
twice, to find the real """fix""" here. #18144 decided that
another condition needed to be added to the logical OR, whereas
the subsequent #18211 instead wanted to do an explicit cast to
size_t for no fucking reason. Funnily enough, the unsignedness of
size_t doesn't cause any problems here, because of the earlier
check to make sure `w` is positive.

Both of them missed that if `foo < bar * 4` overflows `bar`, you
can just check for `foo / 4 < bar`, which is an efficient
division as 4 is a power of 2, and the integer division here does
not lead to any problems because the check is a range check.

Do that, just to stop these stupid fucking PRs from popping up,
and deny these dumb shitheads the satisfaction of getting their
name into the contributor stats.

Closes #18128.
2026-07-02 00:30:18 +02:00
Kacper Michajłow 99b4c12ccc demux_mkv: map hvcE into side-data 2026-07-01 04:46:42 +02:00
Kacper Michajłow abfc3b21c8 mp_image: don't use deprecated pl_avdovi_metadata_supported
It's always true now.
2026-07-01 04:46:42 +02:00
Kacper Michajłow 8930fdb132 vo: bump size of pass desc string
To avoid truncated description.
2026-07-01 04:46:42 +02:00
Kacper Michajłow f53e8f3fed demux_mkv: group EL+BL dovi tracks 2026-07-01 04:46:42 +02:00
Kacper Michajłow 103aee7261 demux_disc: mirror lavf demuxer stream groups to parent
This joins Dolby Vision EL track with base one. libbluray current (as of
1.4.1) doesn't even expose this info, it will be available in next
version, but it is less code to ask lavf for this info in fact.
2026-07-01 04:46:42 +02:00
Kacper Michajłow 6b4a205c99 demux_lavf: handle AV_STREAM_GROUP_PARAMS_DOLBY_VISION 2026-07-01 04:46:42 +02:00
Kacper Michajłow 95f8f18d13 demux_mkv: add support for splitting EL track from in-band 2026-07-01 04:46:42 +02:00
Kacper Michajłow 116c8ecf3c demux_lavf: add support for splitting EL track from in-band 2026-07-01 04:46:42 +02:00
Kacper Michajłow 998c20af67 demux/dovi_split: add helper to split interleaved HEVC with BL and EL 2026-07-01 04:46:42 +02:00
Kacper Michajłow 6f724890e0 vo_gpu_next: upload/map EL frame 2026-07-01 04:46:42 +02:00
Kacper Michajłow 29c693c039 vo_gpu_next: parametrize frame upload/map
This is refactor only.
2026-07-01 04:46:42 +02:00
Kacper Michajłow 5058e6e737 f_output_chain: pair BL+EL and output as single frame 2026-07-01 04:46:42 +02:00
Kacper Michajłow 5330cae57e f_enhancement_pair: add filter to match frames of separate video tracks 2026-07-01 04:46:42 +02:00
Kacper Michajłow 81037dae3a mp_image: add no_dovi / no_enhancement_layer params 2026-07-01 04:46:42 +02:00
Kacper Michajłow 0480011ac4 vf_format: add vf=format=enhancement-layer option 2026-07-01 04:46:42 +02:00
Kacper Michajłow 6c4480cbfd mp_image: add enhancement_layer child image 2026-07-01 04:46:42 +02:00
Kacper Michajłow 314e80f2ec loadfile: reselect dependent tracks only when selected by user 2026-07-01 04:46:42 +02:00
Kacper Michajłow 25606307ff demux: propagate track selection only from base track
Don't try to select dependent track as main and feedback to base track.
To avoid situations where tracks are deselected only to select the same
group again.
2026-07-01 04:46:42 +02:00
Kacper Michajłow bc5684c2cf video/img_format: make mp_imgfmt_desc_get_num_comps input param const 2026-07-01 04:46:42 +02:00
sfan5 3f1b23abd0 stream_curl: fix libavformat version checks
Unfortunately ffmpeg missed the backport of <https://code.ffmpeg.org/FFmpeg/FFmpeg/commit/1a7e802c8f970e76807f08c3d3fb3513ae5a4882>,
so custom IO is still segfaulting on the older versions.
2026-06-29 14:58:15 +02:00
Kacper Michajłow 5abce15f1c vf_d3d11vpp: enable NVIDIA RTX Video only for SDR input
This filter only converts SDR to HDR, so disable it if we have already
HDR on input. Note that it also seems to be disabled if HDR is disabled
in Windows, but we don't check that, because filters are not related to
display. Use conditional profile to enable this filter when needed, this
commit is only small convenience change.

Fixes: #17800
2026-06-28 17:22:20 +02:00
Kacper Michajłow 4dbc235dba select.lua: add property_set getter 2026-06-28 16:36:32 +02:00
yangli 523aea99ad {select,console}.lua: release memory of menu items after menu closing
select.lua: `properties` is a large table containing 10k+ elements,
currently it's a global variable, which makes its memory cannot be
reclaimed by the GC after menu closing, so let's simply change it
to a local variable.

console.lua: `selectable_items` has the same issue, however, it cannot
be changed to a local variable because it is used by too many functions,
but at least we can reset it to nil when closing console.
2026-06-28 16:35:54 +02:00
dependabot[bot] 42149e9eac ci: bump cross-platform-actions/action from 1.2.0 to 1.3.0
Bumps [cross-platform-actions/action](https://github.com/cross-platform-actions/action) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/cross-platform-actions/action/releases)
- [Changelog](https://github.com/cross-platform-actions/action/blob/master/changelog.md)
- [Commits](https://github.com/cross-platform-actions/action/compare/v1.2.0...v1.3.0)

---
updated-dependencies:
- dependency-name: cross-platform-actions/action
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-28 15:57:06 +02:00
dependabot[bot] 508d25590d ci: bump actions/cache from 5 to 6
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-28 15:56:36 +02:00
Kacper Michajłow 62f1494661 vo_gpu_next: follow reference white system setting
This is mostly useful for SDR mode where previously we would output 203
nits, which on Windows is considered HDR, because reference white is
fixed at 80 nits in SDR mode. Which in turn causes additional
tone-mapping when using scRGB output.

Note that for SDR transfers this doesn't change the luminance, but
instead causes more aggressive tone-mapping, might not be fully
expected. But maybe we should follow system setting for better of for
worse.

Fixes: https://github.com/mpv-player/mpv/issues/17850
Not fully, because when ICC profile is embeded in content, libplacebo
will override our luminance, this will be fixed separately.
2026-06-27 23:15:26 +02:00
Kacper Michajłow a01e391851 vulkan/context_win: implement target_ref_luma 2026-06-27 23:15:26 +02:00
Kacper Michajłow be8d1eb3d6 vo/gpu/context: add preferred_ref_luma function 2026-06-27 23:15:26 +02:00
Kacper Michajłow a8a1cdbbbb d3d11/context: implement target_ref_luma 2026-06-27 23:15:26 +02:00
Kacper Michajłow 069a6f004e vo/gpu/context: add target_ref_luma function 2026-06-27 23:15:26 +02:00
Kacper Michajłow 5d15a41b0f vo/win32/d3d11_helpers: add mp_dxgi_sdr_white_level_from_hwnd() 2026-06-27 23:15:26 +02:00
Kacper Michajłow 245652c34c vo/win32/displayconfig: add mp_w32_displayconfig_get_sdr_white_level 2026-06-27 23:15:26 +02:00
Kacper Michajłow fe4eaf7c9d stream_curl: add support for Icecast metadata 2026-06-27 21:47:41 +02:00
Kacper Michajłow 5c97ee2a48 stream_lavf: use common Icecast metadata parser 2026-06-27 21:47:41 +02:00
Kacper Michajłow aa845b965a stream/network: add Icecast metadata parser 2026-06-27 21:47:41 +02:00
Guido Cella 8395d89069 vo_gpu{,_next}: use mp_basename_bstr() 2026-06-27 21:43:18 +02:00
Guido Cella 9dbf5d681d misc/path_utils: rename bstr_strip_ext() to mp_strip_ext() 2026-06-27 21:43:18 +02:00
Guido Cella 089c172dff misc/path_utils: rename bstr_get_ext() to mp_get_ext() 2026-06-27 21:43:18 +02:00
Guido Cella 4fa3c38983 misc/path_utils: remove mp_splitext()
It is no longer used.
2026-06-27 21:43:18 +02:00
Guido Cella 1a09154988 various: replace mp_splitext() with bstr_get_ext()
This will allow removing mp_splitext().
2026-06-27 21:43:18 +02:00
Guido Cella 9ffc0cd861 misc/path_utils: remove mp_strip_ext()
It is no longer used.
2026-06-27 21:43:18 +02:00
Guido Cella df3b76b018 various: replace mp_strip_ext() with bstr_strip_ext()
This will allow removing mp_strip_ext().
2026-06-27 21:43:18 +02:00
Guido Cella 2057e34f45 misc/path_utils: make bstr_strip_ext() and bstr_get_ext() accurate
Move bstr_strip_ext() and bstr_get_ext() to path_utils.c making them use
split_ext(), which is better than the previous bstr.c implementation
because it doesn't consider . in the dirname or at the start of
basenames.
2026-06-27 21:43:18 +02:00
Guido Cella 381e7eb155 misc/path_utils: add split_ext()
This will be used by bstr_get_ext() and bstr_strip_ext() without
allocating memory.
2026-06-27 21:43:18 +02:00
Guido Cella 4eaef66f4c bstr: don't make bstr_splice() add to NULL
If str.start is NULL, don't error on FreeBSD + ASan with "applying zero
offset to null pointer" in bstr_splice().

This will avoid an error in split_ext() in the language test with NULL
as argument.
2026-06-27 21:43:18 +02:00
Guido Cella 29c26b5bc5 misc/path_utils: add mp_basename_bstr()
This will be used by bstr_get_ext() and bstr_split_ext() without
allocating memory.
2026-06-27 21:43:18 +02:00
Guido Cella 9bfdb3d67f vo_gpu{,_next}: use bstr_strip_ext()
Because it is simpler than mp_splitext(), and will allow removing
mp_splitext().
2026-06-27 21:43:18 +02:00
llyyr d8c82c3b16 ao_pipewire: convert pw_time.queued from bytes to frames
pw_time.queued is reported in bytes, while the code here expects it in
nframes.

Note that this doesn't matter at all, because pw_time.queued is always 0
for us since we use it as a pull-based AO. This would be a latent bug if
we ever switched to push model.
2026-06-27 21:06:47 +02:00
Kacper Michajłow 1cf081e66c select.lua: register not yet evaluated properties to item
In case when during parse and initial item creation there is only single
property evaluated, in case like `prop1 or prop2`. Only `prop1` is
evaluated and added to observe that will make item dirty. However, when
item is dirty, we may need to evaluate the rest of the expression,
especially important to add it to observe, so we mark it dirty next
time.

Fixes: #18153
2026-06-27 20:57:24 +02:00
Kacper Michajłow 80865a7d1f f_swresample: guard invalid memory access also in NDEBUG
Mostly to make GCC happy and fix array-bounds warning.
2026-06-27 20:39:35 +02:00
Kacper Michajłow a40319a514 osdep/compiler: make MP_ASSERT_UNREACHABLE no return
On some platforms assert is continuable, we don't want that, so abort()
just after.
2026-06-27 20:39:35 +02:00
Kacper Michajłow fb6c700ecb meson: try vapoursynth-script first
Older versions ship separate `vapoursynth-script.pc` and
`vapoursynth.pc`, we want to use `script` one. Try it first, if not
available use `vapoursynth`, which is the only one new version ships.
2026-06-27 19:16:30 +02:00
Kacper Michajłow ccfe4871db test/avio_crypto: add test for stream decryption 2026-06-27 19:04:03 +02:00
Kacper Michajłow 449944e25f demux/avio_crypto: add AES decryption
This mirrors `crypto+` protocol from ffmpeg, which is used in HLS
demuxer. Unfortunatelly ffmpeg doesn't use custom io_open in their
crypto.c, so we spin our own decryption.
2026-06-27 19:04:03 +02:00
Kacper Michajłow fc26cd23c0 msg: strip raw C1 controls and skip valid UTF-8 in sanitize
Fixes: #17957
2026-06-27 18:55:17 +02:00
arch1t3cht 7da362dfb5 demux_mkv: support demuxing native VC-1 streams
Match lavf's logic for how to parse the extradata.
2026-06-27 18:12:09 +02:00
verygoodlee 6b623e29cd select.lua: make property list more in line with the desc in the manual
1. the index of array-type properties is 0-based. ( e.g. `playlist/N` )
2. boolean values should be formatted as yes/no instead of true/false.
2026-06-27 18:11:48 +02:00
Nicolas F 669f431cf0 demux_edl: fix signed integer overflow in HLS bitrate
Use the new MP_SATURATE_MUL to prevent hls_bitrate from overflowing when
being multiplied by 8.

Fixes: #18120
2026-06-27 18:10:47 +02:00
Nicolas F f5a894de87 common/common: add saturating multiply macro
Add a new function-like macro MP_SATURATE_MUL. It multiplies a * b and
stores it in pointer "result", but sets "result" to its type's maximum
value if it overflows. Evaluates to true if it saturated, otherwise
false.
2026-06-27 18:10:47 +02:00
Nicolas F 8c6ad1b002 osdep/compiler: add MP_CKD_MUL
Add a new MP_CKD_MUL macro which does a multiplication with overflow
checking, backed by either the C23 ckd_mul standard function, or the
GCC/Clang __builtin_mul_overflow otherwise.
2026-06-27 18:10:47 +02:00
Kacper Michajłow ae99e39239 demux_mkv: remove redundant size checks
After previous commit, those sizes are checked in parser.
2026-06-27 18:09:12 +02:00
Kacper Michajłow 0251783b95 TOOLS/matroska.py: add size constraint for fixed size binary data
Those are fixed size per RFC and read as such in demux_mkv.c, fixes OOB
access in some cases that were missing the checks there.
2026-06-27 18:09:12 +02:00
Kacper Michajłow 23b9e96e9b demux/ebml: fix error path on data reject
If we don't read the data it shouldn't set the element as present.
2026-06-27 18:09:12 +02:00
Guido Cella 351291d583 menu.conf: compare aspect overrides more strictly
Fixes #18152.
2026-06-27 18:04:48 +02:00
Kacper Michajłow 0d68a842a0 hwdec_d3d11va: support 4:4:4 and high bit depth zero-copy formats
Replace the static format whitelist with a device-probed table and add
4:4:4 (VUYX/XV30/XV36/XV48) and 4:2:0 12/16-bit (P012/P016) surfaces to
both the zero-copy and copy mapper paths. The packed YUV formats can't be
decomposed by ra_get_imgfmt_desc(), so they are viewed as a single RGBA
texture and the swizzle is recovered from the imgfmt descriptor, which
only works with --vo=gpu-next.
2026-06-27 18:04:15 +02:00
Kacper Michajłow 2a49cc01d6 video/d3d: exclude VP9 on known broken Intel drivers 2026-06-27 18:03:54 +02:00
Kacper Michajłow 1d8cb602ac video/d3d: exclude HEVC on known crashing Intel drivers
The list of failing drivers was taken from a popular browser, which has
a lot of telemetry to determine which versions are not working.

Fixes: #18173
2026-06-27 18:03:54 +02:00
Kacper Michajłow 07c8c16bef vd_lavc: allow disabling codec hwdec per-device 2026-06-27 18:03:54 +02:00
Kacper Michajłow 70894ae039 ytdl_hook: defer loading until the formats become available
yt-dlp can provide time at which formats become available
`available_at`. Sleep until the requested time.

Fixes: #18192
2026-06-27 01:10:41 +02:00
Kacper Michajłow c75b8d2cca stream_curl: strip parameters from content type
It should only be assigned mime-type itself. I'm sure I had this change
locally, but probably lost it during rebase somewhere.

Fixes: #18187
2026-06-26 06:48:16 +02:00
Kacper Michajłow 289723b41a stream_curl: fix initial stream pos when offset is used
This is to match lavf/http.c behaviour and allow proper seeking.
2026-06-26 06:48:16 +02:00
Kacper Michajłow c4207644ba stream_curl: require libcurl 7.88.0
To avoid those awkward definitions. Initially much lower version was
required, but after changes it landed at 7.87.0, so we might as well
bump it one up.
2026-06-26 06:47:50 +02:00
sfan5 2339eb7276 stream_curl: extend version checks that enable curl by default
...to include older ffmpeg patch relases where the nested IO cleanup bug is fixed
2026-06-23 11:03:09 +02:00
Misaki Kasumi 2bd9c3229f osdep: fix MP_NO_ASAN definition 2026-06-22 03:52:21 +02:00
Guido Cella 1bcc17a7be select.lua: fix selecting the default subtitle line
Fixes 53796ca2ea.
2026-06-21 20:34:00 +02:00
dependabot[bot] dde8d8c4a0 ci: bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-21 12:36:13 +02:00
Kacper Michajłow efe8644a93 github/workflows/docs: update package index to fix install failures
When index is outdated some files may not exist, when we try to install
them.
2026-06-20 23:30:14 +02:00
Kacper Michajłow 2d5dfb343a ytdl_hook: use native http-proxy option instead of lavf option
Fixes: #18042
2026-06-18 05:27:17 +02:00
Kacper Michajłow f8150c78d5 demux_mkv: don't apply AVI b-frame delay compensation to VfW tracks
crazy_video_pts_stuff() shifts video PTS back by the codec delay to undo
libavcodec returning frames paired with a later packet's DTS. This is
only correct for genuine AVI, where DTS are decode-order frame indices.

VfW-muxed Matroska looks similar but its block timestamps are real DTS,
already offset by the reorder delay, so the libavcodec shift cancels the
offset and no compensation is needed. Applying it anyway pushed video
one frame ahead of audio (e.g. VC-1, whose Advanced Profile always
reports has_b_frames=1).

Split the `avi_dts` flag. Deep DTS routing for VfW mkv via a new
`block_dts`, and reserve avi_dts for genuine AVI only.

Note that demux_lavf for Matroska was already not affected, because
`avi_dts` wouldn't be set for those.

Fixes #18089
2026-06-17 23:55:51 +02:00
Guido Cella 02491a3949 osc.lua: don't reinit on duration changes
It can be avoided by extracting just the code to update the seekbar and
its chapter markers. This avoids log spam and is more efficient for DVD
and Blu-ray menus which continously update duration and have chapter
markers.

The livemarkers option is removed since updating the chapter markers is
no longer expensive like reiniting.
2026-06-17 13:39:10 +00:00
Guido Cella 715d2b7cf2 DOCS/man/commands: rename COMMANDS to CONSOLE COMMANDS
Avoid confusing it with command interface.
2026-06-17 13:38:57 +00:00
llyyr 3159500884 f_auto_filters: destroy deint filter immediately on format change
When the image format changes (e.g. switching hwdec), immediately
destroy the existing deinterlace filter instead of draining it. Draining
feeds new frames through the old filter, which can cause issues. (e.g.
bwdif_vulkan receiving vaapi frames).

This fixes userdeint filter failure when switching from hwdec=vulkan to
hwdec=vaapi while the filter is applied via --deinterlace=auto/yes
2026-06-17 13:37:57 +00:00
llyyr 4acaa77de5 af_scaletempo: avoid memcpy with disabled overlap
Overlap can be disabled with overlap=0 or very small stride values which
cause calculated overlap size to truncate down to 0.

Fixes: https://github.com/mpv-player/mpv/issues/18107
2026-06-17 13:37:34 +00:00
llyyr 9d828d5e55 vulkan/context: guard for null --vulkan-device param
Fixes: https://github.com/mpv-player/mpv/issues/18106
2026-06-17 13:37:34 +00:00
Dudemanguy 306546f35f bitmap_packer: drop fprintf logging
Ideally we would properly log this with mp_log but the plumbing for that
was deemed too ugly and not really worth it for just this one mysterious
edge case. So instead, drop the fprintf business since we really
shouldn't be doing that.
2026-06-17 13:36:44 +00:00
Dudemanguy 7bc56a77a9 bitmap_packer: return -1 instead of aborting on invalid bitmap size
All current callers of this already handle the -1 case like an error, so
instead of killing everything just return -1.
2026-06-17 13:36:44 +00:00
Misaki Kasumi 688b2738c6 subprocess-posix: skip macro guard in reset_signals_child 2026-06-17 10:47:16 +02:00
Misaki Kasumi 49b06cc337 subprocess-posix: fix uninitialized stacks in fork path 2026-06-17 10:47:16 +02:00
Misaki Kasumi 6b48326ccf subprocess-posix: avoid using mmap 2026-06-17 10:47:16 +02:00
Misaki Kasumi d849fd60d0 subprocess-posix: use rfork_thread on FreeBSD 2026-06-17 10:47:16 +02:00
Misaki Kasumi de1c73a44b subprocess-posix: handle detach inside spawn_process 2026-06-17 10:47:16 +02:00
Misaki Kasumi 5fb6a76ddf subprocess-posix: use clone when available 2026-06-17 10:47:16 +02:00
Pavel 53796ca2ea select.lua: add keep-open argument to select bindings
Adds an optional keep-open argument to all select.lua script bindings
that prevents the menu from closing after a selection.

Menus that display selection state (tracks, audio device) or a current
position indicator (playlist, chapter, edition, subtitle line) are
wrapped in a recursive function so the list is rebuilt after each
selection and the indicators remain accurate. default_item is passed to
preserve the cursor position on the last selected item.

The history confirmation dialog reopens the history menu when No is
selected, and closes when Yes is selected as there is nothing to show
anymore.
2026-06-17 00:30:39 +02:00
dependabot[bot] f88bcd25c7 ci: bump cross-platform-actions/action from 1.1.0 to 1.2.0
Bumps [cross-platform-actions/action](https://github.com/cross-platform-actions/action) from 1.1.0 to 1.2.0.
- [Release notes](https://github.com/cross-platform-actions/action/releases)
- [Changelog](https://github.com/cross-platform-actions/action/blob/master/changelog.md)
- [Commits](https://github.com/cross-platform-actions/action/compare/v1.1.0...v1.2.0)

---
updated-dependencies:
- dependency-name: cross-platform-actions/action
  dependency-version: 1.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-16 16:20:38 +00:00
Wang Yu 9baeefe635 player: fix cover-art-files overriding embedded video tracks
When comparing external cover art images with embedded video tracks,
the external-vs-internal preference should not apply. Without this
check, --cover-art-files causes video files to be treated as audio
by selecting the external image over the embedded video stream.

Only apply the external track preference when both tracks have the
same image type (both images or both non-images).

Fixes #12219
2026-06-16 16:17:48 +00:00
Guido Cella cab6fbe222 context_menu.lua: don't open disabled submenus on hover
A menu item can have a non-empty submenu but still be disabled. This is
the case for mkvs with a single edition. Don't open the submenu on hover
in that case.
2026-06-16 13:02:52 +02:00
verygoodlee 89da9deafe console.lua: do not allow typing newlines when searching for menu items
newline makes no sense in such case, as there're no line breaks in the
selectable items. and it also causes a bug where the cursor may move
outside the background box.
2026-06-16 13:02:11 +02:00
arch1t3cht 65a1852ba3 input.conf: bind right click to context menu 2026-06-15 07:20:37 +02:00
Mikael Magnusson 662d41093c demux_timeline: fix Twitch VOD timestamps being offset by ~60 seconds 2026-06-14 20:14:42 +02:00
llyyr 7d245fd100 video/gpu/lcms: remove stray pointer from video profile handle
cmsHPROFILE is already a pointer type
2026-06-12 19:15:18 +02:00
llyyr bda1dcaa78 video/gpu/lcms,vo_gpu_next: avoid returning bool from pointer functions 2026-06-12 19:15:18 +02:00
llyyr 3d4893a893 test/language: replace accidental octal literal
Surrounding code uses decimal 0, not octal 0, so probably a typo
2026-06-12 19:15:18 +02:00
Kacper Michajłow 304426c390 video/vf_amf: fix use-after-free on AMF_INPUT_FULL 2026-06-09 11:52:29 +02:00
Kacper Michajłow fb5345809d sub/sd_ass: fix animated state indexing for reordered packets 2026-06-09 11:52:29 +02:00
Kacper Michajłow c1c047f8d6 stream/lavf: check child_next return value before dereference 2026-06-09 11:52:29 +02:00
Kacper Michajłow 894c350f83 stream/dvb: initialize parsing buffers for VDR-style configs 2026-06-09 11:52:29 +02:00
Kacper Michajłow 1ad719ff19 osdep/subprocess-posix: restore signal mask on detached fork failure 2026-06-09 11:52:29 +02:00
Kacper Michajłow f232963c61 input/ipc-win: fix uninitialized handle and missing client NULL check 2026-06-09 11:52:29 +02:00
Kacper Michajłow a7d0ebaf76 input: remove unused mp_input_src_feed_cmd_text() 2026-06-09 11:52:29 +02:00
Kacper Michajłow dddafe8b60 filters/f_lavfi: compare height in is_vformat_ok 2026-06-09 11:52:29 +02:00
Kacper Michajłow 5e4b06d977 demux/disc: avoid timestamp shift on unknown duration 2026-06-09 11:52:29 +02:00
Kacper Michajłow 5dfdd39997 demux/packet: fix OOB read in ITU-T T.35 UK parsing 2026-06-09 11:52:29 +02:00
nanahi 6444c05059 filters/frame: use correct check for mp_frame_set_pts
Not effect for current handlers.

Fixes: [Bug] frame.c: Wrong handler check in mp_frame_set_pts
(get_pts instead of set_pts)
2026-06-08 01:26:23 +02:00
nanahi e7f5d8f9c8 filters/filter: fix dropping request logging
Fixes: 76276c9210
2026-06-08 01:26:23 +02:00
nanahi c42393a280 video/filter/vf_amf: fix profile option
Fixes: [Bug] vf_amf.c: Profile always forced to FRC_PROFILE_HIGH
2026-06-08 01:26:23 +02:00
nanahi c9cf510d6a video/filter/vf_format: fix changing gamma check
Fixes: 76276c9210
2026-06-08 01:26:23 +02:00
nanahi 6a5e514509 input/sdl_gamepad: move controller null check to front
No need to call SDL_GameControllerGetJoystick if controller is NULL.
2026-06-08 01:26:23 +02:00
nanahi a24227d7ad stream/stream_bluray: fix chapter range check
Fixes: 62786c0c91
2026-06-08 01:26:23 +02:00
nanahi f29fcafb81 video/filter/vf_gpu_vulkan: parent opts to ctx
And only free explicitly on success. On failure it will be freed by
parent.

Fixes: [Bug] vf_gpu_vulkan: Use-after-free of ctx_opts
2026-06-08 01:26:23 +02:00
nanahi 579a62c2ba video/filter/vf_gpu_vulkan: remove redundant frees
ractx and vk are parented to ctx. No need to free manually.
2026-06-08 01:26:23 +02:00
arch1t3cht 47f81c8d7e lcms: avoid NaN when building BT.1886 transfer curves
After
https://github.com/mm2/Little-CMS/commit/bb60a46e9c50e9d3d18cf6dd81869240e4ebe618
cmsDetectBlackPoint can output nonzero X and Z values, which can end up
slightly negative due to rounding errors.
Bound the resulting src_black values by 0 below to ensure that raising
them to the power of 1 / gamma does not result in NaN, which would
result in a broken video profile.

Fixes #17439.
2026-06-08 00:56:44 +02:00
Hubert Głuchowski 43b14a4c9f sub/sd_sbr: make margin changes trigger redraw
This fixes panning not updating the subtitle overlay.
2026-06-07 16:06:37 +02:00
Hubert Głuchowski 71ebd08406 sub/packer: correctly offset instance bitmap pointer in pack_subrandr
The software scaling code expects this pointer to already be offset
by `src_x` and `src_y` but this function copied a pointer only
offset by the image offset, forgetting about instance offset.
2026-06-05 22:16:45 +02:00
Hubert Głuchowski 85fe21d290 sub/sd_sbr: fix zooming
Previously both the padding and clip rect calculations didn't take into
account zoom (negative margins) which resulted in more or less broken
behavior depending on if `sub-use-margins` was set or not.

This fixes those to properly handle negative margins which:
- Makes us calculate a tighter clip rect if `!sub-use-margins`.
- Unbreaks layout zoomed in and `sub-use-margins`.
2026-06-05 21:37:18 +02:00
Kacper Michajłow f88f423829 meson: consider only 'v0.*' tags when computing the version
This allow creating other tags on the repository and don't interfere
with version string generated.
2026-06-05 07:21:58 +02:00
Amyspark 1ad1322335 ci/win32: enable building without Git Bash shell
Replace `cp` in the subrandr wrap with an inline Python script.
2026-06-05 04:03:04 +02:00
Amyspark 0fc000b10b meson: properly escape backslashes when setting CONFIGURATION macro
This fixes compiling when using Windows-style paths in the Meson
configuration line, e.g.

> '-Dc_args=-IC:\mpv\subprojects\amf-headers'
2026-06-05 04:03:04 +02:00
321 changed files with 19840 additions and 2052 deletions

No files matched your search

+147 -103
View File
@@ -52,14 +52,14 @@ jobs:
- arch: x86_64
wine: wine64
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Get time
id: get_time
run: echo "timestamp=`date +%s%N`" >> $GITHUB_OUTPUT
- name: Restore cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ env.TARGET }}-${{ steps.get_time.outputs.timestamp }}
@@ -123,7 +123,7 @@ jobs:
path: artifact/*
- name: Save Cache
uses: actions/cache/save@v5
uses: actions/cache/save@v6
if: always()
with:
path: ${{ env.CCACHE_DIR }}
@@ -157,7 +157,7 @@ jobs:
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Get time
id: get_time
@@ -165,7 +165,7 @@ jobs:
"timestamp=$((Get-Date).Ticks)" >> $env:GITHUB_OUTPUT
- name: Restore cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ matrix.target }}-${{ steps.get_time.outputs.timestamp }}
@@ -261,7 +261,7 @@ jobs:
build/mpv.pdb
- name: Save Cache
uses: actions/cache/save@v5
uses: actions/cache/save@v6
if: always()
with:
path: ${{ env.CCACHE_DIR }}
@@ -285,7 +285,7 @@ jobs:
swift: "-target arm64-apple-macosx10.15"
target: "10.15"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Remove stray upstream python binary symlinks under /usr/local
run: |
@@ -381,7 +381,7 @@ jobs:
ld: "lld"
args: "-Db_lto=true -Db_lto_mode=thin"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Build with meson
id: build
@@ -411,7 +411,7 @@ jobs:
CC: "clang"
CXX: "clang++"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Build with meson
id: build
@@ -434,7 +434,7 @@ jobs:
# Musl support is an added bonus.
image: alpine:3.21
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Install dependencies
run: |
apk update
@@ -485,104 +485,148 @@ jobs:
openbsd:
runs-on: ubuntu-latest # until https://github.com/actions/runner/issues/385
timeout-minutes: 15 # avoid any weirdness with the VM
defaults:
run:
shell: cpa.sh {0} --sync-files none
steps:
- uses: actions/checkout@v6
- name: Test in OpenBSD VM
uses: cross-platform-actions/action@v1.1.0
with:
operating_system: openbsd
version: '7.8'
- uses: actions/checkout@v7
- name: Start OpenBSD VM
uses: cross-platform-actions/action@v1.4.0
with:
operating_system: openbsd
version: '7.8'
sync_files: runner-to-vm
- name: Install dependencies
run: |
sudo pkg_add -U \
cmake \
curl \
ffmpeg \
git \
libarchive \
libbluray \
libcaca \
libcdio-paranoia \
libdvdnav \
libiconv \
libplacebo \
libv4l \
libxkbcommon \
luajit \
meson \
openal \
pkgconf \
pulseaudio \
python3 \
rubberband \
rust \
sdl2 \
shaderc \
spirv-cross \
spirv-headers \
uchardet \
vulkan-loader \
vulkan-headers \
zimg
./ci/build-openbsd.sh
if ! meson test -C build; then
cat ./build/meson-logs/testlog.txt
exit 1
fi
sudo pkg_add -U \
cmake \
curl \
ffmpeg \
git \
libarchive \
libbluray \
libcaca \
libcdio-paranoia \
libdvdnav \
libiconv \
libplacebo \
libv4l \
libxkbcommon \
luajit \
meson \
openal \
pkgconf \
pulseaudio \
python3 \
rubberband \
rust \
sdl2 \
shaderc \
spirv-cross \
spirv-headers \
uchardet \
vulkan-loader \
vulkan-headers \
zimg
- name: Build with meson
id: build
run: |
./ci/build-openbsd.sh
- name: Print meson log
if: ${{ failure() && steps.build.outcome == 'failure' }}
run: |
cat ./build/meson-logs/meson-log.txt
- name: Run meson tests
id: tests
run: |
meson test -C build
- name: Print meson test log
if: ${{ failure() && steps.tests.outcome == 'failure' }}
run: |
cat ./build/meson-logs/testlog.txt
freebsd:
runs-on: ubuntu-latest # until https://github.com/actions/runner/issues/385
timeout-minutes: 15 # avoid any weirdness with the VM
defaults:
run:
shell: cpa.sh {0} --sync-files none
steps:
- uses: actions/checkout@v6
- name: Test in FreeBSD VM
uses: cross-platform-actions/action@v1.1.0
with:
operating_system: freebsd
version: '15.0'
- uses: actions/checkout@v7
- name: Start FreeBSD VM
uses: cross-platform-actions/action@v1.4.0
with:
operating_system: freebsd
version: '15.1'
sync_files: runner-to-vm
- name: Install dependencies
run: |
sudo pkg update
sudo pkg install -y \
alsa-lib \
cmake \
curl \
evdev-proto \
ffmpeg \
git \
iconv \
jackit \
libarchive \
libbluray \
libcaca \
libcdio-paranoia \
libdvdnav \
libdisplay-info \
libplacebo \
libXinerama \
libxkbcommon \
libXpresent \
libXv \
luajit \
meson \
mujs \
openal-soft \
pipewire \
pkgconf \
pulseaudio \
python3 \
rubberband \
rust \
sekrit-twc-zimg \
sdl2 \
sndio \
uchardet \
v4l_compat \
vulkan-headers \
wayland-protocols
./ci/build-freebsd.sh
if ! meson test -C build; then
cat ./build/meson-logs/testlog.txt
exit 1
fi
sudo pkg update
sudo pkg install -y \
alsa-lib \
cmake \
curl \
evdev-proto \
ffmpeg \
git \
iconv \
jackit \
libarchive \
libbluray \
libcaca \
libcdio-paranoia \
libdvdnav \
libdisplay-info \
libplacebo \
libXinerama \
libxkbcommon \
libXpresent \
libXv \
luajit \
meson \
mujs \
openal-soft \
pipewire \
pkgconf \
pulseaudio \
python3 \
rubberband \
rust \
sekrit-twc-zimg \
sdl2 \
sndio \
uchardet \
v4l_compat \
vulkan-headers \
wayland-protocols
- name: Build with meson
id: build
run: |
./ci/build-freebsd.sh
- name: Print meson log
if: ${{ failure() && steps.build.outcome == 'failure' }}
run: |
cat ./build/meson-logs/meson-log.txt
- name: Run meson tests
id: tests
run: |
meson test -C build
- name: Print meson test log
if: ${{ failure() && steps.tests.outcome == 'failure' }}
run: |
cat ./build/meson-logs/testlog.txt
msys2:
runs-on: ${{ matrix.os }}
@@ -617,7 +661,7 @@ jobs:
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.sys }}
@@ -691,7 +735,7 @@ jobs:
group: publish-git
cancel-in-progress: false
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
@@ -738,7 +782,7 @@ jobs:
}
# Match the version string baked into the build (see common/meson.build).
version=$(git describe --abbrev=9 --tags --dirty)
version=$(git describe --abbrev=9 --tags --dirty --match "v0.*")
notes=$(cat <<EOF
Automated development build of the latest \`master\` branch.
+44 -26
View File
@@ -6,7 +6,7 @@ on:
types: [completed]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
group: ${{ github.workflow }}
cancel-in-progress: true
permissions:
@@ -17,32 +17,50 @@ jobs:
runs-on: ubuntu-latest
steps:
- run: |
gh cache list -L 100 --json id,key,ref -S last_accessed_at -O desc --jq '
map(select(.key | startswith("x86_64-w64-mingw32-") or
startswith("i686-w64-mingw32-") or
startswith("aarch64-pc-windows-msvc-") or
startswith("x86_64-pc-windows-msvc-"))) |
group_by(.ref) |
map({
ref: .[0].ref,
caches: map({
key: .key,
prefix: (.key | capture("^(?<prefix>[\\w_-]+-)\\d+$").prefix)
}) | group_by(.prefix) | map({keys: map(.key)})
}) |
.[]
' |
while read -r group; do
pr=$(echo "$group" | jq -r '.ref | capture("refs/pull/(?<num>[0-9]+)/merge").num')
if [[ -n "$pr" ]] && [ "$(gh pr view $pr --json state --jq '.state')" != "OPEN" ]; then
keys=$(echo "$group" | jq -c '.caches | map(.keys) | .[]')
else
keys=$(echo "$group" | jq -c '.caches | map(.keys[1:]) | .[]')
fi
for key in $(echo "$keys" | jq -r '.[]'); do
gh cache delete "$key"
done
set -o pipefail
gh cache list -L 1000 --json id,key,ref -S last_accessed_at -O desc > caches.json
# Resolve the state of every pull request that owns a cache.
for pr in $(jq -r '.[].ref | select(test("^refs/pull/[0-9]+/merge$")) |
split("/")[2]' caches.json | sort -u); do
jq -n --arg pr "$pr" --arg state "$(gh pr view $pr --json state --jq .state)" \
'{($pr): $state}'
done | jq -s 'add // {}' > prs.json
jq -r --slurpfile prs prs.json --arg default "$DEFAULT_REF" '
$prs[0] as $state |
# Group caches by kind, dropping the timestamp or content hash
# that every save appends to the key.
def family: if startswith("msys2-") then "msys2"
elif test("-[0-9]+$") then "ccache"
else "other" end;
def group: if family == "msys2" then sub("-(files|state):.*$"; "")
else sub("-[0-9]+$"; "") end;
[ .[] | . + { family: (.key | family),
group: (.key | group),
pr: (.ref | if test("^refs/pull/[0-9]+/merge$")
then split("/")[2] else null end) } ]
| map(select(.family != "other")) as $all
# Caches of merged or closed pull requests, and msys2 caches
# outside the default branch, which every ref restores from.
| ( $all | map(select(
(.pr != null and $state[.pr] != "OPEN") or
(.family == "msys2" and .ref != $default)
)) ) as $dead
# Of the rest only the newest cache of each kind per ref is used.
| ( ($all - $dead) | group_by([.ref, .group]) | map(.[1:]) | add // [] ) as $superseded
| ($dead + $superseded)[] | "\(.id)\t\(.ref)\t\(.key)"
' caches.json |
while IFS=$'\t' read -r id ref key; do
echo "deleting $ref $key"
gh cache delete "$id"
done
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEFAULT_REF: refs/heads/${{ github.event.repository.default_branch }}
+2 -1
View File
@@ -21,9 +21,10 @@ jobs:
check-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Docs
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends -y python3-docutils rst2pdf
./TOOLS/docutils-wrapper.py rst2man --strip-elements-with-class=contents --halt=2 ./DOCS/man/mpv.rst mpv.1
./TOOLS/docutils-wrapper.py rst2html --halt=2 ./DOCS/man/mpv.rst mpv.html
+7 -7
View File
@@ -17,7 +17,7 @@ jobs:
commit-msg:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 50
@@ -28,20 +28,20 @@ jobs:
swift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: norio-nomura/action-swiftlint@3.2.1
lua:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: lunarmodules/luacheck@v1
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Check
id: check
uses: astral-sh/ruff-action@v3
@@ -54,7 +54,7 @@ jobs:
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: python/mypy@master
with:
install_project_dependencies: no
@@ -64,12 +64,12 @@ jobs:
editorconfig:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: editorconfig-checker/action-editorconfig-checker@main
- run: editorconfig-checker -config .editorconfig-checker.json
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: pre-commit/action@v3.0.1
+4
View File
@@ -1,3 +1,7 @@
/subprojects/*
!/subprojects/packagefiles
!/subprojects/*.wrap
/build/
/dist/
.hvigor/
.DS_Store
+22
View File
@@ -0,0 +1,22 @@
# Phase 6 Benchmark 方法
## 执行
```sh
python3 benchmark/runner/run_emulator.py startup_local --repeat 20
python3 benchmark/runner/run_emulator.py seek_local --repeat 50
python3 benchmark/runner/run_emulator.py stress_lifecycle --repeat 100
python3 benchmark/runner/run_emulator.py play_pause --repeat 200
```
runner 通过 hdc/uitest 驱动 `Playback Lab`,每次 load 使用内置 `sample.mp4`,记录 timeline、错误、状态和进程采样。startup 的首轮标为 cold-ish,后续为 warm;OS page cache 未被强制清空。seek 使用加载后立即触发的原生 `+10s`,避免短样片在输入框多次 keyEvent 期间自然结束。
每个场景输出逐次 JSON 和 min/max/avg/median/P90/P95。百分位采用排序后的 nearest-rank;负时间线值是 unavailable,不参与统计。错误单独保留,不从成功样本中删除。
## 采样边界
- VmRSS、VmSize、Threads、fd count 和原始 CPU jiffies 来自 `/proc`;PSS、精确 native heap、可靠 CPU permille 未被模拟器公开,输出 null/原始值。
- host wall time 包含 hdc 往返,只用于发现自动化超时,不作为启动性能结论。
- `firstAudioCallbackMs` 是 Playback Lab 诊断轮询观察到 callback_count>0 的时间;要取得 callback 内精确时间需独立 tracing,因此不把它当作硬件时钟证据。
- `stress_lifecycle` 覆盖 load/stop;release/recreate 需要新 XComponent surface,另行作为实体设备/手工场景。
- HTTPS、HLS、鉴权、网络字幕必须使用受控 fixture;本轮没有把公网失败当作性能数据。
+7
View File
@@ -0,0 +1,7 @@
# Buffering Model
buffering 是 MPV `cache-buffering` 属性与网络状态的边沿诊断,不是 UI 的瞬时文案。进入时记录 `bufferingEnterCount` 和起始 monotonic 时间;退出时累加 `bufferingTotalMs`,并更新 `bufferingMaxSingleMs`。重复的同值属性通知不会重复计数。
网络快照同时保留 protocol、host、HTTP status、redirect count、cache duration/bytes、current bitrate、range support 和最近 seek result。`networkWaitMs` 只计算已观察到的 network-open 到 first-byte/file-loaded 间隔;file:// 输入为 -1。
本地短片通常在首帧后很快 EOF,因此 buffering 计数为 0 不代表网络流在真实设备上不会重缓冲。受控 HTTP Range fixture 已在 Phase 5 验证;HTTPS/HLS、鉴权和弱网重缓冲应在实体设备用固定服务端脚本复测。
+97
View File
@@ -0,0 +1,97 @@
# Phase 1 Build Issues
## BUILD-001
Component: FFmpeg configure
Command: `scripts/build-ffmpeg-ohos.sh`
Original Error: `Unknown option "--disable-postproc".`
File: `scripts/build-ffmpeg-ohos.sh`
Root Cause: 当前 FFmpeg 已移除独立 postproc 配置项,Phase 0 示例参数过期。
Platform Difference: 无;属于当前 upstream 配置接口变化。
Fix: 删除不存在的 `--disable-postproc`,以当前 `configure --help` 为准。
Files Changed: `scripts/build-ffmpeg-ohos.sh`
Patch Required: Build script only
Result: configure 继续执行。
Regression Risk: Low
## BUILD-002
Component: FFmpeg compiler/linker probe
Command: `scripts/build-ffmpeg-ohos.sh`
Original Error: `ld: library 'System' not found`,随后 `C compiler test failed`。
File: `scripts/build-ffmpeg-ohos.sh`、`ffbuild/config.log`
Root Cause: `--sysroot` 不会自动改变 Clang target;链接探针仍按 macOS 目标寻找 `libSystem`。
Platform Difference: macOS arm64 与 OHOS arm64 架构相同,但 ABI、对象格式和系统库不同。
Fix: 同时向 CFLAGS 和 LDFLAGS 显式传入 `--target=aarch64-linux-ohos`。
Files Changed: `scripts/build-ffmpeg-ohos.sh`
Patch Required: Build script only
Result: FFmpeg configure、build、install 成功,产物为 AArch64 ELF。
Regression Risk: Low
## BUILD-003
Component: FreeType zlib resolution
Command: `scripts/build-font-stack-ohos.sh`
Original Error: `ERROR: Subproject zlib is buildable: NO`,Meson 尝试下载 WrapDB zlib。
File: `scripts/build-font-stack-ohos.sh`
Root Cause: FreeType 的 `zlib=external` 表示构建 fallback subproject,不表示使用外部系统库。
Platform Difference: SDK 已提供 target `libz.so`,不应另行下载 target zlib。
Fix: 使用当前 FreeType 定义的 `zlib=system`,通过隔离 `.pc` 指向 SDK AArch64 zlib。
Files Changed: `scripts/build-font-stack-ohos.sh`
Patch Required: Build script only
Result: FreeType 2.14.3 静态库构建成功。
Regression Risk: Low
## BUILD-004
Component: Meson cross-file pkg-config resolution
Command: `scripts/build-font-stack-ohos.sh`
Original Error: HarfBuzz 报 `Dependency 'freetype2' is required but not found`,包装器被解析到 HarfBuzz 源码目录。
File: `toolchains/ohos-api24-arm64.ini`
Root Cause: `@GLOBAL_SOURCE_ROOT@` 指向当前被配置项目的 source root,不固定指向 Velix-MPV-Core。
Platform Difference: 多个独立 Meson 源码树共享一个 target prefix。
Fix: 使用以 Cross File 自身为基准的 `@DIRNAME@/../` 定位包装器与 prefix。
Files Changed: `toolchains/ohos-api24-arm64.ini`
Patch Required: Cross File only
Result: FreeType、HarfBuzz、libass 和 MPV 均只从 OHOS prefix 解析依赖。
Regression Risk: Low
+64
View File
@@ -0,0 +1,64 @@
# HarmonyOS Native Build Strategy
## Scope
Phase 1 只构建 HarmonyOS API 24 arm64 `libmpv`。不包含播放器 UI、XComponent 集成、`ao_harmony`、MPV OHCodec backend、性能优化、HDR 或真机运行声明。
## Toolchain
| Item | Value |
|---|---|
| SDK | `/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony` |
| Version | 6.1.1.125 / API 24 |
| Target | `aarch64-linux-ohos` |
| Sysroot | `<SDK>/native/sysroot` |
| Compiler | OHOS Clang 15.0.4 |
| Meson | 1.9.0,仓库隔离 venv |
| Ninja | SDK 1.12.0 |
| pkgconf | 3.0.6,仓库隔离 host tool |
| Cross File | `toolchains/ohos-api24-arm64.ini` |
| Prefix | `build/ohos-arm64/prefix` |
`scripts/setup-ohos-env.sh` 清空 `CPATH`、`C_INCLUDE_PATH`、`CPLUS_INCLUDE_PATH`、`LIBRARY_PATH`、`CMAKE_PREFIX_PATH`,并将 pkg-config 限制到 OHOS prefix。target 编译和链接必须同时带 `--target` 与 `--sysroot`。
## Reproducible Build
```sh
scripts/build-all-ohos.sh
```
顺序固定为:host tools → pinned sources → C/C++ probes → SDK zlib metadata → OpenSSL → FFmpeg → FreeType/FriBidi/HarfBuzz/libass → libplacebo → MPV → 全量 ABI/污染/符号校验。
各步骤也可单独执行:
```sh
scripts/verify-toolchain.sh
scripts/build-openssl-ohos.sh
scripts/build-ffmpeg-ohos.sh
scripts/build-font-stack-ohos.sh
scripts/build-libplacebo-ohos.sh
scripts/build-mpv-ohos.sh
scripts/verify-ohos-prefix.sh
```
所有下载源码按 `scripts/fetch-phase1-sources.sh` 的 tag/commit 固定。构建目录可删除后重建,禁止从 Homebrew 或 macOS prefix 提供 target library。
## MPV Configuration
MPV 使用 `-Dauto_features=disabled`,再显式启用 `libmpv`、GPL、`plain-gl`、OpenGL、vector、zlib。CLI、测试、脚本语言、桌面后端、平台硬件加速 backend 和 Vulkan 均关闭。实际 feature 列表以 `DOCS/MPV_BUILD_REPORT.md` 和 `build/ohos-arm64/logs/mpv.log` 为准。
MPV 源码仅增加明确的 `host_machine.system() == 'ohos'` 检测与 `ohos` feature。POSIX 路径继续复用现有 threads、timer、path、poll、IPC、subprocess 实现;这些能力当前只能标记 compile verified。
## Output and Gates
主产物:`build/ohos-arm64/prefix/lib/libmpv.so.2.5.0`。
`scripts/verify-ohos-prefix.sh` 必须同时证明:
- 所有 prefix 静态库成员为 AArch64 ELF;
- `libmpv` 为 AArch64 ELF shared object;
- 动态依赖仅来自 HarmonyOS SDK allowlist(启用 `harmony-audio` 时包含 `libohaudio.so`);
- `.pc`/CMake/Ninja target metadata 无 Homebrew、`/usr/local`、`.dylib` 或 Apple framework;
- Client API 与 OpenGL Render API 必需符号存在。
真机行为、CA store、XComponent/EGL 生命周期及 OHCodec buffer 语义留到后续阶段验证。
+11
View File
@@ -0,0 +1,11 @@
# Core API versioning
当前冻结版本:`VELIX_CORE_API_VERSION = 0x000100`(v0)。
- `MAJOR` 变化表示 ABI/API 不兼容,必须升级主 App 适配层。
- `MINOR` 变化只增加尾部字段或能力,旧调用方保持可用。
- `PATCH` 变化只修复实现,不改变公开结构布局。
版本宏、`velix_core_get_api_version()`、`velix_core_get_version()` 和 `velix_core_get_build_info()` 位于 `include/velix/core.h`,C ABI 使用 opaque handle 与固定宽度整数。变更 API 时同步更新 `metadata/build-info.json`、此文档与集成文档,并在发布前检查动态符号。
v0 不把 `mpv_handle`、`mpv_node`、FFmpeg、OHCodec、EGL、OHAudio 或 OHNativeWindow 类型带出 SDK。
+27
View File
@@ -0,0 +1,27 @@
# Velix Core SDK
Phase 7 SDK v0 以 `include/velix/core.h` 与 `libvelix_core_binding.so` 为产品边界,主 App 不编译 MPV/FFmpeg 源码。
## 目录
```text
velix-core/
├── include/velix/core.h
├── include/mpv/harmony_playback.h
├── lib/arm64-v8a/libvelix_core_binding.so
├── lib/arm64-v8a/libmpv.so.2
├── lib/arm64-v8a/libc++_shared.so
├── metadata/build-info.json
└── licenses/
```
`libvelix_core_binding.so` 仅提供产品生命周期、控制、状态、媒体、音轨、字幕、指标与 build-info NAPI;XComponent 的 OHNativeWindow/EGL/mpv render context 仍由 Core 内部实现。主 App 只能看到 `PlaybackEngine`/`PlaybackController` 的业务类型。
默认 profile 为 software decode,OHCodec GL mapper 保持关闭。SDK 产物位于 `dist/velix-core`,已在 `.gitignore` 中忽略,禁止把 34MB `libmpv` 二进制提交到 Git。
构建与 staging:
```sh
./scripts/package-velix-core-sdk.sh --profile default
../Velix/scripts/build-velix-app.sh --release-core --build-mode debug
```
+81
View File
@@ -0,0 +1,81 @@
# FFmpeg Strategy
## 决策
Phase 1 使用独立构建的 **官方 FFmpeg upstream**,不把源码揉进 MPV,也暂不建立 `Velix-FFmpeg-Harmony`。
研究基线:FFmpeg master `3acec0a1af2dda0a0838689b8b8649e7deb080a0`(2026-08-28)。该 commit 已提供 OpenHarmony `ohcodec` hwcontext/pixel format 以及 H.264/HEVC decoder/encoder,显著降低自行 fork 的必要性。
## MPV 依赖约束
MPV 当前 Meson 最低要求:
| Library | 最低版本 |
|---|---:|
| libavcodec | 60.31.102 |
| libavfilter | 9.12.100 |
| libavformat | 60.16.100 |
| libavutil | 58.29.100 |
| libswresample | 4.12.100 |
| libswscale | 7.5.100 |
必须同时生成 pkg-config 文件供 MPV cross build 使用。
## 两种方案评估
### A. 官方 FFmpeg 独立构建(采用)
- 独立源码目录与安装前缀。
- arm64 OHOS toolchain 交叉编译。
- Phase 1 首先保留 software decode、demux、HTTP/HLS、字幕相关基础;OHCodec 可编译但不要求首次 MPV 构建即启用硬解。
- TLS 使用 FFmpeg 已支持并经过许可审核的第三方 backend(优先 OpenSSL);Harmony `libnet_ssl.so` 不能直接替代。
- 优点:上游同步清晰、已有 OHCodec、MPV 仓库 patch 最小。
### B. Velix-FFmpeg-Harmony fork(暂不采用)
只有满足以下任一条件才触发:
1. 官方 OHCodec 在 API 24 上存在阻断编译/运行问题,且修复无法及时 upstream。
2. 需要 OHNativeBuffer/GPU import 或 HDR metadata 等实质性 FFmpeg API 扩展。
3. 网络/TLS 或 Harmony 生命周期需要不可在外部构建脚本解决的源码 patch。
建立后必须同样保留完整历史、配置 `upstream`、小 patch 分支,并单独维护许可证审计。
## Phase 1 建议配置轮廓
实际参数必须先由交叉编译探针和 FFmpeg `configure --help` 固化;以下是预计命令,不是已成功结果:
```sh
./configure \
--prefix="$FFMPEG_PREFIX" \
--target-os=linux \
--arch=aarch64 \
--cc="$OHOS_CLANG --target=aarch64-linux-ohos --sysroot=$OHOS_SYSROOT" \
--cxx="$OHOS_CLANGXX --target=aarch64-linux-ohos --sysroot=$OHOS_SYSROOT" \
--ar="$OHOS_LLVM_AR" \
--strip="$OHOS_LLVM_STRIP" \
--enable-cross-compile \
--enable-pic \
--enable-shared \
--disable-programs \
--disable-doc \
--enable-network \
--enable-openssl
```
注意:FFmpeg 当前 configure 未发现独立 `target-os=ohos` 结论,是否应使用 `linux` 必须由真实配置日志决定;编译器会同时定义 `__OHOS__` 与 `__linux__`。`--enable-ohcodec` 在首次软件解码 build 可暂缓,第二个受控构建再开启并验证其五个 Native media 库。
## 网络与 TLS
- HTTP/HLS 主要由 libavformat protocol/demuxer 提供,不新增 MPV 自定义网络栈。
- socket/DNS 可直接使用 OHOS libc。
- HTTPS 需要 FFmpeg 支持的 TLS backend。首选独立交叉构建 OpenSSL,并固定 CA bundle/系统证书策略;证书路径、SNI、代理、IPv6、重定向、Range 请求必须真机验证。
- 禁止将“SDK 有 `libnet_ssl.so`”写成“FFmpeg HTTPS 已支持”。
## 验收与 fork 决策点
1. `configure` 识别 arm64 OHOS、pthreads、network、dlopen。
2. 所需 FFmpeg libraries 完整生成,pkg-config 不泄漏宿主机路径。
3. software decode + HTTP/HLS 单测通过。
4. `--enable-ohcodec` 能链接 API 24 SDK,并枚举 `h264_oh` / `hevc_oh`。
5. 任何源码 patch 先形成最小 diff、根因、upstream 可接受性评估,再决定是否 fork。
+102
View File
@@ -0,0 +1,102 @@
# HarmonyOS OHAudio Backend
状态:Phase 3 CODE COMPLETE / DEVICE VALIDATION PENDING。
代码、OHOS arm64 交叉编译和模拟器 H.264/AAC smoke test 已完成。实体设备上的真实输出路由、延迟、A/V sync 精度、蓝牙/耳机、多声道和长时间稳定性仍未宣称完成。
## Architecture
```text
MPV decoder/filter
↓
MPV AO buffer (audio/out/buffer.c)
↓ pull callback
audio/out/ao_harmony.c
↓
OH_AudioRenderer write callback
↓
HarmonyOS audio device
```
`ao_harmony` 是 MPV pull AO。PCM 始终在 native 层流动;ArkTS 只负责播放控制和诊断展示,不参与持续 PCM 搬运。
实现文件:
- `audio/out/ao_harmony.c`:AO、OHAudio renderer、callback、时钟和统计。
- `audio/out/ao_harmony.h`:Playback Lab 使用的只读诊断 ABI。
- `audio/out/ao.c`、`meson.build`、`meson.options`:driver 注册和 `harmony-audio` feature。
## OHAudio API
实现以当前 API 24 SDK headers 为准:
- `ohaudio/native_audiostreambuilder.h`
- `ohaudio/native_audiorenderer.h`
- `ohaudio/native_audiostream_base.h`
- `multimedia/native_audio_channel_layout.h`
- 链接库:`libohaudio.so`
Builder 设置 `SamplingRate`、`ChannelCount`、`ChannelLayout`、`SampleFormat`、`EncodingType(RAW)`、`LatencyMode(NORMAL)`、`RendererInfo(USAGE_MOVIE)` 和 write/device/error callbacks,然后 `GenerateRenderer` 并销毁 Builder。Renderer 使用 `Start`、`Pause`、`Stop`、`Flush`、`Release`、`SetVolume`、`GetFrameSizeInCallback`、`GetLatency` 和 `GetAudioTimestampInfo`。
## MPV AO / callback model
`write` 保持 NULL,`start` 触发 OHAudio。OHAudio 回调只计算 frame 数、调用 `ao_read_data(..., pad_silence=true, blocking=false)`,不足部分填静音并更新轻量原子计数;不访问网络、不调用 N-API/ArkTS、不分配大对象、不逐回调打印日志。回调首包仅在 debug 日志中记录一次。
MPV AO buffer 是 producer,OHAudio callback 是 consumer。`ao_read_data` 返回的 PCM 只在当前 callback 内有效,系统提供的 `audioData` 由 OHAudio 所有;没有 AVFrame 或 PCM 指针跨生命周期保存。
## Format / sample rate / channel layout
- 仅接受 PCM;MPV float 优先映射 `AUDIOSTREAM_SAMPLE_F32LE`,创建失败时回退 `AF_FORMAT_S16` / `AUDIOSTREAM_SAMPLE_S16LE`。
- 采样率使用 MPV 请求值,不强制改成 48 kHz;设备拒绝时返回真实 renderer 创建错误,由 MPV 上层重新协商/重采样。
- 单声道映射 `CH_LAYOUT_MONO`,其余首版限制为双声道 `CH_LAYOUT_STEREO`;超过 2 声道先通过 MPV channel map downmix 到 stereo。没有声称 5.1/7.1/Atmos/Dolby 已支持,实体设备能力仍待验证。
模拟器实际日志:请求 48 kHz、5.1、floatp,经 MPV downmix 后创建 48 kHz、2 声道、float32 renderer。
## Pause / resume / stop / seek / flush
- pause:`OH_AudioRenderer_Pause`。
- resume/start:`OH_AudioRenderer_Start`。
- stop:MPV 的 stop/EOF 通过 AO reset/uninit 路径停止 renderer;不把 pause、stop、release 混为 destroy。
- seek/reset:先禁止新 callback,`Stop`,等待 inflight callback 清零,再 `Flush`;清空本地提交/播放 epoch,增加 `audioFlushCount` 与 `seekAudioResetCount`,下一次 `start` 重新开始。
- release:禁止 callback → Stop → 等待 callback → 停止并 join timestamp 线程 → Release renderer;回调不会持有释放后的 player/AO 指针。
所有 OHAudio 返回值都检查;失败通过 MPV AO error/reload 路径处理,使用 `AUDIO_BUILDER_FAILED`、`AUDIO_RENDERER_CREATE_FAILED`、`AUDIO_RENDERER_START_FAILED`、`AUDIO_FLUSH_FAILED`、`AUDIO_RELEASE_ERROR`、`AUDIO_UNSUPPORTED_FORMAT` 等明确日志。
## A/V clock and delay
timestamp 线程每 20 ms 调用 `OH_AudioRenderer_GetAudioTimestampInfo`。`submittedFrames` 在 callback 完成填充后递增;`playedFrames` 使用 renderer frame position 减去本次 Flush 的 timestamp origin,避免设备在 Flush 后继续返回旧 epoch。延迟计算为:
```text
pending = max(submittedFrames - playedFrames, 0)
estimatedDelayMs = rendererLatencyMs + pending * 1000 / sampleRate
```
`audioTimestamp` 保留 OHAudio 的 monotonic timestamp。MPV 播放倍速仍由 MPV 音频滤镜/时钟负责,不叠加 renderer speed。算法已编译并在模拟器观察到稳定计数;绝对 A/V 偏差与设备路由连续性必须实体机测量。
## Volume and underrun
Renderer volume 固定为 unity,最终音量由 MPV 软件 gain 处理,避免双重增益。API 24 当前没有可用的系统 underrun counter,因此不伪造 `systemUnderrun`;callback 返回帧不足时只增加 Velix-side `bufferStarvation`。
## Playback Lab diagnostics
Playback Lab 保持原有 video-only 启动路径,新增 `Audio On/Off`(切换 MPV `aid=auto/no`)和以下 native 状态:`audioBackend`、`sampleRate`、`sampleFormat`、`channels`、`channelLayout`、`submittedFrames`、`playedFrames`、`estimatedAudioDelayMs`、`audioBufferStarvation`、`audioFlushCount`、`seekAudioResetCount`、`audioState`。返回值使用 ArkTS Number,避免 BigInt 让 Inspector 无法序列化。
## Validation
- Audio-enabled:OHOS arm64 `libmpv.so.2.5.0` clean build,DT_NEEDED 新增 `libohaudio.so`,Playback Lab HAP 构建成功。
- Video-only:使用 `-Dharmony-audio=disabled` 的独立 Meson build 编译成功。
- Emulator smoke:H.264/AAC MP4 上确认 renderer create、start、callback PCM consumed、pause、resume、seek/flush、stop、release;示例观察到 `starvation=0`,模拟器日志无新增崩溃。
- 未验证:实体设备、真实 latency/A-V 偏差、蓝牙/有线/扬声器切换、系统 audio focus/interruption、5.1/7.1、30 分钟压力。
## Known limitations and physical TODO
1. 当前仅实现 OHAudio renderer;没有 OHCodec/hwdec、Vulkan、音频焦点业务和设备专属路由策略。
2. `systemUnderrun` 需要 SDK 提供真实接口后再接入;当前仅有 `bufferStarvation`。
3. 多声道真实 capability、采样率拒绝后的设备行为、timestamp 在路由切换后的连续性需要实体设备矩阵。
4. 上层应用负责电话/音频焦点策略;Core 只接受 pause/resume 或 AO reload。
Phase 4 不在本次变更中启动。未来 OHCodec 评估至少需要单独验证 surface/buffer 同步、stride/format、fence 与生命周期、零拷贝/DRM 约束、不同设备实现差异五类风险。
## License
本次仅修改 Velix-MPV-Core 内的 MPV 集成代码和 Playback Lab;MPV 原有 GPL/LGPL 许可保持不变。HarmonyOS SDK 头文件/系统库按 SDK 自身许可使用,没有复制第三方实现源码。
+59
View File
@@ -0,0 +1,59 @@
# HarmonyOS Audio Research
> 状态:IMPLEMENTED(代码、OHOS arm64 编译、Playback Lab 模拟器音频 smoke test 已完成);实体设备输出路由、真实延迟、A/V sync 精度、多声道与长稳仍为 NEED PHYSICAL DEVICE VERIFY。
## 1. 推荐方案
新增 MPV 音频输出 `ao_harmony`,直接接 HarmonyOS OHAudio:
```text
MPV audio pipeline -> native PCM ring/buffer -> OHAudio write callback -> AudioRenderer
```
禁止 PCM 逐块跨 N-API/ArkTS。ArkTS 只发送播放器级控制和生命周期事件。
## 2. SDK 依据
- headers:`ohaudio/native_audiostreambuilder.h`、`native_audiorenderer.h`、`native_audiostream_base.h`、`multimedia/native_audio_channel_layout.h`
- library:`libohaudio.so`
- SDK 依据:当前 API 24 headers;builder 基础接口从 API 10 起,独立 write callback 从 API 12 起,`GetAudioTimestampInfo` 从 API 15 起,`GetLatency` 从 API 23 起。
- builder:采样率、声道数、sample format、encoding、latency mode、channel layout、usage、write callback、callback frame size
- renderer:Start、Pause、Stop、Flush、Release、`GetAudioTimestampInfo`、`SetVolume`、`GetLatency`;API 24 没有可用的系统 underrun counter,Velix 只记录 callback starvation。
API 24 还提供 advanced callback 等新接口,但首版使用最低且稳定的 callback API,避免无必要抬高最低版本。
## 3. 与 MPV AO 的映射
| MPV / 播放需求 | OHAudio 映射 | 设计 |
|---|---|---|
| PCM 格式协商 | `SetSamplingRate`、`SetChannelCount/Layout`、`SetSampleFormat` | `init()` 将 MPV `ao->format` 映射为 OHAudio;不支持时返回 unsupported,让 MPV 重采样 |
| 数据输出 | renderer write callback | 回调只读取 MPV AO buffer,禁止阻塞、分配大对象或调用 ArkTS |
| Pause/Resume | `OH_AudioRenderer_Pause/Start` | 与 MPV `control()` 状态串行化 |
| Stop/Seek | `Stop/Flush/Start` | Seek 时停止 callback、清空 OHAudio、重建 timestamp epoch,避免旧 PCM 泄漏 |
| Volume | `OH_AudioRenderer_SetVolume` | 系统 renderer 固定 unity;最终增益由 MPV 软件音量处理,避免重复增益 |
| Speed | MPV 音频滤镜为主 | 不直接用 Renderer speed 改变 MPV 时钟;系统 speed 仅后续专项验证 |
| A/V Sync | `GetAudioTimestampInfo` | 驱动 delay 由 callback 已提交帧与 timestamp epoch 内的硬件播放位置计算;路由切换仍需实体设备测试 |
| 低延迟 | `SetLatencyMode` | 视频播放默认 normal;fast 仅在设备支持和稳定性测试后启用 |
| 多声道 | channel layout + capability | 先 stereo;5.1/7.1 按设备能力协商,失败回退重混音,不 fake success |
## 4. MPV 接入位置
- 新文件建议:`audio/out/ao_harmony.c`。
- 注册位置:`meson.build` 的 audio outputs 区域和 `audio/out/ao.c` driver list。
- 参考结构:`ao_aaudio.c` 的 callback/pull 模式及状态控制;不能复制其 Android API。
- OHAudio callback 与 MPV AO buffer 的线程安全遵循 `audio/out/buffer.c` 现有模型;回调只消费系统请求的 frame 数。
## 5. 格式策略
首版候选优先级:float32(若稳定支持)→ signed 16-bit。采样率优先内容原始值,设备拒绝则让 MPV/libswresample 转为 48 kHz。声道首版 2.0,随后验证 5.1/7.1。所有实际支持值通过 builder 创建结果与设备行为确认,不从枚举存在推断硬件支持。
## 6. 风险与验证
1. write callback 的实时线程约束和最大执行时间。
2. `GetTimestamp` 在暂停、Flush、蓝牙/有线切换后的连续性。
3. underrun、首包预填充与首音频时间。
4. Surface 暂停但音频继续、应用后台、音频焦点中断。
5. 播放速度与 MPV 时钟双重调整风险。
6. 多声道 layout 枚举和实际设备 route 不一致。
代码验收:PCM 正常播放;pause/resume/seek/flush/release 生命周期已在模拟器通过;starvation 与 timestamp metrics 已接入。1 小时稳定性、真实 underrun、A/V sync 偏差、路由与多声道仍需实体设备,不宣称低延迟或多声道完成。
+65
View File
@@ -0,0 +1,65 @@
# HarmonyOS Native Capability Research
## 1. 取证基线
- SDK:`/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony`
- Native / Toolchains:`6.1.1.125`,API 24
- 目标:`aarch64-linux-ohos`
- 编译器:OHOS Clang 15.0.4
- 最小头文件探针已通过:pthread、dlopen、mmap、epoll、poll、socket、DNS、EGL、GLES3、Vulkan、XComponent、OHAudio、Native AVCodec 可被同一 C translation unit 包含。
- 编译器预定义:`__OHOS__`、`__linux__`、`__unix__`、`__aarch64__`、`__LP64__`。EGL 平台头使用 `OHOS_PLATFORM` 选择 `OHNativeWindow *`,构建时必须显式定义该宏。
以下“最低 API”来自本机 API 24 SDK 头文件的 `@since` / availability;运行行为仍需 Phase 1/2 真机验证。
## 2. 能力矩阵
| 能力 | C/C++ API / header | library | 最低 API | 生命周期与限制 | 结论 |
|---|---|---|---:|---|---|
| ArkUI Surface | `OH_NativeXComponent_RegisterCallback`;`OnSurfaceCreated/Changed/Destroyed`;`ace/xcomponent/native_interface_xcomponent.h` | `libace_ndk.z.so` | 8 | `window` 只在组件 Surface 生命周期内有效;销毁前停止渲染并释放 EGL/Vulkan surface | 可用 |
| Native Window | `OHNativeWindow`;`native_window/external_window.h` | `libnative_window.so` | 8 起,部分新 buffer API 23 | XComponent 回调交付 window;不得替换成 Android `ANativeWindow` | 可用 |
| Native Buffer | `OH_NativeBuffer_*`;`native_buffer/native_buffer.h` | `libnative_buffer.so` | 11 起,具体函数不同 | 用于后续互操作;格式、usage、fence 必须真机查询 | 可用,Phase 0 不承诺零拷贝 |
| OpenGL ES | `<GLES2/gl2.h>`、`<GLES3/gl3.h>` | `libGLESv2.so` / `libGLESv3.so` | SDK 基础能力 | 实际 GLES 版本、扩展、10-bit/HDR 格式按设备查询 | 可用 |
| EGL | `eglGetDisplay`、`eglCreateWindowSurface`、`eglSwapBuffers`;`<EGL/egl.h>` | `libEGL.so` | SDK 基础能力 | `EGLNativeWindowType` 在 `OHOS_PLATFORM` 下是 `OHNativeWindow *`;上下文线程归属必须固定 | 可用,首选渲染入口 |
| Vulkan | `VK_OHOS_surface`、`vkCreateSurfaceOHOS`;`<vulkan/vulkan.h>` / `vulkan_ohos.h` | `libvulkan.so` | 10 | Surface 绑定 `OHNativeWindow`;扩展和 format 支持需设备枚举 | 可用,但不是 libmpv 公共 Render API |
| VSync | `OH_NativeVSync_*`;`native_vsync/native_vsync.h` | `libnative_vsync.so` | 9 | 回调与 Surface 生命周期协调 | 可用,首版可先由 EGL swap/MPV 调度 |
| Native Audio | `OH_AudioStreamBuilder_*`、`OH_AudioRenderer_*`;`ohaudio/*.h` | `libohaudio.so` | 10 | builder 生成 renderer 后销毁;renderer 使用写回调拉取 PCM;Start/Pause/Stop/Flush/Release 有明确状态机 | 可用,需新增 AO |
| Native Video Decoder | `OH_VideoDecoder_*`;`multimedia/player_framework/native_avcodec_videodecoder.h` | `libnative_media_vdec.so`、`libnative_media_codecbase.so`、`libnative_media_core.so`、`libnative_window.so` | 9;buffer 新接口 11 | Create→Configure→SetSurface→Prepare→Start;Seek 需要 Flush 后重启;输出可直接 RenderOutputBuffer 到 Surface | 可用 |
| Codec Capability | `OH_AVCodec_GetCapability*`、`OH_AVCapability_*`;`native_avcapability.h` | Native media libs | 10 | 必须在目标设备查询硬件/软件、尺寸、帧率、profile/level;SDK 头不能证明某机型支持 4K/10-bit/HDR | 可用,能力值待真机 |
| POSIX file | `open/read/write/stat`;`fcntl.h`、`unistd.h`、`sys/stat.h` | libc | 基础 | 受应用沙箱与授权 URI 限制;ArkUI picker URI/FD 需桥接到 native | 可用但路径权限需集成层处理 |
| Packaged rawfile | `OH_ResourceManager_*` / `rawfile/*.h` | `librawfile.z.so` | 8 起 | 只适合包内资源,不是通用媒体文件系统 | 可用,不替代 POSIX file |
| pthread | `pthread_create`、mutex/cond;`pthread.h` | libc | 基础 | MPV `threads-posix.c` 可优先复用;线程名函数需 Meson 实测 | 可复用 |
| socket | BSD sockets;`sys/socket.h`、`netinet/in.h` | libc | 基础 | 需要网络权限;移动网络切换、IPv6、代理需真机测试 | 可复用 |
| DNS | `getaddrinfo/freeaddrinfo`;`netdb.h` | libc | 基础 | DNS/网络切换错误传播由 FFmpeg 网络层处理 | 可复用 |
| Native HTTP | `OH_Http_*`;`network/netstack/net_http.h` | `libnet_http.so` | 20 | 与 FFmpeg AVIO 不直接兼容;不作为首版 MPV 网络栈 | API 24 可用但不采用 |
| TLS certificate verification | `OH_NetStack_CertVerification` 等;`network/netstack/net_ssl/net_ssl_c.h` | `libnet_ssl.so` | 11 | 这是证书链验证接口,不是 FFmpeg TLS transport backend;首版需交叉构建 FFmpeg 支持的 TLS 库 | 系统验证能力可用,MPV 路径需第三方 TLS |
| Shared library | `dlopen/dlsym/dlclose`;`dlfcn.h` | `libdl.a` / libc | 基础 | 插件路径和符号可见性受沙箱约束 | 编译可用,运行待测 |
| Timer | `clock_gettime`、`nanosleep`;`time.h` | libc | 基础 | MPV `timer-linux.c` 实际只依赖这两个 POSIX API;源码应改名/条件化以免平台语义错误 | 可复用实现,需整理检测 |
| Memory mapping | `mmap/munmap`;`sys/mman.h` | libc | 基础 | 文件映射权限及大文件压力需真机测试 | 编译可用 |
| poll / epoll | `poll`、`epoll_*`;`poll.h`、`sys/epoll.h` | libc | 基础 | 头文件存在且探针通过;运行语义、FD 类型兼容性仍需测 | 可用,优先 POSIX poll |
| System information | `uname`、`sysconf`、`getauxval`;`sys/utsname.h`、`unistd.h`、`sys/auxv.h`;`OH_GetDeviceType` (`deviceinfo.h`) | libc / `libdeviceinfo_ndk.z.so` | API 10 起(DeviceInfo) | 只用于诊断与保守能力探测,不按设备名硬编码功能 | 可用 |
| CPU detection | AArch64 编译宏、`getauxval`;FFmpeg CPU detection | libc | 基础 | arm64 基线已有 NEON;不得未经实测强开额外 ISA | 可复用 |
| Lifecycle | XComponent Surface callbacks + N-API/ArkTS 页面生命周期桥接 | `libace_ndk.z.so` / `libace_napi.z.so` | 8 | MPV Core 仅接受 surface attach/detach、pause/resume 等播放器级事件;业务生命周期留在宿主 | 需薄桥接 |
| Power | 系统 running lock / 后台任务 API主要由应用层管理 | 系统服务 | 版本依 API | Core 不自行申请长期后台权限;向宿主暴露播放状态,由宿主管理屏幕常亮/后台播放 | 不建 MPV backend |
## 3. 直接可复用的 MPV/POSIX 区域
- `osdep/threads-posix.c`:pthread 基础满足。
- `osdep/path-unix.c`、`osdep/language-posix.c`:大部分 POSIX 路径/locale 逻辑可编译,沙箱路径语义需测试。
- `osdep/timer-linux.c` 的实现仅用 `clock_gettime`/`nanosleep`,能力可复用,但构建选择需改为 `ohos` 明确分支或重命名为 POSIX timer。
- socket、DNS、poll、mmap、dlopen 头文件均存在,先通过 feature detection,不因 `__linux__` 自动宣称全部 Linux 特性可用。
- demux、字幕、滤镜、同步、命令和 libmpv client API 与平台无关,可直接复用。
## 4. 必须新增或调整的边界
1. Meson 增加 `host_machine.system() == 'ohos'` / `features['harmony']`,生成 `HAVE_HARMONY`,防止把 OHOS 的 `__linux__` 等同桌面 Linux。
2. 新增 OHAudio AO,不能复用 Android AAudio/OpenSL ES。
3. 若构建 MPV 自管 VO,新增 Harmony EGL/Vulkan context;首版 Playback Lab 可由宿主持有 EGL context,使用 libmpv OpenGL Render API,减少 upstream patch。
4. 硬解需要 MPV 识别 FFmpeg 的 `AV_PIX_FMT_OHCODEC` 与 `AV_HWDEVICE_TYPE_OHCODEC`;该工作晚于软件解码首帧。
5. 日志可先复用 stderr;实验室集成阶段增加 HiLog sink,不能让日志 backend 阻塞首次构建。
## 5. 未验证项
- 各目标设备具体 Vulkan/GLES 扩展、HDR/10-bit swapchain、codec profile/level、最大分辨率/码率。
- `epoll`、`dlopen`、mmap 大文件在应用沙箱内的运行行为。
- 后台播放、音频焦点、路由切换、蓝牙延迟和 Surface 重建行为。
- Native HTTP/TLS 与系统代理/证书策略;首版按 FFmpeg + 独立 TLS 依赖验证。
+7
View File
@@ -0,0 +1,7 @@
# Harmony 字体策略
第一优先级使用系统字体;应用字体只允许由产品明确提供的开源字体;libass 使用 mpv 已配置的字体 provider,不新增商业字体包。当前 OHOS 字体栈按 `scripts/build-font-stack-ohos.sh` 构建:FreeType + HarfBuzz + FriBidi + libass,libass 明确关闭 fontconfig、DirectWrite、CoreText 和 iconv provider,因此不依赖 Linux `fonts.conf`。
Playback Lab 初始化时将 mpv `sub-fonts-dir` 指向 `/system/fonts`;模拟器已确认该目录存在,`/system/etc/fontconfig` 与 `/system/etc/fonts` 不存在。libass 的 `ass_set_fonts_dir` 会从该目录加载字体,`sub-font-provider=auto` 在当前无系统 provider 时仍可使用目录字体和字幕内嵌字体。中文、英文、日文、韩文的缺字由目录字体顺序、libass fallback 与内嵌字体共同决定;应用若需补充字体,应通过 Resource Manager/应用沙箱提供明确的开源字体目录并在产品层映射到 `sub-fonts-dir`,不修改核心、不提交商业字体。
Playback Lab 只验证字幕渲染和缺字表现,不把字体文件或用户凭证提交到仓库。设备侧若系统字体目录、provider 行为或 fallback 顺序不同,应记录实际字体、脚本和缺字结果。
+64
View File
@@ -0,0 +1,64 @@
# HarmonyOS Hardware Decode Research
## 1. 关键发现
截至 FFmpeg 官方 master `3acec0a1af2dda0a0838689b8b8649e7deb080a0`(2026-08-28),upstream 已包含 OpenHarmony Codec:
- configure:`--enable-ohcodec`
- hw device:`AV_HWDEVICE_TYPE_OHCODEC`
- pixel format:`AV_PIX_FMT_OHCODEC`
- public device context:`AVOHCodecDeviceContext.native_window`
- decoder/encoder:`h264_oh`、`hevc_oh`
- SDK linkage:`native_media_vdec`、`native_media_venc`、`native_media_codecbase`、`native_media_core`、`native_window`
其 surface 路径把 `OHNativeWindow *` 放入 hw device,FFmpeg 配置 `AV_PIXEL_FORMAT_SURFACE_FORMAT`,并在 AVFrame release 时调用 `OH_VideoDecoder_RenderOutputBuffer()`。因此“不存在 FFmpeg Harmony hwcontext”已不是事实;当前缺口主要转移到 MPV 对 OHCODEC 的识别、Surface 所有权和呈现同步。
## 2. Codec 矩阵
| Codec | Harmony Native Support | FFmpeg Support | MPV Support | Surface Decode | Pixel Format | Copy | Zero Copy Feasibility | 10-bit / HDR | Known Limitation | Complexity |
|---|---|---|---|---|---|---|---|---|---|---|
| H.264 | Native decoder API 存在;具体 profile/4K 由设备 capability 查询 | `h264_oh` 已在 upstream master | 无 OHCODEC 映射/backend | 已实现 | `AV_PIX_FMT_OHCODEC` / surface format | 解码器到 Surface 可避免 CPU frame;是否经过系统内部 copy 不可见 | 高,但直出播放 Surface 与 MPV shader/字幕合成冲突 | 设备查询;H.264 10-bit 通常不作为首要目标,不能猜 | FFmpeg 实现新、MPV 未接入、字幕/OSD 合成路径未解决 | 高 |
| HEVC | Native decoder API 存在;profile/level/10-bit 待设备查询 | `hevc_oh` 已在 upstream master | 无 OHCODEC 映射/backend | 已实现 | 同上 | 同上 | 高 | Main10/HDR 必须按真机 capability + 实际流验证 | HDR metadata 传递和 Surface colorspace 未证明 | 高 |
| AV1 | SDK API 可按 MIME 查询,不代表设备必有硬件 | 当前 OHCodec backend 未声明 `av1_oh` | 无 | FFmpeg OH 路径无 | 无 | 未定 | 未定 | 未定 | upstream FFmpeg 尚未接 OH AV1 | 很高 |
| VP9 | SDK API可查询,不代表设备必有硬件 | 当前 OHCodec backend 未声明 `vp9_oh` | 无 | FFmpeg OH 路径无 | 无 | 未定 | 未定 | 未定 | upstream FFmpeg 尚未接 OH VP9 | 很高 |
## 3. MPV 缺口
1. `video/fmt-conversion.c` 没有 `IMGFMT_OHCODEC <-> AV_PIX_FMT_OHCODEC`。
2. `video/decode/vd_lavc.c` 尚无 OHCODEC device/backend 选择。
3. GPU interop driver 列表没有 Harmony/OHNativeBuffer 路径。
4. libmpv OpenGL Render API的目标 Surface与 OHCodec 直接输出 Surface存在所有权冲突:同一个播放画面若让 decoder 直接呈现,MPV 的 shader、OSD、ASS 字幕、色彩管理无法自然叠加。
5. FFmpeg 当前 OHCodec surface frame 在 AVFrame release 时触发呈现,呈现时钟、drop/late frame 与 MPV VO 调度需要逐帧语义验证。
## 4. 推荐路线
### Stage 1:软件解码基线
先以 FFmpeg software decode + libmpv GLES Render API 跑通首帧、Seek、字幕、网络和稳定性。该基线用于证明其后优化收益。
### Stage 2:FFmpeg OHCodec 可用性验证
独立验证官方 `h264_oh` / `hevc_oh`:创建 decoder、Flush/Seek、Surface destroy/recreate、4K、高码率、Main10;记录设备 capability。此阶段不修改 MPV 大量代码。
### Stage 3:选择合成架构
- 路线 3A,直接 Surface decode:最快接通、copy 少,但 MPV GPU shader/ASS/OSD 不能直接叠加。只适合验证硬解吞吐,不直接定为正式架构。
- 路线 3B,OHNativeBuffer/GPU import:decoder 输出可导入 GLES/Vulkan,再由 MPV/libplacebo 合成。最符合最终产品,但 FFmpeg 当前 OHCodec API是否暴露可导入 buffer 尚需源码和真机证明。
- 路线 3C,双 Surface/system overlay:视频与字幕分层,由系统合成。同步、HDR、截图和滤镜语义复杂,仅作备选。
正式路线优先研究 3B;在其不可行证据充分前,不宣称零拷贝。
## 5. Patch 边界
如进入实现,优先局限在:
- `video/fmt-conversion.c` / image format 定义
- `video/decode/vd_lavc.c` 的 hw device 选择
- `video/out/hwdec/hwdec_ohcodec.c` 或明确命名的 Harmony interop driver
- `meson.build` Harmony feature gate
任何不支持格式明确返回 unsupported;不得以 CPU 黑帧、fake success 或注释掉检查伪装完成。
## 6. 当前判定
**不创建 Velix-FFmpeg-Harmony。** 官方 FFmpeg 已有关键 OHCodec 基础,Phase 1 应固定官方 commit 并先编译;只有真实 blocker 证明需要 patch 且无法快速 upstream 后,才建立独立 FFmpeg fork。AV1、VP9、buffer import、HDR metadata 仍为 `NEED VERIFY`。
+74
View File
@@ -0,0 +1,74 @@
# HarmonyOS Rendering Research
## 1. 结论
第一阶段推荐 **OpenGL ES + EGL + libmpv OpenGL Render API**:ArkUI XComponent 仅交付 `OHNativeWindow *`,Native 层创建 EGL window surface/context,并在 GL 线程调用 `mpv_render_context_render()` 渲染到默认 FBO,随后 `eglSwapBuffers()`。视频帧不经过 ArkTS/N-API。
```text
XComponent Surface
-> OHNativeWindow
-> EGLSurface + GLES context(Native 渲染线程)
-> libmpv OpenGL Render API
-> GPU render -> eglSwapBuffers -> Display
```
依据:本机 API 24 SDK 明确把 `EGLNativeWindowType` 定义为 `struct NativeWindow *`;MPV 公共 Render API 当前只公开 `MPV_RENDER_API_TYPE_OPENGL` 与软件渲染,未公开 Vulkan Render API。
## 2. 方案 A:OpenGL ES / EGL
### 调用边界
1. `OH_NativeXComponent_RegisterCallback()` 接收 Surface 创建/变化/销毁。
2. `OnSurfaceCreated` 的 `void *window` 按 SDK 契约转换为 `OHNativeWindow *`。
3. Native GL 线程执行 `eglGetDisplay`、`eglInitialize`、`eglChooseConfig`、`eglCreateWindowSurface`、`eglCreateContext`。
4. 用 `MPV_RENDER_PARAM_API_TYPE=MPV_RENDER_API_TYPE_OPENGL` 和 `mpv_opengl_init_params` 创建 render context。
5. update callback 只唤醒渲染线程;该线程 make-current,调用 `mpv_render_context_render()`,目标为默认 FBO 0 和当前 Surface 尺寸,再 swap。
6. Surface changed 更新 viewport/FBO 尺寸;Surface destroyed 先停止提交帧,再释放 render context 的 surface 相关状态和 EGLSurface。
### 评估
| 维度 | 结论 |
|---|---|
| 实现复杂度 | 中;MPV 已有完整 OpenGL Render API,Harmony 只负责 EGL/XComponent 生命周期 |
| MPV 支持度 | 高;公共 API 稳定,`include/mpv/render.h`、`render_gl.h` 为直接依据 |
| HarmonyOS 支持度 | 高;API 24 提供 EGL、GLES2/3、OHNativeWindow |
| Surface 生命周期 | 必须单线程串行化 create/change/destroy,销毁后禁止 swap |
| Copy 次数 | 软件解码时 FFmpeg frame 上传 GPU,至少一次 CPU→GPU;无逐帧 N-API copy |
| 4K | 渲染路径可行,性能取决于上传、shader、带宽;需 Benchmark |
| 10-bit | 软件帧可由 MPV shader 处理;Surface 色深/输出格式待设备 EGLConfig 验证 |
| HDR | MPV 色彩处理可复用,但 Harmony Surface 色域、transfer、HDR metadata 通路尚未证明 |
| 风险 | EGL context 线程归属、Surface 快速重建、默认 FBO 方向、vsync/帧节奏 |
### MPV 修改范围
- Phase 1 构建 `libmpv` 时启用 `plain-gl`,无需先新增完整 `vo_harmony`。
- Playback Lab 的 Harmony glue 独立放在后续 `playback_lab/native/`,不把 ArkTS 业务代码放进 MPV core。
- 只有确认必须让 MPV 自管 window/context 时,才新增 `video/out/opengl/context_harmony.c` 并在 Meson 中以 `features['harmony']` 注册。
## 3. 方案 B:Vulkan
### 可行基础
API 24 提供 `VK_OHOS_surface`、`VkSurfaceCreateInfoOHOS`、`vkCreateSurfaceOHOS()` 与 `libvulkan.so`,可从 `OHNativeWindow *` 创建 `VkSurfaceKHR`。MPV 内部 `vo_gpu_next` / libplacebo 支持 Vulkan,但 libmpv 公共 Render API 没有 Vulkan 类型。
| 维度 | 结论 |
|---|---|
| 实现复杂度 | 高;需要新增/扩展 MPV Vulkan context 或设计受支持的嵌入路径 |
| MPV 支持度 | 内部高、公共嵌入 API 低 |
| HarmonyOS 支持度 | API 10+ 有 surface 扩展,设备扩展需枚举 |
| Surface 生命周期 | 需重建 swapchain,处理 out-of-date/suboptimal、旋转与尺寸变化 |
| Copy 次数 | 软件解码仍需上传;未来 native buffer/Vulkan interop 有降 copy 潜力 |
| 4K | 理论适合,未实测不能优于 GLES |
| 10-bit/HDR | Vulkan format/colorspace 路径潜力更强,但 Harmony 设备与系统合成支持待证 |
| 风险 | libplacebo/Vulkan 版本、OHOS surface 接入、swapchain、同步/fence、驱动差异 |
## 4. 禁止路径
`MPV -> CPU frame -> Native Buffer -> ArkTS -> ArkUI Image` 不进入正式架构。它会增加逐帧跨 N-API、CPU copy、GC/调度与背压风险,也无法作为 4K 主路径。
## 5. Phase 门禁
- Phase 1:只证明 `libmpv` arm64 编译,OpenGL Render API 符号存在。
- Phase 2:Playback Lab 证明 XComponent→EGL→首帧;记录 Surface 重建和旋转。
- Phase 3:软件解码基线后再接 OHCodec;分别测 CPU upload 与 surface decode。
- Vulkan 只有在 GLES 基线与瓶颈证据齐备后启动,不以“理论更快”替代 Benchmark。
+7
View File
@@ -0,0 +1,7 @@
# OHCodec HDR pipeline notes
本阶段只保留 metadata 研究,不声明 HDR 完成。
FFmpeg upstream OHCodec stream-change 路径会把存在的 matrix、primaries、transfer、range 和 SAR 元数据复制到 `AVCodecContext`;MPV 现有 color-management pipeline 继续消费这些 metadata。设备是否输出 10-bit、支持 Main10 或 HDR surface,必须由 API24 capability 和真实设备 frame metadata 证明。
当前实现不添加 HDR transfer、tone mapping、Dolby Vision 或 10-bit surface 特判。Playback Lab 的 capability/diagnostics 只显示真实查询结果;没有设备数据时显示 `DEVICE TEST REQUIRED`,不得显示 `HDR YES`。
+79
View File
@@ -0,0 +1,79 @@
# HarmonyOS First Native Build Report
## Result
Phase 1 已在 DevEco Studio OpenHarmony SDK 6.1.1.125 / API 24 下生成真实 arm64 `libmpv`。本报告只证明编译、链接和静态检查,不声明真机运行语义已验证。
| 项目 | 结果 |
|---|---|
| Origin | `https://git.leee.xin/leeexin/Velix-MPV-Core.git` |
| origin/harmony-port | 以本次 Phase 1 提交推送后的 commit 为准 |
| MPV | `182fa6ca49f455cadb884858f386e2f00540aeb7` / 0.41.0-UNKNOWN |
| FFmpeg | `3acec0a1af2dda0a0838689b8b8649e7deb080a0` |
| libmpv | `build/ohos-arm64/prefix/lib/libmpv.so.2.5.0` |
| Size | 34,314,184 bytes |
| SHA-256 | `80e4c6fda86d9822543615a97045fff3641e9125b23a4f736fb8511fe5a47790` |
| Architecture / ABI | ELF64, little-endian, AArch64, System V ABI, shared object, SONAME `libmpv.so.2` |
| Toolchain | OHOS Clang/LLD 15.0.4, target `aarch64-linux-ohos`, musl sysroot |
| MPV / build files | 7:`ao_harmony.c/.h`、AO 注册、Meson feature、OHOS build script、依赖 allowlist;第三方依赖源码修改 0 |
| Build issues | 4,全部关闭 |
## Features
MPV enabled:`clone`、`ffmpeg`、`gl`、`glibc-thread-name`、`glob`、`glob-posix`、`gpl`、`harmony-audio`、`libass`、`libdl`、`libplacebo`、`linux-fstatfs`、`memrchr`、`ohos`、`posix`、`posix-shm`、`ppoll`、`pthread-condattr-setclock`、`vector`、`vt.h`、`zlib`、`libmpv shared`、`plain-gl`。
FFmpeg enabled:软件解码、demux、network、HTTP/HTTPS/TCP/TLS、OpenSSL 3.6.4、SDK zlib、OHCodec H.264/HEVC decoder/encoder。FFmpeg GPL/version3/nonfree 均为 0。
Disabled:mpv CLI、tests、fuzzers、Lua/JavaScript、libcurl、桌面音视频后端、X11、Wayland、DRM/GBM、Cocoa、Windows backend、Android-specific backend、Vulkan、VAAPI、VDPAU、CUDA。libplacebo 的 Vulkan/OpenGL 自有 backend 关闭;MPV 自身 OpenGL Render API 保留。
## Verification
- 最小 C/C++ 探针均为 OHOS AArch64 ELF;C++ 探针依赖 SDK `libc++_shared.so`。
- prefix 内 13 个静态库的全部可见 archive object 均通过 AArch64 ELF 检查。
- `libmpv.so.2.5.0` 动态依赖为 `libc.so`、`libz.so`、`libnative_media_vdec.so`、`libnative_media_venc.so`、`libnative_media_codecbase.so`、`libnative_media_core.so`、`libnative_window.so`、`libohaudio.so`;均来自 HarmonyOS SDK allowlist。
- target metadata 未发现 Homebrew、`/usr/local`、`.dylib`、Apple framework 或 Mach-O 污染。
- 已验证 `mpv_create`、`mpv_initialize`、`mpv_command`、`mpv_set_property`、`mpv_get_property`、`mpv_render_context_create`、`mpv_render_context_render`、`mpv_render_context_set_update_callback`、`mpv_render_context_free`。
- `scripts/build-all-ohos.sh` 已完成本次 Phase 3 全链路构建与最终校验(`verified_archives=13`)。
## Warnings
剩余 18 条构建 warning:FFmpeg 5 条 upstream fallthrough/unused-function;FriBidi 5 条 enum-conversion;HarfBuzz 1 条仅开发者需要 Ragel;libass/MPV 4 条非阻塞 CMake compiler-state 探测;libplacebo 3 条无 graphics backend/name-prefix 提示。均不影响当前 target 产物,未通过关闭警告掩盖问题。
## Dependency Graph
实际依赖图见 `DOCS/PHASE1_DEPENDENCY_MATRIX.md`。所有第三方库均静态进入 `libmpv`,SDK zlib、OHCodec 与 libc 保持系统动态依赖。
## License
仓库与 `libmpv` 当前按 MPV GPL-2.0-or-later 路线管理。FFmpeg 为 LGPL 配置且未启用 GPL/nonfree;OpenSSL 为 Apache-2.0;静态 LGPL 组件仍需在正式发布时履行可重新链接、源码与 notices 义务。本文件不是最终发行 notice。
## Phase 2 Risks
1. POSIX、DNS、HTTP/HTTPS 与动态库加载目前仅 compile/link verified,尚未真机 runtime verified。
2. XComponent / OHNativeWindow 生命周期、线程归属、窗口销毁与 EGL context loss 尚未验证。
3. MPV plain-gl 与 HarmonyOS EGL/GLES 的实际格式、扩展、同步和首帧路径尚未联调。
4. FFmpeg OHCodec 已编译,但 MPV hwdec 选择、surface/output buffer 生命周期及零拷贝均未验证。
5. OpenSSL CA 路径与 HAP 内依赖打包、SDK系统库版本兼容仍需真机验证。
## Phase 2 Entry Condition
已具备 XComponent + OHNativeWindow + EGL + libmpv OpenGL Render API 真机首帧测试的编译侧条件:SDK API 可用、libmpv 可链接、OpenGL Render API symbols 齐全。尚不代表首帧已经成功;Phase 2 必须在真机建立窗口和 EGL context 后验证。
## FFmpeg-Harmony export prefix
Phase 4.5 的 FFmpeg fork 独立维护于
`https://git.leee.xin/leeexin/Velix-FFmpeg-Harmony.git`,分支
`harmony-ohcodec-export`,当前分支 tip `f3a036c695`(跨 origin 重定向丢弃敏感凭证;代码修正 commit
`aacbe7d578`)。MPV 不包含 FFmpeg
源码,也不读取其私有头文件;需要切换时执行:
```sh
./scripts/build-mpv-ohos-ffmpeg-harmony.sh
```
脚本默认消费 `../Velix-FFmpeg-Harmony/build/ohos-arm64/prefix`,也可用
`FFMPEG_PREFIX=/path/to/prefix MPV_BUILD_ROOT=/path/to/mpv-build MPV_PREFIX=/path/to/mpv-prefix` 覆盖。切换脚本默认把 MPV 安装到
`build/ohos-arm64/prefix-mpv-ffmpeg-harmony`,避免覆盖默认 prefix。默认
`build-mpv-ohos.sh` 与 `build-mpv-ohos-software.sh` 的原有前缀和
`harmony-ohcodec-gl=disabled` 行为不变。本次切换构建已通过 AArch64 ELF、符号
和动态依赖校验,但没有进入 GPU mapper 或设备硬解验证。
+20
View File
@@ -0,0 +1,20 @@
# MPV 与 OHCodec 缺口分析
扫描基线:MPV 当前 `harmony-port` 工作树,FFmpeg OHCodec 头文件来自锁定 commit `3acec0a1af2dda0a0838689b8b8649e7deb080a0`。
| MPV location | 当前行为 | OHCodec requirement | 本阶段改动/结论 | 风险与 upstreamability |
|---|---|---|---|---|
| `video/img_format.h` / `video/fmt-conversion.c` | 只有通用 hardware format,没有 OHCodec 专用 ID。 | `AV_PIX_FMT_OHCODEC` 必须成为 MPV hardware image format。 | 新增 `IMGFMT_OHCODEC ↔ AV_PIX_FMT_OHCODEC` 单一映射。 | 纯 MPV 架构改动,可 upstream;依赖 FFmpeg enum。 |
| `video/decode/vd_lavc.c:add_all_hwdec_methods()` | 动态枚举 FFmpeg HW config,并使用 `av_hwdevice_get_type_name()` 生成 method。 | 能识别 `AV_HWDEVICE_TYPE_OHCODEC`、`h264_oh`、`hevc_oh`。 | 不改 decoder core;FFmpeg config 会自然生成 `hwdec=ohcodec`,且仅 H.264/HEVC 由 adapter 放行。 | upstreamable;真实 capability 仍需设备验证。 |
| `video/hwdec.c:hwcontext_fns[]` | 未注册 OHCodec copy-device callback;未知类型会尝试空 device。 | copy path 必须不能伪造 null-surface 成功。 | 注册 `hwcontext_fns_ohcodec`,明确拒绝无独占窗口的 copy path,并触发 software fallback。 | 纯 MPV 保护逻辑。 |
| `video/out/gpu/hwdec.c` | 只注册已有 GPU interop/overlay drivers。 | Direct Surface 需要独立 backend,不能复用 MediaCodec/DRM。 | 注册 `ohcodec-direct-surface-experimental`,仅在 host 提供 `ohos_direct_surface` native resource 时创建设备。 | 当前 Playback Lab 不提供该 resource,故默认不会抢占 EGL Surface。 |
| `video/out/gpu/video.c` / `gpu_next` | mapper 或 overlay 二选一;overlay 会绕过视频 GL 合成但保留 OSD。 | Direct Surface 不能与同一 EGL window 双重呈现。 | OHCodec 只使用 overlay probe;没有独占窗口时 init 失败。 | Direct Surface 不是最终 ASS/shader/color-management 产品路径。 |
| `video/mp_image.c:mp_image_from_av_frame()` | 复制 AVFrame buffers/data;无 OHCodec 特判。 | frame 需要保留 `buf[0]` 生命周期和硬件格式。 | 现有 generic path 已满足:`data[3]` 作为 opaque plane,`buf[0]` 持有 release。 | 不能把 opaque plane 当 CPU pixels 或 fd。 |
| `video/out/gpu/ra.h` native resources | 可由 host 注入平台资源。 | Surface ownership 需要明确边界。 | 约定 `ohos_direct_surface` 为独占、已由 host 管理的 `OHNativeWindow*`。 | 当前没有 host 注入,设备验证前不能宣称 direct active。 |
| Diagnostics / Playback Lab | 原来只显示 software audio/video 状态。 | 必须显示 requested/actual decoder、hw state、fallback/capability。 | 新增导出统计 API、日志识别和 Playback Lab 面板字段。 | actual decoder 在设备上需以 FFmpeg 日志和真实 frame 验证。 |
## 选择路径
`hwdec=ohcodec` → FFmpeg OHCodec method → MPV 请求 `IMGFMT_OHCODEC` → GPU backend 请求 OHCodec device → adapter 查询 capability → 检查独占 Surface → 创建 `AVHWDeviceContext` → Direct Surface overlay。
任何一步失败均返回负值并保留 software decoder。GPU interop 不走猜测性的 opaque-pointer reinterpretation。
+13
View File
@@ -0,0 +1,13 @@
# 网络播放
网络播放复用 mpv/FFmpeg 的单一网络栈,不在产品中创建第二 HTTP client。支持范围是 mpv 已提供的 HTTP、HTTPS、HLS、重定向、Range seek、chunked 和大范围 seek 能力。
每次 load 先清空 http-header-fields,再设置本次请求的 headers;Cookie 作为本次请求的 Cookie header 发送,未启用 mpv 持久化 cookie 文件。请求可设置 user-agent、referrer、network-timeout、语言偏好和轨道选择。
网络诊断只报告实际可取得的 mpv 属性:协议、host、networkState、cache duration/bytes、demuxer、container、当前 bitrate,以及从实际日志识别到的 HTTP status/redirect count;`rangeSupported` 仅在观察到 206 响应时为 1,`seekResult` 记录最近一次 seek 的 `ok` 或脱敏错误。没有对应属性或日志时返回 -1/0/空值,不做推断。
HTTP MP4 已在 Playback Lab 模拟器通过宿主受控 Range server 验证:请求日志出现 `bytes=0-`、后续大范围 byte range,播放结束且无核心错误。跨 origin 重定向的敏感头过滤由 FFmpeg-Harmony 固定分支提供,实际 HTTPS/HLS/鉴权资源仍需受控网络源继续验收。
HLS Master/Variant 的当前媒体分辨率来自 video-params/width/height,码率来自 demux-bitrate;当前 MPV 没有独立稳定的“variant 名称”属性,因此不新增伪造字段。
DNS、TCP connect、connection reset、timeout 和 FFmpeg open failure 统一归入 NETWORK,并保留脱敏原始消息;TLS handshake/certificate 归入 TLS,HTTP status 归入 HTTP,demux/decoder 错误不伪装成网络错误。
+15
View File
@@ -0,0 +1,15 @@
# 网络安全
## Header 与会话隔离
每次 load 都覆盖 http-header-fields,不共享上一个 session 的 header、Cookie 或 URL。禁止把认证信息写入仓库、默认日志或诊断快照。Playback Lab 输入框可以编辑 header,但诊断默认只显示字段名和掩码值。
## 脱敏规则
Authorization、Proxy-Authorization、Cookie、X-Api-Key、X-Emby-Token、X-Auth-Token、X-Access-Token 及 token/api-key/signature 等认证头,以及 URL 中的 token、access_token、auth、key、api_key、apikey、auth_token、signature、x-amz-signature、sig、expires 参数在错误文本和日志中替换为 [REDACTED]。原始 mpv 日志不得直接写入 OH 日志。
## TLS、重定向与凭证
默认保持 tls-verify=yes。mpv 网络后端负责 TLS、重定向、Range 和 HLS;本层不复制 HTTP 行为。固定的 FFmpeg-Harmony 分支 `f3a036c695` 已在 `libavformat/http.c` 实现跨 origin(协议/host/port 任一变化)重定向时丢弃自定义 Authorization/Cookie/Token/API-key/Referer 等敏感头及 cookie 状态,同源重定向仍保留请求头;因此不会把凭证无条件转发到不同 host。Referer、User-Agent、header 仅属于当前加载请求。
实现依据:mpv 官方手册的 user-agent、referrer、cookies、http-header-fields、network-timeout、HLS 与 sub-add 选项说明。
+34
View File
@@ -0,0 +1,34 @@
# OHCodec GPU interop 设计与当前 blocker
## 目标
将 OHCodec hardware frame 交给 MPV OpenGL composition,同时保留 ASS、OSD、shader 和 color management。该路径必须是显式实验开关 `harmony-ohcodec-gl`,不能成为默认 hwdec。
## API24 合法链路(仅设计验证)
SDK 头文件提供:
1. `OH_AVBuffer_GetNativeBuffer(OH_AVBuffer*)`,返回一个需要 `OH_NativeBuffer_Unreference()` 的 `OH_NativeBuffer*`。
2. `OH_NativeWindow_CreateNativeWindowBufferFromNativeBuffer()`,把 NativeBuffer 转成 `OHNativeWindowBuffer*`,并要求随后销毁 wrapper。
3. `EGL_OHOS_image_native_buffer` / `EGL_NATIVE_BUFFER_OHOS`、`eglCreateImageKHR()` 和 `glEGLImageTargetTexture2DOES()`。
这证明 API 类型是 HarmonyOS 原生接口,不应使用 Android `AHardwareBuffer`、fd 猜测或指针重解释。未来 mapper 的资源生命周期应为:
`OH_AVBuffer` frame ref → `OH_NativeBuffer_Get/Reference` → `OHNativeWindowBuffer` wrapper → EGLImage → external texture → unmap 时反向销毁。
## OHCODEC-BLOCKER-001
当前 upstream `libavcodec/ohdec.c` 只有两种输出:
- 有 OHCodec device:强制 `output_to_window=true`、配置 `AV_PIXEL_FORMAT_SURFACE_FORMAT`、`SetSurface(window)`;
- 无 device:CPU NV12 buffer,复制到 software frame。
hardware frame 的 `OH_AVBuffer*` 只存在于带 Surface 的分支,且自定义 `AVBuffer` release 会调用 `OH_VideoDecoder_RenderOutputBuffer()`。因此 MPV 不能在不修改 FFmpeg 的前提下请求“NativeBuffer hardware output but no decoder Surface present”模式。若 mapper 同时导入该 buffer 到 EGL,decoder release 仍可能向另一 Surface present,导致两个 owner、未定义 fence/queue ownership 和 seek/recreate race。
结论:本阶段不提交伪 interop mapper,不调用 `eglFinish/glFinish` 充当同步,不宣称零拷贝/GPU interop/HDR 完成。`harmony-ohcodec-gl` 作为 disabled-by-default feature 保留 API 探针入口;启用它前必须先解决该 upstream contract blocker,并补齐 output buffer、fence、crop/stride 和 surface ownership 的真实设备验证。
## Direct Surface ownership
`ohos_direct_surface` 是 adapter 专用 native resource。只有 host 明确注入一个不等于 Playback Lab EGL window 的独占窗口时,`ohcodec-direct-surface-experimental` 才会取得引用并创建设备;当前 Playback Lab 不注入此 resource,所以 decoder 不会和 EGL/libmpv 抢同一 Surface。
Surface destroy/recreate 必须停止 render/decoder、等待所有 AVFrame release、递增 generation,再以新窗口重新创建 OHCodec device。当前只完成 MPV adapter 侧的 ownership guard,实体设备上的 fence、present count、surface recreate 仍为 `DEVICE TEST REQUIRED`。
+42
View File
@@ -0,0 +1,42 @@
# MPV OHCodec 集成说明
## 已实现路径
`hwdec=ohcodec` 由 MPV/FFmpeg 的动态 hardware-decoder 枚举进入独立
`AV_HWDEVICE_TYPE_OHCODEC` backend。`IMGFMT_OHCODEC` 与
`AV_PIX_FMT_OHCODEC` 使用单一映射。当前 adapter 只允许 H.264 和 HEVC,先
查询 API 24 OHCodec hardware capability,再检查 host 注入的独占
`ohos_direct_surface`,最后创建带 `OHNativeWindow*` 的 FFmpeg hwdevice。
Direct Surface backend 的名称是
`ohcodec-direct-surface-experimental`。它只负责证明 device/decoder/surface
plumbing,不与 Playback Lab 的 EGL window 共享呈现权;没有独占窗口时明确失败,
MPV 保留 software decoder fallback。
## 状态与回退
适配器记录 `HWDEC_DISABLED`、`HWDEC_REQUESTED`、`HWDEC_CAPABILITY_CHECK`、
`HWDEC_DEVICE_CREATED`、`HWDEC_DECODER_CREATED`、`HWDEC_DIRECT_SURFACE`、
`HWDEC_GPU_INTEROP`、`HWDEC_ACTIVE`、`HWDEC_FALLBACK`、`HWDEC_ERROR`。
Playback Lab 通过 libmpv 诊断 API 与属性显示 requested/actual decoder、active
状态、backend、capability、fallback reason 及 hardware/software frame 计数。
capability 查询失败、codec/profile/level 不支持、surface 不可用、device/decoder
创建失败或后续 interop 失败都必须返回失败并回到 software path;不把空 device、
opaque pointer 或 CPU readback 当成成功的硬件帧。
## Surface 生命周期约束
`ohos_direct_surface` 的引用由 adapter 持有,host 负责提供一个不等于
Playback Lab EGL window 的独占窗口。Surface 销毁或重建时,host 必须先停止
decoder/render、等待 AVFrame release,再销毁旧 device 并用新窗口重建;当前代
码只完成 ownership guard,generation/fence/present 的实体设备验证仍待做。
## GPU interop 边界
`harmony-ohcodec-gl` 默认关闭,仅编译 API probe:API24 可探测
`OH_AVBuffer_GetNativeBuffer`、NativeWindow wrapper、EGL native-buffer image
和 GLES external texture 扩展。但 FFmpeg upstream 的硬件分支是 surface-first,
没有“不 present 到 decoder Surface、直接导出可导入 NativeBuffer”的契约;见
`DOCS/OHCODEC_GPU_INTEROP_DESIGN.md` 中的 `OHCODEC-BLOCKER-001`。因此当前不
提交 mapper、不宣称 zero-copy,也不宣称 HDR。
+28
View File
@@ -0,0 +1,28 @@
# FFmpeg OHCodec upstream analysis
基线:FFmpeg `3acec0a1af2dda0a0838689b8b8649e7deb080a0`。本阶段没有修改 FFmpeg。
## 已核对的源码事实
| 位置 | 事实 | 对 MPV 的结论 |
|---|---|---|
| `libavutil/hwcontext.h` | 注册 `AV_HWDEVICE_TYPE_OHCODEC`。 | 设备类型可作为独立 MPV hwdec method,不能伪装成 MediaCodec/VAAPI。 |
| `libavutil/pixfmt.h` | 注册 `AV_PIX_FMT_OHCODEC`,并标记为 OpenHarmony hardware decoding。 | MPV 需要自己的 `IMGFMT_OHCODEC` 映射。 |
| `libavutil/hwcontext_oh.h` | `AVOHCodecDeviceContext` 只有 `void *native_window`,注释为 `OHNativeWindow`。 | FFmpeg 公共设备上下文只承载 Surface 指针,没有 device handle、fence 或 NativeBuffer export 字段。 |
| `libavutil/hwcontext_oh.c:27-45` | `oh_device_create()` 只拒绝非空 device string;初始化不创建窗口、不取得引用,也不做 capability 检查。 | MPV 不能把 `av_hwdevice_ctx_create(..., NULL, ...)` 当成可呈现设备;Surface ownership 必须在 MPV/host adapter 中完成。 |
| `libavcodec/ohdec.c:150-197` | OHCodec device 被发现后读取 `native_window`,设置 `output_to_window=true`,使用 `AV_PIXEL_FORMAT_SURFACE_FORMAT`,并调用 `OH_VideoDecoder_SetSurface(window)`。 | upstream 的硬件输出是 Direct Surface 语义。空窗口不是合法的 GPU interop 设备。 |
| `libavcodec/ohdec.c:219-289` | stream change 读取 width/height/stride/slice height/pixel format;Surface 输出时设置 `avctx->pix_fmt=AV_PIX_FMT_OHCODEC`,并复制颜色元数据与 SAR。 | 尺寸/stride/pixel format 可进入 MPV diagnostics;不能由 enum 推导设备能力或 HDR 支持。 |
| `libavcodec/ohdec.c:432-451` | AVFrame buffer release 时读取 buffer attr;非 discard 调 `OH_VideoDecoder_RenderOutputBuffer()`,否则 `FreeOutputBuffer()`;随后释放 decoder ref。 | AVFrame 生命周期会触发 Surface present/release。Surface 的 decoder ownership 必须持续到最后一个 frame 引用释放。 |
| `libavcodec/ohdec.c:453-486` | hardware frame 的 `format=AV_PIX_FMT_OHCODEC`;`buf[0]` 持有 `OH_AVBuffer*` 的自定义 release;`data[3]=buf[0]->data`。 | 当前 FFmpeg 没有 `AVHWFramesContext`、transfer/map/export 回调。`data[3]` 是 upstream 当前内部布局,不应被重解释成 fd/Android hardware buffer。 |
| `libavcodec/ohdec.c:489-532` | 非 Surface 分支才取得 CPU 地址并复制到 software frame。 | 当前 upstream 没有“NativeBuffer output、但不直接 present Surface”的公共 decoder 配置路径。 |
| `libavcodec/ohdec.c` decoder close/flush | decoder 可能因硬件 frame 引用延迟释放;flush 会清空队列并重启 decoder。 | seek/flush 必须让 MPV 丢弃旧 generation 的 frame;Surface recreate 需要完整 decoder recreate。 |
## 关键问题结论
1. `AV_PIX_FMT_OHCODEC` frame 确实携带 `OH_AVBuffer*` 指针(`data[3]`),但 upstream 没有把它作为稳定的 MPV interop contract 暴露,也没有 non-surface output 模式。
2. API24 SDK 的 `OH_AVBuffer_GetNativeBuffer()` 可以合法取得 `OH_NativeBuffer*`,但要导入 EGL 还需要转换为 `OHNativeWindowBuffer*`;FFmpeg 当前仍会在 frame release 时调用 `RenderOutputBuffer`,因此不能安全地同时让 decoder Surface 和 libmpv EGL surface 认为自己拥有呈现权。
3. 因此本阶段在 MPV adapter 层实现了独立的 `ohcodec-direct-surface-experimental` 探针;GPU interop 保留 feature gate 与设计文档,明确记录 upstream blocker,不修改 FFmpeg。
## 变更边界
本仓库只新增 MPV image-format mapping、OHCodec hwdevice/Surface adapter、capability/fallback diagnostics 和 Playback Lab diagnostics。没有复制或修改 FFmpeg 源码。
+18
View File
@@ -0,0 +1,18 @@
# Phase 6 性能改动与证据
## 已采用
1. **事件驱动渲染更新**:`render.cpp` 不再每 16ms 无条件调用 `mpv_render_context_update`,只在 mpv callback/窗口事件唤醒时更新;为避免个别会话漏回调,最多每 250ms 做一次安全探测。5 次 startup 的 update 累计从优化前 617 降到最终实现 364,且 20 次 startup 未复现永久 BUFFERING。
2. **核心快照合并**:`PlaybackEngine` 对 100ms 内的重复 `get_snapshot/get_tracks/get_media_info/get_network_info` 做合并,动作仍立即提交到 MPV,状态最多延迟一个合并窗口。200 次 play/pause 末次面板显示 `coreSyncSkipped=239`;无 error,状态为 PAUSED。
3. **NAPI 诊断轮询降频**:Playback Lab 面板由 250ms 调整为 500ms;播放控制、音频 callback 和渲染线程不依赖该轮询。这样减少纯 UI 诊断序列化,不改变 MPV 时钟。旧/新间隔在 `Index.ets` 可审计,运行中由 `getStateCalls`、`serializeUsTotal` 记录。
4. **session id 原子读取**:渲染线程不再为每次唤醒获取 `PlaybackEngine` 快照互斥锁,session 更新仍由核心快照写入 release/acquire atomic;首帧 session 隔离 smoke 和 stress 均通过。
## 未采用
- 不做主动丢帧、降低音频采样率或缩小视频分辨率;这些会改变播放质量。
- 不做 GPU mapper、OHCodec 直出或 FFmpeg-Harmony 源码改动;实体设备 capability 和 fence 证据不足。
- 不把 PSS、heap、CPU permille、2 小时长跑结果伪装成已测数据。
## 回归与开销
默认构建、软件构建和 FFmpeg-Harmony 构建均通过。默认 HAP 在模拟器上完成 startup/seek/audio 状态面板、load/stop 100 次和 play/pause 100 次。release/profile 构建未启用持续 tracing;诊断仅累加轻量计数和受控序列化时间。
+20
View File
@@ -0,0 +1,20 @@
# Phase 6 性能指标
所有时间均为单调时钟相对当前 load session 的毫秒数。`-1` 表示该事件没有发生或无法可靠观察;runner 会把负值转换为 `null`,不参与百分位统计。
| 类别 | 字段/来源 | 语义 | 模拟器可用性 |
| --- | --- | --- | --- |
| 启动 | `timeline.startupTotalMs` / `firstSwapMs` | load 到首个成功 EGL swap | 可用 |
| 启动分段 | create、initialize、load、START_FILE、FILE_LOADED、decoder reconfig、render context | 各阶段首次事件 | 可用,缺失阶段为 -1 |
| 网络 | `firstByteMs`、`networkWaitMs`、HTTP status、206/range | 只记录观察到的请求/日志证据 | HTTP fixture 可用,HTTPS/HLS pending |
| seek | `seekCommandLatencyMs`、`seekRecoveryMs`、`seekFirstSwapMs` | seek request/command 到恢复首帧/首 swap | 相对 `+10s` 可用 |
| buffering | enter/exit 次数、累计时长、单次最大时长 | cache-buffering 状态边沿 | 字段可用;本地短片通常为 0 |
| 音频 | renderer start、首 callback、submitted/played、delay、starvation、flush/reset | 首 callback 时间由诊断轮询观察,不冒充 callback 内精确时间 | 模拟器字段可用,设备路由 pending |
| 视频 | dropped、late、mistimed、decoder frames、render count | MPV stats 或 renderer 计数 | 计数接口可用,部分 MPV 值可能为 -1 |
| 内存 | `/proc/<pid>/status` VmRSS/VmSize | 进程 RSS/虚拟地址空间 | 可用;PSS/精确 heap 为 null |
| CPU | `/proc/<pid>/stat` utime+stime | 原始 jiffies | 原始值可用;未取得可靠 tick/采样归一化,不输出 permille |
| 线程/资源 | Threads、`/proc/<pid>/fd` | 进程线程和 fd 快照 | 可用;fd=0 视为不可用 |
| NAPI | getState 次数、序列化微秒、估算 payload | NAPI 状态导出成本 | 可用;payload 是上界估算,不是分配追踪 |
| 渲染诊断 | render update/frame update、core sync/skipped | 优化前后计数及合并效果 | 可用 |
NAPI 返回使用 Number 以保持 Inspector 可序列化;大整数仅用于内部原子计数。任何未被设备或 API 真实提供的数据都保持 unavailable,不由模型推断。
+40
View File
@@ -0,0 +1,40 @@
# Phase 1 Dependency Matrix
基线:MPV `182fa6ca49f455cadb884858f386e2f00540aeb7`。状态来自当前 `meson.build`、实际 Meson 配置与最终产物,不沿用 Phase 0 假设。
| Dependency | Version / Commit | Required / Optional | Build System | License | Link | OHOS Status | pkg-config | Blocking libmpv |
|---|---|---|---|---|---|---|---|---|
| libavcodec | 63.8.101 / FFmpeg `3acec0a1` | Required | configure + Make | LGPL-2.1+ | Static | Built AArch64 | `libavcodec` | Yes |
| libavfilter | 12.3.101 / FFmpeg `3acec0a1` | Required | configure + Make | LGPL-2.1+ | Static | Built AArch64 | `libavfilter` | Yes |
| libavformat | 63.6.100 / FFmpeg `3acec0a1` | Required | configure + Make | LGPL-2.1+ | Static | Built AArch64 | `libavformat` | Yes |
| libavutil | 61.6.100 / FFmpeg `3acec0a1` | Required | configure + Make | LGPL-2.1+ | Static | Built AArch64 | `libavutil` | Yes |
| libswresample | 7.2.100 / FFmpeg `3acec0a1` | Required | configure + Make | LGPL-2.1+ | Static | Built AArch64 | `libswresample` | Yes |
| libswscale | 10.2.100 / FFmpeg `3acec0a1` | Required | configure + Make | LGPL-2.1+ | Static | Built AArch64 | `libswscale` | Yes |
| libplacebo | 7.360.1 / `cee9b076` | Required | Meson | LGPL-2.1+ | Static | Built AArch64; graphics backends off | `libplacebo` | Yes |
| libass | 0.17.5 / `4a05d812` | Required | Meson | ISC | Static | Built AArch64 | `libass` | Yes |
| FreeType | 2.14.3 / `0a0221a1` | libass transitive | Meson | FTL/GPL-2.0 dual | Static | Built AArch64 | `freetype2` | Yes via libass |
| HarfBuzz | 14.4.0 / `36cb489c` | libass transitive | Meson | MIT | Static | Built AArch64 | `harfbuzz` | Yes via libass |
| FriBidi | 1.0.16 / `68162bab` | libass transitive | Meson | LGPL-2.1+ | Static | Built AArch64 | `fribidi` | Yes via libass |
| OpenSSL | 3.6.4 / `d3c1b116` | HTTPS transitive | Configure + Make | Apache-2.0 | Static | Built AArch64 | `openssl` | Yes for HTTPS |
| zlib | SDK 1.2.13 | MPV optional; enabled | SDK | Zlib | Dynamic | SDK AArch64 `libz.so` | generated `zlib` | No |
| threads / dl / m / rt | SDK API 24 | Platform required | SDK | SDK system | Dynamic/system | Compile verified | Meson compiler/system | Yes |
| EGL / GLES | SDK API 24 | Phase 2 integration | SDK | SDK system | Not linked | Headers/libs available | No target `.pc` used | No |
| libcurl, libarchive, libbluray, libavdevice | Optional | Disabled | Various | Various | None | Not built | Not queried or skipped | No |
| CD/DVD, Lua, MuJS, uchardet, VapourSynth, zimg, rubberband | Optional | Disabled | Various | Various | None | Not built | Skipped | No |
| ALSA, AudioUnit, CoreAudio, JACK, OpenAL, AAudio, OpenSLES, OSS, PipeWire, Pulse, sndio, WASAPI | Optional AO | Disabled | Various | Various | None | Not applicable | Skipped | No |
| X11, Wayland, DRM/GBM, Cocoa, D3D11, Vulkan, VAAPI, VDPAU, CUDA, Android Media NDK | Optional VO/hwaccel | Disabled | Various | Various | None | Not applicable | Skipped | No |
实际依赖图:
```text
OpenSSL ───────────────┐
SDK zlib ──────────────┼─> FFmpeg ───────────────┐
└─> FreeType ─> HarfBuzz ─┤
FriBidi ──────────────────────────────┐ │
FreeType + HarfBuzz + FriBidi ───────> libass ───┤
libplacebo ──────────────────────────────────────┼─> libmpv.so
SDK libc/pthread/dl/m/rt/z ──────────────────────┘
SDK native_media_* + native_window <─ FFmpeg OHCodec
```
所有 `.pc` 仅由 `scripts/pkg-config-ohos.sh` 从 OHOS prefix 解析。最终校验未发现 `/opt/homebrew`、`/usr/local`、Mach-O 或 `.dylib` target 输入。
+67
View File
@@ -0,0 +1,67 @@
# Phase 2 Playback Lab 报告
日期:2026-08-29
分支:`harmony-port`
## 范围
`playback_lab/` 是独立 HarmonyOS 验证工程,仅链接 Phase 1 生成的 arm64 `libmpv.so.2`,不复用 Velix 主应用的页面或业务逻辑。当前只验证 GLES/EGL、软件解码和本地 MP4 首帧,不接入音频或硬解。
## 构建与产物
环境:DevEco Studio SDK API 24,HarmonyOS emulator 6.1.0.126,`aarch64-linux-ohos`。构建命令:
```sh
./scripts/stage-mpv.sh
DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home \
PATH="$JAVA_HOME/bin:$PATH" \
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw \
--no-daemon assembleHap --mode module -p product=default
```
结果:`BUILD SUCCESSFUL`。HAP:`playback_lab/entry/build/default/outputs/default/entry-default-unsigned.hap`,约 31 MiB,SHA-256 `877568c34a02a4d61b8c4ed7721dd2e6486cb675cd1a58a8eaf44c9b00de05f5`。包内包含 `libentry.so`、`libmpv.so.2` 和 `libc++_shared.so`;未提交 34 MiB 的 staged native 库。
构建仅保留两项预期警告:NAPI 动态模块在当前 SDK 尚未完成类型校验,以及调试 HAP 未配置正式签名;两者不影响本轮模拟器安装验证。
## 模拟器验证
目标:`127.0.0.1:5555`,虚拟设备进程配置为 Pura 90 Pro Max。
| 项目 | 结果 | 证据 |
| --- | --- | --- |
| HAP 安装、启动 | 通过 | `hdc install -r`、`aa start` 成功 |
| XComponent / OHNativeWindow | 通过 | 页面显示 `surface=READY`;Native 回调无崩溃 |
| EGLDisplay / EGLConfig / EGLContext / WindowSurface / makeCurrent | 通过 | 页面显示 `egl=READY`;日志 `EGL vendor=Express ... renderer=Mali-G77` |
| libmpv 初始化与 Render API | 通过 | 页面显示 `mpv=READY`;日志 `libmpv initialized` |
| 本地 H.264 MP4 软件解码 | 通过 | 本地 `sample.mp4` 首帧及持续播放成功 |
| Pause / Resume / Stop / Release | 通过 | UI 状态分别出现 `PAUSED`、`PLAYING`、`STOPPED`、`RELEASED` |
| Seek | 通过 | 有效播放中 `-10s/+10s` 不报错;跳转按 duration 做边界裁剪 |
| 释放后重新进入 | 通过 | `RELEASED` 后重新启动回到 `PLAYER_CREATED`,Surface/EGL/mpv 再次 READY |
| BigInt 状态序列化 | 通过 | `renderCount` 改为 Number,Inspector 不再出现 BigInt 序列化错误 |
一次本地播放快照:`position=2.00`、`duration=5.05`、`fps=29.97`、`dropped=0`、`firstFrameMs=622`、`renderCount=52`。另一轮快照首帧为 117 ms;`firstFrameMs` 从 `load()` 调用开始计时,并在第一次 `eglSwapBuffers` 后记录。
## First Frame Timeline
1. T0:XComponent 创建并取得回调传入的 `OHNativeWindow`,增加 NativeObject 引用。
2. T1:专用渲染线程创建 EGLDisplay、RGBA8 EGLConfig、ES3 EGLContext 和 EGLWindowSurface,执行 `eglMakeCurrent`。
3. T2:同一渲染线程创建 mpv OpenGL Render Context,注册 update callback。
4. T3:`loadfile` 完成本地文件打开,MPV 软件解码产生可渲染帧。
5. T4:渲染线程执行 `mpv_render_context_render`、`eglSwapBuffers`,记录 `firstFrameMs` 和 `renderCount`。
## 尚未完成的真机验收
本轮只有模拟器,没有接入 HarmonyOS 实体手机,因此 Phase 2 不能标记为最终 COMPLETE。以下项目保持待验证:实体机 HAP 安装、实体机本地 1080P H.264/AAC、HTTP MP4、HTTPS MP4、HLS、20 次 Surface recreate、20 次进出、50 次 Play/Pause、50 次 Seek、横竖屏 20 次、30 分钟稳定性、CPU/RAM 曲线。
## 风险状态
| 风险 | 当前状态 |
| --- | --- |
| RISK-01 POSIX / DNS / network runtime | 未验证,网络链路留待实体机 |
| RISK-02 XComponent / OHNativeWindow lifecycle | 模拟器已验证;实体机压力测试待做 |
| RISK-03 EGL context / Surface / plain-gl | 模拟器已验证;旋转与重建待做 |
| RISK-04 OHCodec buffer lifecycle | 保持开放;Phase 2 明确禁用硬解 |
| RISK-05 CA / HAP packaging | 未签名调试 HAP 安装通过;正式签名待接入 |
新增 MPV upstream patch:0;未修改第三方源码。进入 Phase 3(`ao_harmony + OHAudio`)的前置条件是实体机首帧、控制和压力矩阵完成。
+47
View File
@@ -0,0 +1,47 @@
# Phase 4 状态
## 结论
**Phase 4 CODE COMPLETE / DEVICE VALIDATION PENDING**
GPU interop:**BLOCKED**(见 `OHCODEC-BLOCKER-001`)。
代码完成了 MPV 识别 FFmpeg OHCodec、H.264/HEVC decoder selection、能力检查、
direct-surface 实验接入、软件回退、诊断和构建矩阵。当前没有实体设备,因此
不能写成 hardware decode complete、zero-copy complete 或 HDR complete。
## 验证矩阵
| 项目 | 状态 |
| --- | --- |
| `AV_PIX_FMT_OHCODEC` → `IMGFMT_OHCODEC` | COMPILE VERIFIED |
| `AV_HWDEVICE_TYPE_OHCODEC` 独立 backend | COMPILE VERIFIED |
| H.264 / HEVC selection guard | COMPILE VERIFIED;DEVICE TEST REQUIRED |
| API24 OHCodec capability 查询 | COMPILE VERIFIED;设备返回值待验 |
| Direct Surface plumbing | COMPILE VERIFIED;需独占窗口 |
| GPU interop mapper | BLOCKED:上游无可导入 non-surface output contract |
| Software decode / OHAudio | CLEAN BUILD VERIFIED |
| OHCodec-enabled / software / GL-probe | AArch64 BUILD VERIFIED |
| Playback Lab HAP | BUILD VERIFIED;unsigned |
| Emulator | APP START + EGL SMOKE VERIFIED;不是硬解验证 |
| Physical device | PENDING |
## 产物与审计
- `build/ohos-arm64/prefix/lib/libmpv.so.2.5.0` 为 AArch64 ELF,保留 OHCodec、
OHAudio 及诊断符号。
- `scripts/build-mpv-ohos-software.sh` 使用独立 build 目录关闭 OHCodec,避免
软件回退配置与硬解构建互相污染。
- FFmpeg 保持锁定 upstream commit,未复制 SDK/第三方实现;许可证审计见
`LICENSE_AUDIT.md`。
- 当前 blocker、NativeBuffer 生命周期、EGL/fence/crop/stride 待验证项见
`DOCS/OHCODEC_GPU_INTEROP_DESIGN.md` 与 `DOCS/HDR_PIPELINE_NOTES.md`。
- Phase 4.5 FFmpeg export contract 独立维护于
`../Velix-FFmpeg-Harmony`;MPV 只提供
`scripts/build-mpv-ohos-ffmpeg-harmony.sh` 前缀切换,未接入 mapper。
## 下一阶段最大风险
实体设备上的 codec capability、surface recreate、output buffer ownership、
acquire/release fence、crop/stride/color metadata,以及如何在不修改 FFmpeg
surface contract 的前提下把硬件帧交给 MPV GPU composition。
+18
View File
@@ -0,0 +1,18 @@
# Phase 5 状态
状态:CODE COMPLETE / HTTP Range emulator verified / device and remaining network validation pending。
已完成:
- 默认软件解码路径保持 hwdec=no,未启用 harmony-ohcodec-gl。
- 新增 include/mpv/harmony_playback.h 稳定 C API;公共结构不包含 mpv_handle、mpv_node、AVFrame。
- 请求字段、会话 generation、线程安全控制、稳定状态/错误、轨道、媒体信息和网络缓存诊断已实现。
- Playback Lab 已接入请求对象、脱敏 header 编辑器、速度/音量/静音、外部字幕、轨道/媒体/网络面板。
- 文档、测试矩阵和构建矩阵已补齐。
- default/software/FFmpeg-Harmony 三套 AArch64 libmpv 构建、稳定 API 符号校验与 Playback Lab Native/ArkTS/PackageHap 编译已通过;使用 DevEco 自带 JBR 的 `--no-daemon` 流程生成 unsigned HAP,并由 devecocli 安装模拟器。
- 模拟器已启动 Playback Lab,完成本地 H.264/AAC MP4 首帧、软件解码、播放结束状态和渲染指标 smoke;通过宿主受控 HTTP Range server 完成 HTTP MP4 播放与 Range 请求验证;HTTPS/HLS/网络字幕/鉴权资源仍需真实网络源。
- 当前构建日志显示 iconv feature disabled,因此字幕适配器只承诺 UTF-8;GBK/Big5/Shift-JIS 会返回稳定 SUBTITLE 错误。
尚待真实环境确认:HTTPS/HLS、重定向 header 继承、鉴权资源、网络字幕和物理设备音频/视频输出。不可从本地编译结果推断这些运行时结论。
许可:新增代码沿用 MPV 的 LGPL/ISC 边界,不引入商业字体或新的第三方网络库;公共头文件保持独立 ABI。Phase 6 最大风险是将 Provider 业务请求正确转换为通用请求,以及在实体设备上确认网络、音频路由和字幕字体 provider 行为。
+31
View File
@@ -0,0 +1,31 @@
# Phase 5 测试矩阵
| 场景 | 方式 | 结果 |
|---|---|---|
| 本地 MP4 H264/AAC | Playback Lab | 模拟器安装启动并完成首帧/播放/结束 smoke;网络与实体机待确认 |
| 本地 MKV H264/AAC/SRT | Playback Lab | API/UI 编译覆盖;设备运行待确认 |
| 本地 MKV HEVC/AAC/ASS(software) | software mpv | AArch64 构建通过;设备运行待确认 |
| HTTP MP4 | 宿主受控 Range server + Playback Lab | 模拟器通过;观察到 `bytes=0-`、后续 byte range,请求完成且 state=ENDED;`rangeSupported=1` |
| HTTPS MP4 | 受控测试源 | TLS 校验代码就绪;网络环境待确认 |
| HLS Master | 受控测试源 | 复用 mpv/FFmpeg;网络环境待确认 |
| HLS Variant/chunked | 受控测试源 | 复用 mpv/FFmpeg;网络环境待确认 |
| 多音轨 MKV | Playback Lab | Track API/UI 已接入;设备运行待确认 |
| 内封 SRT | Playback Lab | Track API/UI 已接入;设备运行待确认 |
| 内封 ASS/SSA | Playback Lab | libass 路径保留原样式;设备运行待确认 |
| 外挂 SRT | Playback Lab | URL 与本地 file 路径入口已接入;设备运行待确认 |
| 外挂 ASS/SSA | Playback Lab | URL 与本地 file 路径入口已接入;设备运行待确认 |
| 网络字幕/WebVTT | Playback Lab | API/UI 已接入;网络环境待确认 |
| Authorization header | 受控测试源 | 脱敏与隔离代码覆盖;服务端待确认 |
| Cookie header | 受控测试源 | 单会话 Cookie header;服务端待确认 |
| Referer/User-Agent | 受控测试源 | 请求字段与 header 均支持;服务端待确认 |
| Range seek / 大 seek | 宿主受控 Range server + Playback Lab | 模拟器通过 HTTP Range 请求;最近 seek 结果由 `seekResult` 暴露;HTTPS 大 seek 待继续验收 |
| Redirect | FFmpeg-Harmony `f3a036c695` + 受控测试源 | 跨 origin 丢弃 Authorization/Cookie/Token/API-key/Referer;同源保留;受控重定向运行验证待补 |
| 403 / 404 / 500 | 受控测试源 | 稳定 HTTP 错误码已接入;网络环境待确认 |
| Broken media / reset / timeout / TLS | 受控测试源 | DEMUX/NETWORK/TLS 分类已接入;网络环境待确认 |
| 默认 FFmpeg(OHCodec enabled) | scripts/build-mpv-ohos.sh | AArch64 ELF 与符号校验通过 |
| software MPV(OHCodec disabled) | scripts/build-mpv-ohos-software.sh | AArch64 ELF 校验通过 |
| FFmpeg-Harmony | scripts/build-mpv-ohos-ffmpeg-harmony.sh | AArch64 ELF 与符号校验通过 |
| audio-enabled MPV | 默认脚本 `harmony-audio=enabled` | 与默认构建一并通过 |
| Playback Lab HAP | hvigor assembleHap --no-daemon | CompileArkTS/Native/PackageHap 通过;无正式签名,devecocli 已安装模拟器 |
产品禁止以 UI 定时器伪造 buffering;只有 mpv 事件/属性可改变对应诊断字段。
+33
View File
@@ -0,0 +1,33 @@
# Phase 6 状态
状态:**CODE COMPLETE / EMULATOR BENCHMARKED / DEVICE BENCHMARK PENDING**。
Phase 6 交付范围是可重复的 Playback Lab 性能时间线、资源采样、NAPI 诊断、四项低风险优化和模拟器回归。没有修改 GPU mapper,也没有修改 `Velix-FFmpeg-Harmony`。
## 已完成
- 启动、seek、buffering、音频、渲染、NAPI 和资源指标已接入稳定快照;未能可靠取得的 PSS、精确 heap、归一化 CPU 均明确返回 `null` 或文档标记为 unavailable。
- `benchmark/runner/run_emulator.py` 支持 startup、seek、load/stop stress、play/pause stress,输出重复统计(min/max/avg/median/P90/P95)和逐次资源采样。
- 渲染循环改为 mpv update callback 驱动,并保留 250ms 安全探测;快照同步增加 100ms 合并窗口;session id 读取改为 atomic;诊断轮询从 250ms 调整为 500ms。
- default、software、FFmpeg-Harmony 三套 AArch64 libmpv 均通过符号和 OHOS ELF 校验;Playback Lab HAP 编译、安装和模拟器启动通过。
## 模拟器结果
测试设备:`127.0.0.1:5555`,本地 H.264/AAC MP4,软件解码,`ao_harmony`。
| 场景 | 结果文件 | 关键结果 |
| --- | --- | --- |
| startup_local ×20 | `benchmark/results/startup_local-1787999362.json` | 首帧 P50 506ms,P95 1150ms;20/20 暴露时间线,无错误 |
| seek_local ×50 | `benchmark/results/seek_local-1787999663.json` | 恢复 P50 277ms,P95 631ms;49/50 有恢复时间线,1 次 `SEEK` 命令错误 |
| play_pause ×200 | `benchmark/results/play_pause-1788000475.json` | 无错误;末次 RSS 213228KB、线程 39、fd 1 |
| load/stop ×100 | `benchmark/results/stress_lifecycle-1788000229.json` | 进程保持存活;RSS/线程/fd 采样完成,PSS 不可用 |
优化前后计数基线:同等 5 次 startup 中,渲染 update 累计由 617 降至最终版本 364;NAPI/核心同步计数通过面板快照记录。200 次 play/pause 无错误,末次 `coreSyncSkipped=239`,说明合并窗口实际生效。
最终 5 次 smoke 还观察到 `firstAudioCallbackMs` P50 256ms、`audioRendererStartMs` P50 720ms;这些值来自 500ms 诊断轮询,不能替代设备音频 trace。file:// 场景的 `networkWaitMs` 保持 -1,buffering 计数按真实状态边沿记录。
## 尚待实体设备
实体设备上的真实 PSS/heap、CPU 利用率、显示合成延迟、音频路由/A-V 偏差、OHCodec capability、长时间(2h)稳定性、HTTPS/HLS/鉴权/网络字幕仍未验证。Phase 7 建议先建立实体设备矩阵和受控 HTTPS/HLS fixture,再评估硬解直出和端到端 A/V 指标。
许可边界沿用 MPV LGPL/ISC;本阶段未引入新的第三方依赖或商业资源。
+13
View File
@@ -0,0 +1,13 @@
# PlaybackEngine
`PlaybackEngine` 是主 App 的唯一播放抽象,`MpvPlaybackEngine` 是 Core binding 的适配器,`PlaybackController` 负责页面状态刷新与生命周期编排。
业务层只使用:
- `PlaybackSource { kind, url, headers }`
- `PlaybackState { state, position, duration, media, tracks, metrics }`
- play/pause/stop/release、seek、音量/倍速、音轨/字幕操作
provider 接口暂不绑定 Emby、Jellyfin、百度网盘等服务。HTTP(S) 源支持有限 headers;复杂 header 编辑器、登录与播放列表不在 v0 范围。
默认解码策略为软件解码;`requestedDecoder=software` 是主 App 的固定默认值,OHCodec GL mapper 不在本阶段启用。
+13
View File
@@ -0,0 +1,13 @@
# Playback 公共 API
mp_harmony_playback.h 是上层唯一稳定契约,提供 PlaybackRequest、PlaybackState、Track、MediaInfo、PlaybackMetrics 和 PlaybackError 模型别名。PlaybackRequest 只携带 URL、标题、起始位置、User-Agent、Referer、Cookie、任意 HTTP headers、字幕源、语言偏好、轨道选择、速度和超时。空值或负值按头文件注释表示未设置。
公开控制命令:Play、Pause、TogglePause、SeekAbsolute、SeekRelative、SeekPercent、Stop、SetSpeed、SetVolume、Mute、SetAudioEnabled、SelectAudioTrack、SelectSubtitleTrack、DisableSubtitle、AddSubtitle、RemoveSubtitle、SetSubtitleDelay、SetSubtitleScale、SetSubtitlePosition、SetAssOverride、GetTracks、GetMediaInfo、GetNetworkInfo、GetPlaybackState。
所有控制入口串行化;请求中的字符串在调用返回前复制到 mpv 所需的临时节点,调用者可以立即释放输入内存。session id 随每次 load 增长,旧 playlist event 不得改变新会话状态。
Playback Lab 的 C++ 外壳只做 NAPI/渲染适配,所有请求、控制和状态快照均委托上述 C API;渲染器使用私有句柄桥接,不进入公共 ABI。
当前 OHOS 构建未启用 iconv,外挂字幕 encoding 只接受 UTF-8/UTF8;其他编码通过稳定 SUBTITLE 错误拒绝。
起播 `start_position_ms` 在 `MPV_EVENT_FILE_LOADED` 后执行 exact seek,避免异步 load 竞态;`title` 映射到 `force-media-title`。MediaInfo 同时保留历史 `width`/`height`/`color_space` 字段,并提供 `video_width`/`video_height`/`color_matrix` 别名。NetworkInfo 的 `range_supported` 为 -1/0/1(未知/未观察到 206/观察到 206),`seek_result` 为最近一次 seek 的 `ok` 或脱敏错误。
+14
View File
@@ -0,0 +1,14 @@
# Player lifecycle
产品调用顺序:
```text
create → attachSurface → prepare(source) → play
↘ pause / seekTo / seekBy / tracks / subtitles
stop → prepare 或 release
detachSurface → release
```
`onForeground`、`onBackground`、`onSurfaceAvailable`、`onSurfaceDestroyed` 是显式适配接口。当前后台策略只释放/暂停 Surface 相关资源,不承诺后台播放、PiP 或通知栏控制。Surface 与 player 生命周期解耦:Surface 销毁不会自动销毁 Core player,新的 Surface 可重新 attach。
页面状态以 500ms 轮询读取,错误通过 `errorCode` 与 `lastError` 映射到 UI;首帧、缓冲、seek、音频与 renderer 指标仅在“显示调试统计”打开时显示。
+70
View File
@@ -0,0 +1,70 @@
# HarmonyOS Porting Plan
## 1. 源码扫描结论
MPV 核心播放、demux、filter、subtitle、A/V sync、command、client API 基本跨平台。平台耦合集中在 `meson.build`、`osdep/`、`audio/out/`、`video/out/`、`video/decode/` 与 hwdec interop。当前仓库搜索不到 Harmony/OHOS 代码。
MPV 当前检测逻辑 `posix = not win32`;非 Darwin POSIX 自动选择 `osdep/timer-linux.c`。OHOS 编译器同时定义 `__OHOS__` 与 `__linux__`,因此所有 Linux feature 必须由 Meson probe 或显式 Harmony gate 决定,不能只看 `__linux__`。
## 2. 文件级移植矩阵
| MPV source / file | 当前依赖 API / 行为 | HarmonyOS 替代或复用 | Patch | 风险 | 阶段 | 工作量 |
|---|---|---|---:|---|---|---|
| `meson.build` OS detection | win32/darwin/posix/android/linux 分支 | 增加 `ohos = host_machine.system() == 'ohos'`、`features['harmony']`、config `HAVE_HARMONY` | 是 | 高 | P1-01 | 1–2d |
| `meson.build` platform sources | 非 Darwin POSIX选 Linux timer;Android 单独 EGL/media | 为 OHOS 明确选择 POSIX可复用源、Harmony AO/可选 context;不进入 Android 分支 | 是 | 高 | P1-01 | 1d |
| `osdep/threads-posix.c` | pthread、cond、mutex、TLS | 直接使用 SDK `pthread.h`;线程名由 feature probe | 预计否/小 | 中 | P1-02 | 0.5–1d |
| `osdep/timer-linux.c` | `clock_gettime`、`nanosleep` | API均可用;可先明确选该实现,后续重命名/拆成 POSIX timer 以便 upstream | 小 | 低 | P1-02 | 0.5d |
| `osdep/path-unix.c` | POSIX paths、HOME/XDG 等桌面约定 | 文件 API复用;应用 data/cache 路径最终由宿主注入,不能依赖 HOME | 小/后续 | 中 | P1-03 | 1–2d |
| `osdep/language-posix.c` | locale/environment | 先复用;设备语言由宿主 API注入作为后续增强 | 预计否 | 低 | P1-03 | 0.5d |
| `osdep/subprocess-posix.c` | fork/exec/process | Harmony app 不需要 subprocess;若 probe失败明确选 `subprocess-dummy.c` 并返回 unsupported | 小 | 中 | P1-03 | 0.5d |
| `osdep/terminal-unix.c` | tty/terminal | libmpv build 不需要交互 terminal;cplayer关闭 | 配置 | 低 | P1-01 | 0.2d |
| `input/ipc-unix.c` | Unix domain socket | 首版 libmpv 可关闭/保留按 probe;产品 API不暴露 IPC | 小/配置 | 低 | P1-03 | 0.5d |
| `stream/` + FFmpeg AVIO | POSIX socket、DNS、FFmpeg HTTP/TLS | socket/DNS复用;HTTP/HLS由 FFmpeg;TLS 使用交叉构建 OpenSSL | MPV 否 | 高 | P1-04 | 3–5d |
| `demux/demux_lavf.c` | libavformat | 直接复用官方 FFmpeg OHOS build | 否 | 中 | P1-04 | 1d 验证 |
| `sub/`、`sub/osd_libass.c` | libass、字体栈 | 直接复用;字体发现/应用字体目录需配置验证 | 小/配置 | 中 | P1-05 | 2–3d |
| `include/mpv/client.h` | libmpv C API | 直接复用仅作内部依赖;最终 Velix API再封装 | 否 | 低 | P1 | 0.5d |
| `include/mpv/render*.h` | OpenGL / software public render API | 直接使用 OpenGL API;宿主提供 GLES proc resolver/FBO | 否 | 中 | P2-01 | 2–4d lab glue |
| `video/out/vo_libmpv.c` | libmpv render bridge | 直接复用 | 预计否 | 中 | P2-01 | 1d 验证 |
| `video/out/opengl/libmpv_gl.c` | OpenGL Render API | 直接复用 GLES/EGL context;验证所需 GL symbols | 小/预计否 | 中 | P2-01 | 2d |
| `video/out/opengl/context*.c` | Android/X11/Wayland/DRM/Windows context | 首版 libmpv plain-gl 不新增;如 MPV自管 context,再新增 `context_harmony.c` | 后续 | 中 | P2-02 | 3–5d |
| `video/out/vulkan/context.c` | libplacebo/Vulkan context registry | 若采用 Vulkan新增 `context_harmony.c`,调用 `vkCreateSurfaceOHOS` | 后续是 | 高 | P4 | 7–12d |
| `audio/out/ao.c` | AO driver registry | 注册 `audio_out_harmony` | 是 | 低 | P2-03 | 0.5d |
| `audio/out/ao_aaudio.c` | Android AAudio callback模型 | 仅作结构参考;实际新增 `ao_harmony.c` 使用 OHAudio | 新文件 | 高 | P2-03 | 5–8d |
| `audio/out/buffer.c` | MPV AO buffering | 直接复用,由 OHAudio callback消费 | 预计否 | 中 | P2-03 | 1–2d 验证 |
| `video/fmt-conversion.c` | FFmpeg pixfmt↔MPV imgfmt | 增加 `AV_PIX_FMT_OHCODEC` 映射前先确认 MPV image semantics | 后续是 | 高 | P3-02 | 1–2d |
| `video/decode/vd_lavc.c` | hw device选择、frames | 加入 `AV_HWDEVICE_TYPE_OHCODEC` 并传递 native window/device ctx | 后续是 | 高 | P3-02 | 3–6d |
| `video/out/gpu/hwdec.c` | GPU interop registry | 注册 Harmony interop driver,仅在真实 buffer import 方案明确后 | 后续是 | 极高 | P3-03 | 7–15d |
| `video/out/hwdec/` | VAAPI/DRM/VT/Android interop | 新增隔离的 `hwdec_ohcodec.c`,不散落 OHOS ifdef | 后续是 | 极高 | P3-03 | 10–20d |
| `video/out/opengl/egl_helpers.c` | 通用 EGL config/context helpers | 优先复用;验证 GLES profile、10-bit config、context loss | 可能小 | 中 | P2-01 | 2d |
| `misc/io_utils.c` / poll wrapper | poll/FD helper | POSIX poll 复用;epoll不作为无证据优化 | 预计否 | 低 | P1-02 | 0.5d |
| logging (`common/msg.c`) | stderr/terminal | 首编译复用;lab阶段加 HiLog adapter,不能替换核心 logger | 后续小 | 低 | P2 | 1–2d |
## 3. 分阶段执行
### P1:First Build
1. P1-01:Meson 识别 OHOS,构建只产 libmpv。
2. P1-02:逐项编译 POSIX 基础层;每个 blocker 单独记录。
3. P1-03:明确 subprocess/IPC/path 不支持或可复用边界。
4. P1-04:构建 FFmpeg software decode + network + TLS。
5. P1-05:构建 libass/font stack、libplacebo,链接 libmpv。
门禁:AArch64 libmpv 真实产物、导出符号、依赖清单、无 Android API、无宿主机库。
### P2:Playback Lab 基线
XComponent + EGL + libmpv OpenGL Render API;新增 OHAudio AO;URL/local file、play/pause/seek/stop/release、日志和基础 metrics。无正式产品 UI。
### P3:硬件解码
先验证 FFmpeg `h264_oh` / `hevc_oh`,再决定 direct surface 或 GPU import。所有优化必须对比软件解码基线。
### P4:定点性能与 Vulkan
只有 Benchmark 证明 GLES/上传或合成是瓶颈时,研究 Vulkan/HDR/zero-copy;每个 patch 必须有前后数据。
## 4. 可直接复用与必须新增
可直接复用:libmpv client/render API、player core、demux、software decode glue、filters、A/V sync、subtitle/libass、playlist/property/command、POSIX threads/timer/socket/DNS/poll/mmap 的已验证子集、OpenGL renderer。
必须新增:Harmony Meson detection、OHAudio AO、Playback Lab 的 XComponent/EGL native glue。硬解阶段预计新增 OHCodec image/device/interoperability backend。Vulkan context 不是首版必需。
+35
View File
@@ -0,0 +1,35 @@
# Risk Assessment
## Top 10
| # | 风险 | 可能性 / 影响 | 证据 | 缓解与验收 |
|---:|---|---|---|---|
| 1 | MPV 无 OHOS 平台检测,OHOS 被泛 POSIX/Linux 逻辑误分类 | 高 / 高 | 当前 `meson.build` 无 harmony/ohos;编译器同时定义 `__linux__` | 显式 `features['harmony']`;每个 Linux feature 用 compile/link probe |
| 2 | 交叉依赖链复杂且 pkg-config 污染宿主库 | 高 / 高 | 本机无 Meson/Ninja/pkg-config;MPV 强依赖 FFmpeg/libplacebo/libass | 单一 staging prefix、清空默认 `PKG_CONFIG_PATH`、产物 readelf 审计 |
| 3 | HTTPS/TLS 无可直接复用的 FFmpeg Harmony backend | 高 / 高 | SDK有 `libnet_ssl.so`,FFmpeg不使用它 | 交叉构建 OpenSSL;真机证书/SNI/Range/HLS 测试 |
| 4 | Surface 生命周期与 GL 线程竞态导致黑屏/崩溃 | 高 / 高 | XComponent异步 create/change/destroy,EGL context 有线程归属 | 单一 render thread 状态机;销毁屏障;旋转/后台压力测试 |
| 5 | OHAudio callback 与 MPV buffer/clock 语义不匹配 | 中高 / 高 | MPV无 Harmony AO | realtime-safe callback;timestamp/delay 校准;seek/route switch 测试 |
| 6 | FFmpeg OHCodec虽已 upstream,但 MPV完全不识别 OHCODEC | 高 / 高 | MPV无 `AV_PIX_FMT_OHCODEC` / `AV_HWDEVICE_TYPE_OHCODEC` | 软件基线后小步接入;先独立验证 FFmpeg backend |
| 7 | Direct Surface decode 绕过 MPV shader/ASS/色彩管理 | 高 / 高 | FFmpeg OHCodec向 window直接 render | 优先研究 OHNativeBuffer GPU import;direct surface仅作验证路径 |
| 8 | 4K/10-bit/HDR 能力因机型/驱动不同,SDK存在不等于设备支持 | 高 / 高 | capability API要求运行时查询 | 保存设备矩阵、profile/level/format/extension;不做泛化承诺 |
| 9 | GPL/LGPL 与静态链接/FFmpeg选项污染商业发布 | 中高 / 极高 | MPV默认 GPLv2+;开关不自动产生 LGPL授权 | 默认 GPL 管理;保存完整 build manifest;发布前法务审计 |
| 10 | arm64 OHOS ABI/符号/打包规则与普通 Linux不同 | 中 / 高 | target 是 `aarch64-linux-ohos`,系统库命名含 `.z.so` | 小探针→依赖→libmpv;检查 SONAME、NEEDED、符号可见性和 HAP 装载 |
## 其他风险
- 字体发现和字体包体积影响 ASS 首帧及中日韩字幕正确性。
- 网络切换、代理、IPv6、慢速 Range、HLS discontinuity 对稳定性影响大。
- MPV master 与 FFmpeg master 同时变化,需锁 commit 并记录组合。
- API 24 SDK header含更低 API接口,但实际目标机系统版本仍要匹配。
- Vulkan headers/loader存在不代表驱动质量和 HDR swapchain 可用。
## 决策门禁
| 决策 | 必需证据 |
|---|---|
| 进入 Phase 1 | Phase 0 文档完成;origin/upstream/branch明确;依赖与 license 策略确认 |
| 开始 Playback Lab | libmpv arm64真实产物及 build report |
| 启用 OHCodec | software decode 基线 + 官方 FFmpeg OHCodec 独立验证 |
| 宣称 low-copy/zero-copy | 帧路径、buffer ownership、GPU import、同步与 copy 测量证据 |
| 采用 Vulkan | GLES benchmark证明瓶颈且 Vulkan原型在目标机显著改善 |
| 商业发布 | 实际链接清单、许可证正文、源码义务、法务审核全部完成 |
+20
View File
@@ -0,0 +1,20 @@
# SDK packaging
使用 `scripts/package-velix-core-sdk.sh` 生成 staging 目录。脚本按 profile 选择已构建的 `libmpv`,编译 binding,复制公开 headers、运行库、许可证,并生成包含 API 版本、commit、profile、尺寸和 SHA-256 的 `metadata/build-info.json`。
支持 profile:
```sh
./scripts/package-velix-core-sdk.sh --profile default
./scripts/package-velix-core-sdk.sh --profile software
./scripts/package-velix-core-sdk.sh --profile ffmpeg-harmony
```
ABI 检查重点:
- `libvelix_core_binding.so` 只导出 3 个 `velix_core_get_*` ABI 符号;NAPI 模块描述由系统注册段加载,不把内部 C++ 符号作为 ABI;
- `libmpv.so.2` 保持自身运行时导出,不将其头文件类型传给主 App;
- 所有动态依赖必须存在于 `lib/arm64-v8a` 或 OpenHarmony 系统库中;
- staging 与构建目录不应进入 Git,发布包必须保留 LGPL/GPL/第三方声明。
验证:`./scripts/verify-velix-core-sdk.sh`。
+15
View File
@@ -0,0 +1,15 @@
# Seek Pipeline
```text
seek request
-> mpv seek command
-> SEEK event / demux recovery
-> VIDEO_RECONFIG
-> PLAYBACK_RESTART
-> first video frame
-> first render / EGL swap
```
`seekCommandLatencyMs` 是 API request 到 command 提交的耗时;`seekRecoveryMs` 是 request 到 `PLAYBACK_RESTART` 的耗时;`seekFirstVisibleFrameMs` 是 request 到首个成功 swap 的耗时。若 MPV 没有产生对应事件,字段为 -1。每次新 seek 会重置上一次 seek 字段,不把旧会话值带入新会话。
模拟器 runner 使用 `+10s` 相对 seek,50 次中 49 次产生 recovery,1 次返回稳定 `SEEK / error running command`;该失败保留在 JSON,不被隐藏。精确/百分比 seek 入口仍由公共 API 暴露,但短样片不足以让输入框自动化可靠覆盖,需更长受控 fixture。
+19
View File
@@ -0,0 +1,19 @@
# Startup Pipeline
```text
session setup
-> mpv_create / mpv_initialize
-> load command
-> START_FILE
-> network open / first byte (network only)
-> FILE_LOADED (demux ready)
-> VIDEO_RECONFIG / AUDIO_RECONFIG
-> render context ready
-> first video frame available
-> first render begin/end
-> first EGL swap (startupTotalMs)
```
时间线在 core 内以 `clock_gettime(CLOCK_MONOTONIC)` 记录,所有阶段只写首次发生值。文件输入没有网络阶段时保持 -1;网络阶段只在真实 MPV log/event 证据出现时写入。`startupTotalMs` 在首个成功 `eglSwapBuffers` 写入,`firstFrameMs` 保留外壳兼容字段并与首 swap 对齐。
音频 renderer start 和首 callback 由 OHAudio stats poll 观察;这两个字段可能晚于真实 callback,因此用于阶段诊断,不替代设备级 audio trace。
+7
View File
@@ -0,0 +1,7 @@
# 字幕管线
内部字幕和外部字幕统一由 mpv/libass 管线处理。外部源通过 sub-add 加入当前 session,可选中或移除,不销毁播放 session;sub-remove 仅移除指定轨道。language/title 通过 sub-add 元数据传入,forced/default 映射为 mpv 的同名 track flags。
目标格式:SRT、ASS/SSA、WebVTT;ASS 默认保留原样式与 libass override 语义。上层可开关、选择字幕、调整 delay、scale、position、override(具体控件按实际可用属性开放)。
当前 OHOS 构建未启用 iconv,因此只接受 UTF-8/UTF8;GBK、Big5、Shift-JIS 等输入明确返回稳定 SUBTITLE 错误,不静默走 Latin-1 fallback。启用并验证 iconv 后再扩展编码白名单。Playback Lab 同时提供网络 URL 与本地 file 路径入口。
+5
View File
@@ -0,0 +1,5 @@
# Track 模型
上层只接收固定 Track 字段:id、type、title、language、codec、default、forced、selected、external。mpv_node 只在实现层解析,不跨 NAPI/C API 边界。
音频通过 aid 选择,字幕通过 sid 选择或设置为 no。语言偏好使用 mpv 的 alang/slang,可接受 zh、zho、chi、en、eng、ja、jpn 等媒体元数据值。不可用的字段保持空值或 false。
+10
View File
@@ -0,0 +1,10 @@
# Velix 主 App 集成
主 App 工程为 `../Velix`,当前是独立的 Stage Model/API 24 工程,目录本身没有 Git 元数据。集成脚本提供两种依赖模式:
- `scripts/sync-velix-core-sdk.sh --local`:从相邻 Core 重新构建并 staging,适合本地开发。
- `scripts/sync-velix-core-sdk.sh --release --sdk PATH`:消费已经发布的 SDK 目录,适合 release 验证。
`scripts/build-velix-app.sh` 会先同步 SDK,再执行 `devecocli build`。主 App 只将 `libvelix_core_binding.so`、`libmpv.so.2` 与 `libc++_shared.so` 放进 `entry/libs/arm64-v8a`,不引入 Core/FFmpeg 源码。
ArkTS 入口为 `domain/playback/PlaybackEngine.ets` → `MpvPlaybackEngine.ets` → native binding;页面为 `pages/PlayerPage.ets`。XComponent 的 `libraryname` 为 `velix_core_binding`,Surface 回调由 Core binding 注册,页面不重复实现 EGL renderer。
+3
View File
@@ -0,0 +1,3 @@
add `--disc-menu` option
add `disc-menu-active` property
add `discnav` command
+2
View File
@@ -0,0 +1,2 @@
add `dvda://` protocol for DVD-Audio playback
add `--dvda-device` and `--dvda-page` options
+1
View File
@@ -0,0 +1 @@
add `vf=format=enhancement-layer` option
+1
View File
@@ -0,0 +1 @@
`track-list/N/ff-index` may be unavailable if no known mapping exists
@@ -0,0 +1 @@
change `--hwdec-extra-frames` default to `auto`, sized from VO frame retention
@@ -0,0 +1 @@
implement `lavfi-tempo` an alternative speed changing audio filter
@@ -0,0 +1,2 @@
remove `osc-livemarkers` script-opt because chapter markers are now updated
without reiniting the OSC
@@ -0,0 +1,2 @@
add `video_colorspace`, `max_luma`, `primaries` and `transfer` arguments
to the `overlay-add` command
@@ -0,0 +1 @@
add `--playlist-inherit-options` option
@@ -0,0 +1 @@
bind right click to context menu
@@ -0,0 +1,2 @@
add `--secondary-sub-scale` option
`--sub-scale` now only affects primary subtitles
@@ -0,0 +1 @@
add `keep-open` argument support to select.lua script bindings
@@ -0,0 +1 @@
add `--sub-hdr-peak=auto` to follow the reference white and make it the default
+31
View File
@@ -189,6 +189,37 @@ Available filters are:
``window-size=<amount>``
Length in milliseconds of the overlap-and-add window. (default: 12)
``lavfi-tempo[=[filter=]<filter_name>]``
Scales audio tempo using ``atempo`` or ``ascale`` filters from FFmpeg's
libavfilter.
If ``ascale`` (Librempeg only) is not available in the loaded
libavfilter, ``atempo`` will be used as a fall-back.
This can be used in place of ``scaletempo`` and ``scaletempo2``.
.. admonition:: Examples
``mpv --af=lavfi-tempo --speed=1.2 media.ogg``
Would play media at 1.2x normal speed at normal pitch with tempo
scaled by the ``atempo`` filter from lavfi.
``mpv --af=lavfi-tempo=atempo --speed=1.2 media.ogg``
Same as above.
``mpv --af=lavfi-tempo=filter=atempo --speed=1.2 media.ogg``
Same as above.
``mpv --af=lavfi-tempo=ascale --speed=1.2 media.ogg``
Would play media at 1.2x normal speed at normal pitch with tempo
scaled by the ``ascale`` filter from lavfi, unless the filter is
not available, then ``atempo`` will be used as a fall-back.
``mpv --af=lavfi-tempo=filter=ascale --speed=1.2 media.ogg``
Same as above.
``rubberband``
High quality pitch correction with librubberband. This can be used in place
of ``scaletempo`` and ``scaletempo2``, and will be used to adjust audio pitch
+2 -2
View File
@@ -1,5 +1,5 @@
COMMANDS
========
CONSOLE COMMANDS
================
This script allows running and completing input commands in the console
interactively, and also adds mpv's log to the console's log.
+75 -6
View File
@@ -904,7 +904,7 @@ OSD Commands
Show the progress bar, the elapsed time and the total duration of the file
on the OSD. ``no-osd`` has no effect on this command.
``overlay-add <id> <x> <y> <file> <offset> <fmt> <w> <h> <stride> <dw> <dh>``
``overlay-add <id> <x> <y> <file> <offset> <fmt> <w> <h> <stride> [<dw> [<dh> [<video_colorspace> [<max_luma> [<primaries> [<transfer>]]]]]]``
Add an OSD overlay sourced from raw data. This might be useful for scripts
and applications controlling mpv, and which want to display things on top
of the video window.
@@ -964,6 +964,24 @@ OSD Commands
in display to ``dw`` and ``dh``. If parameters are not present, the
values for ``w`` and ``h`` are used.
``video_colorspace`` (default: no) interprets the overlay in the video's
colorspace, including HDR metadata, so it is tone mapped exactly like
the video.
``max_luma`` specifies the (optional) luminance peak of the overlay in
cd/m². If unset, it is inferred from the overlay's colorspace, or taken
from the video for ``video_colorspace`` overlays.
``primaries`` and ``transfer`` specify the (optional) colorspace the
overlay is encoded in, and accept the same values as ``--target-prim``
and ``--target-trc``. The default ``auto`` means sRGB (BT.709 primaries
and sRGB transfer). They are ignored if ``video_colorspace`` is set.
Currently only ``--vo=gpu-next`` honors these parameters. ``--vo=gpu``
always assumes the overlay is sRGB and adapts it to the display. Other
VOs ignore these parameters, and either blend the overlay without any
colorspace conversion or pass it to the system compositor.
.. note::
Before mpv 0.42.0, the file was mapped into memory with ``mmap()``,
@@ -1753,6 +1771,46 @@ Miscellaneous Commands
``context-menu``
Show context menu on the video window. See `Context Menu`_ section for details.
``discnav <action> [<x> <y>]``
Send a navigation command to the optical-disc stream that is currently
playing. The command does nothing when no disc is being played.
``<action>`` is one of:
up, down, left, right
Move the on-disc button selection.
select
Activate the currently highlighted button.
menu
Jump to the root menu of the current title set (DVD: VTSM root menu,
BD: HDMV top menu).
title-menu
Jump to the disc's title menu (DVD: VMGM title menu, treated like
``menu`` on BD).
popup
Show / dismiss the Blu-ray popup menu. For DVDs, falls back to the
chapter menu where available.
prev
Return from a menu to playback, or to the previous menu domain.
mouse-move
Forward a mouse position to the disc, so it can update the focused
button under the cursor. With no ``<x> <y>`` arguments, the position
is read from the ``mouse-pos`` property and mapped from window into
source-video coordinates automatically.
mouse-click
Like ``mouse-move`` but also activates the button under the cursor.
``<x>`` and ``<y>`` are optional and only meaningful for the ``mouse-*``
actions. They are normalized coordinates in the range 0..1 (top-left at
``0,0``), and are used instead of the live ``mouse-pos``.
mpv ships default bindings for this command in the ``{discnav}`` input
section (``UP``/``DOWN``/``LEFT``/``RIGHT`` for navigation, ``ENTER`` to
select, ``ESC`` / ``BS`` to leave, ``MBTN_LEFT`` / ``MOUSE_MOVE`` for
mouse). The player enables and disables that section automatically as
the menu appears and disappears, so the bindings only shadow the normal
ones while a menu is actually on screen.
``update-clipboard <type> [timeout]``
Update the clipboard content so that the ``clipboard`` property reflects
up-to-date value. This command is required to update the ``clipboard``
@@ -1986,7 +2044,7 @@ The following hooks are currently defined:
For example, you could read and write the ``stream-open-filename``
property to redirect an URL to something else (consider support for
streaming sites which rarely give the user a direct media URL), or
you could set per-file options with by setting the property
you could set file-local options by setting the property
``file-local-options/<option name>``. The player will wait until all
hooks are run.
@@ -2423,6 +2481,12 @@ Property list
between having no editions and a single edition, which will be reflected by
the property, although in practice it does not matter.)
``disc-menu-active``
``yes`` when the current optical-disc stream (DVD or Blu-ray) is showing
an interactive menu (a DVD menu domain, or a visible Blu-ray menu
overlay), and ``no`` otherwise. Unavailable when the currently playing
source is not an optical disc.
``chapters``
Number of chapters.
@@ -3576,7 +3640,7 @@ Property list
this can be potentially wrong if a demuxer other than libavformat
(``--demuxer=lavf``) is used. For mkv files, the index will usually
match even if the default (builtin) demuxer is used, but there is
no hard guarantee.
no hard guarantee. May be unavailable if no known mapping exists.
``track-list/N/decoder``
If this track is being decoded, the short decoder name,
@@ -4162,9 +4226,14 @@ Property list
``file-local-options/<name>`` (RW)
Similar to ``options/<name>``, but when setting an option through this
property, the option is reset to its old value once the current file has
stopped playing. Trying to write an option while no file is playing (or
is being loaded) results in an error.
property, the option is marked as file-local, which causes it to reset to
its old value once the current file has stopped playing. Trying to write
an option while no file is playing (or is being loaded) results in an error.
If an option is applied through a file specific mechanism such as per-file
options, watch later, ``options`` parameter of the ``loadfile`` command,
or file-specific configuration files, the option is similarly marked as
file-local.
(Note that if an option is marked as file-local, even ``options/`` will
access the local value, and the ``old`` value, which will be restored on
+50 -7
View File
@@ -174,6 +174,15 @@ z and Z
l
Set/clear A-B loop points. See ``ab-loop`` command for details.
Ctrl+m
Jump to the disc menu when playing a DVD or Blu-ray (see the ``discnav``
command). While a menu with a button highlight is on screen, the arrow
keys, ENTER, ESC/BACKSPACE and the mouse control the menu instead of
their usual functions.
Ctrl+M
Show or hide the Blu-ray popup menu.
L
Toggle infinite looping.
@@ -393,7 +402,7 @@ Left double click
Toggle fullscreen on/off.
Right click
Toggle pause on/off.
Show the context menu (see `CONTEXT MENU`_).
Forward/Back button
Skip to next/previous entry in playlist.
@@ -622,7 +631,8 @@ reset when a new file is played.
Sometimes, it is useful to change options per-file. This can be achieved by
adding the special per-file markers ``--{`` and ``--}``. (Note that you must
escape these on some shells.) Example::
escape these on some shells.) When a file is loaded, the associated per-file
options are applied and marked as file-local options. Example::
mpv --a file1.mkv --b --\{ --c file2.mkv --d file3.mkv --e --\} file4.mkv --f
@@ -637,7 +647,8 @@ file4.mkv ``--a --b --f``
Additionally, any file-local option changed at runtime is reset when the current
file stops playing. If option ``--c`` is changed during playback of
``file2.mkv``, it is reset when advancing to ``file3.mkv``. This only affects
``file2.mkv``, it is reset when advancing to ``file3.mkv``, or when restarting
the current file with the ``playlist-play-index`` command. This only affects
file-local options. The option ``--a`` is never reset here.
@@ -816,6 +827,8 @@ file-specific configuration is loaded from ``~/.config/mpv``. In addition, the
``--use-filedir-conf`` option enables directory-specific configuration files.
For this, mpv first tries to load a mpv.conf from the same directory
as the file played and then tries to load any file-specific configuration.
The options loaded in this way are marked as file-local, which are reset when
the current file stops playing.
Profiles
@@ -1266,6 +1279,9 @@ modified after playback began, for example the volume and selected audio/subtitl
and restores their values the next time the file is played. Which options are
saved can be configured with the ``--watch-later-options`` option.
The options applied in this way are marked as file-local, and they are reset when
playback of the file associated with it stops.
When playing multiple playlist entries, mpv checks if one them has a resume
config file associated, and if it finds one it restarts playback from it. For
example, if you use ``quit-watch-later`` on the 5th episode of a show, and
@@ -1320,8 +1336,12 @@ PROTOCOLS
Play a Blu-ray disc. Since libbluray 1.0.1, you can read from ISO files
by passing them to ``--bluray-device``.
A Blu-ray ``.iso`` image passed directly (e.g. ``mpv disc.iso``) is also
detected and opened.
``title`` can be: ``longest`` or ``first`` (selects the default
playlist); ``mpls/<number>`` (selects <number>.mpls playlist);
playlist); ``menu`` (starts in the disc menu, see ``--disc-menu`` and the
``discnav`` command); ``mpls/<number>`` (selects <number>.mpls playlist);
``<number>`` (select playlist with the same index). mpv will list
the available playlists on loading.
@@ -1329,13 +1349,32 @@ PROTOCOLS
``dvd://[title][/device]`` ``--dvd-device=PATH``
Play a DVD. DVD menus are not supported. If no title is given, the longest
title is auto-selected. Without ``--dvd-device``, it will probably try
to open an actual optical drive, if available and implemented for the OS.
Play a DVD. If no title is given, the longest title is auto-selected.
``title`` can also be ``menu`` to start in the disc menu (see
``--disc-menu`` and the ``discnav`` command). Without ``--dvd-device``,
it will probably try to open an actual optical drive, if available and
implemented for the OS.
A DVD-Video ``.iso`` image passed directly (e.g. ``mpv disc.iso``) is also
detected and opened.
``dvdnav://`` is an old alias for ``dvd://`` and does exactly the same
thing.
``dvda://[title][/device]`` ``--dvda-device=PATH``
Play the AUDIO_TS zone of a DVD-Audio disc. Titles correspond to the
disc's audio groups, tracks are exposed as chapters. If no title is
given, the longest title is auto-selected. Menus are not supported.
Still images (ASVS), such as cover art or booklet pages a disc associates
with its tracks, are exposed as a video track and shown by default. Can be
disabled with ``--vid=no``.
A DVD-Audio ``.iso`` image passed directly (e.g. ``mpv disc.iso``) is also
detected and opened. For hybrid discs, the DVD-Audio zone is preferred over
the DVD-Video zone.
``dvb://[cardnumber@]channel`` ``--dvbin-...``
Digital TV via DVB. (Linux only.)
@@ -1492,6 +1531,10 @@ PROTOCOLS
This will play ``video.mkv`` in the archive file ``file.zip``.
``env://variable``
Read the environment variable ``variable`` as source data.
PSEUDO GUI MODE
===============
+99 -21
View File
@@ -285,8 +285,23 @@ Playback Control
``--pause``
Start the player in paused state.
``--shuffle``
``--shuffle=<yes|no>``
Play files in random order.
This works by shuffling the playlist at the following points:
1. At player startup before playback starts. The files specified on the
command line are shuffled. Note that the directories and
playlist files in these arguments are not expanded at this time, so
their contents are not shuffled until the situation 2 mentioned below
happens. To expand these lists at startup, use ``--playlist``.
2. When loading a directory or playlist file, either with ``loadlist``
command or by playing a playlist file in the current playlist. The
items in the loaded playlist are shuffled before they are added to
the current playlist. Other existing items are not shuffled.
3. When ``--loop-playlist`` is enabled, the player performs a shuffle
after looping.
``--playlist-start=<auto|index>``
Set which file on the internal playlist to start playback with. The index
@@ -312,6 +327,13 @@ Playback Control
different demuxers and will not work with this option. They still can be
played directly, without using this option.
This option differs from specifying playlist files directly as arguments.
The playlists specified by ``--playlist`` are expanded at startup, while
playlist files specified directly as arguments are expanded only when the
list is being played. Note that this expansion is not recursive, except in
the case of ``--playlist=<directory>``, where expansion follows the
``--directory-mode`` option.
By default, mpv doesn't play URLs from playlists which are considered
unsafe. If you trust the playlist file, you can disable any security checks
with ``--load-unsafe-playlists``. Because playlists can load other playlist
@@ -335,6 +357,12 @@ Playback Control
local files, such as special protocols like ``avdevice://`` (which are
inherently unsafe).
``--playlist-inherit-options=<yes|no|current>``
Whether the per-file options of a playlist file are inherited by its items
when the playlist file is resolved and expanded (default: no). The value
``current`` means that for playlists created by ``--autocreate-playlist``,
only the file from which the playlist is created inherits the options.
``--chapter-merge-threshold=<number>``
Threshold for merging almost consecutive ordered chapter parts in
milliseconds (default: 100). Some Matroska files with ordered chapters
@@ -1541,20 +1569,27 @@ Video
Runtime changes to this are ignored (the current option value is used
whenever the renderer is created).
``--hwdec-extra-frames=<N>``
Number of GPU frames hardware decoding should preallocate (default: see
``--list-options`` output). If this is too low, frame allocation may fail
during decoding, and video frames might get dropped and/or corrupted.
Setting it too high simply wastes GPU memory and has no advantages.
``--hwdec-extra-frames=<auto|N>``
Number of extra GPU frames hardware decoding should preallocate, on top of
what the codec itself requires (default: ``auto``).
This value is used only for hardware decoding APIs which require
preallocating surfaces (known examples include ``d3d11va`` and ``vaapi``).
For other APIs, frames are allocated as needed. The details depend on the
libavcodec implementations of the hardware decoders.
The required number of surfaces depends on dynamic runtime situations. The
default is a fixed value that is thought to be sufficient for most uses. But
in certain situations, it may not be enough.
In ``auto`` mode, the number is derived from how many frames the player
and the VO may reference at the same time. For example, enabling
``--interpolation`` increases it in proportion to the ``--tscale`` filter
radius, and VO deinterlacing adds the temporal reference frames it needs.
The value is determined when the decoder is initialized; runtime option
changes that increase frame requirements do not resize the pool, so set a
fixed value if you intend to switch such options during playback.
Setting a fixed value overrides the automatic sizing in both directions.
If it is too low, frame allocation may fail during decoding, and video
frames might get dropped and/or corrupted. Setting it too high simply
wastes GPU memory and has no advantages.
``--hwdec-image-format=<name>``
Set the internal pixel format used by hardware decoding via ``--hwdec``
@@ -1794,6 +1829,10 @@ Video
inserted deinterlacing filters, and that this will make video look worse if
it's not actually interlaced.
Enabling video output deinterlacing at runtime may require setting
``--hwdec-extra-frames``, as the hardware decoder's surface pool is sized
at decoder initialization.
``--deinterlace-field-parity=<tff|bff|auto>``
Specify the field parity/order when deinterlacing (default: auto).
Each frame of an interlaced video is divided into two fields, which are
@@ -2518,6 +2557,10 @@ Subtitles
This affects ASS subtitles as well, and may lead to incorrect subtitle
rendering. Use with care, or use ``--sub-font-size`` instead.
``--secondary-sub-scale=<0-100>``
Factor for the secondary subtitle font size. This is similar to
``--sub-scale`` but for secondary subtitles (default: 1).
``--sub-scale-signs=<yes|no>``
When set to yes, also apply ``--sub-scale`` to typesetting (or "signs").
When this is set to no, ``--sub-scale`` is only applied to dialogue. The
@@ -2815,10 +2858,12 @@ Subtitles
This also affects image subtitle brightness in HDR tone mapping with
``--blend-subtitles=<yes|video>``.
``--sub-hdr-peak=<sdr|10-10000>``
``--sub-hdr-peak=<auto|sdr|10-10000>``
Controls the text subtitle and OSD diffuse white level in cd/m² (nits)
for HDR output (default: sdr). ``sdr`` is 203 cd/m² for standard SDR white.
(``--vo=gpu-next`` only)
for HDR output (default: auto). In ``auto`` mode, subtitles and OSD follow
the reference white (see ``--hdr-reference-white``), matching the diffuse
white level used for SDR content. ``sdr`` forces 203 cd/m² for standard SDR
white. (``--vo=gpu-next`` only)
This also affects text subtitle brightness in HDR tone mapping with
``--blend-subtitles=<yes|video>``.
@@ -3872,6 +3917,12 @@ Window
Disc Devices
------------
``--disc-menu=<yes|no>``
When set, opening ``dvd://`` or ``bd://`` boots into the disc's interactive
menu instead of automatically playing the longest title (default: ``no``).
The menu can also be reached at any time via the synthetic "Disc Menu"
entry in the editions/titles list, or with ``discnav menu`` command.
``--cdda-device=<path>``
Specify the CD device for CDDA playback. The default device path depends on
the OS. See the `OPTICAL DRIVES`_ section.
@@ -3886,6 +3937,21 @@ Disc Devices
``mpv dvd:// --dvd-device=/path/to/dvd/``
``--dvda-device=<path>``
Specify the DVD-Audio device or .iso filename for ``dvda://`` playback.
You can also specify a directory that contains files previously copied
directly from a DVD-Audio disc. The default device path depends on
the OS. See the `OPTICAL DRIVES`_ section.
.. admonition:: Example
``mpv dvda:// --dvda-device=/path/to/dvda/``
``--dvda-page=<-1|0-...>``
Force the still picture page shown during DVD-Audio playback, instead of
the page scheduled for the current playback position (default: -1, use
the scheduled page).
``--bluray-device=<path>``
Specify the Blu-ray disc location. Must be a directory with Blu-ray
structure. The default device path depends on the OS. See the
@@ -4549,14 +4615,16 @@ Input
.. admonition:: Example
``--input-ipc-client=fd://123``
``--input-ipc-client=handle://123``
.. note::
To use this option on Windows, the fd must refer to a wrapped
(created by ``_open_osfhandle``) named pipe server handle with a client
already connected. The named pipe must be created duplex with overlapped
IO and inheritable handles. The program communicates with mpv through
the client handle.
already connected. Alternatively, the Windows HANDLE can be passed by
prefixing it with handle:// if mpv inherited it from the parent. The
named pipe must be created duplex with overlapped IO and inheritable
handles. The program communicates with mpv through the client handle.
.. warning::
@@ -5751,14 +5819,12 @@ TLS/connection diagnostics), set ``--msg-level=curl=trace``.
``--curl-enabled=<yes|no>``
Enable the libcurl-based network backend (default: ``yes``).
Defaults to ``no`` with libavformat < 62.15.101, which has a nested IO
Defaults to ``no`` on known older FFmpeg versions, which have a nested IO
cleanup bug that can cause crashes or memory leaks. The issue happens only
on transfer failures or aborts, can be enabled if you don't mind possible
stability issues.
on transfer failures or aborts.
``--curl-http-version=<auto|1.0|1.1|2|2tls|2-prior-knowledge|3|3only>``
Select the maximum HTTP protocol version libcurl is allowed to negotiate.
If libcurl was built without HTTP/3 support, it will fallback to ``auto``.
(default: ``auto``, i.e. let libcurl pick)
``--curl-max-redirects=<0-100>``
@@ -5950,6 +6016,10 @@ them.
being the smoothest/blurriest and ``oversample`` being the sharpest/least
smooth.
Switching to a filter with a larger radius at runtime may require setting
``--hwdec-extra-frames``, as the hardware decoder's surface pool is sized
at decoder initialization.
``--scale-param1=<value>``, ``--scale-param2=<value>``, ``--cscale-param1=<value>``, ``--cscale-param2=<value>``, ``--dscale-param1=<value>``, ``--dscale-param2=<value>``, ``--tscale-param1=<value>``, ``--tscale-param2=<value>``
Set filter parameters. By default, these are set to the special string
``default``, which maps to a scaler-specific default value. Ignored if the
@@ -6102,6 +6172,9 @@ them.
the video along the temporal axis. The filter used can be controlled using
the ``--tscale`` setting.
Enabling this at runtime may require setting ``--hwdec-extra-frames``, as
the hardware decoder's surface pool is sized at decoder initialization.
``--interpolation-threshold=<0..1,-1>``
Threshold below which frame ratio interpolation gets disabled (default:
``0.01``). This is calculated as ``abs(disphz/vfps - 1) < threshold``,
@@ -7534,11 +7607,16 @@ them.
encoding into the target colorspace, so after the application of
``--target-trc``. (Only for ``--vo=gpu-next``)
``--hdr-reference-white=<auto|10-1000000>``
``--hdr-reference-white=<auto|10-10000>``
Specifies the assumed peak brightness of the mastering display for SDR
content, in cd/m² (nits). This is used as HDR diffuse white level for SDR
content. Essentially this is the SDR brightness in HDR container.
Default is 203 cd/m². (Only for ``--vo=gpu-next``)
(Only for ``--vo=gpu-next``)
In ``auto`` mode (default), the reference white luminance is queried from
the system. This is currently only supported on Windows. If the system does
not provide a value, 203 cd/m² is assumed. (``auto`` mode works only
with libplacebo >= 371)
.. note::
-6
View File
@@ -499,12 +499,6 @@ Configurable Options
Set to ``yes`` to reduce festivity (i.e. disable santa hat in December.)
``livemarkers``
Default: yes
Update chapter markers positions on duration changes, e.g. live streams.
The updates are unoptimized - consider disabling it on very low-end systems.
``chapter_fmt``
Default: ``Chapter: %s``
+7
View File
@@ -68,10 +68,17 @@ By default select.lua's script bindings are bound to key sequences starting with
``g`` listed in `Keyboard Control`_. The names of the script bindings listed
below can be used to bind them to different keys.
All script bindings accept an optional ``keep-open`` argument that
prevents the menu from closing after a selection.
.. admonition:: Example to rebind playlist selection in input.conf
Ctrl+p script-binding select/select-playlist
.. admonition:: Example to keep the track menu open in input.conf
g-t script-binding select/select-track keep-open
Available script bindings are:
``select-playlist``
+8 -1
View File
@@ -323,6 +323,12 @@ Available mpv-only filters are:
Whether or not to include HDR10+ metadata (default: yes). If
disabled, any HDR10+ metadata will be stripped from frames.
``<enhancement-layer=yes|no>``
Whether or not to apply the image enhancement layer (default: yes).
If disabled, the enhancement-layer frame paired with each base-layer
frame is discarded. Currently this controls Dolby Vision Profile 7 FEL
application.
``<min-luma>``
Set the minimum luminance value for the mastering display metadata.
This is a float value in nits (cd/m²).
@@ -760,7 +766,8 @@ Available mpv-only filters are:
of the d3d11 frame.
``nvidia-true-hdr``
Enable NVIDIA RTX Video HDR processing.
Enable NVIDIA RTX Video HDR processing. This only converts SDR to HDR,
so it is ignored when the source is already HDR.
``amf_frc``
AMD Frame Rate Conversion filter. Requires AMD hardware and drivers
+3 -3
View File
@@ -293,7 +293,7 @@ Available video output drivers are:
Instruct SDL to switch the monitor video mode when going fullscreen.
``dmabuf-wayland``
Experimental Wayland output driver designed for use with either drm stateless
Special Wayland output driver designed for use with either drm stateless
or VA API hardware decoding. The driver is designed to avoid any GPU to CPU copies,
and to perform scaling and color space conversion using fixed-function hardware,
if available, rather than GPU shaders. This frees up GPU resources for other tasks.
@@ -309,8 +309,8 @@ Available video output drivers are:
This driver is not required for mpv to work on Wayland. ``vo=gpu``
and ``vo=gpu-next`` will switch to the appropriate Wayland context
automatically. This driver is experimental and generally lower quality
than ``gpu``/``gpu-next``.
automatically. This video output is geared towards power efficiency
and has generally lower quality than ``gpu``/``gpu-next``.
``vaapi``
Intel VA API video output driver with support for hardware decoding. Note
+338
View File
@@ -0,0 +1,338 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Moe Ghoul>, 1 April 1989
Moe Ghoul, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
+501
View File
@@ -0,0 +1,501 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library 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.
This library 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 this library; if not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Moe Ghoul>, 1 April 1990
Moe Ghoul, President of Vice
That's all there is to it!
+6
View File
@@ -0,0 +1,6 @@
# License texts
- `GPL-2.0-or-later.txt`:MPV 默认构建所适用的 GPL v2 文本。
- `LGPL-2.1-or-later.txt`:MPV 合规 LGPL-only 构建及 FFmpeg 默认构建所涉及的 LGPL v2.1 文本。
Phase 1 已锁定第三方源码,实际版本、原始许可证文件位置和源码仓库见 `LICENSE_AUDIT.md`。正式发行包必须从对应锁定 commit 收集原始许可证与版权声明;本目录当前只保存 MPV/FFmpeg 核心 GPL/LGPL 正文,不得作为完整 Release notices 使用。
+38
View File
@@ -0,0 +1,38 @@
# License Audit
审计基线:MPV `182fa6ca49f455cadb884858f386e2f00540aeb7`,Phase 1 实际构建。本文是工程记录,不构成法律意见。
## Conclusion
Velix-MPV-Core 与最终 `libmpv` 按 MPV 默认 GPL-2.0-or-later 路线管理。独立仓库及私有 Gitea 不改变 upstream 义务,也不使许可证自动隔离。当前不得宣称 LGPL-only。
FFmpeg 实际配置为 `CONFIG_GPL=0`、`CONFIG_VERSION3=0`、`CONFIG_NONFREE=0`。所有第三方库静态进入 `libmpv`,因此正式发布仍需满足 LGPL 组件的源码、版权、修改说明与可重新链接要求。
| Component | Version / Commit | License | Feature | Link | Modified | Source Location |
|---|---|---|---|---|---|---|
| MPV | 0.41.0-UNKNOWN / `182fa6ca49` | GPL-2.0-or-later default | libmpv, plain-gl, subtitles, network | Shared output | Yes, OHOS Meson detection | `upstream` remote / current repository |
| FFmpeg | `3acec0a1` | LGPL-2.1-or-later configuration | decode, demux, filter, network, HTTPS, OHCodec | Static | No | `https://git.ffmpeg.org/ffmpeg.git` |
| libplacebo | 7.360.1 / `cee9b076` | LGPL-2.1-or-later | core/shader generation | Static | No | `https://code.videolan.org/videolan/libplacebo.git` |
| libass | 0.17.5 / `4a05d812` | ISC | subtitle rendering | Static | No | `https://github.com/libass/libass.git` |
| FreeType | 2.14.3 / `0a0221a1` | FreeType License or GPL-2.0 | font rasterization | Static | No | `https://github.com/freetype/freetype.git` |
| HarfBuzz | 14.4.0 / `36cb489c` | MIT | text shaping | Static | No | `https://github.com/harfbuzz/harfbuzz.git` |
| FriBidi | 1.0.16 / `68162bab` | LGPL-2.1-or-later | bidirectional text | Static | No | `https://github.com/fribidi/fribidi.git` |
| OpenSSL | 3.6.4 / `d3c1b116` | Apache-2.0 | FFmpeg TLS/HTTPS | Static | No | `https://github.com/openssl/openssl.git` |
| zlib | SDK 1.2.13 | Zlib | FFmpeg/FreeType/MPV compression | System dynamic | No | HarmonyOS Native SDK |
| HarmonyOS Native SDK stubs | 6.1.1.125 / API 24 | SDK terms and header notices | libc, zlib, OHCodec, native window | System dynamic | No | DevEco Studio SDK |
## Phase 4 additions
Phase 4 新增的 MPV 代码(`video/harmony_hwdec.[ch]`、`video/out/hwdec/hwdec_ohcodec.c`、Playback Lab diagnostics)沿用本仓库 MPV GPL-2.0-or-later 路线;没有复制 FFmpeg 源码,也没有修改锁定的 FFmpeg commit。OHCodec capability、NativeWindow 和 NativeBuffer 仅通过 HarmonyOS Native SDK 的公开头文件/系统库使用。`harmony-ohcodec-gl` 是 disabled-by-default 的实验性 API probe,当前因 upstream output ownership blocker 不构成产品 interop 实现。
## Actual Link State
`libmpv.so.2.5.0` 的 `DT_NEEDED` 仅包含 `libc.so`、`libz.so`、`libnative_media_vdec.so`、`libnative_media_venc.so`、`libnative_media_codecbase.so`、`libnative_media_core.so`、`libnative_window.so`。其余依赖为静态链接。未发现 FFmpeg nonfree、Homebrew 或 macOS target library。
## Release Gates
1. 保存锁定 commit、构建选项、`config.h`、link state 与 archive 清单。
2. 随发行物提供对应许可证正文、版权声明、源码获取方式和修改说明。
3. 为静态 LGPL 组件提供满足许可证要求的重新链接材料。
4. 复核 OpenSSL、FFmpeg、MPV GPLv2+ 组合以及 HarmonyOS SDK redistributable 边界。
5. 商业发布前完成法务审核;`THIRD_PARTY_NOTICES.md` 当前仍不是最终 Release notice。
+18
View File
@@ -0,0 +1,18 @@
# Velix-MPV-Core Memory
- 项目:独立 HarmonyOS MPV 播放内核,禁止混入 Velix 主应用源码。
- 当前阶段:Phase 5 CODE COMPLETE / emulator-network validation pending;GPU INTEROP MAPPER 未进入;默认软件解码与网络播放公共 API 已构建验证。
- 基线:MPV `182fa6ca49f455cadb884858f386e2f00540aeb7`,分支 `harmony-port`。
- SDK:OpenHarmony API 24 / 6.1.1.125 / `aarch64-linux-ohos` / Clang 15.0.4。
- 关键结论:首版 GLES+EGL+libmpv Render API;OHAudio AO;官方 FFmpeg OHCodec 路线。
- Phase 1 产物:`build/ohos-arm64/prefix/lib/libmpv.so.2.5.0`,AArch64 ELF,OpenGL Render API symbols 完整;当前默认重建大小约 34.3 MB。
- 远程:`origin` 为私有 Gitea 仓库,默认分支 `harmony-port`;`upstream` 为 MPV 官方仓库。
- Phase 4.5:独立 `../Velix-FFmpeg-Harmony`,分支 `harmony-ohcodec-export`,远程 tip `990614364f`;MPV 通过 `scripts/build-mpv-ohos-ffmpeg-harmony.sh` 选择其 prefix。
- Phase 2:`playback_lab/` 独立 HAP,XComponent/OHNativeWindow/EGL/libmpv/软件解码已通;实体设备待验。
- Phase 3:`audio/out/ao_harmony.c` + OHAudio pull callback、pause/flush/clock/metrics 已通;实体机路由/A-V sync/压力待验。
- Phase 4:`IMGFMT_OHCODEC`、`hwdec=ohcodec`、API24 capability adapter、direct-surface experimental driver、软件构建矩阵、Playback Lab 诊断已提交;FFmpeg 保持 upstream。
- Phase 4 边界:官方 OHCodec hwcontext/decoder 仅提供 surface 输出,NativeBuffer→EGL 仅做禁用默认 API probe,未宣称零拷贝/HDR/真实硬件帧;详见 `DOCS/OHCODEC_GPU_INTEROP_DESIGN.md`。
- Phase 4.5 边界:FFmpeg export 模式不调用 RenderOutputBuffer,NativeBuffer/accessor/refcount/build probes 已完成;crop/fence/真实硬解仍需设备验证,`harmony-ohcodec-gl` 继续 disabled。
- Phase 5:include/mpv/harmony_playback.h + player/harmony_playback.c 提供不暴露 mpv 类型的稳定 PlaybackRequest/Track/Media/Network/State/Error API;Playback Lab 已接入请求 headers、脱敏、轨道、字幕和诊断面板。
- Phase 5 构建:默认、software、FFmpeg-Harmony 三套 AArch64 libmpv 构建及 Native/ArkTS 编译通过;HAP PackageHap 因当前环境缺 Java Runtime 未完成,网络/HLS/实体设备运行待验证。
- 索引:`memory/projects.md`、`memory/infra.md`、`memory/lessons.md`、`memory/2026-08-28.md`。
+20
View File
@@ -0,0 +1,20 @@
# Third-Party Notices
Velix-MPV-Core 基于 MPV upstream Git 历史维护,当前默认按 GNU GPL v2 or later 管理。MPV 完整版权与许可证说明保留在 `Copyright`、`LICENSE.GPL`、`LICENSE.LGPL` 与 `LICENSES/`。
Phase 1 实际构建包含:
| Component | Version / Commit | License | Link | Modified |
|---|---|---|---|---|
| FFmpeg | `3acec0a1` | LGPL-2.1-or-later configuration | Static | No |
| libplacebo | 7.360.1 | LGPL-2.1-or-later | Static | No |
| libass | 0.17.5 | ISC | Static | No |
| FreeType | 2.14.3 | FreeType License / GPL-2.0 dual | Static | No |
| HarfBuzz | 14.4.0 | MIT | Static | No |
| FriBidi | 1.0.16 | LGPL-2.1-or-later | Static | No |
| OpenSSL | 3.6.4 | Apache-2.0 | Static | No |
| zlib | HarmonyOS SDK 1.2.13 | Zlib | System dynamic | No |
源码仓库、完整 commit、启用功能与实际链接状态见 `UPSTREAM.md`、`LICENSE_AUDIT.md`、`DOCS/PHASE1_DEPENDENCY_MATRIX.md`。依赖源码由 `scripts/fetch-phase1-sources.sh` 获取,构建过程不修改其源码。
本文是 Phase 1 工程清单,不是最终发行 notice。正式发布必须补齐各组件原始版权声明、许可证正文、源码交付/获取方式、修改说明及 LGPL 静态链接所需的重新链接材料。
+10 -8
View File
@@ -218,30 +218,32 @@ def process_vulkan_loader(binary, loader_name, loader_relative_folder, library_n
os.path.join("/etc", loader_relative_folder),
os.path.join(os.path.expanduser("~"), ".local/share", loader_relative_folder),
os.path.join("/usr/local/share", loader_relative_folder),
os.path.join("/usr/share/vulkan", loader_relative_folder),
os.path.join("/usr/share", loader_relative_folder),
os.path.join(get_homebrew_prefix(), "etc", loader_relative_folder),
os.path.join(get_homebrew_prefix(), "share", loader_relative_folder), # old location
]
loader_system_folder = ""
loader_system_path = ""
for loader_system_search_folder in loader_system_search_folders:
if os.path.exists(loader_system_search_folder):
loader_system_folder = loader_system_search_folder
break
if os.path.exists(os.path.join(loader_system_folder, loader_name)):
loader_system_path = os.path.join(loader_system_folder, loader_name)
break
if not loader_system_folder:
print(">>> could not find loader folder " + loader_relative_folder)
return
loader_bundle_folder = os.path.join(resources_path(binary), loader_relative_folder)
loader_system_path = os.path.join(loader_system_folder, loader_name)
loader_bundle_path = os.path.join(loader_bundle_folder, loader_name)
library_relative_folder = "../../../Frameworks/"
if not os.path.exists(loader_system_path):
if not loader_system_path:
print(">>> could not find loader " + loader_name)
return
loader_bundle_folder = os.path.join(resources_path(binary), loader_relative_folder)
loader_bundle_path = os.path.join(loader_bundle_folder, loader_name)
library_relative_folder = "../../../Frameworks/"
if not os.path.exists(loader_bundle_folder):
os.makedirs(loader_bundle_folder)
+62 -37
View File
@@ -28,13 +28,13 @@ from math import ldexp
elements_ebml = (
"EBML, 1a45dfa3, sub", (
"EBMLVersion, 4286, uint",
"EBMLReadVersion, 42f7, uint",
"EBMLMaxIDLength, 42f2, uint",
"EBMLMaxSizeLength, 42f3, uint",
"EBMLVersion, 4286, uint, default=1",
"EBMLReadVersion, 42f7, uint, default=1",
"EBMLMaxIDLength, 42f2, uint, default=4",
"EBMLMaxSizeLength, 42f3, uint, default=8",
"DocType, 4282, str",
"DocTypeVersion, 4287, uint",
"DocTypeReadVersion, 4285, uint",
"DocTypeVersion, 4287, uint, default=1",
"DocTypeReadVersion, 4285, uint, default=1",
),
"CRC32, bf, binary",
@@ -52,10 +52,10 @@ elements_matroska = (
),
"Info*, 1549a966, sub", (
"SegmentUID, 73a4, binary",
"PrevUID, 3cb923, binary",
"NextUID, 3eb923, binary",
"TimecodeScale, 2ad7b1, uint",
"SegmentUID, 73a4, binary, length=16",
"PrevUID, 3cb923, binary, length=16",
"NextUID, 3eb923, binary, length=16",
"TimecodeScale, 2ad7b1, uint, default=1000000",
"DateUTC, 4461, sint",
"Title, 7ba9, str",
"MuxingApp, 4d80, str",
@@ -72,7 +72,7 @@ elements_matroska = (
"DiscardPadding, 75A2, sint",
"BlockAdditions, 75A1, sub", (
"BlockMore*, A6, sub", (
"BlockAddID, EE, uint",
"BlockAddID, EE, uint, default=1",
"BlockAdditional, A5, binary",
),
),
@@ -85,44 +85,44 @@ elements_matroska = (
"TrackNumber, d7, uint",
"TrackUID, 73c5, uint",
"TrackType, 83, uint",
"FlagEnabled, b9, uint",
"FlagDefault, 88, uint",
"FlagEnabled, b9, uint, default=1",
"FlagDefault, 88, uint, default=1",
"FlagForced, 55aa, uint",
"FlagHearingImpaired, 55ab, uint",
"FlagVisualImpaired, 55ac, uint",
"FlagOriginal, 55ae, uint",
"FlagCommentary, 55af, uint",
"FlagLacing, 9c, uint",
"FlagLacing, 9c, uint, default=1",
"MinCache, 6de7, uint",
"MaxCache, 6df8, uint",
"DefaultDuration, 23e383, uint",
"TrackTimecodeScale, 23314f, float",
"TrackTimecodeScale, 23314f, float, default=1",
"MaxBlockAdditionID, 55ee, uint",
"Name, 536e, str",
"Language, 22b59c, str",
"Language, 22b59c, str, default=eng",
"LanguageBCP47, 22b59d, str",
"CodecID, 86, str",
"CodecPrivate, 63a2, binary",
"CodecName, 258688, str",
"CodecDecodeAll, aa, uint",
"CodecDecodeAll, aa, uint, default=1",
"CodecDelay, 56aa, uint",
"SeekPreRoll, 56bb, uint",
"Video, e0, sub", (
"FlagInterlaced, 9a, uint",
"PixelWidth, b0, uint",
"PixelHeight, ba, uint",
"DisplayWidth, 54b0, uint",
"DisplayHeight, 54ba, uint",
"DisplayWidth, 54b0, uint, default=context",
"DisplayHeight, 54ba, uint, default=context",
"DisplayUnit, 54b2, uint",
"PixelCropTop, 54bb, uint",
"PixelCropLeft, 54cc, uint",
"PixelCropRight, 54dd, uint",
"PixelCropBottom, 54aa, uint",
"FrameRate, 2383e3, float",
"ColourSpace, 2eb524, binary",
"ColourSpace, 2eb524, binary, length=4",
"StereoMode, 53b8, uint",
"Colour, 55b0, sub", (
"MatrixCoefficients, 55B1, uint",
"MatrixCoefficients, 55B1, uint, default=2",
"BitsPerChannel, 55B2, uint",
"ChromaSubsamplingHorz, 55B3, uint",
"ChromaSubsamplingVert, 55B4, uint",
@@ -131,8 +131,8 @@ elements_matroska = (
"ChromaSitingHorz, 55B7, uint",
"ChromaSitingVert, 55B8, uint",
"Range, 55B9, uint",
"TransferCharacteristics, 55BA, uint",
"Primaries, 55BB, uint",
"TransferCharacteristics, 55BA, uint, default=2",
"Primaries, 55BB, uint, default=2",
"MaxCLL, 55BC, uint",
"MaxFALL, 55BD, uint",
"MasteringMetadata, 55D0, sub", (
@@ -157,15 +157,15 @@ elements_matroska = (
),
),
"Audio, e1, sub", (
"SamplingFrequency, b5, float",
"OutputSamplingFrequency, 78b5, float",
"Channels, 9f, uint",
"SamplingFrequency, b5, float, default=8000",
"OutputSamplingFrequency, 78b5, float, default=context",
"Channels, 9f, uint, default=1",
"BitDepth, 6264, uint",
),
"ContentEncodings, 6d80, sub", (
"ContentEncoding*, 6240, sub", (
"ContentEncodingOrder, 5031, uint",
"ContentEncodingScope, 5032, uint",
"ContentEncodingScope, 5032, uint, default=1",
"ContentEncodingType, 5033, uint",
"ContentCompression, 5034, sub", (
"ContentCompAlgo, 4254, uint",
@@ -215,12 +215,12 @@ elements_matroska = (
"ChapterTimeStart, 91, uint",
"ChapterTimeEnd, 92, uint",
"ChapterFlagHidden, 98, uint",
"ChapterFlagEnabled, 4598, uint",
"ChapterSegmentUID, 6e67, binary",
"ChapterFlagEnabled, 4598, uint, default=1",
"ChapterSegmentUID, 6e67, binary, length=16",
"ChapterSegmentEditionUID, 6ebc, uint",
"ChapterDisplay*, 80, sub", (
"ChapString, 85, str",
"ChapLanguage*, 437c, str",
"ChapLanguage*, 437c, str, default=eng",
"ChapLanguageBCP47*, 437d, str",
"ChapCountry*, 437e, str",
),
@@ -230,7 +230,7 @@ elements_matroska = (
"Tags*, 1254c367, sub", (
"Tag*, 7373, sub", (
"Targets, 63c0, sub", (
"TargetTypeValue, 68ca, uint",
"TargetTypeValue, 68ca, uint, default=50",
"TargetType, 63ca, str",
"TargetTrackUID, 63c5, uint",
"TargetEditionUID, 63c9, uint",
@@ -239,10 +239,10 @@ elements_matroska = (
),
"SimpleTag*, 67c8, sub", (
"TagName, 45a3, str",
"TagLanguage, 447a, str",
"TagLanguage, 447a, str, default=und",
"TagLanguageBCP47, 447b, str",
"TagString, 4487, str",
"TagDefault, 4484, uint",
"TagDefault, 4484, uint, default=1",
),
),
),
@@ -266,13 +266,16 @@ def camelcase_to_words(name):
class MatroskaElement:
def __init__(self, name, elid, valtype, namespace):
def __init__(self, name, elid, valtype, namespace, length=None,
default=None):
self.name = name
self.definename = f"{namespace}_ID_{name.upper()}"
self.fieldname = camelcase_to_words(name)
self.structname = "ebml_" + self.fieldname
self.elid = elid
self.valtype = valtype
self.length = int(length or "0")
self.default = default
if valtype == "sub":
self.ebmltype = "EBML_TYPE_SUBELEMENTS"
self.valname = "struct " + self.structname
@@ -291,17 +294,38 @@ class MatroskaElement:
self.subelements = subelements
self.subids = {x[0].elid for x in subelements}
def default_initializer(self):
if self.default is None:
return "false, {0}"
if self.default == "context":
return "true, {0}"
if self.valtype == "uint":
return f"false, {{.u = {int(self.default)}}}"
if self.valtype == "sint":
return f"false, {{.i = {int(self.default)}}}"
if self.valtype == "float":
return f"false, {{.f = {float(self.default)}}}"
if self.valtype == "str":
return f'false, {{.s = "{self.default}"}}'
raise SyntaxError("Default not supported for type " + self.valtype)
elementd: dict[str, MatroskaElement] = {}
elementlist: list[MatroskaElement] = []
def parse_elems(elements, namespace):
subelements = []
for el in elements:
if isinstance(el, str):
name, hexid, eltype = (x.strip() for x in el.split(","))
name, hexid, eltype, *rest = (x.strip() for x in el.split(","))
attrs = {}
for extra in rest:
key, sep, value = extra.partition("=")
if not sep or key not in ("length", "default"):
raise SyntaxError("Invalid element attribute " + extra)
attrs[key] = value
hexid = hexid.lower()
multiple = name.endswith("*")
name = name.strip("*")
new = MatroskaElement(name, hexid, eltype, namespace)
new = MatroskaElement(name, hexid, eltype, namespace, **attrs)
elementd[hexid] = new
elementlist.append(new)
subelements.append((new, multiple))
@@ -363,7 +387,8 @@ def generate_c_definitions(out):
printf(out, "}};")
printf(out, "#undef N")
else:
printf(out, f'E("{el.name}", {el.fieldname}, {el.ebmltype})')
printf(out, f'E("{el.name}", {el.fieldname}, {el.ebmltype}, '
f'{el.length}, {el.default_initializer()})')
def read(s, length):
t = s.read(length)
+48
View File
@@ -0,0 +1,48 @@
# Upstream 基线
| 项目 | 值 |
|---|---|
| MPV Repository | `https://github.com/mpv-player/mpv.git` |
| MPV Current Commit | `182fa6ca49f455cadb884858f386e2f00540aeb7` |
| Commit Date | 2026-08-26 |
| MPV Version / Tag | `MPV_VERSION=0.41.0-UNKNOWN`;`git describe=git-release-292-g182fa6ca49`;HEAD 无 tag |
| Fork Date | 2026-08-28 |
| Development Branch | `harmony-port` |
| HarmonyOS Target | HarmonyOS NEXT / OpenHarmony Native API 24 |
| HarmonyOS SDK | DevEco Studio 内置 OpenHarmony SDK `6.1.1.125`,API 24 |
| Target Architecture | `arm64-v8a` / `aarch64-linux-ohos` |
| Compiler | OHOS Clang `15.0.4` (`99548401bd80576251755b185ab95945e5fe7884`) |
| Build System | MPV Meson `>=1.3.0` + Ninja;依赖使用各自原生构建系统 |
Velix-MPV-Core 是基于 MPV upstream Git 历史维护的独立派生仓库,不是 GitHub Fork。仓库完整保留 upstream history、基线 commit、MPV 版权与许可证文件,并持续保留 `upstream` remote。
## FFmpeg 基线
| 项目 | 值 |
|---|---|
| Repository | `https://git.ffmpeg.org/ffmpeg.git` |
| Commit | `3acec0a1af2dda0a0838689b8b8649e7deb080a0` |
| Commit Date | 2026-08-28 |
| git describe | `3acec0a`(该 commit 无可达 tag) |
| License Mode | LGPL-2.1-or-later;`CONFIG_GPL=0`、`CONFIG_VERSION3=0`、`CONFIG_NONFREE=0` |
| Build | 静态 PIC;禁用 programs、docs、debug、avdevice 与 autodetect |
| Enabled | network、OpenSSL、zlib、OHCodec |
| OHCodec | `AV_HWDEVICE_TYPE_OHCODEC`、`AV_PIX_FMT_OHCODEC`、H.264/HEVC OH decoder/encoder 已编译 |
FFmpeg configure 参数由 `scripts/build-ffmpeg-ohos.sh` 固定。当前不创建 Velix FFmpeg fork;只有 upstream 出现需要长期维护的真实 blocker 时才重新评估。
## 远程状态
- `upstream` 已指向 MPV 官方仓库。
- `origin` 已指向独立仓库 `https://git.leee.xin/leeexin/Velix-MPV-Core.git`。
- 远程默认分支为 `harmony-port`,同时保留与官方基线一致的 `master` 和 89 个官方标签。
```sh
git fetch upstream
git fetch origin
git log --oneline --decorate harmony-port..upstream/master
git diff harmony-port...upstream/master
git range-diff <old-upstream>..harmony-port upstream/master..<rebased-candidate>
```
基线更新必须先检查 `git log`、`git diff` 和适用的 `git range-diff`,再评估使用 merge 或 rebase,并同步更新本文件。禁止直接强制覆盖 `harmony-port`。
+98 -18
View File
@@ -22,6 +22,7 @@
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/intreadwrite.h>
#include <libavutil/opt.h>
#include "audio/aframe.h"
@@ -37,6 +38,7 @@
#include "options/options.h"
#define OUTBUF_SIZE 65536
#define TRUEHD_MAJOR_SYNC_WARN_INTERVAL 6000
struct spdifContext {
struct mp_log *log;
@@ -48,6 +50,10 @@ struct spdifContext {
uint8_t out_buffer[OUTBUF_SIZE];
bool need_close;
bool use_dts_hd;
bool codec_params_probed;
int codec_profile;
int codec_rate;
unsigned int dropped_startup_packets;
struct mp_aframe *fmt;
int sstride;
struct mp_aframe_pool *pool;
@@ -74,14 +80,12 @@ static int write_packet(void *p, const uint8_t *buf, int buf_size)
return buf_size;
}
// (called on both filter destruction _and_ if lavf fails to init)
static void ad_spdif_destroy(struct mp_filter *da)
static void close_lavf_context(struct spdifContext *spdif_ctx, bool write_trailer)
{
struct spdifContext *spdif_ctx = da->priv;
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
if (lavf_ctx) {
if (spdif_ctx->need_close)
if (write_trailer && spdif_ctx->need_close)
av_write_trailer(lavf_ctx);
if (lavf_ctx->pb)
av_freep(&lavf_ctx->pb->buffer);
@@ -89,9 +93,32 @@ static void ad_spdif_destroy(struct mp_filter *da)
avformat_free_context(lavf_ctx);
spdif_ctx->lavf_ctx = NULL;
}
spdif_ctx->need_close = false;
TA_FREEP(&spdif_ctx->fmt);
}
// (called on both filter destruction _and_ if lavf fails to init)
static void ad_spdif_destroy(struct mp_filter *da)
{
struct spdifContext *spdif_ctx = da->priv;
close_lavf_context(spdif_ctx, true);
mp_free_av_packet(&spdif_ctx->avpkt);
}
static void ad_spdif_reset(struct mp_filter *da)
{
struct spdifContext *spdif_ctx = da->priv;
close_lavf_context(spdif_ctx, false);
spdif_ctx->dropped_startup_packets = 0;
}
static bool truehd_has_major_sync(const AVPacket *pkt)
{
return pkt->size >= 8 && AV_RB32(pkt->data + 4) == 0xf8726fba;
}
static void determine_codec_params(struct mp_filter *da, AVPacket *pkt,
int *out_profile, int *out_rate)
{
@@ -160,9 +187,32 @@ static void determine_codec_params(struct mp_filter *da, AVPacket *pkt,
done:
av_frame_free(&frame);
avcodec_free_context(&ctx);
}
if (profile == AV_PROFILE_UNKNOWN)
MP_WARN(da, "Failed to parse codec profile.\n");
static bool dts_profile_maybe_hd(int profile)
{
return profile == AV_PROFILE_DTS_HD_HRA ||
profile == AV_PROFILE_DTS_HD_MA ||
profile == AV_PROFILE_DTS_HD_MA_X ||
profile == AV_PROFILE_DTS_HD_MA_X_IMAX ||
profile == AV_PROFILE_UNKNOWN;
}
// Some codecs do not need every probed value for spdif muxer setup.
static bool codec_params_warning_needed(struct spdifContext *spdif_ctx,
int profile, int rate)
{
switch (spdif_ctx->codec_id) {
case AV_CODEC_ID_DTS:
if (spdif_ctx->use_dts_hd && dts_profile_maybe_hd(profile))
return profile == AV_PROFILE_UNKNOWN;
return rate <= 0;
case AV_CODEC_ID_AC3:
return rate <= 0;
default:
return false;
}
}
static int init_filter(struct mp_filter *da)
@@ -171,10 +221,20 @@ static int init_filter(struct mp_filter *da)
AVPacket *pkt = spdif_ctx->avpkt;
int profile = AV_PROFILE_UNKNOWN;
int c_rate = 0;
determine_codec_params(da, pkt, &profile, &c_rate);
MP_VERBOSE(da, "In: profile=%d samplerate=%d\n", profile, c_rate);
if (!spdif_ctx->codec_params_probed) {
spdif_ctx->codec_profile = AV_PROFILE_UNKNOWN;
determine_codec_params(da, pkt, &spdif_ctx->codec_profile,
&spdif_ctx->codec_rate);
spdif_ctx->codec_params_probed = true;
MP_VERBOSE(da, "In: profile=%d samplerate=%d\n",
spdif_ctx->codec_profile, spdif_ctx->codec_rate);
if (codec_params_warning_needed(spdif_ctx, spdif_ctx->codec_profile,
spdif_ctx->codec_rate))
MP_WARN(da, "Failed to parse codec parameters.\n");
}
int profile = spdif_ctx->codec_profile;
int c_rate = spdif_ctx->codec_rate;
AVFormatContext *lavf_ctx = avformat_alloc_context();
if (!lavf_ctx)
@@ -225,19 +285,13 @@ static int init_filter(struct mp_filter *da)
num_channels = 2;
break;
case AV_CODEC_ID_DTS: {
bool is_hd = profile == AV_PROFILE_DTS_HD_HRA ||
profile == AV_PROFILE_DTS_HD_MA ||
profile == AV_PROFILE_DTS_HD_MA_X ||
profile == AV_PROFILE_DTS_HD_MA_X_IMAX ||
profile == AV_PROFILE_UNKNOWN;
// Apparently, DTS-HD over SPDIF is specified to be 7.1 (8 channels)
// for DTS-HD MA, and stereo (2 channels) for DTS-HD HRA. The bit
// streaming rate as well as the signaled channel count are defined
// based on this value.
int dts_hd_spdif_channel_count = profile == AV_PROFILE_DTS_HD_HRA ?
2 : 8;
if (spdif_ctx->use_dts_hd && is_hd) {
if (spdif_ctx->use_dts_hd && dts_profile_maybe_hd(profile)) {
av_dict_set_int(&format_opts, "dtshd_rate",
dts_hd_spdif_channel_count * 96000, 0);
sample_format = AF_FORMAT_S_DTSHD;
@@ -318,6 +372,7 @@ static void ad_spdif_process(struct mp_filter *da)
struct demux_packet *mpkt = inframe.data;
struct mp_aframe *out = NULL;
double pts = mpkt->pts;
bool drop_packet = false;
if (!spdif_ctx->avpkt) {
spdif_ctx->avpkt = av_packet_alloc();
@@ -326,6 +381,22 @@ static void ad_spdif_process(struct mp_filter *da)
mp_set_av_packet(spdif_ctx->avpkt, mpkt, NULL);
spdif_ctx->avpkt->pts = spdif_ctx->avpkt->dts = 0;
if (!spdif_ctx->lavf_ctx) {
// A fresh TrueHD spdif muxer needs a major sync to initialize its
// stream parameters, so wait for major sync before initializing.
if (spdif_ctx->codec_id == AV_CODEC_ID_TRUEHD &&
!truehd_has_major_sync(spdif_ctx->avpkt))
{
spdif_ctx->dropped_startup_packets++;
if (spdif_ctx->dropped_startup_packets %
TRUEHD_MAJOR_SYNC_WARN_INTERVAL == 0)
{
MP_WARN(da, "Still waiting for TrueHD major sync after "
"dropping %u packets.\n",
spdif_ctx->dropped_startup_packets);
}
drop_packet = true;
goto done;
}
if (init_filter(da) < 0)
goto done;
mp_assert(spdif_ctx->avpkt);
@@ -339,6 +410,12 @@ static void ad_spdif_process(struct mp_filter *da)
MP_ERR(da, "spdif mux error: '%s'\n", mp_strerror(AVUNERROR(ret)));
goto done;
}
if (spdif_ctx->dropped_startup_packets) {
MP_VERBOSE(da, "dropped %u TrueHD packet%s before major sync\n",
spdif_ctx->dropped_startup_packets,
spdif_ctx->dropped_startup_packets == 1 ? "" : "s");
spdif_ctx->dropped_startup_packets = 0;
}
out = mp_aframe_new_ref(spdif_ctx->fmt);
int samples = spdif_ctx->out_buffer_len / spdif_ctx->sstride;
@@ -360,6 +437,8 @@ done:
talloc_free(mpkt);
if (out) {
mp_pin_in_write(da->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
} else if (drop_packet) {
mp_filter_internal_mark_progress(da);
} else {
mp_filter_internal_mark_failed(da);
}
@@ -423,6 +502,7 @@ static const struct mp_filter_info ad_spdif_filter = {
.name = "ad_spdif",
.priv_size = sizeof(struct spdifContext),
.process = ad_spdif_process,
.reset = ad_spdif_reset,
.destroy = ad_spdif_destroy,
};
+232
View File
@@ -0,0 +1,232 @@
/*
* 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 <assert.h>
#include "common/common.h"
#include "common/msg.h"
#include "audio/aframe.h"
#include "filters/f_lavfi.h"
#include "filters/filter.h"
#include "filters/filter_internal.h"
#include "filters/frame.h"
#include "filters/user_filters.h"
#include "options/m_option.h"
#include "ta/ta_talloc.h"
struct f_opts {
char *filter;
};
struct priv {
struct f_opts *opts;
bool initialized;
bool fall_backed;
bool lavfi_ready;
double speed;
double set_speed;
double in_pts;
struct mp_filter *lavfi_filter;
const struct mp_filter_info *lavfi_filter_info;
};
static bool set_speed(struct mp_filter *f) {
struct priv *p = f->priv;
char *arg = talloc_asprintf(NULL, "%f", p->speed);
struct mp_filter_command cmd = {
.type = MP_FILTER_COMMAND_TEXT,
.target = p->opts->filter,
.cmd = "tempo",
.arg = arg,
};
bool ret = p->lavfi_filter_info->command(p->lavfi_filter, &cmd);
talloc_free(arg);
if (!ret) {
MP_FATAL(f, "failed to set %s=%s for lavfi filter %s\n", cmd.cmd, cmd.arg, p->opts->filter);
return false;
} else {
p->set_speed = p->speed;
return ret;
}
}
static bool init_lavfi_tempo(struct mp_filter *f)
{
struct priv *p = f->priv;
if (strcmp(p->opts->filter, "ascale") && strcmp(p->opts->filter, "atempo")) {
MP_FATAL(f, "'%s' is not recognized in this context, use 'ascale' or 'atempo'.\n",
p->opts->filter);
return false;
}
mp_assert(!p->lavfi_filter);
if (!mp_lavfi_is_usable(p->opts->filter, AVMEDIA_TYPE_AUDIO)) {
MP_WARN(f, "%s filter is not available, using atempo instead.\n", p->opts->filter);
p->opts->filter = "atempo";
p->fall_backed = true;
}
p->lavfi_filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO, p->opts->filter, NULL);
if (!p->lavfi_filter) {
MP_FATAL(f, "failed to create lavfi %s filter.\n", p->opts->filter);
return false;
}
p->lavfi_filter_info = mp_filter_get_info(p->lavfi_filter);
p->lavfi_ready = false;
p->initialized = true;
return true;
}
static void af_lavfi_tempo_reset(struct mp_filter *f)
{
struct priv *p = f->priv;
p->in_pts = MP_NOPTS_VALUE;
p->lavfi_ready = false;
p->set_speed = 1.0;
p->lavfi_filter_info->reset(p->lavfi_filter);
}
static void af_lavfi_tempo_process(struct mp_filter *f)
{
struct priv *p = f->priv;
if (!p->initialized && !init_lavfi_tempo(f))
return;
if (p->lavfi_ready && p->set_speed != p->speed) {
set_speed(f);
}
if (mp_pin_can_transfer_data(p->lavfi_filter->pins[0], f->ppins[0])) {
struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
if (frame.type == MP_FRAME_AUDIO) {
struct mp_aframe *aframe = frame.data;
p->in_pts = mp_aframe_get_pts(aframe);
}
if (!mp_pin_in_write(p->lavfi_filter->pins[0], frame)) {
MP_FATAL(f, "failed to move frame to internal lavfi filter\n");
} else {
p->lavfi_ready = true;
}
}
if (mp_pin_can_transfer_data(f->ppins[1], p->lavfi_filter->pins[1])) {
struct mp_frame frame = mp_pin_out_read(p->lavfi_filter->pins[1]);
if (frame.type == MP_FRAME_AUDIO) {
struct mp_aframe *aframe = frame.data;
mp_aframe_set_speed(aframe, p->set_speed);
if (p->in_pts != MP_NOPTS_VALUE) {
mp_aframe_set_pts(aframe, p->in_pts);
}
}
if (!mp_pin_in_write(f->ppins[1], frame)) {
MP_FATAL(f, "failed to move frame to internal lavfi filter\n");
} else {
p->lavfi_ready = true;
}
}
return;
}
static bool af_lavfi_tempo_command(struct mp_filter *f, struct mp_filter_command *cmd)
{
struct priv *p = f->priv;
switch (cmd->type) {
case MP_FILTER_COMMAND_SET_SPEED:
if (cmd->speed == p->speed) {
return true;
}
p->speed = cmd->speed;
return p->lavfi_ready ? set_speed(f) : true;
}
return false;
}
static void af_lavfi_tempo_destroy(struct mp_filter *f)
{
struct priv *p = f->priv;
if (p->fall_backed) {
// prevent ta_alloc abort since filter name is const
p->opts->filter = NULL;
}
if (p->lavfi_filter_info) {
p->lavfi_filter_info->destroy(p->lavfi_filter);
}
}
static const struct mp_filter_info af_lavfi_tempo_filter = {
.name = "lavfi_tempo",
.priv_size = sizeof(struct priv),
.process = af_lavfi_tempo_process,
.command = af_lavfi_tempo_command,
.reset = af_lavfi_tempo_reset,
.destroy = af_lavfi_tempo_destroy,
};
static struct mp_filter *af_lavfi_tempo_create(struct mp_filter *parent,
void *options)
{
struct mp_filter *f = mp_filter_create(parent, &af_lavfi_tempo_filter);
if (!f) {
talloc_free(options);
return NULL;
}
mp_filter_add_pin(f, MP_PIN_IN, "in");
mp_filter_add_pin(f, MP_PIN_OUT, "out");
struct priv *p = f->priv;
p->opts = talloc_steal(p, options);
p->speed = 1.0;
p->set_speed = 1.0;
p->in_pts = MP_NOPTS_VALUE;
if (!init_lavfi_tempo(f)) {
return NULL;
}
return f;
}
#define OPT_BASE_STRUCT struct f_opts
const struct mp_user_filter_entry af_lavfi_tempo = {
.desc = {
.description = "Tempo change with lavfi ascale or atempo filters",
.name = "lavfi-tempo",
.priv_size = sizeof(OPT_BASE_STRUCT),
.priv_defaults = &(const OPT_BASE_STRUCT) {
.filter = "atempo",
},
.options = (const struct m_option[]) {
{"filter", OPT_STRING(filter)},
{0}
},
},
.create = af_lavfi_tempo_create,
};
+4 -3
View File
@@ -389,9 +389,10 @@ static void af_scaletempo_process(struct mp_filter *f)
out_offset += s->bytes_stride;
// input stride
memcpy(s->buf_overlap,
s->buf_queue + bytes_off + s->bytes_stride,
s->bytes_overlap);
if (s->buf_overlap && s->bytes_overlap)
memcpy(s->buf_overlap,
s->buf_queue + bytes_off + s->bytes_stride,
s->bytes_overlap);
tf = s->frames_stride_scaled + s->frames_stride_error;
ti = (int)tf;
s->frames_stride_error = tf - ti;
+4
View File
@@ -55,6 +55,7 @@ extern const struct ao_driver audio_out_pcm;
extern const struct ao_driver audio_out_lavc;
extern const struct ao_driver audio_out_sdl;
extern const struct ao_driver audio_out_aaudio;
extern const struct ao_driver audio_out_harmony;
static const struct ao_driver * const audio_out_drivers[] = {
// native:
@@ -64,6 +65,9 @@ static const struct ao_driver * const audio_out_drivers[] = {
#if HAVE_AAUDIO
&audio_out_aaudio,
#endif
#if HAVE_HARMONY_AUDIO
&audio_out_harmony,
#endif
#if HAVE_AUDIOUNIT
&audio_out_audiounit,
#endif
+446
View File
@@ -0,0 +1,446 @@
/* HarmonyOS OHAudio output for the MPV pull-based audio contract. */
#include <stdatomic.h>
#include <stdint.h>
#include <string.h>
#include "config.h"
#include "ao.h"
#include "audio/format.h"
#include "audio/out/ao_harmony.h"
#include "common/msg.h"
#include "internal.h"
#include "osdep/threads.h"
#include "osdep/timer.h"
#if HAVE_HARMONY_AUDIO
#include <ohaudio/native_audiostreambuilder.h>
#include <ohaudio/native_audiorenderer.h>
#include <multimedia/native_audio_channel_layout.h>
struct priv {
OH_AudioRenderer *renderer;
mp_thread clock_thread;
bool clock_thread_valid;
atomic_bool stop_clock;
atomic_bool callback_enabled;
atomic_int callbacks_inflight;
_Atomic int64_t submitted_frames;
_Atomic int64_t played_frames;
_Atomic int64_t raw_played_frames;
_Atomic int64_t timestamp_origin_raw;
_Atomic int64_t audio_timestamp_ns;
_Atomic int64_t estimated_delay_ms;
_Atomic int64_t buffer_starvation;
_Atomic int64_t callback_count;
int32_t latency_ms;
int32_t frame_size;
int32_t channel_count;
uint64_t channel_layout;
};
static _Atomic int32_t g_state = MPV_HARMONY_AUDIO_DISABLED;
static _Atomic int32_t g_sample_rate;
static _Atomic int32_t g_sample_format;
static _Atomic int32_t g_channels;
static _Atomic uint64_t g_channel_layout;
static _Atomic int64_t g_submitted_frames;
static _Atomic int64_t g_played_frames;
static _Atomic int64_t g_audio_timestamp_ns;
static _Atomic int64_t g_estimated_delay_ms;
static _Atomic int64_t g_buffer_starvation;
static _Atomic int64_t g_system_underrun;
static _Atomic int64_t g_callback_count;
static _Atomic int64_t g_audio_flush_count;
static _Atomic int64_t g_seek_audio_reset_count;
static void reset_stats(void)
{
atomic_store(&g_state, MPV_HARMONY_AUDIO_DISABLED);
atomic_store(&g_sample_rate, 0);
atomic_store(&g_sample_format, AF_FORMAT_UNKNOWN);
atomic_store(&g_channels, 0);
atomic_store(&g_channel_layout, 0);
atomic_store(&g_submitted_frames, 0);
atomic_store(&g_played_frames, 0);
atomic_store(&g_audio_timestamp_ns, 0);
atomic_store(&g_estimated_delay_ms, 0);
atomic_store(&g_buffer_starvation, 0);
atomic_store(&g_system_underrun, 0);
atomic_store(&g_callback_count, 0);
atomic_store(&g_audio_flush_count, 0);
atomic_store(&g_seek_audio_reset_count, 0);
}
static void set_state(int32_t state)
{
atomic_store(&g_state, state);
}
static void log_result(struct ao *ao, const char *operation, OH_AudioStream_Result result)
{
MP_ERR(ao, "%s failed: %d\n", operation, result);
}
static OH_AudioStream_SampleFormat sample_format_for(int format)
{
return af_fmt_is_float(format) ? AUDIOSTREAM_SAMPLE_F32LE : AUDIOSTREAM_SAMPLE_S16LE;
}
static void fill_silence(struct ao *ao, void *data, int samples)
{
if (samples > 0)
af_fill_silence(data, (size_t)samples * ao->sstride, ao->format);
}
static OH_AudioData_Callback_Result write_data(OH_AudioRenderer *renderer, void *user_data,
void *audio_data, int32_t audio_data_size)
{
(void)renderer;
struct ao *ao = user_data;
struct priv *p = ao->priv;
atomic_fetch_add(&p->callbacks_inflight, 1);
int64_t callback_number = atomic_fetch_add(&p->callback_count, 1) + 1;
atomic_fetch_add(&g_callback_count, 1);
int stride = ao->sstride;
if (audio_data == NULL || audio_data_size <= 0 || stride <= 0) {
atomic_fetch_sub(&p->callbacks_inflight, 1);
return AUDIO_DATA_CALLBACK_RESULT_INVALID;
}
int samples = audio_data_size / stride;
int remainder = audio_data_size - samples * stride;
if (!atomic_load(&p->callback_enabled) || samples <= 0) {
memset(audio_data, 0, (size_t)audio_data_size);
atomic_fetch_sub(&p->callbacks_inflight, 1);
return AUDIO_DATA_CALLBACK_RESULT_VALID;
}
if (callback_number == 1)
MP_VERBOSE(ao, "OHAudio first callback: %d frames, %d bytes\n", samples, audio_data_size);
void *planes[1] = {audio_data};
int64_t end_time = mp_time_ns() + MP_TIME_MS_TO_NS(p->latency_ms);
end_time += MP_TIME_S_TO_NS(samples / (double)ao->samplerate);
int got = ao_read_data(ao, planes, samples, end_time, NULL, true, false);
if (got < samples) {
fill_silence(ao, (char *)audio_data + (size_t)got * stride, samples - got);
atomic_fetch_add(&p->buffer_starvation, 1);
atomic_fetch_add(&g_buffer_starvation, 1);
}
if (remainder > 0)
memset((char *)audio_data + (size_t)samples * stride, 0, (size_t)remainder);
atomic_fetch_add(&p->submitted_frames, samples);
atomic_fetch_add(&g_submitted_frames, samples);
atomic_fetch_sub(&p->callbacks_inflight, 1);
return AUDIO_DATA_CALLBACK_RESULT_VALID;
}
static void renderer_error(OH_AudioRenderer *renderer, void *user_data,
OH_AudioStream_Result error)
{
(void)renderer;
struct ao *ao = user_data;
log_result(ao, "renderer error callback", error);
set_state(MPV_HARMONY_AUDIO_ERROR);
ao_request_reload(ao);
}
static void output_device_changed(OH_AudioRenderer *renderer, void *user_data,
OH_AudioStream_DeviceChangeReason reason)
{
(void)renderer;
struct ao *ao = user_data;
MP_VERBOSE(ao, "output device changed: %d\n", reason);
ao_request_reload(ao);
}
static MP_THREAD_VOID clock_loop(void *arg)
{
struct ao *ao = arg;
struct priv *p = ao->priv;
mp_thread_set_name("ao/harmony-clock");
while (!atomic_load(&p->stop_clock)) {
int64_t played = 0;
int64_t timestamp = 0;
if (OH_AudioRenderer_GetAudioTimestampInfo(p->renderer, &played, &timestamp) == AUDIOSTREAM_SUCCESS) {
atomic_store(&p->raw_played_frames, played);
int64_t origin = atomic_load(&p->timestamp_origin_raw);
int64_t effective_played = played > origin ? played - origin : 0;
atomic_store(&p->played_frames, effective_played);
atomic_store(&p->audio_timestamp_ns, timestamp);
atomic_store(&g_played_frames, effective_played);
atomic_store(&g_audio_timestamp_ns, timestamp);
int64_t submitted = atomic_load(&p->submitted_frames);
int64_t pending = submitted > effective_played ? submitted - effective_played : 0;
int64_t delay = p->latency_ms + pending * 1000 / MPMAX(1, ao->samplerate);
atomic_store(&p->estimated_delay_ms, delay);
atomic_store(&g_estimated_delay_ms, delay);
}
mp_sleep_ns(MP_TIME_MS_TO_NS(20));
}
MP_THREAD_RETURN();
}
static OH_AudioStream_Result create_renderer(struct ao *ao, int format)
{
struct priv *p = ao->priv;
p->renderer = NULL;
OH_AudioStreamBuilder *builder = NULL;
OH_AudioStream_Result result = OH_AudioStreamBuilder_Create(&builder, AUDIOSTREAM_TYPE_RENDERER);
if (result != AUDIOSTREAM_SUCCESS || builder == NULL) {
log_result(ao, "AUDIO_BUILDER_FAILED", result);
return result;
}
OH_AudioStream_SampleFormat sample_format = sample_format_for(format);
OH_AudioChannelLayout layout = p->channel_count == 1 ? CH_LAYOUT_MONO : CH_LAYOUT_STEREO;
result = OH_AudioStreamBuilder_SetSamplingRate(builder, ao->samplerate);
if (result == AUDIOSTREAM_SUCCESS)
result = OH_AudioStreamBuilder_SetChannelCount(builder, p->channel_count);
if (result == AUDIOSTREAM_SUCCESS)
result = OH_AudioStreamBuilder_SetChannelLayout(builder, layout);
if (result == AUDIOSTREAM_SUCCESS)
result = OH_AudioStreamBuilder_SetSampleFormat(builder, sample_format);
if (result == AUDIOSTREAM_SUCCESS)
result = OH_AudioStreamBuilder_SetEncodingType(builder, AUDIOSTREAM_ENCODING_TYPE_RAW);
if (result == AUDIOSTREAM_SUCCESS)
result = OH_AudioStreamBuilder_SetLatencyMode(builder, AUDIOSTREAM_LATENCY_MODE_NORMAL);
if (result == AUDIOSTREAM_SUCCESS)
result = OH_AudioStreamBuilder_SetRendererInfo(builder, AUDIOSTREAM_USAGE_MOVIE);
if (result == AUDIOSTREAM_SUCCESS)
result = OH_AudioStreamBuilder_SetRendererWriteDataCallback(builder, write_data, ao);
if (result == AUDIOSTREAM_SUCCESS)
result = OH_AudioStreamBuilder_SetRendererOutputDeviceChangeCallback(builder,
output_device_changed, ao);
if (result == AUDIOSTREAM_SUCCESS)
result = OH_AudioStreamBuilder_SetRendererErrorCallback(builder, renderer_error, ao);
if (result == AUDIOSTREAM_SUCCESS)
result = OH_AudioStreamBuilder_GenerateRenderer(builder, &p->renderer);
OH_AudioStreamBuilder_Destroy(builder);
if (result != AUDIOSTREAM_SUCCESS || p->renderer == NULL) {
if (p->renderer != NULL) {
OH_AudioRenderer_Release(p->renderer);
p->renderer = NULL;
}
log_result(ao, "AUDIO_RENDERER_CREATE_FAILED", result);
return result;
}
if (OH_AudioRenderer_SetVolume(p->renderer, 1.0f) != AUDIOSTREAM_SUCCESS)
MP_WARN(ao, "renderer unity volume could not be set\n");
if (OH_AudioRenderer_GetFrameSizeInCallback(p->renderer, &p->frame_size) != AUDIOSTREAM_SUCCESS)
p->frame_size = 0;
if (OH_AudioRenderer_GetLatency(p->renderer, AUDIOSTREAM_LATENCY_TYPE_ALL, &p->latency_ms) != AUDIOSTREAM_SUCCESS)
p->latency_ms = 0;
MP_VERBOSE(ao, "OHAudio renderer ready: %d Hz, %d channels, %s, frame_size=%d, latency=%d ms\n",
ao->samplerate, p->channel_count, af_fmt_to_str(format), p->frame_size, p->latency_ms);
return AUDIOSTREAM_SUCCESS;
}
static int init(struct ao *ao)
{
struct priv *p = ao->priv;
reset_stats();
atomic_init(&p->stop_clock, false);
atomic_init(&p->callback_enabled, false);
atomic_init(&p->callbacks_inflight, 0);
atomic_init(&p->submitted_frames, 0);
atomic_init(&p->played_frames, 0);
atomic_init(&p->raw_played_frames, 0);
atomic_init(&p->timestamp_origin_raw, 0);
atomic_init(&p->audio_timestamp_ns, 0);
atomic_init(&p->estimated_delay_ms, 0);
atomic_init(&p->buffer_starvation, 0);
atomic_init(&p->callback_count, 0);
p->renderer = NULL;
p->clock_thread_valid = false;
p->frame_size = 0;
p->latency_ms = 0;
if (!af_fmt_is_pcm(ao->format)) {
MP_ERR(ao, "AUDIO_UNSUPPORTED_FORMAT: %s\n", af_fmt_to_str(ao->format));
set_state(MPV_HARMONY_AUDIO_ERROR);
return -1;
}
ao->format = af_fmt_is_float(ao->format) ? AF_FORMAT_FLOAT : AF_FORMAT_S16;
if (ao->channels.num < 1) {
MP_ERR(ao, "AUDIO_UNSUPPORTED_CHANNEL_LAYOUT\n");
set_state(MPV_HARMONY_AUDIO_ERROR);
return -1;
}
p->channel_count = ao->channels.num == 1 ? 1 : 2;
if (ao->channels.num != p->channel_count)
mp_chmap_from_channels(&ao->channels, p->channel_count);
p->channel_layout = p->channel_count == 1 ? CH_LAYOUT_MONO : CH_LAYOUT_STEREO;
OH_AudioStream_Result result = create_renderer(ao, ao->format);
if (result != AUDIOSTREAM_SUCCESS && ao->format == AF_FORMAT_FLOAT) {
ao->format = AF_FORMAT_S16;
result = create_renderer(ao, ao->format);
}
if (result != AUDIOSTREAM_SUCCESS) {
set_state(MPV_HARMONY_AUDIO_ERROR);
return -1;
}
atomic_store(&g_sample_rate, ao->samplerate);
atomic_store(&g_sample_format, ao->format);
atomic_store(&g_channels, p->channel_count);
atomic_store(&g_channel_layout, p->channel_layout);
atomic_store(&g_estimated_delay_ms, p->latency_ms);
set_state(MPV_HARMONY_AUDIO_READY);
if (mp_thread_create(&p->clock_thread, clock_loop, ao)) {
MP_ERR(ao, "AUDIO_TIMESTAMP_ERROR: clock thread creation failed\n");
OH_AudioRenderer_Release(p->renderer);
p->renderer = NULL;
set_state(MPV_HARMONY_AUDIO_ERROR);
return -1;
}
p->clock_thread_valid = true;
return 0;
}
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;
MP_VERBOSE(ao, "OHAudio release\n");
atomic_store(&p->callback_enabled, false);
if (p->renderer != NULL) {
OH_AudioRenderer_Stop(p->renderer);
while (atomic_load(&p->callbacks_inflight) > 0)
mp_sleep_ns(MP_TIME_MS_TO_NS(1));
}
atomic_store(&p->stop_clock, true);
if (p->clock_thread_valid)
mp_thread_join(p->clock_thread);
if (p->renderer != NULL) {
OH_AudioStream_Result result = OH_AudioRenderer_Release(p->renderer);
if (result != AUDIOSTREAM_SUCCESS)
log_result(ao, "AUDIO_RELEASE_ERROR", result);
p->renderer = NULL;
}
set_state(MPV_HARMONY_AUDIO_RELEASED);
}
static void start(struct ao *ao)
{
struct priv *p = ao->priv;
if (p->renderer == NULL)
return;
atomic_store(&p->callback_enabled, true);
OH_AudioStream_Result result = OH_AudioRenderer_Start(p->renderer);
if (result != AUDIOSTREAM_SUCCESS) {
log_result(ao, "AUDIO_RENDERER_START_FAILED", result);
set_state(MPV_HARMONY_AUDIO_ERROR);
ao_request_reload(ao);
return;
}
MP_VERBOSE(ao, "OHAudio start\n");
set_state(MPV_HARMONY_AUDIO_RUNNING);
}
static bool set_pause(struct ao *ao, bool paused)
{
struct priv *p = ao->priv;
if (p->renderer == NULL)
return false;
OH_AudioStream_Result result = paused ? OH_AudioRenderer_Pause(p->renderer)
: OH_AudioRenderer_Start(p->renderer);
if (result != AUDIOSTREAM_SUCCESS) {
log_result(ao, paused ? "pause" : "resume", result);
set_state(MPV_HARMONY_AUDIO_ERROR);
return false;
}
MP_VERBOSE(ao, "OHAudio %s\n", paused ? "pause" : "resume");
set_state(paused ? MPV_HARMONY_AUDIO_PAUSED : MPV_HARMONY_AUDIO_RUNNING);
return true;
}
static void reset(struct ao *ao)
{
struct priv *p = ao->priv;
if (p->renderer == NULL)
return;
atomic_store(&p->callback_enabled, false);
OH_AudioStream_Result stop_result = OH_AudioRenderer_Stop(p->renderer);
if (stop_result != AUDIOSTREAM_SUCCESS && stop_result != AUDIOSTREAM_ERROR_ILLEGAL_STATE)
log_result(ao, "AUDIO_STOP_FAILED", stop_result);
while (atomic_load(&p->callbacks_inflight) > 0)
mp_sleep_ns(MP_TIME_MS_TO_NS(1));
OH_AudioStream_Result result = OH_AudioRenderer_Flush(p->renderer);
if (result != AUDIOSTREAM_SUCCESS) {
log_result(ao, "AUDIO_FLUSH_FAILED", result);
set_state(MPV_HARMONY_AUDIO_ERROR);
return;
}
atomic_store(&p->timestamp_origin_raw, atomic_load(&p->raw_played_frames));
atomic_store(&p->submitted_frames, 0);
atomic_store(&p->played_frames, 0);
atomic_store(&p->audio_timestamp_ns, 0);
atomic_store(&g_submitted_frames, 0);
atomic_store(&g_played_frames, 0);
atomic_store(&g_audio_timestamp_ns, 0);
atomic_fetch_add(&g_audio_flush_count, 1);
atomic_fetch_add(&g_seek_audio_reset_count, 1);
MP_VERBOSE(ao, "OHAudio flush\n");
set_state(MPV_HARMONY_AUDIO_STOPPED);
}
static void get_state(struct ao *ao, struct mp_pcm_state *state)
{
struct priv *p = ao->priv;
int64_t submitted = atomic_load(&p->submitted_frames);
int64_t played = atomic_load(&p->played_frames);
int64_t pending = submitted > played ? submitted - played : 0;
state->free_samples = 0;
state->queued_samples = pending;
state->delay = pending / (double)MPMAX(1, ao->samplerate) + p->latency_ms / 1000.0;
state->playing = atomic_load(&g_state) == MPV_HARMONY_AUDIO_RUNNING;
}
const struct ao_driver audio_out_harmony = {
.description = "HarmonyOS OHAudio output",
.name = "harmony",
.init = init,
.uninit = uninit,
.reset = reset,
.set_pause = set_pause,
.start = start,
.get_state = get_state,
.priv_size = sizeof(struct priv),
};
#else
MPV_EXPORT int mpv_harmony_audio_get_stats(struct mpv_harmony_audio_stats *stats)
{
if (stats != NULL)
memset(stats, 0, sizeof(*stats));
return 0;
}
#endif
#if HAVE_HARMONY_AUDIO
MPV_EXPORT int mpv_harmony_audio_get_stats(struct mpv_harmony_audio_stats *stats)
{
if (stats == NULL)
return 0;
stats->state = atomic_load(&g_state);
stats->sample_rate = atomic_load(&g_sample_rate);
stats->sample_format = atomic_load(&g_sample_format);
stats->channels = atomic_load(&g_channels);
stats->channel_layout = atomic_load(&g_channel_layout);
stats->submitted_frames = atomic_load(&g_submitted_frames);
stats->played_frames = atomic_load(&g_played_frames);
stats->audio_timestamp_ns = atomic_load(&g_audio_timestamp_ns);
stats->estimated_delay_ms = atomic_load(&g_estimated_delay_ms);
stats->buffer_starvation = atomic_load(&g_buffer_starvation);
stats->system_underrun = atomic_load(&g_system_underrun);
stats->callback_count = atomic_load(&g_callback_count);
stats->audio_flush_count = atomic_load(&g_audio_flush_count);
stats->seek_audio_reset_count = atomic_load(&g_seek_audio_reset_count);
return 1;
}
#endif
+49
View File
@@ -0,0 +1,49 @@
/*
* HarmonyOS audio diagnostics exported for the isolated Playback Lab.
* The AO remains usable without consuming this optional diagnostics API.
*/
#ifndef MPV_AO_HARMONY_H
#define MPV_AO_HARMONY_H
#include <stdint.h>
#include "mpv/client.h"
#ifdef __cplusplus
extern "C" {
#endif
enum mpv_harmony_audio_state {
MPV_HARMONY_AUDIO_DISABLED = 0,
MPV_HARMONY_AUDIO_READY = 1,
MPV_HARMONY_AUDIO_RUNNING = 2,
MPV_HARMONY_AUDIO_PAUSED = 3,
MPV_HARMONY_AUDIO_STOPPED = 4,
MPV_HARMONY_AUDIO_ERROR = 5,
MPV_HARMONY_AUDIO_RELEASED = 6,
};
struct mpv_harmony_audio_stats {
int32_t state;
int32_t sample_rate;
int32_t sample_format;
int32_t channels;
uint64_t channel_layout;
int64_t submitted_frames;
int64_t played_frames;
int64_t audio_timestamp_ns;
int64_t estimated_delay_ms;
int64_t buffer_starvation;
int64_t system_underrun;
int64_t callback_count;
int64_t audio_flush_count;
int64_t seek_audio_reset_count;
};
MPV_EXPORT int mpv_harmony_audio_get_stats(struct mpv_harmony_audio_stats *stats);
#ifdef __cplusplus
}
#endif
#endif
+2 -2
View File
@@ -195,7 +195,7 @@ static void on_process(void *userdata)
int64_t end_time = mp_time_ns();
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;
end_time += MP_TIME_S_TO_NS(time.queued / ao->sstride) / ao->samplerate;
end_time += MP_TIME_S_TO_NS(time.buffered) / ao->samplerate;
end_time -= pw_stream_get_nsec(p->stream) - time.now;
@@ -668,7 +668,7 @@ static int init(struct ao *ao)
pw_thread_loop_unlock(p->loop);
if (p->init_state == INIT_STATE_ERROR)
if (p->init_state != INIT_STATE_SUCCESS)
goto error;
return 0;
+5
View File
@@ -0,0 +1,5 @@
results/*.json
reports/*.md
runner/__pycache__/
!results/.gitkeep
!reports/.gitkeep
View File
Whitespace-only changes.
View File
Whitespace-only changes.
+385
View File
@@ -0,0 +1,385 @@
#!/usr/bin/env python3
"""Repeatable Playback Lab emulator benchmark runner.
Only uses the standard library and the OpenHarmony hdc/uitest command line.
Metrics unavailable from the emulator are emitted as null, never inferred.
"""
from __future__ import annotations
import argparse
import json
import math
import os
import re
import shlex
import subprocess
import tempfile
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Iterable
ROOT = Path(__file__).resolve().parents[2]
PACKAGE = "com.velix.playbacklab"
DEFAULT_SERIAL = "127.0.0.1:5555"
DEFAULT_HDC = "/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc"
def command(argv: list[str], timeout: float = 30.0) -> str:
result = subprocess.run(argv, check=False, capture_output=True, text=True, timeout=timeout)
if result.returncode != 0:
raise RuntimeError(f"command failed ({result.returncode}): {shlex.join(argv)}\n{result.stderr.strip()}")
return result.stdout.strip()
class Emulator:
def __init__(self, serial: str, hdc: str) -> None:
self.serial = serial
self.hdc = hdc
self.layout_counter = 0
def hdc_shell(self, text: str, timeout: float = 30.0) -> str:
return command([self.hdc, "-t", self.serial, "shell", text], timeout)
def click(self, x: int, y: int) -> None:
self.hdc_shell(f"uitest uiInput click {x} {y}", timeout=10)
def layout(self) -> dict[str, Any]:
self.layout_counter += 1
marker = self.hdc_shell(f"uitest dumpLayout -b {PACKAGE} -m true -d 0", timeout=10)
match = re.search(r"saved to:(\S+)", marker)
if not match:
raise RuntimeError(f"layout dump did not return a path: {marker}")
remote = match.group(1)
local = Path(tempfile.gettempdir()) / f"velix-layout-{os.getpid()}-{self.layout_counter}.json"
command([self.hdc, "-t", self.serial, "file", "recv", remote, str(local)], timeout=10)
with local.open(encoding="utf-8") as stream:
return json.load(stream)
def pid(self) -> int | None:
output = self.hdc_shell(f"pidof {PACKAGE}", timeout=10)
match = re.search(r"\b(\d+)\b", output)
return int(match.group(1)) if match else None
def process_sample(self) -> dict[str, Any]:
pid = self.pid()
sample: dict[str, Any] = {"pid": pid, "rssKb": None, "pssKb": None, "heapKb": None,
"virtualMemoryKb": None,
"threadCount": None, "fdCount": None, "cpuPermille": None,
"cpuSupported": False}
if pid is None:
return sample
status = self.hdc_shell(f"cat /proc/{pid}/status", timeout=10)
for key, field in (("rssKb", "VmRSS"), ("virtualMemoryKb", "VmSize"), ("threadCount", "Threads")):
match = re.search(rf"^{field}:\s+(\d+)", status, re.MULTILINE)
if match:
sample[key] = int(match.group(1))
fd_output = self.hdc_shell(f"ls /proc/{pid}/fd 2>&1 | wc -l", timeout=10)
if fd_output.isdigit() and fd_output != "0":
sample["fdCount"] = int(fd_output)
stat = self.hdc_shell(f"cat /proc/{pid}/stat", timeout=10)
match = re.match(r"^\d+ \(.+\) (.*)$", stat)
if match:
fields = match.group(1).split()
if len(fields) > 12:
sample["cpuJiffies"] = int(fields[11]) + int(fields[12])
return sample
def product_version(self) -> str | None:
try:
value = self.hdc_shell("param get const.product.software.version", timeout=10)
except RuntimeError:
return None
return value or None
def walk_nodes(node: dict[str, Any]) -> Iterable[dict[str, Any]]:
yield node
for child in node.get("children", []):
if isinstance(child, dict):
yield from walk_nodes(child)
def node_text(node: dict[str, Any]) -> str:
attrs = node.get("attributes", {})
return str(attrs.get("text") or attrs.get("originalText") or "")
def find_node(layout: dict[str, Any], text: str) -> dict[str, Any] | None:
for node in walk_nodes(layout):
if node_text(node) == text:
return node
return None
def center(bounds: str) -> tuple[int, int]:
values = [int(value) for value in re.findall(r"\d+", bounds)]
if len(values) != 4:
raise ValueError(f"invalid bounds: {bounds}")
return (values[0] + values[2]) // 2, (values[1] + values[3]) // 2
def click_text(emulator: Emulator, text: str) -> None:
node = find_node(emulator.layout(), text)
if node is None:
raise RuntimeError(f"UI node not found: {text}")
emulator.click(*center(node["attributes"]["bounds"]))
def all_texts(layout: dict[str, Any]) -> list[str]:
return [node_text(node) for node in walk_nodes(layout) if node_text(node)]
def text_matching(layout: dict[str, Any], prefix: str) -> str | None:
for value in all_texts(layout):
if value.startswith(prefix):
return value
return None
def parse_int(text: str | None, pattern: str) -> int | None:
if not text:
return None
match = re.search(pattern, text)
return int(match.group(1)) if match else None
def parse_state(layout: dict[str, Any]) -> str | None:
value = text_matching(layout, "state: ")
return value.split(": ", 1)[1] if value and ": " in value else None
def parse_run(layout: dict[str, Any]) -> dict[str, Any]:
startup = text_matching(layout, "startup=") or ""
buffer_line = text_matching(layout, "buffer enter=") or ""
napi_line = text_matching(layout, "napi calls=") or ""
render_line = text_matching(layout, "render updates=") or napi_line
error = text_matching(layout, "error=") or ""
timeline: dict[str, Any] = {}
for key, value in re.findall(r"(startup|firstSwap|seekRecovery|audioStart|audioCallback)=(-?\d+)", startup):
timeline[{"startup": "startupTotalMs", "firstSwap": "firstSwapMs", "seekRecovery": "seekRecoveryMs",
"audioStart": "audioRendererStartMs", "audioCallback": "firstAudioCallbackMs"}[key]] = int(value)
first_frame = parse_int(text_matching(layout, "dropped="), r"firstFrameMs=(-?\d+)")
if first_frame is None or first_frame < 0:
first_frame = timeline.get("firstSwapMs")
if first_frame is not None and first_frame < 0:
first_frame = None
startup_ms = timeline.get("startupTotalMs")
first_swap_ms = timeline.get("firstSwapMs")
seek_recovery_ms = timeline.get("seekRecoveryMs")
audio_start_ms = timeline.get("audioRendererStartMs")
audio_callback_ms = timeline.get("firstAudioCallbackMs")
if startup_ms is not None and startup_ms < 0:
startup_ms = None
if first_swap_ms is not None and first_swap_ms < 0:
first_swap_ms = None
if seek_recovery_ms is not None and seek_recovery_ms < 0:
seek_recovery_ms = None
if audio_start_ms is not None and audio_start_ms < 0:
audio_start_ms = None
if audio_callback_ms is not None and audio_callback_ms < 0:
audio_callback_ms = None
return {
"state": parse_state(layout),
"firstFrameMs": first_frame,
"renderCount": parse_int(text_matching(layout, "dropped="), r"renderCount=(-?\d+)"),
"startupTotalMs": startup_ms,
"firstSwapMs": first_swap_ms,
"seekRecoveryMs": seek_recovery_ms,
"audioRendererStartMs": audio_start_ms,
"firstAudioCallbackMs": audio_callback_ms,
"bufferingEnterCount": parse_int(buffer_line, r"buffer enter=(-?\d+)"),
"bufferingExitCount": parse_int(buffer_line, r"exit=(-?\d+)"),
"napiCalls": parse_int(napi_line, r"napi calls=(-?\d+)"),
"renderUpdateCalls": parse_int(render_line, r"updates=(-?\d+)/"),
"renderFrameUpdates": parse_int(render_line, r"updates=-?\d+/(-?\d+)"),
"coreSyncCalls": parse_int(render_line, r"sync=(-?\d+)/"),
"coreSyncSkipped": parse_int(render_line, r"sync=-?\d+/(-?\d+)"),
"error": error,
}
def wait_for_first_frame(emulator: Emulator, timeout: float = 10.0) -> tuple[dict[str, Any], list[dict[str, Any]]]:
deadline = time.monotonic() + timeout
samples: list[dict[str, Any]] = []
latest: dict[str, Any] = {}
next_sample = 0.0
while time.monotonic() < deadline:
latest = emulator.layout()
now = time.monotonic()
if now >= next_sample:
samples.append({"atMs": int((timeout - max(0.0, deadline - now)) * 1000),
**emulator.process_sample()})
next_sample = now + 1.0
run = parse_run(latest)
if run.get("startupTotalMs") is not None or run.get("state") == "ERROR":
return latest, samples
time.sleep(0.15)
raise TimeoutError(f"playback did not expose first-frame timeline: {parse_run(latest)}")
def stats(values: list[float | int]) -> dict[str, float | int | None]:
numbers = sorted(float(value) for value in values if value is not None and math.isfinite(float(value)))
if not numbers:
return {"count": 0, "min": None, "max": None, "avg": None, "median": None, "p90": None, "p95": None}
def percentile(fraction: float) -> float:
return numbers[max(0, min(len(numbers) - 1, math.ceil(fraction * len(numbers)) - 1))]
return {"count": len(numbers), "min": numbers[0], "max": numbers[-1],
"avg": sum(numbers) / len(numbers), "median": percentile(0.5),
"p90": percentile(0.9), "p95": percentile(0.95)}
def git_revision(path: Path) -> str | None:
try:
return command(["git", "-C", str(path), "rev-parse", "HEAD"], timeout=10)
except RuntimeError:
return None
def load_local(emulator: Emulator) -> tuple[dict[str, Any], list[dict[str, Any]]]:
click_text(emulator, "Load Local Test")
return wait_for_first_frame(emulator)
def startup_local(emulator: Emulator, repeat: int) -> list[dict[str, Any]]:
runs: list[dict[str, Any]] = []
for index in range(repeat):
began = time.monotonic()
click_text(emulator, "Load Local Test")
layout, samples = wait_for_first_frame(emulator)
run = parse_run(layout)
run.update({"runIndex": index + 1, "cacheClass": "cold-ish" if index == 0 else "warm",
"hostFirstFrameMs": int((time.monotonic() - began) * 1000),
"processSamples": samples})
runs.append(run)
return runs
def wait_for_seek_recovery(emulator: Emulator, timeout: float = 5.0) -> tuple[dict[str, Any], list[dict[str, Any]]]:
deadline = time.monotonic() + timeout
samples: list[dict[str, Any]] = []
latest: dict[str, Any] = {}
next_sample = 0.0
while time.monotonic() < deadline:
latest = emulator.layout()
now = time.monotonic()
if now >= next_sample:
samples.append({"atMs": int((timeout - max(0.0, deadline - now)) * 1000),
**emulator.process_sample()})
next_sample = now + 1.0
run = parse_run(latest)
if run.get("seekRecoveryMs") is not None and run.get("seekRecoveryMs") >= 0:
return latest, samples
if run.get("state") == "ERROR":
return latest, samples
time.sleep(0.1)
raise TimeoutError(f"seek did not expose recovery timeline: {parse_run(latest)}")
def seek_local(emulator: Emulator, repeat: int) -> list[dict[str, Any]]:
runs: list[dict[str, Any]] = []
for index in range(repeat):
click_text(emulator, "Load Local Test")
# The bundled clip is intentionally short and may finish before a
# multi-command text-input interaction completes. The native +/-10s
# action exercises the same seek API without that race.
time.sleep(0.05)
click_text(emulator, "+10s")
latest, samples = wait_for_seek_recovery(emulator)
run = parse_run(latest)
run.update({"runIndex": index + 1, "mode": "relative", "offsetSeconds": 10,
"processSamples": samples})
runs.append(run)
return runs
def stress_lifecycle(emulator: Emulator, repeat: int) -> list[dict[str, Any]]:
initial = emulator.layout()
load_center = center(find_node(initial, "Load Local Test")["attributes"]["bounds"])
stop_center = center(find_node(initial, "Stop")["attributes"]["bounds"])
runs: list[dict[str, Any]] = []
for index in range(repeat):
emulator.click(*load_center)
time.sleep(0.08)
emulator.click(*stop_center)
time.sleep(0.08)
layout = emulator.layout()
run = parse_run(layout)
run.update({"runIndex": index + 1, "processSample": emulator.process_sample()})
runs.append(run)
return runs
def play_pause(emulator: Emulator, repeat: int) -> list[dict[str, Any]]:
load_local(emulator)
initial = emulator.layout()
play_center = center(find_node(initial, "Play")["attributes"]["bounds"])
pause_center = center(find_node(initial, "Pause")["attributes"]["bounds"])
runs: list[dict[str, Any]] = []
for index in range(repeat):
emulator.click(*play_center)
emulator.click(*pause_center)
if index % 10 == 0:
time.sleep(0.05)
run = {"runIndex": index + 1, "processSample": emulator.process_sample()}
if index % 10 == 0 or index + 1 == repeat:
run.update(parse_run(emulator.layout()))
runs.append(run)
return runs
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("scenario", choices=["startup_local", "seek_local", "stress_lifecycle", "play_pause"])
parser.add_argument("--repeat", type=int, default=None)
parser.add_argument("--serial", default=os.environ.get("DEVECO_DEVICE", DEFAULT_SERIAL))
parser.add_argument("--hdc", default=os.environ.get("HDC", DEFAULT_HDC))
parser.add_argument("--output", type=Path)
args = parser.parse_args()
defaults = {"startup_local": 20, "seek_local": 50, "stress_lifecycle": 100, "play_pause": 200}
repeat = args.repeat if args.repeat is not None else defaults[args.scenario]
emulator = Emulator(args.serial, args.hdc)
if args.scenario == "startup_local":
runs = startup_local(emulator, repeat)
elif args.scenario == "seek_local":
runs = seek_local(emulator, repeat)
elif args.scenario == "stress_lifecycle":
runs = stress_lifecycle(emulator, repeat)
else:
runs = play_pause(emulator, repeat)
metric_names = ["firstFrameMs", "startupTotalMs", "firstSwapMs", "seekRecoveryMs", "audioRendererStartMs",
"firstAudioCallbackMs", "bufferingEnterCount", "bufferingExitCount", "hostFirstFrameMs", "hostTerminalMs"]
output = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"gitCommit": git_revision(ROOT),
"mpvCommit": git_revision(ROOT),
"ffmpegCommit": git_revision(ROOT.parent / "Velix-FFmpeg-Harmony"),
"deviceType": "emulator",
"deviceSerial": args.serial,
"harmonyVersion": emulator.product_version(),
"testScenario": args.scenario,
"mediaId": "sample.mp4",
"protocol": "file",
"codec": "H.264/AAC",
"resolution": None,
"audio": "ao_harmony",
"subtitle": "none",
"hwdec": "software",
"repeat": repeat,
"metrics": {name: stats([run.get(name) for run in runs]) for name in metric_names},
"runs": runs,
"errors": [run.get("error") for run in runs if run.get("error") and "error=NONE" not in run.get("error", "")],
"sampling": {"pss": "unsupported unless exposed by device", "cpu": "raw /proc stat only when available"},
}
output_path = args.output or ROOT / "benchmark" / "results" / f"{args.scenario}-{int(time.time())}.json"
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(json.dumps(output, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(output_path)
print(json.dumps(output["metrics"], ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+7
View File
@@ -0,0 +1,7 @@
{
"name": "audio_on_off",
"media": "sample.mp4",
"repeat": 10,
"variants": ["audio_on", "audio_off"],
"notes": "Compare firstAudioCallbackMs and startupTotalMs without changing decode quality."
}
+7
View File
@@ -0,0 +1,7 @@
{
"name": "http_range",
"media": "controlled-http-mp4",
"repeat": 10,
"requiredServerEvidence": ["Range bytes=0-", "206 response", "seekResult"],
"notes": "HTTPS/HLS are separate controlled-network scenarios and remain pending without a reliable source."
}
+7
View File
@@ -0,0 +1,7 @@
{
"name": "seek_local",
"media": "sample.mp4",
"positionsPercent": [10, 25, 50, 75, 90],
"repeatPerPosition": 10,
"modes": ["fast", "exact"]
}
+8
View File
@@ -0,0 +1,8 @@
{
"name": "startup_local",
"media": "sample.mp4",
"repeat": 20,
"coldIsh": true,
"warm": true,
"notes": "OS page cache is not controlled; firstFrame/startup timeline comes from the Playback Lab."
}
@@ -0,0 +1,6 @@
{
"name": "stress_lifecycle",
"loadStopCycles": 100,
"playPauseCycles": 200,
"notes": "Release/recreate requires a fresh XComponent surface and is kept separate from load/stop stress."
}
+7
View File
@@ -0,0 +1,7 @@
{
"name": "subtitle_ass",
"media": "sample.mp4",
"repeat": 10,
"variants": ["none", "embedded_srt", "embedded_ass", "external_ass"],
"notes": "Run only when the corresponding controlled fixture is installed on the emulator."
}
+11 -7
View File
@@ -5,7 +5,7 @@ mkdir -p "$prefix_dir"
ln -snf . "$prefix_dir/usr"
ln -snf . "$prefix_dir/local"
wget="wget -nc --progress=bar:force"
wget="wget --progress=bar:force"
gitclone="git clone --depth=1 --recursive --shallow-submodules"
if [[ -z "$TARGET" || -z "$RUST_TARGET" ]]; then
@@ -109,13 +109,15 @@ function gettar {
[ -z "$dname" ] && dname="${fname%.tar.*}"
[ -d "$dname" ] && return 0
local cachename="$(md5sum <<<"$1" | cut -d " " -f 1)"
if [[ -n "$DOWNLOAD_CACHE" && -s "$DOWNLOAD_CACHE/$cachename" ]]; then
if [ -s "$fname" ]; then
: # exists
elif [[ -n "$DOWNLOAD_CACHE" && -s "$DOWNLOAD_CACHE/$cachename" ]]; then
cp -v "$DOWNLOAD_CACHE/$cachename" "$fname"
cachename=
else
$wget "$1" -O "$fname"
$wget "$1" -O "$fname" || return 1
fi
tar -xaf "$fname"
tar -xaf "$fname" || return 1
if [ ! -d "$dname" ]; then
echo "Error: expected $fname to extract to $dname but it was not created" >&2
return 2
@@ -146,7 +148,8 @@ function build_if_missing {
_iconv () {
local ver=1.19
gettar "https://ftpmirror.gnu.org/gnu/libiconv/libiconv-${ver}.tar.gz"
gettar "https://ftpmirror.gnu.org/gnu/libiconv/libiconv-${ver}.tar.gz" || \
gettar "https://ftp.gnu.org/pub/gnu/libiconv/libiconv-${ver}.tar.gz"
builddir libiconv-${ver}
../configure --host=$TARGET $at_flags
makeplusinstall
@@ -206,7 +209,7 @@ _ffmpeg () {
--disable-{doc,programs}
--enable-muxer=spdif --enable-encoder=mjpeg,png --enable-libdav1d
)
pkg-config vulkan && args+=(--enable-vulkan --enable-libshaderc)
pkg-config vulkan && args+=(--enable-vulkan)
../configure "${args[@]}"
makeplusinstall
popd
@@ -274,7 +277,8 @@ _libplacebo_mark=lib/libplacebo.dll.a
_freetype () {
local ver=2.14.3
gettar "https://download.savannah.gnu.org/releases/freetype/freetype-${ver}.tar.xz"
gettar "https://download.savannah.gnu.org/releases/freetype/freetype-${ver}.tar.xz" || \
gettar "https://sourceforge.net/projects/freetype/files/freetype-${ver}.tar.xz"
builddir freetype-${ver}
meson setup .. --cross-file "$prefix_dir/crossfile"
makeplusinstall
+7 -1
View File
@@ -197,11 +197,17 @@ subrandr_build = custom_target(
],
console: true
)
python = find_program('python3')
subrandr_lib = custom_target(
'subrandr-copy',
input: subrandr_build,
output: 'subrandr.lib',
command: ['cp', meson.current_build_dir() / 'lib' / 'subrandr.lib', '@OUTPUT@']
command: [
python,
'-c',
'import shutil; import sys; shutil.copy2(sys.argv[1], sys.argv[2])',
meson.current_build_dir() / 'lib' / 'subrandr.lib', '@OUTPUT@'
]
)
harfbuzz = dependency('harfbuzz', default_options: ['freetype=enabled'])
dep = declare_dependency(
+22
View File
@@ -22,6 +22,7 @@
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <limits.h>
#include "config.h"
@@ -65,6 +66,27 @@
// Return a+b, unless a is NOPTS. b must not be NOPTS.
#define MP_ADD_PTS(a, b) ((a) == MP_NOPTS_VALUE ? (a) : ((a) + (b)))
// Return the maximum value representable by integer type of a.
#define MP_MAX_VAL(a) _Generic((a), \
char: SCHAR_MAX, \
unsigned char: UCHAR_MAX, \
short: SHRT_MAX, \
unsigned short: USHRT_MAX, \
int: INT_MAX, \
unsigned int: UINT_MAX, \
long: LONG_MAX, \
unsigned long: ULONG_MAX, \
long long: LLONG_MAX, \
unsigned long long: ULLONG_MAX)
// Multiply a * b and store in result. If result would overflow, saturate at
// its maximum value instead, and return true. Otherwise, return false.
#define MP_SATURATE_MUL(result, a, b) ( \
MP_CKD_MUL(result, a, b) ? \
*result = MP_MAX_VAL(*result), true : \
false \
)
#define CONTROL_OK 1
#define CONTROL_TRUE 1
#define CONTROL_FALSE 0
+1 -1
View File
@@ -12,7 +12,7 @@ if not sys.argv[1]:
sys.exit(1)
git_cmd = [sys.argv[1], "--git-dir=" + os.path.join(sys.argv[2], ".git"),
"--work-tree=" + sys.argv[2]]
describe_cmd = git_cmd + ["describe", "--abbrev=9", "--tags", "--dirty"]
describe_cmd = git_cmd + ["describe", "--abbrev=9", "--tags", "--dirty", "--match", "v0.*"]
try:
ver = check_output(describe_cmd, stderr=DEVNULL, encoding="UTF-8")
except CalledProcessError:
+35 -13
View File
@@ -27,6 +27,7 @@
#include "common/common.h"
#include "common/global.h"
#include "misc/bstr.h"
#include "misc/codepoint_width.h"
#include "options/options.h"
#include "options/path.h"
@@ -585,23 +586,44 @@ void mp_msg_sanitize(bstr *text, bool allow_sgr)
// Nuke everything that is not an SGR sequence
if (!sgr)
text->start[i] = '?';
continue;
}
// Allow only printable > 0x20 and 0x08-0x0D (backspace, tab, newline, ...)
else if (ch < 0x08 || (ch > 0x0D && ch < 0x20) || ch == 0x7F) {
text->start[i] = '?';
int len = bstr_parse_utf8_code_length(ch);
// ASCII: allow only printable plus HT, LF, CR.
if (len == 1) {
if (ch == 0x7F || (ch < 0x20 && ch != '\t' && ch != '\n' && ch != '\r'))
text->start[i] = '?';
continue;
}
// Block UTF-8 encoded C1 controls (U+0080-U+009F = bytes C2 80..C2 9F),
// except PU1 (0x91) and PU2 (0x92) used as internal escape prefixes.
else if (ch == 0xC2 && i + 1 < text->len &&
(unsigned char)text->start[i + 1] >= 0x80 &&
(unsigned char)text->start[i + 1] <= 0x9F &&
(unsigned char)text->start[i + 1] != 0x91 &&
(unsigned char)text->start[i + 1] != 0x92)
{
text->start[i] = '?';
text->start[i + 1] = '?';
// Block UTF-8 C1 controls (C2 80..C2 9F), except PU1/PU2 used internally.
if (ch == 0xC2 && i + 1 < text->len) {
unsigned char c1 = text->start[i + 1];
if (c1 >= 0x80 && c1 <= 0x9F && c1 != 0x91 && c1 != 0x92) {
text->start[i] = '?';
text->start[i + 1] = '?';
}
i++;
continue;
}
// Skip valid multi-byte sequences so continuation bytes aren't seen as
// raw C1. Reject overlong 0xC0/0xC1 and verify the bytes before skipping.
if (len >= 2 && ch >= 0xC2 && i + len <= text->len) {
bool valid = true;
for (int n = 1; n < len; n++)
valid &= (text->start[i + n] & 0xC0) == 0x80;
if (valid) {
i += len - 1;
continue;
}
}
// Block raw 8-bit C1 (0x80-0x9F); leave other invalid bytes alone.
if (ch <= 0x9F)
text->start[i] = '?';
}
}
+9
View File
@@ -52,6 +52,15 @@ void playlist_entry_add_params(struct playlist_entry *e,
playlist_entry_add_param(e, params[n].name, params[n].value);
}
void playlist_set_params(struct playlist *pl, struct playlist_param *params,
int num_params)
{
for (int n = 0; n < pl->num_entries; n++) {
pl->entries[n]->num_params = 0;
playlist_entry_add_params(pl->entries[n], params, num_params);
}
}
static void playlist_update_indexes(struct playlist *pl, int start, int end)
{
start = MPMAX(start, 0);
+2
View File
@@ -84,6 +84,8 @@ void playlist_entry_add_param(struct playlist_entry *e, bstr name, bstr value);
void playlist_entry_add_params(struct playlist_entry *e,
struct playlist_param *params,
int params_count);
void playlist_set_params(struct playlist *pl, struct playlist_param *params,
int num_params);
struct playlist_entry *playlist_entry_new(const char *filename);
Loaded 100 of 321 files, more files were not shown because too many files have changed in this diff. Show more