Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e00bcc14c | ||
|
|
7635438592 | ||
|
|
e518bf2c72 | ||
|
|
d47b708f00 | ||
|
|
2fb2f9b93a | ||
|
|
995c47da9a | ||
|
|
da9590d368 | ||
|
|
34a33ebd4d | ||
|
|
e58ecc3630 | ||
|
|
6d83455a95 | ||
|
|
35be389031 | ||
|
|
885e991312 | ||
|
|
26b6873ffd | ||
|
|
71a8aa2e9a | ||
|
|
16b48a429d | ||
|
|
f2110f582d | ||
|
|
8660b4c9f0 | ||
|
|
ab6fac43b4 | ||
|
|
5d2f1da7c5 | ||
|
|
771a8bf5c6 | ||
|
|
08cd50b84b | ||
|
|
0b1ef81498 | ||
|
|
fc76966d9e | ||
|
|
0fe62bc1d2 | ||
|
|
d72bcc8041 | ||
|
|
329a7147d0 | ||
|
|
4f4eea6b44 | ||
|
|
33e72ed451 | ||
|
|
6a3da439cd | ||
|
|
f98e1b0b96 | ||
|
|
7cd1a3c766 | ||
|
|
46f1ce1e2f | ||
|
|
be230d16e5 | ||
|
|
5b6cce2b73 | ||
|
|
923e3c7b20 | ||
|
|
d81fb97f45 | ||
|
|
3abf9c9204 | ||
|
|
ea23a86f49 | ||
|
|
8ed32e90c9 | ||
|
|
37cf92c07a | ||
|
|
59972fbfe1 | ||
|
|
f110552898 | ||
|
|
315e2e3da8 | ||
|
|
b57debe5b3 | ||
|
|
8c7f9dc1a8 | ||
|
|
823c353faa | ||
|
|
06219c7f88 | ||
|
|
5d47f9a565 | ||
|
|
1298c29dc7 | ||
|
|
76a73a0a5d | ||
|
|
b2ce00ed56 | ||
|
|
d057e7a142 | ||
|
|
3738bfb451 | ||
|
|
8523e1fd95 | ||
|
|
fd5098a409 | ||
|
|
c0f272fcd1 | ||
|
|
1b71cfccba | ||
|
|
549a9ea6fa | ||
|
|
614efea3e6 | ||
|
|
c57304a591 | ||
|
|
2d6da37b6b | ||
|
|
e40851ea22 | ||
|
|
9ed5dae480 | ||
|
|
c6953bfa8c | ||
|
|
deb1c3c7a8 | ||
|
|
9ca1592f3f | ||
|
|
17c5738cb4 | ||
|
|
d5615102d5 | ||
|
|
40c9b38023 | ||
|
|
dc9a5cbfd7 | ||
|
|
65499d863a | ||
|
|
6e6c32ed6c | ||
|
|
f9fe5d06ad | ||
|
|
f3b6966d14 | ||
|
|
740fdc139f | ||
|
|
247ec0cb84 | ||
|
|
13a63331b6 | ||
|
|
9278ce98f7 | ||
|
|
4ce53025cb | ||
|
|
3e58ce96ac | ||
|
|
acb74236ac | ||
|
|
c5094206ce | ||
|
|
24478a8a72 | ||
|
|
e081e46950 | ||
|
|
22c76e85db | ||
|
|
d1d864546c | ||
|
|
69e7b0d3ea | ||
|
|
ad56f2c46a | ||
|
|
393bb2a565 | ||
|
|
ea098d3462 | ||
|
|
1c3bbd9318 | ||
|
|
e911e208b8 | ||
|
|
5c74da4503 | ||
|
|
c071c30bcd | ||
|
|
b01855714b | ||
|
|
5a60f594e5 | ||
|
|
31b73d5ca0 | ||
|
|
b753c229f7 | ||
|
|
7ea22fe889 | ||
|
|
9d0af06811 | ||
|
|
6f9973618c | ||
|
|
b40d8fdfa3 | ||
|
|
22291a2587 | ||
|
|
fc50f3772c | ||
|
|
65280e47cd | ||
|
|
30adf280fd | ||
|
|
9bac0ea421 | ||
|
|
761eeacf54 | ||
|
|
7be37337f4 | ||
|
|
754ad1d730 | ||
|
|
0cf187caca | ||
|
|
07c11656e3 | ||
|
|
67a6203ce0 | ||
|
|
3df95ee57a | ||
|
|
61bc96518a | ||
|
|
47d9fbd133 | ||
|
|
fb7c5804bb | ||
|
|
0fd5a24ecb | ||
|
|
4a15bc6d73 | ||
|
|
9215a9e598 | ||
|
|
689cf04571 | ||
|
|
8e6704acdb | ||
|
|
28c7dec157 | ||
|
|
56058a95e5 | ||
|
|
f72eb5b394 | ||
|
|
454fff39ad | ||
|
|
8986b37fe7 | ||
|
|
d0bd39eb90 | ||
|
|
7ecac3ae6f | ||
|
|
a6074a3375 | ||
|
|
36f8d41702 | ||
|
|
1878a4655d | ||
|
|
5700da4a8f | ||
|
|
43e964e2d6 | ||
|
|
a30e727266 | ||
|
|
788929e4e0 | ||
|
|
3682df2dd5 | ||
|
|
81ec9cf596 | ||
|
|
b00eab525a | ||
|
|
972ea9ca59 | ||
|
|
3d844cddf8 | ||
|
|
0b082b2086 | ||
|
|
bb9aad097a | ||
|
|
9fcc517cee | ||
|
|
de4c74e5a4 | ||
|
|
9cc5507079 | ||
|
|
bea2e39721 | ||
|
|
2b1656b1ac | ||
|
|
1c5fd2d901 | ||
|
|
d38d26057a | ||
|
|
262ceca731 | ||
|
|
6fd7f5a111 | ||
|
|
b51957fab5 | ||
|
|
4123c9642d | ||
|
|
ff37d7efec | ||
|
|
54c48bd801 | ||
|
|
a15181e5df | ||
|
|
b906eb37ed | ||
|
|
7373158703 | ||
|
|
69d5be1ee9 | ||
|
|
bd9bba40ee | ||
|
|
aeb3df0e2c | ||
|
|
9c286d7472 | ||
|
|
68796fbdf5 | ||
|
|
5b5db336e9 | ||
|
|
38ac5d5e7b | ||
|
|
9bd886f449 | ||
|
|
393a069112 | ||
|
|
8ceb935bd8 | ||
|
|
88b584656d | ||
|
|
a9150a49d7 | ||
|
|
b3e74f652b | ||
|
|
c1cb04b6a3 | ||
|
|
ee337b47be | ||
|
|
b3a3ed00f0 | ||
|
|
65f5fbc5d4 | ||
|
|
352904fd03 | ||
|
|
2dfea67f3b | ||
|
|
a9ffe38aa1 | ||
|
|
026b75e7f5 | ||
|
|
2179879172 | ||
|
|
2d76c145c3 | ||
|
|
645a0d7791 | ||
|
|
62acbd795f | ||
|
|
e44882d792 | ||
|
|
29ea6e5e4a | ||
|
|
68c77d955f | ||
|
|
45c3e0f0d0 | ||
|
|
098ff4174c | ||
|
|
9009601c2d | ||
|
|
4853eca8c6 | ||
|
|
079f67268f | ||
|
|
15bb05d2fe | ||
|
|
48015009b7 | ||
|
|
899d3e55b3 | ||
|
|
256d9d2b89 | ||
|
|
b1dd3beda9 | ||
|
|
240ba92bed | ||
|
|
0348cd080f | ||
|
|
49f73eaf7b | ||
|
|
9eccedf447 | ||
|
|
9f42760538 | ||
|
|
f8df0528b5 | ||
|
|
72e94941c9 | ||
|
|
84fba1df21 | ||
|
|
ca87e623b5 | ||
|
|
306c554376 | ||
|
|
e5f49d9685 | ||
|
|
5c9f164caf | ||
|
|
fc18e1df62 | ||
|
|
68191fdca7 | ||
|
|
5353571de0 | ||
|
|
15a5d33b79 | ||
|
|
b9cc33de58 | ||
|
|
8ff96f0216 | ||
|
|
d941a57bd3 | ||
|
|
6bd021e4ee | ||
|
|
e293e411b5 | ||
|
|
335278b86e | ||
|
|
c4707cdee6 | ||
|
|
e76aa7e8db | ||
|
|
66079048ea | ||
|
|
049e3ccb65 | ||
|
|
80d702dce8 | ||
|
|
afcef4c25b | ||
|
|
75f373cc46 | ||
|
|
cc72a4e8c3 | ||
|
|
cc4a0571fa | ||
|
|
c06a92e09e | ||
|
|
4ff2a60632 | ||
|
|
4aa017e301 | ||
|
|
c4e8c36071 | ||
|
|
5a3c299ca1 | ||
|
|
364af7c630 | ||
|
|
981048e041 | ||
|
|
035297212a | ||
|
|
79afa347cc | ||
|
|
3deedef195 | ||
|
|
f0d7437412 | ||
|
|
e431a7bbfd | ||
|
|
05b3a8cd14 | ||
|
|
561630cb01 | ||
|
|
4e5f1ec00e | ||
|
|
b0d3c2ede7 | ||
|
|
b46eaa3b43 | ||
|
|
5f7bd411fb | ||
|
|
0b911792ca | ||
|
|
26b6d74484 | ||
|
|
a02d77ba0d | ||
|
|
39b64fb176 | ||
|
|
9b56bbecdf | ||
|
|
b18a598444 | ||
|
|
a2d58d9986 | ||
|
|
f00040b9fc | ||
|
|
09763bef83 | ||
|
|
8262a4dd52 | ||
|
|
ca09cd68c8 | ||
|
|
e73c83d3df | ||
|
|
e6f6ae94f3 | ||
|
|
3bdbf6274c | ||
|
|
b6c40953cb | ||
|
|
887b2cc30f | ||
|
|
1bc902336a | ||
|
|
51307e9f18 | ||
|
|
70df6aa375 | ||
|
|
155857dbe6 | ||
|
|
0c40eee479 | ||
|
|
dd99c1e3b4 | ||
|
|
56ed5bf1c7 | ||
|
|
320627d600 | ||
|
|
51603e472d | ||
|
|
e047cc0931 | ||
|
|
3cfe98c684 | ||
|
|
f81f486c68 | ||
|
|
965031ccd5 | ||
|
|
362015cd77 | ||
|
|
dfc7b59909 | ||
|
|
034faaa9d8 | ||
|
|
7c3d78fd82 | ||
|
|
d53142f9ba | ||
|
|
070edd7300 | ||
|
|
3d4889e91e | ||
|
|
32c10956e0 | ||
|
|
6ae7a555bf | ||
|
|
449b948ee8 | ||
|
|
77d50cb3cf | ||
|
|
3858d37b61 | ||
|
|
09e07e92c5 | ||
|
|
7d2c6d60da | ||
|
|
c9d8bc088c | ||
|
|
f7c81c03b2 | ||
|
|
5bd6b76f5c | ||
|
|
ee85473d4a | ||
|
|
a228bf54c8 | ||
|
|
0cd217b039 | ||
|
|
bd41a3ab62 | ||
|
|
84ccebd9b9 | ||
|
|
e68b510a94 | ||
|
|
c160e5f31d | ||
|
|
d4712af5af | ||
|
|
809d235208 | ||
|
|
01d04b100f | ||
|
|
fe540f4477 | ||
|
|
70b3561270 | ||
|
|
2ec26b8396 | ||
|
|
cb694ffd8e | ||
|
|
7983cda5bf | ||
|
|
fd82e14888 | ||
|
|
fde20d10bc | ||
|
|
bea7675307 | ||
|
|
1f6e71c7fa | ||
|
|
a3d416c3d3 | ||
|
|
4b3faf9dc1 | ||
|
|
12ae19c449 | ||
|
|
b0b01aa250 | ||
|
|
7e6e47b8c4 | ||
|
|
013d63b081 | ||
|
|
5d8d8618c4 | ||
|
|
da0bf27930 | ||
|
|
af0a8d7478 | ||
|
|
00c9bdf3f7 | ||
|
|
73aa7484f2 | ||
|
|
46fff8d31a | ||
|
|
ee4c00698f | ||
|
|
f99e48ae6a | ||
|
|
7d9eab15f0 | ||
|
|
2f8b4dd480 | ||
|
|
ababfcbfe3 | ||
|
|
8fe33a11e2 | ||
|
|
11406b9215 | ||
|
|
062493fa7e | ||
|
|
55846641ea | ||
|
|
7c5188c27e | ||
|
|
a93fb460cd | ||
|
|
eef271c8f4 | ||
|
|
7a75e7c002 | ||
|
|
605dd928d3 | ||
|
|
f56555b514 | ||
|
|
e5e1088c92 | ||
|
|
b2646911a9 | ||
|
|
833375f88d | ||
|
|
86b5f1463c | ||
|
|
9054460bba | ||
|
|
05c398fb6c | ||
|
|
485ae095f7 | ||
|
|
5be40f035b | ||
|
|
eefe7ad28b | ||
|
|
51e4c065ff | ||
|
|
8a26e139af | ||
|
|
f54e555d27 | ||
|
|
3ff3749b7d | ||
|
|
622bcb0e37 | ||
|
|
9fda504d18 | ||
|
|
ed695ce689 | ||
|
|
949247d6e6 | ||
|
|
70f64f3ca9 | ||
|
|
5c90352ae6 | ||
|
|
0826b78bc5 | ||
|
|
3a8058658d | ||
|
|
8d51f08010 | ||
|
|
64f9e48bf1 | ||
|
|
016eab2209 | ||
|
|
bda111018c | ||
|
|
0ed206abd9 | ||
|
|
d30634b104 | ||
|
|
304d9d58dd | ||
|
|
9d16837c99 | ||
|
|
757c8baf8c | ||
|
|
5df4ee55f1 | ||
|
|
9cb036f297 | ||
|
|
0b1ba577b1 | ||
|
|
dff33893f2 | ||
|
|
9896994688 | ||
|
|
3706918311 | ||
|
|
cf9b415173 | ||
|
|
d3a26272cd | ||
|
|
0c6c62cb17 | ||
|
|
da59726776 | ||
|
|
66a958bb4f | ||
|
|
dfe40f770d | ||
|
|
6798525251 | ||
|
|
74e3d11f21 | ||
|
|
dfa88271a2 | ||
|
|
8ffd2f1dd4 | ||
|
|
6a1814dfb8 | ||
|
|
7e3d8e7134 | ||
|
|
46e49a37be | ||
|
|
bb17df1f07 | ||
|
|
4f5509e1dd | ||
|
|
85416bc36a | ||
|
|
021cb2c387 | ||
|
|
e232f1a731 | ||
|
|
5800d1845f | ||
|
|
5f97bd4223 | ||
|
|
649e155938 | ||
|
|
47e7676e7e | ||
|
|
786f37ae1c | ||
|
|
abb3f80998 | ||
|
|
b1a8e8dba6 | ||
|
|
244eff9201 | ||
|
|
2b4123f078 | ||
|
|
1944a34c23 | ||
|
|
78346e9c9a | ||
|
|
60664bc00b | ||
|
|
834a236514 | ||
|
|
cd9ee1a853 | ||
|
|
61ac156d47 | ||
|
|
8dbc93a94c | ||
|
|
8db9206c54 | ||
|
|
503dada42f | ||
|
|
382bafcb13 | ||
|
|
ce153bdb42 | ||
|
|
2f5c7af914 | ||
|
|
611594e7e8 | ||
|
|
1f1117d0dd | ||
|
|
d797f7def9 | ||
|
|
44644e69f0 | ||
|
|
8e96e04999 | ||
|
|
9db1b7a001 | ||
|
|
050b17ca9c | ||
|
|
a77cbc504a | ||
|
|
798188cb33 | ||
|
|
d04aa5ef4e | ||
|
|
e6cdfdfa74 | ||
|
|
bbaedfd0c5 | ||
|
|
607ba5f235 | ||
|
|
1aa943d8ab | ||
|
|
b735c0e207 | ||
|
|
fbb5da0010 | ||
|
|
177f5d9e9c | ||
|
|
a9bd4535d2 | ||
|
|
8c02c92ab9 | ||
|
|
4ebac1e936 | ||
|
|
e13cc25a2c | ||
|
|
0467f5287d | ||
|
|
9a4120f21f | ||
|
|
e3e03d0f34 | ||
|
|
f4142ab9ad | ||
|
|
837b865c7f | ||
|
|
f5ff2656e0 | ||
|
|
49431626cb | ||
|
|
160497b8ff |
No files matched your search
@@ -0,0 +1,14 @@
|
||||
### mpv version and platform
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log files
|
||||
|
||||
Make a log file made with -v or --log-file=output.txt, paste it to sprunge.us,
|
||||
and replace this text with a link to it.
|
||||
@@ -0,0 +1,7 @@
|
||||
Ideally, you shouldn't need enter any text here, and your commit messages should
|
||||
explain your changes sufficiently (especially why they are needed). Read
|
||||
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md for coding
|
||||
style and development conventions. Remove this text block, but if you haven't
|
||||
agreed to it before, leave the following sentence in place:
|
||||
|
||||
I agree that my changes can be relicensed to LGPL 2.1 or later.
|
||||
@@ -15,7 +15,6 @@
|
||||
/tags
|
||||
/TAGS
|
||||
/video/out/x11_icon.inc
|
||||
/video/out/opengl/nnedi3_weights.inc
|
||||
/demux/ebml_defs.c
|
||||
/demux/ebml_types.h
|
||||
/sub/osd_font.h
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ matrix:
|
||||
- os: linux
|
||||
compiler: clang
|
||||
|
||||
before_install: ./travis-deps libass-stable $LIBAV
|
||||
before_install: TOOLS/travis-deps libass-stable $LIBAV
|
||||
script:
|
||||
- ./bootstrap.py
|
||||
- ./waf configure
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
Reporting bugs
|
||||
==============
|
||||
|
||||
- State your platform and mpv version.
|
||||
- Make sure you're actually using the latest mpv release. Linux distributions
|
||||
in particular love redistributing ancient and unmaintained mpv releases.
|
||||
- Attach a log file made with ``-v`` or ``--log-file=output.txt``.
|
||||
- Avoid attaching text files packed in archive files (zip/rar/...), unless
|
||||
explicitly asked so. There are better means, such as using http://sprunge.us
|
||||
- If you think this is file specific, upload a sample file and link it.
|
||||
- Don't report multiple unrelated/barely related bugs in one issue.
|
||||
- If you don't get a reply after days, be sure to post a new message to the
|
||||
issue - it could have been overlooked or forgotten.
|
||||
- Read: https://mpv.io/bug-reports/
|
||||
|
||||
Asking questions
|
||||
================
|
||||
|
||||
Preferably, asking questions should be done via IRC (#mpv on irc.freenode.net),
|
||||
but asking questions by opening a github issue is also barely tolerated.
|
||||
|
||||
When asking on IRC (#mpv on irc.freenode.net), don't ask to ask, just state
|
||||
your problem. Stay long enough until someone gets a chance to reply. Sometimes
|
||||
it can take hours.
|
||||
|
||||
Sending patches
|
||||
===============
|
||||
|
||||
See: https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
|
||||
|
||||
In particular, be aware that all changes to GPL code must come with the
|
||||
implicit agreement that they might be LGPLv2.1+ relicensed at a later point.
|
||||
@@ -12,9 +12,6 @@ the code to LGPLv2.1+ at a later point without asking the contributor. (This
|
||||
is a safeguard for making potential relicensing of the project to LGPLv2.1+
|
||||
easier.) Using a more liberal license compatible to LGPLv2.1+ is also ok.
|
||||
|
||||
Some GPLv3 or LGPLv3 code in mpv can be enabled by building with --enable-gpl3,
|
||||
and in this case, the resulting binaries must be distributed under GPLv3.
|
||||
|
||||
For information about authors and contributors, consult the git log, which
|
||||
contains the complete SVN and CVS history as well.
|
||||
|
||||
|
||||
@@ -32,6 +32,29 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.18.1 ---
|
||||
---- - remove "status" log level from mpv_request_log_messages() docs. This
|
||||
is 100% equivalent to "v". The behavior is still the same, thus no
|
||||
actual API change.
|
||||
--- mpv 0.18.0 ---
|
||||
1.21 - mpv_set_property() changes behavior with MPV_FORMAT_NODE. Before this
|
||||
change it rejected mpv_nodes with format==MPV_FORMAT_STRING if the
|
||||
property was not a string or did not have special mechanisms in place
|
||||
the function failed. Now it always invokes the option string parser,
|
||||
and mpv_node with a basic data type works exactly as if the function
|
||||
is invoked with that type directly. This new behavior is equivalent
|
||||
to mpv_set_option().
|
||||
This also affects the mp.set_property_native() Lua function.
|
||||
- generally, setting choice options/properties with "yes"/"no" options
|
||||
can now be set as MPV_FORMAT_FLAG
|
||||
- reading a choice property as MPV_FORMAT_NODE will now return a
|
||||
MPV_FORMAT_FLAG value if the choice is "yes" (true) or "no" (false)
|
||||
This implicitly affects Lua and JSON IPC interfaces as well.
|
||||
- big changes to vo-cmdline on vo_opengl and vo_opengl_hq (but not
|
||||
vo_opengl_cb): options are now normally not reset, but applied on top
|
||||
of the current options. The special undocumented value "-" still
|
||||
works, but now resets all options to before any vo-cmdline command
|
||||
has been called.
|
||||
--- mpv 0.12.0 ---
|
||||
1.20 - deprecate "GL_MP_D3D_interfaces"/"glMPGetD3DInterface", and introduce
|
||||
"GL_MP_MPGetNativeDisplay"/"glMPGetNativeDisplay" (this is a
|
||||
|
||||
@@ -19,6 +19,41 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.18.1 ---
|
||||
- deprecate --heartbeat-cmd
|
||||
- remove --softvol=no capability:
|
||||
- deprecate --softvol, it now does nothing
|
||||
- --volume, --mute, and the corrsponding properties now always control
|
||||
softvol, and behave as expected without surprises (e.g. you can set
|
||||
them normally while no audio is initialized)
|
||||
- rename --softvol-max to --volume-max (deprecated alias is added)
|
||||
- the --volume-restore-data option and property are removed without
|
||||
replacement. They were _always_ internal, and used for watch-later
|
||||
resume/restore. Now --volume/--mute are saved directly instead.
|
||||
- the previous point means resuming files with older watch-later configs
|
||||
will print an error about missing --volume-restore-data (which you can
|
||||
ignore), and will not restore the previous value
|
||||
- as a consequence, volume controls will no longer control PulseAudio
|
||||
per-application value, or use the system mixer's per-application
|
||||
volume processing
|
||||
- system or per-application volume can still be controlled with the
|
||||
ao-volume and ao-mute properties (there are no command line options)
|
||||
--- mpv 0.18.0 ---
|
||||
- now ab-loops are active even if one of the "ab-loop-a"/"-b" properties is
|
||||
unset ("no"), in which case the start of the file is used if the A loop
|
||||
point is unset, and the end of the file for an unset B loop point
|
||||
- deprecate --sub-ass=no option by --ass-style-override=strip
|
||||
(also needs --embeddedfonts=no)
|
||||
- add "hwdec-interop" and "hwdec-current" properties
|
||||
- deprecated "hwdec-active" and "hwdec-detected" properties (to be removed
|
||||
in mpv 0.20.0)
|
||||
- choice option/property values that are "yes" or "no" will now be returned
|
||||
as booleans when using the mpv_node functions in the client API, the
|
||||
"native" property accessors in Lua, and the JSON API. They can be set as
|
||||
such as well.
|
||||
- the VO opengl fbo-format sub-option does not accept "rgb" or "rgba"
|
||||
anymore
|
||||
- all VO opengl prescalers have been removed (replaced by user scripts)
|
||||
--- mpv 0.17.0 ---
|
||||
- deprecate "track-list/N/audio-channels" property (use
|
||||
"track-list/N/demux-channel-count" instead)
|
||||
|
||||
+5
-1
@@ -70,7 +70,7 @@ Available filters are:
|
||||
Set AVOptions on the SwrContext or AVAudioResampleContext. These should
|
||||
be documented by FFmpeg or Libav.
|
||||
|
||||
``lavcac3enc[=tospdif[:bitrate[:minch]]]``
|
||||
``lavcac3enc[=options]``
|
||||
Encode multi-channel audio to AC-3 at runtime using libavcodec. Supports
|
||||
16-bit native-endian input format, maximum 6 channels. The output is
|
||||
big-endian when outputting a raw AC-3 stream, native-endian when
|
||||
@@ -103,6 +103,10 @@ Available filters are:
|
||||
If the input channel number is less than ``<minch>``, the filter will
|
||||
detach itself (default: 3).
|
||||
|
||||
``encoder=<name>``
|
||||
Select the libavcodec encoder used. Currently, this should be an AC-3
|
||||
encoder, and using another codec will fail horribly.
|
||||
|
||||
``equalizer=g1:g2:g3:...:g10``
|
||||
10 octave band graphic equalizer, implemented using 10 IIR band-pass
|
||||
filters. This means that it works regardless of what type of audio is
|
||||
|
||||
+112
-47
@@ -459,6 +459,8 @@ Input Commands that are Possibly Subject to Change
|
||||
Remove all filters. Note that like the other sub-commands, this does
|
||||
not control automatically inserted filters.
|
||||
|
||||
The argument is always needed. E.g. in case of ``clr`` use ``vf clr ""``.
|
||||
|
||||
You can assign labels to filter by prefixing them with ``@name:`` (where
|
||||
``name`` is a user-chosen arbitrary identifier). Labels can be used to
|
||||
refer to filters by name in all of the filter chain modification commands.
|
||||
@@ -561,29 +563,20 @@ Input Commands that are Possibly Subject to Change
|
||||
the resolution is reduced to that of the video's. You can read the
|
||||
``osd-width`` and ``osd-height`` properties. At least with ``--vo-xv`` and
|
||||
anamorphic video (such as DVD), ``osd-par`` should be read as well, and the
|
||||
overlay should be aspect-compensated. (Future directions: maybe mpv should
|
||||
take care of some of these things automatically, but it's hard to tell
|
||||
where to draw the line.)
|
||||
overlay should be aspect-compensated.
|
||||
|
||||
``id`` is an integer between 0 and 63 identifying the overlay element. The
|
||||
ID can be used to add multiple overlay parts, update a part by using this
|
||||
command with an already existing ID, or to remove a part with
|
||||
``overlay-remove``. Using a previously unused ID will add a new overlay,
|
||||
while reusing an ID will update it. (Future directions: there should be
|
||||
something to ensure different programs wanting to create overlays don't
|
||||
conflict with each others, should that ever be needed.)
|
||||
while reusing an ID will update it.
|
||||
|
||||
``x`` and ``y`` specify the position where the OSD should be displayed.
|
||||
|
||||
``file`` specifies the file the raw image data is read from. It can be
|
||||
either a numeric UNIX file descriptor prefixed with ``@`` (e.g. ``@4``),
|
||||
or a filename. The file will be mapped into memory with ``mmap()``. Some VOs
|
||||
will pass the mapped pointer directly to display APIs (e.g. opengl or
|
||||
vdpau), so no actual copying is involved. Truncating the source file while
|
||||
the overlay is active will crash the player. You shouldn't change the data
|
||||
while the overlay is active, because the data is essentially accessed at
|
||||
random points. Instead, call ``overlay-add`` again (preferably with a
|
||||
different memory region to prevent tearing).
|
||||
or a filename. The file will be mapped into memory with ``mmap()``,
|
||||
copied, and unmapped before the command returns (changed in mpv 0.18.1).
|
||||
|
||||
It is also possible to pass a raw memory address for use as bitmap memory
|
||||
by passing a memory address as integer prefixed with an ``&`` character.
|
||||
@@ -616,15 +609,14 @@ Input Commands that are Possibly Subject to Change
|
||||
(Technically, the minimum size would be ``stride * (h - 1) + w * 4``, but
|
||||
for simplicity, the player will access all ``stride * h`` bytes.)
|
||||
|
||||
.. admonition:: Warning
|
||||
.. note::
|
||||
|
||||
When updating the overlay, you should prepare a second shared memory
|
||||
region (e.g. make use of the offset parameter) and add this as overlay,
|
||||
instead of reusing the same memory every time. Otherwise, you might
|
||||
get the equivalent of tearing, when your application and mpv write/read
|
||||
the buffer at the same time. Also, keep in mind that mpv might access
|
||||
an overlay's memory at random times whenever it feels the need to do
|
||||
so, for example when redrawing the screen.
|
||||
Before mpv 0.18.1, you had to do manual "double buffering" when updating
|
||||
an overlay by replacing it with a different memory buffer. Since mpv
|
||||
0.18.1, the memory is simply copied and doesn't reference any of the
|
||||
memory indicated by the command's arguments after the commend returns.
|
||||
If you want to use this command before mpv 0.18.1, reads the old docs
|
||||
to see how to handle this correctly.
|
||||
|
||||
``overlay-remove <id>``
|
||||
Remove an overlay added with ``overlay-add`` and the same ID. Does nothing
|
||||
@@ -634,10 +626,10 @@ Input Commands that are Possibly Subject to Change
|
||||
Send a message to all clients, and pass it the following list of arguments.
|
||||
What this message means, how many arguments it takes, and what the arguments
|
||||
mean is fully up to the receiver and the sender. Every client receives the
|
||||
message, so be careful about name clashes (or use ``script_message_to``).
|
||||
message, so be careful about name clashes (or use ``script-message-to``).
|
||||
|
||||
``script-message-to "<target>" "<arg1>" "<arg2>" ...``
|
||||
Same as ``script_message``, but send it only to the client named
|
||||
Same as ``script-message``, but send it only to the client named
|
||||
``<target>``. Each client (scripts etc.) has a unique name. For example,
|
||||
Lua scripts can get their name via ``mp.get_script_name()``.
|
||||
|
||||
@@ -651,8 +643,8 @@ Input Commands that are Possibly Subject to Change
|
||||
separator, e.g. ``script_binding scriptname/bindingname``.
|
||||
|
||||
For completeness, here is how this command works internally. The details
|
||||
could change any time. On any matching key event, ``script_message_to``
|
||||
or ``script_message`` is called (depending on whether the script name is
|
||||
could change any time. On any matching key event, ``script-message-to``
|
||||
or ``script-message`` is called (depending on whether the script name is
|
||||
included), with the following arguments:
|
||||
|
||||
1. The string ``key-binding``.
|
||||
@@ -1081,8 +1073,8 @@ Property list
|
||||
"default" MPV_FORMAT_FLAG
|
||||
|
||||
``ab-loop-a``, ``ab-loop-b`` (RW)
|
||||
Set/get A-B loop points. See corresponding options and ``ab_loop`` command.
|
||||
The special value ``no`` on either of these properties disables looping.
|
||||
Set/get A-B loop points. See corresponding options and ``ab-loop`` command
|
||||
for details.
|
||||
|
||||
``angle`` (RW)
|
||||
Current DVD angle.
|
||||
@@ -1242,29 +1234,31 @@ Property list
|
||||
See ``--hr-seek``.
|
||||
|
||||
``mixer-active``
|
||||
Return ``yes`` if the audio mixer is active, ``no`` otherwise. This has
|
||||
implications for ``--softvol=no`` mode: if the mixer is inactive, changing
|
||||
``volume`` doesn't actually change anything on the system mixer. If the
|
||||
``--volume`` or ``--mute`` option are used, these might not be applied
|
||||
properly until the mixer becomes active either. (The options, if set, will
|
||||
just overwrite the mixer state at audio initialization.)
|
||||
Return ``yes`` if the audio mixer is active, ``no`` otherwise.
|
||||
|
||||
While the behavior with ``mixer-active==yes`` is relatively well-defined,
|
||||
the ``no`` case will provide possibly wrong or insignificant values.
|
||||
|
||||
Note that an active mixer does not necessarily imply active audio output,
|
||||
although this is implied in the current implementation.
|
||||
This option is relatively useless. Before mpv 0.18.1, it could be used to
|
||||
infer behavior of the ``volume`` property.
|
||||
|
||||
``volume`` (RW)
|
||||
Current volume (see ``--volume`` for details). Also see ``mixer-active``
|
||||
property.
|
||||
Current volume (see ``--volume`` for details).
|
||||
|
||||
``volume-max``
|
||||
Current maximum value the volume property can be set to. (This may depend
|
||||
on the ``--softvol-max`` option.)
|
||||
``volume-max`` (RW)
|
||||
Current maximum value the volume property can be set to. (Equivalent to the
|
||||
``--volume-max`` option.)
|
||||
|
||||
``mute`` (RW)
|
||||
Current mute status (``yes``/``no``). Also see ``mixer-active`` property.
|
||||
Current mute status (``yes``/``no``).
|
||||
|
||||
``ao-volume`` (RW)
|
||||
System volume. This property is available only if mpv audio output is
|
||||
currently active, and only if the underlying implementation supports volume
|
||||
control. What this option does depends on the API. For example, on ALSA
|
||||
this usually changes system-wide audio, while with PulseAudio this controls
|
||||
per-application volume.
|
||||
|
||||
``ao-mute`` (RW)
|
||||
Similar to ``ao-volume``, but controls the mute state. May be unimplemented
|
||||
even if ``ao-volume`` works.
|
||||
|
||||
``audio-delay`` (RW)
|
||||
See ``--audio-delay``.
|
||||
@@ -1349,6 +1343,9 @@ Property list
|
||||
``colormatrix-primaries`` (R)
|
||||
See ``colormatrix``.
|
||||
|
||||
``taskbar-progress`` (RW)
|
||||
See ``--taskbar-progress``.
|
||||
|
||||
``ontop`` (RW)
|
||||
See ``--ontop``.
|
||||
|
||||
@@ -1385,14 +1382,39 @@ Property list
|
||||
properties to see whether this was successful.
|
||||
|
||||
Unlike in mpv 0.9.x and before, this does not return the currently active
|
||||
hardware decoder.
|
||||
hardware decoder. Since mpv 0.18.0, ``hwdec-current`` is available for
|
||||
this purpose.
|
||||
|
||||
``hwdec-current``
|
||||
Return the current hardware decoding in use. If decoding is active, return
|
||||
one of the values used by the ``hwdec`` option/property. ``no`` indicates
|
||||
software decoding. If no decoder is loaded, the property is unavailable.
|
||||
|
||||
``hwdec-interop``
|
||||
This returns the currently loaded hardware decoding/output interop driver.
|
||||
This is known only once the VO has opened (and possibly later). With some
|
||||
VOs (like ``opengl``), this might be never known in advance, but only when
|
||||
the decoder attempted to create the hw decoder successfully. (Using
|
||||
``--hwdec-preload`` can load it eagerly.) If there are multiple drivers
|
||||
loaded, they will be separated by ``,``.
|
||||
|
||||
If no VO is active or no interop driver is known, this property is
|
||||
unavailable.
|
||||
|
||||
This does not necessarily use the same values as ``hwdec``. There can be
|
||||
multiple interop drivers for the same hardware decoder, depending on
|
||||
platform and VO.
|
||||
|
||||
``hwdec-active``
|
||||
Deprecated. To be removed in mpv 0.20.0. Use ``hwdec-current`` instead.
|
||||
|
||||
Return ``yes`` or ``no``, depending on whether any type of hardware decoding
|
||||
is actually in use.
|
||||
|
||||
``hwdec-detected``
|
||||
If software decoding is active, this returns the hardware decoder in use.
|
||||
Deprecated. To be removed in mpv 0.20.0.
|
||||
|
||||
If hardware decoding is active, this returns the hardware decoder in use.
|
||||
Otherwise, it returns either ``no``, or if applicable, the currently loaded
|
||||
hardware decoding API. This is known only once the VO has opened (and
|
||||
possibly later). With some VOs (like ``opengl``), this is never known in
|
||||
@@ -1646,6 +1668,9 @@ Property list
|
||||
Current position on playlist. The first entry is on position 0. Writing
|
||||
to the property will restart playback at the written entry.
|
||||
|
||||
``playlist-pos-1`` (RW)
|
||||
Same as ``playlist-pos``, but 1-based.
|
||||
|
||||
``playlist-count``
|
||||
Number of total playlist entries.
|
||||
|
||||
@@ -1787,9 +1812,11 @@ Property list
|
||||
"albumart" MPV_FORMAT_FLAG
|
||||
"default" MPV_FORMAT_FLAG
|
||||
"forced" MPV_FORMAT_FLAG
|
||||
"selected" MPV_FORMAT_FLAG
|
||||
"external" MPV_FORMAT_FLAG
|
||||
"external-filename" MPV_FORMAT_STRING
|
||||
"codec" MPV_FORMAT_STRING
|
||||
"ff-index" MPV_FORMAT_INT64
|
||||
"decoder-desc" MPV_FORMAT_STRING
|
||||
"demux-w" MPV_FORMAT_INT64
|
||||
"demux-h" MPV_FORMAT_INT64
|
||||
@@ -1904,6 +1931,44 @@ Property list
|
||||
whether the video window is visible. If the ``--force-window`` option is
|
||||
used, this is usually always returns ``yes``.
|
||||
|
||||
``vo-performance``
|
||||
Some video output performance metrics. Not implemented by all VOs. This has
|
||||
a number of sup-properties, of the form ``vo-performance/<metric>-<value>``,
|
||||
all of them in milliseconds.
|
||||
|
||||
``<metric>`` refers to one of:
|
||||
|
||||
``upload``
|
||||
Time needed to make the frame available to the GPU (if necessary).
|
||||
``render``
|
||||
Time needed to perform all necessary video postprocessing and rendering
|
||||
passes (if necessary).
|
||||
``present``
|
||||
Time needed to present a rendered frame on-screen.
|
||||
|
||||
When a step is unnecessary or skipped, it will have the value 0.
|
||||
|
||||
``<value>`` refers to one of:
|
||||
|
||||
``last``
|
||||
Last measured value.
|
||||
``avg``
|
||||
Average over a fixed number of past samples. (The exact timeframe
|
||||
varies, but it should generally be a handful of seconds)
|
||||
``peak``
|
||||
The peak (highest value) within this averaging range.
|
||||
|
||||
When querying the property with the client API using ``MPV_FORMAT_NODE``,
|
||||
or with Lua ``mp.get_property_native``, this will return a mpv_node with
|
||||
the following contents:
|
||||
|
||||
::
|
||||
|
||||
MPV_FORMAT_NODE_MAP
|
||||
"<metric>-<value>" MPV_FORMAT_INT64
|
||||
|
||||
(One entry for each ``<metric>`` and ``<value>`` combination)
|
||||
|
||||
``video-bitrate``, ``audio-bitrate``, ``sub-bitrate``
|
||||
Bitrate values calculated on the packet level. This works by dividing the
|
||||
bit size of all packets between two keyframes by their presentation
|
||||
@@ -2063,7 +2128,7 @@ Property list
|
||||
|
||||
(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
|
||||
end of playback, can not be read or written until end of playback.)
|
||||
end of playback, cannot be read or written until end of playback.)
|
||||
|
||||
``option-info/<name>``
|
||||
Additional per-option information.
|
||||
|
||||
+8
-4
@@ -175,7 +175,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
Similar to ``mp.set_property``, but set the given property using its native
|
||||
type.
|
||||
|
||||
Since there are several data types which can not represented natively in
|
||||
Since there are several data types which cannot represented natively in
|
||||
Lua, this might not always work as expected. For example, while the Lua
|
||||
wrapper can do some guesswork to decide whether a Lua table is an array
|
||||
or a map, this would fail with empty tables. Also, there are not many
|
||||
@@ -350,6 +350,10 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
with ``add_timeout()``), this starts the timer from the beginning,
|
||||
using the initially configured timeout.
|
||||
|
||||
``is_enabled()``
|
||||
Whether the timer is currently enabled or was previously disabled
|
||||
(e.g. by ``stop()`` or ``kill()``).
|
||||
|
||||
``timeout`` (RW)
|
||||
This field contains the current timeout period. This value is not
|
||||
updated as time progresses. It's only used to calculate when the
|
||||
@@ -454,9 +458,9 @@ are useful only in special situations.
|
||||
The level is a string, see ``msg.log`` for allowed log levels.
|
||||
|
||||
``mp.register_script_message(name, fn)``
|
||||
This is a helper to dispatch ``script_message`` or ``script_message_to``
|
||||
invocations to Lua functions. ``fn`` is called if ``script_message`` or
|
||||
``script_message_to`` (with this script as destination) is run
|
||||
This is a helper to dispatch ``script-message`` or ``script-message-to``
|
||||
invocations to Lua functions. ``fn`` is called if ``script-message`` or
|
||||
``script-message-to`` (with this script as destination) is run
|
||||
with ``name`` as first parameter. The other parameters are passed to ``fn``.
|
||||
If a message with the given name is already registered, it's overwritten.
|
||||
|
||||
|
||||
+15
-1
@@ -320,11 +320,22 @@ example, paths starting with ``-`` will be interpreted as options. Likewise,
|
||||
if a path contains the sequence ``://``, the string before that might be
|
||||
interpreted as protocol prefix, even though ``://`` can be part of a legal
|
||||
UNIX path. To avoid problems with arbitrary paths, you should be sure that
|
||||
absolute paths passed to mpv start with ``/``, and relative paths with ``./``.
|
||||
absolute paths passed to mpv start with ``/``, and prefix relative paths with
|
||||
``./``.
|
||||
|
||||
Using the ``file://`` pseudo-protocol is discouraged, because it involves
|
||||
strange URL unescaping rules.
|
||||
|
||||
The name ``-`` itself is interpreted as stdin, and will cause mpv to disable
|
||||
console controls. (Which makes it suitable for playing data piped to stdin.)
|
||||
|
||||
The special argument ``--`` can be used to stop mpv from interpreting the
|
||||
following arguments as options.
|
||||
|
||||
When using the client API, you should strictly avoid using ``mpv_command_string``
|
||||
for invoking the ``loadfile`` command, and instead prefer e.g. ``mpv_command``
|
||||
to avoid the need for filename escaping.
|
||||
|
||||
For paths passed to suboptions, the situation is further complicated by the
|
||||
need to escape special characters. To work this around, the path can be
|
||||
additionally wrapped in the fixed-length syntax, e.g. ``%n%string_of_length_n``
|
||||
@@ -715,6 +726,9 @@ PROTOCOLS
|
||||
``memory://data``
|
||||
Use the ``data`` part as source data.
|
||||
|
||||
``hex://data``
|
||||
Like ``memory://``, but the string is interpreted as hexdump.
|
||||
|
||||
PSEUDO GUI MODE
|
||||
===============
|
||||
|
||||
|
||||
+125
-41
@@ -261,8 +261,13 @@ Playback Control
|
||||
``--ab-loop-a=<time>``, ``--ab-loop-b=<time>``
|
||||
Set loop points. If playback passes the ``b`` timestamp, it will seek to
|
||||
the ``a`` timestamp. Seeking past the ``b`` point doesn't loop (this is
|
||||
intentional). The loop-points can be adjusted at runtime with the
|
||||
corresponding properties. See also ``ab_loop`` command.
|
||||
intentional).
|
||||
|
||||
If both options are set to ``no``, looping is disabled. Otherwise, the
|
||||
start/end of the file is used if one of the options is set to ``no``.
|
||||
|
||||
The loop-points can be adjusted at runtime with the corresponding
|
||||
properties. See also ``ab-loop`` command.
|
||||
|
||||
``--ordered-chapters``, ``--no-ordered-chapters``
|
||||
Enabled by default.
|
||||
@@ -576,6 +581,7 @@ Video
|
||||
|
||||
:no: always use software decoding (default)
|
||||
:auto: see below
|
||||
:auto-copy: see below
|
||||
:vdpau: requires ``--vo=vdpau`` or ``--vo=opengl`` (Linux only)
|
||||
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux only)
|
||||
:vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
|
||||
@@ -583,7 +589,9 @@ Video
|
||||
:dxva2: requires ``--vo=opengl:backend=angle`` or
|
||||
``--vo=opengl:backend=dxinterop`` (Windows only)
|
||||
:dxva2-copy: copies video back to system RAM (Windows only)
|
||||
:d3d11va-copy: experimental (Windows only)
|
||||
:d3d11va: requires ``--vo=opengl:backend=angle`` (Windows only)
|
||||
:d3d11va-copy: copies video back to system RAM (Windows only)
|
||||
:mediacodec: copies video back to system RAM (Android only)
|
||||
:rpi: requires ``--vo=rpi`` (Raspberry Pi only - default if available)
|
||||
|
||||
``auto`` tries to automatically enable hardware decoding using the first
|
||||
@@ -593,6 +601,13 @@ Video
|
||||
work, it will always fall back to software decoding, instead of trying the
|
||||
next method (might matter on some Linux systems).
|
||||
|
||||
``auto-copy`` selects only modes that copy the video data back to system
|
||||
memory after decoding. Currently, this selects only one of the following
|
||||
modes: ``vaapi-copy``, ``dxva2-copy``, ``d3d11va-copy``, ``mediacodec``.
|
||||
If none of these work, hardware decoding is disabled. This mode is always
|
||||
guaranteed to incur no additional loss compared to software decoding, and
|
||||
will allow CPU processing with video filters.
|
||||
|
||||
The ``vaapi`` mode, if used with ``--vo=opengl``, requires Mesa 11 and most
|
||||
likely works with Intel GPUs only. It also requires the opengl EGL backend
|
||||
(automatically used if available). You can also try the old GLX backend by
|
||||
@@ -614,6 +629,46 @@ Video
|
||||
codecs. See ``--hwdec-codecs`` to enable hardware decoding for more
|
||||
codecs.
|
||||
|
||||
.. admonition:: Quality reduction with hardware decoding
|
||||
|
||||
Normally, hardware decoding does not reduce video quality (at least for
|
||||
the codecs h264 and HEVC). However, due to restrictions in video output
|
||||
APIs, there can be some loss, or blatantly incorrect results.
|
||||
|
||||
In some cases, RGB conversion is forced, which means the RGB conversion
|
||||
is performed by the hardware decoding API, instead of the OpenGL code
|
||||
used by ``--vo=opengl``. This means certain obscure colorspaces may
|
||||
not display correctly, not certain filtering (such as debanding)
|
||||
cannot be applied in an ideal way.
|
||||
|
||||
``vdpau`` is usually safe. If deinterlacing enabled (or the ``vdpaupp``
|
||||
video filter is active in general), it forces RGB conversion. The latter
|
||||
currently does not treat certain colorspaces like BT.2020 correctly
|
||||
(which is mostly a mpv-specific restriction). The ``vdpauprb`` video
|
||||
filter retrieves image data without RGB conversion and is safe (but
|
||||
precludes use of vdpau postprocessing).
|
||||
|
||||
``vaapi`` is safe if the ``vaapi-egl`` backend is indicated in the logs.
|
||||
If ``vaapi-glx`` is indicated, and the video colorspace is either BT.601
|
||||
or BT.709, a forced but correct RGB conversion is performed. Otherwise,
|
||||
the result will be incorrect.
|
||||
|
||||
``d3d11va`` is usually safe (if used with ANGLE builds that support
|
||||
``EGL_KHR_stream path`` - otherwise, it converts to RGB), except that
|
||||
10 bit input (HEVC main 10 profiles) will be rounded down to 8 bits.
|
||||
|
||||
``dxva2`` is not safe. It appears to always use BT.601 for forced RGB
|
||||
conversion, but actual behavior depends on the GPU drivers. Some drivers
|
||||
appear to convert to limited range RGB, which gives a faded appearance.
|
||||
In addition to driver-specific behavior, global system settings might
|
||||
affect this additionally. This can give incorrect results even with
|
||||
completely ordinary video sources.
|
||||
|
||||
All other methods, in particular the copy-back methods (like
|
||||
``dxva2-copy`` etc.) are either fully safe, or not worse than software
|
||||
decoding. In particular, ``auto-copy`` will only select safe modes
|
||||
(although potentially slower than other methods).
|
||||
|
||||
``--hwdec-preload=<api>``
|
||||
This is useful for the ``opengl`` and ``opengl-cb`` VOs for creating the
|
||||
hardware decoding OpenGL interop context, but without actually enabling
|
||||
@@ -1017,14 +1072,10 @@ Audio
|
||||
|
||||
``--volume=<value>``
|
||||
Set the startup volume. 0 means silence, 100 means no volume reduction or
|
||||
amplification. A value of -1 (the default) will not change the volume. See
|
||||
also ``--softvol``.
|
||||
amplification. Negative values can be passed for compatibility, but are
|
||||
treated as 0.
|
||||
|
||||
.. note::
|
||||
|
||||
This was changed after the mpv 0.9 release. Before that, 100 actually
|
||||
meant maximum volume. At the same time, the volume scale was made cubic,
|
||||
so the old values won't match up with the new ones anyway.
|
||||
Since mpv 0.18.1, this always controls the internal mixer (aka "softvol").
|
||||
|
||||
``--audio-delay=<sec>``
|
||||
Audio delay in seconds (positive or negative float value). Positive values
|
||||
@@ -1039,20 +1090,17 @@ Audio
|
||||
|
||||
See also: ``--volume``.
|
||||
|
||||
``--softvol=<mode>``
|
||||
Control whether to use the volume controls of the audio output driver or
|
||||
the internal mpv volume filter.
|
||||
``--softvol=<no|yes|auto>``
|
||||
Deprecated/unfunctional. Before mpv 0.18.1, this used to control whether
|
||||
to use the volume controls of the audio output driver or the internal mpv
|
||||
volume filter.
|
||||
|
||||
:no: prefer audio driver controls, use the volume filter only if
|
||||
absolutely needed
|
||||
:yes: always use the volume filter
|
||||
:auto: prefer the volume filter if the audio driver uses the system mixer
|
||||
(default)
|
||||
The current behavior is as if this option was set to ``yes``. The other
|
||||
behaviors are not available anymore, although ``auto`` almost matches
|
||||
current behavior in most cases.
|
||||
|
||||
The intention of ``auto`` is to avoid changing system mixer settings from
|
||||
within mpv with default settings. mpv is a video player, not a mixer panel.
|
||||
On the other hand, mixer controls are enabled for sound servers like
|
||||
PulseAudio, which provide per-application volume.
|
||||
The ``no`` behavior is still partially available through the ``ao-volume``
|
||||
and ``ao-mute`` properties. But there are no options to reset these.
|
||||
|
||||
``--audio-demuxer=<[+]name>``
|
||||
Use this audio demuxer type when using ``--audio-file``. Use a '+' before
|
||||
@@ -1209,10 +1257,12 @@ Audio
|
||||
their start timestamps differ, and then video timing is gradually adjusted
|
||||
if necessary to reach correct synchronization later.
|
||||
|
||||
``--softvol-max=<100.0-1000.0>``
|
||||
``--volume-max=<100.0-1000.0>``, ``--softvol-max=<...>``
|
||||
Set the maximum amplification level in percent (default: 130). A value of
|
||||
130 will allow you to adjust the volume up to about double the normal level.
|
||||
|
||||
``--softvol-max`` is a deprecated alias and should not be used.
|
||||
|
||||
``--audio-file-auto=<no|exact|fuzzy|all>``, ``--no-audio-file-auto``
|
||||
Load additional audio files matching the video filename. The parameter
|
||||
specifies how external audio files are matched. ``exact`` is enabled by
|
||||
@@ -1221,7 +1271,7 @@ Audio
|
||||
:no: Don't automatically load external audio files.
|
||||
:exact: Load the media filename with audio file extension (default).
|
||||
:fuzzy: Load all audio files containing media filename.
|
||||
:all: Load all aufio files in the current and ``--audio-file-paths``
|
||||
:all: Load all audio files in the current and ``--audio-file-paths``
|
||||
directories.
|
||||
|
||||
``--audio-file-paths=<path1:path2:...>``
|
||||
@@ -1265,7 +1315,7 @@ Subtitles
|
||||
.. note::
|
||||
|
||||
Changing styling and position does not work with all subtitles. Image-based
|
||||
subtitles (DVD, Bluray/PGS, DVB) can not changed for fundamental reasons.
|
||||
subtitles (DVD, Bluray/PGS, DVB) cannot changed for fundamental reasons.
|
||||
Subtitles in ASS format are normally not changed intentionally, but
|
||||
overriding them can be controlled with ``--ass-style-override``.
|
||||
|
||||
@@ -1423,7 +1473,7 @@ Subtitles
|
||||
|
||||
Using this option may lead to incorrect subtitle rendering.
|
||||
|
||||
``--ass-style-override=<yes|no|force>``
|
||||
``--ass-style-override=<yes|no|force|signfs|strip>``
|
||||
Control whether user style overrides should be applied.
|
||||
|
||||
:yes: Apply all the ``--ass-*`` style override options. Changing the default
|
||||
@@ -1433,6 +1483,8 @@ Subtitles
|
||||
:no: Render subtitles as forced by subtitle scripts.
|
||||
:force: Try to force the font style as defined by the ``--sub-text-*``
|
||||
options. Can break rendering easily.
|
||||
:strip: Radically strip all ASS tags and styles from the subtitle. This
|
||||
is equivalent to the old ``--no-ass`` / ``--no-sub-ass`` options.
|
||||
|
||||
``--ass-force-margins``
|
||||
Enables placing toptitles and subtitles in black borders when they are
|
||||
@@ -1529,6 +1581,13 @@ Subtitles
|
||||
``--sub-ass``, ``--no-sub-ass``
|
||||
Render ASS subtitles natively (enabled by default).
|
||||
|
||||
.. note::
|
||||
|
||||
This has been deprecated by ``--ass-style-override=strip``. You also
|
||||
may need ``--embeddedfonts=no`` to get the same behavior. Also,
|
||||
using ``--ass-style-override=force`` should give better results
|
||||
without breaking subtitles too much.
|
||||
|
||||
If ``--no-sub-ass`` is specified, all tags and style declarations are
|
||||
stripped and ignored on display. The subtitle renderer uses the font style
|
||||
as specified by the ``--sub-text-`` options instead.
|
||||
@@ -1539,10 +1598,6 @@ Subtitles
|
||||
rendering of ASS/SSA subtitles. It can sometimes be useful to forcibly
|
||||
override the styling of ASS subtitles, but should be avoided in general.
|
||||
|
||||
.. note::
|
||||
|
||||
Try using ``--ass-style-override=force`` instead.
|
||||
|
||||
``--sub-auto=<no|exact|fuzzy|all>``, ``--no-sub-auto``
|
||||
Load additional subtitle files matching the video filename. The parameter
|
||||
specifies how external subtitle files are matched. ``exact`` is enabled by
|
||||
@@ -1783,6 +1838,12 @@ Window
|
||||
mode can be used to create the window always on program start, but this
|
||||
may cause other issues.
|
||||
|
||||
``--taskbar-progress``, ``--no-taskbar-progress``
|
||||
(Windows only)
|
||||
Enable/disable playback progress rendering in taskbar (Windows 7 and above).
|
||||
|
||||
Enabled by default.
|
||||
|
||||
``--ontop``
|
||||
Makes the player window stay on top of other windows.
|
||||
|
||||
@@ -1794,6 +1855,12 @@ Window
|
||||
Play video with window border and decorations. Since this is on by
|
||||
default, use ``--no-border`` to disable the standard window decorations.
|
||||
|
||||
``--fit-border``, ``--no-fit-border``
|
||||
(Windows only) Fit the whole window with border and decorations on the
|
||||
screen. Since this is on by default, use ``--no-fit-border`` to make mpv
|
||||
try to only fit client area with video on the screen. This behavior only
|
||||
applied to window/video with size exceeding size of the screen.
|
||||
|
||||
``--on-all-workspaces``
|
||||
(X11 only)
|
||||
Show the video window on all virtual desktops.
|
||||
@@ -1885,7 +1952,7 @@ Window
|
||||
Make the window width 70% of the screen size, keeping aspect ratio.
|
||||
``1000``
|
||||
Set the window width to 1000 pixels, keeping aspect ratio.
|
||||
``70%:60%``
|
||||
``70%x60%``
|
||||
Make the window as large as possible, without being wider than 70%
|
||||
of the screen width, or higher than 60% of the screen height.
|
||||
|
||||
@@ -1947,6 +2014,14 @@ Window
|
||||
be the default behavior. Currently only affects X11 VOs.
|
||||
|
||||
``--heartbeat-cmd=<command>``
|
||||
|
||||
.. warning::
|
||||
|
||||
This option is redundant with Lua scripting. Further, it shouldn't be
|
||||
needed for disabling screensaver anyway, since mpv will call
|
||||
``xdg-screensaver`` when using X11 backend. As a consequence this
|
||||
option has been deprecated with no direct replacement.
|
||||
|
||||
Command that is executed every 30 seconds during playback via *system()* -
|
||||
i.e. using the shell. The time between the commands can be customized with
|
||||
the ``--heartbeat-interval`` option. The command is not run while playback
|
||||
@@ -1972,7 +2047,7 @@ Window
|
||||
|
||||
.. admonition:: Example for GNOME screensaver
|
||||
|
||||
``mpv --heartbeat-cmd="gnome-screensaver-command -p" file``
|
||||
``mpv --heartbeat-cmd="gnome-screensaver-command --deactivate" file``
|
||||
|
||||
|
||||
``--heartbeat-interval=<sec>``
|
||||
@@ -2068,9 +2143,17 @@ Window
|
||||
|
||||
This option might be removed in the future.
|
||||
|
||||
``--x11-bypass-compositor=<yes|no>``
|
||||
``--x11-bypass-compositor=<yes|no|fs-only|never>``
|
||||
If set to ``yes``, then ask the compositor to unredirect the mpv window
|
||||
(default: no). This uses the ``_NET_WM_BYPASS_COMPOSITOR`` hint.
|
||||
(default: ``fs-only``). This uses the ``_NET_WM_BYPASS_COMPOSITOR`` hint.
|
||||
|
||||
``fs-only`` asks the window manager to disable the compositor only in
|
||||
fullscreen mode.
|
||||
|
||||
``no`` sets ``_NET_WM_BYPASS_COMPOSITOR`` to 0, which is the default value
|
||||
as declared by the EWMH specification, i.e. no change is done.
|
||||
|
||||
``never`` asks the window manager to never disable the compositor.
|
||||
|
||||
|
||||
Disc Devices
|
||||
@@ -2358,7 +2441,7 @@ Demuxer
|
||||
stop reading additional packets as soon as one of the limits is reached.
|
||||
(The limits still can be slightly overstepped due to technical reasons.)
|
||||
|
||||
Set these limits highher if you get a packet queue overflow warning, and
|
||||
Set these limits higher if you get a packet queue overflow warning, and
|
||||
you think normal playback would be possible with a larger packet queue.
|
||||
|
||||
See ``--list-options`` for defaults and value range.
|
||||
@@ -2383,7 +2466,7 @@ Demuxer
|
||||
|
||||
``--force-seekable=<yes|no>``
|
||||
If the player thinks that the media is not seekable (e.g. playing from a
|
||||
pipe, or it's a http stream with a server that doesn't support range
|
||||
pipe, or it's an http stream with a server that doesn't support range
|
||||
requests), seeking will be disabled. This option can forcibly enable it.
|
||||
For seeks within the cache, there's a good chance of success.
|
||||
|
||||
@@ -3210,7 +3293,7 @@ Cache
|
||||
With ``auto``, the cache will usually be enabled for network streams,
|
||||
using the size set by ``--cache-default``. With ``yes``, the cache will
|
||||
always be enabled with the size set by ``--cache-default`` (unless the
|
||||
stream can not be cached, or ``--cache-default`` disables caching).
|
||||
stream cannot be cached, or ``--cache-default`` disables caching).
|
||||
|
||||
May be useful when playing files from slow media, but can also have
|
||||
negative effects, especially with file formats that require a lot of
|
||||
@@ -3524,7 +3607,7 @@ Miscellaneous
|
||||
frame dropping due to the audio "overshooting" and skipping multiple video
|
||||
frames before the sync logic can react.
|
||||
|
||||
``--video-sync-adrop-size=<value``
|
||||
``--video-sync-adrop-size=<value>``
|
||||
For the ``--video-sync=display-adrop`` mode. This mode duplicates/drops
|
||||
audio data to keep audio in sync with video. To avoid audio artifacts on
|
||||
jitter (which would add/remove samples all the time), this is done in
|
||||
@@ -3597,7 +3680,7 @@ Miscellaneous
|
||||
- A label of the form ``aidN`` selects audio track N as input (e.g.
|
||||
``aid1``).
|
||||
- A label of the form ``vidN`` selects video track N as input.
|
||||
- A label named ``ao`` will be connected to the audio input.
|
||||
- A label named ``ao`` will be connected to the audio output.
|
||||
- A label named ``vo`` will be connected to the video output.
|
||||
|
||||
Each label can be used only once. If you want to use e.g. an audio stream
|
||||
@@ -3605,7 +3688,7 @@ Miscellaneous
|
||||
video or audio outputs are not possible, but you can use filters to merge
|
||||
them into one.
|
||||
|
||||
The complex filter can not be changed yet during playback. It's also not
|
||||
The complex filter cannot be changed yet during playback. It's also not
|
||||
possible to change the tracks connected to the filter at runtime. Other
|
||||
tracks, as long as they're not connected to the filter, and the
|
||||
corresponding output is not connected to the filter, can still be freely
|
||||
@@ -3634,4 +3717,5 @@ Miscellaneous
|
||||
- ``null:// --lavfi-complex='life [vo]'``
|
||||
Conways' Life Game.
|
||||
|
||||
See the FFmpeg libavfilter documentation for details on the filter.
|
||||
See the FFmpeg libavfilter documentation for details on the available
|
||||
filters.
|
||||
+2
-2
@@ -266,6 +266,6 @@ Example
|
||||
You could put this into ``input.conf`` to hide the OSC with the ``a`` key and
|
||||
to set auto mode (the default) with ``b``::
|
||||
|
||||
a script_message osc-visibility never
|
||||
b script_message osc-visibility auto
|
||||
a script-message osc-visibility never
|
||||
b script-message osc-visibility auto
|
||||
|
||||
+24
-2
@@ -214,7 +214,7 @@ Available filters are:
|
||||
Format name, e.g. rgb15, bgr24, 420p, etc. (default: don't change).
|
||||
``<outfmt>``
|
||||
Format name that should be substituted for the output. If they do not
|
||||
have the same bytes per pixel and chroma subsamplimg, it will fail.
|
||||
have the same bytes per pixel and chroma subsampling, it will fail.
|
||||
``<colormatrix>``
|
||||
Controls the YUV to RGB color space conversion when playing video. There
|
||||
are various standards. Normally, BT.601 should be used for SD video, and
|
||||
@@ -288,6 +288,8 @@ Available filters are:
|
||||
:adobe: Adobe RGB (1998)
|
||||
:prophoto: ProPhoto RGB (ROMM)
|
||||
:cie1931: CIE 1931 RGB
|
||||
:dci-p3: DCI-P3 (Digital Cinema)
|
||||
:v-gamut: Panasonic V-Gamut primaries
|
||||
|
||||
``<gamma>``
|
||||
Gamma function the source file was encoded with. Normally this should be set
|
||||
@@ -303,13 +305,24 @@ Available filters are:
|
||||
Available gamma functions are:
|
||||
|
||||
:auto: automatic selection (default)
|
||||
:bt.1886: ITU-R BT.1886 (approximation of BT.601/BT.709/BT.2020 curve)
|
||||
:bt.1886: ITU-R BT.1886 (EOTF corresponding to BT.601/BT.709/BT.2020)
|
||||
:srgb: IEC 61966-2-4 (sRGB)
|
||||
:linear: Linear light
|
||||
:gamma1.8: Pure power curve (gamma 1.8)
|
||||
:gamma2.2: Pure power curve (gamma 2.2)
|
||||
:gamma2.8: Pure power curve (gamma 2.8)
|
||||
:prophoto: ProPhoto RGB (ROMM) curve
|
||||
:st2084: SMPTE ST2084 (HDR) curve
|
||||
:std-b67: ARIB STD-B67 (Hybrid Log-gamma) curve
|
||||
:v-log: Panasonic V-Log transfer curve
|
||||
|
||||
``<peak>``
|
||||
Reference peak illumination for the video file. This is mostly
|
||||
interesting for HDR, but it can also be used tone map SDR content
|
||||
to a darker or brighter exposure.
|
||||
|
||||
The default of 0.0 will default to the display's reference brightness
|
||||
for SDR and the source's reference brightness for HDR.
|
||||
|
||||
``<stereo-in>``
|
||||
Set the stereo mode the video is assumed to be encoded in. Takes the
|
||||
@@ -800,6 +813,15 @@ Available filters are:
|
||||
This filter must be specified before ``vdpaupp`` in the filter chain if
|
||||
``vdpaupp`` is used.
|
||||
|
||||
``d3d11vpp``
|
||||
Direct3D 11 video post processing. Currently requires D3D11 hardware
|
||||
decoding for use.
|
||||
|
||||
``deint=<yes|no>``
|
||||
Whether deinterlacing is enabled (default: no).
|
||||
``interlaced-only=<yes|no>``
|
||||
If ``yes`` (default), only deinterlace frames marked as interlaced.
|
||||
|
||||
``buffer=<num>``
|
||||
Buffer ``<num>`` frames in the filter chain. This filter is probably pretty
|
||||
useless, except for debugging. (Note that this won't help to smooth out
|
||||
|
||||
+227
-72
@@ -465,7 +465,7 @@ Available video output drivers are:
|
||||
8
|
||||
Dither to 8 bit output.
|
||||
|
||||
Note that the depth of the connected video display device can not be
|
||||
Note that the depth of the connected video display device cannot be
|
||||
detected. Often, LCD panels will do dithering on their own, which
|
||||
conflicts with ``opengl``'s dithering and leads to ugly output.
|
||||
|
||||
@@ -582,68 +582,6 @@ Available video output drivers are:
|
||||
better than without it) since it will extend the size to match only the
|
||||
milder of the scale factors between the axes.
|
||||
|
||||
``prescale-luma=<filter>``
|
||||
Apply additional pre-scaling (image doubling) on the luma plane
|
||||
(if present). As the name implies, these will run before the main
|
||||
upscaling pass.
|
||||
|
||||
``none``
|
||||
Disable all prescalers. This is the default.
|
||||
|
||||
``superxbr``
|
||||
A relatively fast prescaler originally developed for pixel art.
|
||||
|
||||
Some parameters can be tuned with ``superxbr-sharpness`` and
|
||||
``superxbr-edge-strength`` options.
|
||||
|
||||
``nnedi3``
|
||||
An artificial neural network based deinterlacer, which can be used
|
||||
to upscale images.
|
||||
|
||||
Extremely slow and requires a recent mid or high end graphics card
|
||||
to work smoothly (as of 2015).
|
||||
|
||||
``prescale-passes=<1..5>``
|
||||
The number of passes to apply the prescaler (defaults to be 1). Setting
|
||||
it to 2 will perform a 4x upscaling.
|
||||
|
||||
``prescale-downscaling-threshold=<0..32>``
|
||||
This option prevents "overkill" use of prescalers, which can be caused
|
||||
by misconfiguration, or user trying to play a video with much larger
|
||||
size. With this option, user can specify the maximal allowed downscaling
|
||||
ratio in both dimension. To satisfy it, the number of passes for
|
||||
prescaler will be reduced, and if necessary prescaler could also be
|
||||
disabled.
|
||||
|
||||
The default value is 2.0, and should be able to prevent most seemingly
|
||||
unreasonable use of prescalers. Most user would probably want to set it
|
||||
to a smaller value between 1.0 and 1.5 for better performance.
|
||||
|
||||
A value less than 1.0 will disable the check.
|
||||
|
||||
``nnedi3-neurons=<16|32|64|128>``
|
||||
Specify the neurons for nnedi3 prescaling (defaults to be 32). The
|
||||
rendering time is expected to be linear to the number of neurons.
|
||||
|
||||
``nnedi3-window=<8x4|8x6>``
|
||||
Specify the size of local window for sampling in nnedi3 prescaling
|
||||
(defaults to be ``8x4``). The ``8x6`` window produces sharper images,
|
||||
but is also slower.
|
||||
|
||||
``nnedi3-upload=<ubo|shader>``
|
||||
Specify how to upload the NN weights to GPU. Depending on the graphics
|
||||
card, driver, shader compiler and nnedi3 settings, both method can be
|
||||
faster or slower.
|
||||
|
||||
``ubo``
|
||||
Upload these weights via uniform buffer objects. This is the
|
||||
default. (requires OpenGL 3.1 / GLES 3.0)
|
||||
|
||||
``shader``
|
||||
Hard code all the weights into the shader source code. (requires
|
||||
OpenGL 3.3 / GLES 3.0)
|
||||
|
||||
|
||||
``pre-shaders=<files>``, ``post-shaders=<files>``, ``scale-shader=<file>``
|
||||
Custom GLSL fragment shaders.
|
||||
|
||||
@@ -691,6 +629,9 @@ Available video output drivers are:
|
||||
never resets (regardless of seeks).
|
||||
vec2 image_size
|
||||
The size in pixels of the input image.
|
||||
vec2 target_size
|
||||
The size in pixels of the visible part of the scaled (and possibly
|
||||
cropped) image.
|
||||
|
||||
For example, a shader that inverts the colors could look like this::
|
||||
|
||||
@@ -700,6 +641,152 @@ Available video output drivers are:
|
||||
return vec4(1.0 - color.rgb, color.a);
|
||||
}
|
||||
|
||||
``user-shaders=<files>``
|
||||
Custom GLSL hooks. These are similar to ``post-shaders`` etc., but more
|
||||
flexible: They can be injected at almost arbitrary points in the
|
||||
rendering pipeline, and access all previous intermediate textures.
|
||||
|
||||
.. admonition:: Warning
|
||||
|
||||
The syntax is not stable yet and may change any time.
|
||||
|
||||
The general syntax of a user shader looks like this::
|
||||
|
||||
//!METADATA ARGS...
|
||||
//!METADATA ARGS...
|
||||
|
||||
vec4 hook() {
|
||||
...
|
||||
return something;
|
||||
}
|
||||
|
||||
//!METADATA ARGS...
|
||||
//!METADATA ARGS...
|
||||
|
||||
...
|
||||
|
||||
Each block of metadata, along with the non-metadata lines after it,
|
||||
defines a single pass. Each pass can set the following metadata:
|
||||
|
||||
HOOK <name> (required)
|
||||
The texture which to hook into. May occur multiple times within a
|
||||
metadata block, up to a predetermined limit. See below for a list
|
||||
of hookable textures.
|
||||
|
||||
BIND <name>
|
||||
Loads a texture and makes it available to the pass, and sets up
|
||||
macros to enable accessing it. See below for a list of set macros.
|
||||
By default, no textures are bound. The special name HOOKED can be
|
||||
used to refer to the texture that triggered this pass.
|
||||
|
||||
SAVE <name>
|
||||
Gives the name of the texture to save the result of this pass
|
||||
into. By default, this is set to the special name HOOKED which has
|
||||
the effect of overwriting the hooked texture.
|
||||
|
||||
WIDTH <szexpr>, HEIGHT <szexpr>
|
||||
Specifies the size of the resulting texture for this pass.
|
||||
``szexpr`` refers to an expression in RPN (reverse polish
|
||||
notation), using the operators + - * / > < !, floating point
|
||||
literals, and references to sizes of existing texture and OUTPUT
|
||||
(such as MAIN.width or CHROMA.height). By default, these are set to
|
||||
HOOKED.w and HOOKED.h, respectively.
|
||||
|
||||
WHEN <szexpr>
|
||||
Specifies a condition that needs to be true (non-zero) for the
|
||||
shader stage to be evaluated. If it fails, it will silently be
|
||||
omitted. (Note that a shader stage like this which has a dependency
|
||||
on an optional hook point can still cause that hook point to be
|
||||
saved, which has some minor overhead)
|
||||
|
||||
OFFSET ox oy
|
||||
Indicates a pixel shift (offset) introduced by this pass. These
|
||||
pixel offsets will be accumulated and corrected during the
|
||||
next scaling pass (``cscale`` or ``scale``). The default values
|
||||
are 0 0 which correspond to no shift. Note that offsets are ignored
|
||||
when not overwriting the hooked texture.
|
||||
|
||||
COMPONENTS n
|
||||
Specifies how many components of this pass's output are relevant
|
||||
and should be stored in the texture, up to 4 (rgba). By default,
|
||||
this value is equal to the number of components in HOOKED.
|
||||
|
||||
Each bound texture (via ``BIND``) will make available the following
|
||||
definitions to that shader pass, where NAME is the name of the bound
|
||||
texture:
|
||||
|
||||
vec4 NAME_tex(vec2 pos)
|
||||
The sampling function to use to access the texture at a certain
|
||||
spot (in texture coordinate space, range [0,1]). This takes care
|
||||
of any necessary normalization conversions.
|
||||
vec4 NAME_texOff(vec2 offset)
|
||||
Sample the texture at a certain offset in pixels. This works like
|
||||
NAME_tex but additionally takes care of necessary rotations, so
|
||||
that sampling at e.g. vec2(-1,0) is always one pixel to the left.
|
||||
vec2 NAME_pos
|
||||
The local texture coordinate of that texture, range [0,1].
|
||||
vec2 NAME_size
|
||||
The (rotated) size in pixels of the texture.
|
||||
mat2 NAME_rot
|
||||
The rotation matrix associated with this texture. (Rotates
|
||||
pixel space to texture coordinates)
|
||||
vec2 NAME_pt
|
||||
The (unrotated) size of a single pixel, range [0,1].
|
||||
sampler NAME_raw
|
||||
The raw bound texture itself. The use of this should be
|
||||
avoided unless absolutely necessary.
|
||||
|
||||
In addition, the global uniforms described in ``post-shaders`` are
|
||||
also available.
|
||||
|
||||
Internally, vo_opengl may generate any number of the following
|
||||
textures. Whenever a texture is rendered and saved by vo_opengl, all of
|
||||
the passes that have hooked into it will run, in the order they were
|
||||
added by the user. This is a list of the legal hook points:
|
||||
|
||||
RGB, LUMA, CHROMA, ALPHA, XYZ (resizable)
|
||||
Source planes (raw). Which of these fire depends on the image
|
||||
format of the source.
|
||||
|
||||
CHROMA_SCALED, ALPHA_SCALED (fixed)
|
||||
Source planes (upscaled). These only fire on subsampled content.
|
||||
|
||||
NATIVE (resizable)
|
||||
The combined image, in the source colorspace, before conversion
|
||||
to RGB.
|
||||
|
||||
MAINPRESUB (resizable)
|
||||
The image, after conversion to RGB, but before
|
||||
``blend-subtitles=video`` is applied.
|
||||
|
||||
MAIN (resizable)
|
||||
The main image, after conversion to RGB but before upscaling.
|
||||
|
||||
LINEAR (fixed)
|
||||
Linear light image, before scaling. This only fires when
|
||||
``linear-scaling`` is in effect.
|
||||
|
||||
SIGMOID (fixed)
|
||||
Sigmoidized light, before scaling. This only fires when
|
||||
``sigmoid-upscaling`` is in effect.
|
||||
|
||||
PREKERNEL (fixed)
|
||||
The image immediately before the scaler kernel runs.
|
||||
|
||||
POSTKERNEL (fixed)
|
||||
The image immediately after the scaler kernel runs.
|
||||
|
||||
SCALED (fixed)
|
||||
The final upscaled image, before color management.
|
||||
|
||||
OUTPUT (fixed)
|
||||
The final output image, after color management but before
|
||||
dithering and drawing to screen.
|
||||
|
||||
Only the textures labelled with ``resizable`` may be transformed by the
|
||||
pass. When overwriting a texture marked ``fixed``, the WIDTH, HEIGHT
|
||||
and OFFSET must be left at their default values.
|
||||
|
||||
``deband``
|
||||
Enable the debanding algorithm. This greatly reduces the amount of
|
||||
visible banding, blocking and other quantization artifacts, at the
|
||||
@@ -807,8 +894,8 @@ Available video output drivers are:
|
||||
Win32/WGL
|
||||
angle
|
||||
Direct3D11 through the OpenGL ES translation layer ANGLE. This
|
||||
supports almost everything the ``win`` backend does, except ICC
|
||||
profiles, and the ``nnedi3`` prescaler.
|
||||
supports almost everything the ``win`` backend does (if the ANGLE
|
||||
build is new enough).
|
||||
dxinterop (experimental)
|
||||
Win32, using WGL for rendering and Direct3D 9Ex for presentation.
|
||||
Works on Nvidia and AMD. Newer Intel chips with the latest drivers
|
||||
@@ -835,10 +922,11 @@ Available video output drivers are:
|
||||
``fbo-format=<fmt>``
|
||||
Selects the internal format of textures used for FBOs. The format can
|
||||
influence performance and quality of the video output.
|
||||
``fmt`` can be one of: rgb, rgba, rgb8, rgb10, rgb10_a2, rgb16, rgb16f,
|
||||
``fmt`` can be one of: rgb8, rgb10, rgb10_a2, rgb16, rgb16f,
|
||||
rgb32f, rgba12, rgba16, rgba16f, rgba32f.
|
||||
Default: ``auto``, which maps to rgba16 on desktop GL, and rgba16f or
|
||||
rgb10_a2 on GLES (e.g. ANGLE).
|
||||
rgb10_a2 on GLES (e.g. ANGLE), unless GL_EXT_texture_norm16 is
|
||||
available.
|
||||
|
||||
``gamma=<0.1..2.0>``
|
||||
Set a gamma value (default: 1.0). If gamma is adjusted in other ways
|
||||
@@ -854,6 +942,10 @@ Available video output drivers are:
|
||||
0.8
|
||||
Pitch black room
|
||||
|
||||
NOTE: Typical movie content (Blu-ray etc.) already contains a gamma
|
||||
drop of about 0.8, so specifying it here as well will result in even
|
||||
even darker image than intended!
|
||||
|
||||
``gamma-auto``
|
||||
Automatically corrects the gamma value depending on ambient lighting
|
||||
conditions (adding a gamma boost for dark rooms).
|
||||
@@ -864,8 +956,9 @@ Available video output drivers are:
|
||||
NOTE: Only implemented on OS X.
|
||||
|
||||
``target-prim=<value>``
|
||||
Specifies the primaries of the display. Video colors will be adapted
|
||||
to this colorspace if necessary. Valid values are:
|
||||
Specifies the primaries of the display. Video colors will be adapted to
|
||||
this colorspace when ICC color management is not being used. Valid
|
||||
values are:
|
||||
|
||||
auto
|
||||
Disable any adaptation (default)
|
||||
@@ -889,15 +982,18 @@ Available video output drivers are:
|
||||
CIE 1931 RGB (not to be confused with CIE XYZ)
|
||||
dci-p3
|
||||
DCI-P3 (Digital Cinema Colorspace), SMPTE RP431-2
|
||||
v-gamut
|
||||
Panasonic V-Gamut (VARICAM) primaries
|
||||
|
||||
``target-trc=<value>``
|
||||
Specifies the transfer characteristics (gamma) of the display. Video
|
||||
colors will be adjusted to this curve. Valid values are:
|
||||
colors will be adjusted to this curve when ICC color management is
|
||||
not being used. Valid values are:
|
||||
|
||||
auto
|
||||
Disable any adaptation (default)
|
||||
bt.1886
|
||||
ITU-R BT.1886 curve, without the brightness drop (approx. 1.961)
|
||||
ITU-R BT.1886 curve (assuming infinite contrast)
|
||||
srgb
|
||||
IEC 61966-2-4 (sRGB)
|
||||
linear
|
||||
@@ -910,9 +1006,61 @@ Available video output drivers are:
|
||||
Pure power curve (gamma 2.8), also used for BT.470-BG
|
||||
prophoto
|
||||
ProPhoto RGB (ROMM)
|
||||
st2084
|
||||
SMPTE ST2084 (HDR) curve, PQ OETF
|
||||
std-b67
|
||||
ARIB STD-B67 (Hybrid Log-gamma) curve, also known as BBC/NHK HDR
|
||||
v-log
|
||||
Panasonic V-Log (VARICAM) curve
|
||||
|
||||
NOTE: When using HDR output formats, mpv will encode to the specified
|
||||
curve but it will not set any HDMI flags or other signalling that
|
||||
might be required for the target device to correctly display the
|
||||
HDR signal. The user should independently guarantee this before
|
||||
using these signal formats for display.
|
||||
|
||||
``target-brightness=<1..100000>``
|
||||
Specifies the display's approximate brightness in cd/m^2. When playing
|
||||
HDR content on a SDR display (or SDR content on an HDR display), video
|
||||
colors will be tone mapped to this target brightness using the
|
||||
algorithm specified by ``hdr-tone-mapping``. The default of 250 cd/m^2
|
||||
corresponds to a typical consumer display.
|
||||
|
||||
``hdr-tone-mapping=<value>``
|
||||
Specifies the algorithm used for tone-mapping HDR images onto the
|
||||
target display. Valid values are:
|
||||
|
||||
clip
|
||||
Hard-clip any out-of-range values.
|
||||
reinhard
|
||||
Reinhard tone mapping algorithm. Very simple continuous curve.
|
||||
Preserves dynamic range and peak but uses nonlinear contrast.
|
||||
hable
|
||||
Similar to ``reinhard`` but preserves dark contrast better
|
||||
(slightly sigmoidal). Developed by John Hable for use in video
|
||||
games. (default)
|
||||
gamma
|
||||
Fits a logarithmic transfer between the tone curves.
|
||||
linear
|
||||
Linearly stretches the entire reference gamut to (a linear multiple
|
||||
of) the display.
|
||||
|
||||
``tone-mapping-param=<value>``
|
||||
Set tone mapping parameters. Ignored if the tone mapping algorithm is
|
||||
not tunable. This affects the following tone mapping algorithms:
|
||||
|
||||
reinhard
|
||||
Specifies the local contrast coefficient at the display peak.
|
||||
Defaults to 0.5, which means that in-gamut values will be about
|
||||
half as bright as when clipping.
|
||||
gamma
|
||||
Specifies the exponent of the function. Defaults to 1.8.
|
||||
linear
|
||||
Specifies the scale factor to use while stretching. Defaults to
|
||||
1.0.
|
||||
|
||||
``icc-profile=<file>``
|
||||
Load an ICC profile and use it to transform linear RGB to screen output.
|
||||
Load an ICC profile and use it to transform video RGB to screen output.
|
||||
Needs LittleCMS 2 support compiled in. This option overrides the
|
||||
``target-prim``, ``target-trc`` and ``icc-profile-auto`` options.
|
||||
|
||||
@@ -950,6 +1098,13 @@ Available video output drivers are:
|
||||
Default is 128x256x64.
|
||||
Sizes must be a power of two, and 512 at most.
|
||||
|
||||
``icc-contrast=<0-100000>``
|
||||
Specifies an upper limit on the target device's contrast ratio.
|
||||
This is detected automatically from the profile if possible, but for
|
||||
some profiles it might be missing, causing the contrast to be assumed
|
||||
as infinite. As a result, video may appear darker than intended. This
|
||||
only affects BT.1886 content. The default of 0 means no limit.
|
||||
|
||||
``blend-subtitles=<yes|video|no>``
|
||||
Blend subtitles directly onto upscaled video frames, before
|
||||
interpolation and/or color management (default: no). Enabling this
|
||||
|
||||
@@ -79,7 +79,8 @@ Video
|
||||
using most of FFmpeg's filters, which improve greatly on the old MPlayer
|
||||
filters in features, performance, and correctness.
|
||||
* More correct color reproduction (color matrix generation), including support
|
||||
for BT.2020 (Ultra HD) and linear XYZ (Digital Cinema) inputs.
|
||||
for BT.2020 (Ultra HD). linear XYZ (Digital Cinema) and SMPTE ST2084 (HDR)
|
||||
inputs.
|
||||
* Support for color managed displays, via ICC profiles.
|
||||
* High-quality image resamplers (see the ``opengl`` ``scale`` suboption).
|
||||
* Support for scaling in (sigmoidized) linear light.
|
||||
|
||||
@@ -123,7 +123,7 @@ options/options.h, options/options.c
|
||||
parser-mpcmd.c, and uses the option table in options.c.
|
||||
|
||||
input/input.c:
|
||||
This translates keyboard input comming from VOs and other sources (such
|
||||
This translates keyboard input coming from VOs and other sources (such
|
||||
as remote control devices like Apple IR or client API commands) to the
|
||||
key bindings listed in the user's (or the builtin) input.conf and turns
|
||||
them into items of type struct mp_cmd. These commands are queued, and read
|
||||
|
||||
@@ -137,6 +137,20 @@ most likely the best maintenance out of all stable releases. Older releases
|
||||
are for distros, and at best receive basic changes like fixing critical security
|
||||
issues or build fixes, and at worst are completely abandoned.
|
||||
|
||||
## FFmpeg ABI compatibility
|
||||
|
||||
mpv does not support linking against FFmpeg versions it was not built with, even
|
||||
if the linked version is supposedly ABI-compatible with the version it was
|
||||
compiled against. Expect malfunctions, crashes, and security issues if you
|
||||
do it anyway.
|
||||
|
||||
The reason for not supporting this is because it creates far too much complexity
|
||||
with little to no benefit, coupled with absurd and unusable FFmpeg API
|
||||
artifacts.
|
||||
|
||||
Newer mpv versions will refuse to start if runtime and compile time FFmpeg
|
||||
library versions mismatch.
|
||||
|
||||
## Release cycle
|
||||
|
||||
Every other month, an arbitrary git snapshot is made, and is assigned
|
||||
|
||||
+236
@@ -1,3 +1,239 @@
|
||||
Release 0.18.1
|
||||
==============
|
||||
|
||||
Note: Running mpv with different versions of the FFmpeg/Libav libraries than
|
||||
it was compiled with is no longer supported. Even supposedly ABI-compatible
|
||||
versions have been a source of trouble, and it creates far too much
|
||||
complexity with little to no benefit, coupled with absurd and unusable FFmpeg
|
||||
API artifacts.
|
||||
|
||||
Instead, mpv will exit with an error when such a situation is detected.
|
||||
This simply means that mpv needs to be rebuilt whenever FFmpeg libraries change.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- d3d: implement screenshots for --hwdec=d3d11va
|
||||
- vo_opengl: add output_size uniform to custom shader
|
||||
- vo_opengl: implement the Panasonic V-Log function (#3157)
|
||||
- vo_opengl: implement ARIB STD-B68 (HLG) HDR TRC
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
- command: pack sub image data in overlay-add command
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- options: deprecate --heartbeat-cmd
|
||||
- audio: deprecate --softvol
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- audio: drop --softvol=no and --softvol=auto (#3322)
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- video: fix deinterlace filter handling on pixel format changes
|
||||
- x11: silence xdg-screensaver
|
||||
- vo_opengl: angle: update the swapchain on resize (#3301)
|
||||
- vo_opengl: error out gracefully when trying to use FBOs without FBO API
|
||||
- vd_lavc: expose mastering display side data reference peak (improves results with HDR content)
|
||||
- vo_opengl: generalize HDR tone mapping mechanism (#3293)
|
||||
- vo_opengl: don't constantly resize the output FBO
|
||||
- vo_opengl: use ringbuffer of PBOs
|
||||
- Windows: make WM_NCHITTEST simpler and more accurate
|
||||
- ao_oss: do not add an entry to audio-device-list if device file missing
|
||||
- dec_audio: fix segment boudnary switching
|
||||
- ao_lavc, vo_lavc: Migrate to new FFmpeg encoding API
|
||||
- vo_opengl: explicitly use main framebuffer when reading window content (#3284)
|
||||
- vo_xv: fix behavior with odd sizes
|
||||
- audio: insert auto-inserted filters at end of chain
|
||||
- x11: add missing FocusChangeMask (disables key repeat when losing focus while a key is down)
|
||||
- ao_coreaudio: error out when selecting invalid device
|
||||
- ad_lavc: work around misbehavior of some FFmpeg decoders like wmapro (#3297)
|
||||
- player: cut off status line on terminal width
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API, and DOCS/interface-changes.rst for a history
|
||||
of changes to other user-visible interfaces.
|
||||
|
||||
A complete changelog can be seen by running `git log v0.18.0..v0.18.1`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.18.0...v0.18.1 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.18.0..v0.18.1
|
||||
|
||||
|
||||
Release 0.18.0
|
||||
==============
|
||||
|
||||
|
||||
Build System Changes
|
||||
--------------------
|
||||
|
||||
- build: Do not link to libGL for egl-drm
|
||||
- build: also use the iconv check on FreeBSD
|
||||
- build: don't install tests, only build them
|
||||
- build: re-enable encoding mode by default
|
||||
- vo_opengl: hwdec: remove build-dependency on dxva2 (#3150)
|
||||
- wscript: make at least 1 OpenGL output mandatory
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- csputils: add SMPTE ST2084 support
|
||||
- demux_mkv: support Matroska webvtt (#3247)
|
||||
- demux_playlist: read directories recursively
|
||||
- stream_memory: add hex:// protocol
|
||||
- vf_crop: support opaque hardware decoding formats
|
||||
- vf_d3d11vpp: add a D3D11 video processor filter
|
||||
- vo_opengl: D3D11VA + ANGLE interop
|
||||
- vo_opengl: add an angle-es2 backend
|
||||
- vo_opengl: angle: dynamically load ANGLE
|
||||
- vo_opengl: d3d11egl: native NV12 sampling support
|
||||
- vo_opengl: enable color management on GLES
|
||||
- vo_opengl: implement HDR (SMPTE ST2084)
|
||||
- vo_opengl: implement tone mapping algorithms
|
||||
- vo_opengl: make PBOs work on GLES 3.x
|
||||
- vo_opengl: support external user hooks, enhancing the flexibility of user shaders
|
||||
- vo_opengl: vdpau interop without RGB conversion
|
||||
- wayland: implement HIDPI support
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- vo_opengl: remove nnedi3 prescaler (replaced by user shaders)
|
||||
- vo_opengl: remove prescaling framework with superxbr prescaler (replaced by user shaders)
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Windows: make taskbar progress indication optional (#2535)
|
||||
- af_lavcac3enc: make encoder configurable
|
||||
- command: add playlist-pos-1 property (#2828)
|
||||
- command: introduce hwdec-current and hwdec-interop properties.
|
||||
- options: add --fit-border video option (currently Windows only)
|
||||
- video: add --hwdec=auto-copy mode
|
||||
- vo_opengl: always autoselect ANGLE as backend if available
|
||||
- vo_opengl: expose performance timers as properties
|
||||
- x11: add --x11-bypass-compositor=never
|
||||
- x11: extend --x11-bypass-compositor with fs-only option (#2582)
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- command: allow setting panscan etc. properties if no video is active
|
||||
- command: don't seek immediately when setting a-b loop while paused
|
||||
- command: if only ab-loop-b is set, loop from start of file
|
||||
- options: --geometry: center window position after applying size (#2397)
|
||||
- player: loop on end of file if ab-loop-b is unset
|
||||
- sd_add: replace --sub-ass=no with --ass-style-override=strip
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- vo_opengl: remove non-working rgb/rgba FBO formats
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- TOOLS/zsh.pl: add .f4v extension in zsh completions
|
||||
- TOOLS/zsh.pl: complete --audio-device
|
||||
- Windows: center window on original window center on resize to fit screen
|
||||
- Windows: fix size calculations for window resize (#2935)
|
||||
- Windows: fix wrong behavior with window-scale when window size exceeds screen size
|
||||
- Windows: make VOCTRL_SET_UNFS_WINDOW_SIZE resize the window around its center (#3164)
|
||||
- af_lavcac3enc: fix custom bitrates
|
||||
- ao_alsa: add more workarounds for hardware with broken drivers (e.g. ODROID-C2)
|
||||
- ao_opensles: remove 32-bit audio formats (not supported by Android)
|
||||
- cocoa: fix actual display refresh rate retrieval
|
||||
- cocoa: use displaylink without manually tracking the display id (#2392)
|
||||
- command: improve playlist* properties change notifications (#3267)
|
||||
- command: slightly nicer OSD list formatting
|
||||
- compatibility with recent FFmpeg APIs
|
||||
- d3d: fix hardware decoding of most MPEG2 things
|
||||
- d3dva: move Intel_H264_NoFGT_ClearVideo to lower priority (#3059)
|
||||
- demux_mkv: better resync behavior for broken google-created webms
|
||||
- demux_mkv: fix seeking with files that miss the first index entry
|
||||
- demux_playlist: recognize m3u8 as playlist extension (#3154)
|
||||
- input: fix parsing multiple input command prefixes
|
||||
- lcms: don't warn/error on 3dlut cache misses
|
||||
- lcms: improve black point handling (especially BT.1886)
|
||||
- macOS: handle multiple dropped files on the window (#3076)
|
||||
- player: always show the first frame in DS mode
|
||||
- player: assume video forwards timestamps jumps only with some formats (#3027)
|
||||
- player: do not update OSD all the time when paused
|
||||
- player: eagerly redraw OSD when seeking with coverart
|
||||
- player: fix use-after-free with --screenshot-directory (#3049)
|
||||
- player: force VO reconfig when unselecting video track
|
||||
- player: really start audio only once video is ready
|
||||
- sd_lavc: work around bug in older FFmpeg releases (#3109)
|
||||
- stream_cdda: enable cache by default
|
||||
- sub: fix --sub-gauss
|
||||
- vd_lavc: better hwdec wrapper decoder selection
|
||||
- vo_opengl: EGL: fix hwdec probing
|
||||
- vo_opengl: angle: avoid fullscreen FBO copy for flipping
|
||||
- vo_opengl: angle: enable DirectComposition (lowers vsync jitter)
|
||||
- vo_opengl: angle: prevent DXGI hooking Alt+Enter
|
||||
- vo_opengl: avoid outputting ultra-wide-gamut by default
|
||||
- vo_opengl: correctly disable interpolation if tscale can't be used
|
||||
- vo_opengl: fix bicubic_fast in ES mode
|
||||
- vo_opengl: fix d3d11 hardware decoding probing on Windows 7
|
||||
- vo_opengl: improve scale=oversample performance
|
||||
- vo_opengl: make the screen blue on shader errors
|
||||
- vo_opengl: partially fix 0bgr format support
|
||||
- vo_opengl: possibly update icc profile after changing options
|
||||
- vo_opengl: request core profile on X11/EGL too
|
||||
- vo_opengl: require at least ES 3.0 for float textures
|
||||
- vo_opengl: vdpau: fix certain cases of preemption recovery failures
|
||||
- vo_rpi: attempt to survive display mode changes
|
||||
- vo_rpi: fix destroying overlays (#3100)
|
||||
- vo_rpi: wait for vsync with a timeout
|
||||
- vo_sdl: fix pixel formats.
|
||||
- vo_xv: Handle incorrect size returned by Xv(Shm)CreateImage (#320)
|
||||
- wayland: correctly report display refresh rate
|
||||
- wayland: use the advertised size in fullscreen (#3021, #2657)
|
||||
- x11: tell GNOME to use dark window decorations
|
||||
- ytdl_hook: fix brightcove urls
|
||||
- ytdl_hook: just check if protocol is rtmp (#3090)
|
||||
- ytdl_hook: support multi-arc subtitles
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API, and DOCS/interface-changes.rst for a history
|
||||
of changes to other user-visible interfaces.
|
||||
|
||||
A complete changelog can be seen by running `git log v0.17.0..v0.18.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.17.0...v0.18.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.17.0..v0.18.0
|
||||
|
||||
|
||||
Release 0.17.0
|
||||
==============
|
||||
|
||||
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
export DEST_OS=win32
|
||||
export CC=gcc
|
||||
export PKG_CONFIG=/usr/bin/pkg-config
|
||||
export PERL=/usr/bin/perl
|
||||
export PYTHON=/usr/bin/python3
|
||||
|
||||
"$PYTHON" bootstrap.py
|
||||
"$PYTHON" waf configure \
|
||||
--check-c-compiler=gcc \
|
||||
--disable-cdda \
|
||||
--enable-egl-angle \
|
||||
--enable-jpeg \
|
||||
--enable-lcms2 \
|
||||
--enable-libarchive \
|
||||
--enable-libass \
|
||||
--enable-lua \
|
||||
--enable-rubberband \
|
||||
--enable-uchardet
|
||||
"$PYTHON" waf build
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
case $MSYSTEM in
|
||||
MINGW32)
|
||||
export MINGW_PACKAGE_PREFIX=mingw-w64-i686
|
||||
;;
|
||||
MINGW64)
|
||||
export MINGW_PACKAGE_PREFIX=mingw-w64-x86_64
|
||||
;;
|
||||
esac
|
||||
|
||||
# Write an empty fonts.conf to speed up fc-cache
|
||||
export FONTCONFIG_FILE=/dummy-fonts.conf
|
||||
cat >"$FONTCONFIG_FILE" <<EOF
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig></fontconfig>
|
||||
EOF
|
||||
|
||||
# Install build dependencies for mpv
|
||||
pacman -S --noconfirm --needed \
|
||||
$MINGW_PACKAGE_PREFIX-gcc \
|
||||
$MINGW_PACKAGE_PREFIX-angleproject-git \
|
||||
$MINGW_PACKAGE_PREFIX-ffmpeg \
|
||||
$MINGW_PACKAGE_PREFIX-lcms2 \
|
||||
$MINGW_PACKAGE_PREFIX-libarchive \
|
||||
$MINGW_PACKAGE_PREFIX-libass \
|
||||
$MINGW_PACKAGE_PREFIX-libjpeg-turbo \
|
||||
$MINGW_PACKAGE_PREFIX-lua51 \
|
||||
$MINGW_PACKAGE_PREFIX-rubberband \
|
||||
$MINGW_PACKAGE_PREFIX-uchardet-git
|
||||
|
||||
# Delete unused packages to reduce space used in the Appveyor cache
|
||||
pacman -Sc --noconfirm
|
||||
File renamed without changes.
+23
-7
@@ -36,7 +36,8 @@ chomp $vf_str;
|
||||
|
||||
$protos_str = join(' ', @protos);
|
||||
|
||||
my $profile_comp = <<'EOS';
|
||||
my $runtime_completions = <<'EOS';
|
||||
profile|show-profile)
|
||||
local -a profiles
|
||||
local current
|
||||
for current in "${(@f)$($words[1] --profile=help)}"; do
|
||||
@@ -63,8 +64,25 @@ my $profile_comp = <<'EOS';
|
||||
profiles=($profiles 'help[list profiles]')
|
||||
_values -s , 'profile(s)' $profiles && rc=0
|
||||
fi
|
||||
;;
|
||||
|
||||
audio-device)
|
||||
local -a audio_devices
|
||||
local current
|
||||
for current in "${(@f)$($words[1] --audio-device=help)}"; do
|
||||
current=${current//\*/\\\*}
|
||||
current=${current//\:/\\\:}
|
||||
current=${current//\[/\\\[}
|
||||
current=${current//\]/\\\]}
|
||||
if [[ $current =~ ' '\'([^\']*)\'' \('(.*)'\)' ]]; then
|
||||
audio_devices=($audio_devices "$match[1][$match[2]]")
|
||||
fi
|
||||
done
|
||||
audio_devices=($audio_devices 'help[list audio devices]')
|
||||
_values 'audio device' $audio_devices && rc=0
|
||||
;;
|
||||
EOS
|
||||
chomp $profile_comp;
|
||||
chomp $runtime_completions;
|
||||
|
||||
my $tmpl = <<"EOS";
|
||||
#compdef mpv
|
||||
@@ -111,9 +129,7 @@ $vf_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
profile|show-profile)
|
||||
$profile_comp
|
||||
;;
|
||||
$runtime_completions
|
||||
|
||||
files)
|
||||
compset -P '*,'
|
||||
@@ -126,7 +142,7 @@ $profile_comp
|
||||
_tags files urls
|
||||
while _tags; do
|
||||
_requested files expl 'media file' _files -g \\
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|opus|qt|rm|ts|vob|wav|webm|wma|wmv|wv)(-.)" && rc=0
|
||||
"*.(#i)(asf|asx|avi|f4v|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|opus|qt|rm|ts|vob|wav|webm|wma|wmv|wv)(-.)" && rc=0
|
||||
if _requested urls; then
|
||||
while _next_label urls expl URL; do
|
||||
_urls "\$expl[@]" && rc=0
|
||||
@@ -189,7 +205,7 @@ sub parse_main_opts {
|
||||
}
|
||||
} elsif ($line =~ /\[file\]/) {
|
||||
$entry .= '->files';
|
||||
} elsif ($name =~ /^--(ao|vo|af|vf|profile|show-profile)$/) {
|
||||
} elsif ($name =~ /^--(ao|vo|af|vf|profile|show-profile|audio-device)$/) {
|
||||
$entry .= "->$1";
|
||||
}
|
||||
push @list, $entry;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- appveyor
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- MSYSTEM: MINGW64
|
||||
- MSYSTEM: MINGW32
|
||||
|
||||
shallow_clone: true
|
||||
test: off
|
||||
|
||||
install:
|
||||
# Update core packages
|
||||
- C:\msys64\usr\bin\pacman -Syyuu --noconfirm
|
||||
# Update non-core packages
|
||||
- C:\msys64\usr\bin\pacman -Suu --noconfirm
|
||||
# Install required MSYS2 packages
|
||||
- C:\msys64\usr\bin\pacman -S --noconfirm --needed perl python pkg-config
|
||||
# Now MSYS2 is up to date, do the rest of the install from a bash script
|
||||
- C:\msys64\usr\bin\bash -lc "cd \"$APPVEYOR_BUILD_FOLDER\" && exec ./TOOLS/appveyor-install.sh"
|
||||
build_script:
|
||||
- C:\msys64\usr\bin\bash -lc "cd \"$APPVEYOR_BUILD_FOLDER\" && exec ./TOOLS/appveyor-build.sh"
|
||||
on_failure:
|
||||
- type "%APPVEYOR_BUILD_FOLDER%\build\config.log"
|
||||
|
||||
cache:
|
||||
- C:\msys64\var\cache\pacman\pkg
|
||||
+10
-2
@@ -257,13 +257,21 @@ void mp_audio_skip_samples(struct mp_audio *data, int samples)
|
||||
data->pts += samples / (double)data->rate;
|
||||
}
|
||||
|
||||
// Return the timestamp of the sample just after the end of this frame.
|
||||
double mp_audio_end_pts(struct mp_audio *f)
|
||||
{
|
||||
if (f->pts == MP_NOPTS_VALUE || f->rate < 1)
|
||||
return MP_NOPTS_VALUE;
|
||||
return f->pts + f->samples / (double)f->rate;
|
||||
}
|
||||
|
||||
// Clip the given frame to the given timestamp range. Adjusts the frame size
|
||||
// and timestamp.
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end)
|
||||
{
|
||||
if (f->pts == MP_NOPTS_VALUE || f->rate < 1)
|
||||
double f_end = mp_audio_end_pts(f);
|
||||
if (f_end == MP_NOPTS_VALUE)
|
||||
return;
|
||||
double f_end = f->pts + f->samples / (double)f->rate;
|
||||
if (end != MP_NOPTS_VALUE) {
|
||||
if (f_end >= end) {
|
||||
if (f->pts >= end) {
|
||||
|
||||
@@ -73,6 +73,7 @@ void mp_audio_copy(struct mp_audio *dst, int dst_offset,
|
||||
void mp_audio_copy_attributes(struct mp_audio *dst, struct mp_audio *src);
|
||||
void mp_audio_skip_samples(struct mp_audio *data, int samples);
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end);
|
||||
double mp_audio_end_pts(struct mp_audio *data);
|
||||
|
||||
bool mp_audio_is_writeable(struct mp_audio *data);
|
||||
int mp_audio_make_writeable(struct mp_audio *data);
|
||||
|
||||
@@ -45,6 +45,7 @@ struct priv {
|
||||
uint32_t skip_samples, trim_samples;
|
||||
bool preroll_done;
|
||||
double next_pts;
|
||||
bool needs_reset;
|
||||
AVRational codec_timebase;
|
||||
};
|
||||
|
||||
@@ -173,6 +174,7 @@ static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
ctx->trim_samples = 0;
|
||||
ctx->preroll_done = false;
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
ctx->needs_reset = false;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
@@ -184,6 +186,12 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
|
||||
// If the decoder discards the timestamp for some reason, we use the
|
||||
// interpolated PTS. Initialize it so that it works for the initial
|
||||
// packet as well.
|
||||
if (mpkt && priv->next_pts == MP_NOPTS_VALUE)
|
||||
priv->next_pts = mpkt->pts;
|
||||
|
||||
int in_len = mpkt ? mpkt->len : 0;
|
||||
|
||||
AVPacket pkt;
|
||||
@@ -192,6 +200,9 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
int got_frame = 0;
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
if (priv->needs_reset)
|
||||
control(da, ADCTRL_RESET, NULL);
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
int ret = avcodec_send_packet(avctx, &pkt);
|
||||
if (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
@@ -200,6 +211,8 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
ret = avcodec_receive_frame(avctx, priv->avframe);
|
||||
if (ret >= 0)
|
||||
got_frame = 1;
|
||||
if (ret == AVERROR_EOF)
|
||||
priv->needs_reset = true;
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
@@ -116,9 +116,16 @@ static int determine_codec_profile(struct dec_audio *da, AVPacket *pkt)
|
||||
goto done;
|
||||
}
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
if (avcodec_send_packet(ctx, pkt) < 0)
|
||||
goto done;
|
||||
if (avcodec_receive_frame(ctx, frame) < 0)
|
||||
goto done;
|
||||
#else
|
||||
int got_frame = 0;
|
||||
if (avcodec_decode_audio4(ctx, frame, &got_frame, pkt) < 1 || !got_frame)
|
||||
goto done;
|
||||
#endif
|
||||
|
||||
profile = ctx->profile;
|
||||
|
||||
|
||||
@@ -200,7 +200,9 @@ void audio_work(struct dec_audio *da)
|
||||
if (da->current_frame)
|
||||
return;
|
||||
|
||||
if (!da->packet && demux_read_packet_async(da->header, &da->packet) == 0) {
|
||||
if (!da->packet && !da->new_segment &&
|
||||
demux_read_packet_async(da->header, &da->packet) == 0)
|
||||
{
|
||||
da->current_state = DATA_WAIT;
|
||||
return;
|
||||
}
|
||||
@@ -211,6 +213,7 @@ void audio_work(struct dec_audio *da)
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
bool had_input_packet = !!da->packet;
|
||||
bool had_packet = da->packet || da->new_segment;
|
||||
|
||||
int ret = da->ad_driver->decode_packet(da, da->packet, &da->current_frame);
|
||||
@@ -233,12 +236,12 @@ void audio_work(struct dec_audio *da)
|
||||
|
||||
fix_audio_pts(da);
|
||||
|
||||
bool segment_end = true;
|
||||
bool segment_end = !da->current_frame && !had_input_packet;
|
||||
|
||||
if (da->current_frame) {
|
||||
mp_audio_clip_timestamps(da->current_frame, da->start, da->end);
|
||||
if (da->current_frame->pts != MP_NOPTS_VALUE && da->start != MP_NOPTS_VALUE)
|
||||
segment_end = da->current_frame->pts >= da->start;
|
||||
segment_end = da->current_frame->pts >= da->end;
|
||||
if (da->current_frame->samples == 0) {
|
||||
talloc_free(da->current_frame);
|
||||
da->current_frame = NULL;
|
||||
|
||||
+4
-35
@@ -210,29 +210,6 @@ static struct af_instance *af_prepend(struct af_stream *s,
|
||||
return new;
|
||||
}
|
||||
|
||||
/* Create and insert a new filter of type name after the filter in the
|
||||
argument. This function can be called during runtime, the return
|
||||
value is the new filter */
|
||||
static struct af_instance *af_append(struct af_stream *s,
|
||||
struct af_instance *af,
|
||||
char *name, char **args)
|
||||
{
|
||||
if (!af)
|
||||
af = s->first;
|
||||
if (af == s->last)
|
||||
af = s->last->prev;
|
||||
// Create the new filter and make sure it is OK
|
||||
struct af_instance *new = af_create(s, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
// Update pointers
|
||||
new->prev = af;
|
||||
new->next = af->next;
|
||||
af->next = new;
|
||||
new->next->prev = new;
|
||||
return new;
|
||||
}
|
||||
|
||||
// Uninit and remove the filter "af"
|
||||
static void af_remove(struct af_stream *s, struct af_instance *af)
|
||||
{
|
||||
@@ -275,6 +252,8 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
while (af) {
|
||||
char b[128] = {0};
|
||||
mp_snprintf_cat(b, sizeof(b), " [%s] ", af->info->name);
|
||||
if (af->label)
|
||||
mp_snprintf_cat(b, sizeof(b), "\"%s\" ", af->label);
|
||||
if (af->data)
|
||||
mp_snprintf_cat(b, sizeof(b), "%s", mp_audio_config_to_str(af->data));
|
||||
if (af == at)
|
||||
@@ -287,11 +266,6 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
MP_MSG(s, msg_level, " [ao] %s\n", mp_audio_config_to_str(&s->output));
|
||||
}
|
||||
|
||||
static bool af_is_conversion_filter(struct af_instance *af)
|
||||
{
|
||||
return af && strcmp(af->info->name, "lavrresample") == 0;
|
||||
}
|
||||
|
||||
// in is what af can take as input - insert a conversion filter if the actual
|
||||
// input format doesn't match what af expects.
|
||||
// Returns:
|
||||
@@ -557,7 +531,7 @@ void af_destroy(struct af_stream *s)
|
||||
format of the preferred output respectively. The function is
|
||||
reentrant i.e. if called with an already initialized stream the
|
||||
stream will be reinitialized.
|
||||
If one of the prefered output parameters is 0 the one that needs
|
||||
If one of the preferred output parameters is 0 the one that needs
|
||||
no conversion is used (i.e. the output format in the last filter).
|
||||
The return value is 0 if success and -1 if failure */
|
||||
int af_init(struct af_stream *s)
|
||||
@@ -602,12 +576,7 @@ struct af_instance *af_add(struct af_stream *s, char *name, char *label,
|
||||
if (af_find_by_label(s, label))
|
||||
return NULL;
|
||||
|
||||
struct af_instance *new;
|
||||
// Insert the filter somewhere nice
|
||||
if (af_is_conversion_filter(s->first->next))
|
||||
new = af_append(s, s->first->next, name, args);
|
||||
else
|
||||
new = af_prepend(s, s->first->next, name, args);
|
||||
struct af_instance *new = af_prepend(s, s->last, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
new->label = talloc_strdup(new, label);
|
||||
|
||||
+134
-50
@@ -31,9 +31,12 @@
|
||||
#include <libavutil/bswap.h>
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
|
||||
@@ -54,13 +57,50 @@ typedef struct af_ac3enc_s {
|
||||
struct mp_audio *pending; // unconsumed input data
|
||||
int in_samples; // samples of input per AC3 frame
|
||||
int out_samples; // upper bound on encoded output per AC3 frame
|
||||
int in_sampleformat;
|
||||
|
||||
int cfg_add_iec61937_header;
|
||||
int cfg_bit_rate;
|
||||
int cfg_min_channel_num;
|
||||
char *cfg_encoder;
|
||||
} af_ac3enc_t;
|
||||
|
||||
// fmt carries the input format. Change it to the best next-possible format
|
||||
// the encoder likely accepts.
|
||||
static void select_encode_format(AVCodecContext *c, struct mp_audio *fmt)
|
||||
{
|
||||
int formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(fmt->format, formats);
|
||||
|
||||
for (int n = 0; formats[n]; n++) {
|
||||
const enum AVSampleFormat *lf = c->codec->sample_fmts;
|
||||
for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
int mpfmt = af_from_avformat(lf[i]);
|
||||
if (mpfmt && mpfmt == formats[n]) {
|
||||
mp_audio_set_format(fmt, mpfmt);
|
||||
goto done_fmt;
|
||||
}
|
||||
}
|
||||
}
|
||||
done_fmt: ;
|
||||
|
||||
int rate =
|
||||
af_select_best_samplerate(fmt->rate, c->codec->supported_samplerates);
|
||||
if (rate > 0)
|
||||
fmt->rate = rate;
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
const uint64_t *lch = c->codec->channel_layouts;
|
||||
for (int n = 0; lch && lch[n]; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_chmap_from_lavc(&chmap, lch[n]);
|
||||
mp_chmap_sel_add_map(&sel, &chmap);
|
||||
}
|
||||
struct mp_chmap res = fmt->channels;
|
||||
mp_chmap_sel_adjust(&sel, &res);
|
||||
if (!mp_chmap_is_empty(&res))
|
||||
mp_audio_set_channels(fmt, &res);
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
@@ -76,23 +116,17 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
if (!af_fmt_is_pcm(in->format) || in->nch < s->cfg_min_channel_num)
|
||||
return AF_DETACH;
|
||||
|
||||
mp_audio_set_format(in, s->in_sampleformat);
|
||||
// At least currently, the AC3 encoder doesn't export sample rates.
|
||||
in->rate = 48000;
|
||||
select_encode_format(s->lavc_actx, in);
|
||||
|
||||
if (in->rate != 48000 && in->rate != 44100 && in->rate != 32000)
|
||||
in->rate = 48000;
|
||||
af->data->rate = in->rate;
|
||||
|
||||
mp_chmap_reorder_to_lavc(&in->channels);
|
||||
if (in->nch > AC3_MAX_CHANNELS)
|
||||
mp_audio_set_num_channels(in, AC3_MAX_CHANNELS);
|
||||
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S_AC3);
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
|
||||
if (!mp_audio_config_equals(in, &orig_in))
|
||||
return AF_FALSE;
|
||||
|
||||
s->in_samples = AC3_FRAME_SIZE;
|
||||
if (s->cfg_add_iec61937_header) {
|
||||
s->out_samples = AC3_FRAME_SIZE;
|
||||
} else {
|
||||
@@ -100,14 +134,11 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
}
|
||||
|
||||
mp_audio_copy_config(s->input, in);
|
||||
mp_audio_realloc(s->input, s->in_samples);
|
||||
s->input->samples = 0;
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
|
||||
MP_DBG(af, "af_lavcac3enc reinit: %d, %d, %d.\n",
|
||||
in->nch, in->rate, s->in_samples);
|
||||
MP_DBG(af, "reinit: %d, %d, %d.\n", in->nch, in->rate, s->in_samples);
|
||||
|
||||
int bit_rate = s->bit_rate ? s->bit_rate : default_bit_rate[in->nch];
|
||||
|
||||
@@ -118,6 +149,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
avcodec_close(s->lavc_actx);
|
||||
|
||||
// Put sample parameters
|
||||
s->lavc_actx->sample_fmt = af_to_avformat(in->format);
|
||||
s->lavc_actx->channels = in->nch;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&in->channels);
|
||||
s->lavc_actx->sample_rate = in->rate;
|
||||
@@ -127,14 +159,24 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
MP_ERR(af, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (s->lavc_actx->frame_size < 1) {
|
||||
MP_ERR(af, "encoder didn't specify input frame size\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
if (s->lavc_actx->frame_size != AC3_FRAME_SIZE) {
|
||||
MP_ERR(af, "lavcac3enc: unexpected ac3 "
|
||||
"encoder frame size %d\n", s->lavc_actx->frame_size);
|
||||
return AF_ERROR;
|
||||
}
|
||||
s->in_samples = s->lavc_actx->frame_size;
|
||||
mp_audio_realloc(s->input, s->in_samples);
|
||||
s->input->samples = 0;
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
if (avcodec_is_open(s->lavc_actx))
|
||||
avcodec_flush_buffers(s->lavc_actx);
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
s->input->samples = 0;
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
@@ -199,12 +241,33 @@ static bool fill_buffer(struct af_instance *af)
|
||||
return s->input->samples >= s->in_samples;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
// Return <0 on error, 0 on need more input, 1 on success (and *frame set).
|
||||
// To actually advance the read pointer, set s->input->samples=0 afterwards.
|
||||
static int read_input_frame(struct af_instance *af, AVFrame *frame)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
if (!fill_buffer(af))
|
||||
return 0; // need more input
|
||||
|
||||
frame->nb_samples = s->in_samples;
|
||||
frame->format = s->lavc_actx->sample_fmt;
|
||||
frame->channel_layout = s->lavc_actx->channel_layout;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
frame->channels = s->lavc_actx->channels;
|
||||
#endif
|
||||
assert(s->input->num_planes <= AV_NUM_DATA_POINTERS);
|
||||
frame->extended_data = frame->data;
|
||||
for (int n = 0; n < s->input->num_planes; n++)
|
||||
frame->data[n] = s->input->planes[n];
|
||||
frame->linesize[0] = s->input->samples * s->input->sstride;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
MP_FATAL(af, "Could not allocate memory \n");
|
||||
@@ -212,18 +275,41 @@ static int filter_out(struct af_instance *af)
|
||||
}
|
||||
int err = -1;
|
||||
|
||||
frame->nb_samples = s->in_samples;
|
||||
frame->format = s->lavc_actx->sample_fmt;
|
||||
frame->channel_layout = s->lavc_actx->channel_layout;
|
||||
assert(s->input->num_planes <= AV_NUM_DATA_POINTERS);
|
||||
frame->extended_data = frame->data;
|
||||
for (int n = 0; n < s->input->num_planes; n++)
|
||||
frame->data[n] = s->input->planes[n];
|
||||
frame->linesize[0] = s->input->samples * s->input->sstride;
|
||||
|
||||
AVPacket pkt = {0};
|
||||
av_init_packet(&pkt);
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
// Send input as long as it wants.
|
||||
while (1) {
|
||||
err = read_input_frame(af, frame);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
if (err == 0)
|
||||
break;
|
||||
err = -1;
|
||||
int lavc_ret = avcodec_send_frame(s->lavc_actx, frame);
|
||||
// On EAGAIN, we're supposed to read remaining output.
|
||||
if (lavc_ret == AVERROR(EAGAIN))
|
||||
break;
|
||||
if (lavc_ret < 0) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
s->input->samples = 0;
|
||||
}
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
|
||||
if (lavc_ret == AVERROR(EAGAIN)) {
|
||||
// Need to buffer more input.
|
||||
err = 0;
|
||||
goto done;
|
||||
}
|
||||
#else
|
||||
err = read_input_frame(af, frame);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
if (err == 0)
|
||||
goto done;
|
||||
err = -1;
|
||||
int ok;
|
||||
int lavc_ret = avcodec_encode_audio2(s->lavc_actx, &pkt, frame, &ok);
|
||||
av_frame_free(&frame);
|
||||
@@ -232,9 +318,10 @@ static int filter_out(struct af_instance *af)
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
|
||||
MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n",
|
||||
pkt.size, s->pending->samples);
|
||||
pkt.size, s->pending->samples);
|
||||
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, s->out_samples);
|
||||
@@ -287,9 +374,9 @@ static int af_open(struct af_instance* af){
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name("ac3");
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name(s->cfg_encoder);
|
||||
if (!s->lavc_acodec) {
|
||||
MP_ERR(af, "Audio LAVC, couldn't find encoder for codec %s.\n", "ac3");
|
||||
MP_ERR(af, "Couldn't find encoder %s.\n", s->cfg_encoder);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
@@ -298,35 +385,30 @@ static int af_open(struct af_instance* af){
|
||||
MP_ERR(af, "Audio LAVC, couldn't allocate context!\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
const enum AVSampleFormat *fmts = s->lavc_acodec->sample_fmts;
|
||||
for (int i = 0; fmts[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
s->in_sampleformat = af_from_avformat(fmts[i]);
|
||||
if (s->in_sampleformat) {
|
||||
s->lavc_actx->sample_fmt = fmts[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!s->in_sampleformat) {
|
||||
MP_ERR(af, "Audio LAVC, encoder doesn't "
|
||||
"support expected sample formats!\n");
|
||||
// For this one, we require the decoder to expert lists of all supported
|
||||
// parameters. (Not all decoders do that, but the ones we're interested
|
||||
// in do.)
|
||||
if (!s->lavc_acodec->sample_fmts ||
|
||||
!s->lavc_acodec->channel_layouts)
|
||||
{
|
||||
MP_ERR(af, "Audio encoder doesn't list supported parameters.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
MP_VERBOSE(af, "[af_lavcac3enc]: in sample format: %s\n",
|
||||
af_fmt_to_str(s->in_sampleformat));
|
||||
|
||||
s->input = talloc_zero(s, struct mp_audio);
|
||||
|
||||
if (s->cfg_bit_rate) {
|
||||
int i;
|
||||
for (i = 0; i < 19; i++) {
|
||||
if (ac3_bitrate_tab[i] == s->cfg_bit_rate)
|
||||
if (ac3_bitrate_tab[i] == s->cfg_bit_rate) {
|
||||
s->bit_rate = ac3_bitrate_tab[i] * 1000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= 19) {
|
||||
MP_WARN(af, "af_lavcac3enc unable set unsupported "
|
||||
"bitrate %d, use default bitrate (check manpage to see "
|
||||
"supported bitrates).\n", s->cfg_bit_rate);
|
||||
s->cfg_bit_rate = 0;
|
||||
MP_WARN(af, "unable set unsupported bitrate %d, use default "
|
||||
"bitrate (check manpage to see supported bitrates).\n",
|
||||
s->cfg_bit_rate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,12 +426,14 @@ const struct af_info af_info_lavcac3enc = {
|
||||
.cfg_add_iec61937_header = 1,
|
||||
.cfg_bit_rate = 640,
|
||||
.cfg_min_channel_num = 3,
|
||||
.cfg_encoder = "ac3",
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("tospdif", cfg_add_iec61937_header, 0),
|
||||
OPT_CHOICE_OR_INT("bitrate", cfg_bit_rate, 0, 32, 640,
|
||||
({"auto", 0}, {"default", 0})),
|
||||
OPT_INTRANGE("minch", cfg_min_channel_num, 0, 2, 6),
|
||||
OPT_STRING("encoder", cfg_encoder, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+13
-264
@@ -35,18 +35,6 @@ struct mixer {
|
||||
struct MPOpts *opts;
|
||||
struct ao *ao;
|
||||
struct af_stream *af;
|
||||
// Static, dependent on ao/softvol settings
|
||||
bool softvol; // use AO (false) or af_volume (true)
|
||||
bool persistent_volume; // volume does not need to be restored
|
||||
bool emulate_mute; // if true, emulate mute with volume=0
|
||||
// Last known values (possibly out of sync with reality)
|
||||
float vol_l, vol_r;
|
||||
bool muted;
|
||||
// Used to decide whether we should unmute on uninit
|
||||
bool muted_by_us;
|
||||
/* Contains ao driver name or "softvol" if volume is not persistent
|
||||
* and needs to be restored after the driver is reinitialized. */
|
||||
const char *driver;
|
||||
// Other stuff
|
||||
float balance;
|
||||
};
|
||||
@@ -57,82 +45,25 @@ struct mixer *mixer_init(void *talloc_ctx, struct mpv_global *global)
|
||||
*mixer = (struct mixer) {
|
||||
.log = mp_log_new(mixer, global->log, "mixer"),
|
||||
.opts = global->opts,
|
||||
.vol_l = 100,
|
||||
.vol_r = 100,
|
||||
.driver = "",
|
||||
};
|
||||
return mixer;
|
||||
}
|
||||
|
||||
bool mixer_audio_initialized(struct mixer *mixer)
|
||||
{
|
||||
return !!mixer->ao;
|
||||
return !!mixer->af;
|
||||
}
|
||||
|
||||
float mixer_getmaxvolume(struct mixer *mixer)
|
||||
// Called when opts->softvol_volume or opts->softvol_mute were changed.
|
||||
void mixer_update_volume(struct mixer *mixer)
|
||||
{
|
||||
// gain == 1
|
||||
return mixer->softvol ? mixer->opts->softvol_max : 100;
|
||||
}
|
||||
|
||||
static void checkvolume(struct mixer *mixer)
|
||||
{
|
||||
if (!mixer->ao)
|
||||
if (!mixer->af)
|
||||
return;
|
||||
|
||||
ao_control_vol_t vol = {mixer->vol_l, mixer->vol_r};
|
||||
if (mixer->softvol) {
|
||||
float gain;
|
||||
if (!af_control_any_rev(mixer->af, AF_CONTROL_GET_VOLUME, &gain))
|
||||
gain = 1.0;
|
||||
vol.left = gain * 100.0;
|
||||
vol.right = gain * 100.0;
|
||||
} else {
|
||||
MP_DBG(mixer, "Reading volume from AO.\n");
|
||||
// Rely on the values not changing if the query is not supported
|
||||
ao_control(mixer->ao, AOCONTROL_GET_VOLUME, &vol);
|
||||
ao_control(mixer->ao, AOCONTROL_GET_MUTE, &mixer->muted);
|
||||
}
|
||||
float l = mixer->vol_l;
|
||||
float r = mixer->vol_r;
|
||||
if (mixer->emulate_mute && mixer->muted)
|
||||
l = r = 0;
|
||||
/* Try to detect cases where the volume has been changed by some external
|
||||
* action (such as something else changing a shared system-wide volume).
|
||||
* We don't test for exact equality, as some AOs may round the value
|
||||
* we last set to some nearby supported value. 3 has been the default
|
||||
* volume step for increase/decrease keys, and is apparently big enough
|
||||
* to step to the next possible value in most setups.
|
||||
*/
|
||||
if (FFABS(vol.left - l) >= 3 || FFABS(vol.right - r) >= 3) {
|
||||
mixer->vol_l = vol.left;
|
||||
mixer->vol_r = vol.right;
|
||||
if (mixer->emulate_mute)
|
||||
mixer->muted = false;
|
||||
}
|
||||
mixer->muted_by_us &= mixer->muted;
|
||||
}
|
||||
float gain = MPMAX(mixer->opts->softvol_volume / 100.0, 0);
|
||||
if (mixer->opts->softvol_mute == 1)
|
||||
gain = 0.0;
|
||||
|
||||
void mixer_getvolume(struct mixer *mixer, float *l, float *r)
|
||||
{
|
||||
checkvolume(mixer);
|
||||
*l = mixer->vol_l;
|
||||
*r = mixer->vol_r;
|
||||
}
|
||||
|
||||
static void setvolume_internal(struct mixer *mixer)
|
||||
{
|
||||
float l = mixer->vol_l, r = mixer->vol_r;
|
||||
if (mixer->emulate_mute && mixer->muted)
|
||||
l = r = 0;
|
||||
if (!mixer->softvol) {
|
||||
MP_DBG(mixer, "Setting volume on AO.\n");
|
||||
struct ao_control_vol vol = {.left = l, .right = r};
|
||||
if (ao_control(mixer->ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK)
|
||||
MP_ERR(mixer, "Failed to change audio output volume.\n");
|
||||
return;
|
||||
}
|
||||
float gain = (l + r) / 2.0 / 100.0;
|
||||
if (!af_control_any_rev(mixer->af, AF_CONTROL_SET_VOLUME, &gain)) {
|
||||
if (gain == 1.0)
|
||||
return;
|
||||
@@ -143,57 +74,6 @@ static void setvolume_internal(struct mixer *mixer)
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_setvolume(struct mixer *mixer, float l, float r)
|
||||
{
|
||||
checkvolume(mixer); // to check mute status
|
||||
|
||||
float max = mixer_getmaxvolume(mixer);
|
||||
mixer->vol_l = MPCLAMP(l, 0, max);
|
||||
mixer->vol_r = MPCLAMP(r, 0, max);
|
||||
if (mixer->ao)
|
||||
setvolume_internal(mixer);
|
||||
}
|
||||
|
||||
void mixer_getbothvolume(struct mixer *mixer, float *b)
|
||||
{
|
||||
float mixer_l, mixer_r;
|
||||
mixer_getvolume(mixer, &mixer_l, &mixer_r);
|
||||
*b = (mixer_l + mixer_r) / 2;
|
||||
}
|
||||
|
||||
void mixer_setmute(struct mixer *mixer, bool mute)
|
||||
{
|
||||
checkvolume(mixer);
|
||||
if (mute == mixer->muted)
|
||||
return;
|
||||
if (mixer->ao) {
|
||||
mixer->muted = mute;
|
||||
mixer->muted_by_us = mute;
|
||||
if (mixer->emulate_mute) {
|
||||
setvolume_internal(mixer);
|
||||
} else {
|
||||
ao_control(mixer->ao, AOCONTROL_SET_MUTE, &mute);
|
||||
}
|
||||
checkvolume(mixer);
|
||||
} else {
|
||||
mixer->muted = mute;
|
||||
mixer->muted_by_us = mute;
|
||||
}
|
||||
}
|
||||
|
||||
bool mixer_getmute(struct mixer *mixer)
|
||||
{
|
||||
checkvolume(mixer);
|
||||
return mixer->muted;
|
||||
}
|
||||
|
||||
void mixer_addvolume(struct mixer *mixer, float step)
|
||||
{
|
||||
float vol_l, vol_r;
|
||||
mixer_getvolume(mixer, &vol_l, &vol_r);
|
||||
mixer_setvolume(mixer, vol_l + step, vol_r + step);
|
||||
}
|
||||
|
||||
void mixer_getbalance(struct mixer *mixer, float *val)
|
||||
{
|
||||
if (mixer->af)
|
||||
@@ -242,130 +122,18 @@ void mixer_setbalance(struct mixer *mixer, float val)
|
||||
af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_BALANCE, &val);
|
||||
}
|
||||
|
||||
char *mixer_get_volume_restore_data(struct mixer *mixer)
|
||||
{
|
||||
if (!mixer->driver[0])
|
||||
return NULL;
|
||||
return talloc_asprintf(NULL, "%s:%f:%f:%d", mixer->driver, mixer->vol_l,
|
||||
mixer->vol_r, mixer->muted_by_us);
|
||||
}
|
||||
|
||||
static void probe_softvol(struct mixer *mixer)
|
||||
{
|
||||
bool ao_perapp = ao_control(mixer->ao, AOCONTROL_HAS_PER_APP_VOLUME, 0) == 1;
|
||||
bool ao_softvol = ao_control(mixer->ao, AOCONTROL_HAS_SOFT_VOLUME, 0) == 1;
|
||||
assert(!(ao_perapp && ao_softvol));
|
||||
mixer->persistent_volume = !ao_softvol;
|
||||
|
||||
if (mixer->opts->softvol == SOFTVOL_AUTO) {
|
||||
// No system-wide volume => fine with AO volume control.
|
||||
mixer->softvol = !ao_softvol && !ao_perapp;
|
||||
} else {
|
||||
mixer->softvol = mixer->opts->softvol == SOFTVOL_YES;
|
||||
}
|
||||
|
||||
if (mixer->softvol)
|
||||
mixer->persistent_volume = false;
|
||||
|
||||
MP_DBG(mixer, "Will use af_volume: %s\n", mixer->softvol ? "yes" : "no");
|
||||
|
||||
// If we can't use real volume control => force softvol.
|
||||
if (!mixer->softvol) {
|
||||
ao_control_vol_t vol;
|
||||
if (ao_control(mixer->ao, AOCONTROL_GET_VOLUME, &vol) != CONTROL_OK) {
|
||||
mixer->softvol = true;
|
||||
MP_WARN(mixer, "Hardware volume control unavailable.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Probe native mute support.
|
||||
mixer->emulate_mute = true;
|
||||
if (!mixer->softvol) {
|
||||
if (ao_control(mixer->ao, AOCONTROL_GET_MUTE, &(bool){0}) == CONTROL_OK)
|
||||
mixer->emulate_mute = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void restore_volume(struct mixer *mixer)
|
||||
{
|
||||
struct MPOpts *opts = mixer->opts;
|
||||
struct ao *ao = mixer->ao;
|
||||
|
||||
float force_vol_l = -1, force_vol_r = -1;
|
||||
int force_mute = -1;
|
||||
|
||||
const char *prev_driver = mixer->driver;
|
||||
mixer->driver = mixer->softvol ? "softvol" : ao_get_name(ao);
|
||||
if (!prev_driver[0])
|
||||
prev_driver = mixer->driver;
|
||||
|
||||
// Restore old parameters if volume won't survive reinitialization.
|
||||
// But not if volume scale is possibly different.
|
||||
if (!mixer->persistent_volume && strcmp(mixer->driver, prev_driver) == 0) {
|
||||
force_vol_l = mixer->vol_l;
|
||||
force_vol_r = mixer->vol_r;
|
||||
}
|
||||
|
||||
// Set mute if we disabled it on uninit last time.
|
||||
if (mixer->muted_by_us)
|
||||
force_mute = 1;
|
||||
|
||||
// Set parameters from command line.
|
||||
if (opts->mixer_init_volume >= 0)
|
||||
force_vol_l = force_vol_r = opts->mixer_init_volume;
|
||||
if (opts->mixer_init_mute >= 0)
|
||||
force_mute = opts->mixer_init_mute;
|
||||
|
||||
// Set parameters from playback resume.
|
||||
char *data = mixer->opts->mixer_restore_volume_data;
|
||||
if (!mixer->persistent_volume && data && data[0]) {
|
||||
char drv[40];
|
||||
float v_l, v_r;
|
||||
int m;
|
||||
if (sscanf(data, "%39[^:]:%f:%f:%d", drv, &v_l, &v_r, &m) == 4) {
|
||||
if (strcmp(mixer->driver, drv) == 0) {
|
||||
force_vol_l = v_l;
|
||||
force_vol_r = v_r;
|
||||
force_mute = !!m;
|
||||
MP_DBG(mixer, "Restoring volume from resume config.\n");
|
||||
}
|
||||
}
|
||||
talloc_free(mixer->opts->mixer_restore_volume_data);
|
||||
mixer->opts->mixer_restore_volume_data = NULL;
|
||||
}
|
||||
|
||||
// Using --volume should not reset the volume on every file (i.e. reinit),
|
||||
// OTOH mpv --{ --volume 10 f1.mkv --} --{ --volume 20 f2.mkv --} must work.
|
||||
// Resetting the option volumes to "auto" (-1) is easiest. If file local
|
||||
// options (as shown above) are used, the option handler code will reset
|
||||
// them to other values, and force the volume to be reset as well.
|
||||
opts->mixer_init_volume = -1;
|
||||
opts->mixer_init_mute = -1;
|
||||
|
||||
checkvolume(mixer);
|
||||
if (force_vol_l >= 0 && force_vol_r >= 0) {
|
||||
MP_DBG(mixer, "Restoring previous volume.\n");
|
||||
mixer_setvolume(mixer, force_vol_l, force_vol_r);
|
||||
}
|
||||
if (force_mute >= 0) {
|
||||
MP_DBG(mixer, "Restoring previous mute toggle.\n");
|
||||
mixer_setmute(mixer, force_mute);
|
||||
}
|
||||
}
|
||||
|
||||
// Called after the audio filter chain is built or rebuilt.
|
||||
// (Can be called multiple times, even without mixer_uninit() in-between.)
|
||||
void mixer_reinit_audio(struct mixer *mixer, struct ao *ao, struct af_stream *af)
|
||||
void mixer_reinit_audio(struct mixer *mixer, struct af_stream *af)
|
||||
{
|
||||
if (!ao || !af)
|
||||
return;
|
||||
mixer->ao = ao;
|
||||
mixer->af = af;
|
||||
if (!af)
|
||||
return;
|
||||
|
||||
MP_DBG(mixer, "Reinit...\n");
|
||||
if (mixer->opts->softvol == SOFTVOL_NO)
|
||||
MP_ERR(mixer, "--softvol=no is not supported anymore.\n");
|
||||
|
||||
probe_softvol(mixer);
|
||||
restore_volume(mixer);
|
||||
mixer_update_volume(mixer);
|
||||
|
||||
if (mixer->balance != 0)
|
||||
mixer_setbalance(mixer, mixer->balance);
|
||||
@@ -380,24 +148,5 @@ void mixer_uninit_audio(struct mixer *mixer)
|
||||
if (!mixer->ao)
|
||||
return;
|
||||
|
||||
MP_DBG(mixer, "Uninit...\n");
|
||||
|
||||
checkvolume(mixer);
|
||||
if (mixer->muted_by_us && mixer->persistent_volume) {
|
||||
MP_DBG(mixer, "Draining.\n");
|
||||
/* Current audio output API combines playing the remaining buffered
|
||||
* audio and uninitializing the AO into one operation, even though
|
||||
* ideally unmute would happen between those two steps. We can't do
|
||||
* volume changes after uninitialization, but we don't want the
|
||||
* remaining audio to play at full volume either. Thus this
|
||||
* workaround to drop remaining audio first. */
|
||||
ao_reset(mixer->ao);
|
||||
mixer_setmute(mixer, false);
|
||||
/* We remember mute status and re-enable it if we play more audio
|
||||
* in the same process. */
|
||||
mixer->muted_by_us = true;
|
||||
}
|
||||
mixer->ao = NULL;
|
||||
mixer->af = NULL;
|
||||
mixer->softvol = false;
|
||||
}
|
||||
+2
-9
@@ -33,18 +33,11 @@ struct af_stream;
|
||||
struct mixer;
|
||||
|
||||
struct mixer *mixer_init(void *talloc_ctx, struct mpv_global *global);
|
||||
void mixer_reinit_audio(struct mixer *mixer, struct ao *ao, struct af_stream *af);
|
||||
void mixer_reinit_audio(struct mixer *mixer, struct af_stream *af);
|
||||
void mixer_uninit_audio(struct mixer *mixer);
|
||||
bool mixer_audio_initialized(struct mixer *mixer);
|
||||
void mixer_getvolume(struct mixer *mixer, float *l, float *r);
|
||||
void mixer_setvolume(struct mixer *mixer, float l, float r);
|
||||
void mixer_addvolume(struct mixer *mixer, float step);
|
||||
void mixer_getbothvolume(struct mixer *mixer, float *b);
|
||||
void mixer_setmute(struct mixer *mixer, bool mute);
|
||||
bool mixer_getmute(struct mixer *mixer);
|
||||
void mixer_update_volume(struct mixer *mixer);
|
||||
void mixer_getbalance(struct mixer *mixer, float *bal);
|
||||
void mixer_setbalance(struct mixer *mixer, float bal);
|
||||
float mixer_getmaxvolume(struct mixer *mixer);
|
||||
char *mixer_get_volume_restore_data(struct mixer *mixer);
|
||||
|
||||
#endif /* MPLAYER_MIXER_H */
|
||||
+2
-3
@@ -490,10 +490,9 @@ struct ao_hotplug *ao_hotplug_create(struct mpv_global *global,
|
||||
static void get_devices(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
int num = list->num_devices;
|
||||
if (ao->driver->list_devs)
|
||||
if (ao->driver->list_devs) {
|
||||
ao->driver->list_devs(ao, list);
|
||||
// Add at least a default entry
|
||||
if (list->num_devices == num) {
|
||||
} else {
|
||||
char name[80] = "Default";
|
||||
if (num > 1)
|
||||
mp_snprintf_cat(name, sizeof(name), " (%s)", ao->driver->name);
|
||||
|
||||
+69
-9
@@ -58,6 +58,8 @@ struct priv {
|
||||
snd_pcm_uframes_t buffersize;
|
||||
snd_pcm_uframes_t outburst;
|
||||
|
||||
snd_output_t *output;
|
||||
|
||||
char *cfg_device;
|
||||
char *cfg_mixer_device;
|
||||
char *cfg_mixer_name;
|
||||
@@ -477,6 +479,22 @@ static int set_chmap(struct ao *ao, struct mp_chmap *dev_chmap, int num_channels
|
||||
|
||||
#endif /* else HAVE_CHMAP_API */
|
||||
|
||||
static void dump_hw_params(struct ao *ao, int msglevel, const char *msg,
|
||||
snd_pcm_hw_params_t *hw_params)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_hw_params_dump(hw_params, p->output);
|
||||
CHECK_ALSA_WARN("Dump hwparams error");
|
||||
|
||||
char *tmp = NULL;
|
||||
size_t tmp_s = snd_output_buffer_string(p->output, &tmp);
|
||||
if (tmp)
|
||||
mp_msg(ao->log, msglevel, "%s---\n%.*s---\n", msg, (int)tmp_s, tmp);
|
||||
snd_output_flush(p->output);
|
||||
}
|
||||
|
||||
static int map_iec958_srate(int srate)
|
||||
{
|
||||
switch (srate) {
|
||||
@@ -520,7 +538,7 @@ static char *append_params(void *ta_parent, const char *device, const char *p)
|
||||
abort();
|
||||
}
|
||||
|
||||
static int try_open_device(struct ao *ao, const char *device)
|
||||
static int try_open_device(struct ao *ao, const char *device, int mode)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
@@ -534,7 +552,7 @@ static int try_open_device(struct ao *ao, const char *device)
|
||||
map_iec958_srate(ao->samplerate));
|
||||
const char *ac3_device = append_params(tmp, device, params);
|
||||
MP_VERBOSE(ao, "opening device '%s' => '%s'\n", device, ac3_device);
|
||||
err = snd_pcm_open(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
err = snd_pcm_open(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, mode);
|
||||
if (err < 0) {
|
||||
// Some spdif-capable devices do not accept the AES0 parameter,
|
||||
// and instead require the iec958 pseudo-device (they will play
|
||||
@@ -547,13 +565,13 @@ static int try_open_device(struct ao *ao, const char *device)
|
||||
MP_VERBOSE(ao, "got error %d; opening iec fallback device '%s'\n",
|
||||
err, ac3_device);
|
||||
err = snd_pcm_open
|
||||
(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, mode);
|
||||
}
|
||||
}
|
||||
talloc_free(tmp);
|
||||
} else {
|
||||
MP_VERBOSE(ao, "opening device '%s'\n", device);
|
||||
err = snd_pcm_open(&p->alsa, device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
err = snd_pcm_open(&p->alsa, device, SND_PCM_STREAM_PLAYBACK, mode);
|
||||
}
|
||||
|
||||
return err;
|
||||
@@ -563,6 +581,10 @@ static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (p->output)
|
||||
snd_output_close(p->output);
|
||||
p->output = NULL;
|
||||
|
||||
if (p->alsa) {
|
||||
int err;
|
||||
|
||||
@@ -574,20 +596,35 @@ static void uninit(struct ao *ao)
|
||||
alsa_error: ;
|
||||
}
|
||||
|
||||
static int init_device(struct ao *ao)
|
||||
#define INIT_DEVICE_ERR_GENERIC -1
|
||||
#define INIT_DEVICE_ERR_HWPARAMS -2
|
||||
static int init_device(struct ao *ao, int mode)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int ret = INIT_DEVICE_ERR_GENERIC;
|
||||
char *tmp;
|
||||
size_t tmp_s;
|
||||
int err;
|
||||
|
||||
err = snd_output_buffer_open(&p->output);
|
||||
CHECK_ALSA_ERROR("Unable to create output buffer");
|
||||
|
||||
const char *device = "default";
|
||||
if (ao->device)
|
||||
device = ao->device;
|
||||
if (p->cfg_device && p->cfg_device[0])
|
||||
device = p->cfg_device;
|
||||
|
||||
err = try_open_device(ao, device);
|
||||
err = try_open_device(ao, device, mode);
|
||||
CHECK_ALSA_ERROR("Playback open error");
|
||||
|
||||
err = snd_pcm_dump(p->alsa, p->output);
|
||||
CHECK_ALSA_WARN("Dump PCM error");
|
||||
tmp_s = snd_output_buffer_string(p->output, &tmp);
|
||||
if (tmp)
|
||||
MP_DBG(ao, "PCM setup:\n---\n%.*s---\n", (int)tmp_s, tmp);
|
||||
snd_output_flush(p->output);
|
||||
|
||||
err = snd_pcm_nonblock(p->alsa, 0);
|
||||
CHECK_ALSA_WARN("Unable to set blocking mode");
|
||||
|
||||
@@ -597,12 +634,15 @@ static int init_device(struct ao *ao)
|
||||
err = snd_pcm_hw_params_any(p->alsa, alsa_hwparams);
|
||||
CHECK_ALSA_ERROR("Unable to get initial parameters");
|
||||
|
||||
dump_hw_params(ao, MSGL_DEBUG, "Start HW params:\n", alsa_hwparams);
|
||||
|
||||
// Some ALSA drivers have broken delay reporting, so disable the ALSA
|
||||
// resampling plugin by default.
|
||||
if (!p->cfg_resample) {
|
||||
err = snd_pcm_hw_params_set_rate_resample(p->alsa, alsa_hwparams, 0);
|
||||
CHECK_ALSA_ERROR("Unable to disable resampling");
|
||||
}
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after rate:\n", alsa_hwparams);
|
||||
|
||||
snd_pcm_access_t access = af_fmt_is_planar(ao->format)
|
||||
? SND_PCM_ACCESS_RW_NONINTERLEAVED
|
||||
@@ -614,6 +654,7 @@ static int init_device(struct ao *ao)
|
||||
err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access);
|
||||
}
|
||||
CHECK_ALSA_ERROR("Unable to set access type");
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after access:\n", alsa_hwparams);
|
||||
|
||||
bool found_format = false;
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
@@ -637,6 +678,7 @@ static int init_device(struct ao *ao)
|
||||
|
||||
err = snd_pcm_hw_params_set_format(p->alsa, alsa_hwparams, p->alsa_fmt);
|
||||
CHECK_ALSA_ERROR("Unable to set format");
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after format:\n", alsa_hwparams);
|
||||
|
||||
struct mp_chmap dev_chmap = ao->channels;
|
||||
if (af_fmt_is_spdif(ao->format) || p->cfg_ignore_chmap) {
|
||||
@@ -656,6 +698,7 @@ static int init_device(struct ao *ao)
|
||||
err = snd_pcm_hw_params_set_channels_near
|
||||
(p->alsa, alsa_hwparams, &num_channels);
|
||||
CHECK_ALSA_ERROR("Unable to set channels");
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after channels:\n", alsa_hwparams);
|
||||
|
||||
if (num_channels > MP_NUM_CHANNELS) {
|
||||
MP_FATAL(ao, "Too many audio channels (%d).\n", num_channels);
|
||||
@@ -665,6 +708,7 @@ static int init_device(struct ao *ao)
|
||||
err = snd_pcm_hw_params_set_rate_near
|
||||
(p->alsa, alsa_hwparams, &ao->samplerate, NULL);
|
||||
CHECK_ALSA_ERROR("Unable to set samplerate-2");
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after rate-2:\n", alsa_hwparams);
|
||||
|
||||
snd_pcm_hw_params_t *hwparams_backup;
|
||||
snd_pcm_hw_params_alloca(&hwparams_backup);
|
||||
@@ -682,9 +726,14 @@ static int init_device(struct ao *ao)
|
||||
if (err < 0)
|
||||
snd_pcm_hw_params_copy(alsa_hwparams, hwparams_backup);
|
||||
|
||||
dump_hw_params(ao, MSGL_V, "Going to set final HW params:\n", alsa_hwparams);
|
||||
|
||||
/* finally install hardware parameters */
|
||||
err = snd_pcm_hw_params(p->alsa, alsa_hwparams);
|
||||
ret = INIT_DEVICE_ERR_HWPARAMS;
|
||||
CHECK_ALSA_ERROR("Unable to set hw-parameters");
|
||||
ret = INIT_DEVICE_ERR_GENERIC;
|
||||
dump_hw_params(ao, MSGL_DEBUG, "Final HW params:\n", alsa_hwparams);
|
||||
|
||||
if (set_chmap(ao, &dev_chmap, num_channels) < 0)
|
||||
goto alsa_error;
|
||||
@@ -740,7 +789,7 @@ static int init_device(struct ao *ao)
|
||||
|
||||
alsa_error:
|
||||
uninit(ao);
|
||||
return -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
@@ -751,7 +800,18 @@ static int init(struct ao *ao)
|
||||
|
||||
MP_VERBOSE(ao, "using ALSA version: %s\n", snd_asoundlib_version());
|
||||
|
||||
int r = init_device(ao);
|
||||
int mode = 0;
|
||||
int r = init_device(ao, mode);
|
||||
if (r == INIT_DEVICE_ERR_HWPARAMS) {
|
||||
// With some drivers, ALSA appears to be unable to set valid hwparams,
|
||||
// but they work if at least SND_PCM_NO_AUTO_FORMAT is set. Also, it
|
||||
// appears you can set this flag only on opening a device, thus there
|
||||
// is the need to retry opening the device.
|
||||
MP_WARN(ao, "Attempting to work around even more ALSA bugs...\n");
|
||||
mode |= SND_PCM_NO_AUTO_CHANNELS | SND_PCM_NO_AUTO_FORMAT |
|
||||
SND_PCM_NO_AUTO_RESAMPLE;
|
||||
r = init_device(ao, mode);
|
||||
}
|
||||
|
||||
// Sometimes, ALSA will advertise certain chmaps, but it's not possible to
|
||||
// set them. This can happen with dmix: as of alsa 1.0.29, dmix can do
|
||||
@@ -773,7 +833,7 @@ static int init(struct ao *ao)
|
||||
MP_VERBOSE(ao, "Working around braindead dmix multichannel behavior.\n");
|
||||
uninit(ao);
|
||||
ao->channels = without_na;
|
||||
r = init_device(ao);
|
||||
r = init_device(ao, mode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -133,11 +133,6 @@ static bool reinit_device(struct ao *ao) {
|
||||
OSStatus err = ca_select_device(ao, ao->device, &p->device);
|
||||
CHECK_CA_ERROR("failed to select device");
|
||||
|
||||
char *uid;
|
||||
err = CA_GET_STR(p->device, kAudioDevicePropertyDeviceUID, &uid);
|
||||
CHECK_CA_ERROR("failed to get device UID");
|
||||
ao->detected_device = talloc_steal(ao, uid);
|
||||
|
||||
return true;
|
||||
|
||||
coreaudio_error:
|
||||
|
||||
@@ -114,6 +114,13 @@ OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
|
||||
kAudioObjectSystemObject, &p_addr, 0, 0, &size, &v);
|
||||
CFRelease(uid);
|
||||
CHECK_CA_ERROR("unable to query for device UID");
|
||||
|
||||
uint32_t is_alive = 1;
|
||||
err = CA_GET(*device, kAudioDevicePropertyDeviceIsAlive, &is_alive);
|
||||
CHECK_CA_ERROR("could not check whether device is alive (invalid device?)");
|
||||
|
||||
if (!is_alive)
|
||||
MP_WARN(ao, "device is not alive!\n");
|
||||
} else {
|
||||
// device not set by user, get the default one
|
||||
err = CA_GET(kAudioObjectSystemObject,
|
||||
@@ -323,6 +330,9 @@ bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream)
|
||||
|
||||
for (int i = 0; i < n_formats; i++) {
|
||||
AudioStreamBasicDescription asbd = formats[i].mFormat;
|
||||
|
||||
ca_print_asbd(ao, "- ", &asbd);
|
||||
|
||||
if (ca_formatid_is_compressed(asbd.mFormatID)) {
|
||||
talloc_free(formats);
|
||||
return true;
|
||||
@@ -334,30 +344,6 @@ coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ca_device_supports_compressed(struct ao *ao, AudioDeviceID device)
|
||||
{
|
||||
AudioStreamID *streams = NULL;
|
||||
size_t n_streams;
|
||||
|
||||
/* Retrieve all the output streams. */
|
||||
OSStatus err =
|
||||
CA_GET_ARY_O(device, kAudioDevicePropertyStreams, &streams, &n_streams);
|
||||
|
||||
CHECK_CA_ERROR("could not get number of streams.");
|
||||
|
||||
for (int i = 0; i < n_streams; i++) {
|
||||
if (ca_stream_supports_compressed(ao, streams[i])) {
|
||||
talloc_free(streams);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid)
|
||||
{
|
||||
*pid = getpid();
|
||||
|
||||
@@ -65,7 +65,6 @@ bool ca_asbd_is_better(AudioStreamBasicDescription *req,
|
||||
int64_t ca_frames_to_us(struct ao *ao, uint32_t frames);
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts);
|
||||
|
||||
bool ca_device_supports_compressed(struct ao *ao, AudioDeviceID device);
|
||||
bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream);
|
||||
OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid);
|
||||
OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid);
|
||||
|
||||
+155
-110
@@ -39,9 +39,8 @@
|
||||
#include "common/encode_lavc.h"
|
||||
|
||||
struct priv {
|
||||
uint8_t *buffer;
|
||||
size_t buffer_size;
|
||||
AVStream *stream;
|
||||
AVCodecContext *codec;
|
||||
int pcmhack;
|
||||
int aframesize;
|
||||
int aframecount;
|
||||
@@ -98,15 +97,14 @@ static int init(struct ao *ao)
|
||||
|
||||
pthread_mutex_lock(&ao->encode_lavc_ctx->lock);
|
||||
|
||||
ac->stream = encode_lavc_alloc_stream(ao->encode_lavc_ctx,
|
||||
AVMEDIA_TYPE_AUDIO);
|
||||
|
||||
if (!ac->stream) {
|
||||
MP_ERR(ao, "could not get a new audio stream\n");
|
||||
goto fail;
|
||||
if (encode_lavc_alloc_stream(ao->encode_lavc_ctx,
|
||||
AVMEDIA_TYPE_AUDIO,
|
||||
&ac->stream, &ac->codec) < 0) {
|
||||
MP_ERR(ao, "could not get a new audio stream\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec = encode_lavc_get_codec(ao->encode_lavc_ctx, ac->stream);
|
||||
codec = ao->encode_lavc_ctx->ac;
|
||||
|
||||
int samplerate = af_select_best_samplerate(ao->samplerate,
|
||||
codec->supported_samplerates);
|
||||
@@ -118,46 +116,38 @@ static int init(struct ao *ao)
|
||||
// Using codec->time_bvase is deprecated, but needed for older lavf.
|
||||
ac->stream->time_base.num = 1;
|
||||
ac->stream->time_base.den = ao->samplerate;
|
||||
ac->stream->codec->time_base.num = 1;
|
||||
ac->stream->codec->time_base.den = ao->samplerate;
|
||||
ac->codec->time_base.num = 1;
|
||||
ac->codec->time_base.den = ao->samplerate;
|
||||
|
||||
ac->stream->codec->sample_rate = ao->samplerate;
|
||||
ac->codec->sample_rate = ao->samplerate;
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
goto fail;
|
||||
mp_chmap_reorder_to_lavc(&ao->channels);
|
||||
ac->stream->codec->channels = ao->channels.num;
|
||||
ac->stream->codec->channel_layout = mp_chmap_to_lavc(&ao->channels);
|
||||
ac->codec->channels = ao->channels.num;
|
||||
ac->codec->channel_layout = mp_chmap_to_lavc(&ao->channels);
|
||||
|
||||
ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_NONE;
|
||||
ac->codec->sample_fmt = AV_SAMPLE_FMT_NONE;
|
||||
|
||||
select_format(ao, codec);
|
||||
|
||||
ac->sample_size = af_fmt_to_bytes(ao->format);
|
||||
ac->stream->codec->sample_fmt = af_to_avformat(ao->format);
|
||||
ac->stream->codec->bits_per_raw_sample = ac->sample_size * 8;
|
||||
ac->codec->sample_fmt = af_to_avformat(ao->format);
|
||||
ac->codec->bits_per_raw_sample = ac->sample_size * 8;
|
||||
|
||||
if (encode_lavc_open_codec(ao->encode_lavc_ctx, ac->stream) < 0)
|
||||
if (encode_lavc_open_codec(ao->encode_lavc_ctx, ac->codec) < 0)
|
||||
goto fail;
|
||||
|
||||
ac->pcmhack = 0;
|
||||
if (ac->stream->codec->frame_size <= 1)
|
||||
ac->pcmhack = av_get_bits_per_sample(ac->stream->codec->codec_id) / 8;
|
||||
if (ac->codec->frame_size <= 1)
|
||||
ac->pcmhack = av_get_bits_per_sample(ac->codec->codec_id) / 8;
|
||||
|
||||
if (ac->pcmhack) {
|
||||
if (ac->pcmhack)
|
||||
ac->aframesize = 16384; // "enough"
|
||||
ac->buffer_size =
|
||||
ac->aframesize * ac->pcmhack * ao->channels.num * 2 + 200;
|
||||
} else {
|
||||
ac->aframesize = ac->stream->codec->frame_size;
|
||||
ac->buffer_size =
|
||||
ac->aframesize * ac->sample_size * ao->channels.num * 2 + 200;
|
||||
}
|
||||
if (ac->buffer_size < FF_MIN_BUFFER_SIZE)
|
||||
ac->buffer_size = FF_MIN_BUFFER_SIZE;
|
||||
ac->buffer = talloc_size(ac, ac->buffer_size);
|
||||
else
|
||||
ac->aframesize = ac->codec->frame_size;
|
||||
|
||||
// enough frames for at least 0.25 seconds
|
||||
ac->framecount = ceil(ao->samplerate * 0.25 / ac->aframesize);
|
||||
@@ -182,7 +172,7 @@ fail:
|
||||
}
|
||||
|
||||
// close audio device
|
||||
static int encode(struct ao *ao, double apts, void **data);
|
||||
static void encode(struct ao *ao, double apts, void **data);
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
@@ -199,12 +189,12 @@ static void uninit(struct ao *ao)
|
||||
return;
|
||||
}
|
||||
|
||||
if (ac->buffer) {
|
||||
if (ac->stream) {
|
||||
double outpts = ac->expected_next_pts;
|
||||
if (!ectx->options->rawts && ectx->options->copyts)
|
||||
outpts += ectx->discontinuity_pts_offset;
|
||||
outpts += encode_lavc_getoffset(ectx, ac->stream);
|
||||
while (encode(ao, outpts, NULL) > 0) ;
|
||||
outpts += encode_lavc_getoffset(ectx, ac->codec);
|
||||
encode(ao, outpts, NULL);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
@@ -220,24 +210,130 @@ static int get_space(struct ao *ao)
|
||||
return ac->aframesize * ac->framecount;
|
||||
}
|
||||
|
||||
static void write_packet(struct ao *ao, AVPacket *packet)
|
||||
{
|
||||
// TODO: Can we unify this with the equivalent video code path?
|
||||
struct priv *ac = ao->priv;
|
||||
|
||||
packet->stream_index = ac->stream->index;
|
||||
if (packet->pts != AV_NOPTS_VALUE) {
|
||||
packet->pts = av_rescale_q(packet->pts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
} else {
|
||||
// Do we need this at all? Better be safe than sorry...
|
||||
MP_WARN(ao, "encoder lost pts, why?\n");
|
||||
if (ac->savepts != MP_NOPTS_VALUE) {
|
||||
packet->pts = av_rescale_q(ac->savepts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
}
|
||||
if (packet->dts != AV_NOPTS_VALUE) {
|
||||
packet->dts = av_rescale_q(packet->dts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
if (packet->duration > 0) {
|
||||
packet->duration = av_rescale_q(packet->duration,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
|
||||
ac->savepts = AV_NOPTS_VALUE;
|
||||
|
||||
if (encode_lavc_write_frame(ao->encode_lavc_ctx,
|
||||
ac->stream, packet) < 0) {
|
||||
MP_ERR(ao, "error writing at %d %d/%d\n",
|
||||
(int) packet->pts,
|
||||
ac->stream->time_base.num,
|
||||
ac->stream->time_base.den);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void encode_audio_and_write(struct ao *ao, AVFrame *frame)
|
||||
{
|
||||
// TODO: Can we unify this with the equivalent video code path?
|
||||
struct priv *ac = ao->priv;
|
||||
AVPacket packet = {0};
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
int status = avcodec_send_frame(ac->codec, frame);
|
||||
if (status < 0) {
|
||||
MP_ERR(ao, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
ac->codec->time_base.num,
|
||||
ac->codec->time_base.den);
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
av_init_packet(&packet);
|
||||
status = avcodec_receive_packet(ac->codec, &packet);
|
||||
if (status == AVERROR(EAGAIN)) { // No more packets for now.
|
||||
if (frame == NULL) {
|
||||
MP_ERR(ao, "sent flush frame, got EAGAIN");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (status == AVERROR_EOF) { // No more packets, ever.
|
||||
if (frame != NULL) {
|
||||
MP_ERR(ao, "sent audio frame, got EOF");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (status < 0) {
|
||||
MP_ERR(ao, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
ac->codec->time_base.num,
|
||||
ac->codec->time_base.den);
|
||||
break;
|
||||
}
|
||||
if (frame) {
|
||||
if (ac->savepts == AV_NOPTS_VALUE)
|
||||
ac->savepts = frame->pts;
|
||||
}
|
||||
encode_lavc_write_stats(ao->encode_lavc_ctx, ac->codec);
|
||||
write_packet(ao, &packet);
|
||||
av_packet_unref(&packet);
|
||||
}
|
||||
#else
|
||||
av_init_packet(&packet);
|
||||
int got_packet = 0;
|
||||
int status = avcodec_encode_audio2(ac->codec, &packet, frame, &got_packet);
|
||||
if (status < 0) {
|
||||
MP_ERR(ao, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
ac->codec->time_base.num,
|
||||
ac->codec->time_base.den);
|
||||
return;
|
||||
}
|
||||
if (!got_packet) {
|
||||
return;
|
||||
}
|
||||
if (frame) {
|
||||
if (ac->savepts == AV_NOPTS_VALUE)
|
||||
ac->savepts = frame->pts;
|
||||
}
|
||||
encode_lavc_write_stats(ao->encode_lavc_ctx, ac->codec);
|
||||
write_packet(ao, &packet);
|
||||
av_packet_unref(&packet);
|
||||
#endif
|
||||
}
|
||||
|
||||
// must get exactly ac->aframesize amount of data
|
||||
static int encode(struct ao *ao, double apts, void **data)
|
||||
static void encode(struct ao *ao, double apts, void **data)
|
||||
{
|
||||
AVPacket packet;
|
||||
struct priv *ac = ao->priv;
|
||||
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
|
||||
double realapts = ac->aframecount * (double) ac->aframesize /
|
||||
ao->samplerate;
|
||||
int status, gotpacket;
|
||||
|
||||
ac->aframecount++;
|
||||
|
||||
if (data)
|
||||
ectx->audio_pts_offset = realapts - apts;
|
||||
|
||||
av_init_packet(&packet);
|
||||
packet.data = ac->buffer;
|
||||
packet.size = ac->buffer_size;
|
||||
if(data) {
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
frame->format = af_to_avformat(ao->format);
|
||||
@@ -252,82 +348,29 @@ static int encode(struct ao *ao, double apts, void **data)
|
||||
|
||||
if (ectx->options->rawts || ectx->options->copyts) {
|
||||
// real audio pts
|
||||
frame->pts = floor(apts * ac->stream->codec->time_base.den / ac->stream->codec->time_base.num + 0.5);
|
||||
frame->pts = floor(apts * ac->codec->time_base.den / ac->codec->time_base.num + 0.5);
|
||||
} else {
|
||||
// audio playback time
|
||||
frame->pts = floor(realapts * ac->stream->codec->time_base.den / ac->stream->codec->time_base.num + 0.5);
|
||||
frame->pts = floor(realapts * ac->codec->time_base.den / ac->codec->time_base.num + 0.5);
|
||||
}
|
||||
|
||||
int64_t frame_pts = av_rescale_q(frame->pts, ac->stream->codec->time_base, ac->worst_time_base);
|
||||
int64_t frame_pts = av_rescale_q(frame->pts, ac->codec->time_base, ac->worst_time_base);
|
||||
if (ac->lastpts != AV_NOPTS_VALUE && frame_pts <= ac->lastpts) {
|
||||
// this indicates broken video
|
||||
// (video pts failing to increase fast enough to match audio)
|
||||
MP_WARN(ao, "audio frame pts went backwards (%d <- %d), autofixed\n",
|
||||
(int)frame->pts, (int)ac->lastpts);
|
||||
frame_pts = ac->lastpts + 1;
|
||||
frame->pts = av_rescale_q(frame_pts, ac->worst_time_base, ac->stream->codec->time_base);
|
||||
frame->pts = av_rescale_q(frame_pts, ac->worst_time_base, ac->codec->time_base);
|
||||
}
|
||||
ac->lastpts = frame_pts;
|
||||
|
||||
frame->quality = ac->stream->codec->global_quality;
|
||||
status = avcodec_encode_audio2(ac->stream->codec, &packet, frame, &gotpacket);
|
||||
|
||||
if (!status) {
|
||||
if (ac->savepts == AV_NOPTS_VALUE)
|
||||
ac->savepts = frame->pts;
|
||||
}
|
||||
|
||||
frame->quality = ac->codec->global_quality;
|
||||
encode_audio_and_write(ao, frame);
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = avcodec_encode_audio2(ac->stream->codec, &packet, NULL, &gotpacket);
|
||||
}
|
||||
|
||||
if(status) {
|
||||
MP_ERR(ao, "error encoding\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!gotpacket)
|
||||
return 0;
|
||||
|
||||
MP_DBG(ao, "got pts %f (playback time: %f); out size: %d\n",
|
||||
apts, realapts, packet.size);
|
||||
|
||||
encode_lavc_write_stats(ao->encode_lavc_ctx, ac->stream);
|
||||
|
||||
packet.stream_index = ac->stream->index;
|
||||
|
||||
// Do we need this at all? Better be safe than sorry...
|
||||
if (packet.pts == AV_NOPTS_VALUE) {
|
||||
MP_WARN(ao, "encoder lost pts, why?\n");
|
||||
if (ac->savepts != MP_NOPTS_VALUE)
|
||||
packet.pts = ac->savepts;
|
||||
}
|
||||
|
||||
if (packet.pts != AV_NOPTS_VALUE)
|
||||
packet.pts = av_rescale_q(packet.pts, ac->stream->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
|
||||
if (packet.dts != AV_NOPTS_VALUE)
|
||||
packet.dts = av_rescale_q(packet.dts, ac->stream->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
|
||||
if(packet.duration > 0)
|
||||
packet.duration = av_rescale_q(packet.duration, ac->stream->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
|
||||
ac->savepts = AV_NOPTS_VALUE;
|
||||
|
||||
if (encode_lavc_write_frame(ao->encode_lavc_ctx, &packet) < 0) {
|
||||
MP_ERR(ao, "error writing at %f %f/%f\n",
|
||||
realapts, (double) ac->stream->time_base.num,
|
||||
(double) ac->stream->time_base.den);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return packet.size;
|
||||
encode_audio_and_write(ao, NULL);
|
||||
}
|
||||
|
||||
// this should round samples down to frame sizes
|
||||
@@ -377,22 +420,22 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
}
|
||||
|
||||
if (ac->worst_time_base.den == 0) {
|
||||
//if (ac->stream->codec->time_base.num / ac->stream->codec->time_base.den >= ac->stream->time_base.num / ac->stream->time_base.den)
|
||||
if (ac->stream->codec->time_base.num * (double) ac->stream->time_base.den >=
|
||||
ac->stream->time_base.num * (double) ac->stream->codec->time_base.den) {
|
||||
//if (ac->codec->time_base.num / ac->codec->time_base.den >= ac->stream->time_base.num / ac->stream->time_base.den)
|
||||
if (ac->codec->time_base.num * (double) ac->stream->time_base.den >=
|
||||
ac->stream->time_base.num * (double) ac->codec->time_base.den) {
|
||||
MP_VERBOSE(ao, "NOTE: using codec time base (%d/%d) for pts "
|
||||
"adjustment; the stream base (%d/%d) is not worse.\n",
|
||||
(int)ac->stream->codec->time_base.num,
|
||||
(int)ac->stream->codec->time_base.den,
|
||||
(int)ac->codec->time_base.num,
|
||||
(int)ac->codec->time_base.den,
|
||||
(int)ac->stream->time_base.num,
|
||||
(int)ac->stream->time_base.den);
|
||||
ac->worst_time_base = ac->stream->codec->time_base;
|
||||
ac->worst_time_base = ac->codec->time_base;
|
||||
ac->worst_time_base_is_stream = 0;
|
||||
} else {
|
||||
MP_WARN(ao, "NOTE: not using codec time base (%d/%d) for pts "
|
||||
"adjustment; the stream base (%d/%d) is worse.\n",
|
||||
(int)ac->stream->codec->time_base.num,
|
||||
(int)ac->stream->codec->time_base.den,
|
||||
(int)ac->codec->time_base.num,
|
||||
(int)ac->codec->time_base.den,
|
||||
(int)ac->stream->time_base.num,
|
||||
(int)ac->stream->time_base.den);
|
||||
ac->worst_time_base = ac->stream->time_base;
|
||||
@@ -437,7 +480,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
}
|
||||
|
||||
// Shift pts by the pts offset first.
|
||||
outpts += encode_lavc_getoffset(ectx, ac->stream);
|
||||
outpts += encode_lavc_getoffset(ectx, ac->codec);
|
||||
|
||||
while (samples - bufpos >= ac->aframesize) {
|
||||
void *start[MP_NUM_CHANNELS] = {0};
|
||||
@@ -492,3 +535,5 @@ const struct ao_driver audio_out_lavc = {
|
||||
.play = play,
|
||||
.drain = drain,
|
||||
};
|
||||
|
||||
// vim: sw=4 ts=4 et tw=80
|
||||
@@ -46,7 +46,6 @@ struct priv {
|
||||
static const int fmtmap[][2] = {
|
||||
{ AF_FORMAT_U8, SL_PCMSAMPLEFORMAT_FIXED_8 },
|
||||
{ AF_FORMAT_S16, SL_PCMSAMPLEFORMAT_FIXED_16 },
|
||||
{ AF_FORMAT_S32, SL_PCMSAMPLEFORMAT_FIXED_32 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
||||
+8
-1
@@ -462,7 +462,7 @@ static int init(struct ao *ao)
|
||||
p->buffersize = 0;
|
||||
memset(data, 0, p->outburst);
|
||||
while (p->buffersize < 0x40000 && device_writable(ao) > 0) {
|
||||
write(p->audio_fd, data, p->outburst);
|
||||
(void)write(p->audio_fd, data, p->outburst);
|
||||
p->buffersize += p->outburst;
|
||||
}
|
||||
free(data);
|
||||
@@ -612,6 +612,12 @@ static int audio_wait(struct ao *ao, pthread_mutex_t *lock)
|
||||
return r;
|
||||
}
|
||||
|
||||
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
if (stat(PATH_DEV_DSP, &(struct stat){0}) == 0)
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){"", "Default"});
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_oss = {
|
||||
@@ -629,6 +635,7 @@ const struct ao_driver audio_out_oss = {
|
||||
.drain = drain,
|
||||
.wait = audio_wait,
|
||||
.wakeup = ao_wakeup_poll,
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.audio_fd = -1,
|
||||
|
||||
@@ -132,7 +132,7 @@ static bool thread_feed(struct ao *ao)
|
||||
mp_time_us() + (int64_t)llrint(delay_us));
|
||||
|
||||
// note, we can't use ao_read_data return value here since we already
|
||||
// commited to frame_count above in the GetBuffer call
|
||||
// committed to frame_count above in the GetBuffer call
|
||||
hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient,
|
||||
frame_count, 0);
|
||||
EXIT_ON_ERROR(hr);
|
||||
@@ -270,7 +270,7 @@ static void uninit(struct ao *ao)
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
MP_DBG(ao, "Init wasapi\n");
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
|
||||
struct wasapi_state *state = ao->priv;
|
||||
state->log = ao->log;
|
||||
@@ -469,7 +469,7 @@ static int hotplug_init(struct ao *ao)
|
||||
MP_DBG(ao, "Hotplug init\n");
|
||||
struct wasapi_state *state = ao->priv;
|
||||
state->log = ao->log;
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
HRESULT hr = wasapi_change_init(ao, true);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <initguid.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "ao_wasapi.h"
|
||||
@@ -94,7 +95,7 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceAdded(
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// maybe MPV can go over to the prefered device once it is plugged in?
|
||||
// maybe MPV can go over to the preferred device once it is plugged in?
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceRemoved(
|
||||
IMMNotificationClient *This,
|
||||
LPCWSTR pwstrDeviceId)
|
||||
|
||||
+19
-8
@@ -142,6 +142,7 @@ static void resume(struct ao *ao)
|
||||
static void drain(struct ao *ao)
|
||||
{
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
double maxbuffer = ao->buffer / (double)ao->samplerate + 1;
|
||||
|
||||
MP_VERBOSE(ao, "draining...\n");
|
||||
|
||||
@@ -151,14 +152,23 @@ static void drain(struct ao *ao)
|
||||
|
||||
p->final_chunk = true;
|
||||
wakeup_playthread(ao);
|
||||
while (p->still_playing && mp_audio_buffer_samples(p->buffer) > 0)
|
||||
pthread_cond_wait(&p->wakeup, &p->lock);
|
||||
|
||||
// Wait until everything is done. Since the audio API (especially ALSA)
|
||||
// can't be trusted to do this right, and we're hard-blocking here, apply
|
||||
// an upper bound timeout.
|
||||
struct timespec until = mp_rel_time_to_timespec(maxbuffer);
|
||||
while (p->still_playing && mp_audio_buffer_samples(p->buffer) > 0) {
|
||||
if (pthread_cond_timedwait(&p->wakeup, &p->lock, &until)) {
|
||||
MP_WARN(ao, "Draining is taking too long, aborting.\n");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (ao->driver->drain) {
|
||||
ao->driver->drain(ao);
|
||||
} else {
|
||||
double time = unlocked_get_delay(ao);
|
||||
mp_sleep_us(MPMIN(time, ao->buffer / (double)ao->samplerate + 1) * 1e6);
|
||||
mp_sleep_us(MPMIN(time, maxbuffer) * 1e6);
|
||||
}
|
||||
|
||||
done:
|
||||
@@ -292,11 +302,12 @@ static void ao_play_data(struct ao *ao)
|
||||
// If we just filled the AO completely (r == space), don't refill for a
|
||||
// while. Prevents wakeup feedback with byte-granular AOs.
|
||||
int needed = unlocked_get_space(ao);
|
||||
bool more = needed >= (r == space ? ao->device_buffer / 4 : 1) && !stuck;
|
||||
bool more = needed >= (r == space ? ao->device_buffer / 4 : 1) && !stuck &&
|
||||
!(flags & AOPLAY_FINAL_CHUNK);
|
||||
if (more)
|
||||
mp_input_wakeup(ao->input_ctx); // request more data
|
||||
MP_TRACE(ao, "in=%d flags=%d space=%d r=%d wa=%d needed=%d more=%d\n",
|
||||
max, flags, space, r, p->wait_on_ao, needed, more);
|
||||
MP_TRACE(ao, "in=%d flags=%d space=%d r=%d wa/pl=%d/%d needed=%d more=%d\n",
|
||||
max, flags, space, r, p->wait_on_ao, p->still_playing, needed, more);
|
||||
}
|
||||
|
||||
static void *playthread(void *arg)
|
||||
@@ -489,7 +500,7 @@ int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds,
|
||||
// flush the wakeup pipe contents - might "drown" some wakeups, but
|
||||
// that's ok for our use-case
|
||||
char buf[100];
|
||||
read(p->wakeup_pipe[0], buf, sizeof(buf));
|
||||
(void)read(p->wakeup_pipe[0], buf, sizeof(buf));
|
||||
}
|
||||
return (r >= 0 || r == -EINTR) ? wakeup : -1;
|
||||
}
|
||||
@@ -499,7 +510,7 @@ void ao_wakeup_poll(struct ao *ao)
|
||||
assert(ao->api == &ao_api_push);
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
|
||||
write(p->wakeup_pipe[1], &(char){0}, 1);
|
||||
(void)write(p->wakeup_pipe[1], &(char){0}, 1);
|
||||
}
|
||||
|
||||
#endif
|
||||
+7
-2
@@ -189,7 +189,7 @@ struct lib {
|
||||
unsigned runv;
|
||||
};
|
||||
|
||||
void print_libav_versions(struct mp_log *log, int v)
|
||||
bool print_libav_versions(struct mp_log *log, int v)
|
||||
{
|
||||
const struct lib libs[] = {
|
||||
{"libavutil", LIBAVUTIL_VERSION_INT, avutil_version()},
|
||||
@@ -207,17 +207,22 @@ void print_libav_versions(struct mp_log *log, int v)
|
||||
|
||||
mp_msg(log, v, "%s library versions:\n", LIB_PREFIX);
|
||||
|
||||
bool mismatch = false;
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(libs); n++) {
|
||||
const struct lib *l = &libs[n];
|
||||
mp_msg(log, v, " %-15s %d.%d.%d", l->name, V(l->buildv));
|
||||
if (l->buildv != l->runv)
|
||||
if (l->buildv != l->runv) {
|
||||
mp_msg(log, v, " (runtime %d.%d.%d)", V(l->runv));
|
||||
mismatch = true;
|
||||
}
|
||||
mp_msg(log, v, "\n");
|
||||
}
|
||||
|
||||
#if HAVE_AV_VERSION_INFO
|
||||
mp_msg(log, v, "%s version: %s\n", LIB_PREFIX, av_version_info());
|
||||
#endif
|
||||
|
||||
return !mismatch;
|
||||
}
|
||||
|
||||
#undef V
|
||||
+4
-1
@@ -1,8 +1,11 @@
|
||||
#ifndef MP_AV_LOG_H
|
||||
#define MP_AV_LOG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mpv_global;
|
||||
struct mp_log;
|
||||
void init_libav(struct mpv_global *global);
|
||||
void uninit_libav(struct mpv_global *global);
|
||||
void print_libav_versions(struct mp_log *log, int v);
|
||||
bool print_libav_versions(struct mp_log *log, int v);
|
||||
#endif
|
||||
+176
-148
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <libavutil/avutil.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "encode_lavc.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
@@ -291,32 +292,20 @@ int encode_lavc_start(struct encode_lavc_context *ctx)
|
||||
|
||||
CHECK_FAIL(ctx, 0);
|
||||
|
||||
if (ctx->expect_video) {
|
||||
unsigned i;
|
||||
for (i = 0; i < ctx->avc->nb_streams; ++i)
|
||||
if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
|
||||
break;
|
||||
if (i >= ctx->avc->nb_streams) {
|
||||
if (ctx->avc->oformat->video_codec != AV_CODEC_ID_NONE ||
|
||||
ctx->options->vcodec) {
|
||||
encode_lavc_fail(ctx,
|
||||
"no video stream succeeded - invalid codec?\n");
|
||||
return 0;
|
||||
}
|
||||
if (ctx->expect_video && ctx->vcc == NULL) {
|
||||
if (ctx->avc->oformat->video_codec != AV_CODEC_ID_NONE ||
|
||||
ctx->options->vcodec) {
|
||||
encode_lavc_fail(ctx,
|
||||
"no video stream succeeded - invalid codec?\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (ctx->expect_audio) {
|
||||
unsigned i;
|
||||
for (i = 0; i < ctx->avc->nb_streams; ++i)
|
||||
if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||
break;
|
||||
if (i >= ctx->avc->nb_streams) {
|
||||
if (ctx->avc->oformat->audio_codec != AV_CODEC_ID_NONE ||
|
||||
ctx->options->acodec) {
|
||||
encode_lavc_fail(ctx,
|
||||
"no audio stream succeeded - invalid codec?\n");
|
||||
return 0;
|
||||
}
|
||||
if (ctx->expect_audio && ctx->acc == NULL) {
|
||||
if (ctx->avc->oformat->audio_codec != AV_CODEC_ID_NONE ||
|
||||
ctx->options->acodec) {
|
||||
encode_lavc_fail(ctx,
|
||||
"no audio stream succeeded - invalid codec?\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,33 +376,38 @@ void encode_lavc_finish(struct encode_lavc_context *ctx)
|
||||
if (ctx->header_written > 0)
|
||||
av_write_trailer(ctx->avc); // this is allowed to fail
|
||||
|
||||
for (i = 0; i < ctx->avc->nb_streams; i++) {
|
||||
switch (ctx->avc->streams[i]->codec->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
if (ctx->twopass_bytebuffer_v) {
|
||||
char *stats = ctx->avc->streams[i]->codec->stats_out;
|
||||
if (stats)
|
||||
stream_write_buffer(ctx->twopass_bytebuffer_v,
|
||||
stats, strlen(stats));
|
||||
}
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
if (ctx->twopass_bytebuffer_a) {
|
||||
char *stats = ctx->avc->streams[i]->codec->stats_out;
|
||||
if (stats)
|
||||
stream_write_buffer(ctx->twopass_bytebuffer_a,
|
||||
stats, strlen(stats));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (ctx->vcc) {
|
||||
if (ctx->twopass_bytebuffer_v) {
|
||||
char *stats = ctx->vcc->stats_out;
|
||||
if (stats)
|
||||
stream_write_buffer(ctx->twopass_bytebuffer_v,
|
||||
stats, strlen(stats));
|
||||
}
|
||||
avcodec_close(ctx->avc->streams[i]->codec);
|
||||
talloc_free(ctx->avc->streams[i]->codec->stats_in);
|
||||
av_free(ctx->avc->streams[i]->codec);
|
||||
avcodec_close(ctx->vcc);
|
||||
talloc_free(ctx->vcc->stats_in);
|
||||
av_free(ctx->vcc);
|
||||
ctx->vcc = NULL;
|
||||
}
|
||||
|
||||
if (ctx->acc) {
|
||||
if (ctx->twopass_bytebuffer_a) {
|
||||
char *stats = ctx->acc->stats_out;
|
||||
if (stats)
|
||||
stream_write_buffer(ctx->twopass_bytebuffer_a,
|
||||
stats, strlen(stats));
|
||||
}
|
||||
avcodec_close(ctx->acc);
|
||||
talloc_free(ctx->acc->stats_in);
|
||||
av_free(ctx->acc);
|
||||
ctx->acc = NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < ctx->avc->nb_streams; i++) {
|
||||
av_free(ctx->avc->streams[i]->info);
|
||||
av_free(ctx->avc->streams[i]);
|
||||
}
|
||||
ctx->vst = NULL;
|
||||
ctx->ast = NULL;
|
||||
|
||||
if (ctx->twopass_bytebuffer_v) {
|
||||
free_stream(ctx->twopass_bytebuffer_v);
|
||||
@@ -437,6 +431,7 @@ void encode_lavc_finish(struct encode_lavc_context *ctx)
|
||||
}
|
||||
|
||||
av_free(ctx->avc);
|
||||
ctx->avc = NULL;
|
||||
}
|
||||
|
||||
ctx->finished = true;
|
||||
@@ -461,7 +456,9 @@ void encode_lavc_set_audio_pts(struct encode_lavc_context *ctx, double pts)
|
||||
|
||||
static void encode_2pass_prepare(struct encode_lavc_context *ctx,
|
||||
AVDictionary **dictp,
|
||||
AVStream *stream, struct stream **bytebuf,
|
||||
AVStream *stream,
|
||||
AVCodecContext *codec,
|
||||
struct stream **bytebuf,
|
||||
const char *prefix)
|
||||
{
|
||||
if (!*bytebuf) {
|
||||
@@ -476,7 +473,7 @@ static void encode_2pass_prepare(struct encode_lavc_context *ctx,
|
||||
if (!(*bytebuf = stream_open(buf, ctx->global))) {
|
||||
MP_WARN(ctx, "%s: could not open '%s', "
|
||||
"disabling 2-pass encoding at pass 2\n", prefix, buf);
|
||||
stream->codec->flags &= ~CODEC_FLAG_PASS2;
|
||||
codec->flags &= ~CODEC_FLAG_PASS2;
|
||||
set_to_avdictionary(ctx, dictp, "flags", "-pass2");
|
||||
} else {
|
||||
struct bstr content = stream_read_complete(*bytebuf, NULL,
|
||||
@@ -487,7 +484,7 @@ static void encode_2pass_prepare(struct encode_lavc_context *ctx,
|
||||
prefix, ctx->avc->filename);
|
||||
} else {
|
||||
content.start[content.len] = 0;
|
||||
stream->codec->stats_in = content.start;
|
||||
codec->stats_in = content.start;
|
||||
}
|
||||
free_stream(*bytebuf);
|
||||
*bytebuf = NULL;
|
||||
@@ -506,43 +503,55 @@ static void encode_2pass_prepare(struct encode_lavc_context *ctx,
|
||||
}
|
||||
}
|
||||
|
||||
AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
enum AVMediaType mt)
|
||||
int encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
enum AVMediaType mt,
|
||||
AVStream **stream_out,
|
||||
AVCodecContext **codec_out)
|
||||
{
|
||||
AVDictionaryEntry *de;
|
||||
AVStream *stream = NULL;
|
||||
char **p;
|
||||
int i;
|
||||
|
||||
CHECK_FAIL(ctx, NULL);
|
||||
*stream_out = NULL;
|
||||
*codec_out = NULL;
|
||||
|
||||
CHECK_FAIL(ctx, -1);
|
||||
|
||||
if (ctx->header_written)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < ctx->avc->nb_streams; ++i)
|
||||
if (ctx->avc->streams[i]->codec->codec_type == mt)
|
||||
// already have a stream of that type, this cannot really happen
|
||||
return NULL;
|
||||
return -1;
|
||||
|
||||
if (ctx->avc->nb_streams == 0) {
|
||||
// if this stream isn't stream #0, allocate a dummy stream first for
|
||||
// the next loop to use
|
||||
// the next call to use
|
||||
if (mt == AVMEDIA_TYPE_VIDEO && ctx->audio_first) {
|
||||
MP_INFO(ctx, "vo-lavc: preallocated audio stream for later use\n");
|
||||
avformat_new_stream(ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now
|
||||
ctx->ast = avformat_new_stream(
|
||||
ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now
|
||||
}
|
||||
if (mt == AVMEDIA_TYPE_AUDIO && ctx->video_first) {
|
||||
MP_INFO(ctx, "ao-lavc: preallocated video stream for later use\n");
|
||||
avformat_new_stream(ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now
|
||||
ctx->vst = avformat_new_stream(
|
||||
ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now
|
||||
}
|
||||
} else {
|
||||
// find possibly preallocated stream
|
||||
for (i = 0; i < ctx->avc->nb_streams; ++i)
|
||||
if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_UNKNOWN) // preallocated stream
|
||||
stream = ctx->avc->streams[i];
|
||||
}
|
||||
if (!stream)
|
||||
stream = avformat_new_stream(ctx->avc, NULL);
|
||||
|
||||
// already have a stream of that type (this cannot really happen)?
|
||||
switch (mt) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
if (ctx->vcc != NULL)
|
||||
return -1;
|
||||
if (ctx->vst == NULL)
|
||||
ctx->vst = avformat_new_stream(ctx->avc, NULL);
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
if (ctx->acc != NULL)
|
||||
return -1;
|
||||
if (ctx->ast == NULL)
|
||||
ctx->ast = avformat_new_stream(ctx->avc, NULL);
|
||||
break;
|
||||
default:
|
||||
encode_lavc_fail(ctx, "requested invalid stream type\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ctx->timebase.den == 0) {
|
||||
AVRational r;
|
||||
@@ -584,13 +593,18 @@ AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
ctx->options->vcodec) {
|
||||
encode_lavc_fail(ctx, "vo-lavc: encoder not found\n");
|
||||
}
|
||||
return NULL;
|
||||
return -1;
|
||||
}
|
||||
avcodec_get_context_defaults3(stream->codec, ctx->vc);
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
ctx->vcc = avcodec_alloc_context3(ctx->vc);
|
||||
#else
|
||||
avcodec_get_context_defaults3(ctx->vst->codec, ctx->vc);
|
||||
ctx->vcc = ctx->vst->codec;
|
||||
#endif
|
||||
|
||||
// Using codec->time_base is deprecated, but needed for older lavf.
|
||||
stream->time_base = ctx->timebase;
|
||||
stream->codec->time_base = ctx->timebase;
|
||||
ctx->vst->time_base = ctx->timebase;
|
||||
ctx->vcc->time_base = ctx->timebase;
|
||||
|
||||
ctx->voptions = NULL;
|
||||
|
||||
@@ -606,10 +620,12 @@ AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
set_to_avdictionary(ctx, &ctx->voptions, "flags", "+global_header");
|
||||
|
||||
encode_2pass_prepare(ctx, &ctx->voptions, stream,
|
||||
encode_2pass_prepare(ctx, &ctx->voptions, ctx->vst, ctx->vcc,
|
||||
&ctx->twopass_bytebuffer_v,
|
||||
"vo-lavc");
|
||||
break;
|
||||
*stream_out = ctx->vst;
|
||||
*codec_out = ctx->vcc;
|
||||
return 0;
|
||||
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
if (!ctx->ac) {
|
||||
@@ -617,15 +633,20 @@ AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
ctx->options->acodec) {
|
||||
encode_lavc_fail(ctx, "ao-lavc: encoder not found\n");
|
||||
}
|
||||
return NULL;
|
||||
return -1;
|
||||
}
|
||||
avcodec_get_context_defaults3(stream->codec, ctx->ac);
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
ctx->acc = avcodec_alloc_context3(ctx->ac);
|
||||
#else
|
||||
avcodec_get_context_defaults3(ctx->ast->codec, ctx->ac);
|
||||
ctx->acc = ctx->ast->codec;
|
||||
#endif
|
||||
|
||||
// Using codec->time_base is deprecated, but needed for older lavf.
|
||||
stream->time_base = ctx->timebase;
|
||||
stream->codec->time_base = ctx->timebase;
|
||||
ctx->ast->time_base = ctx->timebase;
|
||||
ctx->acc->time_base = ctx->timebase;
|
||||
|
||||
ctx->aoptions = NULL;
|
||||
ctx->aoptions = 0;
|
||||
|
||||
if (ctx->options->aopts)
|
||||
for (p = ctx->options->aopts; *p; ++p)
|
||||
@@ -639,49 +660,34 @@ AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
set_to_avdictionary(ctx, &ctx->aoptions, "flags", "+global_header");
|
||||
|
||||
encode_2pass_prepare(ctx, &ctx->aoptions, stream,
|
||||
encode_2pass_prepare(ctx, &ctx->aoptions, ctx->ast, ctx->acc,
|
||||
&ctx->twopass_bytebuffer_a,
|
||||
"ao-lavc");
|
||||
break;
|
||||
|
||||
default:
|
||||
encode_lavc_fail(ctx, "requested invalid stream type\n");
|
||||
return NULL;
|
||||
*stream_out = ctx->ast;
|
||||
*codec_out = ctx->acc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return stream;
|
||||
// Unreachable.
|
||||
return -1;
|
||||
}
|
||||
|
||||
AVCodec *encode_lavc_get_codec(struct encode_lavc_context *ctx,
|
||||
AVStream *stream)
|
||||
{
|
||||
CHECK_FAIL(ctx, NULL);
|
||||
|
||||
switch (stream->codec->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
return ctx->vc;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
return ctx->ac;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
int encode_lavc_open_codec(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec)
|
||||
{
|
||||
AVDictionaryEntry *de;
|
||||
int ret;
|
||||
|
||||
CHECK_FAIL(ctx, -1);
|
||||
|
||||
switch (stream->codec->codec_type) {
|
||||
switch (codec->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
MP_INFO(ctx, "Opening video encoder: %s [%s]\n",
|
||||
ctx->vc->long_name, ctx->vc->name);
|
||||
|
||||
if (ctx->vc->capabilities & CODEC_CAP_EXPERIMENTAL) {
|
||||
stream->codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
|
||||
codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
|
||||
MP_WARN(ctx, "\n\n"
|
||||
" ********************************************\n"
|
||||
" **** Experimental VIDEO codec selected! ****\n"
|
||||
@@ -701,7 +707,11 @@ int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
ctx->vc->name);
|
||||
}
|
||||
|
||||
ret = avcodec_open2(stream->codec, ctx->vc, &ctx->voptions);
|
||||
ret = avcodec_open2(codec, ctx->vc, &ctx->voptions);
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
if (ret >= 0)
|
||||
ret = avcodec_parameters_from_context(ctx->vst->codecpar, codec);
|
||||
#endif
|
||||
|
||||
// complain about all remaining options, then free the dict
|
||||
for (de = NULL; (de = av_dict_get(ctx->voptions, "", de,
|
||||
@@ -716,7 +726,7 @@ int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
ctx->ac->long_name, ctx->ac->name);
|
||||
|
||||
if (ctx->ac->capabilities & CODEC_CAP_EXPERIMENTAL) {
|
||||
stream->codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
|
||||
codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
|
||||
MP_WARN(ctx, "\n\n"
|
||||
" ********************************************\n"
|
||||
" **** Experimental AUDIO codec selected! ****\n"
|
||||
@@ -735,7 +745,12 @@ int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
"If none of this helps you, try another codec in place of %s.\n\n",
|
||||
ctx->ac->name);
|
||||
}
|
||||
ret = avcodec_open2(stream->codec, ctx->ac, &ctx->aoptions);
|
||||
|
||||
ret = avcodec_open2(codec, ctx->ac, &ctx->aoptions);
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
if (ret >= 0)
|
||||
ret = avcodec_parameters_from_context(ctx->ast->codecpar, codec);
|
||||
#endif
|
||||
|
||||
// complain about all remaining options, then free the dict
|
||||
for (de = NULL; (de = av_dict_get(ctx->aoptions, "", de,
|
||||
@@ -757,36 +772,43 @@ int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void encode_lavc_write_stats(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
void encode_lavc_write_stats(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec)
|
||||
{
|
||||
CHECK_FAIL(ctx, );
|
||||
|
||||
switch (stream->codec->codec_type) {
|
||||
switch (codec->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
if (ctx->twopass_bytebuffer_v)
|
||||
if (stream->codec->stats_out)
|
||||
if (codec->stats_out)
|
||||
stream_write_buffer(ctx->twopass_bytebuffer_v,
|
||||
stream->codec->stats_out,
|
||||
strlen(stream->codec->stats_out));
|
||||
codec->stats_out,
|
||||
strlen(codec->stats_out));
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
if (ctx->twopass_bytebuffer_a)
|
||||
if (stream->codec->stats_out)
|
||||
if (codec->stats_out)
|
||||
stream_write_buffer(ctx->twopass_bytebuffer_a,
|
||||
stream->codec->stats_out,
|
||||
strlen(stream->codec->stats_out));
|
||||
codec->stats_out,
|
||||
strlen(codec->stats_out));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVPacket *packet)
|
||||
int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVStream *stream,
|
||||
AVPacket *packet)
|
||||
{
|
||||
int r;
|
||||
|
||||
CHECK_FAIL(ctx, -1);
|
||||
|
||||
if (stream->index != packet->stream_index) {
|
||||
MP_ERR(ctx, "Called encode_lavc_write_frame on the wrong stream\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ctx->header_written <= 0)
|
||||
return -1;
|
||||
|
||||
@@ -795,27 +817,32 @@ int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVPacket *packet)
|
||||
(int)packet->stream_index,
|
||||
(int)packet->pts,
|
||||
packet->pts
|
||||
* (double)ctx->avc->streams[packet->stream_index]->time_base.num
|
||||
/ (double)ctx->avc->streams[packet->stream_index]->time_base.den,
|
||||
* (double)stream->time_base.num
|
||||
/ (double)stream->time_base.den,
|
||||
(int)packet->dts,
|
||||
packet->dts
|
||||
* (double)ctx->avc->streams[packet->stream_index]->time_base.num
|
||||
/ (double)ctx->avc->streams[packet->stream_index]->time_base.den,
|
||||
* (double)stream->time_base.num
|
||||
/ (double)stream->time_base.den,
|
||||
(int)packet->size);
|
||||
|
||||
switch (ctx->avc->streams[packet->stream_index]->codec->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
ctx->vbytes += packet->size;
|
||||
++ctx->frames;
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
ctx->abytes += packet->size;
|
||||
ctx->audioseconds += packet->duration
|
||||
* (double)ctx->avc->streams[packet->stream_index]->time_base.num
|
||||
/ (double)ctx->avc->streams[packet->stream_index]->time_base.den;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
switch (stream->codecpar->codec_type) {
|
||||
#else
|
||||
switch (stream->codec->codec_type) {
|
||||
#endif
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
ctx->vbytes += packet->size;
|
||||
++ctx->frames;
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
ctx->abytes += packet->size;
|
||||
ctx->audioseconds += packet->duration
|
||||
* (double)stream->time_base.num
|
||||
/ (double)stream->time_base.den;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
r = av_interleaved_write_frame(ctx->avc, packet);
|
||||
@@ -1062,11 +1089,12 @@ bool encode_lavc_showhelp(struct mp_log *log, struct encode_opts *opts)
|
||||
return help_output;
|
||||
}
|
||||
|
||||
double encode_lavc_getoffset(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
double encode_lavc_getoffset(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec)
|
||||
{
|
||||
CHECK_FAIL(ctx, 0);
|
||||
|
||||
switch (stream->codec->codec_type) {
|
||||
switch (codec->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
return ctx->options->voffset;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
@@ -1151,49 +1179,49 @@ void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...)
|
||||
}
|
||||
|
||||
bool encode_lavc_set_csp(struct encode_lavc_context *ctx,
|
||||
AVStream *stream, enum mp_csp csp)
|
||||
AVCodecContext *codec, enum mp_csp csp)
|
||||
{
|
||||
CHECK_FAIL(ctx, NULL);
|
||||
|
||||
if (ctx->header_written) {
|
||||
if (stream->codec->colorspace != mp_csp_to_avcol_spc(csp))
|
||||
if (codec->colorspace != mp_csp_to_avcol_spc(csp))
|
||||
MP_WARN(ctx, "can not change color space during encoding\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
stream->codec->colorspace = mp_csp_to_avcol_spc(csp);
|
||||
codec->colorspace = mp_csp_to_avcol_spc(csp);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool encode_lavc_set_csp_levels(struct encode_lavc_context *ctx,
|
||||
AVStream *stream, enum mp_csp_levels lev)
|
||||
AVCodecContext *codec, enum mp_csp_levels lev)
|
||||
{
|
||||
CHECK_FAIL(ctx, NULL);
|
||||
|
||||
if (ctx->header_written) {
|
||||
if (stream->codec->color_range != mp_csp_levels_to_avcol_range(lev))
|
||||
if (codec->color_range != mp_csp_levels_to_avcol_range(lev))
|
||||
MP_WARN(ctx, "can not change color space during encoding\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
stream->codec->color_range = mp_csp_levels_to_avcol_range(lev);
|
||||
codec->color_range = mp_csp_levels_to_avcol_range(lev);
|
||||
return true;
|
||||
}
|
||||
|
||||
enum mp_csp encode_lavc_get_csp(struct encode_lavc_context *ctx,
|
||||
AVStream *stream)
|
||||
AVCodecContext *codec)
|
||||
{
|
||||
CHECK_FAIL(ctx, 0);
|
||||
|
||||
return avcol_spc_to_mp_csp(stream->codec->colorspace);
|
||||
return avcol_spc_to_mp_csp(codec->colorspace);
|
||||
}
|
||||
|
||||
enum mp_csp_levels encode_lavc_get_csp_levels(struct encode_lavc_context *ctx,
|
||||
AVStream *stream)
|
||||
AVCodecContext *codec)
|
||||
{
|
||||
CHECK_FAIL(ctx, 0);
|
||||
|
||||
return avcol_range_to_mp_csp_levels(stream->codec->color_range);
|
||||
return avcol_range_to_mp_csp_levels(codec->color_range);
|
||||
}
|
||||
|
||||
// vim: ts=4 sw=4 et
|
||||
+22
-11
@@ -46,8 +46,14 @@ struct encode_lavc_context {
|
||||
|
||||
float vo_fps;
|
||||
|
||||
// these are processed from the options
|
||||
// FFmpeg contexts.
|
||||
AVFormatContext *avc;
|
||||
AVStream *vst;
|
||||
AVStream *ast;
|
||||
AVCodecContext *vcc;
|
||||
AVCodecContext *acc;
|
||||
|
||||
// these are processed from the options
|
||||
AVRational timebase;
|
||||
AVCodec *vc;
|
||||
AVCodec *ac;
|
||||
@@ -88,26 +94,31 @@ struct encode_lavc_context {
|
||||
};
|
||||
|
||||
// interface for vo/ao drivers
|
||||
AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx, enum AVMediaType mt);
|
||||
void encode_lavc_write_stats(struct encode_lavc_context *ctx, AVStream *stream);
|
||||
int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVPacket *packet);
|
||||
int encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
enum AVMediaType mt, AVStream **stream_out,
|
||||
AVCodecContext **codec_out);
|
||||
void encode_lavc_write_stats(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *stream);
|
||||
int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVStream *stream,
|
||||
AVPacket *packet);
|
||||
int encode_lavc_supports_pixfmt(struct encode_lavc_context *ctx, enum AVPixelFormat format);
|
||||
AVCodec *encode_lavc_get_codec(struct encode_lavc_context *ctx, AVStream *stream);
|
||||
int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream);
|
||||
int encode_lavc_open_codec(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec);
|
||||
int encode_lavc_available(struct encode_lavc_context *ctx);
|
||||
int encode_lavc_timesyncfailed(struct encode_lavc_context *ctx);
|
||||
int encode_lavc_start(struct encode_lavc_context *ctx); // returns 1 on success
|
||||
int encode_lavc_oformat_flags(struct encode_lavc_context *ctx);
|
||||
double encode_lavc_getoffset(struct encode_lavc_context *ctx, AVStream *stream);
|
||||
double encode_lavc_getoffset(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec);
|
||||
void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...); // report failure of encoding
|
||||
|
||||
bool encode_lavc_set_csp(struct encode_lavc_context *ctx,
|
||||
AVStream *stream, enum mp_csp csp);
|
||||
AVCodecContext *codec, enum mp_csp csp);
|
||||
bool encode_lavc_set_csp_levels(struct encode_lavc_context *ctx,
|
||||
AVStream *stream, enum mp_csp_levels lev);
|
||||
AVCodecContext *codec, enum mp_csp_levels lev);
|
||||
enum mp_csp encode_lavc_get_csp(struct encode_lavc_context *ctx,
|
||||
AVStream *stream);
|
||||
AVCodecContext *codec);
|
||||
enum mp_csp_levels encode_lavc_get_csp_levels(struct encode_lavc_context *ctx,
|
||||
AVStream *stream);
|
||||
AVCodecContext *codec);
|
||||
|
||||
#endif
|
||||
@@ -1129,6 +1129,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
¶ms2, DEMUX_CHECK_FORCE);
|
||||
if (sub)
|
||||
return sub;
|
||||
timeline_destroy(tl);
|
||||
}
|
||||
return demuxer;
|
||||
}
|
||||
|
||||
+14
-14
@@ -213,25 +213,25 @@ static void build_timeline(struct timeline *tl, struct tl_parts *parts)
|
||||
|
||||
resolve_timestamps(part, source);
|
||||
|
||||
double len = source_get_length(source);
|
||||
if (len > 0) {
|
||||
len += source->start_time;
|
||||
} else {
|
||||
MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n",
|
||||
part->filename);
|
||||
}
|
||||
double end_time = source_get_length(source);
|
||||
if (end_time >= 0)
|
||||
end_time += source->start_time;
|
||||
|
||||
// Unknown length => use rest of the file. If duration is unknown, make
|
||||
// something up.
|
||||
if (part->length < 0)
|
||||
part->length = (len < 0 ? 1 : len) - part->offset;
|
||||
|
||||
if (len > 0) {
|
||||
double partlen = part->offset + part->length;
|
||||
if (partlen > len) {
|
||||
if (part->length < 0) {
|
||||
if (end_time < 0) {
|
||||
MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n",
|
||||
part->filename);
|
||||
end_time = 1;
|
||||
}
|
||||
part->length = end_time - part->offset;
|
||||
} else if (end_time >= 0) {
|
||||
double end_part = part->offset + part->length;
|
||||
if (end_part > end_time) {
|
||||
MP_WARN(tl, "EDL: entry %d uses %f "
|
||||
"seconds, but file has only %f seconds.\n",
|
||||
n, partlen, len);
|
||||
n, end_part, end_time);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -795,6 +795,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
avfc->pb = priv->pb;
|
||||
if (stream_control(priv->stream, STREAM_CTRL_HAS_AVSEEK, NULL) > 0)
|
||||
demuxer->seekable = true;
|
||||
demuxer->seekable |= priv->format_hack.fully_read;
|
||||
}
|
||||
|
||||
if (matches_avinputformat_name(priv, "rtsp")) {
|
||||
@@ -1101,8 +1102,12 @@ static void demux_close_lavf(demuxer_t *demuxer)
|
||||
av_freep(&priv->pb->buffer);
|
||||
av_freep(&priv->pb);
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
if (priv->streams[n])
|
||||
if (priv->streams[n]) {
|
||||
avcodec_free_context(&priv->streams[n]->codec->lav_headers);
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
avcodec_parameters_free(&priv->streams[n]->codec->lav_codecpar);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (priv->stream != demuxer->stream)
|
||||
free_stream(priv->stream);
|
||||
|
||||
+9
-3
@@ -773,8 +773,9 @@ static int demux_mkv_read_cues(demuxer_t *demuxer)
|
||||
if (cues.n_cue_point <= 3) // probably too sparse and will just break seeking
|
||||
goto done;
|
||||
|
||||
// Discard incremental index.
|
||||
mkv_d->num_indexes = 0;
|
||||
// Discard incremental index. (Keep the first entry, which must be the
|
||||
// start of the file - helps with files that miss the first index entry.)
|
||||
mkv_d->num_indexes = MPMIN(1, mkv_d->num_indexes);
|
||||
mkv_d->index_has_durations = false;
|
||||
|
||||
for (int i = 0; i < cues.n_cue_point; i++) {
|
||||
@@ -1719,6 +1720,7 @@ static const char *const mkv_sub_tag[][2] = {
|
||||
{ "S_HDMV/PGS", "hdmv_pgs_subtitle"},
|
||||
{ "D_WEBVTT/SUBTITLES", "webvtt-webm"},
|
||||
{ "D_WEBVTT/CAPTIONS", "webvtt-webm"},
|
||||
{ "S_TEXT/WEBVTT", "webvtt"},
|
||||
{ "S_DVBSUB", "dvb_subtitle"},
|
||||
{0}
|
||||
};
|
||||
@@ -2609,6 +2611,7 @@ static int read_next_block(demuxer_t *demuxer, struct block_info *block)
|
||||
find_next_cluster:
|
||||
mkv_d->cluster_end = 0;
|
||||
for (;;) {
|
||||
stream_peek(s, 4); // guarantee we can undo ebml_read_id() below
|
||||
mkv_d->cluster_start = stream_tell(s);
|
||||
uint32_t id = ebml_read_id(s);
|
||||
if (id == MATROSKA_ID_CLUSTER)
|
||||
@@ -2627,6 +2630,7 @@ static int read_next_block(demuxer_t *demuxer, struct block_info *block)
|
||||
if ((!ebml_is_mkv_level1_id(id) && id != EBML_ID_VOID) ||
|
||||
ebml_read_skip(demuxer->log, -1, s) != 0)
|
||||
{
|
||||
stream_seek(s, mkv_d->cluster_start);
|
||||
ebml_resync_cluster(demuxer->log, s);
|
||||
}
|
||||
}
|
||||
@@ -2963,8 +2967,10 @@ static void probe_first_timestamp(struct demuxer *demuxer)
|
||||
return;
|
||||
|
||||
struct block_info block;
|
||||
if (read_next_block(demuxer, &block) > 0)
|
||||
if (read_next_block(demuxer, &block) > 0) {
|
||||
index_block(demuxer, &block);
|
||||
mkv_d->tmp_block = block;
|
||||
}
|
||||
|
||||
demuxer->start_time = mkv_d->cluster_tc / 1e9;
|
||||
|
||||
|
||||
+66
-19
@@ -20,6 +20,7 @@
|
||||
#include <strings.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "common/common.h"
|
||||
#include "options/options.h"
|
||||
#include "common/msg.h"
|
||||
@@ -105,14 +106,21 @@ static int parse_m3u(struct pl_parser *p)
|
||||
if (p->check_level == DEMUX_CHECK_UNSAFE) {
|
||||
char *ext = mp_splitext(p->real_stream->url, NULL);
|
||||
bstr data = stream_peek(p->real_stream, PROBE_SIZE);
|
||||
if (ext && !strcmp(ext, "m3u") && data.len > 10 && maybe_text(data))
|
||||
goto ok;
|
||||
if (ext && data.len > 10 && maybe_text(data)) {
|
||||
const char *exts[] = {"m3u", "m3u8", NULL};
|
||||
for (int n = 0; exts[n]; n++) {
|
||||
if (strcasecmp(ext, exts[n]) == 0)
|
||||
goto ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
ok:
|
||||
if (p->probing)
|
||||
return 0;
|
||||
|
||||
char *title = NULL;
|
||||
while (line.len || !pl_eof(p)) {
|
||||
if (bstr_eatstart0(&line, "#EXTINF:")) {
|
||||
@@ -209,6 +217,59 @@ static int parse_txt(struct pl_parser *p)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MAX_DIR_STACK 20
|
||||
|
||||
static bool same_st(struct stat *st1, struct stat *st2)
|
||||
{
|
||||
return HAVE_POSIX && st1->st_dev == st2->st_dev && st1->st_ino == st2->st_ino;
|
||||
}
|
||||
|
||||
// Return true if this was a readable directory.
|
||||
static bool scan_dir(struct pl_parser *p, char *path,
|
||||
struct stat *dir_stack, int num_dir_stack,
|
||||
char ***files, int *num_files)
|
||||
{
|
||||
if (strlen(path) >= 8192 || num_dir_stack == MAX_DIR_STACK)
|
||||
return false; // things like mount bind loops
|
||||
|
||||
DIR *dp = opendir(path);
|
||||
if (!dp) {
|
||||
MP_ERR(p, "Could not read directory.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct dirent *ep;
|
||||
while ((ep = readdir(dp))) {
|
||||
if (ep->d_name[0] == '.')
|
||||
continue;
|
||||
|
||||
if (mp_cancel_test(p->s->cancel))
|
||||
break;
|
||||
|
||||
char *file = mp_path_join(p, path, ep->d_name);
|
||||
|
||||
struct stat st;
|
||||
if (stat(file, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||
for (int n = 0; n < num_dir_stack; n++) {
|
||||
if (same_st(&dir_stack[n], &st)) {
|
||||
MP_VERBOSE(p, "Skip recursive entry: %s\n", file);
|
||||
goto skip;
|
||||
}
|
||||
}
|
||||
|
||||
dir_stack[num_dir_stack] = st;
|
||||
scan_dir(p, file, dir_stack, num_dir_stack + 1, files, num_files);
|
||||
} else {
|
||||
MP_TARRAY_APPEND(p, *files, *num_files, file);
|
||||
}
|
||||
|
||||
skip: ;
|
||||
}
|
||||
|
||||
closedir(dp);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int cmp_filename(const void *a, const void *b)
|
||||
{
|
||||
return strcmp(*(char **)a, *(char **)b);
|
||||
@@ -222,32 +283,18 @@ static int parse_dir(struct pl_parser *p)
|
||||
return 0;
|
||||
|
||||
char *path = mp_file_get_path(p, bstr0(p->real_stream->url));
|
||||
if (strlen(path) >= 8192)
|
||||
return -1; // things like mount bind loops
|
||||
|
||||
DIR *dp = opendir(path);
|
||||
if (!dp) {
|
||||
MP_ERR(p, "Could not read directory.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
char **files = NULL;
|
||||
int num_files = 0;
|
||||
struct stat dir_stack[MAX_DIR_STACK];
|
||||
|
||||
struct dirent *ep;
|
||||
while ((ep = readdir(dp))) {
|
||||
if (ep->d_name[0] == '.')
|
||||
continue;
|
||||
MP_TARRAY_APPEND(p, files, num_files, talloc_strdup(p, ep->d_name));
|
||||
}
|
||||
scan_dir(p, path, dir_stack, 0, &files, &num_files);
|
||||
|
||||
if (files)
|
||||
qsort(files, num_files, sizeof(files[0]), cmp_filename);
|
||||
|
||||
for (int n = 0; n < num_files; n++)
|
||||
playlist_add_file(p->pl, mp_path_join(p, path, files[n]));
|
||||
|
||||
closedir(dp);
|
||||
playlist_add_file(p->pl, files[n]);
|
||||
|
||||
p->add_base = false;
|
||||
|
||||
|
||||
+1
-1
@@ -142,7 +142,7 @@
|
||||
#CLOSE_WIN quit
|
||||
#CLOSE_WIN {encode} quit 4
|
||||
#E cycle edition # next edition
|
||||
#l ab_loop # Set/clear A-B loop points
|
||||
#l ab-loop # Set/clear A-B loop points
|
||||
#L cycle-values loop "inf" "no" # toggle infinite looping
|
||||
#ctrl+c quit 4
|
||||
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@
|
||||
#softvol=no
|
||||
|
||||
# Do not filter audio to keep pitch when changing playback speed.
|
||||
audio-pitch-correction=no
|
||||
#audio-pitch-correction=no
|
||||
|
||||
# Output 5.1 audio natively, and upmix/downmix audio with a different format.
|
||||
#audio-channels=5.1
|
||||
|
||||
@@ -273,7 +273,6 @@ static struct mp_cmd *parse_cmd_str(struct mp_log *log, void *tmp,
|
||||
break;
|
||||
if (pctx_read_token(ctx, &cur_token) < 0)
|
||||
goto error;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!find_cmd(ctx->log, cmd, cur_token))
|
||||
|
||||
+1
-4
@@ -1247,12 +1247,9 @@ void mp_input_load(struct input_ctx *ictx)
|
||||
void *tmp = talloc_new(NULL);
|
||||
char **files = mp_find_all_config_files(tmp, ictx->global, "input.conf");
|
||||
for (int n = 0; files && files[n]; n++)
|
||||
config_ok = config_ok | parse_config_file(ictx, files[n], false);
|
||||
parse_config_file(ictx, files[n], false);
|
||||
talloc_free(tmp);
|
||||
}
|
||||
if (!config_ok) {
|
||||
MP_VERBOSE(ictx, "Falling back on default (hardcoded) input config\n");
|
||||
}
|
||||
|
||||
if (input_conf->use_alt_gr) {
|
||||
ictx->using_alt_gr = true;
|
||||
|
||||
+2
-2
@@ -134,7 +134,7 @@ static void *client_thread(void *p)
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
char discard[100];
|
||||
read(pipe_fd, discard, sizeof(discard));
|
||||
(void)read(pipe_fd, discard, sizeof(discard));
|
||||
|
||||
while (1) {
|
||||
mpv_event *event = mpv_wait_event(arg->client, 0);
|
||||
@@ -413,7 +413,7 @@ void mp_uninit_ipc(struct mp_ipc_ctx *arg)
|
||||
if (!arg)
|
||||
return;
|
||||
|
||||
write(arg->death_pipe[1], &(char){0}, 1);
|
||||
(void)write(arg->death_pipe[1], &(char){0}, 1);
|
||||
pthread_join(arg->thread, NULL);
|
||||
|
||||
close(arg->death_pipe[0]);
|
||||
|
||||
+13
-3
@@ -215,7 +215,7 @@ extern "C" {
|
||||
* relational operators (<, >, <=, >=).
|
||||
*/
|
||||
#define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL)
|
||||
#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 20)
|
||||
#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 21)
|
||||
|
||||
/**
|
||||
* Return the MPV_CLIENT_API_VERSION the mpv source has been compiled with.
|
||||
@@ -780,6 +780,10 @@ void mpv_free_node_contents(mpv_node *node);
|
||||
* mpv_set_property() to change settings during playback, because the property
|
||||
* mechanism guarantees that changes take effect immediately.
|
||||
*
|
||||
* Using a format other than MPV_FORMAT_NODE is equivalent to constructing a
|
||||
* mpv_node with the given format and data, and passing the mpv_node to this
|
||||
* function.
|
||||
*
|
||||
* @param name Option name. This is the same as on the mpv command line, but
|
||||
* without the leading "--".
|
||||
* @param format see enum mpv_format.
|
||||
@@ -877,7 +881,13 @@ int mpv_command_node_async(mpv_handle *ctx, uint64_t reply_userdata,
|
||||
* usually will fail with MPV_ERROR_PROPERTY_FORMAT. In some cases, the data
|
||||
* is automatically converted and access succeeds. For example, MPV_FORMAT_INT64
|
||||
* is always converted to MPV_FORMAT_DOUBLE, and access using MPV_FORMAT_STRING
|
||||
* usually invokes a string parser.
|
||||
* usually invokes a string parser. The same happens when calling this function
|
||||
* with MPV_FORMAT_NODE: the underlying format may be converted to another
|
||||
* type if possible.
|
||||
*
|
||||
* Using a format other than MPV_FORMAT_NODE is equivalent to constructing a
|
||||
* mpv_node with the given format and data, and passing the mpv_node to this
|
||||
* function. (Before API version 1.21, this was different.)
|
||||
*
|
||||
* @param name The property name. See input.rst for a list of properties.
|
||||
* @param format see enum mpv_format.
|
||||
@@ -1436,7 +1446,7 @@ int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable);
|
||||
* required log level for a message to be received with MPV_EVENT_LOG_MESSAGE.
|
||||
*
|
||||
* @param min_level Minimal log level as string. Valid log levels:
|
||||
* no fatal error warn info status v debug trace
|
||||
* no fatal error warn info v debug trace
|
||||
* The value "no" disables all messages. This is the default.
|
||||
* An exception is the value "terminal-default", which uses the
|
||||
* log level as set by the "--msg-level" option. This works
|
||||
|
||||
+40
-3
@@ -108,6 +108,28 @@ extern "C" {
|
||||
* context must have been uninitialized. If this doesn't happen, undefined
|
||||
* behavior will result.
|
||||
*
|
||||
* Hardware decoding
|
||||
* -----------------
|
||||
*
|
||||
* Hardware decoding via opengl_cb is fully supported, but requires some
|
||||
* additional setup. (At least if direct hardware decoding modes are wanted,
|
||||
* instead of copying back surface data from GPU to CPU RAM.)
|
||||
*
|
||||
* While "normal" mpv loads the OpenGL hardware decoding interop on demand,
|
||||
* this can't be done with opengl_cb for internal technical reasons. Instead,
|
||||
* make it load the interop at load time by setting the "hwdec-preload"="auto"
|
||||
* option before calling mpv_opengl_cb_init_gl().
|
||||
*
|
||||
* There may be certain requirements on the OpenGL implementation:
|
||||
* - Windows: ANGLE is required (although in theory GL/DX interop could be used)
|
||||
* - Intel/Linux: EGL is required, and also a glMPGetNativeDisplay() callback
|
||||
* must be provided (see sections below)
|
||||
* - nVidia/Linux: GLX is required
|
||||
* - OSX: CGL is required (CGLGetCurrentContext() returning non-NULL)
|
||||
*
|
||||
* Once these things are setup, hardware decoding can be enabled/disabled at
|
||||
* any time by setting the "hwdec" property.
|
||||
*
|
||||
* Special windowing system interop considerations
|
||||
* ------------------------------------------------
|
||||
*
|
||||
@@ -126,19 +148,34 @@ extern "C" {
|
||||
* up until mpv_opengl_cb_uninit_gl() is called. If the name is not anything
|
||||
* you know/expected, return NULL from the function.
|
||||
*
|
||||
* Windowing system interop on Linux
|
||||
* ---------------------------------
|
||||
* Windowing system interop on Intel/Linux with VAAPI
|
||||
* --------------------------------------------------
|
||||
*
|
||||
* The new VAAPI OpenGL interop requires an EGL context. EGL provides no way
|
||||
* to query the X11 Display associated to a specific EGL context, so this API
|
||||
* is used to pass it through.
|
||||
*
|
||||
* glMPGetNativeDisplay("x11") should return a X11 "Display*", which then will
|
||||
* be used to create the hardware decoder state. (On GLX, this is not needed.)
|
||||
* be used to create the hardware decoder state.
|
||||
*
|
||||
* glMPGetNativeDisplay("wl") should return a Wayland "struct wl_display *".
|
||||
*
|
||||
* glMPGetNativeDisplay("drm") should return a DRM FD casted to intptr_t (note
|
||||
* that a 0 FD is not supported - if this can happen in your case, you must
|
||||
* dup2() it to a non-0 FD).
|
||||
*
|
||||
* nVidia/Linux via VDPAU requires GLX, which does not have this problem (the
|
||||
* GLX API can return the current X11 Display).
|
||||
*
|
||||
* Windowing system interop on MS win32
|
||||
* ------------------------------------
|
||||
*
|
||||
* Warning: the following is only required if native OpenGL instead of ANGLE
|
||||
* is used. ANGLE is recommended, because it also allows direct
|
||||
* hardware decoding interop without further setup by the libmpv
|
||||
* API user, while the same with native OpenGL is either very hard
|
||||
* to do (via GL/DX interop with D3D9), or not implemented.
|
||||
*
|
||||
* If OpenGL switches to fullscreen, most players give it access GPU access,
|
||||
* which means DXVA2 hardware decoding in mpv won't work. This can be worked
|
||||
* around by giving mpv access to Direct3D device, which it will then use to
|
||||
|
||||
+10
-2
@@ -215,9 +215,9 @@ struct bstr *bstr_splitlines(void *talloc_ctx, struct bstr str)
|
||||
return r;
|
||||
}
|
||||
|
||||
struct bstr bstr_getline(struct bstr str, struct bstr *rest)
|
||||
struct bstr bstr_splitchar(struct bstr str, struct bstr *rest, const char c)
|
||||
{
|
||||
int pos = bstrchr(str, '\n');
|
||||
int pos = bstrchr(str, c);
|
||||
if (pos < 0)
|
||||
pos = str.len;
|
||||
if (rest)
|
||||
@@ -243,6 +243,14 @@ bool bstr_eatstart(struct bstr *s, struct bstr prefix)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bstr_eatend(struct bstr *s, struct bstr prefix)
|
||||
{
|
||||
if (!bstr_endswith(*s, prefix))
|
||||
return false;
|
||||
s->len -= prefix.len;
|
||||
return true;
|
||||
}
|
||||
|
||||
void bstr_lower(struct bstr str)
|
||||
{
|
||||
for (int i = 0; i < str.len; i++)
|
||||
|
||||
+18
-6
@@ -116,10 +116,15 @@ int bstr_validate_utf8(struct bstr s);
|
||||
// talloc, with talloc_ctx as parent.
|
||||
struct bstr bstr_sanitize_utf8_latin1(void *talloc_ctx, struct bstr s);
|
||||
|
||||
// Return the text before the next line break, and return it. Change *rest to
|
||||
// point to the text following this line break. (rest can be NULL.)
|
||||
// Line break characters are not stripped.
|
||||
struct bstr bstr_getline(struct bstr str, struct bstr *rest);
|
||||
// Return the text before the occurrence of a character, and return it. Change
|
||||
// *rest to point to the text following this character. (rest can be NULL.)
|
||||
struct bstr bstr_splitchar(struct bstr str, struct bstr *rest, const char c);
|
||||
|
||||
// Like bstr_splitchar. Trailing newlines are not stripped.
|
||||
static inline struct bstr bstr_getline(struct bstr str, struct bstr *rest)
|
||||
{
|
||||
return bstr_splitchar(str, rest, '\n');
|
||||
}
|
||||
|
||||
// Strip one trailing line break. This is intended for use with bstr_getline,
|
||||
// and will remove the trailing \n or \r\n sequence.
|
||||
@@ -131,8 +136,10 @@ void bstr_xappend_asprintf(void *talloc_ctx, bstr *s, const char *fmt, ...)
|
||||
void bstr_xappend_vasprintf(void *talloc_ctx, bstr *s, const char *fmt, va_list va)
|
||||
PRINTF_ATTRIBUTE(3, 0);
|
||||
|
||||
// If s starts with prefix, return true and return the rest of the string in s.
|
||||
// If s starts/ends with prefix, return true and return the rest of the string
|
||||
// in s.
|
||||
bool bstr_eatstart(struct bstr *s, struct bstr prefix);
|
||||
bool bstr_eatend(struct bstr *s, struct bstr prefix);
|
||||
|
||||
bool bstr_case_startswith(struct bstr s, struct bstr prefix);
|
||||
bool bstr_case_endswith(struct bstr s, struct bstr suffix);
|
||||
@@ -200,11 +207,16 @@ static inline int bstr_find0(struct bstr haystack, const char *needle)
|
||||
return bstr_find(haystack, bstr0(needle));
|
||||
}
|
||||
|
||||
static inline int bstr_eatstart0(struct bstr *s, const char *prefix)
|
||||
static inline bool bstr_eatstart0(struct bstr *s, const char *prefix)
|
||||
{
|
||||
return bstr_eatstart(s, bstr0(prefix));
|
||||
}
|
||||
|
||||
static inline bool bstr_eatend0(struct bstr *s, const char *prefix)
|
||||
{
|
||||
return bstr_eatend(s, bstr0(prefix));
|
||||
}
|
||||
|
||||
// create a pair (not single value!) for "%.*s" printf syntax
|
||||
#define BSTR_P(bstr) (int)((bstr).len), ((bstr).start ? (char*)(bstr).start : "")
|
||||
|
||||
|
||||
+44
-14
@@ -358,6 +358,18 @@ static void add_options(struct m_config *config,
|
||||
m_config_add_option(config, parent_name, optstruct, optstruct_def, &defs[i]);
|
||||
}
|
||||
|
||||
// Initialize a field with a given value. In case this is dynamic data, it has
|
||||
// to be allocated and copied. src can alias dst, also can be NULL.
|
||||
static void init_opt_inplace(const struct m_option *opt, void *dst,
|
||||
const void *src)
|
||||
{
|
||||
union m_option_value temp = {0};
|
||||
if (src)
|
||||
memcpy(&temp, src, opt->type->size);
|
||||
memset(dst, 0, opt->type->size);
|
||||
m_option_copy(opt, dst, &temp);
|
||||
}
|
||||
|
||||
static void m_config_add_option(struct m_config *config,
|
||||
const char *parent_name,
|
||||
void *optstruct,
|
||||
@@ -418,11 +430,7 @@ static void m_config_add_option(struct m_config *config,
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
// In case this is dynamic data, it has to be allocated and copied.
|
||||
union m_option_value temp = {0};
|
||||
memcpy(&temp, co.default_data, arg->type->size);
|
||||
memset(co.data, 0, arg->type->size);
|
||||
m_option_copy(arg, co.data, &temp);
|
||||
init_opt_inplace(arg, co.data, co.default_data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,6 +499,13 @@ struct m_config_option *m_config_get_co(const struct m_config *config,
|
||||
co->warning_was_printed = true;
|
||||
}
|
||||
return NULL;
|
||||
} else if (co->opt->deprecation_message) {
|
||||
if (!co->warning_was_printed) {
|
||||
MP_WARN(config, "Warning: option %s%s is deprecated "
|
||||
"and might be removed in the future (%s).\n",
|
||||
prefix, co->name, co->opt->deprecation_message);
|
||||
co->warning_was_printed = true;
|
||||
}
|
||||
}
|
||||
return co;
|
||||
}
|
||||
@@ -942,24 +957,39 @@ static void free_substruct(void *ptr)
|
||||
}
|
||||
}
|
||||
|
||||
// Passing ptr==NULL initializes it from proper defaults.
|
||||
void *m_sub_options_copy(void *talloc_ctx, const struct m_sub_options *opts,
|
||||
const void *ptr)
|
||||
{
|
||||
void *new = talloc_zero_size(talloc_ctx, opts->size);
|
||||
void *new = m_config_alloc_struct(talloc_ctx, opts);
|
||||
struct dtor_info *dtor = talloc_ptrtype(new, dtor);
|
||||
*dtor = (struct dtor_info){opts, new};
|
||||
talloc_set_destructor(dtor, free_substruct);
|
||||
// also fill/initialize members not described by opts
|
||||
if (opts->defaults)
|
||||
memcpy(new, opts->defaults, opts->size);
|
||||
for (int n = 0; opts->opts && opts->opts[n].type; n++) {
|
||||
const struct m_option *opt = &opts->opts[n];
|
||||
// not implemented, because it adds lots of complexity
|
||||
assert(!(opt->type->flags & M_OPT_TYPE_HAS_CHILD));
|
||||
void *src = (char *)ptr + opt->offset;
|
||||
if (opt->offset < 0)
|
||||
continue;
|
||||
void *src = ptr ? (char *)ptr + opt->offset : NULL;
|
||||
void *dst = (char *)new + opt->offset;
|
||||
memset(dst, 0, opt->type->size);
|
||||
m_option_copy(opt, dst, src);
|
||||
if (opt->type->flags & M_OPT_TYPE_HAS_CHILD) {
|
||||
// Specifying a default struct for a sub-option field in the
|
||||
// containing struct's default struct is not supported here.
|
||||
// (Out of laziness. Could possibly be supported.)
|
||||
assert(!substruct_read_ptr(dst));
|
||||
|
||||
const struct m_sub_options *subopts = opt->priv;
|
||||
|
||||
const void *sub_src = NULL;
|
||||
if (src)
|
||||
sub_src = substruct_read_ptr(src);
|
||||
if (!sub_src)
|
||||
sub_src = subopts->defaults;
|
||||
|
||||
void *sub_dst = m_sub_options_copy(new, subopts, sub_src);
|
||||
substruct_write_ptr(dst, sub_dst);
|
||||
} else {
|
||||
init_opt_inplace(opt, dst, src);
|
||||
}
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
+43
-3
@@ -106,6 +106,21 @@ const m_option_t *m_option_list_find(const m_option_t *list, const char *name)
|
||||
return m_option_list_findb(list, bstr0(name));
|
||||
}
|
||||
|
||||
int m_option_set_node_or_string(struct mp_log *log, const m_option_t *opt,
|
||||
const char *name, void *dst, struct mpv_node *src)
|
||||
{
|
||||
if (src->format == MPV_FORMAT_STRING) {
|
||||
// The af and vf option unfortunately require this, because the
|
||||
// option name includes the "action".
|
||||
bstr optname = bstr0(name), a, b;
|
||||
if (bstr_split_tok(optname, "/", &a, &b))
|
||||
optname = b;
|
||||
return m_option_parse(log, opt, optname, bstr0(src->u.string), dst);
|
||||
} else {
|
||||
return m_option_set_node(opt, dst, src);
|
||||
}
|
||||
}
|
||||
|
||||
// Default function that just does a memcpy
|
||||
|
||||
static void copy_opt(const m_option_t *opt, void *dst, const void *src)
|
||||
@@ -657,6 +672,8 @@ static int choice_set(const m_option_t *opt, void *dst, struct mpv_node *src)
|
||||
src_str = buf;
|
||||
} else if (src->format == MPV_FORMAT_STRING) {
|
||||
src_str = src->u.string;
|
||||
} else if (src->format == MPV_FORMAT_FLAG) {
|
||||
src_str = src->u.flag ? "yes" : "no";
|
||||
}
|
||||
if (!src_str)
|
||||
return M_OPT_UNKNOWN;
|
||||
@@ -698,8 +715,19 @@ static int choice_get(const m_option_t *opt, void *ta_parent,
|
||||
alt = NULL;
|
||||
}
|
||||
if (alt) {
|
||||
dst->format = MPV_FORMAT_STRING;
|
||||
dst->u.string = talloc_strdup(ta_parent, alt->name);
|
||||
int b = -1;
|
||||
if (strcmp(alt->name, "yes") == 0) {
|
||||
b = 1;
|
||||
} else if (strcmp(alt->name, "no") == 0) {
|
||||
b = 0;
|
||||
}
|
||||
if (b >= 0) {
|
||||
dst->format = MPV_FORMAT_FLAG;
|
||||
dst->u.flag = b;
|
||||
} else {
|
||||
dst->format = MPV_FORMAT_STRING;
|
||||
dst->u.string = talloc_strdup(ta_parent, alt->name);
|
||||
}
|
||||
} else {
|
||||
dst->format = MPV_FORMAT_INT64;
|
||||
dst->u.int64 = ival;
|
||||
@@ -1654,7 +1682,7 @@ static int parse_msglevels(struct mp_log *log, const m_option_t *opt,
|
||||
struct bstr name, struct bstr param, void *dst)
|
||||
{
|
||||
if (bstr_equals0(param, "help")) {
|
||||
mp_info(log, "Syntax: --msglevel=module1=level:module2=level:...\n"
|
||||
mp_info(log, "Syntax:\n\n --msglevel=module1=level,module2=level,...\n\n"
|
||||
"'module' is output prefix as shown with -v, or a prefix\n"
|
||||
"of it. level is one of:\n\n"
|
||||
" fatal error warn info status v debug trace\n\n"
|
||||
@@ -2061,6 +2089,10 @@ void m_geometry_apply(int *xpos, int *ypos, int *widw, int *widh,
|
||||
} else if (!(gm->w > 0) && gm->h > 0) {
|
||||
*widw = *widh * asp;
|
||||
}
|
||||
// Center window after resize. If valid x:y values are passed to
|
||||
// geometry, then those values will be overriden.
|
||||
*xpos += prew / 2 - *widw / 2;
|
||||
*ypos += preh / 2 - *widh / 2;
|
||||
}
|
||||
|
||||
if (gm->xy_valid) {
|
||||
@@ -2287,10 +2319,18 @@ static int parse_chmap(struct mp_log *log, const m_option_t *opt,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char *print_chmap(const m_option_t *opt, const void *val)
|
||||
{
|
||||
const struct mp_chmap *chmap = val;
|
||||
return talloc_strdup(NULL, mp_chmap_to_str(chmap));
|
||||
}
|
||||
|
||||
|
||||
const m_option_type_t m_option_type_chmap = {
|
||||
.name = "Audio channels or channel map",
|
||||
.size = sizeof(struct mp_chmap),
|
||||
.parse = parse_chmap,
|
||||
.print = print_chmap,
|
||||
.copy = copy_opt,
|
||||
};
|
||||
|
||||
|
||||
+9
-1
@@ -333,6 +333,10 @@ struct m_option {
|
||||
|
||||
// Initialize variable to given default before parsing options
|
||||
const void *defval;
|
||||
|
||||
// Print a warning when this option is used (for options with no direct
|
||||
// replacement.)
|
||||
const char *deprecation_message;
|
||||
};
|
||||
|
||||
|
||||
@@ -394,7 +398,7 @@ struct m_option {
|
||||
|
||||
// Dynamic data type.
|
||||
/** This flag indicates that the data is dynamically allocated (m_option::p
|
||||
* points to a pointer). It enables a little hack in the \ref Config wich
|
||||
* points to a pointer). It enables a little hack in the \ref Config which
|
||||
* replaces the initial value of such variables with a dynamic copy in case
|
||||
* the initial value is statically allocated (pretty common with strings).
|
||||
*/
|
||||
@@ -504,6 +508,10 @@ static inline int m_option_set_node(const m_option_t *opt, void *dst,
|
||||
return M_OPT_UNKNOWN;
|
||||
}
|
||||
|
||||
// Call m_option_parse for strings, m_option_set_node otherwise.
|
||||
int m_option_set_node_or_string(struct mp_log *log, const m_option_t *opt,
|
||||
const char *name, void *dst, struct mpv_node *src);
|
||||
|
||||
// see m_option_type.get
|
||||
static inline int m_option_get_node(const m_option_t *opt, void *ta_parent,
|
||||
struct mpv_node *dst, void *src)
|
||||
|
||||
+5
-12
@@ -104,16 +104,8 @@ int m_property_do(struct mp_log *log, const struct m_property *prop_list,
|
||||
return str != NULL;
|
||||
}
|
||||
case M_PROPERTY_SET_STRING: {
|
||||
if (!log)
|
||||
return M_PROPERTY_ERROR;
|
||||
bstr optname = bstr0(name), a, b;
|
||||
if (bstr_split_tok(optname, "/", &a, &b))
|
||||
optname = b;
|
||||
if (m_option_parse(log, &opt, optname, bstr0(arg), &val) < 0)
|
||||
return M_PROPERTY_ERROR;
|
||||
r = do_action(prop_list, name, M_PROPERTY_SET, &val, ctx);
|
||||
m_option_free(&opt, &val);
|
||||
return r;
|
||||
struct mpv_node node = { .format = MPV_FORMAT_STRING, .u.string = arg };
|
||||
return m_property_do(log, prop_list, name, M_PROPERTY_SET_NODE, &node, ctx);
|
||||
}
|
||||
case M_PROPERTY_SWITCH: {
|
||||
if (!log)
|
||||
@@ -163,11 +155,12 @@ int m_property_do(struct mp_log *log, const struct m_property *prop_list,
|
||||
return r;
|
||||
}
|
||||
case M_PROPERTY_SET_NODE: {
|
||||
if (!log)
|
||||
return M_PROPERTY_ERROR;
|
||||
if ((r = do_action(prop_list, name, M_PROPERTY_SET_NODE, arg, ctx)) !=
|
||||
M_PROPERTY_NOT_IMPLEMENTED)
|
||||
return r;
|
||||
struct mpv_node *node = arg;
|
||||
int err = m_option_set_node(&opt, &val, node);
|
||||
int err = m_option_set_node_or_string(log, &opt, name, &val, arg);
|
||||
if (err == M_OPT_UNKNOWN) {
|
||||
r = M_PROPERTY_NOT_IMPLEMENTED;
|
||||
} else if (err < 0) {
|
||||
|
||||
@@ -184,6 +184,8 @@ struct m_sub_property {
|
||||
// Convenience macros which can be used as part of a sub_property entry.
|
||||
#define SUB_PROP_INT(i) \
|
||||
.type = {.type = CONF_TYPE_INT}, .value = {.int_ = (i)}
|
||||
#define SUB_PROP_INT64(i) \
|
||||
.type = {.type = CONF_TYPE_INT64}, .value = {.int64 = (i)}
|
||||
#define SUB_PROP_STR(s) \
|
||||
.type = {.type = CONF_TYPE_STRING}, .value = {.string = (char *)(s)}
|
||||
#define SUB_PROP_FLOAT(f) \
|
||||
|
||||
+19
-10
@@ -82,12 +82,14 @@ extern const struct m_obj_list ao_obj_list;
|
||||
const struct m_opt_choice_alternatives mp_hwdec_names[] = {
|
||||
{"no", HWDEC_NONE},
|
||||
{"auto", HWDEC_AUTO},
|
||||
{"auto-copy", HWDEC_AUTO_COPY},
|
||||
{"vdpau", HWDEC_VDPAU},
|
||||
{"videotoolbox",HWDEC_VIDEOTOOLBOX},
|
||||
{"vaapi", HWDEC_VAAPI},
|
||||
{"vaapi-copy", HWDEC_VAAPI_COPY},
|
||||
{"dxva2", HWDEC_DXVA2},
|
||||
{"dxva2-copy", HWDEC_DXVA2_COPY},
|
||||
{"d3d11va", HWDEC_D3D11VA},
|
||||
{"d3d11va-copy",HWDEC_D3D11VA_COPY},
|
||||
{"rpi", HWDEC_RPI},
|
||||
{"mediacodec", HWDEC_MEDIACODEC},
|
||||
@@ -373,7 +375,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_CHOICE("ass-shaper", ass_shaper, 0,
|
||||
({"simple", 0}, {"complex", 1})),
|
||||
OPT_CHOICE("ass-style-override", ass_style_override, 0,
|
||||
({"no", 0}, {"yes", 1}, {"force", 3}, {"signfs", 4})),
|
||||
({"no", 0}, {"yes", 1}, {"force", 3}, {"signfs", 4}, {"strip", 5})),
|
||||
OPT_FLAG("sub-scale-by-window", sub_scale_by_window, 0),
|
||||
OPT_FLAG("sub-scale-with-window", sub_scale_with_window, 0),
|
||||
OPT_FLAG("ass-scale-with-window", ass_scale_with_window, 0),
|
||||
@@ -396,8 +398,10 @@ const m_option_t mp_opts[] = {
|
||||
OPT_FLAG("audio-fallback-to-null", ao_null_fallback, 0),
|
||||
OPT_CHOICE("force-window", force_vo, 0,
|
||||
({"no", 0}, {"yes", 1}, {"immediate", 2})),
|
||||
OPT_FLAG("taskbar-progress", vo.taskbar_progress, 0),
|
||||
OPT_FLAG("ontop", vo.ontop, M_OPT_FIXED),
|
||||
OPT_FLAG("border", vo.border, M_OPT_FIXED),
|
||||
OPT_FLAG("fit-border", vo.fit_border, M_OPT_FIXED),
|
||||
OPT_FLAG("on-all-workspaces", vo.all_workspaces, M_OPT_FIXED),
|
||||
|
||||
OPT_FLAG("window-dragging", allow_win_drag, CONF_GLOBAL),
|
||||
@@ -406,13 +410,13 @@ const m_option_t mp_opts[] = {
|
||||
({"no", SOFTVOL_NO},
|
||||
{"yes", SOFTVOL_YES},
|
||||
{"auto", SOFTVOL_AUTO})),
|
||||
OPT_FLOATRANGE("softvol-max", softvol_max, 0, 100, 1000),
|
||||
OPT_FLOATRANGE("volume", mixer_init_volume, 0, -1, 1000),
|
||||
OPT_CHOICE("mute", mixer_init_mute, 0,
|
||||
OPT_FLOATRANGE("volume-max", softvol_max, 0, 100, 1000),
|
||||
// values <0 for volume and mute are legacy and ignored
|
||||
OPT_FLOATRANGE("volume", softvol_volume, 0, -1, 1000),
|
||||
OPT_CHOICE("mute", softvol_mute, 0,
|
||||
({"auto", -1},
|
||||
{"no", 0},
|
||||
{"yes", 1})),
|
||||
OPT_STRING("volume-restore-data", mixer_restore_volume_data, 0),
|
||||
OPT_CHOICE("gapless-audio", gapless_audio, 0,
|
||||
({"no", 0},
|
||||
{"yes", 1},
|
||||
@@ -458,13 +462,15 @@ const m_option_t mp_opts[] = {
|
||||
#if HAVE_X11
|
||||
OPT_CHOICE("x11-netwm", vo.x11_netwm, 0,
|
||||
({"auto", 0}, {"no", -1}, {"yes", 1})),
|
||||
OPT_FLAG("x11-bypass-compositor", vo.x11_bypass_compositor, 0),
|
||||
OPT_CHOICE("x11-bypass-compositor", vo.x11_bypass_compositor, 0,
|
||||
({"no", 0}, {"yes", 1}, {"fs-only", 2}, {"never", 3})),
|
||||
#endif
|
||||
#if HAVE_WIN32
|
||||
OPT_STRING("vo-mmcss-profile", vo.mmcss_profile, M_OPT_FIXED),
|
||||
#endif
|
||||
|
||||
OPT_STRING("heartbeat-cmd", heartbeat_cmd, 0),
|
||||
OPT_STRING("heartbeat-cmd", heartbeat_cmd, 0,
|
||||
.deprecation_message = "use Lua scripting instead"),
|
||||
OPT_FLOAT("heartbeat-interval", heartbeat_interval, CONF_MIN, 0),
|
||||
|
||||
OPT_CHOICE_OR_INT("screen", vo.screen_id, 0, 0, 32,
|
||||
@@ -679,6 +685,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_REPLACED("ass-use-margins", "sub-use-margins"),
|
||||
OPT_REPLACED("media-title", "force-media-title"),
|
||||
OPT_REPLACED("input-unix-socket", "input-ipc-server"),
|
||||
OPT_REPLACED("softvol-max", "volume-max"),
|
||||
|
||||
{0}
|
||||
};
|
||||
@@ -692,8 +699,8 @@ const struct MPOpts mp_default_opts = {
|
||||
.deinterlace = -1,
|
||||
.softvol = SOFTVOL_AUTO,
|
||||
.softvol_max = 130,
|
||||
.mixer_init_volume = -1,
|
||||
.mixer_init_mute = -1,
|
||||
.softvol_volume = 100,
|
||||
.softvol_mute = 0,
|
||||
.gapless_audio = -1,
|
||||
.audio_buffer = 0.2,
|
||||
.audio_device = "auto",
|
||||
@@ -706,10 +713,12 @@ const struct MPOpts mp_default_opts = {
|
||||
.panscan = 0.0f,
|
||||
.keepaspect = 1,
|
||||
.keepaspect_window = 1,
|
||||
.taskbar_progress = 1,
|
||||
.border = 1,
|
||||
.fit_border = 1,
|
||||
.WinID = -1,
|
||||
.window_scale = 1.0,
|
||||
.x11_bypass_compositor = 0,
|
||||
.x11_bypass_compositor = 2,
|
||||
.mmcss_profile = "Playback",
|
||||
},
|
||||
.allow_win_drag = 1,
|
||||
|
||||
+4
-3
@@ -9,9 +9,11 @@
|
||||
typedef struct mp_vo_opts {
|
||||
struct m_obj_settings *video_driver_list, *vo_defs;
|
||||
|
||||
int taskbar_progress;
|
||||
int ontop;
|
||||
int fullscreen;
|
||||
int border;
|
||||
int fit_border;
|
||||
int all_workspaces;
|
||||
|
||||
int screen_id;
|
||||
@@ -87,9 +89,8 @@ typedef struct MPOpts {
|
||||
int ao_null_fallback;
|
||||
int force_vo;
|
||||
int softvol;
|
||||
float mixer_init_volume;
|
||||
int mixer_init_mute;
|
||||
char *mixer_restore_volume_data;
|
||||
float softvol_volume;
|
||||
int softvol_mute;
|
||||
float softvol_max;
|
||||
int gapless_audio;
|
||||
double audio_buffer;
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
typedef enum
|
||||
{
|
||||
kHIDRemoteModeNone = 0L,
|
||||
kHIDRemoteModeShared, // Share the remote with others - let's you listen to the remote control events as long as noone has an exclusive lock on it
|
||||
kHIDRemoteModeShared, // Share the remote with others - let's you listen to the remote control events as long as no one has an exclusive lock on it
|
||||
// (RECOMMENDED ONLY FOR SPECIAL PURPOSES)
|
||||
|
||||
kHIDRemoteModeExclusive, // Try to acquire an exclusive lock on the remote (NOT RECOMMENDED)
|
||||
@@ -182,7 +182,7 @@ typedef enum
|
||||
- (BOOL)hidRemote:(HIDRemote *)hidRemote // Invoked when new hardware is inspected
|
||||
inspectNewHardwareWithService:(io_service_t)service //
|
||||
prematchResult:(BOOL)prematchResult; // Return YES if HIDRemote should go on with this hardware and try
|
||||
// to use it, or NO if it should not be persued further.
|
||||
// to use it, or NO if it should not be pursued further.
|
||||
|
||||
// Exlusive lock lending
|
||||
- (BOOL)hidRemote:(HIDRemote *)hidRemote
|
||||
|
||||
@@ -293,7 +293,7 @@ static HIDRemote *sHIDRemote = nil;
|
||||
|
||||
}while(0);
|
||||
|
||||
// An error occured. Do necessary clean up.
|
||||
// An error occurred. Do necessary clean up.
|
||||
if (matchDict!=NULL)
|
||||
{
|
||||
CFRelease(matchDict);
|
||||
@@ -1422,7 +1422,7 @@ static HIDRemote *sHIDRemote = nil;
|
||||
[((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self failedNewHardwareWithError:error];
|
||||
}
|
||||
|
||||
// An error occured or this device is not of interest .. cleanup ..
|
||||
// An error occurred or this device is not of interest .. cleanup ..
|
||||
if (serviceNotification!=0)
|
||||
{
|
||||
IOObjectRelease(serviceNotification);
|
||||
@@ -1615,7 +1615,7 @@ static HIDRemote *sHIDRemote = nil;
|
||||
switch (buttonCode)
|
||||
{
|
||||
case kHIDRemoteButtonCodeIDChanged:
|
||||
// Do nothing, this is handled seperately
|
||||
// Do nothing, this is handled separately
|
||||
break;
|
||||
|
||||
case kHIDRemoteButtonCodeUp:
|
||||
|
||||
@@ -45,6 +45,10 @@ static void *run_subprocess(void *ptr)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mp_devnull(void *ctx, char *data, size_t size)
|
||||
{
|
||||
}
|
||||
|
||||
void mp_subprocess_detached(struct mp_log *log, char **args)
|
||||
{
|
||||
struct subprocess_args *p = talloc_zero(NULL, struct subprocess_args);
|
||||
|
||||
@@ -24,6 +24,8 @@ struct mp_cancel;
|
||||
|
||||
typedef void (*subprocess_read_cb)(void *ctx, char *data, size_t size);
|
||||
|
||||
void mp_devnull(void *ctx, char *data, size_t size);
|
||||
|
||||
// Start a subprocess. Uses callbacks to read from stdout and stderr.
|
||||
int mp_subprocess(char **args, struct mp_cancel *cancel, void *ctx,
|
||||
subprocess_read_cb on_stdout, subprocess_read_cb on_stderr,
|
||||
|
||||
@@ -275,7 +275,7 @@ static void enable_kx(bool enable)
|
||||
// shouldn't be relied on here either.
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
char *cmd = enable ? "\033=" : "\033>";
|
||||
write(STDOUT_FILENO, cmd, strlen(cmd));
|
||||
(void)write(STDOUT_FILENO, cmd, strlen(cmd));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ static void quit_request_sighandler(int signum)
|
||||
{
|
||||
do_deactivate_getch2();
|
||||
|
||||
write(death_pipe[1], &(char){0}, 1);
|
||||
(void)write(death_pipe[1], &(char){0}, 1);
|
||||
}
|
||||
|
||||
static void *terminal_thread(void *ptr)
|
||||
@@ -450,7 +450,7 @@ void terminal_uninit(void)
|
||||
do_deactivate_getch2();
|
||||
|
||||
if (input_ctx) {
|
||||
write(death_pipe[1], &(char){0}, 1);
|
||||
(void)write(death_pipe[1], &(char){0}, 1);
|
||||
pthread_join(input_thread, NULL);
|
||||
close(death_pipe[0]);
|
||||
close(death_pipe[1]);
|
||||
|
||||
@@ -25,19 +25,21 @@
|
||||
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
|
||||
|
||||
typedef struct {
|
||||
char static_mutex;
|
||||
INIT_ONCE static_init;
|
||||
CRITICAL_SECTION cs;
|
||||
char use_cs;
|
||||
union {
|
||||
SRWLOCK srw;
|
||||
CRITICAL_SECTION cs;
|
||||
} lock;
|
||||
} pthread_mutex_t;
|
||||
|
||||
#define PTHREAD_MUTEX_INITIALIZER {1, INIT_ONCE_STATIC_INIT}
|
||||
// Assume SRWLOCK_INIT is {0} so we can easily remain C89-compatible.
|
||||
#define PTHREAD_MUTEX_INITIALIZER {0}
|
||||
|
||||
#define pthread_mutexattr_t int
|
||||
#define pthread_mutexattr_destroy(attr) (void)0
|
||||
#define pthread_mutexattr_init(attr) (*(attr) = 0)
|
||||
#define pthread_mutexattr_settype(attr, type) (void)0
|
||||
// CRITICAL_SECTION is always recursive
|
||||
#define PTHREAD_MUTEX_RECURSIVE 0
|
||||
#define pthread_mutexattr_settype(attr, type) (*(attr) = (type))
|
||||
#define PTHREAD_MUTEX_RECURSIVE 1
|
||||
|
||||
int pthread_mutex_destroy(pthread_mutex_t *mutex);
|
||||
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
|
||||
|
||||
+24
-13
@@ -32,35 +32,40 @@ int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
|
||||
|
||||
int pthread_mutex_destroy(pthread_mutex_t *mutex)
|
||||
{
|
||||
DeleteCriticalSection(&mutex->cs);
|
||||
if (mutex->use_cs)
|
||||
DeleteCriticalSection(&mutex->lock.cs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
|
||||
const pthread_mutexattr_t *restrict attr)
|
||||
{
|
||||
InitializeCriticalSection(&mutex->cs);
|
||||
mutex->use_cs = attr && (*attr & PTHREAD_MUTEX_RECURSIVE);
|
||||
if (mutex->use_cs) {
|
||||
InitializeCriticalSection(&mutex->lock.cs);
|
||||
} else {
|
||||
InitializeSRWLock(&mutex->lock.srw);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_mutex_lock(pthread_mutex_t *mutex)
|
||||
{
|
||||
if (mutex->static_mutex) {
|
||||
BOOL pending;
|
||||
if (!InitOnceBeginInitialize(&mutex->static_init, 0, &pending, NULL))
|
||||
abort();
|
||||
if (pending) {
|
||||
InitializeCriticalSection(&mutex->cs);
|
||||
InitOnceComplete(&mutex->static_init, 0, NULL);
|
||||
}
|
||||
if (mutex->use_cs) {
|
||||
EnterCriticalSection(&mutex->lock.cs);
|
||||
} else {
|
||||
AcquireSRWLockExclusive(&mutex->lock.srw);
|
||||
}
|
||||
EnterCriticalSection(&mutex->cs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_mutex_unlock(pthread_mutex_t *mutex)
|
||||
{
|
||||
LeaveCriticalSection(&mutex->cs);
|
||||
if (mutex->use_cs) {
|
||||
LeaveCriticalSection(&mutex->lock.cs);
|
||||
} else {
|
||||
ReleaseSRWLockExclusive(&mutex->lock.srw);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -68,7 +73,13 @@ static int cond_wait(pthread_cond_t *restrict cond,
|
||||
pthread_mutex_t *restrict mutex,
|
||||
DWORD ms)
|
||||
{
|
||||
return SleepConditionVariableCS(cond, &mutex->cs, ms) ? 0 : ETIMEDOUT;
|
||||
BOOL res;
|
||||
if (mutex->use_cs) {
|
||||
res = SleepConditionVariableCS(cond, &mutex->lock.cs, ms);
|
||||
} else {
|
||||
res = SleepConditionVariableSRW(cond, &mutex->lock.srw, ms, 0);
|
||||
}
|
||||
return res ? 0 : ETIMEDOUT;
|
||||
}
|
||||
|
||||
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
|
||||
|
||||
+11
-6
@@ -132,7 +132,7 @@ static int recreate_audio_filters(struct MPContext *mpctx)
|
||||
if (afs->initialized < 1 && af_init(afs) < 0)
|
||||
goto fail;
|
||||
|
||||
mixer_reinit_audio(mpctx->mixer, mpctx->ao, afs);
|
||||
mixer_reinit_audio(mpctx->mixer, afs);
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
|
||||
|
||||
@@ -886,13 +886,18 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
|
||||
|
||||
assert(mpctx->audio_status >= STATUS_FILLING);
|
||||
|
||||
// We already have as much data as the audio device wants, and can start
|
||||
// writing it any time.
|
||||
if (mpctx->audio_status == STATUS_FILLING)
|
||||
mpctx->audio_status = STATUS_READY;
|
||||
|
||||
// Even if we're done decoding and syncing, let video start first - this is
|
||||
// required, because sending audio to the AO already starts playback.
|
||||
if (mpctx->audio_status == STATUS_FILLING && mpctx->vo_chain &&
|
||||
!mpctx->vo_chain->is_coverart && mpctx->video_status <= STATUS_READY)
|
||||
{
|
||||
mpctx->audio_status = STATUS_READY;
|
||||
return;
|
||||
if (mpctx->audio_status == STATUS_READY) {
|
||||
if (mpctx->vo_chain && !mpctx->vo_chain->is_coverart &&
|
||||
mpctx->video_status <= STATUS_READY)
|
||||
return;
|
||||
MP_VERBOSE(mpctx, "starting audio playback\n");
|
||||
}
|
||||
|
||||
bool audio_eof = status == AD_EOF;
|
||||
|
||||
+12
-36
@@ -280,7 +280,7 @@ static void wakeup_client(struct mpv_handle *ctx)
|
||||
if (ctx->wakeup_cb)
|
||||
ctx->wakeup_cb(ctx->wakeup_cb_ctx);
|
||||
if (ctx->wakeup_pipe[0] != -1)
|
||||
write(ctx->wakeup_pipe[1], &(char){0}, 1);
|
||||
(void)write(ctx->wakeup_pipe[1], &(char){0}, 1);
|
||||
}
|
||||
pthread_mutex_unlock(&ctx->wakeup_lock);
|
||||
}
|
||||
@@ -1071,42 +1071,18 @@ static void setproperty_fn(void *arg)
|
||||
struct setproperty_request *req = arg;
|
||||
const struct m_option *type = get_mp_type(req->format);
|
||||
|
||||
int err;
|
||||
switch (req->format) {
|
||||
case MPV_FORMAT_STRING: {
|
||||
// Go through explicit string conversion. M_PROPERTY_SET_NODE doesn't
|
||||
// do this, because it tries to be somewhat type-strict. But the client
|
||||
// needs a way to set everything by string.
|
||||
char *s = *(char **)req->data;
|
||||
MP_VERBOSE(req->mpctx, "Set property string: %s='%s'\n", req->name, s);
|
||||
err = mp_property_do(req->name, M_PROPERTY_SET_STRING, s, req->mpctx);
|
||||
break;
|
||||
}
|
||||
case MPV_FORMAT_NODE:
|
||||
case MPV_FORMAT_FLAG:
|
||||
case MPV_FORMAT_INT64:
|
||||
case MPV_FORMAT_DOUBLE: {
|
||||
struct mpv_node node;
|
||||
if (req->format == MPV_FORMAT_NODE) {
|
||||
node = *(struct mpv_node *)req->data;
|
||||
} else {
|
||||
// These are basically emulated via mpv_node.
|
||||
node.format = req->format;
|
||||
memcpy(&node.u, req->data, type->type->size);
|
||||
}
|
||||
if (mp_msg_test(req->mpctx->log, MSGL_V)) {
|
||||
struct m_option ot = {.type = &m_option_type_node};
|
||||
char *t = m_option_print(&ot, &node);
|
||||
MP_VERBOSE(req->mpctx, "Set property: %s=%s\n", req->name, t ? t : "?");
|
||||
talloc_free(t);
|
||||
}
|
||||
err = mp_property_do(req->name, M_PROPERTY_SET_NODE, &node, req->mpctx);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
abort();
|
||||
struct mpv_node *node;
|
||||
struct mpv_node tmp;
|
||||
if (req->format == MPV_FORMAT_NODE) {
|
||||
node = req->data;
|
||||
} else {
|
||||
tmp.format = req->format;
|
||||
memcpy(&tmp.u, req->data, type->type->size);
|
||||
node = &tmp;
|
||||
}
|
||||
|
||||
int err = mp_property_do(req->name, M_PROPERTY_SET_NODE, node, req->mpctx);
|
||||
|
||||
req->status = translate_property_error(err);
|
||||
|
||||
if (req->reply_ctx) {
|
||||
@@ -1583,7 +1559,7 @@ int mpv_get_wakeup_pipe(mpv_handle *ctx)
|
||||
pthread_mutex_lock(&ctx->wakeup_lock);
|
||||
if (ctx->wakeup_pipe[0] == -1) {
|
||||
if (mp_make_wakeup_pipe(ctx->wakeup_pipe) >= 0)
|
||||
write(ctx->wakeup_pipe[1], &(char){0}, 1);
|
||||
(void)write(ctx->wakeup_pipe[1], &(char){0}, 1);
|
||||
}
|
||||
int fd = ctx->wakeup_pipe[0];
|
||||
pthread_mutex_unlock(&ctx->wakeup_lock);
|
||||
|
||||
+367
-212
@@ -60,6 +60,7 @@
|
||||
#include "audio/filter/af.h"
|
||||
#include "video/decode/dec_video.h"
|
||||
#include "audio/decode/dec_audio.h"
|
||||
#include "video/out/bitmap_packer.h"
|
||||
#include "options/path.h"
|
||||
#include "screenshot.h"
|
||||
|
||||
@@ -88,7 +89,8 @@ struct command_ctx {
|
||||
// One of these is in use by the OSD; the other one exists so that the
|
||||
// bitmap list can be manipulated without additional synchronization.
|
||||
struct sub_bitmaps overlay_osd[2];
|
||||
struct sub_bitmaps *overlay_osd_current;
|
||||
int overlay_osd_current;
|
||||
struct bitmap_packer *overlay_packer;
|
||||
|
||||
struct hook_handler **hooks;
|
||||
int num_hooks;
|
||||
@@ -98,9 +100,8 @@ struct command_ctx {
|
||||
};
|
||||
|
||||
struct overlay {
|
||||
void *map_start;
|
||||
size_t map_size;
|
||||
struct sub_bitmap osd;
|
||||
struct mp_image *source;
|
||||
int x, y;
|
||||
};
|
||||
|
||||
struct hook_handler {
|
||||
@@ -112,6 +113,13 @@ struct hook_handler {
|
||||
bool active; // hook is currently in progress (only 1 at a time for now)
|
||||
};
|
||||
|
||||
// U+279C HEAVY ROUND-TIPPED RIGHTWARDS ARROW
|
||||
// U+00A0 NO-BREAK SPACE
|
||||
#define ARROW_SP "\342\236\234\302\240"
|
||||
|
||||
const char list_current[] = OSD_ASS_0 ARROW_SP OSD_ASS_1;
|
||||
const char list_normal[] = OSD_ASS_0 "{\\alpha&HFF}" ARROW_SP "{\\r}" OSD_ASS_1;
|
||||
|
||||
static int edit_filters(struct MPContext *mpctx, struct mp_log *log,
|
||||
enum stream_type mediatype,
|
||||
const char *cmd, const char *arg);
|
||||
@@ -870,10 +878,8 @@ static int mp_property_list_chapters(void *ctx, struct m_property *prop,
|
||||
char* time = mp_format_time(t, false);
|
||||
res = talloc_asprintf_append(res, "%s", time);
|
||||
talloc_free(time);
|
||||
char *m1 = "> ", *m2 = " <";
|
||||
if (n != cur)
|
||||
m1 = m2 = "";
|
||||
res = talloc_asprintf_append(res, " %s%s%s\n", m1, name, m2);
|
||||
const char *m = n == cur ? list_current : list_normal;
|
||||
res = talloc_asprintf_append(res, " %s%s\n", m, name);
|
||||
talloc_free(name);
|
||||
}
|
||||
|
||||
@@ -964,16 +970,13 @@ static int property_list_editions(void *ctx, struct m_property *prop,
|
||||
for (int n = 0; n < num_editions; n++) {
|
||||
struct demux_edition *ed = &editions[n];
|
||||
|
||||
if (n == current)
|
||||
res = talloc_asprintf_append(res, "> ");
|
||||
res = talloc_strdup_append(res, n == current ? list_current
|
||||
: list_normal);
|
||||
res = talloc_asprintf_append(res, "%d: ", n);
|
||||
char *title = mp_tags_get_str(ed->metadata, "title");
|
||||
if (!title)
|
||||
title = "unnamed";
|
||||
res = talloc_asprintf_append(res, "'%s' ", title);
|
||||
if (n == current)
|
||||
res = talloc_asprintf_append(res, "<");
|
||||
res = talloc_asprintf_append(res, "\n");
|
||||
res = talloc_asprintf_append(res, "'%s'\n", title);
|
||||
}
|
||||
|
||||
*(char **)arg = res;
|
||||
@@ -1518,10 +1521,10 @@ static int mp_property_cache_buffering(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
double state = get_cache_buffering_percentage(mpctx);
|
||||
int state = get_cache_buffering_percentage(mpctx);
|
||||
if (state < 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return m_property_int_ro(action, arg, state * 100);
|
||||
return m_property_int_ro(action, arg, state);
|
||||
}
|
||||
|
||||
static int mp_property_clock(void *ctx, struct m_property *prop,
|
||||
@@ -1566,44 +1569,29 @@ static int mp_property_volume(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_GET:
|
||||
mixer_getbothvolume(mpctx->mixer, arg);
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
*(struct m_option *)arg = (struct m_option){
|
||||
.type = CONF_TYPE_FLOAT,
|
||||
.flags = M_OPT_RANGE,
|
||||
.min = 0,
|
||||
.max = mixer_getmaxvolume(mpctx->mixer),
|
||||
.max = opts->softvol_max,
|
||||
};
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET_NEUTRAL:
|
||||
*(float *)arg = 100;
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_PRINT: {
|
||||
float val;
|
||||
mixer_getbothvolume(mpctx->mixer, &val);
|
||||
*(char **)arg = talloc_asprintf(NULL, "%i", (int)val);
|
||||
case M_PROPERTY_PRINT:
|
||||
*(char **)arg = talloc_asprintf(NULL, "%i", (int)opts->softvol_volume);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_SET:
|
||||
mixer_setvolume(mpctx->mixer, *(float *) arg, *(float *) arg);
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_SWITCH: {
|
||||
struct m_property_switch_arg *sarg = arg;
|
||||
mixer_addvolume(mpctx->mixer, sarg->inc);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
}
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static int mp_property_volume_max(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
return m_property_float_ro(action, arg, mixer_getmaxvolume(mpctx->mixer));
|
||||
int r = mp_property_generic_option(mpctx, prop, action, arg);
|
||||
if (action == M_PROPERTY_SET)
|
||||
mixer_update_volume(mpctx->mixer);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// Mute (RW)
|
||||
@@ -1611,34 +1599,76 @@ static int mp_property_mute(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
switch (action) {
|
||||
case M_PROPERTY_SET:
|
||||
mixer_setmute(mpctx->mixer, *(int *) arg);
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET:
|
||||
*(int *)arg = mixer_getmute(mpctx->mixer);
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
|
||||
if (action == M_PROPERTY_GET_TYPE) {
|
||||
*(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_FLAG};
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
|
||||
int r = mp_property_generic_option(mpctx, prop, action, arg);
|
||||
if (action == M_PROPERTY_SET)
|
||||
mixer_update_volume(mpctx->mixer);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int mp_property_ao_volume(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct ao *ao = mpctx->ao;
|
||||
if (!ao)
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_SET: {
|
||||
float value = *(float *)arg;
|
||||
ao_control_vol_t vol = {value, value};
|
||||
if (ao_control(ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_GET: {
|
||||
ao_control_vol_t vol = {0};
|
||||
if (ao_control(ao, AOCONTROL_GET_VOLUME, &vol) != CONTROL_OK)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
*(float *)arg = (vol.left + vol.right) / 2.0f;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
*(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_FLOAT};
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static int mp_property_volrestore(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
|
||||
static int mp_property_ao_mute(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
switch (action) {
|
||||
case M_PROPERTY_GET: {
|
||||
char *s = mixer_get_volume_restore_data(mpctx->mixer);
|
||||
*(char **)arg = s;
|
||||
return s ? M_PROPERTY_OK : M_PROPERTY_UNAVAILABLE;
|
||||
}
|
||||
case M_PROPERTY_SET:
|
||||
struct ao *ao = mpctx->ao;
|
||||
if (!ao)
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_SET: {
|
||||
bool value = *(int *)arg;
|
||||
if (ao_control(ao, AOCONTROL_SET_MUTE, &value) != CONTROL_OK)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return mp_property_generic_option(mpctx, prop, action, arg);
|
||||
case M_PROPERTY_GET: {
|
||||
bool value = false;
|
||||
if (ao_control(ao, AOCONTROL_GET_MUTE, &value) != CONTROL_OK)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
*(int *)arg = value;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
*(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_FLAG};
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static int get_device_entry(int item, int action, void *arg, void *ctx)
|
||||
@@ -2033,8 +2063,8 @@ static int property_list_tracks(void *ctx, struct m_property *prop,
|
||||
|
||||
res = talloc_asprintf_append(res, "%s: ",
|
||||
track_type_name(track->type));
|
||||
if (track->selected)
|
||||
res = talloc_asprintf_append(res, "> ");
|
||||
res = talloc_strdup_append(res,
|
||||
track->selected ? list_current : list_normal);
|
||||
res = talloc_asprintf_append(res, "(%d) ", track->user_tid);
|
||||
if (track->title)
|
||||
res = talloc_asprintf_append(res, "'%s' ", track->title);
|
||||
@@ -2042,8 +2072,6 @@ static int property_list_tracks(void *ctx, struct m_property *prop,
|
||||
res = talloc_asprintf_append(res, "(%s) ", track->lang);
|
||||
if (track->is_external)
|
||||
res = talloc_asprintf_append(res, "(external) ");
|
||||
if (track->selected)
|
||||
res = talloc_asprintf_append(res, "<");
|
||||
res = talloc_asprintf_append(res, "\n");
|
||||
}
|
||||
|
||||
@@ -2166,6 +2194,49 @@ static int mp_property_hwdec(void *ctx, struct m_property *prop,
|
||||
return mp_property_generic_option(mpctx, prop, action, arg);
|
||||
}
|
||||
|
||||
static int mp_property_hwdec_current(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct track *track = mpctx->current_track[0][STREAM_VIDEO];
|
||||
struct dec_video *vd = track ? track->d_video : NULL;
|
||||
|
||||
if (!vd)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_GET_TYPE: {
|
||||
// Abuse another hwdec option to resolve the value names
|
||||
struct m_property dummy = {.name = "hwdec"};
|
||||
return mp_property_generic_option(mpctx, &dummy, action, arg);
|
||||
}
|
||||
case M_PROPERTY_GET: {
|
||||
int current = HWDEC_NONE;
|
||||
video_vd_control(vd, VDCTRL_GET_HWDEC, ¤t);
|
||||
*(int *)arg = current;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
}
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static int mp_property_hwdec_interop(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->video_out || !mpctx->video_out->hwdec_devs)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
struct mp_hwdec_ctx *hwctx =
|
||||
hwdec_devices_get_first(mpctx->video_out->hwdec_devs);
|
||||
|
||||
const char *name = hwctx ? hwctx->driver_name : NULL;
|
||||
if (!name && hwctx)
|
||||
name = m_opt_choice_str(mp_hwdec_names, hwctx->type);
|
||||
|
||||
return m_property_strdup_ro(action, arg, name);
|
||||
}
|
||||
|
||||
static int mp_property_hwdec_active(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
@@ -2199,8 +2270,11 @@ static int mp_property_detected_hwdec(void *ctx, struct m_property *prop,
|
||||
if (vd)
|
||||
video_vd_control(vd, VDCTRL_GET_HWDEC, ¤t);
|
||||
|
||||
if (current <= 0 && vd && vd->hwdec_info && vd->hwdec_info->hwctx)
|
||||
current = vd->hwdec_info->hwctx->type;
|
||||
if (current <= 0 && vd && vd->hwdec_devs) {
|
||||
struct mp_hwdec_ctx *hwctx = hwdec_devices_get_first(vd->hwdec_devs);
|
||||
if (hwctx)
|
||||
current = hwctx->type;
|
||||
}
|
||||
|
||||
// In case of the "-copy" ones, which are "detected" every time the
|
||||
// decoder is opened, return "no" if no decoding is active.
|
||||
@@ -2211,84 +2285,6 @@ static int mp_property_detected_hwdec(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
#define VF_DEINTERLACE_LABEL "deinterlace"
|
||||
|
||||
static bool probe_deint_filter(struct MPContext *mpctx, const char *filt)
|
||||
{
|
||||
char filter[80];
|
||||
// add a label so that removing the filter is easier
|
||||
snprintf(filter, sizeof(filter), "@%s:%s", VF_DEINTERLACE_LABEL, filt);
|
||||
return edit_filters(mpctx, mp_null_log, STREAM_VIDEO, "pre", filter) >= 0;
|
||||
}
|
||||
|
||||
static bool check_output_format(struct MPContext *mpctx, int imgfmt)
|
||||
{
|
||||
struct vo_chain *vo_c = mpctx->vo_chain;
|
||||
if (!vo_c)
|
||||
return false;
|
||||
return vo_c->vf->allowed_output_formats[imgfmt - IMGFMT_START];
|
||||
}
|
||||
|
||||
static int probe_deint_filters(struct MPContext *mpctx)
|
||||
{
|
||||
if (check_output_format(mpctx, IMGFMT_VDPAU)) {
|
||||
char filter[80] = "vdpaupp:deint=yes";
|
||||
int pref = 0;
|
||||
vo_control(mpctx->video_out, VOCTRL_GET_PREF_DEINT, &pref);
|
||||
pref = pref < 0 ? -pref : pref;
|
||||
if (pref > 0 && pref <= 4) {
|
||||
const char *types[] =
|
||||
{"", "first-field", "bob", "temporal", "temporal-spatial"};
|
||||
mp_snprintf_cat(filter, sizeof(filter), ":deint-mode=%s",
|
||||
types[pref]);
|
||||
}
|
||||
|
||||
probe_deint_filter(mpctx, filter);
|
||||
return 0;
|
||||
}
|
||||
if (check_output_format(mpctx, IMGFMT_VAAPI) &&
|
||||
probe_deint_filter(mpctx, "vavpp"))
|
||||
return 0;
|
||||
if (probe_deint_filter(mpctx, "yadif"))
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int get_deinterlacing(struct MPContext *mpctx)
|
||||
{
|
||||
struct vo_chain *vo_c = mpctx->vo_chain;
|
||||
int enabled = 0;
|
||||
if (video_vf_vo_control(vo_c, VFCTRL_GET_DEINTERLACE, &enabled) != CONTROL_OK)
|
||||
enabled = -1;
|
||||
if (enabled < 0) {
|
||||
// vf_lavfi doesn't support VFCTRL_GET_DEINTERLACE
|
||||
if (vf_find_by_label(vo_c->vf, VF_DEINTERLACE_LABEL))
|
||||
enabled = 1;
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void remove_deint_filter(struct MPContext *mpctx)
|
||||
{
|
||||
edit_filters(mpctx, mp_null_log, STREAM_VIDEO, "del", "@" VF_DEINTERLACE_LABEL);
|
||||
}
|
||||
|
||||
void set_deinterlacing(struct MPContext *mpctx, bool enable)
|
||||
{
|
||||
struct vo_chain *vo_c = mpctx->vo_chain;
|
||||
if (vf_find_by_label(vo_c->vf, VF_DEINTERLACE_LABEL)) {
|
||||
if (!enable)
|
||||
remove_deint_filter(mpctx);
|
||||
} else {
|
||||
if ((get_deinterlacing(mpctx) > 0) != enable) {
|
||||
int arg = enable;
|
||||
if (video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &arg) != CONTROL_OK)
|
||||
probe_deint_filters(mpctx);
|
||||
}
|
||||
}
|
||||
mpctx->opts->deinterlace = get_deinterlacing(mpctx) > 0;
|
||||
}
|
||||
|
||||
static int mp_property_deinterlace(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
@@ -2324,12 +2320,9 @@ static int panscan_property_helper(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->video_out
|
||||
|| vo_control(mpctx->video_out, VOCTRL_GET_PANSCAN, NULL) != VO_TRUE)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
int r = mp_property_generic_option(mpctx, prop, action, arg);
|
||||
if (action == M_PROPERTY_SET)
|
||||
if (mpctx->video_out && action == M_PROPERTY_SET)
|
||||
vo_control(mpctx->video_out, VOCTRL_SET_PANSCAN, NULL);
|
||||
return r;
|
||||
}
|
||||
@@ -2367,6 +2360,23 @@ static int mp_property_fullscreen(void *ctx, struct m_property *prop,
|
||||
return r;
|
||||
}
|
||||
|
||||
/// Show playback progress in Windows 7+ taskbar (RW)
|
||||
static int mp_property_taskbar_progress(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
if (action == M_PROPERTY_SET) {
|
||||
int desired = !!*(int *) arg;
|
||||
if (mpctx->opts->vo.taskbar_progress == desired)
|
||||
return M_PROPERTY_OK;
|
||||
mpctx->opts->vo.taskbar_progress = desired;
|
||||
if (mpctx->video_out)
|
||||
update_vo_playback_state( mpctx );
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return mp_property_generic_option(mpctx, prop, action, arg);
|
||||
}
|
||||
|
||||
/// Window always on top (RW)
|
||||
static int mp_property_ontop(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
@@ -2504,13 +2514,13 @@ static int property_imgparams(struct mp_image_params p, int action, void *arg)
|
||||
{"aspect", SUB_PROP_FLOAT(d_w / (double)d_h)},
|
||||
{"par", SUB_PROP_FLOAT(p.p_w / (double)p.p_h)},
|
||||
{"colormatrix",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_names, p.colorspace))},
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_names, p.color.space))},
|
||||
{"colorlevels",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_levels_names, p.colorlevels))},
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_levels_names, p.color.levels))},
|
||||
{"primaries",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_prim_names, p.primaries))},
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_prim_names, p.color.primaries))},
|
||||
{"gamma",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_trc_names, p.gamma))},
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_trc_names, p.color.gamma))},
|
||||
{"chroma-location",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_chroma_names, p.chroma_location))},
|
||||
{"stereo-in",
|
||||
@@ -2584,6 +2594,7 @@ static int mp_property_video_frame_info(void *ctx, struct m_property *prop,
|
||||
{0}
|
||||
};
|
||||
|
||||
talloc_free(f);
|
||||
return m_property_read_sub(props, action, arg);
|
||||
}
|
||||
|
||||
@@ -2722,6 +2733,39 @@ static int mp_property_vo_configured(void *ctx, struct m_property *prop,
|
||||
mpctx->video_out && mpctx->video_out->config_ok);
|
||||
}
|
||||
|
||||
static int mp_property_vo_performance(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->video_out)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
// Return the type right away if requested, to avoid having to
|
||||
// go through a completely unnecessary VOCTRL
|
||||
if (action == M_PROPERTY_GET_TYPE) {
|
||||
*(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE};
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
|
||||
struct voctrl_performance_data data = {0};
|
||||
if (vo_control(mpctx->video_out, VOCTRL_PERFORMANCE_DATA, &data) <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
#define SUB_PROP_PERFDATA(N) \
|
||||
{#N "-last", SUB_PROP_INT64(data.N.last)}, \
|
||||
{#N "-avg", SUB_PROP_INT64(data.N.avg)}, \
|
||||
{#N "-peak", SUB_PROP_INT64(data.N.peak)}
|
||||
|
||||
struct m_sub_property props[] = {
|
||||
SUB_PROP_PERFDATA(upload),
|
||||
SUB_PROP_PERFDATA(render),
|
||||
SUB_PROP_PERFDATA(present),
|
||||
{0}
|
||||
};
|
||||
|
||||
return m_property_read_sub(props, action, arg);
|
||||
}
|
||||
|
||||
static int mp_property_vo(void *ctx, struct m_property *p, int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
@@ -2766,8 +2810,8 @@ static int mp_property_osd_ass(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
struct m_sub_property props[] = {
|
||||
{"0", SUB_PROP_STR(osd_ass_0)},
|
||||
{"1", SUB_PROP_STR(osd_ass_1)},
|
||||
{"0", SUB_PROP_STR(OSD_ASS_0)},
|
||||
{"1", SUB_PROP_STR(OSD_ASS_1)},
|
||||
{0}
|
||||
};
|
||||
return m_property_read_sub(props, action, arg);
|
||||
@@ -3075,8 +3119,8 @@ static int mp_property_dvb_channel_name(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static int mp_property_playlist_pos(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
static int mp_property_playlist_pos_x(void *ctx, struct m_property *prop,
|
||||
int action, void *arg, int base)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct playlist *pl = mpctx->playlist;
|
||||
@@ -3088,11 +3132,12 @@ static int mp_property_playlist_pos(void *ctx, struct m_property *prop,
|
||||
int pos = playlist_entry_to_index(pl, pl->current);
|
||||
if (pos < 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
*(int *)arg = pos;
|
||||
*(int *)arg = pos + base;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_SET: {
|
||||
struct playlist_entry *e = playlist_entry_from_index(pl, *(int *)arg);
|
||||
int pos = *(int *)arg - base;
|
||||
struct playlist_entry *e = playlist_entry_from_index(pl, pos);
|
||||
if (!e)
|
||||
return M_PROPERTY_ERROR;
|
||||
mp_set_playlist_entry(mpctx, e);
|
||||
@@ -3102,8 +3147,8 @@ static int mp_property_playlist_pos(void *ctx, struct m_property *prop,
|
||||
struct m_option opt = {
|
||||
.type = CONF_TYPE_INT,
|
||||
.flags = CONF_RANGE,
|
||||
.min = 0,
|
||||
.max = playlist_entry_count(pl) - 1,
|
||||
.min = base,
|
||||
.max = playlist_entry_count(pl) - 1 + base,
|
||||
};
|
||||
*(struct m_option *)arg = opt;
|
||||
return M_PROPERTY_OK;
|
||||
@@ -3112,6 +3157,18 @@ static int mp_property_playlist_pos(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static int mp_property_playlist_pos(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
return mp_property_playlist_pos_x(ctx, prop, action, arg, 0);
|
||||
}
|
||||
|
||||
static int mp_property_playlist_pos_1(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
return mp_property_playlist_pos_x(ctx, prop, action, arg, 1);
|
||||
}
|
||||
|
||||
static int get_playlist_entry(int item, int action, void *arg, void *ctx)
|
||||
{
|
||||
struct MPContext *mpctx = ctx;
|
||||
@@ -3147,11 +3204,9 @@ static int mp_property_playlist(void *ctx, struct m_property *prop,
|
||||
if (s[0])
|
||||
p = s;
|
||||
}
|
||||
if (mpctx->playlist->current == e) {
|
||||
res = talloc_asprintf_append(res, "> %s <\n", p);
|
||||
} else {
|
||||
res = talloc_asprintf_append(res, "%s\n", p);
|
||||
}
|
||||
const char *m = mpctx->playlist->current == e ?
|
||||
list_current : list_normal;
|
||||
res = talloc_asprintf_append(res, "%s%s\n", m, p);
|
||||
}
|
||||
|
||||
*(char **)arg = res;
|
||||
@@ -3222,11 +3277,8 @@ static int mp_property_ab_loop(void *ctx, struct m_property *prop,
|
||||
int r = mp_property_generic_option(mpctx, prop, action, arg);
|
||||
if (r > 0 && action == M_PROPERTY_SET) {
|
||||
if (strcmp(prop->name, "ab-loop-b") == 0) {
|
||||
double now = mpctx->playback_pts;
|
||||
if (now != MP_NOPTS_VALUE && opts->ab_loop[0] != MP_NOPTS_VALUE &&
|
||||
opts->ab_loop[1] != MP_NOPTS_VALUE && now >= opts->ab_loop[1])
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, opts->ab_loop[0],
|
||||
MPSEEK_EXACT, false);
|
||||
struct command_ctx *cctx = mpctx->command_ctx;
|
||||
cctx->prev_pts = opts->ab_loop[0];
|
||||
}
|
||||
// Update if visible
|
||||
set_osd_bar_chapters(mpctx, OSD_BAR_SEEK);
|
||||
@@ -3639,13 +3691,16 @@ static const struct m_property mp_properties[] = {
|
||||
|
||||
{"playlist", mp_property_playlist},
|
||||
{"playlist-pos", mp_property_playlist_pos},
|
||||
{"playlist-pos-1", mp_property_playlist_pos_1},
|
||||
M_PROPERTY_ALIAS("playlist-count", "playlist/count"),
|
||||
|
||||
// Audio
|
||||
{"mixer-active", mp_property_mixer_active},
|
||||
{"volume", mp_property_volume},
|
||||
{"volume-max", mp_property_volume_max},
|
||||
{"volume-max", mp_property_generic_option},
|
||||
{"mute", mp_property_mute},
|
||||
{"ao-volume", mp_property_ao_volume},
|
||||
{"ao-mute", mp_property_ao_mute},
|
||||
{"audio-delay", mp_property_audio_delay},
|
||||
{"audio-codec-name", mp_property_audio_codec_name},
|
||||
{"audio-codec", mp_property_audio_codec},
|
||||
@@ -3655,7 +3710,6 @@ static const struct m_property mp_properties[] = {
|
||||
M_PROPERTY_DEPRECATED_ALIAS("audio-channels", "audio-params/channel-count"),
|
||||
{"aid", mp_property_audio},
|
||||
{"balance", mp_property_balance},
|
||||
{"volume-restore-data", mp_property_volrestore},
|
||||
{"audio-device", mp_property_audio_device},
|
||||
{"audio-device-list", mp_property_audio_devices},
|
||||
{"current-ao", mp_property_ao},
|
||||
@@ -3665,6 +3719,7 @@ static const struct m_property mp_properties[] = {
|
||||
{"fullscreen", mp_property_fullscreen},
|
||||
{"deinterlace", mp_property_deinterlace},
|
||||
{"field-dominance", mp_property_generic_option},
|
||||
{"taskbar-progress", mp_property_taskbar_progress},
|
||||
{"ontop", mp_property_ontop},
|
||||
{"border", mp_property_border},
|
||||
{"on-all-workspaces", mp_property_all_workspaces},
|
||||
@@ -3695,6 +3750,7 @@ static const struct m_property mp_properties[] = {
|
||||
M_PROPERTY_ALIAS("height", "video-params/h"),
|
||||
{"window-scale", mp_property_window_scale},
|
||||
{"vo-configured", mp_property_vo_configured},
|
||||
{"vo-performance", mp_property_vo_performance},
|
||||
{"current-vo", mp_property_vo},
|
||||
{"fps", mp_property_fps},
|
||||
{"estimated-vf-fps", mp_property_vf_fps},
|
||||
@@ -3703,6 +3759,8 @@ static const struct m_property mp_properties[] = {
|
||||
{"program", mp_property_program},
|
||||
{"hwdec", mp_property_hwdec},
|
||||
{"hwdec-active", mp_property_hwdec_active},
|
||||
{"hwdec-current", mp_property_hwdec_current},
|
||||
{"hwdec-interop", mp_property_hwdec_interop},
|
||||
{"hwdec-detected", mp_property_detected_hwdec},
|
||||
|
||||
{"estimated-frame-count", mp_property_frame_count},
|
||||
@@ -3829,7 +3887,7 @@ static const char *const *const mp_event_property_change[] = {
|
||||
"colormatrix-output-range", "colormatrix-primaries", "video-aspect"),
|
||||
E(MPV_EVENT_AUDIO_RECONFIG, "audio-format", "audio-codec", "audio-bitrate",
|
||||
"samplerate", "channels", "audio", "volume", "mute", "balance",
|
||||
"volume-restore-data", "current-ao", "audio-codec-name", "audio-params",
|
||||
"current-ao", "audio-codec-name", "audio-params",
|
||||
"audio-out-params", "volume-max", "mixer-active"),
|
||||
E(MPV_EVENT_SEEK, "seeking", "core-idle", "eof-reached"),
|
||||
E(MPV_EVENT_PLAYBACK_RESTART, "seeking", "core-idle", "eof-reached"),
|
||||
@@ -3839,7 +3897,10 @@ static const char *const *const mp_event_property_change[] = {
|
||||
"demuxer-cache-duration", "demuxer-cache-idle", "paused-for-cache",
|
||||
"demuxer-cache-time", "cache-buffering-state", "cache-speed"),
|
||||
E(MP_EVENT_WIN_RESIZE, "window-scale", "osd-width", "osd-height", "osd-par"),
|
||||
E(MP_EVENT_WIN_STATE, "window-minimized", "display-names", "display-fps", "fullscreen"),
|
||||
E(MP_EVENT_WIN_STATE, "window-minimized", "display-names", "display-fps",
|
||||
"fullscreen"),
|
||||
E(MP_EVENT_CHANGE_PLAYLIST, "playlist", "playlist-pos", "playlist-pos-1",
|
||||
"playlist-count", "playlist/count"),
|
||||
};
|
||||
#undef E
|
||||
|
||||
@@ -3907,6 +3968,23 @@ int mp_property_do(const char *name, int action, void *val,
|
||||
int r = m_property_do(ctx->log, mp_properties, name, action, val, ctx);
|
||||
if (r == M_PROPERTY_OK && is_property_set(action, val))
|
||||
mp_notify_property(ctx, (char *)name);
|
||||
if (mp_msg_test(ctx->log, MSGL_V) && is_property_set(action, val)) {
|
||||
struct m_option ot = {0};
|
||||
void *data = val;
|
||||
switch (action) {
|
||||
case M_PROPERTY_SET_NODE:
|
||||
ot.type = &m_option_type_node;
|
||||
break;
|
||||
case M_PROPERTY_SET_STRING:
|
||||
ot.type = &m_option_type_string;
|
||||
data = &val;
|
||||
break;
|
||||
}
|
||||
char *t = ot.type ? m_option_print(&ot, data) : NULL;
|
||||
MP_VERBOSE(ctx, "Set property: %s%s%s -> %d\n",
|
||||
name, t ? "=" : "", t ? t : "", r);
|
||||
talloc_free(t);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -3979,6 +4057,7 @@ static const struct property_osd_display {
|
||||
{ "balance", "Balance", .osd_progbar = OSD_BALANCE },
|
||||
// video
|
||||
{ "panscan", "Panscan", .osd_progbar = OSD_PANSCAN },
|
||||
{ "taskbar-progress", "Progress in taskbar" },
|
||||
{ "ontop", "Stay on top" },
|
||||
{ "border", "Border" },
|
||||
{ "framedrop", "Framedrop" },
|
||||
@@ -4182,20 +4261,85 @@ static int edit_filters_osd(struct MPContext *mpctx, enum stream_type mediatype,
|
||||
static void recreate_overlays(struct MPContext *mpctx)
|
||||
{
|
||||
struct command_ctx *cmd = mpctx->command_ctx;
|
||||
struct sub_bitmaps *new = &cmd->overlay_osd[0];
|
||||
if (new == cmd->overlay_osd_current)
|
||||
new += 1; // pick the unused one
|
||||
int overlay_next = !cmd->overlay_osd_current;
|
||||
struct sub_bitmaps *new = &cmd->overlay_osd[overlay_next];
|
||||
new->format = SUBBITMAP_RGBA;
|
||||
new->change_id = 1;
|
||||
// overlay array can have unused entries, but parts list must be "packed"
|
||||
|
||||
bool valid = false;
|
||||
|
||||
new->num_parts = 0;
|
||||
for (int n = 0; n < cmd->num_overlays; n++) {
|
||||
struct overlay *o = &cmd->overlays[n];
|
||||
if (o->osd.bitmap)
|
||||
MP_TARRAY_APPEND(cmd, new->parts, new->num_parts, o->osd);
|
||||
if (o->source) {
|
||||
struct mp_image *s = o->source;
|
||||
struct sub_bitmap b = {
|
||||
.bitmap = s->planes[0],
|
||||
.stride = s->stride[0],
|
||||
.w = s->w, .dw = s->w,
|
||||
.h = s->h, .dh = s->h,
|
||||
.x = o->x,
|
||||
.y = o->y,
|
||||
};
|
||||
MP_TARRAY_APPEND(cmd, new->parts, new->num_parts, b);
|
||||
}
|
||||
}
|
||||
cmd->overlay_osd_current = new;
|
||||
osd_set_external2(mpctx->osd, cmd->overlay_osd_current);
|
||||
|
||||
if (!cmd->overlay_packer)
|
||||
cmd->overlay_packer = talloc_zero(cmd, struct bitmap_packer);
|
||||
|
||||
cmd->overlay_packer->padding = 1; // assume bilinear scaling
|
||||
packer_set_size(cmd->overlay_packer, new->num_parts);
|
||||
|
||||
for (int n = 0; n < new->num_parts; n++)
|
||||
cmd->overlay_packer->in[n] = (struct pos){new->parts[n].w, new->parts[n].h};
|
||||
|
||||
if (packer_pack(cmd->overlay_packer) < 0 || new->num_parts == 0)
|
||||
goto done;
|
||||
|
||||
struct pos bb[2];
|
||||
packer_get_bb(cmd->overlay_packer, bb);
|
||||
|
||||
new->packed_w = bb[1].x;
|
||||
new->packed_h = bb[1].y;
|
||||
|
||||
if (!new->packed || new->packed->w < new->packed_w ||
|
||||
new->packed->h < new->packed_h)
|
||||
{
|
||||
talloc_free(new->packed);
|
||||
new->packed = mp_image_alloc(IMGFMT_BGRA, cmd->overlay_packer->w,
|
||||
cmd->overlay_packer->h);
|
||||
if (!new->packed)
|
||||
goto done;
|
||||
}
|
||||
|
||||
// clear padding
|
||||
mp_image_clear(new->packed, 0, 0, new->packed->w, new->packed->h);
|
||||
|
||||
for (int n = 0; n < new->num_parts; n++) {
|
||||
struct sub_bitmap *b = &new->parts[n];
|
||||
struct pos pos = cmd->overlay_packer->result[n];
|
||||
|
||||
int stride = new->packed->stride[0];
|
||||
void *pdata = (uint8_t *)new->packed->planes[0] + pos.y * stride + pos.x * 4;
|
||||
memcpy_pic(pdata, b->bitmap, b->w * 4, b->h, stride, b->stride);
|
||||
|
||||
b->bitmap = pdata;
|
||||
b->stride = stride;
|
||||
|
||||
b->src_x = pos.x;
|
||||
b->src_y = pos.y;
|
||||
}
|
||||
|
||||
valid = true;
|
||||
done:
|
||||
if (!valid) {
|
||||
new->format = SUBBITMAP_EMPTY;
|
||||
new->num_parts = 0;
|
||||
}
|
||||
|
||||
osd_set_external2(mpctx->osd, new);
|
||||
cmd->overlay_osd_current = overlay_next;
|
||||
}
|
||||
|
||||
// Set overlay with the given ID to the contents as described by "new".
|
||||
@@ -4210,17 +4354,11 @@ static void replace_overlay(struct MPContext *mpctx, int id, struct overlay *new
|
||||
}
|
||||
|
||||
struct overlay *ptr = &cmd->overlays[id];
|
||||
struct overlay old = *ptr;
|
||||
|
||||
if (!ptr->osd.bitmap && !new->osd.bitmap)
|
||||
return; // don't need to recreate or unmap
|
||||
|
||||
talloc_free(ptr->source);
|
||||
*ptr = *new;
|
||||
recreate_overlays(mpctx);
|
||||
|
||||
// Do this afterwards, so we never unmap while the OSD is using it.
|
||||
if (old.map_start && old.map_size)
|
||||
munmap(old.map_start, old.map_size);
|
||||
recreate_overlays(mpctx);
|
||||
}
|
||||
|
||||
static int overlay_add(struct MPContext *mpctx, int id, int x, int y,
|
||||
@@ -4236,18 +4374,17 @@ static int overlay_add(struct MPContext *mpctx, int id, int x, int y,
|
||||
MP_ERR(mpctx, "overlay_add: invalid id %d\n", id);
|
||||
goto error;
|
||||
}
|
||||
if (w < 0 || h < 0 || stride < w * 4 || (stride % 4)) {
|
||||
if (w <= 0 || h <= 0 || stride < w * 4 || (stride % 4)) {
|
||||
MP_ERR(mpctx, "overlay_add: inconsistent parameters\n");
|
||||
goto error;
|
||||
}
|
||||
struct overlay overlay = {
|
||||
.osd = {
|
||||
.stride = stride,
|
||||
.x = x, .y = y,
|
||||
.w = w, .h = h,
|
||||
.dw = w, .dh = h,
|
||||
},
|
||||
.source = mp_image_alloc(IMGFMT_BGRA, w, h),
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
if (!overlay.source)
|
||||
goto error;
|
||||
int fd = -1;
|
||||
bool close_fd = true;
|
||||
void *p = NULL;
|
||||
@@ -4266,21 +4403,25 @@ static int overlay_add(struct MPContext *mpctx, int id, int x, int y,
|
||||
} else {
|
||||
fd = open(file, O_RDONLY | O_BINARY | O_CLOEXEC);
|
||||
}
|
||||
int map_size = 0;
|
||||
if (fd >= 0) {
|
||||
overlay.map_size = offset + h * stride;
|
||||
void *m = mmap(NULL, overlay.map_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
map_size = offset + h * stride;
|
||||
void *m = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (close_fd)
|
||||
close(fd);
|
||||
if (m && m != MAP_FAILED) {
|
||||
overlay.map_start = m;
|
||||
if (m && m != MAP_FAILED)
|
||||
p = m;
|
||||
}
|
||||
}
|
||||
if (!p) {
|
||||
MP_ERR(mpctx, "overlay_add: could not open or map '%s'\n", file);
|
||||
talloc_free(overlay.source);
|
||||
goto error;
|
||||
}
|
||||
overlay.osd.bitmap = (char *)p + offset;
|
||||
memcpy_pic(overlay.source->planes[0], (char *)p + offset, w * 4, h,
|
||||
overlay.source->stride[0], stride);
|
||||
if (map_size)
|
||||
munmap(p, map_size);
|
||||
|
||||
replace_overlay(mpctx, id, &overlay);
|
||||
r = 0;
|
||||
error:
|
||||
@@ -4302,6 +4443,8 @@ static void overlay_uninit(struct MPContext *mpctx)
|
||||
for (int id = 0; id < cmd->num_overlays; id++)
|
||||
overlay_remove(mpctx, id);
|
||||
osd_set_external2(mpctx->osd, NULL);
|
||||
for (int n = 0; n < 2; n++)
|
||||
mp_image_unrefp(&cmd->overlay_osd[n].packed);
|
||||
}
|
||||
|
||||
struct cycle_counter {
|
||||
@@ -4740,7 +4883,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
mp_write_watch_later_conf(mpctx);
|
||||
mp_set_playlist_entry(mpctx, entry);
|
||||
}
|
||||
mp_notify_property(mpctx, "playlist");
|
||||
mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4759,7 +4902,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
if (!append && mpctx->playlist->first)
|
||||
mp_set_playlist_entry(mpctx, new ? new : mpctx->playlist->first);
|
||||
|
||||
mp_notify_property(mpctx, "playlist");
|
||||
mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL);
|
||||
} else {
|
||||
MP_ERR(mpctx, "Unable to load playlist %s.\n", filename);
|
||||
return -1;
|
||||
@@ -4780,7 +4923,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
}
|
||||
playlist_remove(mpctx->playlist, e);
|
||||
}
|
||||
mp_notify_property(mpctx, "playlist");
|
||||
mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4795,7 +4938,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
if (mpctx->playlist->current == e && !mpctx->stop_play)
|
||||
mpctx->stop_play = PT_CURRENT_ENTRY;
|
||||
playlist_remove(mpctx->playlist, e);
|
||||
mp_notify_property(mpctx, "playlist");
|
||||
mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4807,7 +4950,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
if (!e1)
|
||||
return -1;
|
||||
playlist_move(mpctx->playlist, e1, e2);
|
||||
mp_notify_property(mpctx, "playlist");
|
||||
mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4826,6 +4969,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
mpctx->add_osd_seek_info |=
|
||||
(msg_osd ? OSD_SEEK_INFO_TEXT : 0) |
|
||||
(bar_osd ? OSD_SEEK_INFO_BAR : 0);
|
||||
mpctx->osd_force_update = true;
|
||||
break;
|
||||
|
||||
case MP_CMD_TV_LAST_CHANNEL: {
|
||||
@@ -5230,16 +5374,24 @@ void handle_ab_loop(struct MPContext *mpctx)
|
||||
struct command_ctx *ctx = mpctx->command_ctx;
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
|
||||
if (opts->pause)
|
||||
return;
|
||||
|
||||
double now = mpctx->restart_complete ? mpctx->playback_pts : MP_NOPTS_VALUE;
|
||||
if (now != MP_NOPTS_VALUE && opts->ab_loop[0] != MP_NOPTS_VALUE &&
|
||||
opts->ab_loop[1] != MP_NOPTS_VALUE)
|
||||
if (now != MP_NOPTS_VALUE && (opts->ab_loop[0] != MP_NOPTS_VALUE ||
|
||||
opts->ab_loop[1] != MP_NOPTS_VALUE))
|
||||
{
|
||||
if (ctx->prev_pts >= opts->ab_loop[0] &&
|
||||
ctx->prev_pts < opts->ab_loop[1] &&
|
||||
(now >= opts->ab_loop[1] || mpctx->stop_play == AT_END_OF_FILE))
|
||||
double start = opts->ab_loop[0];
|
||||
if (start == MP_NOPTS_VALUE)
|
||||
start = 0;
|
||||
double end = opts->ab_loop[1];
|
||||
if (end == MP_NOPTS_VALUE)
|
||||
end = INFINITY;
|
||||
if (ctx->prev_pts >= start && ctx->prev_pts < end &&
|
||||
(now >= end || mpctx->stop_play == AT_END_OF_FILE))
|
||||
{
|
||||
mark_seek(mpctx);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, opts->ab_loop[0],
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, start,
|
||||
MPSEEK_EXACT, false);
|
||||
}
|
||||
}
|
||||
@@ -5260,6 +5412,9 @@ void handle_command_updates(struct MPContext *mpctx)
|
||||
|
||||
void mp_notify(struct MPContext *mpctx, int event, void *arg)
|
||||
{
|
||||
// The OSD can implicitly reference some properties.
|
||||
mpctx->osd_idle_update = true;
|
||||
|
||||
command_event(mpctx, event, arg);
|
||||
|
||||
mp_client_broadcast_event(mpctx, event, arg);
|
||||
|
||||
+1
-3
@@ -51,6 +51,7 @@ enum {
|
||||
MP_EVENT_CACHE_UPDATE,
|
||||
MP_EVENT_WIN_RESIZE,
|
||||
MP_EVENT_WIN_STATE,
|
||||
MP_EVENT_CHANGE_PLAYLIST,
|
||||
};
|
||||
|
||||
bool mp_hook_test_completion(struct MPContext *mpctx, char *type);
|
||||
@@ -58,7 +59,4 @@ void mp_hook_run(struct MPContext *mpctx, char *client, char *type);
|
||||
|
||||
void handle_ab_loop(struct MPContext *mpctx);
|
||||
|
||||
void remove_deint_filter(struct MPContext *mpctx);
|
||||
void set_deinterlacing(struct MPContext *mpctx, bool enable);
|
||||
|
||||
#endif /* MPLAYER_COMMAND_H */
|
||||
@@ -208,7 +208,8 @@ static const char *const backup_properties[] = {
|
||||
"options/speed",
|
||||
"options/edition",
|
||||
"options/pause",
|
||||
"volume-restore-data",
|
||||
"volume",
|
||||
"mute",
|
||||
"options/audio-delay",
|
||||
//"balance",
|
||||
"options/fullscreen",
|
||||
|
||||
+6
-3
@@ -153,7 +153,7 @@ struct track {
|
||||
struct vo_chain {
|
||||
struct mp_log *log;
|
||||
|
||||
struct mp_hwdec_info *hwdec_info;
|
||||
struct mp_hwdec_devices *hwdec_devs;
|
||||
double container_fps;
|
||||
|
||||
struct vf_chain *vf;
|
||||
@@ -243,7 +243,7 @@ typedef struct MPContext {
|
||||
double osd_msg_visible;
|
||||
double osd_msg_next_duration;
|
||||
double osd_last_update;
|
||||
bool osd_force_update;
|
||||
bool osd_force_update, osd_idle_update;
|
||||
char *osd_msg_text;
|
||||
bool osd_show_pos;
|
||||
struct osd_progbar_state osd_progbar;
|
||||
@@ -398,6 +398,7 @@ typedef struct MPContext {
|
||||
|
||||
bool paused_for_cache;
|
||||
double cache_stop_time, cache_wait_time;
|
||||
int cache_buffer;
|
||||
|
||||
// Set after showing warning about decoding being too slow for realtime
|
||||
// playback rate. Used to avoid showing it multiple times.
|
||||
@@ -515,7 +516,7 @@ char *chapter_display_name(struct MPContext *mpctx, int chapter);
|
||||
char *chapter_name(struct MPContext *mpctx, int chapter);
|
||||
double chapter_start_time(struct MPContext *mpctx, int chapter);
|
||||
int get_chapter_count(struct MPContext *mpctx);
|
||||
double get_cache_buffering_percentage(struct MPContext *mpctx);
|
||||
int get_cache_buffering_percentage(struct MPContext *mpctx);
|
||||
void execute_queued_seek(struct MPContext *mpctx);
|
||||
void run_playloop(struct MPContext *mpctx);
|
||||
void mp_idle(struct MPContext *mpctx);
|
||||
@@ -554,5 +555,7 @@ void uninit_video_out(struct MPContext *mpctx);
|
||||
void uninit_video_chain(struct MPContext *mpctx);
|
||||
double calc_average_frame_duration(struct MPContext *mpctx);
|
||||
int init_video_decoder(struct MPContext *mpctx, struct track *track);
|
||||
int get_deinterlacing(struct MPContext *mpctx);
|
||||
void set_deinterlacing(struct MPContext *mpctx, bool enable);
|
||||
|
||||
#endif /* MPLAYER_MP_CORE_H */
|
||||
@@ -176,7 +176,7 @@ static void append_dir_subtitles(struct mpv_global *global,
|
||||
prio += prio;
|
||||
char *subpath = mp_path_join_bstr(*slist, path, dename);
|
||||
if (mp_path_exists(subpath)) {
|
||||
MP_GROW_ARRAY(*slist, *nsub);
|
||||
MP_TARRAY_GROW(NULL, *slist, *nsub);
|
||||
struct subfn *sub = *slist + (*nsub)++;
|
||||
|
||||
// annoying and redundant
|
||||
|
||||
+8
-2
@@ -339,7 +339,7 @@ static bool init_pads(struct lavfi *c)
|
||||
goto error; // can happen if pad reassociation fails
|
||||
|
||||
if (pad->dir == LAVFI_OUT) {
|
||||
AVFilter *dst_filter;
|
||||
AVFilter *dst_filter = NULL;
|
||||
if (pad->type == STREAM_AUDIO) {
|
||||
dst_filter = avfilter_get_by_name("abuffersink");
|
||||
} else if (pad->type == STREAM_VIDEO) {
|
||||
@@ -348,6 +348,9 @@ static bool init_pads(struct lavfi *c)
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if (!dst_filter)
|
||||
goto error;
|
||||
|
||||
char name[256];
|
||||
snprintf(name, sizeof(name), "mpv_sink_%s", pad->name);
|
||||
|
||||
@@ -359,7 +362,7 @@ static bool init_pads(struct lavfi *c)
|
||||
goto error;
|
||||
} else {
|
||||
char src_args[256];
|
||||
AVFilter *src_filter;
|
||||
AVFilter *src_filter = NULL;
|
||||
|
||||
pad->input_eof |= !pad->connected;
|
||||
|
||||
@@ -410,6 +413,9 @@ static bool init_pads(struct lavfi *c)
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if (!src_filter)
|
||||
goto error;
|
||||
|
||||
char name[256];
|
||||
snprintf(name, sizeof(name), "mpv_src_%s", pad->name);
|
||||
|
||||
|
||||
+2
-1
@@ -452,7 +452,7 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type
|
||||
if (type == STREAM_VIDEO) {
|
||||
uninit_video_chain(mpctx);
|
||||
if (!track)
|
||||
handle_force_window(mpctx, false);
|
||||
handle_force_window(mpctx, true);
|
||||
} else if (type == STREAM_AUDIO) {
|
||||
clear_audio_output_buffers(mpctx);
|
||||
uninit_audio_chain(mpctx);
|
||||
@@ -973,6 +973,7 @@ static void play_current_file(struct MPContext *mpctx)
|
||||
mpctx->last_chapter = -2;
|
||||
mpctx->paused = false;
|
||||
mpctx->paused_for_cache = false;
|
||||
mpctx->cache_buffer = -1;
|
||||
mpctx->playing_msg_shown = false;
|
||||
mpctx->max_frames = -1;
|
||||
mpctx->video_speed = mpctx->audio_speed = opts->playback_speed;
|
||||
|
||||
@@ -275,6 +275,10 @@ function timer_mt.resume(t)
|
||||
end
|
||||
end
|
||||
|
||||
function timer_mt.is_enabled(t)
|
||||
return timers[t] ~= nil
|
||||
end
|
||||
|
||||
-- Return the timer that expires next.
|
||||
local function get_next_timer()
|
||||
local best = nil
|
||||
|
||||
@@ -153,13 +153,18 @@ mp.add_hook("on_load", 10, function ()
|
||||
|
||||
|
||||
-- some funky guessing to detect multi-arc videos
|
||||
if not (json.entries[1]["webpage_url"] == nil)
|
||||
and (json.entries[1]["webpage_url"] == json["webpage_url"]) then
|
||||
if (not (json.entries[1]["_type"] == "url_transparent")) and
|
||||
(not (json.entries[1]["webpage_url"] == nil)
|
||||
and (json.entries[1]["webpage_url"] == json["webpage_url"])) then
|
||||
msg.verbose("multi-arc video detected, building EDL")
|
||||
|
||||
local playlist = "edl://"
|
||||
for i, entry in pairs(json.entries) do
|
||||
playlist = playlist .. edl_escape(entry.url) .. ";"
|
||||
playlist = playlist .. edl_escape(entry.url)
|
||||
if not (entry.duration == nil) then
|
||||
playlist = playlist..",start=0,length="..entry.duration
|
||||
end
|
||||
playlist = playlist .. ";"
|
||||
end
|
||||
|
||||
msg.debug("EDL: " .. playlist)
|
||||
@@ -175,6 +180,21 @@ mp.add_hook("on_load", 10, function ()
|
||||
json.title)
|
||||
end
|
||||
|
||||
if not (json.entries[1].requested_subtitles == nil) then
|
||||
for j, req in pairs(json.entries[1].requested_subtitles) do
|
||||
local subfile = "edl://"
|
||||
for i, entry in pairs(json.entries) do
|
||||
subfile = subfile..edl_escape(entry.requested_subtitles[j].url)
|
||||
if not (entry.duration == nil) then
|
||||
subfile = subfile..",start=0,length="..entry.duration
|
||||
end
|
||||
subfile = subfile .. ";"
|
||||
end
|
||||
msg.debug(j.." sub EDL: "..subfile)
|
||||
mp.commandv("sub-add", subfile, "auto", req.ext, j)
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
local playlist = "#EXTM3U\n"
|
||||
@@ -186,7 +206,8 @@ mp.add_hook("on_load", 10, function ()
|
||||
-- directly to the file in that case, which we don't
|
||||
-- want so get the webpage URL instead, which is what
|
||||
-- we want
|
||||
if not (entry["webpage_url"] == nil) then
|
||||
if not (json.entries[1]["_type"] == "url_transparent")
|
||||
and not (entry["webpage_url"] == nil) then
|
||||
site = entry["webpage_url"]
|
||||
end
|
||||
|
||||
@@ -253,7 +274,7 @@ mp.add_hook("on_load", 10, function ()
|
||||
end
|
||||
|
||||
-- for rtmp
|
||||
if not (json.play_path == nil) then
|
||||
if (json.protocol == "rtmp") then
|
||||
local rtmp_prop = append_rtmp_prop(nil,
|
||||
"rtmp_tcurl", streamurl)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop,
|
||||
|
||||
@@ -418,6 +418,18 @@ int mp_initialize(struct MPContext *mpctx, char **options)
|
||||
if (handle_help_options(mpctx))
|
||||
return -2;
|
||||
|
||||
if (!print_libav_versions(mp_null_log, 0)) {
|
||||
// Using mismatched libraries can be legitimate, but even then it's
|
||||
// a bad idea. We don't acknowledge its usefulness and stability.
|
||||
print_libav_versions(mpctx->log, MSGL_FATAL);
|
||||
MP_FATAL(mpctx, "\nmpv was compiled against a different version of "
|
||||
"FFmpeg/Libav than the shared\nlibrary it is linked against. "
|
||||
"This is most likely a broken build and could\nresult in "
|
||||
"misbehavior and crashes.\n\nmpv does not support this "
|
||||
"configuration and will not run - rebuild mpv instead.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (opts->dump_stats && opts->dump_stats[0]) {
|
||||
if (mp_msg_open_stats_file(mpctx->global, opts->dump_stats) < 0)
|
||||
MP_ERR(mpctx, "Failed to open stats file '%s'\n", opts->dump_stats);
|
||||
|
||||
+12
-4
@@ -116,16 +116,24 @@ void update_vo_playback_state(struct MPContext *mpctx)
|
||||
if (mpctx->video_out) {
|
||||
struct voctrl_playback_state oldstate = mpctx->vo_playback_state;
|
||||
struct voctrl_playback_state newstate = {
|
||||
.taskbar_progress = mpctx->opts->vo.taskbar_progress,
|
||||
.playing = mpctx->playing,
|
||||
.paused = mpctx->paused,
|
||||
.percent_pos = get_percent_pos(mpctx),
|
||||
};
|
||||
|
||||
if (oldstate.playing != newstate.playing ||
|
||||
if (oldstate.taskbar_progress != newstate.taskbar_progress ||
|
||||
oldstate.playing != newstate.playing ||
|
||||
oldstate.paused != newstate.paused ||
|
||||
oldstate.percent_pos != newstate.percent_pos) {
|
||||
vo_control(mpctx->video_out,
|
||||
VOCTRL_UPDATE_PLAYBACK_STATE, &newstate);
|
||||
oldstate.percent_pos != newstate.percent_pos)
|
||||
{
|
||||
// Don't update progress bar if it was and still is hidden
|
||||
if ((oldstate.playing && oldstate.taskbar_progress) ||
|
||||
(newstate.playing && newstate.taskbar_progress))
|
||||
{
|
||||
vo_control(mpctx->video_out,
|
||||
VOCTRL_UPDATE_PLAYBACK_STATE, &newstate);
|
||||
}
|
||||
mpctx->vo_playback_state = newstate;
|
||||
}
|
||||
} else {
|
||||
|
||||
+19
-4
@@ -129,6 +129,12 @@ static void term_osd_set_status(struct MPContext *mpctx, const char *text)
|
||||
{
|
||||
talloc_free(mpctx->term_osd_status);
|
||||
mpctx->term_osd_status = talloc_strdup(mpctx, text);
|
||||
|
||||
int w = 80, h = 24;
|
||||
terminal_get_size(&w, &h);
|
||||
if (strlen(mpctx->term_osd_status) > w)
|
||||
mpctx->term_osd_status[w] = '\0';
|
||||
|
||||
term_osd_update(mpctx);
|
||||
}
|
||||
|
||||
@@ -362,14 +368,15 @@ void set_osd_bar_chapters(struct MPContext *mpctx, int type)
|
||||
mpctx->osd_progbar.num_stops = 0;
|
||||
double len = get_time_length(mpctx);
|
||||
if (len > 0) {
|
||||
if (opts->ab_loop[0] != MP_NOPTS_VALUE &&
|
||||
opts->ab_loop[1] != MP_NOPTS_VALUE)
|
||||
{
|
||||
if (opts->ab_loop[0] != MP_NOPTS_VALUE) {
|
||||
MP_TARRAY_APPEND(mpctx, mpctx->osd_progbar.stops,
|
||||
mpctx->osd_progbar.num_stops, opts->ab_loop[0] / len);
|
||||
}
|
||||
if (opts->ab_loop[1] != MP_NOPTS_VALUE) {
|
||||
MP_TARRAY_APPEND(mpctx, mpctx->osd_progbar.stops,
|
||||
mpctx->osd_progbar.num_stops, opts->ab_loop[1] / len);
|
||||
} else {
|
||||
}
|
||||
if (mpctx->osd_progbar.num_stops == 0) {
|
||||
int num = get_chapter_count(mpctx);
|
||||
for (int n = 0; n < num; n++) {
|
||||
double time = chapter_start_time(mpctx, n);
|
||||
@@ -491,6 +498,10 @@ void update_osd_msg(struct MPContext *mpctx)
|
||||
double now = mp_time_sec();
|
||||
|
||||
if (!mpctx->osd_force_update) {
|
||||
// Assume nothing is going on at all.
|
||||
if (!mpctx->osd_idle_update)
|
||||
return;
|
||||
|
||||
double delay = 0.050; // update the OSD at most this often
|
||||
double diff = now - mpctx->osd_last_update;
|
||||
if (diff < delay) {
|
||||
@@ -499,12 +510,14 @@ void update_osd_msg(struct MPContext *mpctx)
|
||||
}
|
||||
}
|
||||
mpctx->osd_force_update = false;
|
||||
mpctx->osd_idle_update = false;
|
||||
mpctx->osd_last_update = now;
|
||||
|
||||
if (mpctx->osd_visible) {
|
||||
double sleep = mpctx->osd_visible - now;
|
||||
if (sleep > 0) {
|
||||
mpctx->sleeptime = MPMIN(mpctx->sleeptime, sleep);
|
||||
mpctx->osd_idle_update = true;
|
||||
} else {
|
||||
mpctx->osd_visible = 0;
|
||||
mpctx->osd_progbar.type = -1; // disable
|
||||
@@ -516,6 +529,7 @@ void update_osd_msg(struct MPContext *mpctx)
|
||||
double sleep = mpctx->osd_function_visible - now;
|
||||
if (sleep > 0) {
|
||||
mpctx->sleeptime = MPMIN(mpctx->sleeptime, sleep);
|
||||
mpctx->osd_idle_update = true;
|
||||
} else {
|
||||
mpctx->osd_function_visible = 0;
|
||||
mpctx->osd_function = 0;
|
||||
@@ -533,6 +547,7 @@ void update_osd_msg(struct MPContext *mpctx)
|
||||
double sleep = mpctx->osd_msg_visible - now;
|
||||
if (sleep > 0) {
|
||||
mpctx->sleeptime = MPMIN(mpctx->sleeptime, sleep);
|
||||
mpctx->osd_idle_update = true;
|
||||
} else {
|
||||
talloc_free(mpctx->osd_msg_text);
|
||||
mpctx->osd_msg_text = NULL;
|
||||
|
||||
+81
-49
@@ -141,7 +141,7 @@ void add_step_frame(struct MPContext *mpctx, int dir)
|
||||
mpctx->step_frames += 1;
|
||||
unpause_player(mpctx);
|
||||
} else if (dir < 0) {
|
||||
if (!mpctx->hrseek_backstep || !mpctx->hrseek_active) {
|
||||
if (!mpctx->hrseek_active) {
|
||||
queue_seek(mpctx, MPSEEK_BACKSTEP, 0, MPSEEK_VERY_EXACT, true);
|
||||
pause_player(mpctx);
|
||||
}
|
||||
@@ -180,17 +180,17 @@ void reset_playback_state(struct MPContext *mpctx)
|
||||
#endif
|
||||
}
|
||||
|
||||
// return -1 if seek failed (non-seekable stream?), 0 otherwise
|
||||
static int mp_seek(MPContext *mpctx, struct seek_params seek)
|
||||
static void mp_seek(MPContext *mpctx, struct seek_params seek)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
|
||||
if (!mpctx->demuxer || seek.type == MPSEEK_NONE || seek.amount == MP_NOPTS_VALUE)
|
||||
return -1;
|
||||
return;
|
||||
|
||||
if (!mpctx->demuxer->seekable) {
|
||||
MP_ERR(mpctx, "Can't seek in this file.\n");
|
||||
return -1;
|
||||
MP_ERR(mpctx, "Cannot seek in this file.\n");
|
||||
MP_ERR(mpctx, "You can forcibly enable it with '--force-seekable=yes'.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
bool hr_seek_very_exact = seek.exact == MPSEEK_VERY_EXACT;
|
||||
@@ -291,8 +291,6 @@ static int mp_seek(MPContext *mpctx, struct seek_params seek)
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_SEEK, NULL);
|
||||
mp_notify(mpctx, MPV_EVENT_TICK, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This combines consecutive seek requests.
|
||||
@@ -343,7 +341,7 @@ void execute_queued_seek(struct MPContext *mpctx)
|
||||
/* If the user seeks continuously (keeps arrow key down)
|
||||
* try to finish showing a frame from one location before doing
|
||||
* another seek (which could lead to unchanging display). */
|
||||
if (!mpctx->seek.immediate && mpctx->video_status < STATUS_READY &&
|
||||
if (!mpctx->seek.immediate && mpctx->video_status < STATUS_PLAYING &&
|
||||
mp_time_sec() - mpctx->start_timestamp < 0.3)
|
||||
return;
|
||||
mp_seek(mpctx, mpctx->seek);
|
||||
@@ -504,7 +502,8 @@ static void handle_osd_redraw(struct MPContext *mpctx)
|
||||
return;
|
||||
}
|
||||
// Don't redraw immediately during a seek (makes it significantly slower).
|
||||
if (mpctx->vo_chain && mp_time_sec() - mpctx->start_timestamp < 0.1) {
|
||||
bool use_video = mpctx->vo_chain && !mpctx->vo_chain->is_coverart;
|
||||
if (use_video && mp_time_sec() - mpctx->start_timestamp < 0.1) {
|
||||
mpctx->sleeptime = MPMIN(mpctx->sleeptime, 0.1);
|
||||
return;
|
||||
}
|
||||
@@ -518,23 +517,27 @@ static void handle_osd_redraw(struct MPContext *mpctx)
|
||||
|
||||
static void handle_pause_on_low_cache(struct MPContext *mpctx)
|
||||
{
|
||||
bool force_update = false;
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
if (!mpctx->demuxer)
|
||||
return;
|
||||
|
||||
struct stream_cache_info info = {0};
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info);
|
||||
int idle = info.size > 0 ? info.idle : -1;
|
||||
double now = mp_time_sec();
|
||||
|
||||
struct stream_cache_info c = {.idle = true};
|
||||
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &c);
|
||||
|
||||
struct demux_ctrl_reader_state s = {.idle = true, .ts_duration = -1};
|
||||
demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s);
|
||||
|
||||
if (mpctx->restart_complete && idle != -1) {
|
||||
int cache_buffer = 100;
|
||||
|
||||
if (mpctx->restart_complete && c.size > 0) {
|
||||
if (mpctx->paused && mpctx->paused_for_cache) {
|
||||
if (!opts->cache_pausing || s.ts_duration >= mpctx->cache_wait_time
|
||||
|| s.idle)
|
||||
{
|
||||
double elapsed_time = mp_time_sec() - mpctx->cache_stop_time;
|
||||
double elapsed_time = now - mpctx->cache_stop_time;
|
||||
if (elapsed_time > mpctx->cache_wait_time) {
|
||||
mpctx->cache_wait_time *= 1.5 + 0.1;
|
||||
} else {
|
||||
@@ -543,7 +546,7 @@ static void handle_pause_on_low_cache(struct MPContext *mpctx)
|
||||
mpctx->paused_for_cache = false;
|
||||
if (!opts->pause)
|
||||
unpause_player(mpctx);
|
||||
mp_notify(mpctx, MP_EVENT_CACHE_UPDATE, NULL);
|
||||
force_update = true;
|
||||
}
|
||||
mpctx->sleeptime = MPMIN(mpctx->sleeptime, 0.2);
|
||||
} else {
|
||||
@@ -552,45 +555,52 @@ static void handle_pause_on_low_cache(struct MPContext *mpctx)
|
||||
pause_player(mpctx);
|
||||
mpctx->paused_for_cache = true;
|
||||
opts->pause = prev_paused_user;
|
||||
mpctx->cache_stop_time = mp_time_sec();
|
||||
mp_notify(mpctx, MP_EVENT_CACHE_UPDATE, NULL);
|
||||
mpctx->cache_stop_time = now;
|
||||
force_update = true;
|
||||
}
|
||||
}
|
||||
mpctx->cache_wait_time = MPCLAMP(mpctx->cache_wait_time, 1, 10);
|
||||
if (mpctx->paused_for_cache) {
|
||||
cache_buffer =
|
||||
100 * MPCLAMP(s.ts_duration / mpctx->cache_wait_time, 0, 0.99);
|
||||
}
|
||||
}
|
||||
|
||||
// Also update cache properties.
|
||||
bool busy = idle == 0;
|
||||
if (!s.idle) {
|
||||
busy |= idle != -1;
|
||||
busy |= mp_client_event_is_registered(mpctx, MP_EVENT_CACHE_UPDATE);
|
||||
}
|
||||
bool busy = !s.idle || !c.idle;
|
||||
if (busy || mpctx->next_cache_update > 0) {
|
||||
double now = mp_time_sec();
|
||||
if (mpctx->next_cache_update <= now) {
|
||||
mpctx->next_cache_update = busy ? now + 0.25 : 0;
|
||||
mp_notify(mpctx, MP_EVENT_CACHE_UPDATE, NULL);
|
||||
force_update = true;
|
||||
}
|
||||
if (mpctx->next_cache_update > 0) {
|
||||
mpctx->sleeptime =
|
||||
MPMIN(mpctx->sleeptime, mpctx->next_cache_update - now);
|
||||
}
|
||||
}
|
||||
|
||||
if (mpctx->cache_buffer != cache_buffer) {
|
||||
if (mpctx->cache_buffer >= 0 &&
|
||||
(mpctx->cache_buffer == 100) != (cache_buffer == 100))
|
||||
{
|
||||
if (cache_buffer < 100) {
|
||||
MP_VERBOSE(mpctx, "Enter buffering.\n");
|
||||
} else {
|
||||
double t = now - mpctx->cache_stop_time;
|
||||
MP_VERBOSE(mpctx, "End buffering (waited %f secs).\n", t);
|
||||
}
|
||||
}
|
||||
mpctx->cache_buffer = cache_buffer;
|
||||
force_update = true;
|
||||
}
|
||||
|
||||
if (force_update)
|
||||
mp_notify(mpctx, MP_EVENT_CACHE_UPDATE, NULL);
|
||||
}
|
||||
|
||||
double get_cache_buffering_percentage(struct MPContext *mpctx)
|
||||
int get_cache_buffering_percentage(struct MPContext *mpctx)
|
||||
{
|
||||
if (mpctx->demuxer && mpctx->paused_for_cache && mpctx->cache_wait_time > 0) {
|
||||
struct demux_ctrl_reader_state s = {.idle = true, .ts_duration = -1};
|
||||
demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s);
|
||||
if (s.ts_duration < 0)
|
||||
s.ts_duration = 0;
|
||||
|
||||
return MPCLAMP(s.ts_duration / mpctx->cache_wait_time, 0.0, 1.0);
|
||||
}
|
||||
if (mpctx->demuxer && !mpctx->paused_for_cache)
|
||||
return 1.0;
|
||||
return -1;
|
||||
return mpctx->demuxer ? mpctx->cache_buffer : -1;
|
||||
}
|
||||
|
||||
static void handle_heartbeat_cmd(struct MPContext *mpctx)
|
||||
@@ -600,7 +610,7 @@ static void handle_heartbeat_cmd(struct MPContext *mpctx)
|
||||
double now = mp_time_sec();
|
||||
if (mpctx->next_heartbeat <= now) {
|
||||
mpctx->next_heartbeat = now + opts->heartbeat_interval;
|
||||
system(opts->heartbeat_cmd);
|
||||
(void)system(opts->heartbeat_cmd);
|
||||
}
|
||||
mpctx->sleeptime = MPMIN(mpctx->sleeptime, mpctx->next_heartbeat - now);
|
||||
}
|
||||
@@ -826,6 +836,21 @@ static void handle_dummy_ticks(struct MPContext *mpctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Update current playback time.
|
||||
static void handle_playback_time(struct MPContext *mpctx)
|
||||
{
|
||||
if (mpctx->vo_chain && !mpctx->vo_chain->is_coverart &&
|
||||
mpctx->video_status >= STATUS_PLAYING &&
|
||||
mpctx->video_status < STATUS_EOF)
|
||||
{
|
||||
mpctx->playback_pts = mpctx->video_pts;
|
||||
} else if (mpctx->audio_status >= STATUS_PLAYING &&
|
||||
mpctx->audio_status < STATUS_EOF)
|
||||
{
|
||||
mpctx->playback_pts = playing_audio_pts(mpctx);
|
||||
}
|
||||
}
|
||||
|
||||
// We always make sure audio and video buffers are filled before actually
|
||||
// starting playback. This code handles starting them at the same time.
|
||||
static void handle_playback_restart(struct MPContext *mpctx)
|
||||
@@ -842,8 +867,17 @@ static void handle_playback_restart(struct MPContext *mpctx)
|
||||
mpctx->sleeptime = 0;
|
||||
}
|
||||
|
||||
if (mpctx->audio_status == STATUS_READY)
|
||||
if (mpctx->audio_status == STATUS_READY) {
|
||||
// If a new seek is queued while the current one finishes, don't
|
||||
// actually play the audio, but resume seeking immediately.
|
||||
if (mpctx->seek.type && mpctx->video_status == STATUS_PLAYING) {
|
||||
handle_playback_time(mpctx);
|
||||
execute_queued_seek(mpctx);
|
||||
return;
|
||||
}
|
||||
|
||||
fill_audio_out_buffers(mpctx); // actually play prepared buffer
|
||||
}
|
||||
|
||||
if (!mpctx->restart_complete) {
|
||||
mpctx->hrseek_active = false;
|
||||
@@ -866,6 +900,7 @@ static void handle_playback_restart(struct MPContext *mpctx)
|
||||
}
|
||||
}
|
||||
mpctx->playing_msg_shown = true;
|
||||
mpctx->sleeptime = 0;
|
||||
MP_VERBOSE(mpctx, "playback restart complete\n");
|
||||
}
|
||||
}
|
||||
@@ -942,9 +977,6 @@ void run_playloop(struct MPContext *mpctx)
|
||||
handle_heartbeat_cmd(mpctx);
|
||||
handle_command_updates(mpctx);
|
||||
|
||||
fill_audio_out_buffers(mpctx);
|
||||
write_video(mpctx);
|
||||
|
||||
if (mpctx->lavfi) {
|
||||
if (lavfi_process(mpctx->lavfi))
|
||||
mpctx->sleeptime = 0;
|
||||
@@ -952,15 +984,12 @@ void run_playloop(struct MPContext *mpctx)
|
||||
mpctx->stop_play = AT_END_OF_FILE;
|
||||
}
|
||||
|
||||
fill_audio_out_buffers(mpctx);
|
||||
write_video(mpctx);
|
||||
|
||||
handle_playback_restart(mpctx);
|
||||
|
||||
// Use the audio timestamp if no video, or video is enabled, but has ended.
|
||||
if (mpctx->video_status == STATUS_EOF &&
|
||||
mpctx->audio_status >= STATUS_PLAYING &&
|
||||
mpctx->audio_status < STATUS_EOF)
|
||||
{
|
||||
mpctx->playback_pts = playing_audio_pts(mpctx);
|
||||
}
|
||||
handle_playback_time(mpctx);
|
||||
|
||||
handle_dummy_ticks(mpctx);
|
||||
|
||||
@@ -978,6 +1007,9 @@ void run_playloop(struct MPContext *mpctx)
|
||||
|
||||
handle_sstep(mpctx);
|
||||
|
||||
if (mpctx->stop_play == AT_END_OF_FILE && mpctx->seek.type)
|
||||
mpctx->stop_play = KEEP_PLAYING;
|
||||
|
||||
if (mpctx->stop_play)
|
||||
return;
|
||||
|
||||
|
||||
+5
-3
@@ -287,9 +287,10 @@ static char *gen_fname(screenshot_ctx *ctx, const char *file_ext)
|
||||
void *t = fname;
|
||||
dir = mp_get_user_path(t, ctx->mpctx->global, dir);
|
||||
fname = mp_path_join(NULL, dir, fname);
|
||||
talloc_free(t);
|
||||
|
||||
mp_mkdirp(dir);
|
||||
|
||||
talloc_free(t);
|
||||
}
|
||||
|
||||
if (!mp_path_exists(fname))
|
||||
@@ -345,8 +346,9 @@ static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode)
|
||||
}
|
||||
}
|
||||
|
||||
if (image && mpctx->vo_chain && mpctx->vo_chain->hwdec_info) {
|
||||
struct mp_hwdec_ctx *ctx = mpctx->vo_chain->hwdec_info->hwctx;
|
||||
if (image && mpctx->vo_chain && mpctx->vo_chain->hwdec_devs) {
|
||||
struct mp_hwdec_ctx *ctx =
|
||||
hwdec_devices_get_first(mpctx->vo_chain->hwdec_devs);
|
||||
struct mp_image *nimage = NULL;
|
||||
if (ctx && ctx->download_image && (image->fmt.flags & MP_IMGFLAG_HWACCEL))
|
||||
nimage = ctx->download_image(ctx, image, NULL);
|
||||
|
||||
+100
-35
@@ -49,6 +49,8 @@
|
||||
#include "command.h"
|
||||
#include "screenshot.h"
|
||||
|
||||
#define VF_DEINTERLACE_LABEL "deinterlace"
|
||||
|
||||
enum {
|
||||
// update_video() - code also uses: <0 error, 0 eof, >0 progress
|
||||
VD_ERROR = -1,
|
||||
@@ -153,8 +155,45 @@ static int try_filter(struct vo_chain *vo_c, char *name, char *label, char **arg
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool check_output_format(struct vo_chain *vo_c, int imgfmt)
|
||||
{
|
||||
return vo_c->vf->output_params.imgfmt == imgfmt;
|
||||
}
|
||||
|
||||
static int probe_deint_filters(struct vo_chain *vo_c)
|
||||
{
|
||||
// Usually, we prefer inserting/removing deint filters. But If there's VO
|
||||
// support, or the user inserted a filter that supports swichting deint and
|
||||
// that has no VF_DEINTERLACE_LABEL, or if the filter was auto-inserted
|
||||
// for other reasons and supports switching deint (like vf_d3d11vpp), then
|
||||
// use the runtime switching method.
|
||||
if (video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &(int){1}) == CONTROL_OK)
|
||||
return 0;
|
||||
|
||||
if (check_output_format(vo_c, IMGFMT_VDPAU)) {
|
||||
char *args[5] = {"deint", "yes"};
|
||||
int pref = 0;
|
||||
vo_control(vo_c->vo, VOCTRL_GET_PREF_DEINT, &pref);
|
||||
pref = pref < 0 ? -pref : pref;
|
||||
if (pref > 0 && pref <= 4) {
|
||||
const char *types[] =
|
||||
{"", "first-field", "bob", "temporal", "temporal-spatial"};
|
||||
args[2] = "deint-mode";
|
||||
args[3] = (char *)types[pref];
|
||||
}
|
||||
|
||||
return try_filter(vo_c, "vdpaupp", VF_DEINTERLACE_LABEL, args);
|
||||
}
|
||||
if (check_output_format(vo_c, IMGFMT_VAAPI))
|
||||
return try_filter(vo_c, "vavpp", VF_DEINTERLACE_LABEL, NULL);
|
||||
if (check_output_format(vo_c, IMGFMT_D3D11VA) ||
|
||||
check_output_format(vo_c, IMGFMT_D3D11NV12))
|
||||
return try_filter(vo_c, "d3d11vpp", VF_DEINTERLACE_LABEL, NULL);
|
||||
return try_filter(vo_c, "yadif", VF_DEINTERLACE_LABEL, NULL);
|
||||
}
|
||||
|
||||
// Reconfigure the filter chain according to the new input format.
|
||||
static void filter_reconfig(struct vo_chain *vo_c)
|
||||
static void filter_reconfig(struct MPContext *mpctx, struct vo_chain *vo_c)
|
||||
{
|
||||
struct mp_image_params params = vo_c->input_format;
|
||||
if (!params.imgfmt)
|
||||
@@ -162,19 +201,21 @@ static void filter_reconfig(struct vo_chain *vo_c)
|
||||
|
||||
set_allowed_vo_formats(vo_c);
|
||||
|
||||
if (vf_reconfig(vo_c->vf, ¶ms) < 0)
|
||||
return;
|
||||
|
||||
char *filters[] = {"autorotate", "autostereo3d", NULL};
|
||||
char *filters[] = {"autorotate", "autostereo3d", "deinterlace", NULL};
|
||||
for (int n = 0; filters[n]; n++) {
|
||||
struct vf_instance *vf = vf_find_by_label(vo_c->vf, filters[n]);
|
||||
if (vf) {
|
||||
if (vf)
|
||||
vf_remove_filter(vo_c->vf, vf);
|
||||
if (vf_reconfig(vo_c->vf, ¶ms) < 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (vo_c->vf->initialized < 1) {
|
||||
if (vf_reconfig(vo_c->vf, ¶ms) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure to reset this even if runtime deint switching is used.
|
||||
video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &(int){0});
|
||||
|
||||
if (params.rotate && (params.rotate % 90 == 0)) {
|
||||
if (!(vo_c->vo->driver->caps & VO_CAP_ROTATE90)) {
|
||||
// Try to insert a rotation filter.
|
||||
@@ -194,6 +235,42 @@ static void filter_reconfig(struct vo_chain *vo_c)
|
||||
MP_ERR(vo_c, "Can't insert 3D conversion filter.\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (mpctx->opts->deinterlace == 1)
|
||||
probe_deint_filters(vo_c);
|
||||
}
|
||||
|
||||
static void recreate_auto_filters(struct MPContext *mpctx)
|
||||
{
|
||||
filter_reconfig(mpctx, mpctx->vo_chain);
|
||||
|
||||
mp_force_video_refresh(mpctx);
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
|
||||
}
|
||||
|
||||
int get_deinterlacing(struct MPContext *mpctx)
|
||||
{
|
||||
struct vo_chain *vo_c = mpctx->vo_chain;
|
||||
int enabled = 0;
|
||||
if (video_vf_vo_control(vo_c, VFCTRL_GET_DEINTERLACE, &enabled) != CONTROL_OK)
|
||||
enabled = -1;
|
||||
if (enabled < 0) {
|
||||
// vf_lavfi doesn't support VFCTRL_GET_DEINTERLACE
|
||||
if (vf_find_by_label(vo_c->vf, VF_DEINTERLACE_LABEL))
|
||||
enabled = 1;
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void set_deinterlacing(struct MPContext *mpctx, bool enable)
|
||||
{
|
||||
if (enable == (get_deinterlacing(mpctx) > 0))
|
||||
return;
|
||||
|
||||
mpctx->opts->deinterlace = enable;
|
||||
recreate_auto_filters(mpctx);
|
||||
mpctx->opts->deinterlace = get_deinterlacing(mpctx) > 0;
|
||||
}
|
||||
|
||||
static void recreate_video_filters(struct MPContext *mpctx)
|
||||
@@ -204,7 +281,7 @@ static void recreate_video_filters(struct MPContext *mpctx)
|
||||
|
||||
vf_destroy(vo_c->vf);
|
||||
vo_c->vf = vf_new(mpctx->global);
|
||||
vo_c->vf->hwdec = vo_c->hwdec_info;
|
||||
vo_c->vf->hwdec_devs = vo_c->hwdec_devs;
|
||||
vo_c->vf->wakeup_callback = wakeup_playloop;
|
||||
vo_c->vf->wakeup_callback_ctx = mpctx;
|
||||
vo_c->vf->container_fps = vo_c->container_fps;
|
||||
@@ -230,7 +307,7 @@ int reinit_video_filters(struct MPContext *mpctx)
|
||||
recreate_video_filters(mpctx);
|
||||
|
||||
if (need_reconfig)
|
||||
filter_reconfig(vo_c);
|
||||
filter_reconfig(mpctx, vo_c);
|
||||
|
||||
mp_force_video_refresh(mpctx);
|
||||
|
||||
@@ -314,7 +391,6 @@ void uninit_video_chain(struct MPContext *mpctx)
|
||||
|
||||
mpctx->video_status = STATUS_EOF;
|
||||
|
||||
remove_deint_filter(mpctx);
|
||||
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
|
||||
}
|
||||
}
|
||||
@@ -334,7 +410,7 @@ int init_video_decoder(struct MPContext *mpctx, struct track *track)
|
||||
d_video->codec = track->stream->codec;
|
||||
d_video->fps = d_video->header->codec->fps;
|
||||
if (mpctx->vo_chain)
|
||||
d_video->hwdec_info = mpctx->vo_chain->hwdec_info;
|
||||
d_video->hwdec_devs = mpctx->vo_chain->hwdec_devs;
|
||||
|
||||
MP_VERBOSE(d_video, "Container reported FPS: %f\n", d_video->fps);
|
||||
|
||||
@@ -404,7 +480,7 @@ int reinit_video_chain_src(struct MPContext *mpctx, struct lavfi_pad *src)
|
||||
vo_c->vo = mpctx->video_out;
|
||||
vo_c->vf = vf_new(mpctx->global);
|
||||
|
||||
vo_control(vo_c->vo, VOCTRL_GET_HWDEC_INFO, &vo_c->hwdec_info);
|
||||
vo_c->hwdec_devs = vo_c->vo->hwdec_devs;
|
||||
|
||||
vo_c->filter_src = src;
|
||||
if (!vo_c->filter_src) {
|
||||
@@ -522,22 +598,6 @@ static int decode_image(struct MPContext *mpctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Called after video reinit. This can be generally used to try to insert more
|
||||
// filters using the filter chain edit functionality in command.c.
|
||||
static void init_filter_params(struct MPContext *mpctx)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
|
||||
// Note that the filter chain is already initialized. This code might
|
||||
// recreate the chain a second time, which is not very elegant, but allows
|
||||
// us to test whether enabling deinterlacing works with the current video
|
||||
// format and other filters.
|
||||
if (opts->deinterlace >= 0) {
|
||||
remove_deint_filter(mpctx);
|
||||
set_deinterlacing(mpctx, opts->deinterlace != 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Feed newly decoded frames to the filter, take care of format changes.
|
||||
// If eof=true, drain the filter chain, and return VD_EOF if empty.
|
||||
static int video_filter(struct MPContext *mpctx, bool eof)
|
||||
@@ -564,7 +624,8 @@ static int video_filter(struct MPContext *mpctx, bool eof)
|
||||
return VD_PROGRESS;
|
||||
|
||||
// The filter chain is drained; execute the filter format change.
|
||||
filter_reconfig(mpctx->vo_chain);
|
||||
vf->initialized = 0;
|
||||
filter_reconfig(mpctx, mpctx->vo_chain);
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
|
||||
|
||||
@@ -586,7 +647,6 @@ static int video_filter(struct MPContext *mpctx, bool eof)
|
||||
MP_FATAL(mpctx, "Cannot initialize video filters.\n");
|
||||
return VD_ERROR;
|
||||
}
|
||||
init_filter_params(mpctx);
|
||||
return VD_RECONFIG;
|
||||
}
|
||||
|
||||
@@ -685,7 +745,7 @@ static void handle_new_frame(struct MPContext *mpctx)
|
||||
double pts = mpctx->next_frames[0]->pts;
|
||||
if (mpctx->video_pts != MP_NOPTS_VALUE) {
|
||||
frame_time = pts - mpctx->video_pts;
|
||||
double tolerance = 15;
|
||||
double tolerance = mpctx->demuxer->ts_resets_possible ? 5 : 1e4;
|
||||
if (frame_time <= 0 || frame_time >= tolerance) {
|
||||
// Assume a discontinuity.
|
||||
MP_WARN(mpctx, "Invalid video timestamp: %f -> %f\n",
|
||||
@@ -1130,6 +1190,10 @@ static void handle_display_sync_frame(struct MPContext *mpctx,
|
||||
drop_repeat = MPCLAMP(drop_repeat, -num_vsyncs, num_vsyncs * 10);
|
||||
num_vsyncs += drop_repeat;
|
||||
|
||||
// Always show the first frame.
|
||||
if (mpctx->num_past_frames <= 1 && num_vsyncs < 1)
|
||||
num_vsyncs = 1;
|
||||
|
||||
// Estimate the video position, so we can calculate a good A/V difference
|
||||
// value below. This is used to estimate A/V drift.
|
||||
double time_left = vo_get_delay(vo);
|
||||
@@ -1356,11 +1420,13 @@ void write_video(struct MPContext *mpctx)
|
||||
};
|
||||
calculate_frame_duration(mpctx);
|
||||
|
||||
int req = vo_get_num_req_frames(mpctx->video_out);
|
||||
assert(req >= 1 && req <= VO_MAX_REQ_FRAMES);
|
||||
struct vo_frame dummy = {
|
||||
.pts = pts,
|
||||
.duration = -1,
|
||||
.still = mpctx->step_frames > 0,
|
||||
.num_frames = MPMIN(mpctx->num_next_frames, VO_MAX_REQ_FRAMES),
|
||||
.num_frames = MPMIN(mpctx->num_next_frames, req),
|
||||
.num_vsyncs = 1,
|
||||
};
|
||||
for (int n = 0; n < dummy.num_frames; n++)
|
||||
@@ -1380,7 +1446,6 @@ void write_video(struct MPContext *mpctx)
|
||||
|
||||
mpctx->video_pts = mpctx->next_frames[0]->pts;
|
||||
mpctx->last_vo_pts = mpctx->video_pts;
|
||||
mpctx->playback_pts = mpctx->video_pts;
|
||||
|
||||
shift_frames(mpctx);
|
||||
|
||||
|
||||
+7
-19
@@ -159,11 +159,11 @@ static void cache_drop_contents(struct priv *s)
|
||||
static void update_speed(struct priv *s)
|
||||
{
|
||||
int64_t now = mp_time_us();
|
||||
s->speed = 0;
|
||||
if (s->speed_start && s->speed_start < now)
|
||||
if (s->speed_start + 1000000 <= now) {
|
||||
s->speed = s->speed_amount * 1e6 / (now - s->speed_start);
|
||||
s->speed_amount = 0;
|
||||
s->speed_start = now;
|
||||
s->speed_amount = 0;
|
||||
s->speed_start = now;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy at most dst_size from the cache at the given absolute file position pos.
|
||||
@@ -232,11 +232,6 @@ static void cache_fill(struct priv *s)
|
||||
if (mp_cancel_test(s->cache->cancel))
|
||||
goto done;
|
||||
|
||||
if (!s->speed_start) {
|
||||
s->speed_start = mp_time_us();
|
||||
s->speed_amount = 0;
|
||||
}
|
||||
|
||||
// number of buffer bytes which should be preserved in backwards direction
|
||||
int64_t back = MPCLAMP(read - s->min_filepos, 0, s->back_size);
|
||||
|
||||
@@ -300,20 +295,12 @@ done: ;
|
||||
s->eof = len <= 0;
|
||||
if (!prev_eof && s->eof) {
|
||||
s->eof_pos = stream_tell(s->stream);
|
||||
s->speed_start = 0;
|
||||
MP_VERBOSE(s, "EOF reached.\n");
|
||||
}
|
||||
s->idle = s->eof || !read_attempted;
|
||||
s->reads++;
|
||||
|
||||
if (s->idle) {
|
||||
update_speed(s);
|
||||
s->speed_start = 0;
|
||||
}
|
||||
|
||||
int64_t now = mp_time_us();
|
||||
if (s->speed_start && s->speed_start + 1000000 <= now)
|
||||
update_speed(s);
|
||||
update_speed(s);
|
||||
|
||||
pthread_cond_signal(&s->wakeup);
|
||||
}
|
||||
@@ -366,7 +353,6 @@ static int resize_cache(struct priv *s, int64_t size)
|
||||
s->buffer_size = buffer_size;
|
||||
s->buffer = buffer;
|
||||
s->idle = false;
|
||||
s->speed_start = 0;
|
||||
s->eof = false;
|
||||
|
||||
//make sure that we won't wait from cache_fill
|
||||
@@ -661,6 +647,8 @@ int stream_cache_init(stream_t *cache, stream_t *stream,
|
||||
|
||||
cache_drop_contents(s);
|
||||
|
||||
s->speed_start = mp_time_us();
|
||||
|
||||
s->seek_limit = opts->seek_min * 1024ULL;
|
||||
s->back_size = opts->back_buffer * 1024ULL;
|
||||
|
||||
|
||||
+59
-31
@@ -952,20 +952,15 @@ struct bstr stream_read_file(const char *filename, void *talloc_ctx,
|
||||
return res;
|
||||
}
|
||||
|
||||
#ifndef __MINGW32__
|
||||
struct mp_cancel {
|
||||
atomic_bool triggered;
|
||||
#ifdef __MINGW32__
|
||||
HANDLE event;
|
||||
#endif
|
||||
int wakeup_pipe[2];
|
||||
};
|
||||
|
||||
static void cancel_destroy(void *p)
|
||||
{
|
||||
struct mp_cancel *c = p;
|
||||
#ifdef __MINGW32__
|
||||
CloseHandle(c->event);
|
||||
#endif
|
||||
close(c->wakeup_pipe[0]);
|
||||
close(c->wakeup_pipe[1]);
|
||||
}
|
||||
@@ -975,9 +970,6 @@ struct mp_cancel *mp_cancel_new(void *talloc_ctx)
|
||||
struct mp_cancel *c = talloc_ptrtype(talloc_ctx, c);
|
||||
talloc_set_destructor(c, cancel_destroy);
|
||||
*c = (struct mp_cancel){.triggered = ATOMIC_VAR_INIT(false)};
|
||||
#ifdef __MINGW32__
|
||||
c->event = CreateEventW(NULL, TRUE, FALSE, NULL);
|
||||
#endif
|
||||
mp_make_wakeup_pipe(c->wakeup_pipe);
|
||||
return c;
|
||||
}
|
||||
@@ -986,19 +978,13 @@ struct mp_cancel *mp_cancel_new(void *talloc_ctx)
|
||||
void mp_cancel_trigger(struct mp_cancel *c)
|
||||
{
|
||||
atomic_store(&c->triggered, true);
|
||||
#ifdef __MINGW32__
|
||||
SetEvent(c->event);
|
||||
#endif
|
||||
write(c->wakeup_pipe[1], &(char){0}, 1);
|
||||
(void)write(c->wakeup_pipe[1], &(char){0}, 1);
|
||||
}
|
||||
|
||||
// Restore original state. (Allows reusing a mp_cancel.)
|
||||
void mp_cancel_reset(struct mp_cancel *c)
|
||||
{
|
||||
atomic_store(&c->triggered, false);
|
||||
#ifdef __MINGW32__
|
||||
ResetEvent(c->event);
|
||||
#endif
|
||||
// Flush it fully.
|
||||
while (1) {
|
||||
int r = read(c->wakeup_pipe[0], &(char[256]){0}, 256);
|
||||
@@ -1018,27 +1004,12 @@ bool mp_cancel_test(struct mp_cancel *c)
|
||||
|
||||
// Wait until the even is signaled. If the timeout (in seconds) expires, return
|
||||
// false. timeout==0 polls, timeout<0 waits forever.
|
||||
#ifdef __MINGW32__
|
||||
bool mp_cancel_wait(struct mp_cancel *c, double timeout)
|
||||
{
|
||||
return WaitForSingleObject(c->event, timeout < 0 ? INFINITE : timeout * 1000)
|
||||
== WAIT_OBJECT_0;
|
||||
}
|
||||
#else
|
||||
bool mp_cancel_wait(struct mp_cancel *c, double timeout)
|
||||
{
|
||||
struct pollfd fd = { .fd = c->wakeup_pipe[0], .events = POLLIN };
|
||||
poll(&fd, 1, timeout * 1000);
|
||||
return fd.revents & POLLIN;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __MINGW32__
|
||||
void *mp_cancel_get_event(struct mp_cancel *c)
|
||||
{
|
||||
return c->event;
|
||||
}
|
||||
#endif
|
||||
|
||||
// The FD becomes readable if mp_cancel_test() would return true.
|
||||
// Don't actually read from it, just use it for poll().
|
||||
@@ -1047,6 +1018,63 @@ int mp_cancel_get_fd(struct mp_cancel *c)
|
||||
return c->wakeup_pipe[0];
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
struct mp_cancel {
|
||||
atomic_bool triggered;
|
||||
HANDLE event;
|
||||
};
|
||||
|
||||
static void cancel_destroy(void *p)
|
||||
{
|
||||
struct mp_cancel *c = p;
|
||||
CloseHandle(c->event);
|
||||
}
|
||||
|
||||
struct mp_cancel *mp_cancel_new(void *talloc_ctx)
|
||||
{
|
||||
struct mp_cancel *c = talloc_ptrtype(talloc_ctx, c);
|
||||
talloc_set_destructor(c, cancel_destroy);
|
||||
*c = (struct mp_cancel){.triggered = ATOMIC_VAR_INIT(false)};
|
||||
c->event = CreateEventW(NULL, TRUE, FALSE, NULL);
|
||||
return c;
|
||||
}
|
||||
|
||||
void mp_cancel_trigger(struct mp_cancel *c)
|
||||
{
|
||||
atomic_store(&c->triggered, true);
|
||||
SetEvent(c->event);
|
||||
}
|
||||
|
||||
void mp_cancel_reset(struct mp_cancel *c)
|
||||
{
|
||||
atomic_store(&c->triggered, false);
|
||||
ResetEvent(c->event);
|
||||
}
|
||||
|
||||
bool mp_cancel_test(struct mp_cancel *c)
|
||||
{
|
||||
return c ? atomic_load_explicit(&c->triggered, memory_order_relaxed) : false;
|
||||
}
|
||||
|
||||
bool mp_cancel_wait(struct mp_cancel *c, double timeout)
|
||||
{
|
||||
return WaitForSingleObject(c->event, timeout < 0 ? INFINITE : timeout * 1000)
|
||||
== WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
void *mp_cancel_get_event(struct mp_cancel *c)
|
||||
{
|
||||
return c->event;
|
||||
}
|
||||
|
||||
int mp_cancel_get_fd(struct mp_cancel *c)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
char **stream_get_proto_list(void)
|
||||
{
|
||||
char **list = NULL;
|
||||
|
||||
@@ -384,6 +384,8 @@ static int open_cdda(stream_t *st)
|
||||
st->control = control;
|
||||
st->close = close_cdda;
|
||||
|
||||
st->streaming = true;
|
||||
|
||||
st->type = STREAMTYPE_CDDA;
|
||||
st->demuxer = "+disc";
|
||||
|
||||
|
||||
+35
-2
@@ -55,6 +55,32 @@ static int control(stream_t *s, int cmd, void *arg)
|
||||
return STREAM_UNSUPPORTED;
|
||||
}
|
||||
|
||||
static int h_to_i(unsigned char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool bstr_to_hex_inplace(bstr *h)
|
||||
{
|
||||
if (h->len % 2)
|
||||
return false;
|
||||
for (int n = 0; n < h->len / 2; n++) {
|
||||
int hi = h_to_i(h->start[n * 2 + 0]);
|
||||
int lo = h_to_i(h->start[n * 2 + 1]);
|
||||
if (hi < 0 || lo < 0)
|
||||
return false;
|
||||
h->start[n] = (hi << 4) | lo;
|
||||
}
|
||||
h->len /= 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int open_f(stream_t *stream)
|
||||
{
|
||||
stream->fill_buffer = fill_buffer;
|
||||
@@ -68,14 +94,21 @@ static int open_f(stream_t *stream)
|
||||
|
||||
// Initial data
|
||||
bstr data = bstr0(stream->url);
|
||||
bstr_eatstart0(&data, "memory://");
|
||||
bool use_hex = bstr_eatstart0(&data, "hex://");
|
||||
if (!use_hex)
|
||||
bstr_eatstart0(&data, "memory://");
|
||||
stream_control(stream, STREAM_CTRL_SET_CONTENTS, &data);
|
||||
|
||||
if (use_hex && !bstr_to_hex_inplace(&p->data)) {
|
||||
MP_FATAL(stream, "Invalid data.\n");
|
||||
return STREAM_ERROR;
|
||||
}
|
||||
|
||||
return STREAM_OK;
|
||||
}
|
||||
|
||||
const stream_info_t stream_info_memory = {
|
||||
.name = "memory",
|
||||
.open = open_f,
|
||||
.protocols = (const char*const[]){ "memory", NULL },
|
||||
.protocols = (const char*const[]){ "memory", "hex", NULL },
|
||||
};
|
||||
+1
-1
@@ -1352,7 +1352,7 @@ static int start(priv_t *priv)
|
||||
if (priv->map[i].buf.flags & V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC)
|
||||
MP_WARN(priv, "compiled without clock_gettime() that is needed to handle monotone video timestamps from the kernel. Expect desync.\n");
|
||||
#endif
|
||||
/* count up to make sure this is correct everytime */
|
||||
/* count up to make sure this is correct every time */
|
||||
priv->mapcount++;
|
||||
|
||||
if (v4l2_ioctl(priv->video_fd, VIDIOC_QBUF, &(priv->map[i].buf)) < 0) {
|
||||
|
||||
Loaded 100 of 236 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user