Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f1afc6ba2 | ||
|
|
35aa705c3e | ||
|
|
3739d1318f | ||
|
|
8432514c5f | ||
|
|
8bc335e3db | ||
|
|
6ac3d77e90 | ||
|
|
d0f1d382e6 | ||
|
|
1672204dc5 | ||
|
|
f89ff14195 | ||
|
|
cc3eb531eb | ||
|
|
f4d75376fe | ||
|
|
5085cf295f | ||
|
|
e285e22143 | ||
|
|
c94ede9e62 | ||
|
|
6d236bd96e | ||
|
|
96a45a16af | ||
|
|
061b752217 | ||
|
|
e0250b9604 | ||
|
|
5fbad204a6 | ||
|
|
46a556bd4b | ||
|
|
c7b72a6afe | ||
|
|
19551200d5 | ||
|
|
92f9747c2b | ||
|
|
aaad2d847e | ||
|
|
8362577f8c | ||
|
|
58c9f1a822 | ||
|
|
7e6b37be07 | ||
|
|
61202bb364 | ||
|
|
97680bf604 | ||
|
|
a46fc5e5eb | ||
|
|
50991fac81 | ||
|
|
1ec6cd89b9 | ||
|
|
95d4c2d7f6 | ||
|
|
fb9a32977d | ||
|
|
e13a62fc34 | ||
|
|
d11c03faee | ||
|
|
5f10a415d7 | ||
|
|
6da224b1dd | ||
|
|
f39a1cb1b0 | ||
|
|
8bbdecea83 | ||
|
|
ce23dfa2fa | ||
|
|
644e23a0f5 | ||
|
|
738de7d3fe | ||
|
|
f1c9032ddf | ||
|
|
bbf01346ff | ||
|
|
2493d91122 | ||
|
|
9831d3ada6 | ||
|
|
ee000f2133 | ||
|
|
ccf2b302cf | ||
|
|
9c12d54afa | ||
|
|
55d6408526 | ||
|
|
c178920505 | ||
|
|
07494aab16 | ||
|
|
9a464f53ef | ||
|
|
e017c24174 | ||
|
|
6dafc85187 | ||
|
|
dccb752b5f | ||
|
|
954625c165 | ||
|
|
bed94df521 | ||
|
|
9692814502 | ||
|
|
a6d29494ca | ||
|
|
2a7d2d1bc9 | ||
|
|
0aac7533dc | ||
|
|
443d3a91d3 | ||
|
|
d9ef1333c6 | ||
|
|
d41f25009d | ||
|
|
ea40fa36ee | ||
|
|
9c90c902c1 | ||
|
|
cfda696580 | ||
|
|
a9e0f4b279 | ||
|
|
c3205d294e | ||
|
|
70411f2709 | ||
|
|
4e53f9e5b2 | ||
|
|
abc6d130ac | ||
|
|
60452f640f | ||
|
|
753dbea83f | ||
|
|
9980b11058 | ||
|
|
bdbb3142ec | ||
|
|
d303ebd9b6 | ||
|
|
39adaf3dcc | ||
|
|
04376fa024 | ||
|
|
801fa486b0 | ||
|
|
ce803da90d | ||
|
|
194b26536b | ||
|
|
3b0e886193 | ||
|
|
63b34143a5 | ||
|
|
7be495b369 | ||
|
|
f762fc4230 | ||
|
|
d16ba1f7f6 | ||
|
|
c06fd19d8f | ||
|
|
b14fac9afa | ||
|
|
6be58df8d1 | ||
|
|
00eadcec8d | ||
|
|
3e89e061c2 | ||
|
|
5c942128d8 | ||
|
|
06c55ac6c3 | ||
|
|
310671e91a | ||
|
|
73858bb0cc | ||
|
|
085dfdea32 | ||
|
|
fd203ff16a | ||
|
|
d890e0731c | ||
|
|
a35a5bb5f3 | ||
|
|
4adfde5dd1 | ||
|
|
13add62cf0 | ||
|
|
717ee3ddba | ||
|
|
9490b628c5 | ||
|
|
fe02e5023c | ||
|
|
202f9b218a | ||
|
|
4fe45fb3a1 | ||
|
|
8cbb2b5e9a | ||
|
|
880bf54d7e | ||
|
|
06c8ec27f6 | ||
|
|
e277fadd60 | ||
|
|
c54c3b6991 | ||
|
|
04858c0b83 | ||
|
|
e7d902bdc0 | ||
|
|
c522d0dfbd | ||
|
|
da8011c5df | ||
|
|
398e2d5d42 | ||
|
|
a38283d5d9 | ||
|
|
9d68d8fb0f | ||
|
|
e94890a5d6 | ||
|
|
4b3b3a045a | ||
|
|
9d69eae162 | ||
|
|
ff9f2c4b6e | ||
|
|
cda31b71de | ||
|
|
a4cdd8bb82 | ||
|
|
9850932722 | ||
|
|
64041f963e | ||
|
|
fb6481ecb5 | ||
|
|
083e470091 | ||
|
|
1b1771f2a7 | ||
|
|
91fb7078e2 | ||
|
|
8a23892d04 | ||
|
|
3a85976f8e | ||
|
|
6b00663755 | ||
|
|
bbdecb792a | ||
|
|
348c610b68 | ||
|
|
397705b12c | ||
|
|
e91331e683 | ||
|
|
191cdbd31e | ||
|
|
ddaab3349b | ||
|
|
e48f1f31cb | ||
|
|
6c28824a92 | ||
|
|
812128bab7 | ||
|
|
d9376fc86f | ||
|
|
b26ab4d08c | ||
|
|
88dfb9a5e7 | ||
|
|
6de00d10c8 | ||
|
|
14010e7cf6 | ||
|
|
e4ae62da52 | ||
|
|
44e06b70d5 | ||
|
|
ac98ff71dd | ||
|
|
e552f5342e | ||
|
|
aecedd9250 | ||
|
|
5174add43a | ||
|
|
3d7b3996d2 | ||
|
|
e618b1b352 | ||
|
|
fb4ae3c06c | ||
|
|
06b30cc81f | ||
|
|
162c2e2d00 | ||
|
|
26d25d567f | ||
|
|
a51e991b56 | ||
|
|
fcb970492c | ||
|
|
32186de367 | ||
|
|
a41bedfce5 | ||
|
|
2afef344fb | ||
|
|
f4263922c7 | ||
|
|
9fd986b53a | ||
|
|
7dc74ee037 | ||
|
|
5f449fc11f | ||
|
|
fee5ceccad | ||
|
|
449eb208f4 | ||
|
|
46b74a38f1 | ||
|
|
a05c5b4ec6 | ||
|
|
cbd8abcbff | ||
|
|
f8baae5854 | ||
|
|
77320ad45e | ||
|
|
902424d065 | ||
|
|
c2c065913b | ||
|
|
ee66efeb99 | ||
|
|
c000b37e9a | ||
|
|
ed937b6eca | ||
|
|
4e25feda0d | ||
|
|
334cad57c6 | ||
|
|
0e8b9ed228 | ||
|
|
9c5cbbf5d7 | ||
|
|
854651f4f5 | ||
|
|
5ab11016c8 | ||
|
|
0ae753bc3d | ||
|
|
42a60143c2 | ||
|
|
5d7f881bdc | ||
|
|
afcf3e154a | ||
|
|
c4ad2732f9 | ||
|
|
e0f25010c7 | ||
|
|
c2fdedc2ec | ||
|
|
2ce5523893 | ||
|
|
d230194f62 | ||
|
|
f2a24ccfa5 | ||
|
|
68d737bd92 | ||
|
|
c17c26f404 | ||
|
|
d3eafc976d | ||
|
|
7aca328eb1 | ||
|
|
0b9f1e0323 | ||
|
|
737e3b1758 | ||
|
|
0e7dd6d4ff | ||
|
|
2ff0745ddb | ||
|
|
f34faca910 | ||
|
|
43386a7c92 | ||
|
|
8e41f314f1 | ||
|
|
0067d1dbef | ||
|
|
5ed4119057 | ||
|
|
b14fc38590 | ||
|
|
58a0c43cf4 | ||
|
|
7033a4334b | ||
|
|
d4e7b981bf | ||
|
|
b9cebf180b | ||
|
|
35716b53db | ||
|
|
22a22322cb | ||
|
|
a283b35556 | ||
|
|
321dd794c8 | ||
|
|
10a005df0c | ||
|
|
b3d670a3e1 | ||
|
|
1c761bd6f5 | ||
|
|
1d5e95783e | ||
|
|
9d21f2503f | ||
|
|
c560f6ff0a | ||
|
|
17d6ba7f77 | ||
|
|
b1c0bbe8b8 | ||
|
|
1ba3525816 | ||
|
|
99a4be4127 | ||
|
|
3b5777d86a | ||
|
|
e57037dc95 | ||
|
|
4bfec4279f | ||
|
|
1ef9876b7d | ||
|
|
a61eb81141 | ||
|
|
ff9f5e06ff | ||
|
|
2b8b17402e | ||
|
|
aab98776f6 | ||
|
|
a8347eb9ba | ||
|
|
870a6a11d9 | ||
|
|
544110bf43 | ||
|
|
a6035439a9 | ||
|
|
3bb06155c9 | ||
|
|
bf5727a60f | ||
|
|
765a7228d1 | ||
|
|
6b6789a47a | ||
|
|
ed7e650d3a | ||
|
|
4399be5b62 | ||
|
|
c21c68edd4 | ||
|
|
2d9b6ff7cd | ||
|
|
0aa9b2a469 | ||
|
|
82855bec50 | ||
|
|
c324bfab59 | ||
|
|
0eb87e1baf | ||
|
|
2aebf3e482 | ||
|
|
736cd7336a | ||
|
|
a8b8295f8d | ||
|
|
51fd8f6fe1 | ||
|
|
3eceac2eab | ||
|
|
a660e15c9b | ||
|
|
2b1d150a53 | ||
|
|
42799005dc | ||
|
|
b60817f9dd | ||
|
|
ceb2e1026d | ||
|
|
83c5f704e7 | ||
|
|
47f974a395 | ||
|
|
f53b2952eb | ||
|
|
5544ffecd9 | ||
|
|
796b48b2a3 | ||
|
|
a7a919f93f | ||
|
|
a89785f297 | ||
|
|
09238a9bb5 | ||
|
|
ec74a79e12 | ||
|
|
20f02229cd | ||
|
|
4958c1a556 | ||
|
|
3203d6003c | ||
|
|
89837523b5 | ||
|
|
1a2319f3e4 | ||
|
|
98a257b3a8 | ||
|
|
c03a67c37c | ||
|
|
48a7c4be3a | ||
|
|
5aab17f833 | ||
|
|
e89382f5f6 | ||
|
|
7eacaf51f8 | ||
|
|
3abb6f1fef | ||
|
|
755e9fad29 | ||
|
|
f696975fe3 | ||
|
|
79d99bd264 | ||
|
|
f9668f5596 | ||
|
|
535e180b9e | ||
|
|
6b3d682f4e | ||
|
|
f5e82d5ed3 | ||
|
|
ebd9ce9fca | ||
|
|
585c5c34f1 | ||
|
|
5087816a74 | ||
|
|
7201fd7d08 | ||
|
|
78f76bdddd | ||
|
|
c731513efa | ||
|
|
07b6969ba4 | ||
|
|
f30c5d09f4 | ||
|
|
745862131f | ||
|
|
f843ae9983 | ||
|
|
de37c5b1cb | ||
|
|
fcba41e2e4 | ||
|
|
b39a7fddb5 | ||
|
|
1d4273cfb5 | ||
|
|
8700700de8 | ||
|
|
eafc273d2c | ||
|
|
bb48f09532 | ||
|
|
5a011c5c06 | ||
|
|
c4d6fcbb02 | ||
|
|
6250007bca | ||
|
|
803e2c5f87 | ||
|
|
40b626fd9b | ||
|
|
a3c27786c6 | ||
|
|
03ea61c895 | ||
|
|
274c6f36f5 | ||
|
|
79efe248a5 | ||
|
|
106f607ef7 | ||
|
|
c1ae1def85 | ||
|
|
a2b93e0c27 | ||
|
|
84513ba58b | ||
|
|
cf85191cb7 | ||
|
|
19c9347e47 | ||
|
|
aceeeaf9bb | ||
|
|
98e7b4e538 | ||
|
|
ca175871cd | ||
|
|
fc1017c335 | ||
|
|
67467103e8 | ||
|
|
a55e8ff31c | ||
|
|
b787a4121a | ||
|
|
adf65634d8 | ||
|
|
5beb230690 | ||
|
|
616ecd9365 | ||
|
|
c676c31815 | ||
|
|
81ceb7b6a5 | ||
|
|
33012b4141 | ||
|
|
1d51dc20ea | ||
|
|
2e113a7391 | ||
|
|
2b71bef2ba | ||
|
|
8f1ec91b45 | ||
|
|
8b7f23129d | ||
|
|
6a06e6002b | ||
|
|
de72cb2c33 | ||
|
|
4914e688cf | ||
|
|
932ecbfe9e | ||
|
|
7c9f32620f | ||
|
|
3f5b41dfa3 | ||
|
|
0dc1fe3269 | ||
|
|
654721c27b | ||
|
|
5838322975 | ||
|
|
8dff51e06c | ||
|
|
17733bd5b8 | ||
|
|
eedda59a69 | ||
|
|
8fad4b8eab | ||
|
|
bc77565838 | ||
|
|
b0ef3dd4fb | ||
|
|
1ff6d8ae2e | ||
|
|
1afc0c8b55 | ||
|
|
a1801da717 | ||
|
|
eac977b12f | ||
|
|
3adc6071b6 | ||
|
|
0c3e4b13ac | ||
|
|
22133f8efc | ||
|
|
4abd23bd95 | ||
|
|
993466fc30 | ||
|
|
ad819b5458 | ||
|
|
894dbcd1b7 | ||
|
|
90b968a67a | ||
|
|
fc4318d23e | ||
|
|
65c06511bf | ||
|
|
ee4bed25a8 | ||
|
|
6953a1ca2f | ||
|
|
c8e3ee494c | ||
|
|
363982c774 | ||
|
|
02d2c2cc97 | ||
|
|
6e143ffec3 | ||
|
|
61ff2408e3 | ||
|
|
d0b997d528 | ||
|
|
18681a7dd2 | ||
|
|
3a78eefc88 | ||
|
|
1bf474e19a | ||
|
|
bf5c4c42fa | ||
|
|
11b8cbcce7 | ||
|
|
082340b467 | ||
|
|
5f544c1081 | ||
|
|
9ea9bdf130 | ||
|
|
202f695398 | ||
|
|
f64de3ea66 | ||
|
|
61a51c57a9 | ||
|
|
b8b3163b85 | ||
|
|
d38baa65ca | ||
|
|
8b00d82b79 | ||
|
|
7cd32ec29e | ||
|
|
183af9d72e | ||
|
|
ea03ae157f | ||
|
|
a6da4faac5 | ||
|
|
773d52162e | ||
|
|
cc9e8e7e3d | ||
|
|
ee2ba599e7 | ||
|
|
26458419a7 | ||
|
|
4bd3e51fbe | ||
|
|
5da510b5fc | ||
|
|
6b18d4dba5 | ||
|
|
dd02369c32 | ||
|
|
7684483052 |
No files matched your search
@@ -32,13 +32,15 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.21.0 ---
|
||||
--- mpv 0.23.0 ---
|
||||
1.24 - the deprecated mpv_suspend() and mpv_resume() APIs now do nothing.
|
||||
--- mpv 0.22.0 ---
|
||||
1.23 - deprecate setting "no-" options via mpv_set_option*(). For example,
|
||||
instead of "no-video=" you should set "video=no".
|
||||
- do not override the SIGPIPE signal handler anymore. This was done as
|
||||
workaround for the FFmpeg TLS code, which has been fixed long ago.
|
||||
- deprecate mpv_suspend() and mpv_resume(). They will be stubbed out
|
||||
in mpv 0.22.0.
|
||||
in mpv 0.23.0.
|
||||
- make mpv_set_property() work to some degree before mpv_initialize().
|
||||
It can now be used instead of mpv_set_option().
|
||||
- semi-deprecate mpv_set_option()/mpv_set_option_string(). You should
|
||||
@@ -49,8 +51,8 @@ API changes
|
||||
will be removed, and mpv_set_option() will internally translate API
|
||||
calls to mpv_set_property().
|
||||
- qthelper.hpp: deprecate get_property_variant, set_property_variant,
|
||||
mpv_set_option, command_variant, and replace them with get_property,
|
||||
set_property, command.
|
||||
set_option_variant, command_variant, and replace them with
|
||||
get_property, set_property, command.
|
||||
--- mpv 0.19.0 ---
|
||||
1.22 - add stream_cb API for custom protocols
|
||||
--- mpv 0.18.1 ---
|
||||
|
||||
+8
-12
@@ -166,11 +166,8 @@ Or, compile and install both libmpv and mpv:
|
||||
./waf configure CC=gcc.exe --check-c-compiler=gcc --enable-libmpv-shared --prefix=/mingw64
|
||||
./waf install
|
||||
|
||||
# waf installs libmpv to the wrong directory, so fix it up
|
||||
mv -f /mingw64/bin/pkgconfig/mpv.pc /mingw64/lib/pkgconfig/
|
||||
mv -f /mingw64/bin/libmpv.dll.a /mingw64/lib/
|
||||
sed -i 's_/mingw64/bin_/mingw64/lib_' /mingw64/lib/pkgconfig/mpv.pc
|
||||
rmdir /mingw64/bin/pkgconfig
|
||||
# waf installs libmpv dll to the wrong directory, so fix it up
|
||||
mv -f /mingw64/lib/mpv-1.dll /mingw64/bin/
|
||||
```
|
||||
|
||||
Linking libmpv with MSVC programs
|
||||
@@ -208,11 +205,10 @@ DLLs in that folder. The simplest solution is to add ``C:\msys64\mingw64\bin``
|
||||
to the windows system ``%PATH%``. Beware though that this can cause problems or
|
||||
confusion in Cygwin if that is also installed on the machine.
|
||||
|
||||
Use of the ANGLE OpenGL backend requires a copy of ``d3dcompiler_43.dll`` (yes,
|
||||
exactly 43) in the path or in the same folder as mpv. It must be of the same
|
||||
architecture (x86_64 / i686) as the mpv you compiled. You can find a copy in the
|
||||
official mpv builds:
|
||||
Use of the ANGLE OpenGL backend requires a copy of the D3D compiler DLL that
|
||||
matches the version of the D3D SDK that ANGLE was built with
|
||||
(``d3dcompiler_43.dll`` in case of MinGW-built ANGLE) in the path or in the
|
||||
same folder as mpv. It must be of the same architecture (x86_64 / i686) as the
|
||||
mpv you compiled. You can find copies here:
|
||||
|
||||
https://mpv.srsfckn.biz/mpv-x86_64-20160118.7z
|
||||
|
||||
https://mpv.srsfckn.biz/mpv-i686-20160118.7z
|
||||
https://mpv.srsfckn.biz/d3dcompiler.7z
|
||||
+4
-6
@@ -101,18 +101,16 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
do_something();
|
||||
}
|
||||
```
|
||||
- If the body of an if statement uses braces, the else branch should also
|
||||
use braces (and reverse).
|
||||
- If the if has an else branch, both branches should use braces, even if they're
|
||||
technically redundant.
|
||||
|
||||
Example:
|
||||
|
||||
```C
|
||||
if (a) {
|
||||
// do something
|
||||
something();
|
||||
something_else();
|
||||
} else {
|
||||
one_line();
|
||||
} else {
|
||||
one_other_line();
|
||||
}
|
||||
```
|
||||
- If an if condition spans multiple physical lines, then put the opening brace
|
||||
|
||||
+40
-3
@@ -51,7 +51,8 @@ The rest of the lines belong to one of these classes:
|
||||
1) An empty or commented line. A comment starts with ``#``, which must be the
|
||||
first character in the line. The rest of the line (up until the next line
|
||||
break) is ignored. An empty line has 0 bytes between two line feed bytes.
|
||||
2) A segment entry in all other cases.
|
||||
2) A header entry if the line starts with ``!``.
|
||||
3) A segment entry in all other cases.
|
||||
|
||||
Each segment entry consists of a list of named or unnamed parameters.
|
||||
Parameters are separated with ``,``. Named parameters consist of a name,
|
||||
@@ -63,8 +64,8 @@ Syntax::
|
||||
segment_entry ::= <param> ( <param> ',' )*
|
||||
param ::= [ <name> '=' ] ( <value> | '%' <number> '%' <valuebytes> )
|
||||
|
||||
The ``name`` string can consist of any characters, except ``=%,;\n``. The
|
||||
``value`` string can consist of any characters except of ``,;\n``.
|
||||
The ``name`` string can consist of any characters, except ``=%,;\n!``. The
|
||||
``value`` string can consist of any characters except of ``,;\n!``.
|
||||
|
||||
The construct starting with ``%`` allows defining any value with arbitrary
|
||||
contents inline, where ``number`` is an integer giving the number of bytes in
|
||||
@@ -94,6 +95,42 @@ to ``20``, ``param3`` to ``value,escaped``, ``param4`` to ``value2``.
|
||||
Instead of line breaks, the character ``;`` can be used. Line feed bytes and
|
||||
``;`` are treated equally.
|
||||
|
||||
Header entries start with ``!`` as first character after a line break. Header
|
||||
entries affect all other file entries in the EDL file. Their format is highly
|
||||
implementation specific. They should generally follow the file header, and come
|
||||
before any file entries.
|
||||
|
||||
MP4 DASH
|
||||
========
|
||||
|
||||
This is a header that helps implementing DASH, although it only provides a low
|
||||
level mechanism.
|
||||
|
||||
If this header is set, the given url designates an mp4 init fragment. It's
|
||||
downloaded, and every URL in the EDL is prefixed with the init fragment on the
|
||||
byte stream level. This is mostly for use by mpv's internal ytdl support. The
|
||||
ytdl script will call youtube-dl, which in turn actually processes DASH
|
||||
manifests. It may work only for this very specific purpose and fail to be
|
||||
useful in other scenarios. It can be removed or changed in incompatible ways
|
||||
at any times.
|
||||
|
||||
Example::
|
||||
|
||||
!mp4_dash,init=url
|
||||
|
||||
The ``url`` is encoded as parameter value as defined in the general EDL syntax.
|
||||
It's expected to point to an "initialization fragment", which will be prefixed
|
||||
to every entry in the EDL on the byte stream level.
|
||||
|
||||
The current implementation will
|
||||
|
||||
- ignore stream start times
|
||||
- use durations as hint for seeking only
|
||||
- not adjust source timestamps
|
||||
- open and close segments (i.e. fragments) as needed
|
||||
- not add segment boundaries as chapter points
|
||||
- require full compatibility between all segments (same codec etc.)
|
||||
|
||||
Timestamp format
|
||||
================
|
||||
|
||||
|
||||
@@ -19,9 +19,66 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.24.0 ---
|
||||
- deprecate --hwdec-api and replace it with --opengl-hwdec-interop.
|
||||
The new option accepts both --hwdec values, as well as named backends.
|
||||
A minor difference is that --hwdec-api=no (which used to be the default)
|
||||
now actually does not preload any interop layer, while the new default
|
||||
("") uses the value of --hwdec.
|
||||
- drop deprecated --ad/--vd features
|
||||
- drop deprecated --sub-codepage syntax
|
||||
- rename properties:
|
||||
- "drop-frame-count" to "decoder-frame-drop-count"
|
||||
- "vo-drop-frame-count" to "frame-drop-count"
|
||||
The old names still work, but are deprecated.
|
||||
- remove the --stream-capture option and property. No replacement.
|
||||
(--record-file might serve as alternative)
|
||||
- add --sub-justify
|
||||
- add --sub-ass-justify
|
||||
- internally there's a different way to enable the demuxer cache now
|
||||
it can be auto-enabled even if the stream cache remains disabled
|
||||
--- mpv 0.23.0 ---
|
||||
- remove deprecated vf_vdpaurb (use "--hwdec=vdpau-copy" instead)
|
||||
- the following properties now have new semantics:
|
||||
- "demuxer" (use "current-demuxer")
|
||||
- "fps" (use "container-fps")
|
||||
- "idle" (use "idle-active")
|
||||
- "cache" (use "cache-percent")
|
||||
- "audio-samplerate" (use "audio-params/samplerate")
|
||||
- "audio-channels" (use "audio-params/channel-count")
|
||||
- "audio-format" (use "audio-codec-name")
|
||||
(the properties equivalent to the old semantics are in parentheses)
|
||||
- remove deprecated --vo and --ao sub-options (like --vo=opengl:...), and
|
||||
replace them with global options. A somewhat complete list can be found
|
||||
here: https://github.com/mpv-player/mpv/wiki/Option-replacement-list#mpv-0210
|
||||
- remove --vo-defaults and --ao-defaults as well
|
||||
- remove deprecated global sub-options (like -demuxer-rawaudio format=...),
|
||||
use flat options (like --demuxer-rawaudio-format=...)
|
||||
- the --sub-codepage option changes in incompatible ways:
|
||||
- detector-selection and fallback syntax is deprecated
|
||||
- enca/libguess are removed and deprecated (behaves as if they hadn't
|
||||
been compiled-in)
|
||||
- --sub-codepage=<codepage> does not force the codepage anymore
|
||||
(this requires different and new syntax)
|
||||
- remove --fs-black-out-screens option for macOS
|
||||
- change how spdif codecs are selected. You can't enable spdif passthrough
|
||||
with --ad anymore. This was deprecated; use --audio-spdif instead.
|
||||
- deprecate the "family" selection with --ad/--vd
|
||||
forcing/excluding codecs with "+", "-", "-" is deprecated as well
|
||||
- explicitly mark --ad-spdif-dtshd as deprecated (it was done so a long time
|
||||
ago, but it didn't complain when using the option)
|
||||
--- mpv 0.22.0 ---
|
||||
- the "audio-device-list" property now sets empty device description to the
|
||||
device name as a fallback
|
||||
- add --hidpi-window-scale option for macOS
|
||||
- add audiounit audio output for iOS
|
||||
- make --start-time work with --rebase-start-time=no
|
||||
- add --opengl-early-flush=auto mode
|
||||
- add --hwdec=vdpau-copy, deprecate vf_vdpaurb
|
||||
- add tct video output for true-color and 256-color terminals
|
||||
--- mpv 0.21.0 ---
|
||||
- setting certain options at runtime will now take care of updating them
|
||||
property (see for example issue #3281). On the other hand, it will also
|
||||
- unlike in older versions, setting options at runtime will now take effect
|
||||
immediately (see for example issue #3281). On the other hand, it will also
|
||||
do runtime verification and reject option changes that do not work
|
||||
(example: setting the "vf" option to a filter during playback, which fails
|
||||
to initialize - the option value will remain at its old value). In general,
|
||||
@@ -58,7 +115,7 @@ Interface changes
|
||||
- "fps" -> "container-fps"
|
||||
- "idle" -> "idle-active"
|
||||
- "cache" -> "cache-percent"
|
||||
the old names are deprecated and will change behavior in mpv 0.22.0.
|
||||
the old names are deprecated and will change behavior in mpv 0.23.0.
|
||||
- remove deprecated "hwdec-active" and "hwdec-detected" properties
|
||||
- deprecate the ao and vo auto-profiles (they never made any sense)
|
||||
- deprecate "--vo=direct3d_shaders" - use "--vo=direct3d" instead.
|
||||
@@ -72,17 +129,17 @@ Interface changes
|
||||
now always sets the device, not the span or speed to be played. No
|
||||
separating extra "/" is needed. The hidden --cdda-device options is also
|
||||
deleted (it was redundant with the documented --cdrom-device).
|
||||
- deprecate --vo=rpi. It will be removed in mpv 0.22.0. Its functionality
|
||||
- deprecate --vo=rpi. It will be removed in mpv 0.23.0. Its functionality
|
||||
was folded into --vo=opengl, which now uses RPI hardware decoding by
|
||||
treating it as a hardware overlay (without applying GL filtering). Also
|
||||
to be changed in 0.22.0: the --fs flag will be reset to "no" by default
|
||||
to be changed in 0.23.0: the --fs flag will be reset to "no" by default
|
||||
(like on the other platforms).
|
||||
- deprecate --mute=auto (informally has been since 0.18.1)
|
||||
- deprecate "resume" and "suspend" IPC commands. They will be completely
|
||||
removed in 0.22.0.
|
||||
removed in 0.23.0.
|
||||
- deprecate mp.suspend(), mp.resume(), mp.resume_all() Lua scripting
|
||||
commands, as well as setting mp.use_suspend. They will be completely
|
||||
removed in 0.22.0.
|
||||
removed in 0.23.0.
|
||||
- the "seek" command's absolute seek mode will now interpret negative
|
||||
seek times as relative from the end of the file (and clamps seeks that
|
||||
still go before 0)
|
||||
|
||||
@@ -10,11 +10,6 @@ syntax is:
|
||||
If the list has a trailing ',', mpv will fall back on drivers not contained
|
||||
in the list.
|
||||
|
||||
``--ao-defaults=<driver1[:parameter1:parameter2:...],driver2,...>``
|
||||
Set defaults for each driver.
|
||||
|
||||
Deprecated. No replacement.
|
||||
|
||||
.. note::
|
||||
|
||||
See ``--ao=help`` for a list of compiled-in audio output drivers. The
|
||||
|
||||
+17
-14
@@ -940,15 +940,19 @@ Property list
|
||||
Total A-V sync correction done. Unavailable if audio or video is
|
||||
disabled.
|
||||
|
||||
``drop-frame-count``
|
||||
``decoder-frame-drop-count``
|
||||
Video frames dropped by decoder, because video is too far behind audio (when
|
||||
using ``--framedrop=decoder``). Sometimes, this may be incremented in other
|
||||
situations, e.g. when video packets are damaged, or the decoder doesn't
|
||||
follow the usual rules. Unavailable if video is disabled.
|
||||
|
||||
``vo-drop-frame-count``
|
||||
``drop-frame-count`` is a deprecated alias.
|
||||
|
||||
``frame-drop-count``
|
||||
Frames dropped by VO (when using ``--framedrop=vo``).
|
||||
|
||||
``vo-drop-frame-count`` is a deprecated alias.
|
||||
|
||||
``mistimed-frame-count``
|
||||
Number of video frames that were not timed correctly in display-sync mode
|
||||
for the sake of keeping A/V sync. This does not include external
|
||||
@@ -1331,8 +1335,8 @@ Property list
|
||||
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 ``,``.
|
||||
``--opengl-hwdec-interop`` 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.
|
||||
@@ -1341,6 +1345,9 @@ Property list
|
||||
multiple interop drivers for the same hardware decoder, depending on
|
||||
platform and VO.
|
||||
|
||||
This is somewhat similar to the ``--opengl-hwdec-interop`` option, but
|
||||
it returns the actually loaded backend, not the value of this option.
|
||||
|
||||
``video-format``
|
||||
Video format as string.
|
||||
|
||||
@@ -1545,10 +1552,6 @@ Property list
|
||||
|
||||
This property is experimental and might be removed in the future.
|
||||
|
||||
``stream-capture`` (RW)
|
||||
A filename, see ``--stream-capture``. Setting this will start capture using
|
||||
the given filename. Setting it to an empty string will stop it.
|
||||
|
||||
``tv-brightness``, ``tv-contrast``, ``tv-saturation``, ``tv-hue`` (RW)
|
||||
TV stuff.
|
||||
|
||||
@@ -1912,8 +1915,9 @@ Property list
|
||||
|
||||
The ``name`` is what is to be passed to the ``--audio-device`` option (and
|
||||
often a rather cryptic audio API-specific ID), while ``description`` is
|
||||
human readable free form text. The description is an empty string if none
|
||||
was received.
|
||||
human readable free form text. The description is set to the device name
|
||||
(minus mpv-specific ``<driver>/`` prefix) if no description is available
|
||||
or the description would have been an empty string.
|
||||
|
||||
The special entry with the name set to ``auto`` selects the default audio
|
||||
output driver and the default device.
|
||||
@@ -2146,10 +2150,9 @@ caveats with some properties (due to historical reasons):
|
||||
Strictly speaking, option access via API (e.g. ``mpv_set_option_string()``)
|
||||
has the same problem, and it's only a difference between CLI/API.
|
||||
|
||||
``demuxer``, ``idle``, ``length``, ``audio-samplerate``, ``audio-channels``, ``audio-format``, ``fps``, ``cache``, ``playlist-pos``, ``chapter``
|
||||
These behave completely different as property, but are deprecated (newer
|
||||
aliases which don't conflict have been added). After the deprecation period
|
||||
they will be changed to the proper option behavior.
|
||||
``playlist-pos``, ``chapter``
|
||||
These properties behave different from the deprecated options with the same
|
||||
names.
|
||||
|
||||
Property Expansion
|
||||
------------------
|
||||
|
||||
@@ -241,20 +241,6 @@ extra commands can also be used as part of the protocol:
|
||||
By default, most events are enabled, and there is not much use for this
|
||||
command.
|
||||
|
||||
``suspend``
|
||||
Deprecated, will be removed completely in 0.21.0.
|
||||
|
||||
Suspend the mpv main loop. There is a long-winded explanation of this in
|
||||
the C API function ``mpv_suspend()``. In short, this prevents the player
|
||||
from displaying the next video frame, so that you don't get blocked when
|
||||
trying to access the player.
|
||||
|
||||
``resume``
|
||||
Deprecated, will be removed completely in 0.21.0.
|
||||
|
||||
Undo one ``suspend`` call. ``suspend`` increments an internal counter, and
|
||||
``resume`` decrements it. When 0 is reached, the player is actually resumed.
|
||||
|
||||
``get_version``
|
||||
Returns the client API version the C API of the remote mpv instance
|
||||
provides.
|
||||
|
||||
@@ -14,3 +14,62 @@ mpv, further documentation is spread over a few places:
|
||||
- http://mpv.io/manual/master/#list-of-input-commands
|
||||
- http://mpv.io/manual/master/#properties
|
||||
- https://github.com/mpv-player/mpv-examples/tree/master/libmpv
|
||||
|
||||
C PLUGINS
|
||||
=========
|
||||
|
||||
You can write C plugins for mpv. These use the libmpv API, although they do not
|
||||
use the libmpv library itself.
|
||||
|
||||
Currently, they must be explicitly enabled at build time with
|
||||
``--enable-cplugins``. They are available on Linux/BSD platforms only.
|
||||
|
||||
C plugins location
|
||||
------------------
|
||||
|
||||
C plugins are put into the mpv scripts directory in its config directory
|
||||
(see the `FILES`_ section for details). They must have a ``.so`` file extension.
|
||||
They can also be explicitly loaded with the ``--script`` option.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
A C plugin must export the following function::
|
||||
|
||||
int mpv_open_cplugin(mpv_handle *handle)
|
||||
|
||||
The plugin function will be called on loading time. This function does not
|
||||
return as long as your plugin is loaded (it runs in its own thread). The
|
||||
``handle`` will be deallocated as soon as the plugin function returns.
|
||||
|
||||
The return value is interpreted as error status. A value of ``0`` is
|
||||
interpreted as success, while ``-1`` signals an error. In the latter case,
|
||||
the player prints an uninformative error message that loading failed.
|
||||
|
||||
Return values other than ``0`` and ``-1`` are reserved, and trigger undefined
|
||||
behavior.
|
||||
|
||||
Within the plugin function, you can call libmpv API functions. The ``handle``
|
||||
is created by ``mpv_create_client()`` (or actually an internal equivalent),
|
||||
and belongs to you. You can call ``mpv_wait_event()`` to wait for things
|
||||
happening, and so on.
|
||||
|
||||
Note that the player might block until your plugin calls ``mpv_wait_event()``
|
||||
for the first time. This gives you a chance to install initial hooks etc.
|
||||
before playback begins.
|
||||
|
||||
The details are quite similar to Lua scripts.
|
||||
|
||||
Linkage to libmpv
|
||||
-----------------
|
||||
|
||||
The current implementation requires that your plugins are **not** linked against
|
||||
libmpv. What your plugins uses are not symbols from a libmpv binary, but
|
||||
symbols from the mpv host binary.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
See:
|
||||
|
||||
- https://github.com/mpv-player/mpv-examples/tree/master/cplugins
|
||||
+13
-20
@@ -43,9 +43,8 @@ timers added with ``mp.add_timeout`` or similar.
|
||||
|
||||
When the player quits, all scripts will be asked to terminate. This happens via
|
||||
a ``shutdown`` event, which by default will make the event loop return. If your
|
||||
script got into an endless loop, mpv will probably behave fine during playback
|
||||
(unless the player is suspended, see ``mp.suspend``), but it won't terminate
|
||||
when quitting, because it's waiting on your script.
|
||||
script got into an endless loop, mpv will probably behave fine during playback,
|
||||
but it won't terminate when quitting, because it's waiting on your script.
|
||||
|
||||
Internally, the C code will call the Lua function ``mp_event_loop`` after
|
||||
loading a Lua script. This function is normally defined by the default prelude
|
||||
@@ -412,27 +411,16 @@ These also live in the ``mp`` module, but are documented separately as they
|
||||
are useful only in special situations.
|
||||
|
||||
``mp.suspend()``
|
||||
This function has been deprecated in mpv 0.21.0 (no replacement).
|
||||
|
||||
Suspend the mpv main loop. There is a long-winded explanation of this in
|
||||
the C API function ``mpv_suspend()``. In short, this prevents the player
|
||||
from displaying the next video frame, so that you don't get blocked when
|
||||
trying to access the player.
|
||||
|
||||
Before mpv 0.17.0, this was automatically called by the event handler.
|
||||
This function has been deprecated in mpv 0.21.0 and does nothing starting
|
||||
with mpv 0.23.0 (no replacement).
|
||||
|
||||
``mp.resume()``
|
||||
This function has been deprecated in mpv 0.21.0 (no replacement).
|
||||
|
||||
Undo one ``mp.suspend()`` call. ``mp.suspend()`` increments an internal
|
||||
counter, and ``mp.resume()`` decrements it. When 0 is reached, the player
|
||||
is actually resumed.
|
||||
This function has been deprecated in mpv 0.21.0 and does nothing starting
|
||||
with mpv 0.23.0 (no replacement).
|
||||
|
||||
``mp.resume_all()``
|
||||
This function has been deprecated in mpv 0.21.0 (no replacement).
|
||||
|
||||
This resets the internal suspend counter and resumes the player. (It's
|
||||
like calling ``mp.resume()`` until the player is actually resumed.)
|
||||
This function has been deprecated in mpv 0.21.0 and does nothing starting
|
||||
with mpv 0.23.0 (no replacement).
|
||||
|
||||
``mp.get_wakeup_pipe()``
|
||||
Calls ``mpv_get_wakeup_pipe()`` and returns the read end of the wakeup
|
||||
@@ -463,6 +451,11 @@ are useful only in special situations.
|
||||
multiple properties at once, you might not want to act on each property
|
||||
change, but only when all change notifications have been received.
|
||||
|
||||
``mp.unregister_idle(fn)``
|
||||
Undo ``mp.register_idle(fn)``. This removes all idle handlers that
|
||||
are equal to the ``fn`` parameter. This uses normal Lua ``==`` comparison,
|
||||
so be careful when dealing with closures.
|
||||
|
||||
``mp.enable_messages(level)``
|
||||
Set the minimum log level of which mpv message output to receive. These
|
||||
messages are normally printed to the terminal. By calling this function,
|
||||
|
||||
+45
-5
@@ -9,6 +9,8 @@ a media player
|
||||
:Manual section: 1
|
||||
:Manual group: multimedia
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
@@ -138,7 +140,7 @@ L
|
||||
Toggle infinite looping.
|
||||
|
||||
Ctrl + and Ctrl -
|
||||
Adjust audio delay by +/- 0.1 seconds.
|
||||
Adjust audio delay (A/V sync) by +/- 0.1 seconds.
|
||||
|
||||
u
|
||||
Switch between applying no style overrides to SSA/ASS subtitles, and
|
||||
@@ -208,9 +210,6 @@ Alt+2 (and command+2 on OSX)
|
||||
command + f (OSX only)
|
||||
Toggle fullscreen (see also ``--fs``).
|
||||
|
||||
command + [ and command + ] (OSX only)
|
||||
Set video window alpha.
|
||||
|
||||
(The following keys are valid if you have a keyboard with multimedia keys.)
|
||||
|
||||
PAUSE
|
||||
@@ -244,6 +243,16 @@ button 5 and button 6
|
||||
USAGE
|
||||
=====
|
||||
|
||||
Command line arguments starting with ``-`` are interpreted as options,
|
||||
everything else as filenames or URLs. All options except *flag* options (or
|
||||
choice options which include ``yes``) require a parameter in the form
|
||||
``--option=value``.
|
||||
|
||||
One exception is the lone ``-`` (without anything else), which means media data
|
||||
will be read from stdin. Also, ``--`` (without anything else) will make the
|
||||
player interpret all following arguments as filenames, even if they start with
|
||||
``-``. (To play a file named ``-``, you need to use ``./-``.)
|
||||
|
||||
Every *flag* option has a *no-flag* counterpart, e.g. the opposite of the
|
||||
``--fs`` option is ``--no-fs``. ``--fs=yes`` is same as ``--fs``, ``--fs=no``
|
||||
is the same as ``--no-fs``.
|
||||
@@ -251,6 +260,24 @@ is the same as ``--no-fs``.
|
||||
If an option is marked as *(XXX only)*, it will only work in combination with
|
||||
the *XXX* option or if *XXX* is compiled in.
|
||||
|
||||
Legacy option syntax
|
||||
--------------------
|
||||
|
||||
The ``--option=value`` syntax is not strictly enforced, and the alternative
|
||||
legacy syntax ``-option value`` and ``--option value`` will also work. This is
|
||||
mostly for compatibility with MPlayer. Using these should be avoided. Their
|
||||
semantics can change any time in the future.
|
||||
|
||||
For example, the alternative syntax will consider an argument following the
|
||||
option a filename. ``mpv -fs no`` will attempt to play a file named ``no``,
|
||||
because ``--fs`` is a flag option that requires no parameter. If an option
|
||||
changes and its parameter becomes optional, then a command line using the
|
||||
alternative syntax will break.
|
||||
|
||||
Currently, the parser makes no difference whether an option starts with ``--``
|
||||
or a single ``-``. This might also change in the future, and ``--option value``
|
||||
might always interpret ``value`` as filename in order to reduce ambiguities.
|
||||
|
||||
Escaping spaces and other special characters
|
||||
--------------------------------------------
|
||||
|
||||
@@ -431,7 +458,7 @@ tree and play the longest title.
|
||||
a bitmap video stream which can be superimposed over the main
|
||||
movie. mpv's subtitle styling and positioning options and keyboard
|
||||
shortcuts generally do not work with image-based subtitles.
|
||||
Exceptions include options like ``--stretch-dvd-subs`` and
|
||||
Exceptions include options like ``--stretch-dvd-subs`` and
|
||||
``--stretch-image-subs-to-screen``.
|
||||
|
||||
|
||||
@@ -646,6 +673,19 @@ PROTOCOLS
|
||||
either aliases to documented protocols, or are just redirections to
|
||||
protocols implemented and documented in FFmpeg.
|
||||
|
||||
``data:`` is supported in FFmpeg (not in Libav), but needs to be in the
|
||||
format ``data://``. This is done to avoid ambiguity with filenames. You
|
||||
can also prefix it with ``lavf://`` or ``ffmpeg://``.
|
||||
|
||||
``ytdl://...``
|
||||
By default, the youtube-dl hook script (enabled by default for mpv CLI)
|
||||
only looks at http URLs. Prefixing an URL with ``ytdl://`` forces it to
|
||||
be always processed by the script. This can also be used to invoke special
|
||||
youtube-dl functionality like playing a video by ID or invoking search.
|
||||
|
||||
Keep in mind that you can't pass youtube-dl command line options by this,
|
||||
and you have to use ``--ytdl-raw-options`` instead.
|
||||
|
||||
``-``
|
||||
Play data from stdin.
|
||||
|
||||
|
||||
+342
-230
@@ -134,11 +134,11 @@ Playback Control
|
||||
speed higher than normal automatically inserts the ``scaletempo`` audio
|
||||
filter.
|
||||
|
||||
``--loop=<N|inf|force|no>``
|
||||
``--loop=<N|inf|force|no>``, ``--loop``
|
||||
Loops playback ``N`` times. A value of ``1`` plays it one time (default),
|
||||
``2`` two times, etc. ``inf`` means forever. ``no`` is the same as ``1`` and
|
||||
disables looping. If several files are specified on command line, the
|
||||
entire playlist is looped.
|
||||
entire playlist is looped. ``--loop`` is the same as ``--loop=inf``.
|
||||
|
||||
The ``force`` mode is like ``inf``, but does not skip playlist entries
|
||||
which have been marked as failing. This means the player might waste CPU
|
||||
@@ -267,6 +267,26 @@ Playback Control
|
||||
Note that ``--playlist`` always loads all entries, so you use that instead
|
||||
if you really have the need for this functionality.
|
||||
|
||||
``--access-references=<yes|no>``
|
||||
Follow any references in the file being opened (default: yes). Disabling
|
||||
this is helpful if the file is automatically scanned (e.g. thumbnail
|
||||
generation). If the thumbnail scanner for example encounters a playlist
|
||||
file, which contains network URLs, and the scanner should not open these,
|
||||
enabling this option will prevent it. This option also disables ordered
|
||||
chapters, mov reference files, opening of archives, and a number of other
|
||||
features.
|
||||
|
||||
On older FFmpeg versions, this will not work in some cases. Some FFmpeg
|
||||
demuxers might not respect this option.
|
||||
|
||||
This option does not prevent opening of paired subtitle files and such. Use
|
||||
``--autoload-files=no`` to prevent this.
|
||||
|
||||
This option does not always work if you open non-files (for example using
|
||||
``dvd://directory`` would open a whole bunch of files in the given
|
||||
directory). Prefixing the filename with ``./`` if it doesn't start with
|
||||
a ``/`` will avoid this.
|
||||
|
||||
``--loop-file=<N|inf|no>``
|
||||
Loop a single file N times. ``inf`` means forever, ``no`` means normal
|
||||
playback. For compatibility, ``--loop-file`` and ``--loop-file=yes`` are
|
||||
@@ -330,9 +350,10 @@ Program Behavior
|
||||
``--help``, ``--h``
|
||||
Show short summary of options.
|
||||
|
||||
You can also pass a shell pattern to this option, which will list all
|
||||
matching top-level options, e.g. ``--h=*scale*`` for all options that
|
||||
contain the word "scale".
|
||||
You can also pass a string to this option, which will list all top-level
|
||||
options which contain the string in the name, e.g. ``--h=scale`` for all
|
||||
options that contain the word ``scale``. The special string ``*`` lists
|
||||
all top-level options.
|
||||
|
||||
``-v``
|
||||
Increment verbosity level, one level for each ``-v`` found on the command
|
||||
@@ -534,7 +555,7 @@ Video
|
||||
Specify the video output backend to be used. See `VIDEO OUTPUT DRIVERS`_ for
|
||||
details and descriptions of available drivers.
|
||||
|
||||
``--vd=<[+|-]family1:(*|decoder1),[+|-]family2:(*|decoder2),...[-]>``
|
||||
``--vd=<...>``
|
||||
Specify a priority list of video decoders to be used, according to their
|
||||
family and name. See ``--ad`` for further details. Both of these options
|
||||
use the same syntax and semantics; the only difference is that they
|
||||
@@ -610,6 +631,7 @@ Video
|
||||
:yes: exactly the same as ``auto``
|
||||
:auto-copy: enable best hw decoder with copy-back (see below)
|
||||
:vdpau: requires ``--vo=vdpau`` or ``--vo=opengl`` (Linux only)
|
||||
:vdpau-copy: copies video back into system RAM (Linux with some GPUs only)
|
||||
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux only)
|
||||
:vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
|
||||
:videotoolbox: requires ``--vo=opengl`` (OS X 10.8 and up only)
|
||||
@@ -618,8 +640,8 @@ Video
|
||||
``--opengl-backend=dxinterop`` (Windows only)
|
||||
:dxva2-copy: copies video back to system RAM (Windows only)
|
||||
:d3d11va: requires ``--vo=opengl`` with ``--opengl-backend=angle``
|
||||
(Windows only)
|
||||
:d3d11va-copy: copies video back to system RAM (Windows only)
|
||||
(Windows 8+ only)
|
||||
:d3d11va-copy: copies video back to system RAM (Windows 8+ only)
|
||||
:mediacodec: copies video back to system RAM (Android only)
|
||||
:rpi: requires ``--vo=opengl`` (Raspberry Pi only - default if available)
|
||||
:rpi-copy: copies video back to system RAM (Raspberry Pi only)
|
||||
@@ -647,14 +669,22 @@ Video
|
||||
forcing it with ``--opengl-backend=x11``, but the vaapi/GLX interop is
|
||||
said to be slower than ``vaapi-copy``.
|
||||
|
||||
The ``cuda`` and ``cuda-copy`` modes provides deinterlacing in the decoder
|
||||
which is useful as there is no other deinterlacing mechanism in the opengl
|
||||
output path. To use this deinterlacing you must pass the option:
|
||||
``vd-lavc-o=deint=[weave|bob|adaptive]``.
|
||||
Pass ``weave`` (or leave the option unset) to not attempt any
|
||||
deinterlacing. ``cuda`` should always be preferred unless the ``opengl``
|
||||
vo is not being used or filters are required.
|
||||
|
||||
Most video filters will not work with hardware decoding as they are
|
||||
primarily implemented on the CPU. Some exceptions are ``vdpaupp``,
|
||||
``vdpaurb`` and ``vavpp``. See `VIDEO FILTERS`_ for more details.
|
||||
|
||||
The ``vaapi-copy`` and ``dxva2-copy`` modes allow you to use hardware
|
||||
The ``...-copy`` modes (e.g. ``dxva2-copy``) allow you to use hardware
|
||||
decoding with any VO, backend or filter. Because these copy the decoded
|
||||
video back to system RAM, they're likely less efficient than the ``vaapi``
|
||||
or ``dxva2`` modes respectively.
|
||||
video back to system RAM, they're likely less efficient than the direct
|
||||
modes (like e.g. ``dxva2``).
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -697,18 +727,6 @@ Video
|
||||
affect this additionally. This can give incorrect results even with
|
||||
completely ordinary video sources.
|
||||
|
||||
``cuda`` is usually safe. Interlaced content can be deinterlaced by
|
||||
the decoder, which is useful as there is no other deinterlacing
|
||||
mechanism in the opengl output path. To use this deinterlacing you
|
||||
must pass the option: ``vd-lavc-o=deint=[weave|bob|adaptive]``. Pass
|
||||
``weave`` to not attempt any deinterlacing.
|
||||
10bit HEVC is available if the hardware supports it but it will be
|
||||
rounded down to 8 bits.
|
||||
|
||||
``cuda-copy`` has the same behaviour as ``cuda`` - including the ability
|
||||
to deinterlace inside the decoder. However, traditional deinterlacing
|
||||
filters can be used in this case.
|
||||
|
||||
``rpi`` always uses the hardware overlay renderer, even with
|
||||
``--vo=opengl``.
|
||||
|
||||
@@ -724,12 +742,12 @@ Video
|
||||
In particular, ``auto-copy`` will only select safe modes
|
||||
(although potentially slower than other methods).
|
||||
|
||||
``--hwdec-preload=<api>``
|
||||
``--opengl-hwdec-interop=<name>``
|
||||
This is useful for the ``opengl`` and ``opengl-cb`` VOs for creating the
|
||||
hardware decoding OpenGL interop context, but without actually enabling
|
||||
hardware decoding itself (like ``--hwdec`` does).
|
||||
|
||||
If set to ``no`` (default), the ``--hwdec`` option is used.
|
||||
If set to an empty string (default), the ``--hwdec`` option is used.
|
||||
|
||||
For ``opengl``, if set, do not create the interop context on demand, but
|
||||
when the VO is created.
|
||||
@@ -740,6 +758,19 @@ Video
|
||||
to temporarily set the ``hwdec`` option just during OpenGL context
|
||||
initialization with ``mpv_opengl_cb_init_gl()``.
|
||||
|
||||
See ``--opengl-hwdec-interop=help`` for accepted values. This lists the
|
||||
interop backend, with the ``--hwdec`` alias after it in ``[...]``. Consider
|
||||
all values except the proper interop backend name, ``auto``, and ``no`` as
|
||||
silently deprecated and subject to change. Also, if you use this in
|
||||
application code (e.g. via libmpv), any value other than ``auto`` and ``no``
|
||||
should be avoided, as backends can change.
|
||||
|
||||
Currently the option sets a single value. It is possible that the option
|
||||
type changes to a list in the future.
|
||||
|
||||
The old alias ``--hwdec-preload`` has different behavior if the option value
|
||||
is ``no``.
|
||||
|
||||
``--videotoolbox-format=<name>``
|
||||
Set the internal pixel format used by ``--hwdec=videotoolbox`` on OSX. The
|
||||
choice of the format can influence performance considerably. On the other
|
||||
@@ -1099,11 +1130,10 @@ Audio
|
||||
Possible codecs are ``ac3``, ``dts``, ``dts-hd``. Multiple codecs can be
|
||||
specified by separating them with ``,``. ``dts`` refers to low bitrate DTS
|
||||
core, while ``dts-hd`` refers to DTS MA (receiver and OS support varies).
|
||||
You should only use either ``dts`` or ``dts-hd`` (if both are specified,
|
||||
and ``dts`` comes first, only ``dts`` will be used).
|
||||
If both ``dts`` and ``dts-hd`` are specified, it behaves equivalent to
|
||||
specifying ``dts-hd`` only.
|
||||
|
||||
In general, all codecs in the ``spdif`` family listed with ``--ad=help``
|
||||
are supported in theory.
|
||||
In earlier mpv versions
|
||||
|
||||
.. admonition:: Warning
|
||||
|
||||
@@ -1111,37 +1141,31 @@ Audio
|
||||
multichannel PCM, and mpv supports lossless DTS-HD decoding via
|
||||
FFmpeg's new DCA decoder (based on libdcadec).
|
||||
|
||||
``--ad=<[+|-]family1:(*|decoder1),[+|-]family2:(*|decoder2),...[-]>``
|
||||
``--ad=<decoder1,decoder2,...[-]>``
|
||||
Specify a priority list of audio decoders to be used, according to their
|
||||
family and decoder name. Entries like ``family:*`` prioritize all decoders
|
||||
of the given family. When determining which decoder to use, the first
|
||||
decoder that matches the audio format is selected. If that is unavailable,
|
||||
the next decoder is used. Finally, it tries all other decoders that are not
|
||||
decoder name. When determining which decoder to use, the first decoder that
|
||||
matches the audio format is selected. If that is unavailable, the next
|
||||
decoder is used. Finally, it tries all other decoders that are not
|
||||
explicitly selected or rejected by the option.
|
||||
|
||||
``-`` at the end of the list suppresses fallback on other available
|
||||
decoders not on the ``--ad`` list. ``+`` in front of an entry forces the
|
||||
decoder. Both of these should not normally be used, because they break
|
||||
normal decoder auto-selection!
|
||||
|
||||
``-`` in front of an entry disables selection of the decoder.
|
||||
normal decoder auto-selection! Both of these methods are deprecated.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``--ad=lavc:mp3float``
|
||||
``--ad=mp3float``
|
||||
Prefer the FFmpeg/Libav ``mp3float`` decoder over all other MP3
|
||||
decoders.
|
||||
|
||||
``--ad=spdif:ac3,lavc:*``
|
||||
Always prefer spdif AC3 over FFmpeg/Libav over anything else.
|
||||
|
||||
``--ad=help``
|
||||
List all available decoders.
|
||||
|
||||
.. admonition:: Warning
|
||||
|
||||
Enabling compressed audio passthrough (AC3 and DTS via SPDIF/HDMI) with
|
||||
this option is deprecated. Use ``--audio-spdif`` instead.
|
||||
this option is not possible. Use ``--audio-spdif`` instead.
|
||||
|
||||
``--volume=<value>``
|
||||
Set the startup volume. 0 means silence, 100 means no volume reduction or
|
||||
@@ -1174,9 +1198,9 @@ Audio
|
||||
to use the volume controls of the audio output driver or the internal mpv
|
||||
volume filter.
|
||||
|
||||
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 current behavior is that softvol is always enabled, i.e. as if this
|
||||
option is set to ``yes``. The other behaviors are not available anymore,
|
||||
although ``auto`` almost matches current behavior in most cases.
|
||||
|
||||
The ``no`` behavior is still partially available through the ``ao-volume``
|
||||
and ``ao-mute`` properties. But there are no options to reset these.
|
||||
@@ -1507,7 +1531,7 @@ Subtitles
|
||||
``--sub-scale-with-window=<yes|no>``
|
||||
Make the subtitle font size relative to the window, instead of the video.
|
||||
This is useful if you always want the same font size, even if the video
|
||||
doesn't covert the window fully, e.g. because screen aspect and window
|
||||
doesn't cover the window fully, e.g. because screen aspect and window
|
||||
aspect mismatch (and the player adds black bars).
|
||||
|
||||
Default: yes.
|
||||
@@ -1702,6 +1726,13 @@ Subtitles
|
||||
|
||||
Disabled by default.
|
||||
|
||||
``--image-subs-video-resolution=<yes|no>``
|
||||
Override the image subtitle resolution with the video resolution
|
||||
(default: no). Normally, the subtitle canvas is fit into the video canvas
|
||||
(e.g. letterboxed). Setting this option uses the video size as subtitle
|
||||
canvas size. Can be useful to test broken subtitles, which often happen
|
||||
when the video was trancoded, while attempting to keep the old subtitles.
|
||||
|
||||
``--sub-ass``, ``--no-sub-ass``
|
||||
Render ASS subtitles natively (enabled by default).
|
||||
|
||||
@@ -1733,66 +1764,32 @@ Subtitles
|
||||
:all: Load all subs in the current and ``--sub-paths`` directories.
|
||||
|
||||
``--sub-codepage=<codepage>``
|
||||
If your system supports ``iconv(3)``, you can use this option to specify
|
||||
the subtitle codepage. By default, uchardet will be used to guess the
|
||||
charset. If mpv is not compiled with uchardet, enca will be used.
|
||||
If mpv is compiled with neither uchardet nor enca, ``UTF-8:UTF-8-BROKEN``
|
||||
is the default, which means it will try to use UTF-8, otherwise the
|
||||
``UTF-8-BROKEN`` pseudo codepage (see below).
|
||||
You can use this option to specify the subtitle codepage. uchardet will be
|
||||
used to guess the charset. (If mpv was not compiled with uchardet, then
|
||||
``utf-8`` is the effective default.)
|
||||
|
||||
The default value for this option is ``auto``, whose actual effect depends
|
||||
on whether ENCA is compiled.
|
||||
The default value for this option is ``auto``, which enables autodetection.
|
||||
|
||||
.. admonition:: Warning
|
||||
The following steps are taken to determine the final codepage, in order:
|
||||
|
||||
If you force the charset, even subtitles that are known to be
|
||||
UTF-8 will be recoded, which is perhaps not what you expect. Prefix
|
||||
codepages with ``utf8:`` if you want the codepage to be used only if the
|
||||
input is not valid UTF-8.
|
||||
- if the specific codepage has a ``+``, use that codepage
|
||||
- if the data looks like UTF-8, assume it is UTF-8
|
||||
- if ``--sub-codepage`` is set to a specific codepage, use that
|
||||
- run uchardet, and if successful, use that
|
||||
- otherwise, use ``UTF-8-BROKEN``
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
- ``--sub-codepage=utf8:latin2`` Use Latin 2 if input is not UTF-8.
|
||||
- ``--sub-codepage=cp1250`` Always force recoding to cp1250.
|
||||
- ``--sub-codepage=latin2`` Use Latin 2 if input is not UTF-8.
|
||||
- ``--sub-codepage=+cp1250`` Always force recoding to cp1250.
|
||||
|
||||
The pseudo codepage ``UTF-8-BROKEN`` is used internally. When it
|
||||
is the codepage, subtitles are interpreted as UTF-8 with "Latin 1" as
|
||||
fallback for bytes which are not valid UTF-8 sequences. iconv is
|
||||
never involved in this mode.
|
||||
The pseudo codepage ``UTF-8-BROKEN`` is used internally. If it's set,
|
||||
subtitles are interpreted as UTF-8 with "Latin 1" as fallback for bytes
|
||||
which are not valid UTF-8 sequences. iconv is never involved in this mode.
|
||||
|
||||
If the player was compiled with ENCA support, you can control it with the
|
||||
following syntax:
|
||||
This option changed in mpv 0.23.0. Support for the old syntax was fully
|
||||
removed in mpv 0.24.0.
|
||||
|
||||
``--sub-codepage=enca:<language>:<fallback codepage>``
|
||||
|
||||
Language is specified using a two letter code to help ENCA detect
|
||||
the codepage automatically. If an invalid language code is
|
||||
entered, mpv will complain and list valid languages. (Note
|
||||
however that this list will only be printed when the conversion code is actually
|
||||
called, for example when loading an external subtitle). The
|
||||
fallback codepage is used if autodetection fails. If no fallback
|
||||
is specified, ``UTF-8-BROKEN`` is used.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
- ``--sub-codepage=enca:pl:cp1250`` guess the encoding, assuming the subtitles
|
||||
are Polish, fall back on cp1250
|
||||
- ``--sub-codepage=enca:pl`` guess the encoding for Polish, fall back on UTF-8.
|
||||
- ``--sub-codepage=enca`` try universal detection, fall back on UTF-8.
|
||||
|
||||
If the player was compiled with libguess support, you can use it with:
|
||||
|
||||
``--sub-codepage=guess:<language>:<fallback codepage>``
|
||||
|
||||
libguess always needs a language. There is no universal detection
|
||||
mode. Use ``--sub-codepage=guess:help`` to get a list of
|
||||
languages subject to the same caveat as with ENCA above.
|
||||
|
||||
If the player was compiled with uchardet support you can use it with:
|
||||
|
||||
``--sub-codepage=uchardet``
|
||||
|
||||
This mode doesn't take language or fallback codepage.
|
||||
|
||||
``--sub-fix-timing``, ``--no-sub-fix-timing``
|
||||
By default, subtitle timing is adjusted to remove minor gaps or overlaps
|
||||
@@ -1975,6 +1972,18 @@ Subtitles
|
||||
Vertical position (default: ``bottom``).
|
||||
Details see ``--sub-align-x``.
|
||||
|
||||
``--sub-justify=<auto|left|center|right>``
|
||||
Control how multi line subs are justified irrespective of where they
|
||||
are aligned (default: ``auto`` which justifies as defined by
|
||||
``--sub-align-y``).
|
||||
Left justification is recommended to make the subs easier to read
|
||||
as it is easier for the eyes.
|
||||
|
||||
``--sub-ass-justify=<yes|no>``
|
||||
Applies justification as defined by ``--sub-justify`` on ASS subtitles
|
||||
if ``--sub-ass-style-override`` is not set to ``no``.
|
||||
Default: ``no``.
|
||||
|
||||
``--sub-shadow-color=<color>``
|
||||
See ``--sub-color``. Color used for sub text shadow.
|
||||
|
||||
@@ -2040,10 +2049,6 @@ Window
|
||||
|
||||
See also ``--screen``.
|
||||
|
||||
``--fs-black-out-screens``
|
||||
|
||||
OS X only. Black out other displays when going fullscreen.
|
||||
|
||||
``--keep-open=<yes|no|always>``
|
||||
Do not terminate when playing or seeking beyond the end of the file, and
|
||||
there is not next file to be played (and ``--loop`` is not used).
|
||||
@@ -2115,6 +2120,9 @@ Window
|
||||
|
||||
Enabled by default.
|
||||
|
||||
``--snap-window``
|
||||
(Windows only) Snap the player window to screen edges.
|
||||
|
||||
``--ontop``
|
||||
Makes the player window stay on top of other windows.
|
||||
|
||||
@@ -2181,7 +2189,7 @@ Window
|
||||
``50%x50%``
|
||||
Forces the window width and height to half the screen width and
|
||||
height. Will show black borders to compensate for the video aspect
|
||||
ration (with most VOs and without ``--no-keepaspect``).
|
||||
ratio (with most VOs and without ``--no-keepaspect``).
|
||||
``50%+10+10``
|
||||
Sets the window to half the screen widths, and positions it 10
|
||||
pixels below/left of the top left corner of the screen.
|
||||
@@ -2304,7 +2312,7 @@ Window
|
||||
ensure it does not cause security problems (e.g. make sure to use full
|
||||
paths if "." is in your path like on Windows). It also only works when
|
||||
playing video (i.e. not with ``--no-video`` but works with
|
||||
``-vo=null``).
|
||||
``--vo=null``).
|
||||
|
||||
This can be "misused" to disable screensavers that do not support the
|
||||
proper X API (see also ``--stop-screensaver``). If you think this is too
|
||||
@@ -2352,6 +2360,13 @@ Window
|
||||
- ``--monitoraspect=4:3`` or ``--monitoraspect=1.3333``
|
||||
- ``--monitoraspect=16:9`` or ``--monitoraspect=1.7777``
|
||||
|
||||
``--hidpi-window-scale``, ``--no-hidpi-window-scale``
|
||||
(OS X and X11 only)
|
||||
Scale the window size according to the backing scale factor (default: yes).
|
||||
On regular HiDPI resolutions the window opens with double the size but appears
|
||||
as having the same size as on none-HiDPI resolutions. This is the default OS X
|
||||
behavior.
|
||||
|
||||
``--monitorpixelaspect=<ratio>``
|
||||
Set the aspect of a single pixel of your monitor or TV screen (default:
|
||||
1). A value of 1 means square pixels (correct for (almost?) all LCDs). See
|
||||
@@ -2731,6 +2746,27 @@ Demuxer
|
||||
(This value tends to be fuzzy, because many file formats don't store linear
|
||||
timestamps.)
|
||||
|
||||
``--prefetch-playlist=<yes|no>``
|
||||
Prefetch next playlist entry while playback of the current entry is ending
|
||||
(default: no). This merely opens the URL of the next playlist entry as soon
|
||||
as the current URL is fully read.
|
||||
|
||||
This does **not** work with URLs resolved by the ``youtube-dl`` wrapper,
|
||||
and it won't.
|
||||
|
||||
This does not affect HLS (``.m3u8`` URLs) - HLS prefetching depends on the
|
||||
demuxer cache settings and is on by default.
|
||||
|
||||
This can give subtly wrong results if per-file options are used, or if
|
||||
options are changed in the time window between prefetching start and next
|
||||
file played.
|
||||
|
||||
This can occasionally make wrong prefetching decisions. For example, it
|
||||
can't predict whether you go backwards in the playlist, and assumes you
|
||||
won't edit the playlist.
|
||||
|
||||
Highly experimental.
|
||||
|
||||
``--force-seekable=<yes|no>``
|
||||
If the player thinks that the media is not seekable (e.g. playing from a
|
||||
pipe, or it's an http stream with a server that doesn't support range
|
||||
@@ -3789,21 +3825,7 @@ OpenGL renderer options
|
||||
-----------------------
|
||||
|
||||
The following video options are currently all specific to ``--vo=opengl`` and
|
||||
``-vo=opengl-cb`` only, which are the only VOs that implement them.
|
||||
|
||||
``--opengl-dumb-mode=<yes|no>``
|
||||
This mode is extremely restricted, and will disable most extended OpenGL
|
||||
features. This includes high quality scalers and custom shaders!
|
||||
|
||||
It is intended for hardware that does not support FBOs (including GLES,
|
||||
which supports it insufficiently), or to get some more performance out of
|
||||
bad or old hardware.
|
||||
|
||||
This mode is forced automatically if needed, and this option is mostly
|
||||
useful for debugging. It's also enabled automatically if nothing uses
|
||||
features which require FBOs.
|
||||
|
||||
This option might be silently removed in the future.
|
||||
``--vo=opengl-cb`` only, which are the only VOs that implement them.
|
||||
|
||||
``--scale=<filter>``
|
||||
|
||||
@@ -3856,7 +3878,25 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
|
||||
mpv --scale=help
|
||||
|
||||
``--scale-param1=<value>``, ``--scale-param2=<value>``
|
||||
``--cscale=<filter>``
|
||||
As ``--scale``, but for interpolating chroma information. If the image is
|
||||
not subsampled, this option is ignored entirely.
|
||||
|
||||
``--dscale=<filter>``
|
||||
Like ``--scale``, but apply these filters on downscaling instead. If this
|
||||
option is unset, the filter implied by ``--scale`` will be applied.
|
||||
|
||||
``--tscale=<filter>``
|
||||
The filter used for interpolating the temporal axis (frames). This is only
|
||||
used if ``--interpolation`` is enabled. The only valid choices for
|
||||
``--tscale`` are separable convolution filters (use ``--tscale=help`` to
|
||||
get a list). The default is ``mitchell``.
|
||||
|
||||
Note that the maximum supported filter radius is currently 3, due to
|
||||
limitations in the number of video textures that can be loaded
|
||||
simultaneously.
|
||||
|
||||
``--scale-param1=<value>``, ``--scale-param2=<value>``, ``--cscale-param1=<value>``, ``--cscale-param2=<value>``, ``--dscale-param1=<value>``, ``--dscale-param2=<value>``, ``--tscale-param1=<value>``, ``--tscale-param2=<value>``
|
||||
Set filter parameters. Ignored if the filter is not tunable. Currently,
|
||||
this affects the following filter parameters:
|
||||
|
||||
@@ -3873,14 +3913,28 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
never interpolate, thus behaving as if the regular nearest neighbour
|
||||
algorithm was used. Defaults to 0.0.
|
||||
|
||||
``--scale-blur=<value>``
|
||||
Kernel scaling factor (also known as a blur factor). Decreasing this makes
|
||||
the result sharper, increasing it makes it blurrier (default 0). If set to
|
||||
0, the kernel's preferred blur factor is used. Note that setting this too
|
||||
low (eg. 0.5) leads to bad results. It's generally recommended to stick to
|
||||
values between 0.8 and 1.2.
|
||||
``--scale-blur=<value>``, ``--scale-wblur=<value>``, ``--cscale-blur=<value>``, ``--cscale-wblur=<value>``, ``--dscale-blur=<value>``, ``--dscale-wblur=<value>``, ``--tscale-blur=<value>``, ``--tscale-wblur=<value>``
|
||||
Kernel/window scaling factor (also known as a blur factor). Decreasing this
|
||||
makes the result sharper, increasing it makes it blurrier (default 0). If
|
||||
set to 0, the kernel's preferred blur factor is used. Note that setting
|
||||
this too low (eg. 0.5) leads to bad results. It's generally recommended to
|
||||
stick to values between 0.8 and 1.2.
|
||||
|
||||
``--scale-radius=<value>``
|
||||
``--scale-clamp``, ``--cscale-clamp``, ``--dscale-clamp``, ``--tscale-clamp``
|
||||
Clamp the filter kernel's value range to [0-1]. This is especially useful
|
||||
for ``--tscale``, where it reduces excessive ringing artifacts in the
|
||||
temporal domain (which typically manifest themselves as short flashes or
|
||||
fringes of black, mostly around moving edges) in exchange for potentially
|
||||
adding more blur.
|
||||
|
||||
``--scale-taper=<value>``, ``--scale-wtaper=<value>``, ``--dscale-taper=<value>``, ``--dscale-wtaper=<value>``, ``--cscale-taper=<value>``, ``--cscale-wtaper=<value>``, ``--tscale-taper=<value>``, ``--tscale-wtaper=<value>``
|
||||
Kernel/window taper factor. Increasing this flattens the filter function.
|
||||
Value range is 0 to 1. A value of 0 (the default) means no flattening, a
|
||||
value of 1 makes the filter completely flat (equivalent to a box function).
|
||||
Values in between mean that some portion will be flat and the actual filter
|
||||
function will be squeezed into the space in between.
|
||||
|
||||
``--scale-radius=<value>``, ``--cscale-radius=<value>``, ``--dscale-radius=<value>``, ``--tscale-radius=<value>``
|
||||
Set radius for tunable filters, must be a float number between 0.5 and
|
||||
16.0. Defaults to the filter's preferred radius if not specified. Doesn't
|
||||
work for every scaler and VO combination.
|
||||
@@ -3889,7 +3943,7 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
ratio, the radius that actually being used might be different (most likely
|
||||
being increased a bit).
|
||||
|
||||
``--scale-antiring=<value>``
|
||||
``--scale-antiring=<value>``, ``--cscale-antiring=<value>``, ``--dscale-antiring=<value>``, ``--tscale-antiring=<value>``
|
||||
Set the antiringing strength. This tries to eliminate ringing, but can
|
||||
introduce other artifacts in the process. Must be a float number between
|
||||
0.0 and 1.0. The default value of 0.0 disables antiringing entirely.
|
||||
@@ -3897,14 +3951,14 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
Note that this doesn't affect the special filters ``bilinear`` and
|
||||
``bicubic_fast``.
|
||||
|
||||
``--scale-window=<window>``
|
||||
``--scale-window=<window>``, ``--cscale-window=<window>``, ``--dscale-window=<window>``, ``--tscale-window=<window>``
|
||||
(Advanced users only) Choose a custom windowing function for the kernel.
|
||||
Defaults to the filter's preferred window if unset. Use
|
||||
``--scale-window=help`` to get a list of supported windowing functions.
|
||||
|
||||
``--scale-wparam=<window>``
|
||||
``--scale-wparam=<window>``, ``--cscale-wparam=<window>``, ``--cscale-wparam=<window>``, ``--tscale-wparam=<window>``
|
||||
(Advanced users only) Configure the parameter for the window function given
|
||||
by ``--scale-window``. Ignored if the window is not tunable. Currently,
|
||||
by ``--scale-window`` etc. Ignored if the window is not tunable. Currently,
|
||||
this affects the following window parameters:
|
||||
|
||||
kaiser
|
||||
@@ -3929,6 +3983,50 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
will reproduce the source image perfectly if no scaling is performed.
|
||||
Enabled by default. Note that this option never affects ``--cscale``.
|
||||
|
||||
``--linear-scaling``
|
||||
Scale in linear light. It should only be used with a
|
||||
``--opengl-fbo-format`` that has at least 16 bit precision.
|
||||
|
||||
``--correct-downscaling``
|
||||
When using convolution based filters, extend the filter size when
|
||||
downscaling. Increases quality, but reduces performance while downscaling.
|
||||
|
||||
This will perform slightly sub-optimally for anamorphic video (but still
|
||||
better than without it) since it will extend the size to match only the
|
||||
milder of the scale factors between the axes.
|
||||
|
||||
``--interpolation``
|
||||
Reduce stuttering caused by mismatches in the video fps and display refresh
|
||||
rate (also known as judder).
|
||||
|
||||
.. warning:: This requires setting the ``--video-sync`` option to one
|
||||
of the ``display-`` modes, or it will be silently disabled.
|
||||
This was not required before mpv 0.14.0.
|
||||
|
||||
This essentially attempts to interpolate the missing frames by convoluting
|
||||
the video along the temporal axis. The filter used can be controlled using
|
||||
the ``--tscale`` setting.
|
||||
|
||||
Note that this relies on vsync to work, see ``--opengl-swapinterval`` for
|
||||
more information.
|
||||
|
||||
``--interpolation-threshold=<0..1,-1>``
|
||||
Threshold below which frame ratio interpolation gets disabled (default:
|
||||
``0.0001``). This is calculated as ``abs(disphz/vfps - 1) < threshold``,
|
||||
where ``vfps`` is the speed-adjusted video FPS, and ``disphz`` the
|
||||
display refresh rate. (The speed-adjusted video FPS is roughly equal to
|
||||
the normal video FPS, but with slowdown and speedup applied. This matters
|
||||
if you use ``--video-sync=display-resample`` to make video run synchronously
|
||||
to the display FPS, or if you change the ``speed`` property.)
|
||||
|
||||
The default is intended to almost always enable interpolation if the
|
||||
playback rate is even slightly different from the display refresh rate. But
|
||||
note that if you use e.g. ``--video-sync=display-vdrop``, small deviations
|
||||
in the rate can disable interpolation and introduce a discontinuity every
|
||||
other minute.
|
||||
|
||||
Set this to ``-1`` to disable this logic.
|
||||
|
||||
``--opengl-pbo``
|
||||
Enable use of PBOs. On some drivers this can be faster, especially if the
|
||||
source video size is huge (e.g. so called "4K" video). On other drivers it
|
||||
@@ -3981,21 +4079,6 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
debug OpenGL context (which does nothing with current graphics drivers
|
||||
as of this writing).
|
||||
|
||||
``--interpolation``
|
||||
Reduce stuttering caused by mismatches in the video fps and display refresh
|
||||
rate (also known as judder).
|
||||
|
||||
.. warning:: This requires setting the ``--video-sync`` option to one
|
||||
of the ``display-`` modes, or it will be silently disabled.
|
||||
This was not required before mpv 0.14.0.
|
||||
|
||||
This essentially attempts to interpolate the missing frames by convoluting
|
||||
the video along the temporal axis. The filter used can be controlled using
|
||||
the ``--tscale`` setting.
|
||||
|
||||
Note that this relies on vsync to work, see ``--opengl-swapinterval`` for
|
||||
more information.
|
||||
|
||||
``--opengl-swapinterval=<n>``
|
||||
Interval in displayed frames between two buffer swaps. 1 is equivalent to
|
||||
enable VSYNC, 0 to disable VSYNC. Defaults to 1 if not specified.
|
||||
@@ -4007,65 +4090,6 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
results, as can missing or incorrect display FPS information (see
|
||||
``--display-fps``).
|
||||
|
||||
``--dscale=<filter>``
|
||||
Like ``--scale``, but apply these filters on downscaling instead. If this
|
||||
option is unset, the filter implied by ``--scale`` will be applied.
|
||||
|
||||
``--cscale=<filter>``
|
||||
As ``--scale``, but for interpolating chroma information. If the image is
|
||||
not subsampled, this option is ignored entirely.
|
||||
|
||||
``--tscale=<filter>``
|
||||
The filter used for interpolating the temporal axis (frames). This is only
|
||||
used if ``--interpolation`` is enabled. The only valid choices for
|
||||
``--tscale`` are separable convolution filters (use ``--tscale=help`` to
|
||||
get a list). The default is ``mitchell``.
|
||||
|
||||
Note that the maximum supported filter radius is currently 3, due to
|
||||
limitations in the number of video textures that can be loaded
|
||||
simultaneously.
|
||||
|
||||
``--tscale-clamp``
|
||||
Clamp the ``--tscale`` filter kernel's value range to [0-1]. This reduces
|
||||
excessive ringing artifacts in the temporal domain (which typically
|
||||
manifest themselves as short flashes or fringes of black, mostly around
|
||||
moving edges) in exchange for potentially adding more blur.
|
||||
|
||||
``--interpolation-threshold=<0..1,-1>``
|
||||
Threshold below which frame ratio interpolation gets disabled (default:
|
||||
``0.0001``). This is calculated as ``abs(disphz/vfps - 1) < threshold``,
|
||||
where ``vfps`` is the speed-adjusted video FPS, and ``disphz`` the
|
||||
display refresh rate. (The speed-adjusted video FPS is roughly equal to
|
||||
the normal video FPS, but with slowdown and speedup applied. This matters
|
||||
if you use ``--video-sync=display-resample`` to make video run synchronously
|
||||
to the display FPS, or if you change the ``speed`` property.)
|
||||
|
||||
The default is intended to almost always enable interpolation if the
|
||||
playback rate is even slightly different from the display refresh rate. But
|
||||
note that if you use e.g. ``--video-sync=display-vdrop``, small deviations
|
||||
in the rate can disable interpolation and introduce a discontinuity every
|
||||
other minute.
|
||||
|
||||
Set this to ``-1`` to disable this logic.
|
||||
|
||||
``--dscale-radius``, ``--cscale-radius``, ``--tscale-radius``, etc.
|
||||
Set filter parameters for ``--dscale``, ``--cscale`` and ``--tscale``,
|
||||
respectively.
|
||||
|
||||
See the corresponding options for ``--scale``.
|
||||
|
||||
``--linear-scaling``
|
||||
Scale in linear light. It should only be used with a
|
||||
``--opengl-fbo-format`` that has at least 16 bit precision.
|
||||
|
||||
``--correct-downscaling``
|
||||
When using convolution based filters, extend the filter size when
|
||||
downscaling. Increases quality, but reduces performance while downscaling.
|
||||
|
||||
This will perform slightly sub-optimally for anamorphic video (but still
|
||||
better than without it) since it will extend the size to match only the
|
||||
milder of the scale factors between the axes.
|
||||
|
||||
``--opengl-shaders=<files>``
|
||||
Custom GLSL hooks. These are a flexible way to add custom fragment shaders,
|
||||
which can be injected at almost arbitrary points in the rendering pipeline,
|
||||
@@ -4307,16 +4331,70 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
|
||||
Windows only.
|
||||
|
||||
``--opengl-dcomposition=<yes|no>``
|
||||
Allows DirectComposition when using the ANGLE backend (default: yes).
|
||||
DirectComposition implies flip-model presentation, which can improve
|
||||
rendering efficiency on Windows 8+ by avoiding a copy of the video frame.
|
||||
mpv uses it by default where possible, but it can cause poor behaviour with
|
||||
some drivers, such as a black screen or graphical corruption when leaving
|
||||
full-screen mode. Use "no" to disable it.
|
||||
``--angle-d3d11-feature-level=<11_0|10_1|10_0|9_3>``
|
||||
Selects a specific feature level when using the ANGLE backend with D3D11.
|
||||
By default, the highest available feature level is used. This option can be
|
||||
used to select a lower feature level, which is mainly useful for debugging.
|
||||
Note that OpenGL ES 3.0 is only supported at feature level 10_1 or higher.
|
||||
Most extended OpenGL features will not work at lower feature levels
|
||||
(similar to ``--opengl-dumb-mode``).
|
||||
|
||||
Windows with ANGLE only.
|
||||
|
||||
``--angle-d3d11-warp=<yes|no|auto>``
|
||||
Use WARP (Windows Advanced Rasterization Platform) when using the ANGLE
|
||||
backend with D3D11 (default: auto). This is a high performance software
|
||||
renderer. By default, it is used when the Direct3D hardware does not
|
||||
support Direct3D 11 feature level 9_3. While the extended OpenGL features
|
||||
will work with WARP, they can be very slow.
|
||||
|
||||
Windows with ANGLE only.
|
||||
|
||||
``--angle-egl-windowing=<yes|no|auto>``
|
||||
Use ANGLE's built in EGL windowing functions to create a swap chain
|
||||
(default: auto). If this is set to ``no`` and the D3D11 renderer is in use,
|
||||
ANGLE's built in swap chain will not be used and a custom swap chain that
|
||||
is optimized for video rendering will be created instead. If set to
|
||||
``auto``, a custom swap chain will be used for D3D11 and the built in swap
|
||||
chain will be used for D3D9. This option is mainly for debugging purposes,
|
||||
in case the custom swap chain has poor performance or does not work.
|
||||
|
||||
If set to ``yes``, the ``--angle-max-frame-latency`` and
|
||||
``--angle-swapchain-length`` options will have no effect.
|
||||
|
||||
Windows with ANGLE only.
|
||||
|
||||
``--angle-max-frame-latency=<1-16>``
|
||||
Sets the maximum number of frames that the system is allowed to queue for
|
||||
rendering with the ANGLE backend (default: 3). Lower values should make
|
||||
VSync timing more accurate, but a value of ``1`` requires powerful
|
||||
hardware, since the CPU will not be able to "render ahead" of the GPU.
|
||||
|
||||
Windows with ANGLE only.
|
||||
|
||||
``--angle-renderer=<d3d9|d3d11|auto>``
|
||||
Forces a specific renderer when using the ANGLE backend (default: auto). In
|
||||
auto mode this will pick D3D11 for systems that support Direct3D 11 feature
|
||||
level 9_3 or higher, and D3D9 otherwise. This option is mainly for
|
||||
debugging purposes. Normally there is no reason to force a specific
|
||||
renderer, though ``--angle-renderer=d3d9`` may give slightly better
|
||||
performance on old hardware. Note that the D3D9 renderer only supports
|
||||
OpenGL ES 2.0, so most extended OpenGL features will not work if this
|
||||
renderer is selected (similar to ``--opengl-dumb-mode``).
|
||||
|
||||
Windows with ANGLE only.
|
||||
|
||||
``--angle-swapchain-length=<2-16>``
|
||||
Sets the number of buffers in the D3D11 presentation queue when using the
|
||||
ANGLE backend (default: 6). At least 2 are required, since one is the back
|
||||
buffer that mpv renders to and the other is the front buffer that is
|
||||
presented by the DWM. Additional buffers can improve performance, because
|
||||
for example, mpv will not have to wait on the DWM to release the front
|
||||
buffer before rendering a new frame to it. For this reason, Microsoft
|
||||
recommends at least 4.
|
||||
|
||||
Windows 8+ with ANGLE only.
|
||||
|
||||
``--opengl-sw``
|
||||
Continue even if a software renderer is detected.
|
||||
|
||||
@@ -4341,6 +4419,9 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
work.
|
||||
x11
|
||||
X11/GLX
|
||||
x11probe
|
||||
For internal autoprobing, equivalent to ``x11`` otherwise. Don't use
|
||||
directly, it could be removed without warning as autoprobing is changed.
|
||||
wayland
|
||||
Wayland/EGL
|
||||
drm
|
||||
@@ -4571,7 +4652,8 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
blend-tiles
|
||||
Blend the frame against a 16x16 gray/white tiles background (default).
|
||||
blend
|
||||
Blend the frame against a black background.
|
||||
Blend the frame against the background color (``--background``, normally
|
||||
black).
|
||||
yes
|
||||
Try to create a framebuffer with alpha component. This only makes sense
|
||||
if the video contains alpha information (which is extremely rare). May
|
||||
@@ -4579,6 +4661,7 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
unavailable, it silently falls back on a normal framebuffer. Note that
|
||||
if you set the ``--opengl-fbo-format`` option to a non-default value, a
|
||||
format with alpha must be specified, or this won't work.
|
||||
This does not work on X11 with EGL and Mesa (freedesktop bug 67676).
|
||||
no
|
||||
Ignore alpha component.
|
||||
|
||||
@@ -4597,10 +4680,27 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
we may have to deal with additional padding, which can be tested with these
|
||||
options). Could be removed any time.
|
||||
|
||||
``--opengl-early-flush=<yes|no>``
|
||||
``--opengl-early-flush=<yes|no|auto>``
|
||||
Call ``glFlush()`` after rendering a frame and before attempting to display
|
||||
it (default: no). Can fix stuttering in some cases, in other cases probably
|
||||
causes it. For testing - could be removed any time.
|
||||
it (default: auto). Can fix stuttering in some cases, in other cases
|
||||
probably causes it. The ``auto`` mode will call ``glFlush()`` only if
|
||||
the renderer is going to wait for a while after rendering, instead of
|
||||
flipping GL front and backbuffers immediately (i.e. it doesn't call it
|
||||
in display-sync mode).
|
||||
|
||||
``--opengl-dumb-mode=<yes|no>``
|
||||
This mode is extremely restricted, and will disable most extended OpenGL
|
||||
features. This includes high quality scalers and custom shaders!
|
||||
|
||||
It is intended for hardware that does not support FBOs (including GLES,
|
||||
which supports it insufficiently), or to get some more performance out of
|
||||
bad or old hardware.
|
||||
|
||||
This mode is forced automatically if needed, and this option is mostly
|
||||
useful for debugging. It's also enabled automatically if nothing uses
|
||||
features which require FBOs.
|
||||
|
||||
This option might be silently removed in the future.
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
@@ -4717,21 +4817,10 @@ Miscellaneous
|
||||
Input file type for ``mf://`` (available: jpeg, png, tga, sgi). By default,
|
||||
this is guessed from the file extension.
|
||||
|
||||
``--stream-capture=<filename>``
|
||||
Allows capturing the primary stream (not additional audio tracks or other
|
||||
kind of streams) into the given file. Capturing can also be started and
|
||||
stopped by changing the filename with the ``stream-capture`` property.
|
||||
Generally this will not produce usable results for anything else than MPEG
|
||||
or raw streams, unless capturing includes the file headers and is not
|
||||
interrupted. Note that, due to cache latencies, captured data may begin and
|
||||
end somewhat delayed compared to what you see displayed.
|
||||
|
||||
The destination file is always appended. (Before mpv 0.8.0, the file was
|
||||
overwritten.)
|
||||
|
||||
``--stream-dump=<filename>``
|
||||
Same as ``--stream-capture``, but do not start playback. Instead, the entire
|
||||
file is dumped.
|
||||
``--stream-dump=<destination-filename>``
|
||||
Instead of playing a file, read its byte stream and write it to the given
|
||||
destination file. The destination is overwritten. Can be useful to test
|
||||
network-related behavior.
|
||||
|
||||
``--stream-lavf-o=opt1=value1,opt2=value2,...``
|
||||
Set AVOptions on streams opened with libavformat. Unknown or misspelled
|
||||
@@ -4774,6 +4863,29 @@ Miscellaneous
|
||||
This does not affect playlist expansion, redirection, or other loading of
|
||||
referenced files like with ordered chapters.
|
||||
|
||||
``--record-file=<file>``
|
||||
Record the current stream to the given target file. The target file will
|
||||
always be overwritten without asking.
|
||||
|
||||
This remuxes the source stream without reencoding, which makes this a
|
||||
highly fragile and experimental feature. It's entirely possible that this
|
||||
writes files which are broken, not standards compliant, not playable with
|
||||
all players (including mpv), or incomplete.
|
||||
|
||||
The target file format is determined by the file extension of the target
|
||||
filename. It is recommended to use the same target container as the source
|
||||
container if possible, and preferring Matroska as fallback.
|
||||
|
||||
Seeking during stream recording, or enabling/disabling stream recording
|
||||
during playback, can cut off data, or produce "holes" in the output file.
|
||||
These are technical restrictions. In particular, video data or subtitles
|
||||
which were read ahead can produce such holes, which might cause playback
|
||||
problems with various players (including mpv).
|
||||
|
||||
The behavior of this option might changed in the future, such as changing
|
||||
it to a template (similar to ``--screenshot-template``), being renamed,
|
||||
removed, or anything else, until it is declared semi-stable.
|
||||
|
||||
``--lavfi-complex=<string>``
|
||||
Set a "complex" libavfilter filter, which means a single filter graph can
|
||||
take input from multiple source audio and video tracks. The graph can result
|
||||
|
||||
+104
-78
@@ -147,95 +147,117 @@ Example::
|
||||
Configurable Options
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``showwindowed``
|
||||
| Default: yes
|
||||
| Enable the OSC when windowed
|
||||
|
||||
``showfullscreen``
|
||||
| Default: yes
|
||||
| Enable the OSC when fullscreen
|
||||
|
||||
``scalewindowed``
|
||||
| Default: 1.5
|
||||
| Scale factor of the OSC when windowed
|
||||
|
||||
``scalefullscreen``
|
||||
| Default: 1.5
|
||||
| Scale factor of the OSC when fullscreen
|
||||
|
||||
``scaleforcedwindow``
|
||||
| Default: 2.0
|
||||
| Scale factor of the OSC when rendered on a forced (dummy) window
|
||||
|
||||
``vidscale``
|
||||
| Default: yes
|
||||
| Scale the OSC with the video
|
||||
| ``no`` tries to keep the OSC size constant as much as the window size allows
|
||||
|
||||
``valign``
|
||||
| Default: 0.8
|
||||
| Vertical alignment, -1 (top) to 1 (bottom)
|
||||
|
||||
``halign``
|
||||
| Default: 0.0
|
||||
| Horizontal alignment, -1 (left) to 1 (right)
|
||||
|
||||
``barmargin``
|
||||
| Default: 0
|
||||
| Margin from bottom (bottombar) or top (topbar), in pixels
|
||||
|
||||
``boxalpha``
|
||||
| Default: 80
|
||||
| Alpha of the background box, 0 (opaque) to 255 (fully transparent)
|
||||
|
||||
``hidetimeout``
|
||||
| Default: 500
|
||||
| Duration in ms until the OSC hides if no mouse movement, must not be
|
||||
negative
|
||||
|
||||
``fadeduration``
|
||||
| Default: 200
|
||||
| Duration of fade out in ms, 0 = no fade
|
||||
|
||||
``deadzonesize``
|
||||
| Default: 1
|
||||
| Size of the deadzone. The deadzone is an area that makes the mouse act
|
||||
like leaving the window. Movement there won't make the OSC show up and
|
||||
it will hide immediately if the mouse enters it. The deadzone starts
|
||||
at the window border opposite to the OSC and the size controls how much
|
||||
of the window it will span. Values between 0 and 1, where 0 means the
|
||||
OSC will always popup with mouse movement in the window, and 1 means the
|
||||
OSC will only show up when the mouse hovers it.
|
||||
|
||||
``minmousemove``
|
||||
| Default: 0
|
||||
| Minimum amount of pixels the mouse has to move between ticks to make
|
||||
the OSC show up
|
||||
|
||||
``layout``
|
||||
| Default: bottombar
|
||||
| The layout for the OSC. Currently available are: box, slimbox,
|
||||
bottombar and topbar.
|
||||
Default: bottombar
|
||||
|
||||
The layout for the OSC. Currently available are: box, slimbox,
|
||||
bottombar and topbar. Default pre-0.21.0 was 'box'.
|
||||
|
||||
``seekbarstyle``
|
||||
| Default: bar
|
||||
| Sets the style of the seekbar, slider (diamond marker) or bar (fill)
|
||||
Default: bar
|
||||
|
||||
Sets the style of the seekbar, slider (diamond marker), knob (circle
|
||||
marker with guide), or bar (fill).
|
||||
Default pre-0.21.0 was 'slider'.
|
||||
|
||||
``deadzonesize``
|
||||
Default: 0.5
|
||||
|
||||
Size of the deadzone. The deadzone is an area that makes the mouse act
|
||||
like leaving the window. Movement there won't make the OSC show up and
|
||||
it will hide immediately if the mouse enters it. The deadzone starts
|
||||
at the window border opposite to the OSC and the size controls how much
|
||||
of the window it will span. Values between 0.0 and 1.0, where 0 means the
|
||||
OSC will always popup with mouse movement in the window, and 1 means the
|
||||
OSC will only show up when the mouse hovers it. Default pre-0.21.0 was 0.
|
||||
|
||||
``minmousemove``
|
||||
Default: 0
|
||||
|
||||
Minimum amount of pixels the mouse has to move between ticks to make
|
||||
the OSC show up. Default pre-0.21.0 was 3.
|
||||
|
||||
``showwindowed``
|
||||
Default: yes
|
||||
|
||||
Enable the OSC when windowed
|
||||
|
||||
``showfullscreen``
|
||||
Default: yes
|
||||
|
||||
Enable the OSC when fullscreen
|
||||
|
||||
``scalewindowed``
|
||||
Default: 1.0
|
||||
|
||||
Scale factor of the OSC when windowed.
|
||||
|
||||
``scalefullscreen``
|
||||
Default: 1.0
|
||||
|
||||
Scale factor of the OSC when fullscreen
|
||||
|
||||
``scaleforcedwindow``
|
||||
Default: 2.0
|
||||
|
||||
Scale factor of the OSC when rendered on a forced (dummy) window
|
||||
|
||||
``vidscale``
|
||||
Default: yes
|
||||
|
||||
Scale the OSC with the video
|
||||
``no`` tries to keep the OSC size constant as much as the window size allows
|
||||
|
||||
``valign``
|
||||
Default: 0.8
|
||||
|
||||
Vertical alignment, -1 (top) to 1 (bottom)
|
||||
|
||||
``halign``
|
||||
Default: 0.0
|
||||
|
||||
Horizontal alignment, -1 (left) to 1 (right)
|
||||
|
||||
``barmargin``
|
||||
Default: 0
|
||||
|
||||
Margin from bottom (bottombar) or top (topbar), in pixels
|
||||
|
||||
``boxalpha``
|
||||
Default: 80
|
||||
|
||||
Alpha of the background box, 0 (opaque) to 255 (fully transparent)
|
||||
|
||||
``hidetimeout``
|
||||
Default: 500
|
||||
|
||||
Duration in ms until the OSC hides if no mouse movement, must not be
|
||||
negative
|
||||
|
||||
``fadeduration``
|
||||
Default: 200
|
||||
|
||||
Duration of fade out in ms, 0 = no fade
|
||||
|
||||
``tooltipborder``
|
||||
| Default: 1
|
||||
| Size of the tooltip outline when using bottombar or topbar layouts
|
||||
Default: 1
|
||||
|
||||
Size of the tooltip outline when using bottombar or topbar layouts
|
||||
|
||||
``timetotal``
|
||||
| Default: no
|
||||
| Show total time instead of time remaining
|
||||
Default: no
|
||||
|
||||
Show total time instead of time remaining
|
||||
|
||||
``timems``
|
||||
| Default: no
|
||||
| Display timecodes with milliseconds
|
||||
Default: no
|
||||
|
||||
Display timecodes with milliseconds
|
||||
|
||||
``visibility``
|
||||
| Default: auto (auto hide/show on mouse move)
|
||||
| Also supports ``never`` and ``always``
|
||||
Default: auto (auto hide/show on mouse move)
|
||||
|
||||
Also supports ``never`` and ``always``
|
||||
|
||||
Script Commands
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -259,3 +281,7 @@ to set auto mode (the default) with ``b``::
|
||||
a script-message osc-visibility never
|
||||
b script-message osc-visibility auto
|
||||
|
||||
``osc-playlist``, ``osc-chapterlist``, ``osc-tracklist``
|
||||
Shows a limited view of the respective type of list using the OSC. First
|
||||
argument is duration in seconds.
|
||||
|
||||
@@ -806,13 +806,6 @@ Available filters are:
|
||||
1-9
|
||||
Apply high quality VDPAU scaling (needs capable hardware).
|
||||
|
||||
``vdpaurb``
|
||||
VDPAU video read back. Works with ``--vo=vdpau`` and ``--vo=opengl`` only.
|
||||
This filter will read back frames decoded by VDPAU so that other filters,
|
||||
which are not normally compatible with VDPAU, can be used like normal.
|
||||
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.
|
||||
|
||||
+26
-9
@@ -10,11 +10,6 @@ syntax is:
|
||||
If the list has a trailing ``,``, mpv will fall back on drivers not contained
|
||||
in the list.
|
||||
|
||||
``--vo-defaults=<driver1[:parameter1:parameter2:...],driver2,...>``
|
||||
Set defaults for each driver.
|
||||
|
||||
Deprecated. No replacement.
|
||||
|
||||
.. note::
|
||||
|
||||
See ``--vo=help`` for a list of compiled-in video output drivers.
|
||||
@@ -159,7 +154,7 @@ Available video output drivers are:
|
||||
means timing is somewhat less accurate than without compositing, but
|
||||
with the composited mode behavior of the NVIDIA driver, there is no
|
||||
hard playback speed limit even without the disabled logic. Enabled by
|
||||
default, use ``no-composite-detect`` to disable.
|
||||
default, use ``--vo-vdpau-composite-detect=no`` to disable.
|
||||
``--vo-vdpau-queuetime-windowed=<number>`` and ``queuetime-fs=<number>``
|
||||
Use VDPAU's presentation queue functionality to queue future video
|
||||
frame changes at most this many milliseconds in advance (default: 50).
|
||||
@@ -387,6 +382,28 @@ Available video output drivers are:
|
||||
|
||||
.. note:: This driver is a joke.
|
||||
|
||||
``tct``
|
||||
Color Unicode art video output driver that works on a text console.
|
||||
Depends on support of true color by modern terminals to display the images
|
||||
at full color range. On Windows it requires an ansi terminal such as mintty.
|
||||
|
||||
``--vo-tct-algo=<algo>``
|
||||
Select how to write the pixels to the terminal.
|
||||
|
||||
half-blocks
|
||||
Uses unicode LOWER HALF BLOCK character to achieve higher vertical
|
||||
resolution. (Default.)
|
||||
plain
|
||||
Uses spaces. Causes vertical resolution to drop twofolds, but in
|
||||
theory works in more places.
|
||||
|
||||
``--vo-tct-width=<width>`` ``--vo-tct-height=<height>``
|
||||
Assume the terminal has the specified character width and/or height.
|
||||
These default to 80x25 if the terminal size cannot be determined.
|
||||
|
||||
``--vo-tct-256=<yes|no>`` (default: no)
|
||||
Use 256 colors - for terminals which don't support true color.
|
||||
|
||||
``image``
|
||||
Output each frame into an image file in the current directory. Each file
|
||||
takes the frame number padded with leading zeros as name.
|
||||
@@ -461,9 +478,9 @@ Available video output drivers are:
|
||||
|
||||
This is deprecated. Use ``--vo=opengl`` instead, which is the default and
|
||||
provides the same functionality. The ``rpi`` VO will be removed in
|
||||
mpv 0.22.0. Its functionality was folded into --vo=opengl, which now uses
|
||||
mpv 0.23.0. Its functionality was folded into --vo=opengl, which now uses
|
||||
RPI hardware decoding by treating it as a hardware overlay (without applying
|
||||
GL filtering). Also to be changed in 0.22.0: the --fs flag will be reset to
|
||||
GL filtering). Also to be changed in 0.23.0: the --fs flag will be reset to
|
||||
"no" by default (like on the other platforms).
|
||||
|
||||
The following deprecated global options are supported by this video output:
|
||||
@@ -491,7 +508,7 @@ Available video output drivers are:
|
||||
Video output driver using Kernel Mode Setting / Direct Rendering Manager.
|
||||
Should be used when one doesn't want to install full-blown graphical
|
||||
environment (e.g. no X). Does not support hardware acceleration (if you
|
||||
need this, check the ``drm-egl`` backend for ``opengl`` VO).
|
||||
need this, check the ``drm`` backend for ``opengl`` VO).
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ behaves. Although there are still many similarities to its ancestors, **mpv**
|
||||
should generally be treated as a completely different program.
|
||||
|
||||
.. admonition:: Warning
|
||||
This document is not updated anymore, and is incomplete and outdated.
|
||||
|
||||
This document is **not updated** anymore, and is **incomplete** and
|
||||
**outdated**. If you look for old option replacements, always check with
|
||||
the current mpv manpage, as the options could have changed meanwhile.
|
||||
|
||||
General Changes from MPlayer to mpv
|
||||
-----------------------------------
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
* [Overview](#overview)
|
||||
* [Downloads](#downloads)
|
||||
* [Changelog](#changelog)
|
||||
* [Compilation](#compilation)
|
||||
* [FFmpeg vs. Libav](#ffmpeg-vs-libav)
|
||||
* [Release cycle](#release-cycle)
|
||||
@@ -14,6 +15,7 @@
|
||||
* [Contributing](#contributing)
|
||||
* [Relation to MPlayer and mplayer2](#relation-to-mplayer-and-mplayer2)
|
||||
* [Wiki](https://github.com/mpv-player/mpv/wiki)
|
||||
* [FAQ](https://github.com/mpv-player/mpv/wiki/FAQ)
|
||||
* [Man pages](http://mpv.io/manual/master/)
|
||||
* [Contact](#contact)
|
||||
* [License](#license)
|
||||
@@ -47,6 +49,20 @@ Releases can be found on the [release list][releases].
|
||||
For semi-official builds and third-party packages please see
|
||||
[mpv.io](http://mpv.io/installation/).
|
||||
|
||||
## Changelog
|
||||
|
||||
|
||||
There is no completely changelog, however changes to the player core interface
|
||||
are listed in the [interface changelog][interface-changes].
|
||||
|
||||
Changes to the C API are documented in the [client API changelog][api-changes].
|
||||
|
||||
The [release list][releases] has a summary of most of the important changes
|
||||
on every release.
|
||||
|
||||
Changes to the default key bindings are inidcated in
|
||||
[restore-old-bindings.conf][restore-old-bindings].
|
||||
|
||||
## Compilation
|
||||
|
||||
|
||||
@@ -79,7 +95,7 @@ Essential dependencies (incomplete list):
|
||||
- Audio output development headers (libasound/ALSA, pulseaudio)
|
||||
- FFmpeg libraries (libavutil libavcodec libavformat libswscale libavfilter
|
||||
and either libswresample or libavresample)
|
||||
At least FFmpeg 2.4.0 or Libav 11 is required.
|
||||
At least FFmpeg 3.2.2 or Libav 12 is required.
|
||||
- zlib
|
||||
- iconv (normally provided by the system libc)
|
||||
- libass (OSD, OSC, text subtitles)
|
||||
@@ -197,7 +213,7 @@ In general, mpv should be considered a completely new program, rather than a
|
||||
MPlayer drop-in replacement.
|
||||
|
||||
If you are wondering what's different from mplayer2 and MPlayer, an incomplete
|
||||
list of changes is located [here][mplayer-changes].
|
||||
and now unmaintained list of changes is located [here][mplayer-changes].
|
||||
|
||||
## Contact
|
||||
|
||||
@@ -220,6 +236,9 @@ only if discretion is required.
|
||||
[release-policy]: https://github.com/mpv-player/mpv/blob/master/DOCS/release-policy.md
|
||||
[windows_compilation]: https://github.com/mpv-player/mpv/blob/master/DOCS/compile-windows.md
|
||||
[mplayer-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/mplayer-changes.rst
|
||||
[interface-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/interface-changes.rst
|
||||
[api-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/client-api-changes.rst
|
||||
[restore-old-bindings]: https://github.com/mpv-player/mpv/blob/master/etc/restore-old-bindings.conf
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+290
@@ -1,3 +1,293 @@
|
||||
Release 0.24.0
|
||||
==============
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Windows: allow snapping to screen edges (#2248)
|
||||
- macOS: add border cycling (#2430)
|
||||
- player: add experimental C plugin interface
|
||||
- player: add experimental stream recording feature (--record-file)
|
||||
- player: add prefetching of the next playlist entry (--prefetch-playlist; does NOT work with URLs resolved by youtube-dl)
|
||||
- stream_lavf: add support for data URIs (#4058)
|
||||
- vaapi: add support for 10-bit video formats (requires patched Mesa and capable hardware)
|
||||
- x11: pseudo HiDPI scaling
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- sub: add --sub-justify and --sub-ass-justify options
|
||||
- sub: add option to force using video resolution for image subtitles (--image-subs-video-resolution)
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- command: rename framedrop properties
|
||||
- drop-frame-count → decoder-frame-drop-count
|
||||
- vo-drop-frame-count → frame-drop-count
|
||||
- input.conf: change "L" to change loop-file by default
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- --hwdec-preload (replaced with --opengl-hwdec-interop)
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- options: drop deprecated --sub-codepage syntax
|
||||
- options: drop deprecated --vd/--ad codecs selection features
|
||||
- player: remove --stream-capture option/property
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- Windows: update the fullscreen state on restoring
|
||||
- ad_spdif: fix obscure cases of AC3 passthrough (e.g. 44100 Hz AC3)
|
||||
- ao_oss: use --audio-device if --oss-device isn't set (#4122)
|
||||
- build: fix --disable-gl if cuda is enabled
|
||||
- build: rpi: rely on pkgconfig for compiler flags to ease cross compilation
|
||||
- charset_conv: fallback to interpreting subs as latin1 if iconv fails
|
||||
- charset_conv: fix "auto" fallback with uchardet not compiled (#3954)
|
||||
- client API: fix freeze when destroying mpv_handle before mpv_initialize
|
||||
- client API: fix mpv_set_property() return value before init (#3988)
|
||||
- command: fix potential crash for script-binding with multi-commands
|
||||
- command: nicer OSD formatting for loop-file
|
||||
- command: shorten long playlists on OSD
|
||||
- config: do not resolve default profile during "include" processing (#4024)
|
||||
- cuda: fix 10 bit decoding
|
||||
- cue: accept lower-case cue commands (#4057)
|
||||
- demux_mkv: any reference makes a frame not a keyframe (fixes seeking in WebM files with alpha channel)
|
||||
- lavfi: slightly better disconnected output handling (#4118)
|
||||
- lua: close directory after reading its entries (#4045)
|
||||
- macOS: consistent normalization when searching for external files (#4016)
|
||||
- macOS: don't change Space on quit in fullscreen (#3957)
|
||||
- macOS: don't constrain window frame for fullscreen (#4044)
|
||||
- macOS: don't init displaylink on reconfig (#4031)
|
||||
- macOS: fix build on OS X 10.9 (#3946)
|
||||
- macOS: fix color profile retrieval
|
||||
- macOS: fix displaylink refresh rate retrieval
|
||||
- macOS: fix dropping of files and URLs (#4036)
|
||||
- macOS: fix handling of geometry option (#3867)
|
||||
- macOS: fix unwanted behavior with window level other than the default (#1757, #1884)
|
||||
- macOS: fix window size in certain circumstances
|
||||
- macOS: properly recover from toggleFullscreen fail (#4035)
|
||||
- osc: allow playlist buttons when looping (#4092)
|
||||
- osc: bottom/topbar: clip title instead of stretching
|
||||
- osc: bottombar/topbar: make chapter markers slightly bigger
|
||||
- osc: box: clip with ellipsis after too much stretching
|
||||
- osc: fix crashes related to field eventresponder being nil (#3210)
|
||||
- player: add .scc subtitle extension
|
||||
- stream_bluray: use proper 0-based index
|
||||
- sub: remove .txt as text subtitle extension
|
||||
- tv: fix segfaults on TV input (#4096)
|
||||
- vaapi: set libva > 0.39.4 message callbacks to prevent it from printing messages to the terminal
|
||||
- vd_lavc: improved fallback behavior for --hwdec=cuda (#3914)
|
||||
- vdpau: reject decoding of non-4:2:0 (would decode to garbage)
|
||||
- vf_lavfi: don't crash with VOs without hardware decoding support (#4064)
|
||||
- vo_opengl, vo_opengl_cb: better hwdec interop backend selection
|
||||
- vo_opengl: angle: rewrite with custom swap chain (fixes problems with e.g. 24 fps video on 24 Hz monitors, avoids problems with DirectComposition, enables "screenshot window" command on Windows 8 and newer)
|
||||
- vo_opengl: partially fix rotation for 4:2:2 content
|
||||
- vo_opengl: x11egl: fix alpha mode
|
||||
- wayland: fix high CPU usage with mpv paused and visible
|
||||
- ytdl_hook, edl: implement pseudo-DASH support
|
||||
- ytdl_hook: add non-dash fallbacks to default formats
|
||||
- ytdl_hook: fix opening hitbox.tv rtmp stream
|
||||
- ytdl_hook: respect --no-audio, don't force-select track
|
||||
- ytdl_hook: support livestream segmented DASH VODs
|
||||
- ytdl_hook: support segmented dash
|
||||
|
||||
|
||||
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.23.0..v0.24.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.23.0...v0.24.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.23.0..v0.24.0
|
||||
|
||||
|
||||
Release 0.23.0
|
||||
==============
|
||||
|
||||
Now requires at least FFmpeg 3.2.2.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- vo_rpi: partially undeprecate
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- vo_opengl: hwdec_cuda: Support P016 output surfaces
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- charset_conv: drop enca and libguess support in favor of uchardet
|
||||
- vf_vdpaurb: remove this filter in favor of --hwdec=vdpau-copy
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- TOOLS/autoload: allow disabling through script-opts
|
||||
- demux, stream: add --access-references to prevent opening referenced files
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- options: deprecate codec family selection in --vd/--ad
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- macOS: remove --fs-black-out-screens
|
||||
- options: remove deprecated sub-option handling for --vo and --ao
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- Windows: window styles improvements (allow minimizing borderless/fullscreen window) (#2229, #2451)
|
||||
- ad_spdif: Fix crash when spdif muxer is not available
|
||||
- audio: fix --audio-stream-silence with ao_alsa
|
||||
- audio: fix --audio-stream-silence with ao_wasapi
|
||||
- build: drop build-time dependency on Perl
|
||||
- build: support linking ANGLE (previously loaded dynamically)
|
||||
- d3d11va: unconditionally load D3D DLLs (#3348)
|
||||
- demux_mkv: fix seeking in some broken files (#3920)
|
||||
- hwdec_cuda: allow building without CUDA SDK (load CUDA dynamically)
|
||||
- macOS: fix dropping of URLs containing query strings on the window
|
||||
- macOS: fullscreen refactoring (#2857, #3272, #1352, #2062, #3864)
|
||||
- macOS: support append file to paylist on drop (#2166)
|
||||
- macOS: update the menu and remove conflicting “Quit & remember position” item (#3865)
|
||||
- osc: don't hide playlist buttons, just disable
|
||||
- osc: fix possible race condition in right timecode
|
||||
- osc: topbar: use same styles as bottombar
|
||||
- player: don't print format detection error when aborting loading
|
||||
- vdpau: fix vaapi probing if libvdpau-va-gl1 is present
|
||||
- video: use demuxer-signaled duration for last video frame (#3924)
|
||||
|
||||
|
||||
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.22.0..v0.23.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.22.0...v0.23.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.22.0..v0.23.0
|
||||
|
||||
|
||||
Release 0.22.0
|
||||
==============
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- audio/out: add AudioUnit output driver for iOS
|
||||
- demux_mkv: parse Matroska colorimetry metadata
|
||||
- filter_kernels: add ability to taper kernels/windows, add tukey window
|
||||
- osc: add seekbarstyle=knob (#2365)
|
||||
- video/out: add tct as modern caca alternative for true-color and 256-color terminals
|
||||
- video: add --hwdec=vdpau-copy mode
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- vf_vdpaurb: deprecated in favor of --hwdec=vdpau-copy
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- --opengl-early-flush=auto
|
||||
- --scale-taper, --scale-wtaper
|
||||
- --scale-wblur
|
||||
- macOS: --hidpi-window-scale (#3716)
|
||||
- osc: add script message handlers for chapter/track/playlists
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- Apply --autofit-larger after --autofit-smaller (#3753)
|
||||
- Set subtitle track title to indicate hearing/visual impaired tracks
|
||||
- ao_alsa: disable chmap API use for mono/stereo (#2905, #3045)
|
||||
- build: add required failure message for libavfilter check (#3692)
|
||||
- build: fix compilation with mingw-w64/Clang (#3800)
|
||||
- build: make VideoToolbox available on iOS
|
||||
- command: fix reset-on-next-file=all and tv-freq option (#3708)
|
||||
- command: if window-scale can't be set properly, set it as option (#3724)
|
||||
- demux_mkv: don't recursively resolve timeline for opened reference files
|
||||
- demux_mkv: fix ordered chapter sources with ordered editions
|
||||
- opengl: compile against iOS OpenGLES implementation
|
||||
- options: handle legacy no-* sub-options
|
||||
- osc: add alpha animation to tooltip (fix lingering tooltip)
|
||||
- osc: change default deadzonesize to 0.5
|
||||
- osc: don't wrap the title
|
||||
- osc: fix crash after reaching a certain position in limited lists (#3691)
|
||||
- osc: fix crash with no chapters
|
||||
- osc: fix crashes when dragging seekbar across file changes (#3210)
|
||||
- osc: fix displaying only half of the entries when at the end of a list
|
||||
- osc: fix missing chapter ticks with seekbarstyle=bar
|
||||
- osc: slimbox: fix clipping with seekbarstyle=bar (#3737)
|
||||
- osc: top/bottombar: also scale when min-width is reached to match box/slimbox behavior
|
||||
- osc: top/bottombar: dynamically size timecodes according to timems
|
||||
- osc: top/bottombar: rescale layout to same size with scale=1
|
||||
- osc: top/bottombar: scale title if too large like box
|
||||
- player: consistently initialize screensaver state with --force-window
|
||||
- player: enable no-video subtitle display on coverart too
|
||||
- player: make --start-time work with --rebase-start-time=no
|
||||
- player: make sure non-video subtitle rendering is reset if video resumes (#3770)
|
||||
- player: removing last playlist entry while looping should not stop (#3808)
|
||||
- player: show subtitles on VO if --force-window is used (#3684)
|
||||
- player: speed up audio/video re-sync when there is a huge delay
|
||||
- vdpau: fix hwdec uninit (#3788)
|
||||
- vo_opengl: blend against background color for --alpha=blend
|
||||
- vo_opengl: context_rpi: fix stdatomic usage (#3699)
|
||||
- vo_opengl: fix --blend-subtitles handling (#3773)
|
||||
- vo_opengl: fix redrawing with hardware decoding (#3773)
|
||||
- vo_opengl: partially re-enable glFlush() calls (#3670)
|
||||
- ytdl_hook: sort chapters by time
|
||||
|
||||
|
||||
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.21.0..v0.22.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.21.0...v0.22.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.21.0..v0.22.0
|
||||
|
||||
|
||||
Release 0.21.0
|
||||
==============
|
||||
|
||||
|
||||
Whitespace-only changes.
@@ -29,7 +29,7 @@ pacman -S --noconfirm --needed \
|
||||
$MINGW_PACKAGE_PREFIX-libjpeg-turbo \
|
||||
$MINGW_PACKAGE_PREFIX-lua51 \
|
||||
$MINGW_PACKAGE_PREFIX-rubberband \
|
||||
$MINGW_PACKAGE_PREFIX-uchardet-git
|
||||
$MINGW_PACKAGE_PREFIX-uchardet
|
||||
|
||||
# Delete unused packages to reduce space used in the Appveyor cache
|
||||
pacman -Sc --noconfirm
|
||||
@@ -1,24 +0,0 @@
|
||||
#! /usr/bin/env perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# Convert the contents of a file into a C string constant.
|
||||
# Note that the compiler will implicitly add an extra 0 byte at the end
|
||||
# of every string, so code using the string may need to remove that to get
|
||||
# the exact contents of the original file.
|
||||
# FIXME: why not a char array?
|
||||
|
||||
# treat only alphanumeric and punctuations (excluding " and ?) as safe
|
||||
my $unsafe_chars = qr{[^][A-Za-z0-9!#%&'()*+,./:;<=>^_{|}~ -]};
|
||||
|
||||
for my $file (@ARGV) {
|
||||
open my $fh, '<:raw', $file or next;
|
||||
print "/* Generated from $file */\n";
|
||||
while (<$fh>) {
|
||||
# replace unsafe chars with their equivalent octal escapes
|
||||
s/($unsafe_chars)/\\@{[sprintf '%03o', ord($1)]}/gos;
|
||||
print "\"$_\"\n"
|
||||
}
|
||||
close $fh;
|
||||
}
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Convert the contents of a file into a C string constant.
|
||||
# Note that the compiler will implicitly add an extra 0 byte at the end
|
||||
# of every string, so code using the string may need to remove that to get
|
||||
# the exact contents of the original file.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import sys
|
||||
|
||||
# Indexing a byte string yields int on Python 3.x, and a str on Python 2.x
|
||||
def pord(c):
|
||||
return ord(c) if type(c) == str else c
|
||||
|
||||
def file2string(infilename, infile, outfile):
|
||||
outfile.write("// Generated from %s\n\n" % infilename)
|
||||
|
||||
conv = ['\\' + ("%03o" % c) for c in range(256)]
|
||||
safe_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" \
|
||||
"0123456789!#%&'()*+,-./:;<=>?[]^_{|}~ "
|
||||
|
||||
for c in safe_chars:
|
||||
conv[ord(c)] = c
|
||||
for c, esc in ("\nn", "\tt", r"\\", '""'):
|
||||
conv[ord(c)] = '\\' + esc
|
||||
for line in infile:
|
||||
outfile.write('"' + ''.join(conv[pord(c)] for c in line) + '"\n')
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open(sys.argv[1], 'rb') as infile:
|
||||
file2string(sys.argv[1], infile, sys.stdout)
|
||||
@@ -1,30 +0,0 @@
|
||||
use 5.008;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# ABSTRACT: Module collection to parse Matroska files.
|
||||
package Parse::Matroska;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<use>s L<Parse::Matroska::Reader>. See the documentation
|
||||
of the modules mentioned in L</"SEE ALSO"> for more information
|
||||
in how to use this module.
|
||||
|
||||
It's intended for this module to contain high-level interfaces
|
||||
to the other modules in the distribution.
|
||||
|
||||
=head1 SOURCE CODE
|
||||
|
||||
L<https://github.com/Kovensky/Parse-Matroska>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Parse::Matroska::Reader>, L<Parse::Matroska::Element>,
|
||||
L<Parse::Matroska::Definitions>.
|
||||
|
||||
=cut
|
||||
|
||||
use Parse::Matroska::Reader;
|
||||
|
||||
1;
|
||||
@@ -1,357 +0,0 @@
|
||||
use 5.008;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# ABSTRACT: internal EBML grammar definitions
|
||||
package Parse::Matroska::Definitions;
|
||||
|
||||
use Parse::Matroska::Utils qw{uniq uncamelize};
|
||||
|
||||
use Exporter;
|
||||
our @ISA = qw{Exporter};
|
||||
our @EXPORT_OK = qw{elem_by_hexid %EBML_DEFINITION %MATROSKA_DEFINITION};
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Parse::Matroska::Definitions qw{elem_by_hexid};
|
||||
my $ebml_id = elem_by_hexid('1a45dfa3');
|
||||
print "EBML ID $ebml_id->{elid}'s name: $ebml_id->{name}";
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Contains the definition of the EBML grammar as expected in
|
||||
Matroska files. This module is meant mostly for internal use.
|
||||
|
||||
As this was extended from a script in mpv-player, some data
|
||||
generated is apparently useless for regular module users
|
||||
but is still relevant to the mpv-player script. Such data
|
||||
is annotated as being for mpv compatibility.
|
||||
|
||||
=head1 NOTE
|
||||
|
||||
The API of this module is not yet considered stable.
|
||||
|
||||
=head1 GLOBALS
|
||||
|
||||
These global variables are considered B<immutable>.
|
||||
|
||||
=head2 @Parse::Matroska::Definitions::global_elem_list
|
||||
|
||||
A global list of known matroska elements. Useful for
|
||||
mpv's matroska script, used for generating C headers
|
||||
that parse matroska.
|
||||
|
||||
=head2 %Parse::Matroska::Definitions::global_elem_dict
|
||||
|
||||
A global hash of known matroska elements. Used internally
|
||||
by L</elem_by_hexid($id)>.
|
||||
|
||||
=cut
|
||||
|
||||
@Parse::Matroska::Definitions::global_elem_list = ();
|
||||
%Parse::Matroska::Definitions::global_elem_dict = ();
|
||||
|
||||
=head2 %EBML_DEFINITION
|
||||
|
||||
Optionally-importable hash of known EBML IDs belonging
|
||||
to the EBML generic grammar.
|
||||
|
||||
=head2 %MATROSKA_DEFINITION
|
||||
|
||||
Optionally-importable hash of known EBML IDs belonging
|
||||
to the Matroska-specific grammar.
|
||||
|
||||
=cut
|
||||
|
||||
our %EBML_DEFINITION = define_ebml();
|
||||
our %MATROSKA_DEFINITION = define_matroska();
|
||||
|
||||
=method elem_by_hexid($id)
|
||||
|
||||
Returns an EBML Element Definition corresponding to the provided
|
||||
hexadecimal string. Returns C<undef> if the element is unknown.
|
||||
|
||||
=cut
|
||||
sub elem_by_hexid {
|
||||
my ($elid) = @_;
|
||||
return $Parse::Matroska::Definitions::global_elem_dict{$elid};
|
||||
}
|
||||
|
||||
################################################
|
||||
### Helper functions for document definition ###
|
||||
################################################
|
||||
|
||||
# used by elem when setting the 'valname' key
|
||||
use constant TYPE_MAP => {
|
||||
uint => 'uint64_t',
|
||||
str => 'char *',
|
||||
binary => 'struct bstr',
|
||||
ebml_id => 'uint32_t',
|
||||
float => 'double',
|
||||
sint => 'int64_t',
|
||||
};
|
||||
|
||||
# this will be localized to "MATROSKA" or "EBML" on the elem declarations
|
||||
our $ELEM_DEFINE_TYPE = undef;
|
||||
|
||||
=method elem($name,$elid,$valtype)
|
||||
|
||||
NOTE: never call this function yourself; it changes data structures
|
||||
that are considered immutable outside of this package.
|
||||
|
||||
Internal API function that generates the EBML Element Definitions.
|
||||
|
||||
This API function returns an array which first element is C<$elid>
|
||||
and the second is a generated hash. The generated hash is stored
|
||||
in the @global_elem_list and %global_elem_dict.
|
||||
|
||||
The generated hash contains:
|
||||
|
||||
=for :list
|
||||
= name
|
||||
The EBML Element's name, given through C<$name>.
|
||||
= elid
|
||||
The EBML Element's hex id, given through C<$elid>. Used for lookups by L</elem_by_hexid($id)>.
|
||||
= valtype
|
||||
The EBML Element's type, given through C<$valtype>, except when C<$valtype> is an arrayref.
|
||||
= multiple
|
||||
If C<$name> ends with a C<*>, this is set as true and strips the C<*> from L</name>. Used to
|
||||
mark elements that may be repeated.
|
||||
= subelements
|
||||
An arrayref of elements that may be children of this element, given through C<$valtype> if it
|
||||
is an arrayref. Sets L</valtype> to C<sub> if there are subelements.
|
||||
= subids
|
||||
An arrayref listing all the L</elid>s of subelements, C<uniq>ified.
|
||||
|
||||
The following elements are for mpv compatibility:
|
||||
|
||||
=for :list
|
||||
= definename
|
||||
Name used for generating C #defines.
|
||||
= fieldname
|
||||
Name used for generating C struct fields.
|
||||
= structname
|
||||
Name used for generating C struct names.
|
||||
= ebmltype
|
||||
A pre-#defined constant to describe the element's type.
|
||||
= valname
|
||||
Typename used when declaring a struct field referring to this element.
|
||||
|
||||
=cut
|
||||
sub elem {
|
||||
my %e = (name => shift, elid => shift, valtype => shift);
|
||||
|
||||
# strip * from name, set 'multiple' if there was one
|
||||
$e{multiple} = scalar $e{name} =~ s/\*$//;
|
||||
|
||||
# ELEM_DEFINE_TYPE is either MATROSKA or EBML
|
||||
$e{definename} = "${ELEM_DEFINE_TYPE}_ID_".uc($e{name});
|
||||
$e{fieldname} = uncamelize $e{name};
|
||||
$e{structname} = "ebml_$e{fieldname}";
|
||||
|
||||
if (ref $e{valtype} eq 'HASH') {
|
||||
$e{subelements} = $e{valtype};
|
||||
$e{subids} = uniq map { $_->{elid} } values %{$e{subelements}};
|
||||
$e{valtype} = 'sub';
|
||||
$e{ebmltype} = 'EBML_TYPE_SUBELEMENTS';
|
||||
$e{valname} = "struct $e{structname}";
|
||||
} else {
|
||||
$e{ebmltype} = "EBML_TYPE_\U$e{valtype}";
|
||||
die "Unrecognized value type $e{valtype}" unless
|
||||
defined ($e{valname} = TYPE_MAP->{$e{valtype}});
|
||||
}
|
||||
my $e = \%e;
|
||||
push @Parse::Matroska::Definitions::global_elem_list, $e;
|
||||
$Parse::Matroska::Definitions::global_elem_dict{$e{elid}} = $e;
|
||||
return ($e{elid}, $e);
|
||||
}
|
||||
|
||||
#############################################
|
||||
### EBML and Matroska document definitons ###
|
||||
#############################################
|
||||
|
||||
=method define_ebml
|
||||
|
||||
Internal function that defines the EBML generic grammar.
|
||||
|
||||
Must not be called from outside the package.
|
||||
|
||||
=cut
|
||||
sub define_ebml {
|
||||
local $ELEM_DEFINE_TYPE = 'EBML';
|
||||
return (
|
||||
elem('EBML', '1a45dfa3', {
|
||||
elem('EBMLVersion', '4286', 'uint'),
|
||||
elem('EBMLReadVersion', '42f7', 'uint'),
|
||||
elem('EBMLMaxIDLength', '42f2', 'uint'),
|
||||
elem('EBMLMaxSizeLength', '42f3', 'uint'),
|
||||
elem('DocType', '4282', 'str'),
|
||||
elem('DocTypeVersion', '4287', 'uint'),
|
||||
elem('DocTypeReadVersion', '4285', 'uint'),
|
||||
}),
|
||||
|
||||
elem('CRC32', 'bf', 'binary'),
|
||||
elem('Void', 'ec', 'binary'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
=method define_matroska
|
||||
|
||||
Internal function that defines the Matroska-specific EBML grammar.
|
||||
|
||||
Must not be called from outside the package.
|
||||
|
||||
=cut
|
||||
sub define_matroska {
|
||||
local $ELEM_DEFINE_TYPE = 'MATROSKA';
|
||||
return (
|
||||
elem('Segment', '18538067', {
|
||||
elem('SeekHead*', '114d9b74', {
|
||||
elem('Seek*', '4dbb', {
|
||||
elem('SeekID', '53ab', 'ebml_id'),
|
||||
elem('SeekPosition', '53ac', 'uint'),
|
||||
}),
|
||||
}),
|
||||
|
||||
elem('Info*', '1549a966', {
|
||||
elem('SegmentUID', '73a4', 'binary'),
|
||||
elem('PrevUID', '3cb923', 'binary'),
|
||||
elem('NextUID', '3eb923', 'binary'),
|
||||
elem('TimecodeScale', '2ad7b1', 'uint'),
|
||||
elem('DateUTC', '4461', 'sint'),
|
||||
elem('Title', '7ba9', 'str'),
|
||||
elem('MuxingApp', '4d80', 'str'),
|
||||
elem('WritingApp', '5741', 'str'),
|
||||
elem('Duration', '4489', 'float'),
|
||||
}),
|
||||
|
||||
elem('Cluster*', '1f43b675', {
|
||||
elem('Timecode', 'e7', 'uint'),
|
||||
elem('BlockGroup*', 'a0', {
|
||||
elem('Block', 'a1', 'binary'),
|
||||
elem('BlockDuration', '9b', 'uint'),
|
||||
elem('ReferenceBlock*', 'fb', 'sint'),
|
||||
elem('DiscardPadding', '75A2', 'sint'),
|
||||
}),
|
||||
elem('SimpleBlock*', 'a3', 'binary'),
|
||||
}),
|
||||
|
||||
elem('Tracks*', '1654ae6b', {
|
||||
elem('TrackEntry*', 'ae', {
|
||||
elem('TrackNumber', 'd7', 'uint'),
|
||||
elem('TrackUID', '73c5', 'uint'),
|
||||
elem('TrackType', '83', 'uint'),
|
||||
elem('FlagEnabled', 'b9', 'uint'),
|
||||
elem('FlagDefault', '88', 'uint'),
|
||||
elem('FlagForced', '55aa', 'uint'),
|
||||
elem('FlagLacing', '9c', 'uint'),
|
||||
elem('MinCache', '6de7', 'uint'),
|
||||
elem('MaxCache', '6df8', 'uint'),
|
||||
elem('DefaultDuration', '23e383', 'uint'),
|
||||
elem('TrackTimecodeScale', '23314f', 'float'),
|
||||
elem('MaxBlockAdditionID', '55ee', 'uint'),
|
||||
elem('Name', '536e', 'str'),
|
||||
elem('Language', '22b59c', 'str'),
|
||||
elem('CodecID', '86', 'str'),
|
||||
elem('CodecPrivate', '63a2', 'binary'),
|
||||
elem('CodecName', '258688', 'str'),
|
||||
elem('CodecDecodeAll', 'aa', 'uint'),
|
||||
elem('CodecDelay', '56AA', 'uint'),
|
||||
elem('SeekPreRoll', '56BB', 'uint'),
|
||||
elem('Video', 'e0', {
|
||||
elem('FlagInterlaced', '9a', 'uint'),
|
||||
elem('PixelWidth', 'b0', 'uint'),
|
||||
elem('PixelHeight', 'ba', 'uint'),
|
||||
elem('DisplayWidth', '54b0', 'uint'),
|
||||
elem('DisplayHeight', '54ba', 'uint'),
|
||||
elem('DisplayUnit', '54b2', 'uint'),
|
||||
elem('FrameRate', '2383e3', 'float'),
|
||||
elem('ColourSpace', '2eb524', 'binary'),
|
||||
elem('StereoMode', '53b8', 'uint'),
|
||||
}),
|
||||
elem('Audio', 'e1', {
|
||||
elem('SamplingFrequency', 'b5', 'float'),
|
||||
elem('OutputSamplingFrequency', '78b5', 'float'),
|
||||
elem('Channels', '9f', 'uint'),
|
||||
elem('BitDepth', '6264', 'uint'),
|
||||
}),
|
||||
elem('ContentEncodings', '6d80', {
|
||||
elem('ContentEncoding*', '6240', {
|
||||
elem('ContentEncodingOrder', '5031', 'uint'),
|
||||
elem('ContentEncodingScope', '5032', 'uint'),
|
||||
elem('ContentEncodingType', '5033', 'uint'),
|
||||
elem('ContentCompression', '5034', {
|
||||
elem('ContentCompAlgo', '4254', 'uint'),
|
||||
elem('ContentCompSettings', '4255', 'binary'),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
elem('Cues', '1c53bb6b', {
|
||||
elem('CuePoint*', 'bb', {
|
||||
elem('CueTime', 'b3', 'uint'),
|
||||
elem('CueTrackPositions*', 'b7', {
|
||||
elem('CueTrack', 'f7', 'uint'),
|
||||
elem('CueClusterPosition', 'f1', 'uint'),
|
||||
elem('CueRelativePosition','f0', 'uint'),
|
||||
elem('CueDuration', 'b2', 'uint'),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
elem('Attachments', '1941a469', {
|
||||
elem('AttachedFile*', '61a7', {
|
||||
elem('FileDescription', '467e', 'str'),
|
||||
elem('FileName', '466e', 'str'),
|
||||
elem('FileMimeType', '4660', 'str'),
|
||||
elem('FileData', '465c', 'binary'),
|
||||
elem('FileUID', '46ae', 'uint'),
|
||||
}),
|
||||
}),
|
||||
|
||||
elem('Chapters', '1043a770', {
|
||||
elem('EditionEntry*', '45b9', {
|
||||
elem('EditionUID', '45bc', 'uint'),
|
||||
elem('EditionFlagHidden', '45bd', 'uint'),
|
||||
elem('EditionFlagDefault', '45db', 'uint'),
|
||||
elem('EditionFlagOrdered', '45dd', 'uint'),
|
||||
elem('ChapterAtom*', 'b6', {
|
||||
elem('ChapterUID', '73c4', 'uint'),
|
||||
elem('ChapterTimeStart', '91', 'uint'),
|
||||
elem('ChapterTimeEnd', '92', 'uint'),
|
||||
elem('ChapterFlagHidden', '98', 'uint'),
|
||||
elem('ChapterFlagEnabled', '4598', 'uint'),
|
||||
elem('ChapterSegmentUID', '6e67', 'binary'),
|
||||
elem('ChapterSegmentEditionUID', '6ebc', 'uint'),
|
||||
elem('ChapterDisplay*', '80', {
|
||||
elem('ChapString', '85', 'str'),
|
||||
elem('ChapLanguage*', '437c', 'str'),
|
||||
elem('ChapCountry*', '437e', 'str'),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
elem('Tags*', '1254c367', {
|
||||
elem('Tag*', '7373', {
|
||||
elem('Targets', '63c0', {
|
||||
elem('TargetTypeValue', '68ca', 'uint'),
|
||||
elem('TargetTrackUID', '63c5', 'uint'),
|
||||
elem('TargetEditionUID', '63c9', 'uint'),
|
||||
elem('TargetChapterUID', '63c4', 'uint'),
|
||||
elem('TargetAttachmentUID', '63c6', 'uint'),
|
||||
}),
|
||||
elem('SimpleTag*', '67c8', {
|
||||
elem('TagName', '45a3', 'str'),
|
||||
elem('TagLanguage', '447a', 'str'),
|
||||
elem('TagString', '4487', 'str'),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,331 +0,0 @@
|
||||
use 5.008;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# ABSTRACT: a mid-level representation of an EBML element
|
||||
package Parse::Matroska::Element;
|
||||
|
||||
use Carp;
|
||||
use List::Util qw{first};
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Parse::Matroska::Reader;
|
||||
my $reader = Parse::Matroska::Reader->new($path);
|
||||
my $elem = $reader->read_element;
|
||||
|
||||
print "ID: $elem->{elid}\n";
|
||||
print "Name: $elem->{name}\n";
|
||||
print "Length: $elem->{content_len}\n";
|
||||
print "Type: $elem->{type}\n";
|
||||
print "Child count: ", scalar(@{$elem->all_children}), "\n";
|
||||
if ($elem->{type} eq 'sub') {
|
||||
while (my $chld = $elem->next_child) {
|
||||
print "Child Name: $chld->{name}\n";
|
||||
}
|
||||
} else {
|
||||
print "Value: ", $elem->get_value, "\n";
|
||||
}
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Represents a single Matroska element as decoded by
|
||||
L<Parse::Matroska::Reader>. This is essentially a hash
|
||||
augmented with functions for delay-loading of binary
|
||||
values and children elements.
|
||||
|
||||
=head1 NOTE
|
||||
|
||||
The API of this module is not yet considered stable.
|
||||
|
||||
=attr elid
|
||||
|
||||
The EBML Element ID, suitable for passing to
|
||||
L<Parse::Matroska::Definitions/elem_by_hexid>.
|
||||
|
||||
=attr name
|
||||
|
||||
The EBML Element's name.
|
||||
|
||||
=attr type
|
||||
|
||||
The EBML Element's type. Can be C<uint>, C<sint>,
|
||||
C<float>, C<ebml_id>, C<str> or C<binary>. See L</value>
|
||||
for details.
|
||||
|
||||
Equivalent to
|
||||
C<elem_by_hexid($elem-E<gt>{value})-E<gt>{valtype}>.
|
||||
|
||||
=attr value
|
||||
|
||||
The EBML Element's value. Should be obtained through
|
||||
L</get_value>.
|
||||
|
||||
Is an unicode string if the L</type> is C<str>, that is,
|
||||
the string has already been decoded by L<Encode/decode>.
|
||||
|
||||
Is C<undef> if the L</type> is C<binary> and the contents
|
||||
were delay-loaded and not yet read. L</get_value> will
|
||||
do the delayed load if needed.
|
||||
|
||||
Is an arrayref if the L</type> is C<sub>, containing
|
||||
the children nodes that were already loaded.
|
||||
|
||||
Is a hashref if the L</type> is C<ebml_id>, containing
|
||||
the referred element's information as defined in
|
||||
L<Parse::Matroska::Definitions>. Calling
|
||||
C<elem_by_hexid($elem-E<gt>{value}-E<gt>{elid})> will
|
||||
return the same object as $elem->{value}.
|
||||
|
||||
=attr full_len
|
||||
|
||||
The entire length of this EBML Element, including
|
||||
the header's.
|
||||
|
||||
=attr size_len
|
||||
|
||||
The length of the size marker. Used when calculating
|
||||
L</full_len> from L</content_len>
|
||||
|
||||
=attr content_len
|
||||
|
||||
The length of the contents of this EBML Element,
|
||||
which excludes the header.
|
||||
|
||||
=attr reader
|
||||
|
||||
A weakened reference to the associated
|
||||
L<Parse::Matroska::Reader>.
|
||||
|
||||
=method new(%hash)
|
||||
|
||||
Creates a new Element initialized with the hash
|
||||
given as argument.
|
||||
|
||||
=cut
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
$self->initialize(@_);
|
||||
return $self;
|
||||
}
|
||||
|
||||
=method initialize(%hash)
|
||||
|
||||
Called by L</new> on initialization.
|
||||
|
||||
=cut
|
||||
sub initialize {
|
||||
my ($self, %args) = @_;
|
||||
for (keys %args) {
|
||||
$self->{$_} = $args{$_};
|
||||
}
|
||||
$self->{depth} = 0 unless $self->{depth};
|
||||
}
|
||||
|
||||
=method skip
|
||||
|
||||
Called by the user to ignore the contents of this EBML node.
|
||||
Needed when ignoring the children of a node.
|
||||
|
||||
=cut
|
||||
sub skip {
|
||||
my ($self) = @_;
|
||||
my $reader = $self->{reader};
|
||||
return unless $reader; # we don't have to skip if there's no reader
|
||||
my $pos = $reader->getpos;
|
||||
croak "Too late to skip, reads were already done"
|
||||
if $pos ne $self->{data_pos};
|
||||
$reader->skip($self->{content_len});
|
||||
}
|
||||
|
||||
=method get_value($keep_bin)
|
||||
|
||||
Returns the value contained by this EBML element.
|
||||
|
||||
If the element has children, returns an arrayref to
|
||||
the children elements that were already encountered.
|
||||
|
||||
If the element's type is C<binary> and the value was
|
||||
delay-loaded, does the reading now.
|
||||
|
||||
If $keep_bin is true, the delay-loaded data is kept
|
||||
as the L</value>, otherwise, further calls to
|
||||
C<get_value> will reread the data from the L</reader>.
|
||||
|
||||
=cut
|
||||
sub get_value {
|
||||
my ($self, $keep_bin) = @_;
|
||||
|
||||
return undef if $self->{type} eq 'skip';
|
||||
return $self->{value} if $self->{value};
|
||||
|
||||
my $reader = $self->{reader} or
|
||||
croak "The associated Reader has been deleted";
|
||||
|
||||
# delay-loaded 'binary'
|
||||
if ($self->{type} eq 'binary') {
|
||||
croak "Cannot seek in the current Reader" unless $self->{data_pos};
|
||||
# seek to the data position...
|
||||
$reader->setpos($self->{data_pos});
|
||||
# read the data, keeping it in value if requested
|
||||
if ($keep_bin) {
|
||||
$self->{value} = $reader->readlen($self->{content_len});
|
||||
return $self->{value};
|
||||
} else {
|
||||
return $reader->readlen($self->{content_len});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
=method next_child($read_bin)
|
||||
|
||||
Builtin iterator; reads and returns the next child element.
|
||||
Always returns undef if the type isn't C<sub>.
|
||||
|
||||
Returns undef at the end of the iterator and resets itself to
|
||||
point to the first element; so calling L</next_child($read_bin)>
|
||||
after the iterator returned C<undef> will return the first child.
|
||||
|
||||
The optional C<$read_bin> parameter has the children elements
|
||||
not delay-load their value if their type is C<binary>.
|
||||
|
||||
If all children elements have already been read, return
|
||||
each element in-order as would be given by
|
||||
L</all_children($recurse,$read_bin)>.
|
||||
|
||||
=cut
|
||||
sub next_child {
|
||||
my ($self, $read_bin) = @_;
|
||||
return unless $self->{type} eq 'sub';
|
||||
|
||||
if ($self->{_all_children_read}) {
|
||||
my $idx = $self->{_last_child} ||= 0;
|
||||
if ($idx == @{$self->{value}}) {
|
||||
# reset the iterator, returning undef once
|
||||
$self->{_last_child} = 0;
|
||||
return;
|
||||
}
|
||||
my $ret = $self->{value}->[$idx];
|
||||
|
||||
++$idx;
|
||||
$self->{_last_child} = $idx;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
my $len = defined $self->{remaining_len}
|
||||
? $self->{remaining_len}
|
||||
: $self->{content_len};
|
||||
|
||||
if ($len == 0) {
|
||||
# we've read all children; switch into $self->{value} iteration mode
|
||||
$self->{_all_children_read} = 1;
|
||||
# return undef since the iterator will reset
|
||||
return;
|
||||
}
|
||||
|
||||
$self->{pos_offset} ||= 0;
|
||||
my $pos = $self->{data_pos};
|
||||
my $reader = $self->{reader} or croak "The associated reader has been deleted";
|
||||
$reader->setpos($pos);
|
||||
$reader->{fh}->seek($self->{pos_offset}, 1) if $pos;
|
||||
|
||||
my $chld = $reader->read_element($read_bin);
|
||||
return undef unless defined $chld;
|
||||
$self->{pos_offset} += $chld->{full_len};
|
||||
|
||||
$self->{remaining_len} = $len - $chld->{full_len};
|
||||
|
||||
if ($self->{remaining_len} < 0) {
|
||||
croak "Child elements consumed $self->{remaining_len} more bytes than parent $self->{name} contained";
|
||||
}
|
||||
|
||||
$chld->{depth} = $self->{depth} + 1;
|
||||
$self->{value} ||= [];
|
||||
|
||||
push @{$self->{value}}, $chld;
|
||||
|
||||
return $chld;
|
||||
}
|
||||
|
||||
=method all_children($recurse,$read_bin)
|
||||
|
||||
Calls L</populate_children($recurse,$read_bin)> on self
|
||||
and returns an arrayref with the children nodes.
|
||||
|
||||
Both C<$recurse> and C<$read_bin> are optional and default
|
||||
to false.
|
||||
|
||||
=cut
|
||||
sub all_children {
|
||||
my ($self, $recurse, $read_bin) = @_;
|
||||
$self->populate_children($recurse, $read_bin);
|
||||
return $self->{value};
|
||||
}
|
||||
|
||||
=method children_by_name($name)
|
||||
|
||||
Searches in the already read children elements for all
|
||||
elements with the EBML name C<$name>. Returns an array
|
||||
containing all found elements. On scalar context,
|
||||
returns only the first element found.
|
||||
|
||||
Croaks if the element's C<type> isn't C<sub>.
|
||||
|
||||
=cut
|
||||
sub children_by_name {
|
||||
my ($self, $name) = @_;
|
||||
return unless defined wantarray; # don't do work if work isn't wanted
|
||||
croak "Element can't have children" unless $self->{type} eq 'sub';
|
||||
|
||||
my @found = grep { $_->{name} eq $name } @{$self->{value}};
|
||||
return @found if wantarray; # list
|
||||
return shift @found if defined wantarray; # scalar
|
||||
}
|
||||
|
||||
=method populate_children($recurse,$read_bin)
|
||||
|
||||
Populates the internal array of children elements, that is,
|
||||
requests that the associated L<Matroska::Parser::Reader> reads
|
||||
all children elements. Returns itself.
|
||||
|
||||
Returns false if the element's C<type> isn't C<sub>.
|
||||
|
||||
If C<$recurse> is provided and is true, the method will call
|
||||
itself in the children elements with the same parameters it
|
||||
received; this will build a full EBML tree.
|
||||
|
||||
If C<$read_bin> is provided and is true, disables delay-loading
|
||||
of the contents of C<binary>-type nodes, reading the contents
|
||||
to memory.
|
||||
|
||||
If both C<$recurse> and C<$read_bin> are true, entire EBML trees
|
||||
can be loaded without requiring seeks, thus behaving correctly
|
||||
on unseekable streams. If C<$read_bin> is false, the entire EBML
|
||||
tree is still loaded, but calling L</get_value> on C<binary>-type
|
||||
nodes will produce an error on unseekable streams.
|
||||
|
||||
=cut
|
||||
sub populate_children {
|
||||
my ($self, $recurse, $read_bin) = @_;
|
||||
|
||||
return unless $self->{type} eq 'sub';
|
||||
|
||||
if (@{$self->{value}} && $recurse) {
|
||||
# only recurse
|
||||
foreach (@{$self->{value}}) {
|
||||
$_->populate_children($recurse, $read_bin);
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
while (my $chld = $self->next_child($read_bin)) {
|
||||
$chld->populate_children($recurse, $read_bin) if $recurse;
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,426 +0,0 @@
|
||||
use 5.008;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# ABSTRACT: a low-level reader for EBML files
|
||||
package Parse::Matroska::Reader;
|
||||
|
||||
use Parse::Matroska::Definitions qw{elem_by_hexid};
|
||||
use Parse::Matroska::Element;
|
||||
|
||||
use Carp;
|
||||
use Scalar::Util qw{openhandle weaken};
|
||||
use IO::Handle;
|
||||
use IO::File;
|
||||
use List::Util qw{first};
|
||||
use Encode;
|
||||
|
||||
use constant BIGINT_TRY => 'Pari,GMP,FastCalc';
|
||||
use Math::BigInt try => BIGINT_TRY;
|
||||
use Math::BigRat try => BIGINT_TRY;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Parse::Matroska::Reader;
|
||||
my $reader = Parse::Matroska::Reader->new($path);
|
||||
$reader->close;
|
||||
$reader->open(\$string_with_matroska_data);
|
||||
|
||||
my $elem = $reader->read_element;
|
||||
print "Element ID: $elem->{elid}\n";
|
||||
print "Element name: $elem->{name}\n";
|
||||
if ($elem->{type} ne 'sub') {
|
||||
print "Element value: $elem->get_value\n";
|
||||
} else {
|
||||
while (my $child = $elem->next_child) {
|
||||
print "Child element: $child->{name}\n";
|
||||
}
|
||||
}
|
||||
$reader->close;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Reads EBML data, which is used in Matroska files.
|
||||
This is a low-level reader which is meant to be used as a backend
|
||||
for higher level readers. TODO: write the high level readers :)
|
||||
|
||||
=head1 NOTE
|
||||
|
||||
The API of this module is not yet considered stable.
|
||||
|
||||
=method new
|
||||
|
||||
Creates a new reader.
|
||||
Calls L</open($arg)> with its arguments if provided.
|
||||
|
||||
=cut
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
$self->open(@_) if @_;
|
||||
return $self;
|
||||
}
|
||||
|
||||
=method open($arg)
|
||||
|
||||
Creates the internal filehandle. The argument can be:
|
||||
|
||||
=for :list
|
||||
* An open filehandle or L<IO::Handle> object.
|
||||
The filehandle is not C<dup()>ed, so calling L</close> in this
|
||||
object will close the given filehandle as well.
|
||||
* A scalar containing a path to a file.
|
||||
* On perl v5.14 or newer, a scalarref pointing to EBML data.
|
||||
For similar functionality in older perls, give an L<IO::String> object
|
||||
or the handle to an already C<open>ed scalarref.
|
||||
|
||||
=cut
|
||||
sub open {
|
||||
my ($self, $arg) = @_;
|
||||
$self->{fh} = openhandle($arg) || IO::File->new($arg, "<:raw")
|
||||
or croak "Can't open $arg: $!";
|
||||
}
|
||||
|
||||
=method close
|
||||
|
||||
Closes the internal filehandle.
|
||||
|
||||
=cut
|
||||
sub close {
|
||||
my ($self) = @_;
|
||||
$self->{fh}->close;
|
||||
delete $self->{fh};
|
||||
}
|
||||
|
||||
# equivalent to $self->readlen(1), possibly faster
|
||||
sub _getc {
|
||||
my ($self) = @_;
|
||||
my $c = $self->{fh}->getc;
|
||||
croak "Can't do read of length 1: $!" if !defined $c && $!;
|
||||
return $c;
|
||||
}
|
||||
|
||||
=method readlen($length)
|
||||
|
||||
Reads C<$length> bytes from the internal filehandle.
|
||||
|
||||
=cut
|
||||
sub readlen {
|
||||
my ($self, $len) = @_;
|
||||
my $data;
|
||||
my $readlen = $self->{fh}->read($data, $len);
|
||||
croak "Can't do read of length $len: $!"
|
||||
unless defined $readlen;
|
||||
return $data;
|
||||
}
|
||||
|
||||
# converts a byte string into an integer
|
||||
# we do so by converting the integer into a hex string (big-endian)
|
||||
# and then reading the hex-string into an integer
|
||||
sub _bin2int($) {
|
||||
my ($bin) = @_;
|
||||
# if the length is larger than 3
|
||||
# the resulting integer might be larger than INT_MAX
|
||||
if (length($bin) > 3) {
|
||||
return Math::BigInt->from_hex(unpack("H*", $bin));
|
||||
}
|
||||
return hex(unpack("H*", $bin));
|
||||
}
|
||||
|
||||
# creates a floating-point number with the given mantissa and exponent
|
||||
sub _ldexp {
|
||||
my ($mantissa, $exponent) = @_;
|
||||
my $r = new Math::BigRat($mantissa);
|
||||
return $r * Math::BigRat->new(2)**$exponent;
|
||||
}
|
||||
|
||||
# NOTE: the read_* functions are hard to read because they're ports
|
||||
# of even harder to read python functions.
|
||||
# TODO: make them readable
|
||||
|
||||
=method read_id
|
||||
|
||||
Reads an EBML ID atom in hexadecimal string format, suitable
|
||||
for passing to L<Parse::Matroska::Definitions/elem_by_hexid($id)>.
|
||||
|
||||
=cut
|
||||
sub read_id {
|
||||
my ($self) = @_;
|
||||
my $t = $self->_getc;
|
||||
return undef unless defined $t;
|
||||
my $i = 0;
|
||||
my $mask = 1<<7;
|
||||
|
||||
if (ord($t) == 0) {
|
||||
croak "Matroska Syntax error: first byte of ID was \\0"
|
||||
}
|
||||
until (ord($t) & $mask) {
|
||||
++$i;
|
||||
$mask >>= 1;
|
||||
}
|
||||
# return hex string of the bytes we just read
|
||||
return unpack "H*", ($t . $self->readlen($i));
|
||||
}
|
||||
|
||||
=method read_size
|
||||
|
||||
Reads an EBML Data Size atom, which immediately follows
|
||||
an EBML ID atom.
|
||||
|
||||
This returns an array consisting of:
|
||||
|
||||
=for :list
|
||||
0. The length of the Data Size atom.
|
||||
1. The value encoded in the Data Size atom, which is the length of all the data following it.
|
||||
|
||||
=cut
|
||||
sub read_size {
|
||||
my ($self) = @_;
|
||||
my $t = $self->_getc;
|
||||
my $i = 0;
|
||||
my $mask = 1<<7;
|
||||
|
||||
if (ord($t) == 0) {
|
||||
croak "Matroska Syntax error: first byte of data size was \\0"
|
||||
}
|
||||
until (ord($t) & $mask) {
|
||||
++$i;
|
||||
$mask >>= 1;
|
||||
}
|
||||
$t = $t & chr($mask-1); # strip length bits (keep only significant bits)
|
||||
return ($i+1, _bin2int $t . $self->readlen($i));
|
||||
}
|
||||
|
||||
=method read_str($length)
|
||||
|
||||
Reads a string of length C<$length> bytes from the internal filehandle.
|
||||
The string is already L<Encode/decode>d from C<UTF-8>, which is the
|
||||
standard Matroska string encoding.
|
||||
|
||||
=cut
|
||||
{
|
||||
my $utf8 = find_encoding("UTF-8");
|
||||
sub read_str {
|
||||
my ($self, $length) = @_;
|
||||
return $utf8->decode($self->readlen($length));
|
||||
}
|
||||
}
|
||||
|
||||
=method read_uint($length)
|
||||
|
||||
Reads an unsigned integer of length C<$length> bytes
|
||||
from the internal filehandle.
|
||||
|
||||
Returns a L<Math::BigInt> object if C<$length> is greater
|
||||
than 4.
|
||||
|
||||
=cut
|
||||
sub read_uint {
|
||||
my ($self, $length) = @_;
|
||||
return _bin2int $self->readlen($length);
|
||||
}
|
||||
|
||||
=method read_sint($length)
|
||||
|
||||
Reads a signed integer of length C<$length> bytes
|
||||
from the internal filehandle.
|
||||
|
||||
Returns a L<Math::BigInt> object if C<$length> is greater
|
||||
than 4.
|
||||
|
||||
=cut
|
||||
sub read_sint {
|
||||
my ($self, $length) = @_;
|
||||
my $i = $self->read_uint($length);
|
||||
|
||||
# Apply 2's complement to the unsigned int
|
||||
my $mask = int(2 ** ($length * 8 - 1));
|
||||
# if the most significant bit is set...
|
||||
if ($i & $mask) {
|
||||
# subtract the MSB twice
|
||||
$i -= 2 * $mask;
|
||||
}
|
||||
return $i;
|
||||
}
|
||||
|
||||
=method read_float($length)
|
||||
|
||||
Reads an IEEE floating point number of length C<$length>
|
||||
bytes from the internal filehandle.
|
||||
|
||||
Only lengths C<4> and C<8> are supported (C C<float> and C<double>).
|
||||
|
||||
=cut
|
||||
{
|
||||
my $b1 = new Math::BigInt 1;
|
||||
|
||||
sub read_float {
|
||||
my ($self, $length) = @_;
|
||||
my $i = new Math::BigInt $self->read_uint($length)->bstr;
|
||||
my $f;
|
||||
|
||||
# These evil expressions reinterpret an unsigned int as IEEE binary floats
|
||||
if ($length == 4) {
|
||||
$f = _ldexp(($i & ((1<<23) - 1)) + (1<<23), ($i>>23 & ((1<<8) - 1)) - 150);
|
||||
$f = -$f if $i & ($b1<<31);
|
||||
} elsif ($length == 8) {
|
||||
$f = _ldexp(($i & (($b1<<52) - 1)) + ($b1<<52), ($i>>52 & ((1<<12) - 1)) - 1075);
|
||||
$f = -$f if $i & ($b1<<63);
|
||||
} else {
|
||||
croak "Matroska Syntax error: unsupported IEEE float byte size $length";
|
||||
}
|
||||
|
||||
return $f;
|
||||
}
|
||||
}
|
||||
|
||||
=method read_ebml_id($length)
|
||||
|
||||
Reads an EBML ID when it's encoded as the data inside another
|
||||
EBML element, that is, when the enclosing element's C<type> is
|
||||
C<ebml_id>.
|
||||
|
||||
This returns a hashref with the EBML element description as
|
||||
defined in L<Parse::Matroska::Definitions>.
|
||||
|
||||
=cut
|
||||
sub read_ebml_id {
|
||||
my ($self, $length) = @_;
|
||||
return elem_by_hexid(unpack("H*", $self->readlen($length)));
|
||||
}
|
||||
|
||||
=method skip($length)
|
||||
|
||||
Skips C<$length> bytes in the internal filehandle.
|
||||
|
||||
=cut
|
||||
sub skip {
|
||||
my ($self, $len) = @_;
|
||||
return if $self->{fh}->can('seek') && $self->{fh}->seek($len, 1);
|
||||
$self->readlen($len);
|
||||
return;
|
||||
}
|
||||
|
||||
=method getpos
|
||||
|
||||
Wrapper for L<IO::Seekable/$io-E<gt>getpos> in the internal filehandle.
|
||||
|
||||
Returns undef if the internal filehandle can't C<getpos>.
|
||||
|
||||
=cut
|
||||
sub getpos {
|
||||
my ($self) = @_;
|
||||
return undef unless $self->{fh}->can('getpos');
|
||||
return $self->{fh}->getpos;
|
||||
}
|
||||
|
||||
=method setpos($pos)
|
||||
|
||||
Wrapper for L<IO::Seekable/$io-E<gt>setpos> in the internal filehandle.
|
||||
|
||||
Returns C<undef> if the internal filehandle can't C<setpos>.
|
||||
|
||||
Croaks if C<setpos> does not seek to the requested position,
|
||||
that is, if calling C<getpos> does not yield the same object
|
||||
as the C<$pos> argument.
|
||||
|
||||
=cut
|
||||
sub setpos {
|
||||
my ($self, $pos) = @_;
|
||||
return undef unless $pos && $self->{fh}->can('setpos');
|
||||
|
||||
my $ret = $self->{fh}->setpos($pos);
|
||||
croak "Cannot seek to correct position"
|
||||
unless $self->getpos eq $pos;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
=method read_element($read_bin)
|
||||
|
||||
Reads a full EBML element from the internal filehandle.
|
||||
|
||||
Returns a L<Parse::Matroska::Element> object initialized with
|
||||
the read data. If C<read_bin> is not present or is false, will
|
||||
delay-load the contents of C<binary> type elements, that is,
|
||||
they will only be loaded when calling C<get_value> on the
|
||||
returned L<Parse::Matroska::Element> object.
|
||||
|
||||
Does not read the children of the element if its type is
|
||||
C<sub>. Look into the L<Parse::Matroska::Element> interface
|
||||
for details in how to read children elements.
|
||||
|
||||
Pass a true C<$read_bin> if the stream being read is not
|
||||
seekable (C<getpos> is undef) and the contents of C<binary>
|
||||
elements is desired, otherwise seeking errors or internal
|
||||
filehandle corruption might occur.
|
||||
|
||||
=cut
|
||||
sub read_element {
|
||||
my ($self, $read_bin) = @_;
|
||||
return undef if $self->{fh}->eof;
|
||||
|
||||
my $elem_pos = $self->getpos;
|
||||
|
||||
my $elid = $self->read_id;
|
||||
my $elem_def = elem_by_hexid($elid);
|
||||
my ($size_len, $content_len) = $self->read_size;
|
||||
my $full_len = length($elid)/2 + $size_len + $content_len;
|
||||
|
||||
my $elem = Parse::Matroska::Element->new(
|
||||
elid => $elid,
|
||||
name => $elem_def && $elem_def->{name},
|
||||
type => $elem_def && $elem_def->{valtype},
|
||||
size_len => $size_len,
|
||||
content_len => $content_len,
|
||||
full_len => $full_len,
|
||||
reader => $self,
|
||||
elem_pos => $elem_pos,
|
||||
data_pos => $self->getpos,
|
||||
);
|
||||
weaken($elem->{reader});
|
||||
|
||||
if (defined $elem_def) {
|
||||
if ($elem->{type} eq 'sub') {
|
||||
$elem->{value} = [];
|
||||
} elsif ($elem->{type} eq 'str') {
|
||||
$elem->{value} = $self->read_str($content_len);
|
||||
} elsif ($elem->{type} eq 'ebml_id') {
|
||||
$elem->{value} = $self->read_ebml_id($content_len);
|
||||
} elsif ($elem->{type} eq 'uint') {
|
||||
$elem->{value} = $self->read_uint($content_len);
|
||||
} elsif ($elem->{type} eq 'sint') {
|
||||
$elem->{value} = $self->read_sint($content_len);
|
||||
} elsif ($elem->{type} eq 'float') {
|
||||
$elem->{value} = $self->read_float($content_len);
|
||||
} elsif ($elem->{type} eq 'skip') {
|
||||
$self->skip($content_len);
|
||||
} elsif ($elem->{type} eq 'binary') {
|
||||
if ($read_bin) {
|
||||
$elem->{value} = $self->readlen($content_len);
|
||||
} else {
|
||||
$self->skip($content_len);
|
||||
}
|
||||
} else {
|
||||
die "Matroska Definition error: type $elem->{valtype} unknown"
|
||||
}
|
||||
} else {
|
||||
$self->skip($content_len);
|
||||
}
|
||||
return $elem;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 CAVEATS
|
||||
|
||||
Children elements have to be processed as soon as an element
|
||||
with children is found, or their children ignored with
|
||||
L<Parse::Matroska::Element/skip>. Not doing so doesn't cause
|
||||
errors but results in an invalid structure, with constant C<0>
|
||||
depth.
|
||||
|
||||
To work correctly in unseekable streams, either the contents
|
||||
of C<binary>-type elements has to be ignored or the C<read_bin>
|
||||
flag to C<read_element> has to be true.
|
||||
@@ -1,37 +0,0 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# ABSTRACT: internally-used helper functions
|
||||
package Parse::Matroska::Utils;
|
||||
|
||||
use Exporter;
|
||||
our @ISA = qw{Exporter};
|
||||
our @EXPORT_OK = qw{uniq uncamelize};
|
||||
|
||||
=method uniq(@array)
|
||||
|
||||
The same as L<List::MoreUtils/"uniq LIST">.
|
||||
Included to avoid depending on it since it's
|
||||
not a core module.
|
||||
|
||||
=cut
|
||||
sub uniq(@) {
|
||||
my %seen;
|
||||
return grep { !$seen{$_}++ } @_;
|
||||
}
|
||||
|
||||
=method uncamelize($string)
|
||||
|
||||
Converts a "StringLikeTHIS" into a
|
||||
"string_like_this".
|
||||
|
||||
=cut
|
||||
sub uncamelize($) {
|
||||
local $_ = shift;
|
||||
# lc followed by UC: lc_UC
|
||||
s/(?<=[a-z])([A-Z])/_\L$1/g;
|
||||
# UC followed by two lc: _UClclc
|
||||
s/([A-Z])(?=[a-z]{2})/_\L$1/g;
|
||||
# strip leading _ that the second regexp might add; lowercase all
|
||||
s/^_//; lc
|
||||
}
|
||||
@@ -7,6 +7,13 @@
|
||||
-- Add at most 5000 * 2 files when starting a file (before + after).
|
||||
MAXENTRIES = 5000
|
||||
|
||||
local options = require 'mp.options'
|
||||
|
||||
o = {
|
||||
disabled = false
|
||||
}
|
||||
options.read_options(o)
|
||||
|
||||
function Set (t)
|
||||
local set = {}
|
||||
for _, v in pairs(t) do set[v] = true end
|
||||
@@ -49,7 +56,7 @@ end
|
||||
function find_and_add_entries()
|
||||
local path = mp.get_property("path", "")
|
||||
local dir, filename = mputils.split_path(path)
|
||||
if #dir == 0 then
|
||||
if o.disabled or #dir == 0 then
|
||||
return
|
||||
end
|
||||
local pl_count = mp.get_property_number("playlist-count", 1)
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
#! /usr/bin/env perl
|
||||
|
||||
# Generate C definitions for parsing Matroska files.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/lib";
|
||||
use Parse::Matroska::Definitions;
|
||||
use Parse::Matroska::Reader;
|
||||
|
||||
use Getopt::Long;
|
||||
use List::Util qw{max};
|
||||
|
||||
my @global_elem_list = @Parse::Matroska::Definitions::global_elem_list;
|
||||
|
||||
Getopt::Long::Configure(qw{auto_version auto_help});
|
||||
my %opt;
|
||||
GetOptions(\%opt,
|
||||
"generate-header",
|
||||
"generate-definitions",
|
||||
"full",
|
||||
);
|
||||
|
||||
if ($opt{"generate-header"}) {
|
||||
generate_c_header();
|
||||
} elsif ($opt{"generate-definitions"}) {
|
||||
generate_c_definitions();
|
||||
} else {
|
||||
for (@ARGV) {
|
||||
my $reader = Parse::Matroska::Reader->new($_ eq '-' ? \*STDIN : $_) or die $!;
|
||||
while (my $elem = $reader->read_element($_ eq '-')) {
|
||||
process_elem($elem, $_ eq '-');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Generate declarations for libmpdemux/ebml_types.h
|
||||
sub generate_c_header {
|
||||
print "/* Generated by TOOLS/matroska.pl, do not edit manually */\n\n";
|
||||
|
||||
# Write a #define for the ElementID of each known element
|
||||
for my $el (@global_elem_list) {
|
||||
printf "#define %-40s 0x%s\n", $el->{definename}, $el->{elid};
|
||||
}
|
||||
print "\n";
|
||||
|
||||
# Define a struct for each ElementID that has child elements
|
||||
for my $el (@global_elem_list) {
|
||||
next unless $el->{subelements};
|
||||
print "\nstruct $el->{structname} {\n";
|
||||
|
||||
# Figure out the length of the longest variable name
|
||||
# Used for pretty-printing in the next step
|
||||
my $l = max(map { length $_->{valname} } values %{$el->{subelements}});
|
||||
|
||||
# Output each variable, with pointers for array (multiple) elements
|
||||
for my $subel (sort { $a->{definename} cmp $b->{definename} } values %{$el->{subelements}}) {
|
||||
printf " %-${l}s %s%s;\n",
|
||||
$subel->{valname}, $subel->{multiple}?'*':' ', $subel->{fieldname};
|
||||
}
|
||||
print "\n";
|
||||
|
||||
# Output a counter variable for each element
|
||||
# (presence/absence for scalars, item count for arrays)
|
||||
for my $subel (sort values %{$el->{subelements}}) {
|
||||
print " int n_$subel->{fieldname};\n"
|
||||
}
|
||||
print "};\n";
|
||||
}
|
||||
print "\n";
|
||||
|
||||
# Output extern references for ebml_elem_desc structs for each of the elements
|
||||
# These are defined by generate_c_definitions
|
||||
for my $el (@global_elem_list) {
|
||||
next unless $el->{subelements};
|
||||
print "extern const struct ebml_elem_desc $el->{structname}_desc;\n";
|
||||
}
|
||||
print "\n";
|
||||
|
||||
# Output the max number of sub-elements a known element might have
|
||||
printf "#define MAX_EBML_SUBELEMENTS %d\n",
|
||||
max(map { scalar keys %{$_->{subelements}} }
|
||||
grep { $_->{subelements} } @global_elem_list);
|
||||
}
|
||||
|
||||
# Generate definitions for libmpdemux/ebml_defs.c
|
||||
sub generate_c_definitions {
|
||||
print "/* Generated by TOOLS/matroska.pl, do not edit manually */\n\n";
|
||||
# ebml_defs.c uses macros declared in ebml.c
|
||||
for my $el (@global_elem_list) {
|
||||
print "\n";
|
||||
if ($el->{subelements}) {
|
||||
# set N for the next macros
|
||||
print "#define N $el->{fieldname}\n";
|
||||
|
||||
# define a struct ebml_$N_desc and gets ready to define fields
|
||||
# this secretly opens two scopes; hence the }}; at the end
|
||||
print "E_S(\"$el->{name}\", ".scalar(keys %{$el->{subelements}}).")\n";
|
||||
|
||||
# define a field for each subelement
|
||||
# also does lots of macro magic, but doesn't open a scope
|
||||
for my $subel (sort { $a->{definename} cmp $b->{definename} } values %{$el->{subelements}}) {
|
||||
print "F($subel->{definename}, $subel->{fieldname}, ".
|
||||
($subel->{multiple}?'1':'0').")\n";
|
||||
}
|
||||
# close the struct
|
||||
print "}};\n";
|
||||
|
||||
# unset N since we've used it
|
||||
print "#undef N\n";
|
||||
} else {
|
||||
print "E(\"$el->{name}\", $el->{fieldname}, $el->{ebmltype})\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub repr {
|
||||
my @ret;
|
||||
foreach (@_) {
|
||||
if (/'/) {
|
||||
s/"/\\"/g;
|
||||
push @ret, "\"$_\"";
|
||||
} else {
|
||||
push @ret, "'$_'";
|
||||
}
|
||||
}
|
||||
return @ret if wantarray;
|
||||
return pop @ret if defined wantarray;
|
||||
return;
|
||||
}
|
||||
|
||||
sub process_elem {
|
||||
my ($elem, $read_bin) = @_;
|
||||
unless ($opt{full}) {
|
||||
if ($elem->{name} eq 'Cluster' || $elem->{name} eq 'Cues') {
|
||||
$elem->skip;
|
||||
return;
|
||||
}
|
||||
}
|
||||
die unless $elem;
|
||||
|
||||
if ($elem->{type} ne 'skip') {
|
||||
print "$elem->{depth} $elem->{elid} $elem->{name} size: $elem->{content_len} value: ";
|
||||
}
|
||||
|
||||
if ($elem->{type} eq 'sub') {
|
||||
print "subelements:\n";
|
||||
while (my $chld = $elem->next_child($read_bin)) {
|
||||
process_elem($chld);
|
||||
}
|
||||
} elsif ($elem->{type} eq 'binary') {
|
||||
my $t = "<skipped $elem->{content_len} bytes>";
|
||||
if ($elem->{content_len} < 20) {
|
||||
$t = unpack "H*", $elem->get_value;
|
||||
}
|
||||
print "binary $t\n";
|
||||
delete $elem->{value};
|
||||
} elsif ($elem->{type} eq 'ebml_id') {
|
||||
print "binary $elem->{value}->{elid} (".($elem->{value}->{name}||"UNKNOWN").")\n";
|
||||
} elsif ($elem->{type} eq 'skip') {
|
||||
# skip
|
||||
} elsif ($elem->{type} eq 'str') {
|
||||
print "string ". repr($elem->get_value) . "\n";
|
||||
} else {
|
||||
print "$elem->{type} ". $elem->get_value ."\n";
|
||||
}
|
||||
}
|
||||
Executable
+471
@@ -0,0 +1,471 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate C definitions for parsing Matroska files.
|
||||
Can also be used to directly parse Matroska files and display their contents.
|
||||
"""
|
||||
|
||||
#
|
||||
# This file is part of MPlayer.
|
||||
#
|
||||
# MPlayer is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# MPlayer is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
|
||||
# for compatibility with Python 2.x
|
||||
from __future__ import print_function
|
||||
|
||||
elements_ebml = (
|
||||
'EBML, 1a45dfa3, sub', (
|
||||
'EBMLVersion, 4286, uint',
|
||||
'EBMLReadVersion, 42f7, uint',
|
||||
'EBMLMaxIDLength, 42f2, uint',
|
||||
'EBMLMaxSizeLength, 42f3, uint',
|
||||
'DocType, 4282, str',
|
||||
'DocTypeVersion, 4287, uint',
|
||||
'DocTypeReadVersion, 4285, uint',
|
||||
),
|
||||
|
||||
'CRC32, bf, binary',
|
||||
'Void, ec, binary',
|
||||
)
|
||||
|
||||
elements_matroska = (
|
||||
'Segment, 18538067, sub', (
|
||||
|
||||
'SeekHead*, 114d9b74, sub', (
|
||||
'Seek*, 4dbb, sub', (
|
||||
'SeekID, 53ab, ebml_id',
|
||||
'SeekPosition, 53ac, uint',
|
||||
),
|
||||
),
|
||||
|
||||
'Info*, 1549a966, sub', (
|
||||
'SegmentUID, 73a4, binary',
|
||||
'PrevUID, 3cb923, binary',
|
||||
'NextUID, 3eb923, binary',
|
||||
'TimecodeScale, 2ad7b1, uint',
|
||||
'DateUTC, 4461, sint',
|
||||
'Title, 7ba9, str',
|
||||
'MuxingApp, 4d80, str',
|
||||
'WritingApp, 5741, str',
|
||||
'Duration, 4489, float',
|
||||
),
|
||||
|
||||
'Cluster*, 1f43b675, sub', (
|
||||
'Timecode, e7, uint',
|
||||
'BlockGroup*, a0, sub', (
|
||||
'Block, a1, binary',
|
||||
'BlockDuration, 9b, uint',
|
||||
'ReferenceBlock*, fb, sint',
|
||||
'DiscardPadding, 75A2, sint',
|
||||
'BlockAdditions, 75A1, sub', (
|
||||
'BlockMore*, A6, sub', (
|
||||
'BlockAddID, EE, uint',
|
||||
'BlockAdditional, A5, binary',
|
||||
),
|
||||
),
|
||||
),
|
||||
'SimpleBlock*, a3, binary',
|
||||
),
|
||||
|
||||
'Tracks*, 1654ae6b, sub', (
|
||||
'TrackEntry*, ae, sub', (
|
||||
'TrackNumber, d7, uint',
|
||||
'TrackUID, 73c5, uint',
|
||||
'TrackType, 83, uint',
|
||||
'FlagEnabled, b9, uint',
|
||||
'FlagDefault, 88, uint',
|
||||
'FlagForced, 55aa, uint',
|
||||
'FlagLacing, 9c, uint',
|
||||
'MinCache, 6de7, uint',
|
||||
'MaxCache, 6df8, uint',
|
||||
'DefaultDuration, 23e383, uint',
|
||||
'TrackTimecodeScale, 23314f, float',
|
||||
'MaxBlockAdditionID, 55ee, uint',
|
||||
'Name, 536e, str',
|
||||
'Language, 22b59c, str',
|
||||
'CodecID, 86, str',
|
||||
'CodecPrivate, 63a2, binary',
|
||||
'CodecName, 258688, str',
|
||||
'CodecDecodeAll, aa, uint',
|
||||
'CodecDelay, 56aa, uint',
|
||||
'SeekPreRoll, 56bb, uint',
|
||||
'Video, e0, sub', (
|
||||
'FlagInterlaced, 9a, uint',
|
||||
'PixelWidth, b0, uint',
|
||||
'PixelHeight, ba, uint',
|
||||
'DisplayWidth, 54b0, uint',
|
||||
'DisplayHeight, 54ba, uint',
|
||||
'DisplayUnit, 54b2, uint',
|
||||
'FrameRate, 2383e3, float',
|
||||
'ColourSpace, 2eb524, binary',
|
||||
'StereoMode, 53b8, uint',
|
||||
'Colour, 55b0, sub', (
|
||||
'MatrixCoefficients, 55B1, uint',
|
||||
'BitsPerChannel, 55B2, uint',
|
||||
'ChromaSubsamplingHorz, 55B3, uint',
|
||||
'ChromaSubsamplingVert, 55B4, uint',
|
||||
'CbSubsamplingHorz, 55B5, uint',
|
||||
'CbSubsamplingVert, 55B6, uint',
|
||||
'ChromaSitingHorz, 55B7, uint',
|
||||
'ChromaSitingVert, 55B8, uint',
|
||||
'Range, 55B9, uint',
|
||||
'TransferCharacteristics, 55BA, uint',
|
||||
'Primaries, 55BB, uint',
|
||||
'MaxCLL, 55BC, uint',
|
||||
'MaxFALL, 55BD, uint',
|
||||
'MasteringMetadata, 55D0, sub', (
|
||||
'PrimaryRChromaticityX, 55D1, float',
|
||||
'PrimaryRChromaticityY, 55D2, float',
|
||||
'PrimaryGChromaticityX, 55D3, float',
|
||||
'PrimaryGChromaticityY, 55D4, float',
|
||||
'PrimaryBChromaticityX, 55D5, float',
|
||||
'PrimaryBChromaticityY, 55D6, float',
|
||||
'WhitePointChromaticityX, 55D7, float',
|
||||
'WhitePointChromaticityY, 55D8, float',
|
||||
'LuminanceMax, 55D9, float',
|
||||
'LuminanceMin, 55DA, float',
|
||||
),
|
||||
),
|
||||
),
|
||||
'Audio, e1, sub', (
|
||||
'SamplingFrequency, b5, float',
|
||||
'OutputSamplingFrequency, 78b5, float',
|
||||
'Channels, 9f, uint',
|
||||
'BitDepth, 6264, uint',
|
||||
),
|
||||
'ContentEncodings, 6d80, sub', (
|
||||
'ContentEncoding*, 6240, sub', (
|
||||
'ContentEncodingOrder, 5031, uint',
|
||||
'ContentEncodingScope, 5032, uint',
|
||||
'ContentEncodingType, 5033, uint',
|
||||
'ContentCompression, 5034, sub', (
|
||||
'ContentCompAlgo, 4254, uint',
|
||||
'ContentCompSettings, 4255, binary',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
'Cues, 1c53bb6b, sub', (
|
||||
'CuePoint*, bb, sub', (
|
||||
'CueTime, b3, uint',
|
||||
'CueTrackPositions*, b7, sub', (
|
||||
'CueTrack, f7, uint',
|
||||
'CueClusterPosition, f1, uint',
|
||||
'CueRelativePosition, f0, uint',
|
||||
'CueDuration, b2, uint',
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
'Attachments, 1941a469, sub', (
|
||||
'AttachedFile*, 61a7, sub', (
|
||||
'FileDescription, 467e, str',
|
||||
'FileName, 466e, str',
|
||||
'FileMimeType, 4660, str',
|
||||
'FileData, 465c, binary',
|
||||
'FileUID, 46ae, uint',
|
||||
),
|
||||
),
|
||||
|
||||
'Chapters, 1043a770, sub', (
|
||||
'EditionEntry*, 45b9, sub', (
|
||||
'EditionUID, 45bc, uint',
|
||||
'EditionFlagHidden, 45bd, uint',
|
||||
'EditionFlagDefault, 45db, uint',
|
||||
'EditionFlagOrdered, 45dd, uint',
|
||||
'ChapterAtom*, b6, sub', (
|
||||
'ChapterUID, 73c4, uint',
|
||||
'ChapterTimeStart, 91, uint',
|
||||
'ChapterTimeEnd, 92, uint',
|
||||
'ChapterFlagHidden, 98, uint',
|
||||
'ChapterFlagEnabled, 4598, uint',
|
||||
'ChapterSegmentUID, 6e67, binary',
|
||||
'ChapterSegmentEditionUID, 6ebc, uint',
|
||||
'ChapterDisplay*, 80, sub', (
|
||||
'ChapString, 85, str',
|
||||
'ChapLanguage*, 437c, str',
|
||||
'ChapCountry*, 437e, str',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'Tags*, 1254c367, sub', (
|
||||
'Tag*, 7373, sub', (
|
||||
'Targets, 63c0, sub', (
|
||||
'TargetTypeValue, 68ca, uint',
|
||||
'TargetTrackUID, 63c5, uint',
|
||||
'TargetEditionUID, 63c9, uint',
|
||||
'TargetChapterUID, 63c4, uint',
|
||||
'TargetAttachmentUID, 63c6, uint',
|
||||
),
|
||||
'SimpleTag*, 67c8, sub', (
|
||||
'TagName, 45a3, str',
|
||||
'TagLanguage, 447a, str',
|
||||
'TagString, 4487, str'
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
import sys
|
||||
from math import ldexp
|
||||
from binascii import hexlify
|
||||
|
||||
def byte2num(s):
|
||||
return int(hexlify(s), 16)
|
||||
|
||||
class EOF(Exception): pass
|
||||
|
||||
def camelcase_to_words(name):
|
||||
parts = []
|
||||
start = 0
|
||||
for i in range(1, len(name)):
|
||||
if name[i].isupper() and (name[i-1].islower() or
|
||||
name[i+1:i+2].islower()):
|
||||
parts.append(name[start:i])
|
||||
start = i
|
||||
parts.append(name[start:])
|
||||
return '_'.join(parts).lower()
|
||||
|
||||
class MatroskaElement(object):
|
||||
|
||||
def __init__(self, name, elid, valtype, namespace):
|
||||
self.name = name
|
||||
self.definename = '{0}_ID_{1}'.format(namespace, name.upper())
|
||||
self.fieldname = camelcase_to_words(name)
|
||||
self.structname = 'ebml_' + self.fieldname
|
||||
self.elid = elid
|
||||
self.valtype = valtype
|
||||
if valtype == 'sub':
|
||||
self.ebmltype = 'EBML_TYPE_SUBELEMENTS'
|
||||
self.valname = 'struct ' + self.structname
|
||||
else:
|
||||
self.ebmltype = 'EBML_TYPE_' + valtype.upper()
|
||||
try:
|
||||
self.valname = {'uint': 'uint64_t', 'str': 'char *',
|
||||
'binary': 'bstr', 'ebml_id': 'uint32_t',
|
||||
'float': 'double', 'sint': 'int64_t',
|
||||
}[valtype]
|
||||
except KeyError:
|
||||
raise SyntaxError('Unrecognized value type ' + valtype)
|
||||
self.subelements = ()
|
||||
|
||||
def add_subelements(self, subelements):
|
||||
self.subelements = subelements
|
||||
self.subids = set(x[0].elid for x in subelements)
|
||||
|
||||
elementd = {}
|
||||
elementlist = []
|
||||
def parse_elems(l, namespace):
|
||||
subelements = []
|
||||
for el in l:
|
||||
if isinstance(el, str):
|
||||
name, hexid, eltype = [x.strip() for x in el.split(',')]
|
||||
hexid = hexid.lower()
|
||||
multiple = name.endswith('*')
|
||||
name = name.strip('*')
|
||||
new = MatroskaElement(name, hexid, eltype, namespace)
|
||||
elementd[hexid] = new
|
||||
elementlist.append(new)
|
||||
subelements.append((new, multiple))
|
||||
else:
|
||||
new.add_subelements(parse_elems(el, namespace))
|
||||
return subelements
|
||||
|
||||
parse_elems(elements_ebml, 'EBML')
|
||||
parse_elems(elements_matroska, 'MATROSKA')
|
||||
|
||||
def printf(out, *args):
|
||||
out.write(u' '.join([str(x) for x in args]))
|
||||
out.write(u'\n')
|
||||
|
||||
def generate_C_header(out):
|
||||
printf(out, '// Generated by TOOLS/matroska.py, do not edit manually')
|
||||
printf(out)
|
||||
|
||||
for el in elementlist:
|
||||
printf(out, '#define {0.definename:40} 0x{0.elid}'.format(el))
|
||||
|
||||
printf(out)
|
||||
|
||||
for el in reversed(elementlist):
|
||||
if not el.subelements:
|
||||
continue
|
||||
printf(out)
|
||||
printf(out, 'struct {0.structname} {{'.format(el))
|
||||
l = max(len(subel.valname) for subel, multiple in el.subelements)+1
|
||||
for subel, multiple in el.subelements:
|
||||
printf(out, ' {e.valname:{l}} {star}{e.fieldname};'.format(
|
||||
e=subel, l=l, star=' *'[multiple]))
|
||||
printf(out)
|
||||
for subel, multiple in el.subelements:
|
||||
printf(out, ' int n_{0.fieldname};'.format(subel))
|
||||
printf(out, '};')
|
||||
|
||||
for el in elementlist:
|
||||
if not el.subelements:
|
||||
continue
|
||||
printf(out, 'extern const struct ebml_elem_desc {0.structname}_desc;'.format(el))
|
||||
|
||||
printf(out)
|
||||
printf(out, '#define MAX_EBML_SUBELEMENTS', max(len(el.subelements)
|
||||
for el in elementlist))
|
||||
|
||||
|
||||
def generate_C_definitions(out):
|
||||
printf(out, '// Generated by TOOLS/matroska.py, do not edit manually')
|
||||
printf(out)
|
||||
for el in reversed(elementlist):
|
||||
printf(out)
|
||||
if el.subelements:
|
||||
printf(out, '#define N', el.fieldname)
|
||||
printf(out, 'E_S("{0}", {1})'.format(el.name, len(el.subelements)))
|
||||
for subel, multiple in el.subelements:
|
||||
printf(out, 'F({0.definename}, {0.fieldname}, {1})'.format(
|
||||
subel, int(multiple)))
|
||||
printf(out, '}};')
|
||||
printf(out, '#undef N')
|
||||
else:
|
||||
printf(out, 'E("{0.name}", {0.fieldname}, {0.ebmltype})'.format(el))
|
||||
|
||||
def read(s, length):
|
||||
t = s.read(length)
|
||||
if len(t) != length:
|
||||
raise EOF
|
||||
return t
|
||||
|
||||
def read_id(s):
|
||||
t = read(s, 1)
|
||||
i = 0
|
||||
mask = 128
|
||||
if ord(t) == 0:
|
||||
raise SyntaxError
|
||||
while not ord(t) & mask:
|
||||
i += 1
|
||||
mask >>= 1
|
||||
t += read(s, i)
|
||||
return t
|
||||
|
||||
def read_vint(s):
|
||||
t = read(s, 1)
|
||||
i = 0
|
||||
mask = 128
|
||||
if ord(t) == 0:
|
||||
raise SyntaxError
|
||||
while not ord(t) & mask:
|
||||
i += 1
|
||||
mask >>= 1
|
||||
t = bytes((ord(t) & (mask - 1),))
|
||||
t += read(s, i)
|
||||
return i+1, byte2num(t)
|
||||
|
||||
def read_str(s, length):
|
||||
return read(s, length)
|
||||
|
||||
def read_uint(s, length):
|
||||
t = read(s, length)
|
||||
return byte2num(t)
|
||||
|
||||
def read_sint(s, length):
|
||||
i = read_uint(s, length)
|
||||
mask = 1 << (length * 8 - 1)
|
||||
if i & mask:
|
||||
i -= 2 * mask
|
||||
return i
|
||||
|
||||
def read_float(s, length):
|
||||
t = read(s, length)
|
||||
i = byte2num(t)
|
||||
if length == 4:
|
||||
f = ldexp((i & 0x7fffff) + (1 << 23), (i >> 23 & 0xff) - 150)
|
||||
if i & (1 << 31):
|
||||
f = -f
|
||||
elif length == 8:
|
||||
f = ldexp((i & ((1 << 52) - 1)) + (1 << 52), (i >> 52 & 0x7ff) - 1075)
|
||||
if i & (1 << 63):
|
||||
f = -f
|
||||
else:
|
||||
raise SyntaxError
|
||||
return f
|
||||
|
||||
def parse_one(s, depth, parent, maxlen):
|
||||
elid = hexlify(read_id(s)).decode('ascii')
|
||||
elem = elementd.get(elid)
|
||||
size, length = read_vint(s)
|
||||
this_length = len(elid) / 2 + size + length
|
||||
if elem is not None:
|
||||
if elem.valtype != 'skip':
|
||||
print(" " * depth, '[' + elid + ']', elem.name, 'size:', length, 'value:', end=' ')
|
||||
if elem.valtype == 'sub':
|
||||
print('subelements:')
|
||||
while length > 0:
|
||||
length -= parse_one(s, depth + 1, elem, length)
|
||||
if length < 0:
|
||||
raise SyntaxError
|
||||
elif elem.valtype == 'str':
|
||||
print('string', repr(read_str(s, length).decode('utf8', 'replace')))
|
||||
elif elem.valtype in ('binary', 'ebml_id'):
|
||||
t = read_str(s, length)
|
||||
dec = ''
|
||||
if elem.valtype == 'ebml_id':
|
||||
idelem = elementd.get(hexlify(t).decode('ascii'))
|
||||
if idelem is None:
|
||||
dec = '(UNKNOWN)'
|
||||
else:
|
||||
dec = '({0.name})'.format(idelem)
|
||||
if len(t) < 20:
|
||||
t = hexlify(t).decode('ascii')
|
||||
else:
|
||||
t = '<{0} bytes>'.format(len(t))
|
||||
print('binary', t, dec)
|
||||
elif elem.valtype == 'uint':
|
||||
print('uint', read_uint(s, length))
|
||||
elif elem.valtype == 'sint':
|
||||
print('sint', read_sint(s, length))
|
||||
elif elem.valtype == 'float':
|
||||
print('float', read_float(s, length))
|
||||
elif elem.valtype == 'skip':
|
||||
read(s, length)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
else:
|
||||
print(" " * depth, '[' + elid + '] Unknown element! size:', length)
|
||||
read(s, length)
|
||||
return this_length
|
||||
|
||||
if __name__ == "__main__":
|
||||
def parse_toplevel(s):
|
||||
parse_one(s, 0, None, 1 << 63)
|
||||
|
||||
if sys.argv[1] == '--generate-header':
|
||||
generate_C_header(sys.stdout)
|
||||
elif sys.argv[1] == '--generate-definitions':
|
||||
generate_C_definitions(sys.stdout)
|
||||
else:
|
||||
if sys.version_info.major < 3:
|
||||
raise Exception("Dumping requires Python 3.")
|
||||
s = open(sys.argv[1], "rb")
|
||||
while 1:
|
||||
start = s.tell()
|
||||
try:
|
||||
parse_toplevel(s)
|
||||
except EOF:
|
||||
if s.tell() != start:
|
||||
raise Exception("Unexpected end of file")
|
||||
break
|
||||
@@ -67,13 +67,12 @@ EOF
|
||||
chapters
|
||||
editions
|
||||
titles
|
||||
duration
|
||||
|
||||
audio
|
||||
audio-bitrate
|
||||
audio-codec
|
||||
audio-format
|
||||
channels
|
||||
samplerate
|
||||
audio-codec-name
|
||||
|
||||
video
|
||||
angle
|
||||
|
||||
+3
-3
@@ -89,7 +89,7 @@ class Libav < TravisDepsBuilder
|
||||
{
|
||||
"libav-stable" => {
|
||||
:action => :stable,
|
||||
:url => 'http://libav.org/releases/libav-11.tar.gz'
|
||||
:url => 'http://libav.org/releases/libav-12.tar.gz'
|
||||
},
|
||||
"libav-git" => {
|
||||
:action => :git,
|
||||
@@ -97,7 +97,7 @@ class Libav < TravisDepsBuilder
|
||||
},
|
||||
"ffmpeg-stable" => {
|
||||
:action => :stable,
|
||||
:url => 'http://www.ffmpeg.org/releases/ffmpeg-2.4.tar.gz'
|
||||
:url => 'http://www.ffmpeg.org/releases/ffmpeg-3.2.2.tar.gz'
|
||||
},
|
||||
"ffmpeg-git" => {
|
||||
:action => :git,
|
||||
@@ -107,7 +107,7 @@ class Libav < TravisDepsBuilder
|
||||
end
|
||||
|
||||
def configure
|
||||
[super, "--cc=#{ENV['CC']}"].join(" ")
|
||||
[super, "--cc=#{ENV['CC']} --disable-asm"].join(" ")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+5
-2
@@ -35,8 +35,11 @@ struct ad_functions {
|
||||
int (*init)(struct dec_audio *da, const char *decoder);
|
||||
void (*uninit)(struct dec_audio *da);
|
||||
int (*control)(struct dec_audio *da, int cmd, void *arg);
|
||||
int (*decode_packet)(struct dec_audio *da, struct demux_packet *pkt,
|
||||
struct mp_audio **out);
|
||||
// Return whether or not the packet has been consumed.
|
||||
bool (*send_packet)(struct dec_audio *da, struct demux_packet *pkt);
|
||||
// Return whether decoding is still going on (false if EOF was reached).
|
||||
// Never returns false & *out set, but can return true with !*out.
|
||||
bool (*receive_frame)(struct dec_audio *da, struct mp_audio **out);
|
||||
};
|
||||
|
||||
enum ad_ctrl {
|
||||
|
||||
+44
-68
@@ -45,7 +45,6 @@ struct priv {
|
||||
uint32_t skip_samples, trim_samples;
|
||||
bool preroll_done;
|
||||
double next_pts;
|
||||
bool needs_reset;
|
||||
AVRational codec_timebase;
|
||||
};
|
||||
|
||||
@@ -100,7 +99,6 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
lavc_context = avcodec_alloc_context3(lavc_codec);
|
||||
ctx->avctx = lavc_context;
|
||||
ctx->avframe = av_frame_alloc();
|
||||
lavc_context->refcounted_frames = 1;
|
||||
lavc_context->codec_type = AVMEDIA_TYPE_AUDIO;
|
||||
lavc_context->codec_id = lavc_codec->id;
|
||||
|
||||
@@ -117,26 +115,18 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
av_opt_set_double(lavc_context, "drc_scale", opts->ac3drc,
|
||||
AV_OPT_SEARCH_CHILDREN);
|
||||
|
||||
#if HAVE_AVFRAME_SKIP_SAMPLES
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
// Let decoder add AV_FRAME_DATA_SKIP_SAMPLES.
|
||||
av_opt_set(lavc_context, "flags2", "+skip_manual", AV_OPT_SEARCH_CHILDREN);
|
||||
#endif
|
||||
|
||||
mp_set_avopts(da->log, lavc_context, opts->avopts);
|
||||
|
||||
lavc_context->codec_tag = c->codec_tag;
|
||||
lavc_context->sample_rate = c->samplerate;
|
||||
lavc_context->bit_rate = c->bitrate;
|
||||
lavc_context->block_align = c->block_align;
|
||||
lavc_context->bits_per_coded_sample = c->bits_per_coded_sample;
|
||||
lavc_context->channels = c->channels.num;
|
||||
if (!mp_chmap_is_unknown(&c->channels))
|
||||
lavc_context->channel_layout = mp_chmap_to_lavc(&c->channels);
|
||||
|
||||
// demux_mkv
|
||||
mp_lavc_set_extradata(lavc_context, c->extradata, c->extradata_size);
|
||||
|
||||
mp_set_lav_codec_headers(lavc_context, c);
|
||||
if (mp_set_avctx_codec_headers(lavc_context, c) < 0) {
|
||||
MP_ERR(da, "Could not set decoder parameters.\n");
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
mp_set_avcodec_threads(da->log, lavc_context, opts->threads);
|
||||
|
||||
@@ -178,14 +168,12 @@ 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;
|
||||
}
|
||||
|
||||
static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
struct mp_audio **out)
|
||||
static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
@@ -196,61 +184,48 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
if (mpkt && priv->next_pts == MP_NOPTS_VALUE)
|
||||
priv->next_pts = mpkt->pts;
|
||||
|
||||
int in_len = mpkt ? mpkt->len : 0;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, &priv->codec_timebase);
|
||||
|
||||
int got_frame = 0;
|
||||
av_frame_unref(priv->avframe);
|
||||
int ret = avcodec_send_packet(avctx, mpkt ? &pkt : NULL);
|
||||
|
||||
if (priv->needs_reset)
|
||||
control(da, ADCTRL_RESET, NULL);
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
return false;
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
int ret = avcodec_send_packet(avctx, &pkt);
|
||||
if (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
if (ret >= 0 && mpkt)
|
||||
mpkt->len = 0;
|
||||
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;
|
||||
}
|
||||
#else
|
||||
int ret = avcodec_decode_audio4(avctx, priv->avframe, &got_frame, &pkt);
|
||||
if (mpkt) {
|
||||
// At least "shorten" decodes sub-frames, instead of the whole packet.
|
||||
// At least "mpc8" can return 0 and wants the packet again next time.
|
||||
if (ret >= 0) {
|
||||
ret = FFMIN(ret, mpkt->len); // sanity check against decoder overreads
|
||||
mpkt->buffer += ret;
|
||||
mpkt->len -= ret;
|
||||
mpkt->pts = MP_NOPTS_VALUE; // don't reset PTS next time
|
||||
}
|
||||
// LATM may need many packets to find mux info
|
||||
if (ret == AVERROR(EAGAIN)) {
|
||||
mpkt->len = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (ret < 0) {
|
||||
if (ret < 0)
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
return -1;
|
||||
}
|
||||
if (!got_frame)
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
double out_pts = mp_pts_from_av(MP_AVFRAME_DEC_PTS(priv->avframe),
|
||||
&priv->codec_timebase);
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_audio **out)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
|
||||
int ret = avcodec_receive_frame(avctx, priv->avframe);
|
||||
|
||||
if (ret == AVERROR_EOF) {
|
||||
// If flushing was initialized earlier and has ended now, make it start
|
||||
// over in case we get new packets at some point in the future.
|
||||
control(da, ADCTRL_RESET, NULL);
|
||||
return false;
|
||||
} else if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
if (priv->avframe->flags & AV_FRAME_FLAG_DISCARD)
|
||||
av_frame_unref(priv->avframe);
|
||||
#endif
|
||||
|
||||
if (!priv->avframe->buf[0])
|
||||
return true;
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pts, &priv->codec_timebase);
|
||||
|
||||
struct mp_audio *mpframe = mp_audio_from_avframe(priv->avframe);
|
||||
if (!mpframe)
|
||||
return -1;
|
||||
return true;
|
||||
|
||||
struct mp_chmap lavc_chmap = mpframe->channels;
|
||||
if (lavc_chmap.num != avctx->channels)
|
||||
@@ -268,7 +243,7 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
if (mpframe->pts != MP_NOPTS_VALUE)
|
||||
priv->next_pts = mpframe->pts + mpframe->samples / (double)mpframe->rate;
|
||||
|
||||
#if HAVE_AVFRAME_SKIP_SAMPLES
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
AVFrameSideData *sd =
|
||||
av_frame_get_side_data(priv->avframe, AV_FRAME_DATA_SKIP_SAMPLES);
|
||||
if (sd && sd->size >= 10) {
|
||||
@@ -300,8 +275,8 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
MP_DBG(da, "Decoded %d -> %d samples\n", in_len, mpframe->samples);
|
||||
return 0;
|
||||
MP_DBG(da, "Decoded %d samples\n", mpframe->samples);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void add_decoders(struct mp_decoder_list *list)
|
||||
@@ -315,5 +290,6 @@ const struct ad_functions ad_lavc = {
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.decode_packet = decode_packet,
|
||||
.send_packet = send_packet,
|
||||
.receive_frame = receive_frame,
|
||||
};
|
||||
+114
-41
@@ -42,6 +42,8 @@ struct spdifContext {
|
||||
bool use_dts_hd;
|
||||
struct mp_audio fmt;
|
||||
struct mp_audio_pool *pool;
|
||||
bool got_eof;
|
||||
struct demux_packet *queued_packet;
|
||||
};
|
||||
|
||||
static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
@@ -71,6 +73,8 @@ static void uninit(struct dec_audio *da)
|
||||
av_freep(&lavf_ctx->pb->buffer);
|
||||
av_freep(&lavf_ctx->pb);
|
||||
avformat_free_context(lavf_ctx);
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->lavf_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,22 +86,41 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
spdif_ctx->use_dts_hd = da->opts->dtshd;
|
||||
spdif_ctx->pool = mp_audio_pool_create(spdif_ctx);
|
||||
|
||||
if (strcmp(decoder, "dts-hd") == 0) {
|
||||
decoder = "dts";
|
||||
if (strcmp(decoder, "spdif_dts_hd") == 0)
|
||||
spdif_ctx->use_dts_hd = true;
|
||||
}
|
||||
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(decoder);
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(da->codec->codec);
|
||||
return spdif_ctx->codec_id != AV_CODEC_ID_NONE;
|
||||
}
|
||||
|
||||
static int determine_codec_profile(struct dec_audio *da, AVPacket *pkt)
|
||||
static void determine_codec_params(struct dec_audio *da, AVPacket *pkt,
|
||||
int *out_profile, int *out_rate)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
int profile = FF_PROFILE_UNKNOWN;
|
||||
AVCodecContext *ctx = NULL;
|
||||
AVFrame *frame = NULL;
|
||||
|
||||
AVCodecParserContext *parser = av_parser_init(spdif_ctx->codec_id);
|
||||
if (parser) {
|
||||
// Don't make it wait for the next frame.
|
||||
parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
|
||||
|
||||
ctx = avcodec_alloc_context3(NULL);
|
||||
|
||||
uint8_t *d = NULL;
|
||||
int s = 0;
|
||||
av_parser_parse2(parser, ctx, &d, &s, pkt->data, pkt->size, 0, 0, 0);
|
||||
*out_profile = profile = ctx->profile;
|
||||
*out_rate = ctx->sample_rate;
|
||||
|
||||
av_free(ctx);
|
||||
av_parser_close(parser);
|
||||
}
|
||||
|
||||
if (profile != FF_PROFILE_UNKNOWN || spdif_ctx->codec_id != AV_CODEC_ID_DTS)
|
||||
return;
|
||||
|
||||
AVCodec *codec = avcodec_find_decoder(spdif_ctx->codec_id);
|
||||
if (!codec)
|
||||
goto done;
|
||||
@@ -116,18 +139,13 @@ 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;
|
||||
*out_profile = profile = ctx->profile;
|
||||
*out_rate = ctx->sample_rate;
|
||||
|
||||
done:
|
||||
av_frame_free(&frame);
|
||||
@@ -137,8 +155,6 @@ done:
|
||||
|
||||
if (profile == FF_PROFILE_UNKNOWN)
|
||||
MP_WARN(da, "Failed to parse codec profile.\n");
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
@@ -146,8 +162,9 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
int profile = FF_PROFILE_UNKNOWN;
|
||||
if (spdif_ctx->codec_id == AV_CODEC_ID_DTS)
|
||||
profile = determine_codec_profile(da, pkt);
|
||||
int c_rate = 0;
|
||||
determine_codec_params(da, pkt, &profile, &c_rate);
|
||||
MP_VERBOSE(da, "In: profile=%d samplerate=%d\n", profile, c_rate);
|
||||
|
||||
AVFormatContext *lavf_ctx = avformat_alloc_context();
|
||||
if (!lavf_ctx)
|
||||
@@ -178,11 +195,7 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
if (!stream)
|
||||
goto fail;
|
||||
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
stream->codecpar->codec_id = spdif_ctx->codec_id;
|
||||
#else
|
||||
stream->codec->codec_id = spdif_ctx->codec_id;
|
||||
#endif
|
||||
|
||||
AVDictionary *format_opts = NULL;
|
||||
|
||||
@@ -197,7 +210,7 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
break;
|
||||
case AV_CODEC_ID_AC3:
|
||||
sample_format = AF_FORMAT_S_AC3;
|
||||
samplerate = 48000;
|
||||
samplerate = c_rate > 0 ? c_rate : 48000;
|
||||
num_channels = 2;
|
||||
break;
|
||||
case AV_CODEC_ID_DTS: {
|
||||
@@ -254,44 +267,72 @@ fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
struct mp_audio **out)
|
||||
|
||||
static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
spdif_ctx->out_buffer_len = 0;
|
||||
if (spdif_ctx->queued_packet || spdif_ctx->got_eof)
|
||||
return false;
|
||||
|
||||
if (!mpkt)
|
||||
return 0;
|
||||
spdif_ctx->queued_packet = mpkt ? demux_copy_packet(mpkt) : NULL;
|
||||
spdif_ctx->got_eof = !mpkt;
|
||||
return true;
|
||||
}
|
||||
|
||||
double pts = mpkt->pts;
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_audio **out)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
if (spdif_ctx->got_eof) {
|
||||
spdif_ctx->got_eof = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!spdif_ctx->queued_packet)
|
||||
return true;
|
||||
|
||||
double pts = spdif_ctx->queued_packet->pts;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
mpkt->len = 0; // will be fully consumed
|
||||
mp_set_av_packet(&pkt, spdif_ctx->queued_packet, NULL);
|
||||
pkt.pts = pkt.dts = 0;
|
||||
if (!spdif_ctx->lavf_ctx) {
|
||||
if (init_filter(da, &pkt) < 0)
|
||||
return -1;
|
||||
goto done;
|
||||
}
|
||||
spdif_ctx->out_buffer_len = 0;
|
||||
int ret = av_write_frame(spdif_ctx->lavf_ctx, &pkt);
|
||||
avio_flush(spdif_ctx->lavf_ctx->pb);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
if (ret < 0) {
|
||||
MP_ERR(da, "spdif mux error: '%s'\n", mp_strerror(AVUNERROR(ret)));
|
||||
goto done;
|
||||
}
|
||||
|
||||
int samples = spdif_ctx->out_buffer_len / spdif_ctx->fmt.sstride;
|
||||
*out = mp_audio_pool_get(spdif_ctx->pool, &spdif_ctx->fmt, samples);
|
||||
if (!*out)
|
||||
return -1;
|
||||
goto done;
|
||||
|
||||
memcpy((*out)->planes[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len);
|
||||
(*out)->pts = pts;
|
||||
|
||||
return 0;
|
||||
done:
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->queued_packet = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESET:
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->queued_packet = NULL;
|
||||
spdif_ctx->got_eof = false;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -305,24 +346,56 @@ static const int codecs[] = {
|
||||
AV_CODEC_ID_NONE
|
||||
};
|
||||
|
||||
static void add_decoders(struct mp_decoder_list *list)
|
||||
static bool find_codec(const char *name)
|
||||
{
|
||||
for (int n = 0; codecs[n] != AV_CODEC_ID_NONE; n++) {
|
||||
const char *format = mp_codec_from_av_codec_id(codecs[n]);
|
||||
if (format) {
|
||||
mp_add_decoder(list, "spdif", format, format,
|
||||
"libavformat/spdifenc audio pass-through decoder");
|
||||
if (format && name && strcmp(format, name) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// codec is the libavcodec name of the source audio codec.
|
||||
// pref is a ","-separated list of names, some of them which do not match with
|
||||
// libavcodec names (like dts-hd).
|
||||
struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref)
|
||||
{
|
||||
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
|
||||
|
||||
if (!find_codec(codec))
|
||||
return list;
|
||||
|
||||
bool spdif_allowed = false, dts_hd_allowed = false;
|
||||
bstr sel = bstr0(pref);
|
||||
while (sel.len) {
|
||||
bstr decoder;
|
||||
bstr_split_tok(sel, ",", &decoder, &sel);
|
||||
if (decoder.len) {
|
||||
if (bstr_equals0(decoder, codec))
|
||||
spdif_allowed = true;
|
||||
if (bstr_equals0(decoder, "dts-hd") && strcmp(codec, "dts") == 0)
|
||||
spdif_allowed = dts_hd_allowed = true;
|
||||
}
|
||||
}
|
||||
mp_add_decoder(list, "spdif", "dts", "dts-hd",
|
||||
|
||||
if (!spdif_allowed)
|
||||
return list;
|
||||
|
||||
const char *suffix_name = dts_hd_allowed ? "dts_hd" : codec;
|
||||
char name[80];
|
||||
snprintf(name, sizeof(name), "spdif_%s", suffix_name);
|
||||
mp_add_decoder(list, "spdif", codec, name,
|
||||
"libavformat/spdifenc audio pass-through decoder");
|
||||
return list;
|
||||
}
|
||||
|
||||
const struct ad_functions ad_spdif = {
|
||||
.name = "spdif",
|
||||
.add_decoders = add_decoders,
|
||||
.add_decoders = NULL,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.decode_packet = decode_packet,
|
||||
.send_packet = send_packet,
|
||||
.receive_frame = receive_frame,
|
||||
};
|
||||
+29
-27
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "common/codecs.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/recorder.h"
|
||||
#include "misc/bstr.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
@@ -43,11 +44,12 @@
|
||||
#include "audio/filter/af.h"
|
||||
|
||||
extern const struct ad_functions ad_lavc;
|
||||
|
||||
// Not a real codec - specially treated.
|
||||
extern const struct ad_functions ad_spdif;
|
||||
|
||||
static const struct ad_functions * const ad_drivers[] = {
|
||||
&ad_lavc,
|
||||
&ad_spdif,
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -90,10 +92,10 @@ static struct mp_decoder_list *audio_select_decoders(struct dec_audio *d_audio)
|
||||
|
||||
struct mp_decoder_list *list = audio_decoder_list();
|
||||
struct mp_decoder_list *new =
|
||||
mp_select_decoders(list, codec, opts->audio_decoders);
|
||||
if (d_audio->try_spdif) {
|
||||
mp_select_decoders(d_audio->log, list, codec, opts->audio_decoders);
|
||||
if (d_audio->try_spdif && codec) {
|
||||
struct mp_decoder_list *spdif =
|
||||
mp_select_decoder_list(list, codec, "spdif", opts->audio_spdif);
|
||||
select_spdif_codec(codec, opts->audio_spdif);
|
||||
mp_append_decoders(spdif, new);
|
||||
talloc_free(new);
|
||||
new = spdif;
|
||||
@@ -108,6 +110,8 @@ static const struct ad_functions *find_driver(const char *name)
|
||||
if (strcmp(ad_drivers[i]->name, name) == 0)
|
||||
return ad_drivers[i];
|
||||
}
|
||||
if (strcmp(name, "spdif") == 0)
|
||||
return &ad_spdif;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -126,21 +130,18 @@ int audio_init_best_codec(struct dec_audio *d_audio)
|
||||
const struct ad_functions *driver = find_driver(sel->family);
|
||||
if (!driver)
|
||||
continue;
|
||||
MP_VERBOSE(d_audio, "Opening audio decoder %s:%s\n",
|
||||
sel->family, sel->decoder);
|
||||
MP_VERBOSE(d_audio, "Opening audio decoder %s\n", sel->decoder);
|
||||
d_audio->ad_driver = driver;
|
||||
if (init_audio_codec(d_audio, sel->decoder)) {
|
||||
decoder = sel;
|
||||
break;
|
||||
}
|
||||
MP_WARN(d_audio, "Audio decoder init failed for "
|
||||
"%s:%s\n", sel->family, sel->decoder);
|
||||
MP_WARN(d_audio, "Audio decoder init failed for %s\n", sel->decoder);
|
||||
}
|
||||
|
||||
if (d_audio->ad_driver) {
|
||||
d_audio->decoder_desc =
|
||||
talloc_asprintf(d_audio, "%s [%s:%s]", decoder->desc, decoder->family,
|
||||
decoder->decoder);
|
||||
talloc_asprintf(d_audio, "%s (%s)", decoder->decoder, decoder->desc);
|
||||
MP_VERBOSE(d_audio, "Selected audio codec: %s\n", d_audio->decoder_desc);
|
||||
} else {
|
||||
MP_ERR(d_audio, "Failed to initialize an audio decoder for codec '%s'.\n",
|
||||
@@ -201,7 +202,7 @@ static void fix_audio_pts(struct dec_audio *da)
|
||||
|
||||
void audio_work(struct dec_audio *da)
|
||||
{
|
||||
if (da->current_frame)
|
||||
if (da->current_frame || !da->ad_driver)
|
||||
return;
|
||||
|
||||
if (!da->packet && !da->new_segment &&
|
||||
@@ -217,30 +218,28 @@ void audio_work(struct dec_audio *da)
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
bool had_input_packet = !!da->packet;
|
||||
bool had_packet = da->packet || da->new_segment;
|
||||
if (da->ad_driver->send_packet(da, da->packet)) {
|
||||
if (da->recorder_sink)
|
||||
mp_recorder_feed_packet(da->recorder_sink, da->packet);
|
||||
|
||||
int ret = da->ad_driver->decode_packet(da, da->packet, &da->current_frame);
|
||||
if (ret < 0 || (da->packet && da->packet->len == 0)) {
|
||||
talloc_free(da->packet);
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
bool progress = da->ad_driver->receive_frame(da, &da->current_frame);
|
||||
|
||||
if (da->current_frame && !mp_audio_config_valid(da->current_frame)) {
|
||||
talloc_free(da->current_frame);
|
||||
da->current_frame = NULL;
|
||||
}
|
||||
|
||||
da->current_state = DATA_OK;
|
||||
if (!da->current_frame) {
|
||||
da->current_state = da->current_frame ? DATA_OK : DATA_AGAIN;
|
||||
if (!progress)
|
||||
da->current_state = DATA_EOF;
|
||||
if (had_packet)
|
||||
da->current_state = DATA_AGAIN;
|
||||
}
|
||||
|
||||
fix_audio_pts(da);
|
||||
|
||||
bool segment_end = !da->current_frame && !had_input_packet;
|
||||
bool segment_end = da->current_state == DATA_EOF;
|
||||
|
||||
if (da->current_frame) {
|
||||
mp_audio_clip_timestamps(da->current_frame, da->start, da->end);
|
||||
@@ -257,12 +256,15 @@ void audio_work(struct dec_audio *da)
|
||||
struct demux_packet *new_segment = da->new_segment;
|
||||
da->new_segment = NULL;
|
||||
|
||||
// Could avoid decoder reinit; would still need flush.
|
||||
da->codec = new_segment->codec;
|
||||
if (da->ad_driver)
|
||||
da->ad_driver->uninit(da);
|
||||
da->ad_driver = NULL;
|
||||
audio_init_best_codec(da);
|
||||
if (da->codec == new_segment->codec) {
|
||||
audio_reset_decoding(da);
|
||||
} else {
|
||||
da->codec = new_segment->codec;
|
||||
if (da->ad_driver)
|
||||
da->ad_driver->uninit(da);
|
||||
da->ad_driver = NULL;
|
||||
audio_init_best_codec(da);
|
||||
}
|
||||
|
||||
da->start = new_segment->start;
|
||||
da->end = new_segment->end;
|
||||
|
||||
@@ -37,6 +37,8 @@ struct dec_audio {
|
||||
|
||||
bool try_spdif;
|
||||
|
||||
struct mp_recorder_sink *recorder_sink;
|
||||
|
||||
// For free use by the ad_driver
|
||||
void *priv;
|
||||
|
||||
@@ -59,4 +61,7 @@ int audio_get_frame(struct dec_audio *d_audio, struct mp_audio **out_frame);
|
||||
|
||||
void audio_reset_decoding(struct dec_audio *d_audio);
|
||||
|
||||
// ad_spdif.c
|
||||
struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref);
|
||||
|
||||
#endif /* MPLAYER_DEC_AUDIO_H */
|
||||
@@ -280,7 +280,6 @@ static int filter_out(struct af_instance *af)
|
||||
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);
|
||||
@@ -310,21 +309,6 @@ static int filter_out(struct af_instance *af)
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
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);
|
||||
s->input->samples = 0;
|
||||
if (lavc_ret < 0 || !ok) {
|
||||
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 + s->input->samples);
|
||||
|
||||
@@ -246,7 +246,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
|
||||
static void get_metadata_from_av_frame(struct af_instance *af, AVFrame *frame)
|
||||
{
|
||||
#if HAVE_AVFRAME_METADATA
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
struct priv *p = af->priv;
|
||||
if (!p->metadata)
|
||||
p->metadata = talloc_zero(p, struct mp_tags);
|
||||
@@ -266,6 +266,12 @@ static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
if (!p->graph)
|
||||
goto error;
|
||||
|
||||
if (!data) {
|
||||
if (p->eof)
|
||||
return 0;
|
||||
p->eof = true;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
frame = mp_audio_to_avframe_and_unref(data);
|
||||
data = NULL;
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
#include "common/common.h"
|
||||
#include "config.h"
|
||||
|
||||
#define HAVE_LIBSWRESAMPLE HAVE_IS_FFMPEG
|
||||
#define HAVE_LIBAVRESAMPLE HAVE_IS_LIBAV
|
||||
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
#include <libavresample/avresample.h>
|
||||
#elif HAVE_LIBSWRESAMPLE
|
||||
@@ -111,12 +114,7 @@ static double get_delay(struct af_resample *s)
|
||||
}
|
||||
static int get_out_samples(struct af_resample *s, int in_samples)
|
||||
{
|
||||
#if LIBSWRESAMPLE_VERSION_MAJOR > 1 || LIBSWRESAMPLE_VERSION_MINOR >= 2
|
||||
return swr_get_out_samples(s->avrctx, in_samples);
|
||||
#else
|
||||
return av_rescale_rnd(in_samples, s->out_rate, s->in_rate, AV_ROUND_UP)
|
||||
+ swr_get_delay(s->avrctx, s->out_rate);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
+28
-18
@@ -35,6 +35,7 @@
|
||||
#include "common/global.h"
|
||||
|
||||
extern const struct ao_driver audio_out_oss;
|
||||
extern const struct ao_driver audio_out_audiounit;
|
||||
extern const struct ao_driver audio_out_coreaudio;
|
||||
extern const struct ao_driver audio_out_coreaudio_exclusive;
|
||||
extern const struct ao_driver audio_out_rsound;
|
||||
@@ -52,6 +53,9 @@ extern const struct ao_driver audio_out_sdl;
|
||||
|
||||
static const struct ao_driver * const audio_out_drivers[] = {
|
||||
// native:
|
||||
#if HAVE_AUDIOUNIT
|
||||
&audio_out_audiounit,
|
||||
#endif
|
||||
#if HAVE_COREAUDIO
|
||||
&audio_out_coreaudio,
|
||||
#endif
|
||||
@@ -108,8 +112,8 @@ static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
.priv_size = ao->priv_size,
|
||||
.priv_defaults = ao->priv_defaults,
|
||||
.options = ao->options,
|
||||
.options_prefix = ao->options_prefix,
|
||||
.global_opts = ao->global_opts,
|
||||
.legacy_prefix = ao->legacy_prefix,
|
||||
.hidden = ao->encode,
|
||||
.p = ao,
|
||||
};
|
||||
@@ -123,11 +127,12 @@ const struct m_obj_list ao_obj_list = {
|
||||
.allow_unknown_entries = true,
|
||||
.allow_trailer = true,
|
||||
.disallow_positional_parameters = true,
|
||||
.use_global_options = true,
|
||||
};
|
||||
|
||||
static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
void (*wakeup_cb)(void *ctx), void *wakeup_ctx,
|
||||
char *name, char **args)
|
||||
char *name)
|
||||
{
|
||||
assert(wakeup_cb);
|
||||
|
||||
@@ -151,12 +156,9 @@ static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
.def_buffer = opts->audio_buffer,
|
||||
.client_name = talloc_strdup(ao, opts->audio_client_name),
|
||||
};
|
||||
struct m_config *config =
|
||||
m_config_from_obj_desc_and_args(ao, ao->log, global, &desc,
|
||||
name, opts->ao_defs, args);
|
||||
if (!config)
|
||||
ao->priv = m_config_group_from_desc(ao, ao->log, global, &desc, name);
|
||||
if (!ao->priv)
|
||||
goto error;
|
||||
ao->priv = config->optstruct;
|
||||
return ao;
|
||||
error:
|
||||
talloc_free(ao);
|
||||
@@ -167,9 +169,9 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
void (*wakeup_cb)(void *ctx), void *wakeup_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx, int flags,
|
||||
int samplerate, int format, struct mp_chmap channels,
|
||||
char *dev, char *name, char **args)
|
||||
char *dev, char *name)
|
||||
{
|
||||
struct ao *ao = ao_alloc(probing, global, wakeup_cb, wakeup_ctx, name, args);
|
||||
struct ao *ao = ao_alloc(probing, global, wakeup_cb, wakeup_ctx, name);
|
||||
if (!ao)
|
||||
return NULL;
|
||||
ao->samplerate = samplerate;
|
||||
@@ -202,7 +204,7 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
talloc_free(ao);
|
||||
return ao_init(probing, global, wakeup_cb, wakeup_ctx,
|
||||
encode_lavc_ctx, flags, samplerate, format, channels,
|
||||
rdevice, redirect, NULL);
|
||||
rdevice, redirect);
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
@@ -309,8 +311,7 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
mp_verbose(log, "Using preferred device '%s'\n", dev);
|
||||
}
|
||||
ao = ao_init(probing, global, wakeup_cb, wakeup_ctx, encode_lavc_ctx,
|
||||
init_flags, samplerate, format, channels, dev,
|
||||
entry->name, entry->attribs);
|
||||
init_flags, samplerate, format, channels, dev, entry->name);
|
||||
if (ao)
|
||||
break;
|
||||
if (!probing)
|
||||
@@ -525,14 +526,10 @@ 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) {
|
||||
ao->driver->list_devs(ao, list);
|
||||
} else {
|
||||
char name[80] = "Default";
|
||||
if (num > 1)
|
||||
mp_snprintf_cat(name, sizeof(name), " (%s)", ao->driver->name);
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){"", name});
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){"", ""});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,7 +568,7 @@ struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp)
|
||||
break; // don't add unsafe/special entries
|
||||
|
||||
struct ao *ao = ao_alloc(true, hp->global, hp->wakeup_cb, hp->wakeup_ctx,
|
||||
(char *)d->name, NULL);
|
||||
(char *)d->name);
|
||||
if (!ao)
|
||||
continue;
|
||||
|
||||
@@ -595,6 +592,19 @@ void ao_device_list_add(struct ao_device_list *list, struct ao *ao,
|
||||
{
|
||||
struct ao_device_desc c = *e;
|
||||
const char *dname = ao->driver->name;
|
||||
char buf[80];
|
||||
if (!c.desc || !c.desc[0]) {
|
||||
if (c.name && c.name[0]) {
|
||||
c.desc = c.name;
|
||||
} else if (list->num_devices) {
|
||||
// Assume this is the default device.
|
||||
snprintf(buf, sizeof(buf), "Default (%s)", dname);
|
||||
c.desc = buf;
|
||||
} else {
|
||||
// First default device (and maybe the only one).
|
||||
c.desc = "Default";
|
||||
}
|
||||
}
|
||||
c.name = c.name[0] ? talloc_asprintf(list, "%s/%s", dname, c.name)
|
||||
: talloc_strdup(list, dname);
|
||||
c.desc = talloc_strdup(list, c.desc);
|
||||
|
||||
+51
-40
@@ -376,12 +376,18 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
struct mp_chmap entry;
|
||||
if (mp_chmap_from_alsa(&entry, &maps[i]->map)) {
|
||||
struct mp_chmap reorder = entry;
|
||||
if (maps[i]->type == SND_CHMAP_TYPE_VAR)
|
||||
mp_chmap_reorder_norm(&reorder);
|
||||
MP_DBG(ao, "Got supported channel map: %s (type %s) -> %s -> %s\n",
|
||||
aname, snd_pcm_chmap_type_name(maps[i]->type),
|
||||
mp_chmap_to_str(&entry), mp_chmap_to_str(&reorder));
|
||||
mp_chmap_sel_add_map(&chmap_sel, &reorder);
|
||||
mp_chmap_reorder_norm(&reorder);
|
||||
|
||||
MP_DBG(ao, "got ALSA chmap: %s (%s) -> %s", aname,
|
||||
snd_pcm_chmap_type_name(maps[i]->type),
|
||||
mp_chmap_to_str(&entry));
|
||||
if (!mp_chmap_equals(&entry, &reorder))
|
||||
MP_DBG(ao, " -> %s", mp_chmap_to_str(&reorder));
|
||||
MP_DBG(ao, "\n");
|
||||
|
||||
struct mp_chmap final =
|
||||
maps[i]->type == SND_CHMAP_TYPE_VAR ? reorder : entry;
|
||||
mp_chmap_sel_add_map(&chmap_sel, &final);
|
||||
} else {
|
||||
MP_VERBOSE(ao, "skipping unknown ALSA channel map: %s\n", aname);
|
||||
}
|
||||
@@ -482,10 +488,14 @@ static int set_chmap(struct ao *ao, struct mp_chmap *dev_chmap, int num_channels
|
||||
} else if (chmap.num != num_channels) {
|
||||
MP_WARN(ao, "ALSA channel map conflicts with channel count!\n");
|
||||
} else {
|
||||
MP_VERBOSE(ao, "using the ALSA channel map.\n");
|
||||
if (mp_chmap_equals(&chmap, &ao->channels))
|
||||
if (mp_chmap_equals(&chmap, &ao->channels)) {
|
||||
MP_VERBOSE(ao, "which is what we requested.\n");
|
||||
ao->channels = chmap;
|
||||
} else if (!mp_chmap_is_valid(dev_chmap)) {
|
||||
MP_VERBOSE(ao, "ignoring the ALSA channel map.\n");
|
||||
} else {
|
||||
MP_VERBOSE(ao, "using the ALSA channel map.\n");
|
||||
ao->channels = chmap;
|
||||
}
|
||||
}
|
||||
|
||||
free(alsa_chmap);
|
||||
@@ -593,8 +603,8 @@ static int try_open_device(struct ao *ao, const char *device, int mode)
|
||||
const char *const fallbacks[] = {"hdmi", "iec958", NULL};
|
||||
for (int n = 0; fallbacks[n]; n++) {
|
||||
char *ndev = append_params(tmp, fallbacks[n], params);
|
||||
MP_VERBOSE(ao, "got error %d; opening iec fallback "
|
||||
"device '%s'\n", err, ndev);
|
||||
MP_VERBOSE(ao, "got error '%s'; opening iec fallback "
|
||||
"device '%s'\n", snd_strerror(err), ndev);
|
||||
err = snd_pcm_open
|
||||
(&p->alsa, ndev, SND_PCM_STREAM_PLAYBACK, mode);
|
||||
if (err >= 0)
|
||||
@@ -714,18 +724,27 @@ static int init_device(struct ao *ao, int mode)
|
||||
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->opts->ignore_chmap) {
|
||||
dev_chmap.num = 0; // disable chmap API
|
||||
} else if (dev_chmap.num == 1 && dev_chmap.speaker[0] == MP_SPEAKER_ID_FC) {
|
||||
// As yet another ALSA API inconsistency, mono is not reported correctly.
|
||||
dev_chmap.num = 0;
|
||||
} else if (query_chmaps(ao, &dev_chmap)) {
|
||||
ao->channels = dev_chmap;
|
||||
} else {
|
||||
// Assume only stereo and mono are supported.
|
||||
mp_chmap_from_channels(&ao->channels, MPMIN(2, dev_chmap.num));
|
||||
dev_chmap.num = 0;
|
||||
// Stereo, or mono if input is 1 channel.
|
||||
struct mp_chmap reduced;
|
||||
mp_chmap_from_channels(&reduced, MPMIN(2, ao->channels.num));
|
||||
|
||||
struct mp_chmap dev_chmap = {0};
|
||||
if (!af_fmt_is_spdif(ao->format) && !p->opts->ignore_chmap &&
|
||||
!mp_chmap_equals(&ao->channels, &reduced))
|
||||
{
|
||||
struct mp_chmap res = ao->channels;
|
||||
if (query_chmaps(ao, &res))
|
||||
dev_chmap = res;
|
||||
|
||||
// Whatever it is, we dumb it down to mono or stereo. Some drivers may
|
||||
// return things like bl-br, but the user (probably) still wants stereo.
|
||||
// This also handles the failure case (dev_chmap.num==0).
|
||||
if (dev_chmap.num <= 2) {
|
||||
dev_chmap.num = 0;
|
||||
ao->channels = reduced;
|
||||
} else if (dev_chmap.num) {
|
||||
ao->channels = dev_chmap;
|
||||
}
|
||||
}
|
||||
|
||||
int num_channels = ao->channels.num;
|
||||
@@ -775,8 +794,13 @@ static int init_device(struct ao *ao, int mode)
|
||||
if (num_channels != ao->channels.num) {
|
||||
int req = ao->channels.num;
|
||||
mp_chmap_from_channels(&ao->channels, MPMIN(2, num_channels));
|
||||
mp_chmap_fill_na(&ao->channels, num_channels);
|
||||
MP_ERR(ao, "Asked for %d channels, got %d - fallback to %s.\n", req,
|
||||
num_channels, mp_chmap_to_str(&ao->channels));
|
||||
if (num_channels != ao->channels.num) {
|
||||
MP_FATAL(ao, "mismatching channel counts.\n");
|
||||
goto alsa_error;
|
||||
}
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_get_buffer_size(alsa_hwparams, &p->buffersize);
|
||||
@@ -821,12 +845,6 @@ static int init_device(struct ao *ao, int mode)
|
||||
|
||||
ao->device_buffer = p->buffersize;
|
||||
|
||||
// ao_alsa implements this by relying on underrun behavior (no data means
|
||||
// underrun, during which silence is played). Trigger by playing some
|
||||
// initial silence.
|
||||
if (ao->stream_silence)
|
||||
ao_play_silence(ao, p->outburst);
|
||||
|
||||
return 0;
|
||||
|
||||
alsa_error:
|
||||
@@ -1121,7 +1139,8 @@ static bool is_useless_device(char *name)
|
||||
{
|
||||
char *crap[] = {"front", "rear", "center_lfe", "side", "surround21",
|
||||
"surround40", "surround41", "surround50", "surround51", "surround71",
|
||||
"sysdefault", "pulse", "null", "dsnoop", "dmix", "hw", "iec958"};
|
||||
"sysdefault", "pulse", "null", "dsnoop", "dmix", "hw", "iec958",
|
||||
"default"};
|
||||
for (int i = 0; i < MP_ARRAY_SIZE(crap); i++) {
|
||||
int l = strlen(crap[i]);
|
||||
if (name && strncmp(name, crap[i], l) == 0 &&
|
||||
@@ -1137,6 +1156,8 @@ static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
if (snd_device_name_hint(-1, "pcm", &hints) < 0)
|
||||
return;
|
||||
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){"", ""});
|
||||
|
||||
for (int n = 0; hints[n]; n++) {
|
||||
char *name = snd_device_name_get_hint(hints[n], "NAME");
|
||||
char *desc = snd_device_name_get_hint(hints[n], "DESC");
|
||||
@@ -1175,15 +1196,5 @@ const struct ao_driver audio_out_alsa = {
|
||||
.wakeup = ao_wakeup_poll,
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_SUBOPT_LEGACY("device", "alsa-device"),
|
||||
OPT_SUBOPT_LEGACY("resample", "alsa-resample"),
|
||||
OPT_SUBOPT_LEGACY("mixer-device", "alsa-mixer-device"),
|
||||
OPT_SUBOPT_LEGACY("mixer-name", "alsa-mixer-name"),
|
||||
OPT_SUBOPT_LEGACY("mixer-index", "alsa-mixer-index"),
|
||||
OPT_SUBOPT_LEGACY("non-interleaved", "alsa-non-interleaved"),
|
||||
OPT_SUBOPT_LEGACY("ignore-chmap", "alsa-ignore-chmap"),
|
||||
{0}
|
||||
},
|
||||
.global_opts = &ao_alsa_conf,
|
||||
};
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "options/m_option.h"
|
||||
#include "misc/ring.h"
|
||||
#include "common/msg.h"
|
||||
#include "ao_coreaudio_utils.h"
|
||||
#include "ao_coreaudio_chmap.h"
|
||||
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <CoreAudio/CoreAudioTypes.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <mach/mach_time.h>
|
||||
|
||||
struct priv {
|
||||
AudioUnit audio_unit;
|
||||
double device_latency;
|
||||
};
|
||||
|
||||
static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||
const AudioTimeStamp *ts, UInt32 bus,
|
||||
UInt32 frames, AudioBufferList *buffer_list)
|
||||
{
|
||||
struct ao *ao = ctx;
|
||||
struct priv *p = ao->priv;
|
||||
void *planes[MP_NUM_CHANNELS] = {0};
|
||||
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
planes[n] = buffer_list->mBuffers[n].mData;
|
||||
|
||||
int64_t end = mp_time_us();
|
||||
end += ca_frames_to_us(ao, frames);
|
||||
end += p->device_latency * 1e6;
|
||||
ao_read_data(ao, planes, frames, end);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static bool init_audiounit(struct ao *ao)
|
||||
{
|
||||
AudioStreamBasicDescription asbd;
|
||||
OSStatus err;
|
||||
uint32_t size;
|
||||
struct priv *p = ao->priv;
|
||||
AVAudioSession *instance = AVAudioSession.sharedInstance;
|
||||
AVAudioSessionPortDescription *port = nil;
|
||||
NSInteger maxChannels = instance.maximumOutputNumberOfChannels;
|
||||
NSInteger prefChannels = MIN(maxChannels, ao->channels.num);
|
||||
|
||||
[instance setCategory:AVAudioSessionCategoryPlayback error:nil];
|
||||
[instance setMode:AVAudioSessionModeMoviePlayback error:nil];
|
||||
[instance setActive:YES error:nil];
|
||||
[instance setPreferredOutputNumberOfChannels:prefChannels error:nil];
|
||||
|
||||
AudioComponentDescription desc = (AudioComponentDescription) {
|
||||
.componentType = kAudioUnitType_Output,
|
||||
.componentSubType = kAudioUnitSubType_RemoteIO,
|
||||
.componentManufacturer = kAudioUnitManufacturer_Apple,
|
||||
.componentFlags = 0,
|
||||
.componentFlagsMask = 0,
|
||||
};
|
||||
|
||||
AudioComponent comp = AudioComponentFindNext(NULL, &desc);
|
||||
if (comp == NULL) {
|
||||
MP_ERR(ao, "unable to find audio component\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
err = AudioComponentInstanceNew(comp, &(p->audio_unit));
|
||||
CHECK_CA_ERROR("unable to open audio component");
|
||||
|
||||
err = AudioUnitInitialize(p->audio_unit);
|
||||
CHECK_CA_ERROR_L(coreaudio_error_component,
|
||||
"unable to initialize audio unit");
|
||||
|
||||
if (af_fmt_is_spdif(ao->format) || instance.outputNumberOfChannels <= 2) {
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP_INIT_STEREO;
|
||||
} else {
|
||||
port = instance.currentRoute.outputs.firstObject;
|
||||
if (port.channels.count == 2 &&
|
||||
port.channels[0].channelLabel == kAudioChannelLabel_Unknown) {
|
||||
// Special case when using an HDMI adapter. The iOS device will
|
||||
// perform SPDIF conversion for us, so send all available channels
|
||||
// using the AC3 mapping.
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP6(FL, FC, FR, SL, SR, LFE);
|
||||
} else {
|
||||
ao->channels.num = (uint8_t)port.channels.count;
|
||||
for (AVAudioSessionChannelDescription *ch in port.channels) {
|
||||
ao->channels.speaker[ch.channelNumber - 1] =
|
||||
ca_label_to_mp_speaker_id(ch.channelLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
size = sizeof(AudioStreamBasicDescription);
|
||||
err = AudioUnitSetProperty(p->audio_unit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &asbd, size);
|
||||
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"unable to set the input format on the audio unit");
|
||||
|
||||
AURenderCallbackStruct render_cb = (AURenderCallbackStruct) {
|
||||
.inputProc = render_cb_lpcm,
|
||||
.inputProcRefCon = ao,
|
||||
};
|
||||
|
||||
err = AudioUnitSetProperty(p->audio_unit,
|
||||
kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &render_cb,
|
||||
sizeof(AURenderCallbackStruct));
|
||||
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"unable to set render callback on audio unit");
|
||||
|
||||
return true;
|
||||
|
||||
coreaudio_error_audiounit:
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
coreaudio_error_component:
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
static void stop(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = AudioOutputUnitStop(p->audio_unit);
|
||||
CHECK_CA_WARN("can't stop audio unit");
|
||||
}
|
||||
|
||||
static void start(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
AVAudioSession *instance = AVAudioSession.sharedInstance;
|
||||
|
||||
p->device_latency = [instance outputLatency];
|
||||
|
||||
OSStatus err = AudioOutputUnitStart(p->audio_unit);
|
||||
CHECK_CA_WARN("can't start audio unit");
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
AudioOutputUnitStop(p->audio_unit);
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
|
||||
[AVAudioSession.sharedInstance
|
||||
setActive:NO
|
||||
withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
|
||||
error:nil];
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
if (!init_audiounit(ao))
|
||||
goto coreaudio_error;
|
||||
|
||||
return CONTROL_OK;
|
||||
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_audiounit = {
|
||||
.description = "AudioUnit (iOS)",
|
||||
.name = "audiounit",
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.pause = stop,
|
||||
.resume = start,
|
||||
.priv_size = sizeof(struct priv),
|
||||
};
|
||||
@@ -39,7 +39,6 @@ struct priv {
|
||||
AudioStreamID original_asbd_stream;
|
||||
|
||||
int change_physical_format;
|
||||
int exclusive;
|
||||
};
|
||||
|
||||
static int64_t ca_get_hardware_latency(struct ao *ao) {
|
||||
@@ -143,9 +142,7 @@ static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
p->exclusive |= ao->init_flags & AO_INIT_EXCLUSIVE;
|
||||
|
||||
if (!af_fmt_is_pcm(ao->format) || p->exclusive) {
|
||||
if (!af_fmt_is_pcm(ao->format) || (ao->init_flags & AO_INIT_EXCLUSIVE)) {
|
||||
MP_VERBOSE(ao, "redirecting to coreaudio_exclusive\n");
|
||||
ao->redirect = "coreaudio_exclusive";
|
||||
return CONTROL_ERROR;
|
||||
@@ -429,9 +426,7 @@ const struct ao_driver audio_out_coreaudio = {
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]){
|
||||
OPT_FLAG("change-physical-format", change_physical_format, 0),
|
||||
OPT_FLAG("exclusive", exclusive, 0,
|
||||
.deprecation_message = "use --audio-exclusive"),
|
||||
{0}
|
||||
},
|
||||
.legacy_prefix = "coreaudio",
|
||||
.options_prefix = "coreaudio",
|
||||
};
|
||||
@@ -56,7 +56,7 @@ static const int speaker_map[][2] = {
|
||||
{ 0, -1 },
|
||||
};
|
||||
|
||||
static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
{
|
||||
for (int i = 0; speaker_map[i][1] >= 0; i++)
|
||||
if (speaker_map[i][0] == label)
|
||||
@@ -64,6 +64,7 @@ static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
static void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout)
|
||||
{
|
||||
if (!mp_msg_test(ao->log, l))
|
||||
@@ -327,3 +328,4 @@ void ca_get_active_chmap(struct ao *ao, AudioDeviceID device, int channel_count,
|
||||
MP_WARN(ao, "mismatching channels - falling back to %s\n",
|
||||
mp_chmap_to_str(out_map));
|
||||
}
|
||||
#endif
|
||||
@@ -22,8 +22,12 @@
|
||||
|
||||
struct mp_chmap;
|
||||
|
||||
int ca_label_to_mp_speaker_id(AudioChannelLabel label);
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
bool ca_init_chmap(struct ao *ao, AudioDeviceID device);
|
||||
void ca_get_active_chmap(struct ao *ao, AudioDeviceID device, int channel_count,
|
||||
struct mp_chmap *out_map);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -22,15 +22,17 @@
|
||||
* on CoreAudio but not the AUHAL (such as using AudioQueue services).
|
||||
*/
|
||||
|
||||
#include <CoreAudio/HostTime.h>
|
||||
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/endian.h"
|
||||
#include "osdep/semaphore.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include <CoreAudio/HostTime.h>
|
||||
#endif
|
||||
|
||||
CFStringRef cfstr_from_cstr(char *str)
|
||||
{
|
||||
return CFStringCreateWithCString(NULL, str, CA_CFSTR_ENCODING);
|
||||
@@ -46,6 +48,7 @@ char *cfstr_get_cstr(CFStringRef cfstr)
|
||||
return buffer;
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
static bool ca_is_output_device(struct ao *ao, AudioDeviceID dev)
|
||||
{
|
||||
size_t n_buffers;
|
||||
@@ -142,6 +145,7 @@ OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
|
||||
coreaudio_error:
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
|
||||
{
|
||||
@@ -306,6 +310,7 @@ int64_t ca_frames_to_us(struct ao *ao, uint32_t frames)
|
||||
return frames / (float) ao->samplerate * 1e6;
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts)
|
||||
{
|
||||
uint64_t out = AudioConvertHostTimeToNanos(ts->mHostTime);
|
||||
@@ -522,4 +527,4 @@ coreaudio_error:
|
||||
sem_destroy(&wakeup);
|
||||
return format_set;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -49,7 +49,9 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message);
|
||||
} while (0)
|
||||
|
||||
void ca_get_device_list(struct ao *ao, struct ao_device_list *list);
|
||||
#if HAVE_COREAUDIO
|
||||
OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device);
|
||||
#endif
|
||||
|
||||
bool ca_formatid_is_compressed(uint32_t formatid);
|
||||
void ca_fill_asbd(struct ao *ao, AudioStreamBasicDescription *asbd);
|
||||
@@ -63,8 +65,11 @@ bool ca_asbd_is_better(AudioStreamBasicDescription *req,
|
||||
AudioStreamBasicDescription *new);
|
||||
|
||||
int64_t ca_frames_to_us(struct ao *ao, uint32_t frames);
|
||||
#if HAVE_COREAUDIO
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts);
|
||||
#endif
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
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);
|
||||
@@ -73,5 +78,6 @@ OSStatus ca_enable_mixing(struct ao *ao, AudioDeviceID device, bool changed);
|
||||
int64_t ca_get_device_latency_us(struct ao *ao, AudioDeviceID device);
|
||||
bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
AudioStreamBasicDescription change_format);
|
||||
#endif
|
||||
|
||||
#endif /* MPV_COREAUDIO_UTILS_H */
|
||||
@@ -246,13 +246,5 @@ const struct ao_driver audio_out_jack = {
|
||||
.uninit = uninit,
|
||||
.resume = resume,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_SUBOPT_LEGACY("port", "jack-port"),
|
||||
OPT_SUBOPT_LEGACY("name", "jack-name"),
|
||||
OPT_SUBOPT_LEGACY("autostart", "jack-autostart"),
|
||||
OPT_SUBOPT_LEGACY("connect", "jack-connect"),
|
||||
OPT_SUBOPT_LEGACY("std-channel-layout", "jack-std-channel-layout"),
|
||||
{0}
|
||||
},
|
||||
.global_opts = &ao_jack_conf,
|
||||
};
|
||||
@@ -258,7 +258,6 @@ static void encode_audio_and_write(struct ao *ao, AVFrame *frame)
|
||||
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",
|
||||
@@ -297,28 +296,6 @@ static void encode_audio_and_write(struct ao *ao, AVFrame *frame)
|
||||
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
|
||||
|
||||
+1
-1
@@ -241,5 +241,5 @@ const struct ao_driver audio_out_null = {
|
||||
OPT_CHANNELS("channel-layouts", channel_layouts, 0),
|
||||
{0}
|
||||
},
|
||||
.legacy_prefix = "ao-null",
|
||||
.options_prefix = "ao-null",
|
||||
};
|
||||
@@ -374,5 +374,5 @@ const struct ao_driver audio_out_openal = {
|
||||
DEVICE_OPT_DEPRECATION),
|
||||
{0}
|
||||
},
|
||||
.legacy_prefix = "ao-openal",
|
||||
.options_prefix = "ao-openal",
|
||||
};
|
||||
@@ -246,5 +246,5 @@ const struct ao_driver audio_out_opensles = {
|
||||
OPT_INTRANGE("sample-rate", cfg_sample_rate, 0, 1000, 100000),
|
||||
{0}
|
||||
},
|
||||
.legacy_prefix = "opensles",
|
||||
.options_prefix = "opensles",
|
||||
};
|
||||
+12
-8
@@ -279,14 +279,21 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
|
||||
int format = ao->format;
|
||||
struct mp_chmap channels = ao->channels;
|
||||
|
||||
const char *device = PATH_DEV_DSP;
|
||||
if (ao->device)
|
||||
device = ao->device;
|
||||
if (p->dsp && p->dsp[0])
|
||||
device = p->dsp;
|
||||
|
||||
MP_VERBOSE(ao, "using '%s' dsp device\n", device);
|
||||
#ifdef __linux__
|
||||
p->audio_fd = open(p->dsp, O_WRONLY | O_NONBLOCK);
|
||||
p->audio_fd = open(device, O_WRONLY | O_NONBLOCK);
|
||||
#else
|
||||
p->audio_fd = open(p->dsp, O_WRONLY);
|
||||
p->audio_fd = open(device, O_WRONLY);
|
||||
#endif
|
||||
if (p->audio_fd < 0) {
|
||||
MP_ERR(ao, "Can't open audio device %s: %s\n",
|
||||
p->dsp, mp_strerror(errno));
|
||||
device, mp_strerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@@ -443,9 +450,8 @@ static int init(struct ao *ao)
|
||||
p->oss_mixer_channel = SOUND_MIXER_PCM;
|
||||
}
|
||||
|
||||
MP_VERBOSE(ao, "using '%s' dsp device\n", p->dsp);
|
||||
MP_VERBOSE(ao, "using '%s' mixer device\n", p->oss_mixer_device);
|
||||
MP_VERBOSE(ao, "using '%s' mixer device\n", mixer_channels[p->oss_mixer_channel]);
|
||||
MP_VERBOSE(ao, "using '%s' mixer channel\n", mixer_channels[p->oss_mixer_channel]);
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
@@ -643,8 +649,6 @@ const struct ao_driver audio_out_oss = {
|
||||
.buffersize = -1,
|
||||
.outburst = 512,
|
||||
.oss_mixer_channel = SOUND_MIXER_PCM,
|
||||
|
||||
.dsp = PATH_DEV_DSP,
|
||||
.oss_mixer_device = PATH_DEV_MIXER,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
@@ -653,5 +657,5 @@ const struct ao_driver audio_out_oss = {
|
||||
OPT_STRING("mixer-channel", cfg_oss_mixer_channel, 0),
|
||||
{0}
|
||||
},
|
||||
.legacy_prefix = "oss",
|
||||
.options_prefix = "oss",
|
||||
};
|
||||
+1
-1
@@ -224,5 +224,5 @@ const struct ao_driver audio_out_pcm = {
|
||||
OPT_FLAG("append", append, 0),
|
||||
{0}
|
||||
},
|
||||
.legacy_prefix = "ao-pcm",
|
||||
.options_prefix = "ao-pcm",
|
||||
};
|
||||
@@ -841,5 +841,5 @@ const struct ao_driver audio_out_pulse = {
|
||||
OPT_FLAG("latency-hacks", cfg_latency_hacks, 0),
|
||||
{0}
|
||||
},
|
||||
.legacy_prefix = "pulse",
|
||||
.options_prefix = "pulse",
|
||||
};
|
||||
@@ -162,6 +162,6 @@ const struct ao_driver audio_out_rsound = {
|
||||
.deprecation_message = "request --audio-device support on issue tracker"),
|
||||
{0}
|
||||
},
|
||||
.legacy_prefix = "rsound",
|
||||
.options_prefix = "rsound",
|
||||
};
|
||||
|
||||
+1
-1
@@ -212,5 +212,5 @@ const struct ao_driver audio_out_sdl = {
|
||||
OPT_FLOAT("buflen", buflen, 0),
|
||||
{0}
|
||||
},
|
||||
.legacy_prefix = "sdl",
|
||||
.options_prefix = "sdl",
|
||||
};
|
||||
@@ -324,5 +324,5 @@ const struct ao_driver audio_out_sndio = {
|
||||
DEVICE_OPT_DEPRECATION),
|
||||
{0}
|
||||
},
|
||||
.legacy_prefix = "ao-sndio",
|
||||
.options_prefix = "ao-sndio",
|
||||
};
|
||||
+6
-13
@@ -255,9 +255,9 @@ static void uninit(struct ao *ao)
|
||||
"while waiting for audio thread to terminate\n");
|
||||
}
|
||||
|
||||
SAFE_RELEASE(state->hInitDone, CloseHandle(state->hInitDone));
|
||||
SAFE_RELEASE(state->hWake, CloseHandle(state->hWake));
|
||||
SAFE_RELEASE(state->hAudioThread,CloseHandle(state->hAudioThread));
|
||||
SAFE_DESTROY(state->hInitDone, CloseHandle(state->hInitDone));
|
||||
SAFE_DESTROY(state->hWake, CloseHandle(state->hWake));
|
||||
SAFE_DESTROY(state->hAudioThread,CloseHandle(state->hAudioThread));
|
||||
|
||||
wasapi_change_uninit(ao);
|
||||
|
||||
@@ -305,7 +305,7 @@ static int init(struct ao *ao)
|
||||
}
|
||||
|
||||
WaitForSingleObject(state->hInitDone, INFINITE); // wait on init complete
|
||||
SAFE_RELEASE(state->hInitDone,CloseHandle(state->hInitDone));
|
||||
SAFE_DESTROY(state->hInitDone,CloseHandle(state->hInitDone));
|
||||
if (FAILED(state->init_ret)) {
|
||||
if (!ao->probing)
|
||||
MP_FATAL(ao, "Received failure from audio thread\n");
|
||||
@@ -418,10 +418,10 @@ static int thread_control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
do {
|
||||
IAudioSessionControl_SetDisplayName(state->pSessionControl, title, NULL);
|
||||
|
||||
SAFE_RELEASE(tmp, CoTaskMemFree(tmp));
|
||||
SAFE_DESTROY(tmp, CoTaskMemFree(tmp));
|
||||
IAudioSessionControl_GetDisplayName(state->pSessionControl, &tmp);
|
||||
} while (lstrcmpW(title, tmp));
|
||||
SAFE_RELEASE(tmp, CoTaskMemFree(tmp));
|
||||
SAFE_DESTROY(tmp, CoTaskMemFree(tmp));
|
||||
talloc_free(title);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
@@ -496,11 +496,4 @@ const struct ao_driver audio_out_wasapi = {
|
||||
.hotplug_init = hotplug_init,
|
||||
.hotplug_uninit = hotplug_uninit,
|
||||
.priv_size = sizeof(wasapi_state),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("exclusive", opt_exclusive, 0,
|
||||
.deprecation_message = "use --audio-exclusive"),
|
||||
OPT_STRING("device", opt_device, 0, DEVICE_OPT_DEPRECATION),
|
||||
{NULL},
|
||||
},
|
||||
.legacy_prefix = "ao-wasapi",
|
||||
};
|
||||
@@ -47,9 +47,13 @@ void wasapi_change_uninit(struct ao* ao);
|
||||
|
||||
#define EXIT_ON_ERROR(hres) \
|
||||
do { if (FAILED(hres)) { goto exit_label; } } while(0)
|
||||
#define SAFE_RELEASE(unk, release) \
|
||||
#define SAFE_DESTROY(unk, release) \
|
||||
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
|
||||
|
||||
#define mp_format_res_str(hres) \
|
||||
(SUCCEEDED(hres) ? "ok" : ((hres) == AUDCLNT_E_UNSUPPORTED_FORMAT) \
|
||||
? "unsupported" : mp_HRESULT_to_str(hres))
|
||||
|
||||
enum wasapi_thread_state {
|
||||
WASAPI_THREAD_FEED = 0,
|
||||
WASAPI_THREAD_RESUME,
|
||||
@@ -92,8 +96,6 @@ typedef struct wasapi_state {
|
||||
|
||||
// ao options
|
||||
int opt_exclusive;
|
||||
int opt_list;
|
||||
char *opt_device;
|
||||
|
||||
// format info
|
||||
WAVEFORMATEXTENSIBLE format;
|
||||
|
||||
@@ -242,5 +242,5 @@ void wasapi_change_uninit(struct ao *ao)
|
||||
change->pEnumerator, (IMMNotificationClient *)change);
|
||||
}
|
||||
|
||||
SAFE_RELEASE(change->pEnumerator, IMMDeviceEnumerator_Release(change->pEnumerator));
|
||||
SAFE_RELEASE(change->pEnumerator);
|
||||
}
|
||||
+25
-40
@@ -289,18 +289,12 @@ static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf,
|
||||
static bool try_format_exclusive(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
MP_VERBOSE(ao, "Trying %s (exclusive)\n",
|
||||
waveformat_to_str(&wformat->Format));
|
||||
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
|
||||
AUDCLNT_SHAREMODE_EXCLUSIVE,
|
||||
&wformat->Format, NULL);
|
||||
if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT)
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
MP_VERBOSE(ao, "Trying %s (exclusive) -> %s\n",
|
||||
waveformat_to_str(&wformat->Format), mp_format_res_str(hr));
|
||||
return SUCCEEDED(hr);
|
||||
exit_label:
|
||||
MP_ERR(state, "Error testing exclusive format: %s\n", mp_HRESULT_to_str(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
// This works like try_format_exclusive(), but will try to fallback to the AC3
|
||||
@@ -393,11 +387,8 @@ static bool search_channels(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
for (int j = 0; channel_layouts[j]; j++) {
|
||||
mp_chmap_from_str(&entry, bstr0(channel_layouts[j]));
|
||||
if (!wformat->Format.nSamplesPerSec) {
|
||||
if (search_samplerates(ao, wformat, &entry)) {
|
||||
if (search_samplerates(ao, wformat, &entry))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &entry);
|
||||
MP_VERBOSE(ao, "%s is supported\n",
|
||||
waveformat_to_str(&wformat->Format));
|
||||
}
|
||||
} else {
|
||||
change_waveformat_channels(wformat, &entry);
|
||||
if (try_format_exclusive(ao, wformat))
|
||||
@@ -442,11 +433,12 @@ static bool find_formats_shared(struct ao *ao)
|
||||
WAVEFORMATEXTENSIBLE wformat;
|
||||
set_waveformat_with_ao(&wformat, ao);
|
||||
|
||||
MP_VERBOSE(ao, "Trying %s (shared)\n", waveformat_to_str(&wformat.Format));
|
||||
WAVEFORMATEX *closestMatch;
|
||||
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
|
||||
AUDCLNT_SHAREMODE_SHARED,
|
||||
&wformat.Format, &closestMatch);
|
||||
MP_VERBOSE(ao, "Trying %s (shared) -> %s\n",
|
||||
waveformat_to_str(&wformat.Format), mp_format_res_str(hr));
|
||||
if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT)
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
@@ -547,8 +539,7 @@ static void init_session_display(struct wasapi_state *state) {
|
||||
return;
|
||||
exit_label:
|
||||
// if we got here then the session control is useless - release it
|
||||
SAFE_RELEASE(state->pSessionControl,
|
||||
IAudioSessionControl_Release(state->pSessionControl));
|
||||
SAFE_RELEASE(state->pSessionControl);
|
||||
MP_WARN(state, "Error setting audio session display name: %s\n",
|
||||
mp_HRESULT_to_str(hr));
|
||||
return;
|
||||
@@ -579,10 +570,8 @@ static void init_volume_control(struct wasapi_state *state)
|
||||
return;
|
||||
exit_label:
|
||||
state->vol_hw_support = 0;
|
||||
SAFE_RELEASE(state->pEndpointVolume,
|
||||
IAudioEndpointVolume_Release(state->pEndpointVolume));
|
||||
SAFE_RELEASE(state->pAudioVolume,
|
||||
ISimpleAudioVolume_Release(state->pAudioVolume));
|
||||
SAFE_RELEASE(state->pEndpointVolume);
|
||||
SAFE_RELEASE(state->pAudioVolume);
|
||||
MP_WARN(state, "Error setting up volume control: %s\n",
|
||||
mp_HRESULT_to_str(hr));
|
||||
}
|
||||
@@ -715,7 +704,7 @@ exit_label:
|
||||
if (FAILED(hr))
|
||||
mp_warn(l, "Failed getting device name: %s\n", mp_HRESULT_to_str(hr));
|
||||
PropVariantClear(&devname);
|
||||
SAFE_RELEASE(pProps, IPropertyStore_Release(pProps));
|
||||
SAFE_RELEASE(pProps);
|
||||
return namestr ? namestr : talloc_strdup(talloc_ctx, "");
|
||||
}
|
||||
|
||||
@@ -730,7 +719,7 @@ static struct device_desc *get_device_desc(struct mp_log *l, IMMDevice *pDevice)
|
||||
struct device_desc *d = talloc_zero(NULL, struct device_desc);
|
||||
d->deviceID = talloc_memdup(d, deviceID,
|
||||
(wcslen(deviceID) + 1) * sizeof(wchar_t));
|
||||
SAFE_RELEASE(deviceID, CoTaskMemFree(deviceID));
|
||||
SAFE_DESTROY(deviceID, CoTaskMemFree(deviceID));
|
||||
|
||||
char *full_id = mp_to_utf8(NULL, d->deviceID);
|
||||
bstr id = bstr0(full_id);
|
||||
@@ -753,8 +742,8 @@ static void destroy_enumerator(struct enumerator *e)
|
||||
{
|
||||
if (!e)
|
||||
return;
|
||||
SAFE_RELEASE(e->pDevices, IMMDeviceCollection_Release(e->pDevices));
|
||||
SAFE_RELEASE(e->pEnumerator, IMMDeviceEnumerator_Release(e->pEnumerator));
|
||||
SAFE_RELEASE(e->pDevices);
|
||||
SAFE_RELEASE(e->pEnumerator);
|
||||
talloc_free(e);
|
||||
}
|
||||
|
||||
@@ -790,7 +779,7 @@ static struct device_desc *device_desc_for_num(struct enumerator *e, UINT i)
|
||||
return NULL;
|
||||
}
|
||||
struct device_desc *d = get_device_desc(e->log, pDevice);
|
||||
SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice));
|
||||
SAFE_RELEASE(pDevice);
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -805,7 +794,7 @@ static struct device_desc *default_device_desc(struct enumerator *e)
|
||||
return NULL;
|
||||
}
|
||||
struct device_desc *d = get_device_desc(e->log, pDevice);
|
||||
SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice));
|
||||
SAFE_RELEASE(pDevice);
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -842,7 +831,7 @@ static HRESULT load_device(struct mp_log *l,
|
||||
exit_label:
|
||||
if (FAILED(hr))
|
||||
mp_err(l, "Error loading selected device: %s\n", mp_HRESULT_to_str(hr));
|
||||
SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator));
|
||||
SAFE_RELEASE(pEnumerator);
|
||||
return hr;
|
||||
}
|
||||
|
||||
@@ -857,11 +846,7 @@ static LPWSTR select_device(struct mp_log *l, struct device_desc *d)
|
||||
|
||||
bstr wasapi_get_specified_device_string(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
bstr device = bstr_strip(bstr0(state->opt_device));
|
||||
if (!device.len)
|
||||
device = bstr_strip(bstr0(ao->device));
|
||||
return device;
|
||||
return bstr_strip(bstr0(ao->device));
|
||||
}
|
||||
|
||||
LPWSTR wasapi_find_deviceID(struct ao *ao)
|
||||
@@ -920,7 +905,7 @@ LPWSTR wasapi_find_deviceID(struct ao *ao)
|
||||
BSTR_P(device), d->id, d->name);
|
||||
}
|
||||
}
|
||||
SAFE_RELEASE(d, talloc_free(d));
|
||||
SAFE_DESTROY(d, talloc_free(d));
|
||||
}
|
||||
|
||||
if (!deviceID)
|
||||
@@ -981,13 +966,13 @@ void wasapi_thread_uninit(struct ao *ao)
|
||||
if (state->pAudioClient)
|
||||
IAudioClient_Stop(state->pAudioClient);
|
||||
|
||||
SAFE_RELEASE(state->pRenderClient, IAudioRenderClient_Release(state->pRenderClient));
|
||||
SAFE_RELEASE(state->pAudioClock, IAudioClock_Release(state->pAudioClock));
|
||||
SAFE_RELEASE(state->pAudioVolume, ISimpleAudioVolume_Release(state->pAudioVolume));
|
||||
SAFE_RELEASE(state->pEndpointVolume, IAudioEndpointVolume_Release(state->pEndpointVolume));
|
||||
SAFE_RELEASE(state->pSessionControl, IAudioSessionControl_Release(state->pSessionControl));
|
||||
SAFE_RELEASE(state->pAudioClient, IAudioClient_Release(state->pAudioClient));
|
||||
SAFE_RELEASE(state->pDevice, IMMDevice_Release(state->pDevice));
|
||||
SAFE_RELEASE(state->hTask, AvRevertMmThreadCharacteristics(state->hTask));
|
||||
SAFE_RELEASE(state->pRenderClient);
|
||||
SAFE_RELEASE(state->pAudioClock);
|
||||
SAFE_RELEASE(state->pAudioVolume);
|
||||
SAFE_RELEASE(state->pEndpointVolume);
|
||||
SAFE_RELEASE(state->pSessionControl);
|
||||
SAFE_RELEASE(state->pAudioClient);
|
||||
SAFE_RELEASE(state->pDevice);
|
||||
SAFE_DESTROY(state->hTask, AvRevertMmThreadCharacteristics(state->hTask));
|
||||
MP_DBG(ao, "Thread uninit done\n");
|
||||
}
|
||||
@@ -182,8 +182,8 @@ struct ao_driver {
|
||||
int priv_size;
|
||||
const void *priv_defaults;
|
||||
const struct m_option *options;
|
||||
const char *options_prefix;
|
||||
const struct m_sub_options *global_opts;
|
||||
const char *legacy_prefix;
|
||||
};
|
||||
|
||||
// These functions can be called by AOs.
|
||||
|
||||
+4
-2
@@ -101,7 +101,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
int state = atomic_load(&p->state);
|
||||
if (!IS_PLAYING(state)) {
|
||||
set_state(ao, AO_STATE_PLAY);
|
||||
ao->driver->resume(ao);
|
||||
if (!ao->stream_silence)
|
||||
ao->driver->resume(ao);
|
||||
}
|
||||
|
||||
return write_samples;
|
||||
@@ -203,7 +204,8 @@ static void pause(struct ao *ao)
|
||||
static void resume(struct ao *ao)
|
||||
{
|
||||
set_state(ao, AO_STATE_PLAY);
|
||||
ao->driver->resume(ao);
|
||||
if (!ao->stream_silence)
|
||||
ao->driver->resume(ao);
|
||||
}
|
||||
|
||||
static bool get_eof(struct ao *ao)
|
||||
|
||||
+40
-13
@@ -49,6 +49,8 @@ struct ao_push_state {
|
||||
|
||||
struct mp_audio_buffer *buffer;
|
||||
|
||||
struct mp_audio *silence;
|
||||
|
||||
bool terminate;
|
||||
bool wait_on_ao;
|
||||
bool still_playing;
|
||||
@@ -249,25 +251,47 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
if (got_data) {
|
||||
p->still_playing = true;
|
||||
p->expected_end_time = 0;
|
||||
}
|
||||
|
||||
// If we don't have new data, the decoder thread basically promises it
|
||||
// will send new data as soon as it's available.
|
||||
if (got_data)
|
||||
// If we don't have new data, the decoder thread basically promises it
|
||||
// will send new data as soon as it's available.
|
||||
wakeup_playthread(ao);
|
||||
}
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
return write_samples;
|
||||
}
|
||||
|
||||
static void ao_get_silence(struct ao *ao, struct mp_audio *data, int size)
|
||||
{
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
if (!p->silence) {
|
||||
p->silence = talloc_zero(p, struct mp_audio);
|
||||
mp_audio_set_format(p->silence, ao->format);
|
||||
mp_audio_set_channels(p->silence, &ao->channels);
|
||||
p->silence->rate = ao->samplerate;
|
||||
}
|
||||
if (p->silence->samples < size) {
|
||||
mp_audio_realloc_min(p->silence, size);
|
||||
p->silence->samples = size;
|
||||
mp_audio_fill_silence(p->silence, 0, size);
|
||||
}
|
||||
*data = *p->silence;
|
||||
data->samples = size;
|
||||
}
|
||||
|
||||
// called locked
|
||||
static void ao_play_data(struct ao *ao)
|
||||
{
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
struct mp_audio data;
|
||||
mp_audio_buffer_peek(p->buffer, &data);
|
||||
int max = data.samples;
|
||||
int space = ao->driver->get_space(ao);
|
||||
bool play_silence = p->paused || (ao->stream_silence && !p->still_playing);
|
||||
space = MPMAX(space, 0);
|
||||
struct mp_audio data;
|
||||
if (play_silence) {
|
||||
ao_get_silence(ao, &data, space);
|
||||
} else {
|
||||
mp_audio_buffer_peek(p->buffer, &data);
|
||||
}
|
||||
int max = data.samples;
|
||||
if (data.samples > space)
|
||||
data.samples = space;
|
||||
int flags = 0;
|
||||
@@ -289,7 +313,8 @@ static void ao_play_data(struct ao *ao)
|
||||
MP_ERR(ao, "Audio output driver seems to ignore AOPLAY_FINAL_CHUNK.\n");
|
||||
r = max;
|
||||
}
|
||||
mp_audio_buffer_skip(p->buffer, r);
|
||||
if (!play_silence)
|
||||
mp_audio_buffer_skip(p->buffer, r);
|
||||
if (r > 0)
|
||||
p->expected_end_time = 0;
|
||||
// Nothing written, but more input data than space - this must mean the
|
||||
@@ -300,7 +325,7 @@ static void ao_play_data(struct ao *ao)
|
||||
// Wait until space becomes available. Also wait if we actually wrote data,
|
||||
// so the AO wakes us up properly if it needs more data.
|
||||
p->wait_on_ao = space == 0 || r > 0 || stuck;
|
||||
p->still_playing |= r > 0;
|
||||
p->still_playing |= r > 0 && !play_silence;
|
||||
// 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);
|
||||
@@ -319,12 +344,13 @@ static void *playthread(void *arg)
|
||||
mpthread_set_name("ao");
|
||||
pthread_mutex_lock(&p->lock);
|
||||
while (!p->terminate) {
|
||||
if (!p->paused)
|
||||
bool playing = !p->paused || ao->stream_silence;
|
||||
if (playing)
|
||||
ao_play_data(ao);
|
||||
|
||||
if (!p->need_wakeup) {
|
||||
MP_STATS(ao, "start audio wait");
|
||||
if (!p->wait_on_ao || p->paused) {
|
||||
if (!p->wait_on_ao || !playing) {
|
||||
// Avoid busy waiting, because the audio API will still report
|
||||
// that it needs new data, even if we're not ready yet, or if
|
||||
// get_space() decides that the amount of audio buffered in the
|
||||
@@ -453,8 +479,9 @@ int ao_play_silence(struct ao *ao, int samples)
|
||||
assert(ao->api == &ao_api_push);
|
||||
if (samples <= 0 || !af_fmt_is_pcm(ao->format) || !ao->driver->play)
|
||||
return 0;
|
||||
char *p = talloc_size(NULL, samples * ao->sstride);
|
||||
af_fill_silence(p, samples * ao->sstride, ao->format);
|
||||
int bytes = af_fmt_to_bytes(ao->format) * samples * ao->channels.num;
|
||||
char *p = talloc_size(NULL, bytes);
|
||||
af_fill_silence(p, bytes, ao->format);
|
||||
void *tmp[MP_NUM_CHANNELS];
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++)
|
||||
tmp[n] = p;
|
||||
|
||||
+86
-53
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/log.h>
|
||||
@@ -32,6 +33,7 @@
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "video/fmt-conversion.h"
|
||||
#include "av_common.h"
|
||||
#include "codecs.h"
|
||||
|
||||
@@ -40,7 +42,7 @@ int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size)
|
||||
if (size) {
|
||||
av_free(avctx->extradata);
|
||||
avctx->extradata_size = 0;
|
||||
avctx->extradata = av_mallocz(size + FF_INPUT_BUFFER_PADDING_SIZE);
|
||||
avctx->extradata = av_mallocz(size + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (!avctx->extradata)
|
||||
return -1;
|
||||
avctx->extradata_size = size;
|
||||
@@ -49,40 +51,81 @@ int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy the codec-related fields from st into avctx. This does not set the
|
||||
// codec itself, only codec related header data provided by libavformat.
|
||||
// The goal is to initialize a new decoder with the header data provided by
|
||||
// libavformat, and unlike avcodec_copy_context(), allow the user to create
|
||||
// a clean AVCodecContext for a manually selected AVCodec.
|
||||
// This is strictly for decoding only.
|
||||
void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st)
|
||||
enum AVMediaType mp_to_av_stream_type(int type)
|
||||
{
|
||||
mp_lavc_set_extradata(avctx, st->extradata, st->extradata_size);
|
||||
avctx->codec_tag = st->codec_tag;
|
||||
avctx->bit_rate = st->bit_rate;
|
||||
avctx->width = st->width;
|
||||
avctx->height = st->height;
|
||||
avctx->pix_fmt = st->pix_fmt;
|
||||
avctx->chroma_sample_location = st->chroma_sample_location;
|
||||
avctx->sample_rate = st->sample_rate;
|
||||
avctx->channels = st->channels;
|
||||
avctx->block_align = st->block_align;
|
||||
avctx->channel_layout = st->channel_layout;
|
||||
avctx->bits_per_coded_sample = st->bits_per_coded_sample;
|
||||
avctx->has_b_frames = st->has_b_frames;
|
||||
switch (type) {
|
||||
case STREAM_VIDEO: return AVMEDIA_TYPE_VIDEO;
|
||||
case STREAM_AUDIO: return AVMEDIA_TYPE_AUDIO;
|
||||
case STREAM_SUB: return AVMEDIA_TYPE_SUBTITLE;
|
||||
default: return AVMEDIA_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// This only copies ffmpeg-native codec parameters. Parameters produced by
|
||||
// other demuxers must be handled manually.
|
||||
void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c)
|
||||
AVCodecParameters *mp_codec_params_to_av(struct mp_codec_params *c)
|
||||
{
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
if (c->lav_codecpar)
|
||||
avcodec_parameters_to_context(avctx, c->lav_codecpar);
|
||||
#else
|
||||
if (c->lav_headers)
|
||||
mp_copy_lav_codec_headers(avctx, c->lav_headers);
|
||||
#endif
|
||||
AVCodecParameters *avp = avcodec_parameters_alloc();
|
||||
if (!avp)
|
||||
return NULL;
|
||||
|
||||
// If we have lavf demuxer params, they overwrite by definition any others.
|
||||
if (c->lav_codecpar) {
|
||||
if (avcodec_parameters_copy(avp, c->lav_codecpar) < 0)
|
||||
goto error;
|
||||
return avp;
|
||||
}
|
||||
|
||||
avp->codec_type = mp_to_av_stream_type(c->type);
|
||||
avp->codec_id = mp_codec_to_av_codec_id(c->codec);
|
||||
avp->codec_tag = c->codec_tag;
|
||||
if (c->extradata_size) {
|
||||
avp->extradata =
|
||||
av_mallocz(c->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (!avp->extradata)
|
||||
goto error;
|
||||
avp->extradata_size = c->extradata_size;
|
||||
memcpy(avp->extradata, c->extradata, avp->extradata_size);
|
||||
}
|
||||
avp->bits_per_coded_sample = c->bits_per_coded_sample;
|
||||
|
||||
// Video only
|
||||
avp->width = c->disp_w;
|
||||
avp->height = c->disp_h;
|
||||
if (c->codec && strcmp(c->codec, "mp-rawvideo") == 0) {
|
||||
avp->format = imgfmt2pixfmt(c->codec_tag);
|
||||
avp->codec_tag = 0;
|
||||
}
|
||||
|
||||
// Audio only
|
||||
avp->sample_rate = c->samplerate;
|
||||
avp->bit_rate = c->bitrate;
|
||||
avp->block_align = c->block_align;
|
||||
avp->channels = c->channels.num;
|
||||
if (!mp_chmap_is_unknown(&c->channels))
|
||||
avp->channel_layout = mp_chmap_to_lavc(&c->channels);
|
||||
|
||||
return avp;
|
||||
error:
|
||||
avcodec_parameters_free(&avp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set avctx codec headers for decoding. Returns <0 on failure.
|
||||
int mp_set_avctx_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c)
|
||||
{
|
||||
enum AVMediaType codec_type = avctx->codec_type;
|
||||
enum AVCodecID codec_id = avctx->codec_id;
|
||||
AVCodecParameters *avp = mp_codec_params_to_av(c);
|
||||
if (!avp)
|
||||
return -1;
|
||||
|
||||
int r = avcodec_parameters_to_context(avctx, avp) < 0 ? -1 : 0;
|
||||
avcodec_parameters_free(&avp);
|
||||
|
||||
if (avctx->codec_type != AVMEDIA_TYPE_UNKNOWN)
|
||||
avctx->codec_type = codec_type;
|
||||
if (avctx->codec_id != AV_CODEC_ID_NONE)
|
||||
avctx->codec_id = codec_id;
|
||||
return r;
|
||||
}
|
||||
|
||||
// Pick a "good" timebase, which will be used to convert double timestamps
|
||||
@@ -104,42 +147,32 @@ AVRational mp_get_codec_timebase(struct mp_codec_params *c)
|
||||
tb.den *= (r.num + r.den - 1) / r.den;
|
||||
}
|
||||
|
||||
av_reduce(&tb.num, &tb.den, tb.num, tb.den, 1000000);
|
||||
av_reduce(&tb.num, &tb.den, tb.num, tb.den, INT_MAX);
|
||||
|
||||
if (tb.num < 1 || tb.den < 1)
|
||||
tb = AV_TIME_BASE_Q;
|
||||
|
||||
return tb;
|
||||
}
|
||||
|
||||
// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it.
|
||||
union pts { int64_t i; double d; };
|
||||
static AVRational get_def_tb(AVRational *tb)
|
||||
{
|
||||
return tb && tb->num > 0 && tb->den > 0 ? *tb : AV_TIME_BASE_Q;
|
||||
}
|
||||
|
||||
// Convert the mpv style timestamp (seconds as double) to a libavcodec style
|
||||
// timestamp (integer units in a given timebase).
|
||||
//
|
||||
// If the given timebase is NULL or invalid, pass through the mpv timestamp by
|
||||
// reinterpret casting them to int64_t. In this case, the timestamps will be
|
||||
// non-sense for libavcodec, but we expect that it doesn't interpret them,
|
||||
// and treats them as opaque.
|
||||
int64_t mp_pts_to_av(double mp_pts, AVRational *tb)
|
||||
{
|
||||
assert(sizeof(int64_t) >= sizeof(double));
|
||||
if (tb && tb->num > 0 && tb->den > 0) {
|
||||
return mp_pts == MP_NOPTS_VALUE ?
|
||||
AV_NOPTS_VALUE : llrint(mp_pts / av_q2d(*tb));
|
||||
}
|
||||
// The + 0.0 is to squash possible negative zero mp_pts, which would
|
||||
// happen to end up as AV_NOPTS_VALUE.
|
||||
return (union pts){.d = mp_pts + 0.0}.i;
|
||||
AVRational b = get_def_tb(tb);
|
||||
return mp_pts == MP_NOPTS_VALUE ? AV_NOPTS_VALUE : llrint(mp_pts / av_q2d(b));
|
||||
}
|
||||
|
||||
// Inverse of mp_pts_to_av(). (The timebases must be exactly the same.)
|
||||
double mp_pts_from_av(int64_t av_pts, AVRational *tb)
|
||||
{
|
||||
assert(sizeof(int64_t) >= sizeof(double));
|
||||
if (tb && tb->num > 0 && tb->den > 0)
|
||||
return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : av_pts * av_q2d(*tb);
|
||||
// Should libavcodec set the PTS to AV_NOPTS_VALUE, it would end up as
|
||||
// non-sense (usually negative zero) when unwrapped to double.
|
||||
return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : (union pts){.i = av_pts}.d;
|
||||
AVRational b = get_def_tb(tb);
|
||||
return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : av_pts * av_q2d(b);
|
||||
}
|
||||
|
||||
// Set dst from mpkt. Note that dst is not refcountable.
|
||||
|
||||
+3
-11
@@ -31,8 +31,9 @@ struct AVDictionary;
|
||||
struct mp_log;
|
||||
|
||||
int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size);
|
||||
void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st);
|
||||
void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c);
|
||||
enum AVMediaType mp_to_av_stream_type(int type);
|
||||
AVCodecParameters *mp_codec_params_to_av(struct mp_codec_params *c);
|
||||
int mp_set_avctx_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c);
|
||||
AVRational mp_get_codec_timebase(struct mp_codec_params *c);
|
||||
void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb);
|
||||
int64_t mp_pts_to_av(double mp_pts, AVRational *tb);
|
||||
@@ -46,13 +47,4 @@ void mp_set_avdict(struct AVDictionary **dict, char **kv);
|
||||
void mp_avdict_print_unset(struct mp_log *log, int msgl, struct AVDictionary *d);
|
||||
int mp_set_avopts(struct mp_log *log, void *avobj, char **kv);
|
||||
|
||||
#if (LIBAVCODEC_VERSION_MICRO >= 100 && \
|
||||
LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 61, 100)) || \
|
||||
(LIBAVCODEC_VERSION_MICRO < 100 && \
|
||||
LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 24, 0))
|
||||
#define MP_AVFRAME_DEC_PTS(frame) ((frame)->pts)
|
||||
#else
|
||||
#define MP_AVFRAME_DEC_PTS(frame) ((frame)->pkt_pts)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+4
-6
@@ -42,10 +42,10 @@
|
||||
#include <libavdevice/avdevice.h>
|
||||
#endif
|
||||
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
#if HAVE_IS_LIBAV
|
||||
#include <libavresample/avresample.h>
|
||||
#endif
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
#if HAVE_IS_FFMPEG
|
||||
#include <libswresample/swresample.h>
|
||||
#endif
|
||||
|
||||
@@ -197,10 +197,10 @@ bool print_libav_versions(struct mp_log *log, int v)
|
||||
{"libavformat", LIBAVFORMAT_VERSION_INT, avformat_version()},
|
||||
{"libswscale", LIBSWSCALE_VERSION_INT, swscale_version()},
|
||||
{"libavfilter", LIBAVFILTER_VERSION_INT, avfilter_version()},
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
#if HAVE_IS_LIBAV
|
||||
{"libavresample", LIBAVRESAMPLE_VERSION_INT, avresample_version()},
|
||||
#endif
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
#if HAVE_IS_FFMPEG
|
||||
{"libswresample", LIBSWRESAMPLE_VERSION_INT, swresample_version()},
|
||||
#endif
|
||||
};
|
||||
@@ -218,9 +218,7 @@ bool print_libav_versions(struct mp_log *log, int v)
|
||||
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;
|
||||
}
|
||||
|
||||
+15
-70
@@ -33,25 +33,6 @@ void mp_add_decoder(struct mp_decoder_list *list, const char *family,
|
||||
MP_TARRAY_APPEND(list, list->entries, list->num_entries, entry);
|
||||
}
|
||||
|
||||
static void mp_add_decoder_entry(struct mp_decoder_list *list,
|
||||
struct mp_decoder_entry *entry)
|
||||
{
|
||||
mp_add_decoder(list, entry->family, entry->codec, entry->decoder,
|
||||
entry->desc);
|
||||
}
|
||||
|
||||
static struct mp_decoder_entry *find_decoder(struct mp_decoder_list *list,
|
||||
bstr family, bstr decoder)
|
||||
{
|
||||
for (int n = 0; n < list->num_entries; n++) {
|
||||
struct mp_decoder_entry *cur = &list->entries[n];
|
||||
if (bstr_equals0(decoder, cur->decoder) &&
|
||||
bstr_equals0(family, cur->family))
|
||||
return cur;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add entry, but only if it's not yet on the list, and if the codec matches.
|
||||
// If codec == NULL, don't compare codecs.
|
||||
static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
|
||||
@@ -59,8 +40,7 @@ static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
|
||||
{
|
||||
if (!entry || (codec && strcmp(entry->codec, codec) != 0))
|
||||
return;
|
||||
if (!find_decoder(to, bstr0(entry->family), bstr0(entry->decoder)))
|
||||
mp_add_decoder_entry(to, entry);
|
||||
mp_add_decoder(to, entry->family, entry->codec, entry->decoder, entry->desc);
|
||||
}
|
||||
|
||||
// Select a decoder from the given list for the given codec. The selection
|
||||
@@ -70,18 +50,16 @@ static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
|
||||
// The selection string corresponds to --vd/--ad directly, and has the
|
||||
// following syntax:
|
||||
// selection = [<entry> ("," <entry>)*]
|
||||
// entry = <family> ":" <decoder> // prefer decoder
|
||||
// entry = <family> ":*" // prefer all decoders
|
||||
// entry = "+" <family> ":" <decoder> // force a decoder
|
||||
// entry = "-" <family> ":" <decoder> // exclude a decoder
|
||||
// entry = <decoder> // prefer decoder
|
||||
// entry = "-" <decoder> // exclude a decoder
|
||||
// entry = "-" // don't add fallback decoders
|
||||
// Forcing a decoder means it's added even if the codec mismatches.
|
||||
struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all,
|
||||
struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
|
||||
struct mp_decoder_list *all,
|
||||
const char *codec,
|
||||
const char *selection)
|
||||
{
|
||||
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
|
||||
struct mp_decoder_list *remove = talloc_zero(NULL, struct mp_decoder_list);
|
||||
if (!codec)
|
||||
codec = "unknown";
|
||||
bool stop = false;
|
||||
@@ -90,26 +68,19 @@ struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all,
|
||||
bstr entry;
|
||||
bstr_split_tok(sel, ",", &entry, &sel);
|
||||
if (bstr_equals0(entry, "-")) {
|
||||
mp_warn(log, "Excluding codecs is deprecated.\n");
|
||||
stop = true;
|
||||
break;
|
||||
}
|
||||
bool force = bstr_eatstart0(&entry, "+");
|
||||
bool exclude = !force && bstr_eatstart0(&entry, "-");
|
||||
struct mp_decoder_list *dest = exclude ? remove : list;
|
||||
bstr family, decoder;
|
||||
if (!bstr_split_tok(entry, ":", &family, &decoder)) {
|
||||
family = entry;
|
||||
decoder = bstr0("*");
|
||||
if (bstr_find0(entry, ":") >= 0) {
|
||||
mp_err(log, "Codec family selection was removed. "
|
||||
"Pass the codec name directly.\n");
|
||||
break;
|
||||
}
|
||||
if (bstr_equals0(decoder, "*")) {
|
||||
for (int n = 0; n < all->num_entries; n++) {
|
||||
struct mp_decoder_entry *cur = &all->entries[n];
|
||||
if (bstr_equals0(family, cur->family))
|
||||
add_new(dest, cur, codec);
|
||||
}
|
||||
} else {
|
||||
add_new(dest, find_decoder(all, family, decoder),
|
||||
force ? NULL : codec);
|
||||
for (int n = 0; n < all->num_entries; n++) {
|
||||
struct mp_decoder_entry *cur = &all->entries[n];
|
||||
if (bstr_equals0(entry, cur->decoder))
|
||||
add_new(list, cur, codec);
|
||||
}
|
||||
}
|
||||
if (!stop) {
|
||||
@@ -117,32 +88,6 @@ struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all,
|
||||
for (int n = 0; n < all->num_entries; n++)
|
||||
add_new(list, &all->entries[n], codec);
|
||||
}
|
||||
for (int n = 0; n < remove->num_entries; n++) {
|
||||
struct mp_decoder_entry *ex = &remove->entries[n];
|
||||
struct mp_decoder_entry *del =
|
||||
find_decoder(list, bstr0(ex->family), bstr0(ex->decoder));
|
||||
if (del) {
|
||||
int index = del - &list->entries[0];
|
||||
MP_TARRAY_REMOVE_AT(list->entries, list->num_entries, index);
|
||||
}
|
||||
}
|
||||
talloc_free(remove);
|
||||
return list;
|
||||
}
|
||||
|
||||
// selection is a ","-separated list of decoders, all in the given family.
|
||||
struct mp_decoder_list *mp_select_decoder_list(struct mp_decoder_list *all,
|
||||
const char *codec,
|
||||
const char *family,
|
||||
const char *selection)
|
||||
{
|
||||
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
|
||||
bstr sel = bstr0(selection);
|
||||
while (sel.len) {
|
||||
bstr decoder;
|
||||
bstr_split_tok(sel, ",", &decoder, &sel);
|
||||
add_new(list, find_decoder(all, bstr0(family), decoder), codec);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -158,7 +103,7 @@ void mp_print_decoders(struct mp_log *log, int msgl, const char *header,
|
||||
mp_msg(log, msgl, "%s\n", header);
|
||||
for (int n = 0; n < list->num_entries; n++) {
|
||||
struct mp_decoder_entry *entry = &list->entries[n];
|
||||
mp_msg(log, msgl, " %s:%s", entry->family, entry->decoder);
|
||||
mp_msg(log, msgl, " %s", entry->decoder);
|
||||
if (strcmp(entry->decoder, entry->codec) != 0)
|
||||
mp_msg(log, msgl, " (%s)", entry->codec);
|
||||
mp_msg(log, msgl, " - %s\n", entry->desc);
|
||||
|
||||
+4
-6
@@ -18,6 +18,8 @@
|
||||
#ifndef MP_CODECS_H
|
||||
#define MP_CODECS_H
|
||||
|
||||
struct mp_log;
|
||||
|
||||
struct mp_decoder_entry {
|
||||
const char *family; // decoder module (e.g. ad_lavc => "lavc")
|
||||
const char *codec; // name of the codec (e.g. "mp3")
|
||||
@@ -33,15 +35,11 @@ struct mp_decoder_list {
|
||||
void mp_add_decoder(struct mp_decoder_list *list, const char *family,
|
||||
const char *codec, const char *decoder, const char *desc);
|
||||
|
||||
struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all,
|
||||
struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
|
||||
struct mp_decoder_list *all,
|
||||
const char *codec,
|
||||
const char *selection);
|
||||
|
||||
struct mp_decoder_list *mp_select_decoder_list(struct mp_decoder_list *all,
|
||||
const char *codec,
|
||||
const char *family,
|
||||
const char *selection);
|
||||
|
||||
void mp_append_decoders(struct mp_decoder_list *list, struct mp_decoder_list *a);
|
||||
|
||||
struct mp_log;
|
||||
|
||||
+1
-19
@@ -473,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);
|
||||
codec->flags &= ~CODEC_FLAG_PASS2;
|
||||
codec->flags &= ~AV_CODEC_FLAG_PASS2;
|
||||
set_to_avdictionary(ctx, dictp, "flags", "-pass2");
|
||||
} else {
|
||||
struct bstr content = stream_read_complete(*bytebuf, NULL,
|
||||
@@ -595,12 +595,7 @@ int encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#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.
|
||||
ctx->vst->time_base = ctx->timebase;
|
||||
@@ -635,12 +630,7 @@ int encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#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.
|
||||
ctx->ast->time_base = ctx->timebase;
|
||||
@@ -708,10 +698,8 @@ int encode_lavc_open_codec(struct encode_lavc_context *ctx,
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -747,10 +735,8 @@ int encode_lavc_open_codec(struct encode_lavc_context *ctx,
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -826,11 +812,7 @@ int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVStream *stream,
|
||||
(int)packet->size);
|
||||
|
||||
|
||||
#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;
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
|
||||
#include "recorder.h"
|
||||
|
||||
#define PTS_ADD(a, b) ((a) == MP_NOPTS_VALUE ? (a) : ((a) + (b)))
|
||||
|
||||
// Maximum number of packets we buffer at most to attempt to resync streams.
|
||||
// Essentially, this should be higher than the highest supported keyframe
|
||||
// interval.
|
||||
#define QUEUE_MAX_PACKETS 256
|
||||
// Number of packets we should buffer at least to determine timestamps (due to
|
||||
// codec delay and frame reordering, and potentially lack of DTS).
|
||||
// Keyframe flags can trigger this earlier.
|
||||
#define QUEUE_MIN_PACKETS 16
|
||||
|
||||
struct mp_recorder {
|
||||
struct mpv_global *global;
|
||||
struct mp_log *log;
|
||||
|
||||
struct mp_recorder_sink **streams;
|
||||
int num_streams;
|
||||
|
||||
bool opened; // mux context is valid
|
||||
bool muxing; // we're currently recording (instead of preparing)
|
||||
bool muxing_from_start; // no discontinuity at start
|
||||
bool dts_warning;
|
||||
|
||||
// The start timestamp of the currently recorded segment (the timestamp of
|
||||
// the first packet of the incoming packet stream).
|
||||
double base_ts;
|
||||
// The output packet timestamp corresponding to base_ts. It's the timestamp
|
||||
// of the first packet of the current segment written to the output.
|
||||
double rebase_ts;
|
||||
|
||||
AVFormatContext *mux;
|
||||
};
|
||||
|
||||
struct mp_recorder_sink {
|
||||
struct mp_recorder *owner;
|
||||
struct sh_stream *sh;
|
||||
AVStream *av_stream;
|
||||
double max_out_pts;
|
||||
bool discont;
|
||||
bool proper_eof;
|
||||
struct demux_packet **packets;
|
||||
int num_packets;
|
||||
};
|
||||
|
||||
static int add_stream(struct mp_recorder *priv, struct sh_stream *sh)
|
||||
{
|
||||
enum AVMediaType av_type = mp_to_av_stream_type(sh->type);
|
||||
if (av_type == AVMEDIA_TYPE_UNKNOWN)
|
||||
return -1;
|
||||
|
||||
struct mp_recorder_sink *rst = talloc(priv, struct mp_recorder_sink);
|
||||
*rst = (struct mp_recorder_sink) {
|
||||
.owner = priv,
|
||||
.sh = sh,
|
||||
.av_stream = avformat_new_stream(priv->mux, NULL),
|
||||
.max_out_pts = MP_NOPTS_VALUE,
|
||||
};
|
||||
|
||||
if (!rst->av_stream)
|
||||
return -1;
|
||||
|
||||
AVCodecParameters *avp = mp_codec_params_to_av(sh->codec);
|
||||
if (!avp)
|
||||
return -1;
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
// We don't know the delay, so make something up. If the format requires
|
||||
// DTS, the result will probably be broken. FFmpeg provides nothing better
|
||||
// yet (unless you demux with libavformat, which contains tons of hacks
|
||||
// that try to determine a PTS).
|
||||
if (!sh->codec->lav_codecpar)
|
||||
avp->video_delay = 16;
|
||||
#endif
|
||||
|
||||
if (avp->codec_id == AV_CODEC_ID_NONE)
|
||||
return -1;
|
||||
|
||||
if (avcodec_parameters_copy(rst->av_stream->codecpar, avp) < 0)
|
||||
return -1;
|
||||
|
||||
rst->av_stream->time_base = mp_get_codec_timebase(sh->codec);
|
||||
|
||||
MP_TARRAY_APPEND(priv, priv->streams, priv->num_streams, rst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct mp_recorder *mp_recorder_create(struct mpv_global *global,
|
||||
const char *target_file,
|
||||
struct sh_stream **streams,
|
||||
int num_streams)
|
||||
{
|
||||
struct mp_recorder *priv = talloc_zero(NULL, struct mp_recorder);
|
||||
|
||||
priv->global = global;
|
||||
priv->log = mp_log_new(priv, global->log, "recorder");
|
||||
|
||||
if (!num_streams) {
|
||||
MP_ERR(priv, "No streams.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
priv->mux = avformat_alloc_context();
|
||||
if (!priv->mux)
|
||||
goto error;
|
||||
|
||||
priv->mux->oformat = av_guess_format(NULL, target_file, NULL);
|
||||
if (!priv->mux->oformat) {
|
||||
MP_ERR(priv, "Output format not found.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (avio_open2(&priv->mux->pb, target_file, AVIO_FLAG_WRITE, NULL, NULL) < 0) {
|
||||
MP_ERR(priv, "Failed opening output file.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (int n = 0; n < num_streams; n++) {
|
||||
if (add_stream(priv, streams[n]) < 0) {
|
||||
MP_ERR(priv, "Can't mux one of the input streams.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
// Not sure how to write this in a "standard" way. It appears only mkv
|
||||
// and mp4 support this directly.
|
||||
char version[200];
|
||||
snprintf(version, sizeof(version), "%s experimental stream recording "
|
||||
"feature (can generate broken files - please report bugs)",
|
||||
mpv_version);
|
||||
av_dict_set(&priv->mux->metadata, "encoding_tool", version, 0);
|
||||
|
||||
if (avformat_write_header(priv->mux, NULL) < 0) {
|
||||
MP_ERR(priv, "Write header failed.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
priv->opened = true;
|
||||
priv->muxing_from_start = true;
|
||||
|
||||
priv->base_ts = MP_NOPTS_VALUE;
|
||||
priv->rebase_ts = 0;
|
||||
|
||||
MP_WARN(priv, "This is an experimental feature. Output files might be "
|
||||
"broken or not play correctly with various players "
|
||||
"(including mpv itself).\n");
|
||||
|
||||
return priv;
|
||||
|
||||
error:
|
||||
mp_recorder_destroy(priv);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void flush_packets(struct mp_recorder *priv)
|
||||
{
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
struct mp_recorder_sink *rst = priv->streams[n];
|
||||
for (int i = 0; i < rst->num_packets; i++)
|
||||
talloc_free(rst->packets[i]);
|
||||
rst->num_packets = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void mux_packet(struct mp_recorder_sink *rst,
|
||||
struct demux_packet *pkt)
|
||||
{
|
||||
struct mp_recorder *priv = rst->owner;
|
||||
struct demux_packet mpkt = *pkt;
|
||||
|
||||
double diff = priv->rebase_ts - priv->base_ts;
|
||||
mpkt.pts = PTS_ADD(mpkt.pts, diff);
|
||||
mpkt.dts = PTS_ADD(mpkt.dts, diff);
|
||||
|
||||
rst->max_out_pts = MPMAX(rst->max_out_pts, pkt->pts);
|
||||
|
||||
AVPacket avpkt;
|
||||
mp_set_av_packet(&avpkt, &mpkt, &rst->av_stream->time_base);
|
||||
|
||||
avpkt.stream_index = rst->av_stream->index;
|
||||
|
||||
if (avpkt.duration < 0 && rst->sh->type != STREAM_SUB)
|
||||
avpkt.duration = 0;
|
||||
|
||||
AVPacket *new_packet = av_packet_clone(&avpkt);
|
||||
if (!new_packet) {
|
||||
MP_ERR(priv, "Failed to allocate packet.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (av_interleaved_write_frame(priv->mux, new_packet) < 0)
|
||||
MP_ERR(priv, "Failed writing packet.\n");
|
||||
}
|
||||
|
||||
// Write all packets that currently can be written.
|
||||
static void mux_packets(struct mp_recorder_sink *rst, bool force)
|
||||
{
|
||||
struct mp_recorder *priv = rst->owner;
|
||||
if (!priv->muxing || !rst->num_packets)
|
||||
return;
|
||||
|
||||
int safe_count = 0;
|
||||
for (int n = 0; n < rst->num_packets; n++) {
|
||||
if (rst->packets[n]->keyframe)
|
||||
safe_count = n;
|
||||
}
|
||||
if (force)
|
||||
safe_count = rst->num_packets;
|
||||
|
||||
for (int n = 0; n < safe_count; n++) {
|
||||
mux_packet(rst, rst->packets[n]);
|
||||
talloc_free(rst->packets[n]);
|
||||
}
|
||||
|
||||
// Remove packets[0..safe_count]
|
||||
memmove(&rst->packets[0], &rst->packets[safe_count],
|
||||
(rst->num_packets - safe_count) * sizeof(rst->packets[0]));
|
||||
rst->num_packets -= safe_count;
|
||||
}
|
||||
|
||||
// If there was a discontinuity, check whether we can resume muxing (and from
|
||||
// where).
|
||||
static void check_restart(struct mp_recorder *priv)
|
||||
{
|
||||
if (priv->muxing)
|
||||
return;
|
||||
|
||||
double min_ts = INFINITY;
|
||||
double rebase_ts = 0;
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
struct mp_recorder_sink *rst = priv->streams[n];
|
||||
int min_packets = rst->sh->type == STREAM_VIDEO ? QUEUE_MIN_PACKETS : 1;
|
||||
|
||||
rebase_ts = MPMAX(rebase_ts, rst->max_out_pts);
|
||||
|
||||
if (rst->num_packets < min_packets) {
|
||||
if (!rst->proper_eof && rst->sh->type != STREAM_SUB)
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < min_packets; i++)
|
||||
min_ts = MPMIN(min_ts, rst->packets[i]->pts);
|
||||
}
|
||||
|
||||
// Subtitle only stream (wait longer) or stream without any PTS (fuck it).
|
||||
if (!isfinite(min_ts))
|
||||
return;
|
||||
|
||||
priv->rebase_ts = rebase_ts;
|
||||
priv->base_ts = min_ts;
|
||||
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
struct mp_recorder_sink *rst = priv->streams[n];
|
||||
rst->max_out_pts = min_ts;
|
||||
}
|
||||
|
||||
priv->muxing = true;
|
||||
|
||||
if (!priv->muxing_from_start)
|
||||
MP_WARN(priv, "Discontinuity at timestamp %f.\n", priv->rebase_ts);
|
||||
}
|
||||
|
||||
void mp_recorder_destroy(struct mp_recorder *priv)
|
||||
{
|
||||
if (priv->opened) {
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
struct mp_recorder_sink *rst = priv->streams[n];
|
||||
if (!rst->proper_eof)
|
||||
continue;
|
||||
mux_packets(rst, true);
|
||||
}
|
||||
|
||||
if (av_write_trailer(priv->mux) < 0)
|
||||
MP_ERR(priv, "Writing trailer failed.\n");
|
||||
}
|
||||
|
||||
if (priv->mux) {
|
||||
if (avio_closep(&priv->mux->pb) < 0)
|
||||
MP_ERR(priv, "Closing file failed\n");
|
||||
|
||||
avformat_free_context(priv->mux);
|
||||
}
|
||||
|
||||
flush_packets(priv);
|
||||
talloc_free(priv);
|
||||
}
|
||||
|
||||
// This is called on a seek, or when recording was started mid-stream.
|
||||
void mp_recorder_mark_discontinuity(struct mp_recorder *priv)
|
||||
{
|
||||
flush_packets(priv);
|
||||
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
struct mp_recorder_sink *rst = priv->streams[n];
|
||||
rst->discont = true;
|
||||
rst->proper_eof = false;
|
||||
}
|
||||
|
||||
priv->muxing = false;
|
||||
priv->muxing_from_start = false;
|
||||
}
|
||||
|
||||
// Get a stream for writing. The pointer is valid until mp_recorder is
|
||||
// destroyed. The stream is the index referencing the stream passed to
|
||||
// mp_recorder_create().
|
||||
struct mp_recorder_sink *mp_recorder_get_sink(struct mp_recorder *r, int stream)
|
||||
{
|
||||
assert(stream >= 0 && stream < r->num_streams);
|
||||
return r->streams[stream];
|
||||
}
|
||||
|
||||
// Pass a packet to the given stream. The function does not own the packet, but
|
||||
// can create a new reference to it if it needs to retain it. Can be NULL to
|
||||
// signal proper end of stream.
|
||||
void mp_recorder_feed_packet(struct mp_recorder_sink *rst,
|
||||
struct demux_packet *pkt)
|
||||
{
|
||||
struct mp_recorder *priv = rst->owner;
|
||||
|
||||
if (!pkt) {
|
||||
rst->proper_eof = true;
|
||||
check_restart(priv);
|
||||
mux_packets(rst, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkt->dts == MP_NOPTS_VALUE && !priv->dts_warning) {
|
||||
// No, FFmpeg has no actually usable helpers to generate correct DTS.
|
||||
// No, FFmpeg doesn't tell us which formats need DTS at all.
|
||||
// No, we can not shut up the FFmpeg warning, which will follow.
|
||||
MP_WARN(priv, "Source stream misses DTS on at least some packets!\n"
|
||||
"If the target file format requires DTS, the written\n"
|
||||
"file will be invalid.\n");
|
||||
priv->dts_warning = true;
|
||||
}
|
||||
|
||||
if (rst->discont && !pkt->keyframe)
|
||||
return;
|
||||
rst->discont = false;
|
||||
|
||||
if (rst->num_packets >= QUEUE_MAX_PACKETS) {
|
||||
MP_ERR(priv, "Stream %d has too many queued packets; dropping.\n",
|
||||
rst->av_stream->index);
|
||||
return;
|
||||
}
|
||||
|
||||
pkt = demux_copy_packet(pkt);
|
||||
if (!pkt)
|
||||
return;
|
||||
MP_TARRAY_APPEND(rst, rst->packets, rst->num_packets, pkt);
|
||||
|
||||
check_restart(priv);
|
||||
mux_packets(rst, false);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef MP_RECORDER_H_
|
||||
#define MP_RECORDER_H_
|
||||
|
||||
struct mp_recorder;
|
||||
struct mpv_global;
|
||||
struct demux_packet;
|
||||
struct sh_stream;
|
||||
struct mp_recorder_sink;
|
||||
|
||||
struct mp_recorder *mp_recorder_create(struct mpv_global *global,
|
||||
const char *target_file,
|
||||
struct sh_stream **streams,
|
||||
int num_streams);
|
||||
void mp_recorder_destroy(struct mp_recorder *r);
|
||||
void mp_recorder_mark_discontinuity(struct mp_recorder *r);
|
||||
|
||||
struct mp_recorder_sink *mp_recorder_get_sink(struct mp_recorder *r, int stream);
|
||||
void mp_recorder_feed_packet(struct mp_recorder_sink *s,
|
||||
struct demux_packet *pkt);
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -70,7 +70,7 @@ static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params)
|
||||
return CUE_EMPTY;
|
||||
for (int n = 0; cue_command_strings[n].command != -1; n++) {
|
||||
struct bstr name = bstr0(cue_command_strings[n].text);
|
||||
if (bstr_startswith(line, name)) {
|
||||
if (bstr_case_startswith(line, name)) {
|
||||
struct bstr rest = bstr_cut(line, name.len);
|
||||
if (rest.len && !strchr(WHITESPACE, rest.start[0]))
|
||||
continue;
|
||||
|
||||
+89
-78
@@ -89,6 +89,7 @@ struct demux_opts {
|
||||
double min_secs;
|
||||
int force_seekable;
|
||||
double min_secs_cache;
|
||||
int access_references;
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct demux_opts
|
||||
@@ -100,6 +101,7 @@ const struct m_sub_options demux_conf = {
|
||||
OPT_INTRANGE("demuxer-max-bytes", max_bytes, 0, 0, INT_MAX),
|
||||
OPT_FLAG("force-seekable", force_seekable, 0),
|
||||
OPT_DOUBLE("cache-secs", min_secs_cache, M_OPT_MIN, .min = 0),
|
||||
OPT_FLAG("access-references", access_references, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct demux_opts),
|
||||
@@ -108,6 +110,7 @@ const struct m_sub_options demux_conf = {
|
||||
.max_bytes = 400 * 1024 * 1024,
|
||||
.min_secs = 1.0,
|
||||
.min_secs_cache = 10.0,
|
||||
.access_references = 1,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -202,6 +205,9 @@ struct demux_stream {
|
||||
struct demux_packet *head;
|
||||
struct demux_packet *tail;
|
||||
|
||||
struct demux_packet *attached_picture;
|
||||
bool attached_picture_added;
|
||||
|
||||
// for closed captions (demuxer_feed_caption)
|
||||
struct sh_stream *cc;
|
||||
};
|
||||
@@ -240,6 +246,7 @@ static void ds_flush(struct demux_stream *ds)
|
||||
ds->last_pos = -1;
|
||||
ds->last_dts = MP_NOPTS_VALUE;
|
||||
ds->correct_dts = ds->correct_pos = true;
|
||||
ds->attached_picture_added = false;
|
||||
}
|
||||
|
||||
void demux_set_ts_offset(struct demuxer *demuxer, double offset)
|
||||
@@ -288,6 +295,8 @@ void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh)
|
||||
if (!sh->codec->codec)
|
||||
sh->codec->codec = "";
|
||||
|
||||
sh->ds->attached_picture = sh->attached_picture;
|
||||
|
||||
sh->index = in->num_streams;
|
||||
if (sh->ff_index < 0)
|
||||
sh->ff_index = sh->index;
|
||||
@@ -503,7 +512,7 @@ static double get_refresh_seek_pts(struct demux_internal *in)
|
||||
// Streams which didn't have any packets yet will return all packets,
|
||||
// other streams return packets only starting from the last position.
|
||||
if (ds->last_pos != -1 || ds->last_dts != MP_NOPTS_VALUE)
|
||||
ds->refreshing = true;
|
||||
ds->refreshing |= ds->selected;
|
||||
}
|
||||
|
||||
// Seek back to player's current position, with a small offset added.
|
||||
@@ -652,7 +661,9 @@ static bool read_packet(struct demux_internal *in)
|
||||
demux->desc->seek(demux, seek_pts, SEEK_BACKWARD | SEEK_HR);
|
||||
}
|
||||
|
||||
bool eof = !demux->desc->fill_buffer || demux->desc->fill_buffer(demux) <= 0;
|
||||
bool eof = true;
|
||||
if (demux->desc->fill_buffer && !demux_cancel_test(demux))
|
||||
eof = demux->desc->fill_buffer(demux) <= 0;
|
||||
update_cache(in);
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
@@ -674,27 +685,6 @@ static bool read_packet(struct demux_internal *in)
|
||||
return true;
|
||||
}
|
||||
|
||||
// must be called locked; may temporarily unlock
|
||||
static void ds_get_packets(struct demux_stream *ds)
|
||||
{
|
||||
const char *t = stream_type_name(ds->type);
|
||||
struct demux_internal *in = ds->in;
|
||||
MP_DBG(in, "reading packet for %s\n", t);
|
||||
in->eof = false; // force retry
|
||||
ds->eof = false;
|
||||
while (ds->selected && !ds->head && !ds->eof) {
|
||||
ds->active = true;
|
||||
// Note: the following code marks EOF if it can't continue
|
||||
if (in->threading) {
|
||||
MP_VERBOSE(in, "waiting for demux thread (%s)\n", t);
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
} else {
|
||||
read_packet(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void execute_trackswitch(struct demux_internal *in)
|
||||
{
|
||||
in->tracks_switched = false;
|
||||
@@ -773,6 +763,13 @@ static void *demux_thread(void *pctx)
|
||||
|
||||
static struct demux_packet *dequeue_packet(struct demux_stream *ds)
|
||||
{
|
||||
if (ds->attached_picture) {
|
||||
ds->eof = true;
|
||||
if (ds->attached_picture_added)
|
||||
return NULL;
|
||||
ds->attached_picture_added = true;
|
||||
return demux_copy_packet(ds->attached_picture);
|
||||
}
|
||||
if (!ds->head)
|
||||
return NULL;
|
||||
struct demux_packet *pkt = ds->head;
|
||||
@@ -816,16 +813,23 @@ static struct demux_packet *dequeue_packet(struct demux_stream *ds)
|
||||
return pkt;
|
||||
}
|
||||
|
||||
// Whether to avoid actively demuxing new packets to find a new packet on the
|
||||
// given stream.
|
||||
// Attached pictures (cover art) should never actively read.
|
||||
// Sparse packets (Subtitles) interleaved with other non-sparse packets (video,
|
||||
// audio) should never be read actively, meaning the demuxer thread does not
|
||||
// try to exceed default readahead in order to find a new packet.
|
||||
static bool use_lazy_subtitle_reading(struct demux_stream *ds)
|
||||
static bool use_lazy_packet_reading(struct demux_stream *ds)
|
||||
{
|
||||
if (ds->attached_picture)
|
||||
return true;
|
||||
if (ds->type != STREAM_SUB)
|
||||
return false;
|
||||
// Subtitles are only lazily read if there's at least 1 other actively read
|
||||
// stream.
|
||||
for (int n = 0; n < ds->in->num_streams; n++) {
|
||||
struct demux_stream *s = ds->in->streams[n]->ds;
|
||||
if (s->type != STREAM_SUB && s->selected && !s->eof)
|
||||
if (s->type != STREAM_SUB && s->selected && !s->eof && !s->attached_picture)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -839,12 +843,29 @@ struct demux_packet *demux_read_packet(struct sh_stream *sh)
|
||||
struct demux_stream *ds = sh ? sh->ds : NULL;
|
||||
struct demux_packet *pkt = NULL;
|
||||
if (ds) {
|
||||
pthread_mutex_lock(&ds->in->lock);
|
||||
if (!use_lazy_subtitle_reading(ds))
|
||||
ds_get_packets(ds);
|
||||
struct demux_internal *in = ds->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
if (!use_lazy_packet_reading(ds)) {
|
||||
const char *t = stream_type_name(ds->type);
|
||||
MP_DBG(in, "reading packet for %s\n", t);
|
||||
in->eof = false; // force retry
|
||||
while (ds->selected && !ds->head) {
|
||||
ds->active = true;
|
||||
// Note: the following code marks EOF if it can't continue
|
||||
if (in->threading) {
|
||||
MP_VERBOSE(in, "waiting for demux thread (%s)\n", t);
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
} else {
|
||||
read_packet(in);
|
||||
}
|
||||
if (ds->eof)
|
||||
break;
|
||||
}
|
||||
}
|
||||
pkt = dequeue_packet(ds);
|
||||
pthread_cond_signal(&ds->in->wakeup); // possibly read more
|
||||
pthread_mutex_unlock(&ds->in->lock);
|
||||
pthread_cond_signal(&in->wakeup); // possibly read more
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
return pkt;
|
||||
}
|
||||
@@ -870,7 +891,7 @@ int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt)
|
||||
if (ds->in->threading) {
|
||||
pthread_mutex_lock(&ds->in->lock);
|
||||
*out_pkt = dequeue_packet(ds);
|
||||
if (use_lazy_subtitle_reading(ds)) {
|
||||
if (use_lazy_packet_reading(ds)) {
|
||||
r = *out_pkt ? 1 : -1;
|
||||
} else {
|
||||
r = *out_pkt ? 1 : ((ds->eof || !ds->selected) ? -1 : 0);
|
||||
@@ -1212,6 +1233,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
return NULL;
|
||||
|
||||
struct demuxer *demuxer = talloc_ptrtype(NULL, demuxer);
|
||||
struct demux_opts *opts = mp_get_config_group(demuxer, global, &demux_conf);
|
||||
*demuxer = (struct demuxer) {
|
||||
.desc = desc,
|
||||
.stream = stream,
|
||||
@@ -1222,15 +1244,13 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
.glog = log,
|
||||
.filename = talloc_strdup(demuxer, stream->url),
|
||||
.is_network = stream->is_network,
|
||||
.access_references = opts->access_references,
|
||||
.events = DEMUX_EVENT_ALL,
|
||||
};
|
||||
demuxer->seekable = stream->seekable;
|
||||
if (demuxer->stream->uncached_stream &&
|
||||
!demuxer->stream->uncached_stream->seekable)
|
||||
if (demuxer->stream->underlying && !demuxer->stream->underlying->seekable)
|
||||
demuxer->seekable = false;
|
||||
|
||||
struct demux_opts *opts = mp_get_config_group(demuxer, global, &demux_conf);
|
||||
|
||||
struct demux_internal *in = demuxer->in = talloc_ptrtype(demuxer, in);
|
||||
*in = (struct demux_internal){
|
||||
.log = demuxer->log,
|
||||
@@ -1245,9 +1265,6 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
pthread_mutex_init(&in->lock, NULL);
|
||||
pthread_cond_init(&in->wakeup, NULL);
|
||||
|
||||
if (stream->uncached_stream)
|
||||
in->min_secs = MPMAX(in->min_secs, opts->min_secs_cache);
|
||||
|
||||
*in->d_thread = *demuxer;
|
||||
*in->d_buffer = *demuxer;
|
||||
|
||||
@@ -1286,18 +1303,25 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
demux_init_cache(demuxer);
|
||||
demux_changed(in->d_thread, DEMUX_EVENT_ALL);
|
||||
demux_update(demuxer);
|
||||
stream_control(demuxer->stream, STREAM_CTRL_SET_READAHEAD, &(int){false});
|
||||
struct timeline *tl = timeline_load(global, log, demuxer);
|
||||
if (tl) {
|
||||
struct demuxer_params params2 = {0};
|
||||
params2.timeline = tl;
|
||||
struct demuxer *sub = open_given_type(global, log,
|
||||
&demuxer_desc_timeline, stream,
|
||||
¶ms2, DEMUX_CHECK_FORCE);
|
||||
if (sub)
|
||||
return sub;
|
||||
timeline_destroy(tl);
|
||||
stream_control(demuxer->stream, STREAM_CTRL_SET_READAHEAD,
|
||||
&(int){params ? params->initial_readahead : false});
|
||||
if (!(params && params->disable_timeline)) {
|
||||
struct timeline *tl = timeline_load(global, log, demuxer);
|
||||
if (tl) {
|
||||
struct demuxer_params params2 = {0};
|
||||
params2.timeline = tl;
|
||||
struct demuxer *sub =
|
||||
open_given_type(global, log, &demuxer_desc_timeline, stream,
|
||||
¶ms2, DEMUX_CHECK_FORCE);
|
||||
if (sub) {
|
||||
demuxer = sub;
|
||||
} else {
|
||||
timeline_destroy(tl);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (demuxer->is_network || stream->caching)
|
||||
in->min_secs = MPMAX(in->min_secs, opts->min_secs_cache);
|
||||
return demuxer;
|
||||
}
|
||||
|
||||
@@ -1376,12 +1400,6 @@ struct demuxer *demux_open_url(const char *url,
|
||||
cancel, global);
|
||||
if (!s)
|
||||
return NULL;
|
||||
if (params->allow_capture) {
|
||||
char *f;
|
||||
mp_read_option_raw(global, "stream-capture", &m_option_type_string, &f);
|
||||
stream_set_capture_file(s, f);
|
||||
talloc_free(f);
|
||||
}
|
||||
if (!params->disable_cache)
|
||||
stream_enable_cache_defaults(&s);
|
||||
struct demuxer *d = demux_open(s, params, global);
|
||||
@@ -1660,7 +1678,7 @@ static int cached_demux_control(struct demux_internal *in, int cmd, void *arg)
|
||||
int num_packets = 0;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
if (ds->active) {
|
||||
if (ds->active && !(!ds->head && ds->eof)) {
|
||||
r->underrun |= !ds->head && !ds->eof;
|
||||
r->ts_range[0] = MP_PTS_MAX(r->ts_range[0], ds->base_ts);
|
||||
r->ts_range[1] = MP_PTS_MIN(r->ts_range[1], ds->last_ts);
|
||||
@@ -1718,6 +1736,7 @@ static void thread_demux_control(void *p)
|
||||
int demux_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
assert(demuxer == in->d_user);
|
||||
|
||||
if (in->threading) {
|
||||
pthread_mutex_lock(&in->lock);
|
||||
@@ -1729,7 +1748,20 @@ int demux_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
|
||||
int r = 0;
|
||||
struct demux_control_args args = {demuxer, cmd, arg, &r};
|
||||
demux_run_on_thread(demuxer, thread_demux_control, &args);
|
||||
if (in->threading) {
|
||||
MP_VERBOSE(in, "blocking on demuxer thread\n");
|
||||
pthread_mutex_lock(&in->lock);
|
||||
while (in->run_fn)
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
in->run_fn = thread_demux_control;
|
||||
in->run_fn_arg = &args;
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
while (in->run_fn)
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
} else {
|
||||
thread_demux_control(&args);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -1741,27 +1773,6 @@ int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg)
|
||||
return c.res;
|
||||
}
|
||||
|
||||
void demux_run_on_thread(struct demuxer *demuxer, void (*fn)(void *), void *ctx)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
assert(demuxer == in->d_user);
|
||||
|
||||
if (in->threading) {
|
||||
MP_VERBOSE(in, "blocking on demuxer thread\n");
|
||||
pthread_mutex_lock(&in->lock);
|
||||
while (in->run_fn)
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
in->run_fn = fn;
|
||||
in->run_fn_arg = ctx;
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
while (in->run_fn)
|
||||
pthread_cond_wait(&in->wakeup, &in->lock);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
} else {
|
||||
fn(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
bool demux_cancel_test(struct demuxer *demuxer)
|
||||
{
|
||||
return mp_cancel_test(demuxer->stream->cancel);
|
||||
|
||||
+5
-3
@@ -161,9 +161,12 @@ struct demuxer_params {
|
||||
int matroska_wanted_segment;
|
||||
bool *matroska_was_valid;
|
||||
struct timeline *timeline;
|
||||
bool disable_timeline;
|
||||
bool initial_readahead;
|
||||
bstr init_fragment;
|
||||
bool skip_lavf_probing;
|
||||
// -- demux_open_url() only
|
||||
int stream_flags;
|
||||
bool allow_capture;
|
||||
bool disable_cache;
|
||||
// result
|
||||
bool demuxer_failed;
|
||||
@@ -185,6 +188,7 @@ typedef struct demuxer {
|
||||
// Typical examples: text subtitles, playlists
|
||||
bool fully_read;
|
||||
bool is_network; // opened directly from a network stream
|
||||
bool access_references; // allow opening other files/URLs
|
||||
|
||||
// Bitmask of DEMUX_EVENT_*
|
||||
int events;
|
||||
@@ -285,8 +289,6 @@ double demuxer_get_time_length(struct demuxer *demuxer);
|
||||
|
||||
int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg);
|
||||
|
||||
void demux_run_on_thread(struct demuxer *demuxer, void (*fn)(void *), void *ctx);
|
||||
|
||||
void demux_changed(demuxer_t *demuxer, int events);
|
||||
void demux_update(demuxer_t *demuxer);
|
||||
|
||||
|
||||
@@ -253,6 +253,9 @@ out:
|
||||
|
||||
static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
if (!demuxer->access_references)
|
||||
return -1;
|
||||
|
||||
struct stream *s = demuxer->stream;
|
||||
if (check >= DEMUX_CHECK_UNSAFE) {
|
||||
bstr d = stream_peek(s, PROBE_SIZE);
|
||||
|
||||
+22
-6
@@ -43,6 +43,8 @@ struct priv {
|
||||
double base_dts; // packet DTS that maps to base_time
|
||||
double last_dts; // DTS of previously demuxed packet
|
||||
bool seek_reinit; // needs reinit after seek
|
||||
|
||||
bool is_dvd, is_cdda;
|
||||
};
|
||||
|
||||
// If the timestamp difference between subsequent packets is this big, assume
|
||||
@@ -65,7 +67,7 @@ static void reselect_streams(demuxer_t *demuxer)
|
||||
static void get_disc_lang(struct stream *stream, struct sh_stream *sh)
|
||||
{
|
||||
struct stream_lang_req req = {.type = sh->type, .id = sh->demuxer_id};
|
||||
if (stream->uncached_type == STREAMTYPE_DVD && sh->type == STREAM_SUB)
|
||||
if (sh->type == STREAM_SUB)
|
||||
req.id = req.id & 0x1F; // mpeg ID to index
|
||||
stream_control(stream, STREAM_CTRL_GET_LANG, &req);
|
||||
if (req.name[0])
|
||||
@@ -76,7 +78,7 @@ static void add_dvd_streams(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
struct stream *stream = demuxer->stream;
|
||||
if (stream->uncached_type != STREAMTYPE_DVD)
|
||||
if (!p->is_dvd)
|
||||
return;
|
||||
struct stream_dvd_info_req info;
|
||||
if (stream_control(stream, STREAM_CTRL_GET_DVD_INFO, &info) > 0) {
|
||||
@@ -162,7 +164,7 @@ static void d_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) {
|
||||
if (p->is_cdda) {
|
||||
demux_seek(p->slave, seek_pts, flags);
|
||||
return;
|
||||
}
|
||||
@@ -222,7 +224,7 @@ static int d_fill_buffer(demuxer_t *demuxer)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) {
|
||||
if (p->is_cdda) {
|
||||
demux_add_packet(sh, pkt);
|
||||
return 1;
|
||||
}
|
||||
@@ -285,7 +287,21 @@ static int d_open(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
struct demuxer_params params = {.force_format = "+lavf"};
|
||||
|
||||
if (demuxer->stream->uncached_type == STREAMTYPE_CDDA)
|
||||
struct stream *cur = demuxer->stream;
|
||||
const char *sname = "";
|
||||
while (cur) {
|
||||
if (cur->info)
|
||||
sname = cur->info->name;
|
||||
cur = cur->underlying; // down the caching chain
|
||||
}
|
||||
|
||||
p->is_cdda = strcmp(sname, "cdda") == 0;
|
||||
p->is_dvd = strcmp(sname, "dvd") == 0 ||
|
||||
strcmp(sname, "ifo") == 0 ||
|
||||
strcmp(sname, "dvdnav") == 0 ||
|
||||
strcmp(sname, "ifo_dvdnav") == 0;
|
||||
|
||||
if (p->is_cdda)
|
||||
params.force_format = "+rawaudio";
|
||||
|
||||
char *t = NULL;
|
||||
@@ -311,7 +327,7 @@ static int d_open(demuxer_t *demuxer, enum demux_check check)
|
||||
// (actually libavformat/mpegts.c) to seek sometimes when reading a packet.
|
||||
// It does this to seek back a bit in case the current file position points
|
||||
// into the middle of a packet.
|
||||
if (demuxer->stream->uncached_type != STREAMTYPE_CDDA) {
|
||||
if (!p->is_cdda) {
|
||||
demuxer->stream->seekable = false;
|
||||
|
||||
// Can be seekable even if the stream isn't.
|
||||
|
||||
+118
-39
@@ -44,12 +44,15 @@ struct tl_part {
|
||||
};
|
||||
|
||||
struct tl_parts {
|
||||
bool dash;
|
||||
char *init_fragment_url;
|
||||
struct tl_part *parts;
|
||||
int num_parts;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
bstr data;
|
||||
bool allow_any;
|
||||
};
|
||||
|
||||
// Parse a time (absolute file time or duration). Currently equivalent to a
|
||||
@@ -64,6 +67,8 @@ static bool parse_time(bstr str, double *out_time)
|
||||
return true;
|
||||
}
|
||||
|
||||
#define MAX_PARAMS 10
|
||||
|
||||
/* Returns a list of parts, or NULL on parse error.
|
||||
* Syntax (without file header or URI prefix):
|
||||
* url ::= <entry> ( (';' | '\n') <entry> )*
|
||||
@@ -78,7 +83,10 @@ static struct tl_parts *parse_edl(bstr str)
|
||||
bstr_split_tok(str, "\n", &(bstr){0}, &str);
|
||||
if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";"))
|
||||
continue;
|
||||
bool is_header = bstr_eatstart0(&str, "!");
|
||||
struct tl_part p = { .length = -1 };
|
||||
bstr param_names[MAX_PARAMS];
|
||||
bstr param_vals[MAX_PARAMS];
|
||||
int nparam = 0;
|
||||
while (1) {
|
||||
bstr name, val;
|
||||
@@ -116,10 +124,25 @@ static struct tl_parts *parse_edl(bstr str)
|
||||
if (bstr_equals0(val, "chapters"))
|
||||
p.chapter_ts = true;
|
||||
}
|
||||
if (nparam >= MAX_PARAMS)
|
||||
goto error;
|
||||
param_names[nparam] = name;
|
||||
param_vals[nparam] = val;
|
||||
nparam++;
|
||||
if (!bstr_eatstart0(&str, ","))
|
||||
break;
|
||||
}
|
||||
if (is_header) {
|
||||
if (tl->num_parts)
|
||||
goto error; // can't have header once an entry was defined
|
||||
bstr type = param_vals[0]; // value, because no "="
|
||||
if (bstr_equals0(type, "mp4_dash")) {
|
||||
tl->dash = true;
|
||||
if (bstr_equals0(param_names[1], "init"))
|
||||
tl->init_fragment_url = bstrto0(tl, param_vals[1]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!p.filename)
|
||||
goto error;
|
||||
MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p);
|
||||
@@ -139,7 +162,10 @@ static struct demuxer *open_source(struct timeline *tl, char *filename)
|
||||
if (strcmp(d->stream->url, filename) == 0)
|
||||
return d;
|
||||
}
|
||||
struct demuxer *d = demux_open_url(filename, NULL, tl->cancel, tl->global);
|
||||
struct demuxer_params params = {
|
||||
.init_fragment = tl->init_fragment,
|
||||
};
|
||||
struct demuxer *d = demux_open_url(filename, ¶ms, tl->cancel, tl->global);
|
||||
if (d) {
|
||||
MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d);
|
||||
} else {
|
||||
@@ -202,61 +228,110 @@ static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer)
|
||||
|
||||
static void build_timeline(struct timeline *tl, struct tl_parts *parts)
|
||||
{
|
||||
tl->track_layout = NULL;
|
||||
tl->dash = parts->dash;
|
||||
|
||||
if (parts->init_fragment_url && parts->init_fragment_url[0]) {
|
||||
MP_VERBOSE(tl, "Opening init fragment...\n");
|
||||
stream_t *s = stream_create(parts->init_fragment_url, STREAM_READ,
|
||||
tl->cancel, tl->global);
|
||||
if (s)
|
||||
tl->init_fragment = stream_read_complete(s, tl, 1000000);
|
||||
free_stream(s);
|
||||
if (!tl->init_fragment.len) {
|
||||
MP_ERR(tl, "Could not read init fragment.\n");
|
||||
goto error;
|
||||
}
|
||||
s = open_memory_stream(tl->init_fragment.start, tl->init_fragment.len);
|
||||
tl->track_layout = demux_open(s, NULL, tl->global);
|
||||
if (!tl->track_layout) {
|
||||
free_stream(s);
|
||||
MP_ERR(tl, "Could not demux init fragment.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
tl->parts = talloc_array_ptrtype(tl, tl->parts, parts->num_parts + 1);
|
||||
double starttime = 0;
|
||||
for (int n = 0; n < parts->num_parts; n++) {
|
||||
struct tl_part *part = &parts->parts[n];
|
||||
struct demuxer *source = open_source(tl, part->filename);
|
||||
if (!source)
|
||||
goto error;
|
||||
struct demuxer *source = NULL;
|
||||
|
||||
resolve_timestamps(part, source);
|
||||
if (tl->dash) {
|
||||
part->offset = starttime;
|
||||
if (part->length <= 0)
|
||||
MP_WARN(tl, "Segment %d has unknown duration.\n", n);
|
||||
if (part->offset_set)
|
||||
MP_WARN(tl, "Offsets are ignored.\n");
|
||||
tl->demuxer->is_network = true;
|
||||
|
||||
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) {
|
||||
if (end_time < 0) {
|
||||
MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n",
|
||||
part->filename);
|
||||
end_time = 1;
|
||||
if (!tl->track_layout) {
|
||||
source = open_source(tl, part->filename);
|
||||
if (!source)
|
||||
goto error;
|
||||
}
|
||||
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, end_part, end_time);
|
||||
} else {
|
||||
MP_VERBOSE(tl, "Opening segment %d...\n", n);
|
||||
|
||||
source = open_source(tl, part->filename);
|
||||
if (!source)
|
||||
goto error;
|
||||
|
||||
resolve_timestamps(part, source);
|
||||
|
||||
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) {
|
||||
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, end_part, end_time);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a chapter between each file.
|
||||
struct demux_chapter ch = {
|
||||
.pts = starttime,
|
||||
.metadata = talloc_zero(tl, struct mp_tags),
|
||||
};
|
||||
mp_tags_set_str(ch.metadata, "title", part->filename);
|
||||
MP_TARRAY_APPEND(tl, tl->chapters, tl->num_chapters, ch);
|
||||
|
||||
// Also copy the source file's chapters for the relevant parts
|
||||
copy_chapters(&tl->chapters, &tl->num_chapters, source, part->offset,
|
||||
part->length, starttime);
|
||||
}
|
||||
|
||||
// Add a chapter between each file.
|
||||
struct demux_chapter ch = {
|
||||
.pts = starttime,
|
||||
.metadata = talloc_zero(tl, struct mp_tags),
|
||||
};
|
||||
mp_tags_set_str(ch.metadata, "title", part->filename);
|
||||
MP_TARRAY_APPEND(tl, tl->chapters, tl->num_chapters, ch);
|
||||
|
||||
// Also copy the source file's chapters for the relevant parts
|
||||
copy_chapters(&tl->chapters, &tl->num_chapters, source, part->offset,
|
||||
part->length, starttime);
|
||||
|
||||
tl->parts[n] = (struct timeline_part) {
|
||||
.start = starttime,
|
||||
.source_start = part->offset,
|
||||
.source = source,
|
||||
.url = talloc_strdup(tl, part->filename),
|
||||
};
|
||||
|
||||
starttime += part->length;
|
||||
|
||||
if (source) {
|
||||
tl->demuxer->is_network |= source->is_network;
|
||||
|
||||
if (!tl->track_layout)
|
||||
tl->track_layout = source;
|
||||
}
|
||||
}
|
||||
tl->parts[parts->num_parts] = (struct timeline_part) {.start = starttime};
|
||||
tl->num_parts = parts->num_parts;
|
||||
tl->track_layout = tl->parts[0].source;
|
||||
return;
|
||||
|
||||
error:
|
||||
@@ -286,8 +361,7 @@ static void build_mpv_edl_timeline(struct timeline *tl)
|
||||
return;
|
||||
}
|
||||
MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, tl->demuxer);
|
||||
// Source is .edl and not edl:// => don't allow arbitrary paths
|
||||
if (tl->demuxer->stream->uncached_type != STREAMTYPE_EDL)
|
||||
if (!p->allow_any)
|
||||
fix_filenames(parts, tl->demuxer->filename);
|
||||
build_timeline(tl, parts);
|
||||
talloc_free(parts);
|
||||
@@ -295,13 +369,18 @@ static void build_mpv_edl_timeline(struct timeline *tl)
|
||||
|
||||
static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
if (!demuxer->access_references)
|
||||
return -1;
|
||||
|
||||
struct priv *p = talloc_zero(demuxer, struct priv);
|
||||
demuxer->priv = p;
|
||||
demuxer->fully_read = true;
|
||||
|
||||
struct stream *s = demuxer->stream;
|
||||
if (s->uncached_type == STREAMTYPE_EDL) {
|
||||
if (s->info && strcmp(s->info->name, "edl") == 0) {
|
||||
p->data = bstr0(s->path);
|
||||
// Source is edl:// and not .edl => allow arbitrary paths
|
||||
p->allow_any = true;
|
||||
return 0;
|
||||
}
|
||||
if (check >= DEMUX_CHECK_UNSAFE) {
|
||||
|
||||
+60
-39
@@ -22,6 +22,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "config.h"
|
||||
@@ -167,6 +168,8 @@ static const struct format_hack format_hacks[] = {
|
||||
BLACKLIST("bin"),
|
||||
// Useless, does not work with custom streams.
|
||||
BLACKLIST("image2"),
|
||||
// Probably a security risk.
|
||||
BLACKLIST("ffm"),
|
||||
// Image demuxers ("<name>_pipe" is detected explicitly)
|
||||
{"image2pipe", .image_format = true},
|
||||
{0}
|
||||
@@ -180,6 +183,8 @@ typedef struct lavf_priv {
|
||||
AVInputFormat *avif;
|
||||
int avif_flags;
|
||||
AVFormatContext *avfc;
|
||||
bstr init_fragment;
|
||||
int64_t stream_pos;
|
||||
AVIOContext *pb;
|
||||
struct sh_stream **streams; // NULL for unknown streams
|
||||
int num_streams;
|
||||
@@ -215,7 +220,14 @@ static int mp_read(void *opaque, uint8_t *buf, int size)
|
||||
struct stream *stream = priv->stream;
|
||||
int ret;
|
||||
|
||||
ret = stream_read(stream, buf, size);
|
||||
if (priv->stream_pos < priv->init_fragment.len) {
|
||||
ret = MPMIN(size, priv->init_fragment.len - priv->stream_pos);
|
||||
memcpy(buf, priv->init_fragment.start + priv->stream_pos, ret);
|
||||
priv->stream_pos += ret;
|
||||
} else {
|
||||
ret = stream_read(stream, buf, size);
|
||||
priv->stream_pos = priv->init_fragment.len + stream_tell(stream);
|
||||
}
|
||||
|
||||
MP_TRACE(demuxer, "%d=mp_read(%p, %p, %d), pos: %"PRId64", eof:%d\n",
|
||||
ret, stream, buf, size, stream_tell(stream), stream->eof);
|
||||
@@ -227,32 +239,44 @@ static int64_t mp_seek(void *opaque, int64_t pos, int whence)
|
||||
struct demuxer *demuxer = opaque;
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
struct stream *stream = priv->stream;
|
||||
int64_t current_pos;
|
||||
|
||||
MP_TRACE(demuxer, "mp_seek(%p, %"PRId64", %s)\n", stream, pos,
|
||||
whence == SEEK_END ? "end" :
|
||||
whence == SEEK_CUR ? "cur" :
|
||||
whence == SEEK_SET ? "set" : "size");
|
||||
if (whence == SEEK_END || whence == AVSEEK_SIZE) {
|
||||
int64_t end = stream_get_size(stream);
|
||||
int64_t end = stream_get_size(stream) + priv->init_fragment.len;
|
||||
if (end < 0)
|
||||
return -1;
|
||||
if (whence == AVSEEK_SIZE)
|
||||
return end;
|
||||
pos += end;
|
||||
} else if (whence == SEEK_CUR) {
|
||||
pos += stream_tell(stream);
|
||||
pos += priv->stream_pos;
|
||||
} else if (whence != SEEK_SET) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pos < 0)
|
||||
return -1;
|
||||
current_pos = stream_tell(stream);
|
||||
if (stream_seek(stream, pos) == 0) {
|
||||
|
||||
int64_t stream_target = pos - priv->init_fragment.len;
|
||||
bool seek_before = stream_target < 0;
|
||||
if (seek_before)
|
||||
stream_target = 0; // within init segment - seek real stream to 0
|
||||
|
||||
int64_t current_pos = stream_tell(stream);
|
||||
if (stream_seek(stream, stream_target) == 0) {
|
||||
stream_seek(stream, current_pos);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (seek_before) {
|
||||
priv->stream_pos = pos;
|
||||
} else {
|
||||
priv->stream_pos = priv->init_fragment.len + stream_tell(stream);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
@@ -335,7 +359,7 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
priv->filename = remove_prefix(s->url, prefixes);
|
||||
|
||||
char *avdevice_format = NULL;
|
||||
if (s->uncached_type == STREAMTYPE_AVDEVICE) {
|
||||
if (s->info && strcmp(s->info->name, "avdevice") == 0) {
|
||||
// always require filename in the form "format:filename"
|
||||
char *sep = strchr(priv->filename, ':');
|
||||
if (!sep) {
|
||||
@@ -373,7 +397,7 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
// Disable file-extension matching with normal checks
|
||||
.filename = check <= DEMUX_CHECK_REQUEST ? priv->filename : "",
|
||||
.buf_size = 0,
|
||||
.buf = av_mallocz(PROBE_BUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE),
|
||||
.buf = av_mallocz(PROBE_BUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE),
|
||||
};
|
||||
if (!avpd.buf)
|
||||
return -1;
|
||||
@@ -574,13 +598,8 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
AVFormatContext *avfc = priv->avfc;
|
||||
AVStream *st = avfc->streams[i];
|
||||
struct sh_stream *sh = NULL;
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
AVCodecParameters *codec = st->codecpar;
|
||||
int lavc_delay = codec->initial_padding;
|
||||
#else
|
||||
AVCodecContext *codec = st->codec;
|
||||
int lavc_delay = codec->delay;
|
||||
#endif
|
||||
|
||||
switch (codec->codec_type) {
|
||||
case AVMEDIA_TYPE_AUDIO: {
|
||||
@@ -676,17 +695,9 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
sh->ff_index = st->index;
|
||||
sh->codec->codec = mp_codec_from_av_codec_id(codec->codec_id);
|
||||
sh->codec->codec_tag = codec->codec_tag;
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
sh->codec->lav_codecpar = avcodec_parameters_alloc();
|
||||
if (sh->codec->lav_codecpar)
|
||||
avcodec_parameters_copy(sh->codec->lav_codecpar, codec);
|
||||
#else
|
||||
sh->codec->codec = mp_codec_from_av_codec_id(codec->codec_id);
|
||||
sh->codec->codec_tag = codec->codec_tag;
|
||||
sh->codec->lav_headers = avcodec_alloc_context3(NULL);
|
||||
if (sh->codec->lav_headers)
|
||||
mp_copy_lav_codec_headers(sh->codec->lav_headers, codec);
|
||||
#endif
|
||||
sh->codec->native_tb_num = st->time_base.num;
|
||||
sh->codec->native_tb_den = st->time_base.den;
|
||||
|
||||
@@ -699,6 +710,10 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
AVDictionaryEntry *title = av_dict_get(st->metadata, "title", NULL, 0);
|
||||
if (title && title->value)
|
||||
sh->title = talloc_strdup(sh, title->value);
|
||||
if (!sh->title && st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED)
|
||||
sh->title = talloc_asprintf(sh, "visual impaired");
|
||||
if (!sh->title && st->disposition & AV_DISPOSITION_HEARING_IMPAIRED)
|
||||
sh->title = talloc_asprintf(sh, "hearing impaired");
|
||||
AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0);
|
||||
if (lang && lang->value)
|
||||
sh->lang = talloc_strdup(sh, lang->value);
|
||||
@@ -748,6 +763,14 @@ static int interrupt_cb(void *ctx)
|
||||
return mp_cancel_test(priv->stream->cancel);
|
||||
}
|
||||
|
||||
static int block_io_open(struct AVFormatContext *s, AVIOContext **pb,
|
||||
const char *url, int flags, AVDictionary **options)
|
||||
{
|
||||
struct demuxer *demuxer = s->opaque;
|
||||
MP_ERR(demuxer, "Not opening '%s' due to --access-references=no.\n", url);
|
||||
return AVERROR(EACCES);
|
||||
}
|
||||
|
||||
static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
AVFormatContext *avfc;
|
||||
@@ -769,6 +792,9 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
if (lavf_check_file(demuxer, check) < 0)
|
||||
return -1;
|
||||
|
||||
if (demuxer->params)
|
||||
priv->init_fragment = bstrdup(priv, demuxer->params->init_fragment);
|
||||
|
||||
avfc = avformat_alloc_context();
|
||||
if (!avfc)
|
||||
return -1;
|
||||
@@ -806,10 +832,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
AVDictionary *dopts = NULL;
|
||||
|
||||
if ((priv->avif_flags & AVFMT_NOFILE) ||
|
||||
priv->stream->type == STREAMTYPE_AVDEVICE ||
|
||||
priv->format_hack.no_stream)
|
||||
{
|
||||
if ((priv->avif_flags & AVFMT_NOFILE) || priv->format_hack.no_stream) {
|
||||
mp_setup_av_network_options(&dopts, demuxer->global, demuxer->log);
|
||||
// This might be incorrect.
|
||||
demuxer->seekable = true;
|
||||
@@ -849,6 +872,10 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
.opaque = demuxer,
|
||||
};
|
||||
|
||||
avfc->opaque = demuxer;
|
||||
if (!demuxer->access_references)
|
||||
avfc->io_open = block_io_open;
|
||||
|
||||
mp_set_avdict(&dopts, lavfdopts->avopts);
|
||||
|
||||
if (avformat_open_input(&avfc, priv->filename, priv->avif, &dopts) < 0) {
|
||||
@@ -861,9 +888,12 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
av_dict_free(&dopts);
|
||||
|
||||
priv->avfc = avfc;
|
||||
if (avformat_find_stream_info(avfc, NULL) < 0) {
|
||||
MP_ERR(demuxer, "av_find_stream_info() failed\n");
|
||||
return -1;
|
||||
|
||||
if (!demuxer->params || !demuxer->params->skip_lavf_probing) {
|
||||
if (avformat_find_stream_info(avfc, NULL) < 0) {
|
||||
MP_ERR(demuxer, "av_find_stream_info() failed\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
MP_VERBOSE(demuxer, "avformat_find_stream_info() finished after %"PRId64
|
||||
@@ -932,14 +962,9 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
if (pkt->dts != AV_NOPTS_VALUE)
|
||||
dp->dts = pkt->dts * av_q2d(st->time_base);
|
||||
dp->duration = pkt->duration * av_q2d(st->time_base);
|
||||
#if !HAVE_AV_AVPACKET_INT64_DURATION
|
||||
if (pkt->convergence_duration > 0)
|
||||
dp->duration = pkt->convergence_duration * av_q2d(st->time_base);
|
||||
#endif
|
||||
dp->pos = pkt->pos;
|
||||
dp->keyframe = pkt->flags & AV_PKT_FLAG_KEY;
|
||||
#if LIBAVFORMAT_VERSION_MICRO >= 100 && \
|
||||
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 50, 100)
|
||||
#if LIBAVFORMAT_VERSION_MICRO >= 100
|
||||
if (pkt->flags & AV_PKT_FLAG_DISCARD)
|
||||
MP_ERR(demux, "Edit lists are not correctly supported (FFmpeg issue).\n");
|
||||
#endif
|
||||
@@ -1120,12 +1145,8 @@ 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]) {
|
||||
avcodec_free_context(&priv->streams[n]->codec->lav_headers);
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
if (priv->streams[n])
|
||||
avcodec_parameters_free(&priv->streams[n]->codec->lav_codecpar);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (priv->own_stream)
|
||||
free_stream(priv->stream);
|
||||
|
||||
@@ -32,6 +32,9 @@ static int cmp_filename(const void *a, const void *b)
|
||||
|
||||
static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
if (!demuxer->access_references)
|
||||
return -1;
|
||||
|
||||
int flags = 0;
|
||||
int probe_size = STREAM_BUFFER_SIZE;
|
||||
if (check <= DEMUX_CHECK_REQUEST) {
|
||||
|
||||
+3
-2
@@ -294,9 +294,10 @@ static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
|
||||
mf_t *mf;
|
||||
|
||||
if (strncmp(demuxer->stream->url, "mf://", 5) == 0 &&
|
||||
demuxer->stream->type == STREAMTYPE_MF)
|
||||
demuxer->stream->info && strcmp(demuxer->stream->info->name, "mf") == 0)
|
||||
{
|
||||
mf = open_mf_pattern(demuxer, demuxer->log, demuxer->stream->url + 5);
|
||||
else {
|
||||
} else {
|
||||
mf = open_mf_single(demuxer, demuxer->log, demuxer->stream->url);
|
||||
int bog = 0;
|
||||
MP_TARRAY_APPEND(mf, mf->streams, bog, demuxer->stream);
|
||||
|
||||
+90
-5
@@ -107,6 +107,7 @@ typedef struct mkv_track {
|
||||
double v_frate;
|
||||
uint32_t colorspace;
|
||||
int stereo_mode;
|
||||
struct mp_colorspace color;
|
||||
|
||||
uint32_t a_channels, a_bps;
|
||||
float a_sfreq;
|
||||
@@ -127,6 +128,8 @@ typedef struct mkv_track {
|
||||
AVCodecParserContext *av_parser;
|
||||
AVCodecContext *av_parser_codec;
|
||||
|
||||
bool require_keyframes;
|
||||
|
||||
/* stuff for realaudio braincancer */
|
||||
double ra_pts; /* previous audio timestamp */
|
||||
uint32_t sub_packet_size; ///< sub packet size, per stream
|
||||
@@ -159,6 +162,7 @@ struct block_info {
|
||||
bstr data;
|
||||
void *alloc;
|
||||
int64_t filepos;
|
||||
struct ebml_block_additions *additions;
|
||||
};
|
||||
|
||||
typedef struct mkv_demuxer {
|
||||
@@ -199,7 +203,7 @@ typedef struct mkv_demuxer {
|
||||
|
||||
bool index_has_durations;
|
||||
|
||||
bool eof_warning;
|
||||
bool eof_warning, keyframe_warning;
|
||||
|
||||
struct block_info tmp_block;
|
||||
} mkv_demuxer_t;
|
||||
@@ -536,6 +540,42 @@ static void parse_trackaudio(struct demuxer *demuxer, struct mkv_track *track,
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_trackcolour(struct demuxer *demuxer, struct mkv_track *track,
|
||||
struct ebml_colour *colour)
|
||||
{
|
||||
// Note: As per matroska spec, the order is consistent with ISO/IEC
|
||||
// 23001-8:2013/DCOR1, which is the same order used by libavutil/pixfmt.h,
|
||||
// so we can just re-use our avcol_ conversion functions.
|
||||
if (colour->n_matrix_coefficients) {
|
||||
track->color.space = avcol_spc_to_mp_csp(colour->matrix_coefficients);
|
||||
MP_VERBOSE(demuxer, "| + Matrix: %s\n",
|
||||
m_opt_choice_str(mp_csp_names, track->color.space));
|
||||
}
|
||||
if (colour->n_primaries) {
|
||||
track->color.primaries = avcol_pri_to_mp_csp_prim(colour->primaries);
|
||||
MP_VERBOSE(demuxer, "| + Primaries: %s\n",
|
||||
m_opt_choice_str(mp_csp_prim_names, track->color.primaries));
|
||||
}
|
||||
if (colour->n_transfer_characteristics) {
|
||||
track->color.gamma = avcol_trc_to_mp_csp_trc(colour->transfer_characteristics);
|
||||
MP_VERBOSE(demuxer, "| + Gamma: %s\n",
|
||||
m_opt_choice_str(mp_csp_trc_names, track->color.gamma));
|
||||
}
|
||||
if (colour->n_range) {
|
||||
track->color.levels = avcol_range_to_mp_csp_levels(colour->range);
|
||||
MP_VERBOSE(demuxer, "| + Levels: %s\n",
|
||||
m_opt_choice_str(mp_csp_levels_names, track->color.levels));
|
||||
}
|
||||
if (colour->n_mastering_metadata) {
|
||||
struct ebml_mastering_metadata *mastering = &colour->mastering_metadata;
|
||||
|
||||
if (mastering->n_luminance_max) {
|
||||
track->color.sig_peak = mastering->luminance_max;
|
||||
MP_VERBOSE(demuxer, "| + HDR peak: %f\n", track->color.sig_peak);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track,
|
||||
struct ebml_video *video)
|
||||
{
|
||||
@@ -575,6 +615,8 @@ static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track,
|
||||
MP_WARN(demuxer, "Unknown StereoMode: %d\n", (int)video->stereo_mode);
|
||||
}
|
||||
}
|
||||
if (video->n_colour)
|
||||
parse_trackcolour(demuxer, track, &video->colour);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -862,6 +904,7 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
|
||||
if (wanted_edition_uid) {
|
||||
MP_ERR(demuxer, "Unable to find expected edition uid: %"PRIu64"\n",
|
||||
wanted_edition_uid);
|
||||
talloc_free(parse_ctx.talloc_ctx);
|
||||
return -1;
|
||||
} else {
|
||||
selected_edition = 0;
|
||||
@@ -1396,6 +1439,7 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
sh_v->par_h = p.p_h;
|
||||
|
||||
sh_v->stereo_mode = track->stereo_mode;
|
||||
sh_v->color = track->color;
|
||||
|
||||
done:
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
@@ -1470,7 +1514,7 @@ static void parse_flac_chmap(struct mp_chmap *channels, unsigned char *data,
|
||||
}
|
||||
|
||||
static const char *const mkv_audio_tags[][2] = {
|
||||
{ "A_MPEG/L2", "mp3" },
|
||||
{ "A_MPEG/L2", "mp2" },
|
||||
{ "A_MPEG/L3", "mp3" },
|
||||
{ "A_AC3", "ac3" },
|
||||
{ "A_EAC3", "eac3" },
|
||||
@@ -1660,7 +1704,9 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
mp_chmap_set_unknown(&sh_a->channels, track->a_channels);
|
||||
|
||||
const char *codec = sh_a->codec;
|
||||
if (!strcmp(codec, "mp3") || !strcmp(codec, "truehd")) {
|
||||
if (!strcmp(codec, "mp2") || !strcmp(codec, "mp3") ||
|
||||
!strcmp(codec, "truehd"))
|
||||
{
|
||||
track->parse = true;
|
||||
} else if (!strcmp(codec, "flac")) {
|
||||
unsigned char *ptr = extradata;
|
||||
@@ -1709,6 +1755,11 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
if (sh_a->samplerate == 8000 && strcmp(codec, "ac3") == 0)
|
||||
track->default_duration = 0;
|
||||
|
||||
// Deal with some FFmpeg-produced garbage, and assume all audio codecs can
|
||||
// start decoding from anywhere.
|
||||
if (strcmp(codec, "truehd") != 0)
|
||||
track->require_keyframes = true;
|
||||
|
||||
sh_a->extradata = extradata;
|
||||
sh_a->extradata_size = extradata_len;
|
||||
|
||||
@@ -2342,6 +2393,8 @@ static void free_block(struct block_info *block)
|
||||
free(block->alloc);
|
||||
block->alloc = NULL;
|
||||
block->data = (bstr){0};
|
||||
talloc_free(block->additions);
|
||||
block->additions = NULL;
|
||||
}
|
||||
|
||||
static void index_block(demuxer_t *demuxer, struct block_info *block)
|
||||
@@ -2431,6 +2484,15 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
|
||||
|
||||
current_pts = tc / 1e9 - track->codec_delay;
|
||||
|
||||
if (track->require_keyframes && !keyframe) {
|
||||
keyframe = true;
|
||||
if (!mkv_d->keyframe_warning) {
|
||||
MP_WARN(demuxer, "This is a broken file! Packets with incorrect "
|
||||
"keyframe flag found. Enabling workaround.\n");
|
||||
mkv_d->keyframe_warning = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (track->type == MATROSKA_TRACK_AUDIO) {
|
||||
if (mkv_d->a_skip_to_keyframe)
|
||||
use_this_block &= keyframe;
|
||||
@@ -2493,6 +2555,15 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
|
||||
block_info->discardpadding / 1e9 * srate);
|
||||
mkv_d->a_skip_preroll = 0;
|
||||
}
|
||||
if (block_info->additions) {
|
||||
for (int n = 0; n < block_info->additions->n_block_more; n++) {
|
||||
struct ebml_block_more *add =
|
||||
&block_info->additions->block_more[n];
|
||||
int64_t id = add->n_block_add_id ? add->block_add_id : 1;
|
||||
demux_packet_add_blockadditional(dp, id,
|
||||
add->block_additional.start, add->block_additional.len);
|
||||
}
|
||||
}
|
||||
|
||||
mkv_parse_and_add_packet(demuxer, track, dp);
|
||||
talloc_free_children(track->parser_tmp);
|
||||
@@ -2545,8 +2616,22 @@ static int read_block_group(demuxer_t *demuxer, int64_t end,
|
||||
int64_t num = ebml_read_int(s);
|
||||
if (num == EBML_INT_INVALID)
|
||||
goto error;
|
||||
if (num)
|
||||
block->keyframe = false;
|
||||
block->keyframe = false;
|
||||
break;
|
||||
|
||||
case MATROSKA_ID_BLOCKADDITIONS:;
|
||||
struct ebml_block_additions additions = {0};
|
||||
struct ebml_parse_ctx parse_ctx = {demuxer->log};
|
||||
if (ebml_read_element(s, &parse_ctx, &additions,
|
||||
&ebml_block_additions_desc) < 0)
|
||||
return -1;
|
||||
if (additions.n_block_more > 0) {
|
||||
block->additions =
|
||||
talloc_memdup(NULL, &additions, sizeof(additions));
|
||||
talloc_steal(block->additions, parse_ctx.talloc_ctx);
|
||||
parse_ctx.talloc_ctx = NULL;
|
||||
}
|
||||
talloc_free(parse_ctx.talloc_ctx);
|
||||
break;
|
||||
|
||||
case MATROSKA_ID_CLUSTER:
|
||||
|
||||
+20
-15
@@ -170,6 +170,7 @@ static bool check_file_seg(struct tl_ctx *ctx, char *filename, int segment)
|
||||
.matroska_wanted_uids = ctx->uids,
|
||||
.matroska_wanted_segment = segment,
|
||||
.matroska_was_valid = &was_valid,
|
||||
.disable_timeline = true,
|
||||
.disable_cache = true,
|
||||
};
|
||||
struct mp_cancel *cancel = ctx->tl->cancel;
|
||||
@@ -187,27 +188,31 @@ static bool check_file_seg(struct tl_ctx *ctx, char *filename, int segment)
|
||||
if (ctx->sources[i])
|
||||
continue;
|
||||
/* Accept the source if the segment uid matches and the edition
|
||||
* either matches or isn't specified. */
|
||||
* either matches or isn't specified. */
|
||||
if (!memcmp(uid->segment, m->uid.segment, 16) &&
|
||||
(!uid->edition || uid->edition == m->uid.edition))
|
||||
{
|
||||
MP_INFO(ctx, "Match for source %d: %s\n", i, d->filename);
|
||||
|
||||
for (int j = 0; j < m->num_ordered_chapters; j++) {
|
||||
struct matroska_chapter *c = m->ordered_chapters + j;
|
||||
if (!uid->edition) {
|
||||
m->uid.edition = 0;
|
||||
} else {
|
||||
for (int j = 0; j < m->num_ordered_chapters; j++) {
|
||||
struct matroska_chapter *c = m->ordered_chapters + j;
|
||||
|
||||
if (!c->has_segment_uid)
|
||||
continue;
|
||||
if (!c->has_segment_uid)
|
||||
continue;
|
||||
|
||||
if (has_source_request(ctx, &c->uid))
|
||||
continue;
|
||||
if (has_source_request(ctx, &c->uid))
|
||||
continue;
|
||||
|
||||
/* Set the requested segment. */
|
||||
MP_TARRAY_GROW(NULL, ctx->uids, ctx->num_sources);
|
||||
ctx->uids[ctx->num_sources] = c->uid;
|
||||
/* Set the requested segment. */
|
||||
MP_TARRAY_GROW(NULL, ctx->uids, ctx->num_sources);
|
||||
ctx->uids[ctx->num_sources] = c->uid;
|
||||
|
||||
/* Add a new source slot. */
|
||||
MP_TARRAY_APPEND(NULL, ctx->sources, ctx->num_sources, NULL);
|
||||
/* Add a new source slot. */
|
||||
MP_TARRAY_APPEND(NULL, ctx->sources, ctx->num_sources, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (stream_wants_cache(d->stream, ctx->opts->stream_cache)) {
|
||||
@@ -262,7 +267,7 @@ static void find_ordered_chapter_sources(struct tl_ctx *ctx)
|
||||
talloc_steal(tmp, pl);
|
||||
for (struct playlist_entry *e = pl ? pl->first : NULL; e; e = e->next)
|
||||
MP_TARRAY_APPEND(tmp, filenames, num_filenames, e->filename);
|
||||
} else if (ctx->demuxer->stream->uncached_type != STREAMTYPE_FILE) {
|
||||
} else if (!ctx->demuxer->stream->is_local_file) {
|
||||
MP_WARN(ctx, "Playback source is not a "
|
||||
"normal disk file. Will not search for related files.\n");
|
||||
} else {
|
||||
@@ -378,7 +383,7 @@ static void build_timeline_loop(struct tl_ctx *ctx,
|
||||
|
||||
/* If we're the source or it's a non-ordered edition reference,
|
||||
* just add a timeline part from the source. */
|
||||
if (current_source == j || !linked_m->num_ordered_chapters) {
|
||||
if (current_source == j || !linked_m->uid.edition) {
|
||||
uint64_t source_full_length =
|
||||
demuxer_get_time_length(linked_source) * 1e9;
|
||||
uint64_t source_length = source_full_length - c->start;
|
||||
@@ -514,7 +519,7 @@ void build_ordered_chapter_timeline(struct timeline *tl)
|
||||
.opts = mp_get_config_group(ctx, tl->global, NULL),
|
||||
};
|
||||
|
||||
if (!ctx->opts->ordered_chapters) {
|
||||
if (!ctx->opts->ordered_chapters || !demuxer->access_references) {
|
||||
MP_INFO(demuxer, "File uses ordered chapters, but "
|
||||
"you have disabled support for them. Ignoring.\n");
|
||||
talloc_free(ctx);
|
||||
|
||||
@@ -277,12 +277,14 @@ static int cmp_filename(const void *a, const void *b)
|
||||
|
||||
static int parse_dir(struct pl_parser *p)
|
||||
{
|
||||
if (p->real_stream->type != STREAMTYPE_DIR)
|
||||
if (!p->real_stream->is_directory)
|
||||
return -1;
|
||||
if (p->probing)
|
||||
return 0;
|
||||
|
||||
char *path = mp_file_get_path(p, bstr0(p->real_stream->url));
|
||||
if (!path)
|
||||
return -1;
|
||||
|
||||
char **files = NULL;
|
||||
int num_files = 0;
|
||||
@@ -339,6 +341,9 @@ static const struct pl_format *probe_pl(struct pl_parser *p)
|
||||
|
||||
static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
if (!demuxer->access_references)
|
||||
return -1;
|
||||
|
||||
bool force = check < DEMUX_CHECK_UNSAFE || check == DEMUX_CHECK_REQUEST;
|
||||
|
||||
struct pl_parser *p = talloc_zero(NULL, struct pl_parser);
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
|
||||
static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
if (!demuxer->access_references)
|
||||
return -1;
|
||||
|
||||
if (RarProbe(demuxer->stream))
|
||||
return -1;
|
||||
|
||||
|
||||
+123
-61
@@ -24,11 +24,14 @@
|
||||
#include "demux.h"
|
||||
#include "timeline.h"
|
||||
#include "stheader.h"
|
||||
#include "stream/stream.h"
|
||||
|
||||
struct segment {
|
||||
int index;
|
||||
double start, end;
|
||||
double d_start;
|
||||
char *url;
|
||||
bool lazy;
|
||||
struct demuxer *d;
|
||||
// stream_map[sh_stream.index] = index into priv.streams, where sh_stream
|
||||
// is a stream from the source d. It's used to map the streams of the
|
||||
@@ -51,6 +54,7 @@ struct priv {
|
||||
struct timeline *tl;
|
||||
|
||||
double duration;
|
||||
bool dash;
|
||||
|
||||
struct segment **segments;
|
||||
int num_segments;
|
||||
@@ -65,6 +69,50 @@ struct priv {
|
||||
int eos_packets;
|
||||
};
|
||||
|
||||
static bool target_stream_used(struct segment *seg, int target_index)
|
||||
{
|
||||
for (int n = 0; n < seg->num_stream_map; n++) {
|
||||
if (seg->stream_map[n] == target_index)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create mapping from segment streams to virtual timeline streams.
|
||||
static void associate_streams(struct demuxer *demuxer, struct segment *seg)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
if (!seg->d || seg->stream_map)
|
||||
return;
|
||||
|
||||
int counts[STREAM_TYPE_COUNT] = {0};
|
||||
|
||||
int num_streams = demux_get_num_stream(seg->d);
|
||||
for (int n = 0; n < num_streams; n++) {
|
||||
struct sh_stream *sh = demux_get_stream(seg->d, n);
|
||||
// Try associating by demuxer ID (supposedly useful for ordered chapters).
|
||||
struct sh_stream *other =
|
||||
demuxer_stream_by_demuxer_id(demuxer, sh->type, sh->demuxer_id);
|
||||
if (!other || !target_stream_used(seg, other->index)) {
|
||||
// Try to associate the first unused stream with matching media type.
|
||||
for (int i = 0; i < p->num_streams; i++) {
|
||||
struct sh_stream *cur = p->streams[i].sh;
|
||||
if (cur->type == sh->type && !target_stream_used(seg, cur->index))
|
||||
{
|
||||
other = cur;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MP_TARRAY_APPEND(seg, seg->stream_map, seg->num_stream_map,
|
||||
other ? other->index : -1);
|
||||
|
||||
counts[sh->type] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void reselect_streams(struct demuxer *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
@@ -77,6 +125,9 @@ static void reselect_streams(struct demuxer *demuxer)
|
||||
for (int n = 0; n < p->num_segments; n++) {
|
||||
struct segment *seg = p->segments[n];
|
||||
for (int i = 0; i < seg->num_stream_map; i++) {
|
||||
if (!seg->d)
|
||||
continue;
|
||||
|
||||
struct sh_stream *sh = demux_get_stream(seg->d, i);
|
||||
bool selected = false;
|
||||
if (seg->stream_map[i] >= 0)
|
||||
@@ -89,11 +140,44 @@ static void reselect_streams(struct demuxer *demuxer)
|
||||
}
|
||||
}
|
||||
|
||||
static void close_lazy_segments(struct demuxer *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
// unload previous segment
|
||||
for (int n = 0; n < p->num_segments; n++) {
|
||||
struct segment *seg = p->segments[n];
|
||||
if (seg != p->current && seg->d && seg->lazy) {
|
||||
free_demuxer_and_stream(seg->d);
|
||||
seg->d = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void reopen_lazy_segments(struct demuxer *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
if (p->current->d)
|
||||
return;
|
||||
|
||||
close_lazy_segments(demuxer);
|
||||
|
||||
struct demuxer_params params = {
|
||||
.init_fragment = p->tl->init_fragment,
|
||||
.skip_lavf_probing = true,
|
||||
};
|
||||
p->current->d = demux_open_url(p->current->url, ¶ms,
|
||||
demuxer->stream->cancel, demuxer->global);
|
||||
if (!p->current->d && !demux_cancel_test(demuxer))
|
||||
MP_ERR(demuxer, "failed to load segment\n");
|
||||
associate_streams(demuxer, p->current);
|
||||
}
|
||||
|
||||
static void switch_segment(struct demuxer *demuxer, struct segment *new,
|
||||
double start_pts, int flags)
|
||||
double start_pts, int flags, bool init)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
bool new_segment = p->current != new;
|
||||
|
||||
if (!(flags & (SEEK_FORWARD | SEEK_BACKWARD)))
|
||||
flags |= SEEK_BACKWARD | SEEK_HR;
|
||||
@@ -101,14 +185,18 @@ static void switch_segment(struct demuxer *demuxer, struct segment *new,
|
||||
MP_VERBOSE(demuxer, "switch to segment %d\n", new->index);
|
||||
|
||||
p->current = new;
|
||||
reopen_lazy_segments(demuxer);
|
||||
if (!new->d)
|
||||
return;
|
||||
reselect_streams(demuxer);
|
||||
demux_set_ts_offset(new->d, new->start - new->d_start);
|
||||
demux_seek(new->d, start_pts, flags);
|
||||
if (!p->dash)
|
||||
demux_set_ts_offset(new->d, new->start - new->d_start);
|
||||
if (!p->dash || !init)
|
||||
demux_seek(new->d, start_pts, flags);
|
||||
|
||||
for (int n = 0; n < p->num_streams; n++) {
|
||||
struct virtual_stream *vs = &p->streams[n];
|
||||
if (new_segment)
|
||||
vs->new_segment = true;
|
||||
vs->new_segment = true;
|
||||
vs->eos_packets = 0;
|
||||
}
|
||||
|
||||
@@ -131,7 +219,7 @@ static void d_seek(struct demuxer *demuxer, double seek_pts, int flags)
|
||||
}
|
||||
}
|
||||
|
||||
switch_segment(demuxer, new, pts, flags);
|
||||
switch_segment(demuxer, new, pts, flags, false);
|
||||
}
|
||||
|
||||
static int d_fill_buffer(struct demuxer *demuxer)
|
||||
@@ -139,9 +227,11 @@ static int d_fill_buffer(struct demuxer *demuxer)
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
if (!p->current)
|
||||
switch_segment(demuxer, p->segments[0], 0, 0);
|
||||
switch_segment(demuxer, p->segments[0], 0, 0, true);
|
||||
|
||||
struct segment *seg = p->current;
|
||||
if (!seg || !seg->d)
|
||||
return 0;
|
||||
|
||||
struct demux_packet *pkt = demux_read_any_packet(seg->d);
|
||||
if (!pkt || pkt->pts >= seg->end)
|
||||
@@ -178,20 +268,21 @@ static int d_fill_buffer(struct demuxer *demuxer)
|
||||
}
|
||||
if (!next)
|
||||
return 0;
|
||||
switch_segment(demuxer, next, next->start, 0);
|
||||
switch_segment(demuxer, next, next->start, 0, true);
|
||||
return 1; // reader will retry
|
||||
}
|
||||
|
||||
if (pkt->stream < 0 || pkt->stream > seg->num_stream_map)
|
||||
goto drop;
|
||||
|
||||
if (!pkt->codec)
|
||||
pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
|
||||
|
||||
if (pkt->start == MP_NOPTS_VALUE || pkt->start < seg->start)
|
||||
pkt->start = seg->start;
|
||||
if (pkt->end == MP_NOPTS_VALUE || pkt->end > seg->end)
|
||||
pkt->end = seg->end;
|
||||
if (!p->dash) {
|
||||
if (!pkt->codec)
|
||||
pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
|
||||
if (pkt->start == MP_NOPTS_VALUE || pkt->start < seg->start)
|
||||
pkt->start = seg->start;
|
||||
if (pkt->end == MP_NOPTS_VALUE || pkt->end > seg->end)
|
||||
pkt->end = seg->end;
|
||||
}
|
||||
|
||||
pkt->stream = seg->stream_map[pkt->stream];
|
||||
if (pkt->stream < 0)
|
||||
@@ -216,7 +307,8 @@ static int d_fill_buffer(struct demuxer *demuxer)
|
||||
}
|
||||
}
|
||||
|
||||
pkt->new_segment |= vs->new_segment;
|
||||
if (!p->dash)
|
||||
pkt->new_segment |= vs->new_segment;
|
||||
vs->new_segment = false;
|
||||
|
||||
demux_add_packet(vs->sh, pkt);
|
||||
@@ -234,9 +326,9 @@ static void print_timeline(struct demuxer *demuxer)
|
||||
MP_VERBOSE(demuxer, "Timeline segments:\n");
|
||||
for (int n = 0; n < p->num_segments; n++) {
|
||||
struct segment *seg = p->segments[n];
|
||||
int src_num = -1;
|
||||
for (int i = 0; i < p->tl->num_sources; i++) {
|
||||
if (p->tl->sources[i] == seg->d) {
|
||||
int src_num = n;
|
||||
for (int i = 0; i < n - 1; i++) {
|
||||
if (seg->d && p->segments[i]->d == seg->d) {
|
||||
src_num = i;
|
||||
break;
|
||||
}
|
||||
@@ -244,50 +336,12 @@ static void print_timeline(struct demuxer *demuxer)
|
||||
MP_VERBOSE(demuxer, " %2d: %12f [%12f] (", n, seg->start, seg->d_start);
|
||||
for (int i = 0; i < seg->num_stream_map; i++)
|
||||
MP_VERBOSE(demuxer, "%s%d", i ? " " : "", seg->stream_map[i]);
|
||||
MP_VERBOSE(demuxer, ") %d:'%s'\n", src_num, seg->d->filename);
|
||||
MP_VERBOSE(demuxer, ") %d:'%s'\n", src_num, seg->url);
|
||||
}
|
||||
MP_VERBOSE(demuxer, "Total duration: %f\n", p->duration);
|
||||
}
|
||||
|
||||
static bool target_stream_used(struct segment *seg, int target_index)
|
||||
{
|
||||
for (int n = 0; n < seg->num_stream_map; n++) {
|
||||
if (seg->stream_map[n] == target_index)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create mapping from segment streams to virtual timeline streams.
|
||||
static void associate_streams(struct demuxer *demuxer, struct segment *seg)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
int counts[STREAM_TYPE_COUNT] = {0};
|
||||
|
||||
int num_streams = demux_get_num_stream(seg->d);
|
||||
for (int n = 0; n < num_streams; n++) {
|
||||
struct sh_stream *sh = demux_get_stream(seg->d, n);
|
||||
// Try associating by demuxer ID (supposedly useful for ordered chapters).
|
||||
struct sh_stream *other =
|
||||
demuxer_stream_by_demuxer_id(demuxer, sh->type, sh->demuxer_id);
|
||||
if (!other || !target_stream_used(seg, other->index)) {
|
||||
// Try to associate the first unused stream with matching media type.
|
||||
for (int i = 0; i < p->num_streams; i++) {
|
||||
struct sh_stream *cur = p->streams[i].sh;
|
||||
if (cur->type == sh->type && !target_stream_used(seg, cur->index))
|
||||
{
|
||||
other = cur;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MP_TARRAY_APPEND(seg, seg->stream_map, seg->num_stream_map,
|
||||
other ? other->index : -1);
|
||||
|
||||
counts[sh->type] += 1;
|
||||
}
|
||||
if (p->dash)
|
||||
MP_VERBOSE(demuxer, "Durations and offsets are non-authoritative.\n");
|
||||
}
|
||||
|
||||
static int d_open(struct demuxer *demuxer, enum demux_check check)
|
||||
@@ -336,6 +390,8 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
|
||||
struct segment *seg = talloc_ptrtype(p, seg);
|
||||
*seg = (struct segment){
|
||||
.d = part->source,
|
||||
.url = part->source ? part->source->filename : part->url,
|
||||
.lazy = !part->source,
|
||||
.d_start = part->source_start,
|
||||
.start = part->start,
|
||||
.end = next->start,
|
||||
@@ -347,6 +403,8 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
|
||||
MP_TARRAY_APPEND(p, p->segments, p->num_segments, seg);
|
||||
}
|
||||
|
||||
p->dash = p->tl->dash;
|
||||
|
||||
print_timeline(demuxer);
|
||||
|
||||
demuxer->seekable = true;
|
||||
@@ -354,6 +412,8 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
|
||||
|
||||
demuxer->filetype = meta->filetype ? meta->filetype : meta->desc->name;
|
||||
|
||||
demuxer->is_network = p->tl->demuxer->is_network;
|
||||
|
||||
reselect_streams(demuxer);
|
||||
|
||||
return 0;
|
||||
@@ -363,6 +423,8 @@ static void d_close(struct demuxer *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
struct demuxer *master = p->tl->demuxer;
|
||||
p->current = NULL;
|
||||
close_lazy_segments(demuxer);
|
||||
timeline_destroy(p->tl);
|
||||
free_demuxer(master);
|
||||
}
|
||||
|
||||
+5
-2
@@ -22,7 +22,10 @@ static int demux_open_tv(demuxer_t *demuxer, enum demux_check check)
|
||||
tvi_handle_t *tvh;
|
||||
const tvi_functions_t *funcs;
|
||||
|
||||
if (check > DEMUX_CHECK_REQUEST || demuxer->stream->type != STREAMTYPE_TV)
|
||||
if (check > DEMUX_CHECK_REQUEST)
|
||||
return -1;
|
||||
|
||||
if (!demuxer->stream->info || strcmp(demuxer->stream->info->name, "tv") != 0)
|
||||
return -1;
|
||||
|
||||
tv_param_t *params = mp_get_config_group(demuxer, demuxer->global,
|
||||
@@ -31,7 +34,7 @@ static int demux_open_tv(demuxer_t *demuxer, enum demux_check check)
|
||||
bstr channel, input;
|
||||
bstr_split_tok(urlparams, "/", &channel, &input);
|
||||
if (channel.len) {
|
||||
talloc_free(params->channels);
|
||||
talloc_free(params->channel);
|
||||
params->channel = bstrto0(NULL, channel);
|
||||
}
|
||||
if (input.len) {
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ int64_t ebml_read_vlen_int(bstr *buffer)
|
||||
return EBML_INT_INVALID;
|
||||
l = len - buffer->len;
|
||||
|
||||
return unum - ((1 << ((7 * l) - 1)) - 1);
|
||||
return unum - ((1LL << ((7 * l) - 1)) - 1);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+21
-3
@@ -94,7 +94,7 @@ void demux_packet_shorten(struct demux_packet *dp, size_t len)
|
||||
{
|
||||
assert(len <= dp->len);
|
||||
dp->len = len;
|
||||
memset(dp->buffer + dp->len, 0, FF_INPUT_BUFFER_PADDING_SIZE);
|
||||
memset(dp->buffer + dp->len, 0, AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
}
|
||||
|
||||
void free_demux_packet(struct demux_packet *dp)
|
||||
@@ -132,8 +132,8 @@ struct demux_packet *demux_copy_packet(struct demux_packet *dp)
|
||||
|
||||
int demux_packet_set_padding(struct demux_packet *dp, int start, int end)
|
||||
{
|
||||
#if HAVE_AVFRAME_SKIP_SAMPLES
|
||||
if (!start && !end)
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
if (!start && !end)
|
||||
return 0;
|
||||
if (!dp->avpacket)
|
||||
return -1;
|
||||
@@ -146,3 +146,21 @@ int demux_packet_set_padding(struct demux_packet *dp, int start, int end)
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int demux_packet_add_blockadditional(struct demux_packet *dp, uint64_t id,
|
||||
void *data, size_t size)
|
||||
{
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
if (!dp->avpacket)
|
||||
return -1;
|
||||
uint8_t *sd = av_packet_new_side_data(dp->avpacket,
|
||||
AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL,
|
||||
8 + size);
|
||||
if (!sd)
|
||||
return -1;
|
||||
AV_WB64(sd, id);
|
||||
if (size > 0)
|
||||
memcpy(sd + 8, data, size);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
@@ -55,5 +55,7 @@ struct demux_packet *demux_copy_packet(struct demux_packet *dp);
|
||||
void demux_packet_copy_attribs(struct demux_packet *dst, struct demux_packet *src);
|
||||
|
||||
int demux_packet_set_padding(struct demux_packet *dp, int start, int end);
|
||||
int demux_packet_add_blockadditional(struct demux_packet *dp, uint64_t id,
|
||||
void *data, size_t size);
|
||||
|
||||
#endif /* MPLAYER_DEMUX_PACKET_H */
|
||||
+2
-2
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "common/common.h"
|
||||
#include "audio/chmap.h"
|
||||
#include "video/csputils.h"
|
||||
|
||||
struct MPOpts;
|
||||
struct demuxer;
|
||||
@@ -70,8 +71,6 @@ struct mp_codec_params {
|
||||
int extradata_size;
|
||||
|
||||
// Codec specific header data (set by demux_lavf.c only)
|
||||
// Which one is in use depends on HAVE_AVCODEC_HAS_CODECPAR.
|
||||
struct AVCodecContext *lav_headers;
|
||||
struct AVCodecParameters *lav_codecpar;
|
||||
|
||||
// Timestamp granularity for converting double<->rational timestamps.
|
||||
@@ -93,6 +92,7 @@ struct mp_codec_params {
|
||||
int disp_w, disp_h; // display size
|
||||
int rotate; // intended display rotation, in degrees, [0, 359]
|
||||
int stereo_mode; // mp_stereo3d_mode (0 if none/unknown)
|
||||
struct mp_colorspace color; // colorspace info where available
|
||||
|
||||
// STREAM_VIDEO + STREAM_AUDIO
|
||||
int bits_per_coded_sample;
|
||||
|
||||
+3
-1
@@ -33,8 +33,10 @@ void timeline_destroy(struct timeline *tl)
|
||||
return;
|
||||
for (int n = 0; n < tl->num_sources; n++) {
|
||||
struct demuxer *d = tl->sources[n];
|
||||
if (d != tl->demuxer)
|
||||
if (d != tl->demuxer && d != tl->track_layout)
|
||||
free_demuxer_and_stream(d);
|
||||
}
|
||||
if (tl->track_layout && tl->track_layout != tl->demuxer)
|
||||
free_demuxer_and_stream(tl->track_layout);
|
||||
talloc_free(tl);
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
struct timeline_part {
|
||||
double start;
|
||||
double source_start;
|
||||
char *url;
|
||||
struct demuxer *source;
|
||||
};
|
||||
|
||||
@@ -15,6 +16,9 @@ struct timeline {
|
||||
// main source
|
||||
struct demuxer *demuxer;
|
||||
|
||||
bstr init_fragment;
|
||||
bool dash;
|
||||
|
||||
// All referenced files. The source file must be at sources[0].
|
||||
struct demuxer **sources;
|
||||
int num_sources;
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@
|
||||
#CLOSE_WIN {encode} quit 4
|
||||
#E cycle edition # next edition
|
||||
#l ab-loop # Set/clear A-B loop points
|
||||
#L cycle-values loop "inf" "no" # toggle infinite looping
|
||||
#L cycle-values loop-file "inf" "no" # toggle infinite looping
|
||||
#ctrl+c quit 4
|
||||
|
||||
# Apple Remote section
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#
|
||||
# Older installations use ~/.mpv/input.conf instead.
|
||||
|
||||
# changed in mpv 0.24.0
|
||||
|
||||
L cycle-values loop "inf" "no"
|
||||
|
||||
# changed in mpv 0.10.0
|
||||
|
||||
O osd
|
||||
|
||||
+6
-4
@@ -144,7 +144,8 @@ struct input_ctx {
|
||||
|
||||
struct cmd_queue cmd_queue;
|
||||
|
||||
struct mp_cancel *cancel;
|
||||
void (*cancel)(void *cancel_ctx);
|
||||
void *cancel_ctx;
|
||||
|
||||
void (*wakeup_cb)(void *ctx);
|
||||
void *wakeup_ctx;
|
||||
@@ -809,7 +810,7 @@ int mp_input_queue_cmd(struct input_ctx *ictx, mp_cmd_t *cmd)
|
||||
input_lock(ictx);
|
||||
if (cmd) {
|
||||
if (ictx->cancel && test_abort_cmd(ictx, cmd))
|
||||
mp_cancel_trigger(ictx->cancel);
|
||||
ictx->cancel(ictx->cancel_ctx);
|
||||
queue_add_tail(&ictx->cmd_queue, cmd);
|
||||
mp_input_wakeup(ictx);
|
||||
}
|
||||
@@ -1335,10 +1336,11 @@ void mp_input_uninit(struct input_ctx *ictx)
|
||||
talloc_free(ictx);
|
||||
}
|
||||
|
||||
void mp_input_set_cancel(struct input_ctx *ictx, struct mp_cancel *cancel)
|
||||
void mp_input_set_cancel(struct input_ctx *ictx, void (*cb)(void *c), void *c)
|
||||
{
|
||||
input_lock(ictx);
|
||||
ictx->cancel = cancel;
|
||||
ictx->cancel = cb;
|
||||
ictx->cancel_ctx = c;
|
||||
input_unlock(ictx);
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -242,8 +242,7 @@ void mp_input_wakeup(struct input_ctx *ictx);
|
||||
|
||||
// Used to asynchronously abort playback. Needed because the core still can
|
||||
// block on network in some situations.
|
||||
struct mp_cancel;
|
||||
void mp_input_set_cancel(struct input_ctx *ictx, struct mp_cancel *cancel);
|
||||
void mp_input_set_cancel(struct input_ctx *ictx, void (*cb)(void *c), void *c);
|
||||
|
||||
// If this returns true, use Right Alt key as Alt Gr to produce special
|
||||
// characters. If false, count Right Alt as the modifier Alt key.
|
||||
|
||||
Loaded 100 of 275 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user