Compare commits
351
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c49631d857 | ||
|
|
ecaad47c23 | ||
|
|
1b24aebe77 | ||
|
|
67ed15e5d4 | ||
|
|
6064d0e05a | ||
|
|
246653e2e1 | ||
|
|
5d411509a5 | ||
|
|
7afc6919ce | ||
|
|
cfa8fa3cef | ||
|
|
c8b8e76705 | ||
|
|
f67cf6ce00 | ||
|
|
6e06941fec | ||
|
|
afad6a40fe | ||
|
|
f51544026b | ||
|
|
471c9dbb6c | ||
|
|
a06e098787 | ||
|
|
c3a4f9469a | ||
|
|
8cae735e3c | ||
|
|
82325ddfa9 | ||
|
|
9c8e679c94 | ||
|
|
fe1935c3b3 | ||
|
|
8f58fad5cd | ||
|
|
3bcdcfd2a8 | ||
|
|
e2f4fbd831 | ||
|
|
29cd001796 | ||
|
|
e34a718c48 | ||
|
|
f1f0484eb4 | ||
|
|
32422c4e06 | ||
|
|
7f3d2e0747 | ||
|
|
14149854b8 | ||
|
|
e594f66c35 | ||
|
|
c6f267d6dd | ||
|
|
49b078737b | ||
|
|
f9d1481fb8 | ||
|
|
0db086a1e9 | ||
|
|
d7cf273432 | ||
|
|
8a30e06621 | ||
|
|
e4bc586d77 | ||
|
|
5a50baf26a | ||
|
|
d84fd47f9b | ||
|
|
09de481b03 | ||
|
|
329728ce56 | ||
|
|
46582ed8bd | ||
|
|
e5f6c0ced2 | ||
|
|
e1bc8cc52f | ||
|
|
c239b1c186 | ||
|
|
1f1a6e5962 | ||
|
|
37df87fc56 | ||
|
|
3f6132637b | ||
|
|
9fdf08073e | ||
|
|
dfda10b6d8 | ||
|
|
9941ad1da9 | ||
|
|
7021f883cb | ||
|
|
c650a4214d | ||
|
|
d4c84601fa | ||
|
|
2356e2f67b | ||
|
|
75ed9dd2f4 | ||
|
|
f178d38ce6 | ||
|
|
2c469d4299 | ||
|
|
182fa6ca49 | ||
|
|
c318236b88 | ||
|
|
e1cf77c0e9 | ||
|
|
93451ef64f | ||
|
|
795cd15438 | ||
|
|
96b9d84b60 | ||
|
|
79186bd167 | ||
|
|
6f6bedec72 | ||
|
|
a47452aac0 | ||
|
|
0c87e46c83 | ||
|
|
15750e1212 | ||
|
|
16896f33c0 | ||
|
|
dd18d1eabc | ||
|
|
abbbffe0dd | ||
|
|
4a110f39cf | ||
|
|
02ec593037 | ||
|
|
fee9b14d46 | ||
|
|
4109805d0c | ||
|
|
c855d13170 | ||
|
|
daaab42166 | ||
|
|
b3e3d1774e | ||
|
|
34c649b864 | ||
|
|
a2448f3b81 | ||
|
|
263bea5261 | ||
|
|
8c6680a868 | ||
|
|
be04215d3f | ||
|
|
76c0c5de03 | ||
|
|
bb19b0fc36 | ||
|
|
c9422ff88f | ||
|
|
c7859fe5b6 | ||
|
|
b17b4371f8 | ||
|
|
15f8a74aa4 | ||
|
|
7c13c58ffa | ||
|
|
61416bd8b5 | ||
|
|
b48fb6c86c | ||
|
|
2c8d954d21 | ||
|
|
8356c7f674 | ||
|
|
47a53910a5 | ||
|
|
b0c4e8cab6 | ||
|
|
582810ac1d | ||
|
|
dbe496e6be | ||
|
|
af53a9dc4b | ||
|
|
4084e7609f | ||
|
|
3897b3a579 | ||
|
|
fbcced5bf0 | ||
|
|
5f19209953 | ||
|
|
f0bc30aaf1 | ||
|
|
c625405ddc | ||
|
|
70174945a4 | ||
|
|
4725c2e492 | ||
|
|
216e26c871 | ||
|
|
a5682eb2ad | ||
|
|
5713c30160 | ||
|
|
816eca1fef | ||
|
|
652ca81af9 | ||
|
|
8d36904e05 | ||
|
|
e204c1cfd9 | ||
|
|
af9a11a8e5 | ||
|
|
3a8b6995e9 | ||
|
|
b17feb1f79 | ||
|
|
fbd0e047b1 | ||
|
|
e2b7a983fa | ||
|
|
5db1db2852 | ||
|
|
3ba4f76096 | ||
|
|
8b583e9a0d | ||
|
|
fad4b3f321 | ||
|
|
b573edaaa1 | ||
|
|
fbca538bfe | ||
|
|
f73dd13376 | ||
|
|
57b0a0a695 | ||
|
|
654e9382c0 | ||
|
|
1c2ff7d56b | ||
|
|
66ff2efbc7 | ||
|
|
345e783643 | ||
|
|
e89a491052 | ||
|
|
413153fd4e | ||
|
|
65d138fd57 | ||
|
|
9f9f8c4dd4 | ||
|
|
b955aa28f3 | ||
|
|
228f3109fd | ||
|
|
3b4caf0f8b | ||
|
|
49418246f3 | ||
|
|
a87a678573 | ||
|
|
e7191f2a65 | ||
|
|
b6d3434493 | ||
|
|
e034d612cf | ||
|
|
e167836802 | ||
|
|
52bb166f30 | ||
|
|
7b8915bc1d | ||
|
|
f4d13e1c2c | ||
|
|
513d3407d4 | ||
|
|
dd5d17d328 | ||
|
|
21277b0ccf | ||
|
|
1d15686142 | ||
|
|
55146ecd14 | ||
|
|
612bf5b6e8 | ||
|
|
22c09166d7 | ||
|
|
63ada87ec7 | ||
|
|
56b6200986 | ||
|
|
74356c0fc6 | ||
|
|
1388a45395 | ||
|
|
24c1cc52a3 | ||
|
|
7f72f64b7f | ||
|
|
48e6c35c0e | ||
|
|
390ebe2eac | ||
|
|
b27573a239 | ||
|
|
762c5999c2 | ||
|
|
5910e9c2ba | ||
|
|
8849c4963f | ||
|
|
5659a9a9c8 | ||
|
|
f76519ebd8 | ||
|
|
05d45f4955 | ||
|
|
42d50061f6 | ||
|
|
731229bd2b | ||
|
|
19215efe61 | ||
|
|
97db44f89f | ||
|
|
8c67647b50 | ||
|
|
0fb136f685 | ||
|
|
6f6c29b0b4 | ||
|
|
1c4adc9819 | ||
|
|
11a9054b1d | ||
|
|
7ff087a6b0 | ||
|
|
e61d819bf6 | ||
|
|
893ffae982 | ||
|
|
0583bb892e | ||
|
|
181090e70d | ||
|
|
e2eaf31ce2 | ||
|
|
e4abaaa26b | ||
|
|
23d39a6048 | ||
|
|
11e6a50025 | ||
|
|
15de3e6193 | ||
|
|
8ab3f8b66d | ||
|
|
25451bcc11 | ||
|
|
94335ab87a | ||
|
|
e5486b96d7 | ||
|
|
115b87b521 | ||
|
|
5b087dfb18 | ||
|
|
e517fd6043 | ||
|
|
68387ea859 | ||
|
|
cc763d17dc | ||
|
|
cef62fbc39 | ||
|
|
7f93b06797 | ||
|
|
02baec9701 | ||
|
|
ab34ec35d2 | ||
|
|
4c220ffd95 | ||
|
|
6a0ef86e00 | ||
|
|
ba92eb63c4 | ||
|
|
8e88661257 | ||
|
|
28a7932599 | ||
|
|
ed9c05d06e | ||
|
|
9281bccfdf | ||
|
|
c8c7d91a8e | ||
|
|
7bf2d9cee6 | ||
|
|
318e4e21fb | ||
|
|
f6df7dfd8b | ||
|
|
33111f3212 | ||
|
|
7a94ec5719 | ||
|
|
7aa8580cab | ||
|
|
c02751a0bf | ||
|
|
dc8101574d | ||
|
|
0279c0f07d | ||
|
|
c22cac669e | ||
|
|
6d64f2867c | ||
|
|
1c467ad4f9 | ||
|
|
a3d4b59197 | ||
|
|
99b4c12ccc | ||
|
|
abfc3b21c8 | ||
|
|
8930fdb132 | ||
|
|
f53e8f3fed | ||
|
|
103aee7261 | ||
|
|
6b4a205c99 | ||
|
|
95f8f18d13 | ||
|
|
116c8ecf3c | ||
|
|
998c20af67 | ||
|
|
6f724890e0 | ||
|
|
29c693c039 | ||
|
|
5058e6e737 | ||
|
|
5330cae57e | ||
|
|
81037dae3a | ||
|
|
0480011ac4 | ||
|
|
6c4480cbfd | ||
|
|
314e80f2ec | ||
|
|
25606307ff | ||
|
|
bc5684c2cf | ||
|
|
3f1b23abd0 | ||
|
|
5abce15f1c | ||
|
|
4dbc235dba | ||
|
|
523aea99ad | ||
|
|
42149e9eac | ||
|
|
508d25590d | ||
|
|
62f1494661 | ||
|
|
a01e391851 | ||
|
|
be8d1eb3d6 | ||
|
|
a8a1cdbbbb | ||
|
|
069a6f004e | ||
|
|
5d15a41b0f | ||
|
|
245652c34c | ||
|
|
fe4eaf7c9d | ||
|
|
5c97ee2a48 | ||
|
|
aa845b965a | ||
|
|
8395d89069 | ||
|
|
9dbf5d681d | ||
|
|
089c172dff | ||
|
|
4fa3c38983 | ||
|
|
1a09154988 | ||
|
|
9ffc0cd861 | ||
|
|
df3b76b018 | ||
|
|
2057e34f45 | ||
|
|
381e7eb155 | ||
|
|
4eaef66f4c | ||
|
|
29c26b5bc5 | ||
|
|
9bfdb3d67f | ||
|
|
d8c82c3b16 | ||
|
|
1cf081e66c | ||
|
|
80865a7d1f | ||
|
|
a40319a514 | ||
|
|
fb6c700ecb | ||
|
|
ccfe4871db | ||
|
|
449944e25f | ||
|
|
fc26cd23c0 | ||
|
|
7da362dfb5 | ||
|
|
6b623e29cd | ||
|
|
669f431cf0 | ||
|
|
f5a894de87 | ||
|
|
8c6ad1b002 | ||
|
|
ae99e39239 | ||
|
|
0251783b95 | ||
|
|
23b9e96e9b | ||
|
|
351291d583 | ||
|
|
0d68a842a0 | ||
|
|
2a49cc01d6 | ||
|
|
1d8cb602ac | ||
|
|
07c8c16bef | ||
|
|
70894ae039 | ||
|
|
c75b8d2cca | ||
|
|
289723b41a | ||
|
|
c4207644ba | ||
|
|
2339eb7276 | ||
|
|
2bd9c3229f | ||
|
|
1bcc17a7be | ||
|
|
dde8d8c4a0 | ||
|
|
efe8644a93 | ||
|
|
2d5dfb343a | ||
|
|
f8150c78d5 | ||
|
|
02491a3949 | ||
|
|
715d2b7cf2 | ||
|
|
3159500884 | ||
|
|
4acaa77de5 | ||
|
|
9d828d5e55 | ||
|
|
306546f35f | ||
|
|
7bc56a77a9 | ||
|
|
688b2738c6 | ||
|
|
49b06cc337 | ||
|
|
6b48326ccf | ||
|
|
d849fd60d0 | ||
|
|
de1c73a44b | ||
|
|
5fb6a76ddf | ||
|
|
53796ca2ea | ||
|
|
f88bcd25c7 | ||
|
|
9baeefe635 | ||
|
|
cab6fbe222 | ||
|
|
89da9deafe | ||
|
|
65a1852ba3 | ||
|
|
662d41093c | ||
|
|
7d245fd100 | ||
|
|
bda1dcaa78 | ||
|
|
3d4893a893 | ||
|
|
304426c390 | ||
|
|
fb5345809d | ||
|
|
c1c047f8d6 | ||
|
|
894c350f83 | ||
|
|
1ad719ff19 | ||
|
|
f232963c61 | ||
|
|
a7d0ebaf76 | ||
|
|
dddafe8b60 | ||
|
|
5e4b06d977 | ||
|
|
5dfdd39997 | ||
|
|
6444c05059 | ||
|
|
e7f5d8f9c8 | ||
|
|
c42393a280 | ||
|
|
c9cf510d6a | ||
|
|
6a5e514509 | ||
|
|
a24227d7ad | ||
|
|
f29fcafb81 | ||
|
|
579a62c2ba | ||
|
|
47f81c8d7e | ||
|
|
43b14a4c9f | ||
|
|
71ebd08406 | ||
|
|
85fe21d290 | ||
|
|
f88f423829 | ||
|
|
1ad1322335 | ||
|
|
0fc000b10b |
No files matched your search
+147
-103
@@ -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.
|
||||
|
||||
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -1,3 +1,7 @@
|
||||
/subprojects/*
|
||||
!/subprojects/packagefiles
|
||||
!/subprojects/*.wrap
|
||||
/build/
|
||||
/dist/
|
||||
.hvigor/
|
||||
.DS_Store
|
||||
@@ -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;本轮没有把公网失败当作性能数据。
|
||||
@@ -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、鉴权和弱网重缓冲应在实体设备用固定服务端脚本复测。
|
||||
@@ -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
|
||||
@@ -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 语义留到后续阶段验证。
|
||||
@@ -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。
|
||||
@@ -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
|
||||
```
|
||||
@@ -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。
|
||||
@@ -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 自身许可使用,没有复制第三方实现源码。
|
||||
@@ -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 偏差、路由与多声道仍需实体设备,不宣称低延迟或多声道完成。
|
||||
@@ -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 依赖验证。
|
||||
@@ -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 顺序不同,应记录实际字体、脚本和缺字结果。
|
||||
@@ -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`。
|
||||
@@ -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。
|
||||
@@ -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`。
|
||||
@@ -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 或设备硬解验证。
|
||||
@@ -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。
|
||||
@@ -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 错误不伪装成网络错误。
|
||||
@@ -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 选项说明。
|
||||
@@ -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`。
|
||||
@@ -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。
|
||||
@@ -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 源码。
|
||||
@@ -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;诊断仅累加轻量计数和受控序列化时间。
|
||||
@@ -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,不由模型推断。
|
||||
@@ -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 输入。
|
||||
@@ -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`)的前置条件是实体机首帧、控制和压力矩阵完成。
|
||||
@@ -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。
|
||||
@@ -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 行为。
|
||||
@@ -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 事件/属性可改变对应诊断字段。
|
||||
@@ -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;本阶段未引入新的第三方依赖或商业资源。
|
||||
@@ -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 不在本阶段启用。
|
||||
@@ -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` 或脱敏错误。
|
||||
@@ -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 指标仅在“显示调试统计”打开时显示。
|
||||
@@ -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 不是首版必需。
|
||||
@@ -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原型在目标机显著改善 |
|
||||
| 商业发布 | 实际链接清单、许可证正文、源码义务、法务审核全部完成 |
|
||||
@@ -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`。
|
||||
@@ -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。
|
||||
@@ -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。
|
||||
@@ -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 路径入口。
|
||||
@@ -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。
|
||||
@@ -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。
|
||||
@@ -0,0 +1,3 @@
|
||||
add `--disc-menu` option
|
||||
add `disc-menu-active` property
|
||||
add `discnav` command
|
||||
@@ -0,0 +1,2 @@
|
||||
add `dvda://` protocol for DVD-Audio playback
|
||||
add `--dvda-device` and `--dvda-page` options
|
||||
@@ -0,0 +1 @@
|
||||
add `vf=format=enhancement-layer` option
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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::
|
||||
|
||||
|
||||
@@ -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``
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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!
|
||||
@@ -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 使用。
|
||||
@@ -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。
|
||||
@@ -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`。
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ×tamp) == 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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
results/*.json
|
||||
reports/*.md
|
||||
runner/__pycache__/
|
||||
!results/.gitkeep
|
||||
!reports/.gitkeep
|
||||
Whitespace-only changes.
Whitespace-only changes.
Executable
+385
@@ -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())
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "seek_local",
|
||||
"media": "sample.mp4",
|
||||
"positionsPercent": [10, 25, 50, 75, 90],
|
||||
"repeatPerPosition": 10,
|
||||
"modes": ["fast", "exact"]
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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,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
@@ -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
@@ -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] = '?';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Reference in new issue
Block a user