Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1993d5ad2 | ||
|
|
0607dcebff | ||
|
|
3d12312806 | ||
|
|
a6521f3941 | ||
|
|
475fe453cc | ||
|
|
3da91a673a | ||
|
|
47e6ef0bdf | ||
|
|
6d36c432ab | ||
|
|
45ae0716be | ||
|
|
c5c7b239b6 | ||
|
|
c2d0d7818f | ||
|
|
0d7d935e88 | ||
|
|
3d66a5d14e | ||
|
|
c138505813 | ||
|
|
663415b914 | ||
|
|
c569d4f6ed | ||
|
|
e6058d3dc3 | ||
|
|
c1a96de41c | ||
|
|
18fe6e6dc8 | ||
|
|
aaa64b879e | ||
|
|
159eb3f962 | ||
|
|
9db50c6760 | ||
|
|
970606e491 | ||
|
|
7d7ea72854 | ||
|
|
0262295c4b | ||
|
|
48ed48f7b8 | ||
|
|
94c062d0d2 | ||
|
|
04934e86dd | ||
|
|
ff1eaea3e7 | ||
|
|
9a240dc82e | ||
|
|
a7cf091519 | ||
|
|
a2e7642d3c | ||
|
|
565efeeada | ||
|
|
c46106d633 | ||
|
|
63204eda5d | ||
|
|
791228f2ab | ||
|
|
19b0b1df51 | ||
|
|
30bbcd6452 | ||
|
|
832cb56f2d | ||
|
|
68c6da69f7 | ||
|
|
f626bef7c8 | ||
|
|
81746a1798 | ||
|
|
01c418f9df | ||
|
|
3b6c7275bc | ||
|
|
6562d4a562 | ||
|
|
17507b5935 | ||
|
|
57e691b901 | ||
|
|
344d44681e | ||
|
|
03d311823d | ||
|
|
9cb7646071 | ||
|
|
42a0f4d87b | ||
|
|
69cc002c92 | ||
|
|
b325ed1105 | ||
|
|
30f18b8dc7 | ||
|
|
e1d414c992 | ||
|
|
7f3cf75c5a | ||
|
|
ece794d905 | ||
|
|
3fa76c84be | ||
|
|
66d93c711b | ||
|
|
03a81a9d85 | ||
|
|
e2c0e7f5c2 | ||
|
|
8bf34950c1 | ||
|
|
2b990ac810 | ||
|
|
005f03d99f | ||
|
|
6ff1cd5502 | ||
|
|
9fc74d5acd | ||
|
|
2b07d3eb47 | ||
|
|
73e0d545ce | ||
|
|
50d43a1db4 | ||
|
|
318e9801f2 | ||
|
|
ea1caa474a | ||
|
|
7023c383b2 | ||
|
|
2bee47fd27 | ||
|
|
c8e0e35709 | ||
|
|
9afd62bfcd | ||
|
|
eec844a06e | ||
|
|
50bb209a80 | ||
|
|
41f2c653e2 | ||
|
|
85498a0797 | ||
|
|
b250c19a38 | ||
|
|
69ba4f776f | ||
|
|
4c111fbcde | ||
|
|
12eb8b2de8 | ||
|
|
44376d2d9b | ||
|
|
b8bcf0f466 | ||
|
|
1fe64c61be | ||
|
|
772961f0ce | ||
|
|
5bc9b273b3 | ||
|
|
0425741754 | ||
|
|
19c1a208ec | ||
|
|
422ed42817 | ||
|
|
fa2fdaab0a | ||
|
|
b7d614aa0d | ||
|
|
dab2e909af | ||
|
|
06df54a111 | ||
|
|
ef918b239e | ||
|
|
00b7fb3023 | ||
|
|
4f103b2093 | ||
|
|
2e401201ad | ||
|
|
b2e8d5749b | ||
|
|
52af1f72e2 | ||
|
|
c9d1aa1d3c | ||
|
|
718807b78b | ||
|
|
d2efa56d48 | ||
|
|
01a2af6c7c | ||
|
|
b38094ad91 | ||
|
|
9817473163 | ||
|
|
c5a4fd9bee | ||
|
|
d5df90a295 | ||
|
|
3245b7f275 | ||
|
|
59eb489425 | ||
|
|
4fd0cd4a73 | ||
|
|
6df3fa2ec1 | ||
|
|
1a8b06f67e | ||
|
|
92d06f43fa | ||
|
|
23f3b99003 | ||
|
|
b86a59aa4f | ||
|
|
368431f57c | ||
|
|
94147762fc | ||
|
|
e10727baa7 | ||
|
|
a6fb80baa4 | ||
|
|
f9a2fc592f | ||
|
|
76e4374d06 | ||
|
|
7e285a6f71 | ||
|
|
516e7d19da | ||
|
|
58caf577f1 | ||
|
|
76fcef618b | ||
|
|
74c862c68e | ||
|
|
9f858cc759 | ||
|
|
ca77bcd543 | ||
|
|
e086d40486 | ||
|
|
eda518f558 | ||
|
|
e76ec2c963 | ||
|
|
a3195381d1 | ||
|
|
8ff2058681 | ||
|
|
0ec35fa111 | ||
|
|
25fe9e89e7 | ||
|
|
0e0f07bbef | ||
|
|
c7a39b8521 | ||
|
|
5a89150a46 | ||
|
|
85450d06a1 | ||
|
|
70df1608d6 | ||
|
|
e24e0ccd68 | ||
|
|
514f5d760e | ||
|
|
05be95c8ce | ||
|
|
6b22b21651 | ||
|
|
07c546b2b4 | ||
|
|
bf0b178e71 | ||
|
|
883d311413 | ||
|
|
c7d82dd25c | ||
|
|
cf3b34f17a | ||
|
|
a790009a63 | ||
|
|
542d88472f | ||
|
|
9f43778eb2 | ||
|
|
0f3dedebb4 | ||
|
|
70d46a9fb8 | ||
|
|
c362c3d7ae | ||
|
|
07b8abbd62 | ||
|
|
d5981924fe | ||
|
|
62b386c2fd | ||
|
|
fad254562b | ||
|
|
d32c4c75ef | ||
|
|
624c9e46ce | ||
|
|
f0feea5591 | ||
|
|
def87f1e5f | ||
|
|
b726fefe5d | ||
|
|
49a41bf4df | ||
|
|
384b13c5fd | ||
|
|
977869a804 | ||
|
|
7c4fe02d01 | ||
|
|
b7398cbdd8 | ||
|
|
9774be0d15 | ||
|
|
a33a44cf4e | ||
|
|
273607c1c6 | ||
|
|
fee45c0170 | ||
|
|
9a6ec9de2f | ||
|
|
4682b0147e | ||
|
|
479eb93d9e | ||
|
|
9d9f863f55 | ||
|
|
dd0c85679b | ||
|
|
4efadb2808 | ||
|
|
cf5b117553 | ||
|
|
434512827f | ||
|
|
03bbaad686 | ||
|
|
3dc0f2ecf0 | ||
|
|
eeb5f98758 | ||
|
|
caa497ee8a | ||
|
|
930f841589 | ||
|
|
8baf773d0e | ||
|
|
11888a9270 | ||
|
|
76e50f6a3d | ||
|
|
7d5282ea5d | ||
|
|
f757163058 | ||
|
|
ac64ce71d6 | ||
|
|
16cd20c46f | ||
|
|
8f7b12d9ae | ||
|
|
66f8e45311 | ||
|
|
0ff3ffb2be | ||
|
|
d91434756b | ||
|
|
03013e0fd7 | ||
|
|
62f261a7b8 | ||
|
|
c3f2ef5491 | ||
|
|
67caea357c | ||
|
|
b1893d2084 | ||
|
|
46cee66563 | ||
|
|
0062c98dff | ||
|
|
6bbb1e4cf9 | ||
|
|
1356755ad4 | ||
|
|
2dc18a2f82 | ||
|
|
a7f51f8fd4 | ||
|
|
5a7c22a1ac | ||
|
|
617aff6cda | ||
|
|
b984ec52aa | ||
|
|
9693e0f57a | ||
|
|
647b360a0a | ||
|
|
68ac45e487 | ||
|
|
9e10cd9fa1 | ||
|
|
b2a3e877c0 | ||
|
|
5f89ba707d | ||
|
|
cfa4952f7c | ||
|
|
945df57357 | ||
|
|
fb193713a3 | ||
|
|
27dc834f37 | ||
|
|
3f73d63523 | ||
|
|
4c43c30421 | ||
|
|
7438f208c3 | ||
|
|
017f3d0674 | ||
|
|
2cf9ee989c | ||
|
|
66ed50aa00 | ||
|
|
179d7317e7 | ||
|
|
3108a3a001 | ||
|
|
8f7cf9443f | ||
|
|
e3db686e87 | ||
|
|
db1fc3628f | ||
|
|
3ad03f6673 | ||
|
|
f00edfd9c5 | ||
|
|
e6a395c297 | ||
|
|
be49da72ea | ||
|
|
46f59f25c2 | ||
|
|
0ca8b290a4 | ||
|
|
5a18c5ea91 | ||
|
|
a9833a71ce | ||
|
|
350d902799 | ||
|
|
e187de7eed | ||
|
|
49d94853b5 | ||
|
|
163c6ad862 | ||
|
|
4058b418bf | ||
|
|
4e358a9636 | ||
|
|
9d4aff8ac7 | ||
|
|
e162a5bf04 | ||
|
|
8d415b2e01 | ||
|
|
d634394da6 | ||
|
|
609de236a9 | ||
|
|
3f0d831af0 | ||
|
|
587bb5e811 | ||
|
|
c2220c526d | ||
|
|
b58e4abc01 | ||
|
|
3fb161ecd2 | ||
|
|
1d2d649281 | ||
|
|
8e8b420c9d | ||
|
|
9de8730dc9 | ||
|
|
30a6106477 | ||
|
|
f1e767b6a2 | ||
|
|
8581835161 | ||
|
|
cdeb0e4c72 | ||
|
|
8fd63c8f0c | ||
|
|
b92fd602ce | ||
|
|
7984d25ceb | ||
|
|
2b6241a09a | ||
|
|
93f748e77f | ||
|
|
7c6d7017bd | ||
|
|
344b65c4db | ||
|
|
03cf69f0a8 | ||
|
|
05e39ec513 | ||
|
|
7aba3a5d96 | ||
|
|
acd5816a6d | ||
|
|
8737732035 | ||
|
|
67aab3a9f6 | ||
|
|
8eb7cb28f9 | ||
|
|
3dec68ba47 | ||
|
|
d5b8d77a17 | ||
|
|
a1315c7644 | ||
|
|
532f05b83d | ||
|
|
5b7d0fbab3 | ||
|
|
ec567d8a77 | ||
|
|
33489feca8 | ||
|
|
ce82d88215 | ||
|
|
d1ad0815fe | ||
|
|
fc3f1e7c54 | ||
|
|
466658ee7e | ||
|
|
cf18922d19 | ||
|
|
3c081dfd93 | ||
|
|
a135c9cffc | ||
|
|
f891b24cb5 | ||
|
|
70aa3b04f8 | ||
|
|
c24921d56b | ||
|
|
aaec2aba38 | ||
|
|
3b95dd47d5 | ||
|
|
555ecbb70e | ||
|
|
919707efb7 | ||
|
|
c1de8cdc38 | ||
|
|
0cc440f291 | ||
|
|
0524907c18 | ||
|
|
c971fefd41 | ||
|
|
9ed289ef90 | ||
|
|
f3a003e550 | ||
|
|
48c2e9d67d | ||
|
|
0ffaf653a2 | ||
|
|
76d1b430b0 | ||
|
|
ec27d573f4 | ||
|
|
72d3c5ef00 | ||
|
|
81109dcbb6 | ||
|
|
fa510bd00c | ||
|
|
c21c26472c | ||
|
|
d6e7190836 | ||
|
|
d1a46c2c32 | ||
|
|
a02f3a5dd4 | ||
|
|
17b4fb02b3 | ||
|
|
252dcdcc99 | ||
|
|
97fc4f4a85 | ||
|
|
ad6e7c31df | ||
|
|
bf6b981367 | ||
|
|
72ded5ccef | ||
|
|
0344abd67a | ||
|
|
56fefde7e2 | ||
|
|
955c6843b6 | ||
|
|
d1528e51d5 | ||
|
|
53c720d412 | ||
|
|
878f12885b | ||
|
|
0a1a5707bf | ||
|
|
dd08018e9e | ||
|
|
56d04c6570 | ||
|
|
96eb480299 | ||
|
|
14bb3d06a2 | ||
|
|
1a1ac76d2a | ||
|
|
d93a9be656 | ||
|
|
dda16ee1fb | ||
|
|
78112c8582 | ||
|
|
ff778f6d68 | ||
|
|
cee9aeaf6b | ||
|
|
eb66038d4f | ||
|
|
1ee8ce75f1 | ||
|
|
0121d00478 | ||
|
|
e157d005ba | ||
|
|
251107076b | ||
|
|
750c29766f | ||
|
|
b0b67cdf5d | ||
|
|
50f8548b2f | ||
|
|
0ab6031d76 | ||
|
|
667b968939 | ||
|
|
e1128c8b28 | ||
|
|
e3de309804 | ||
|
|
9ca312b4b1 | ||
|
|
d27d91715f | ||
|
|
0eb45343ca | ||
|
|
7e7ef2f01d | ||
|
|
76bfd5b4a2 | ||
|
|
8d414e2fe7 | ||
|
|
2483dab54d | ||
|
|
3a7a2385df | ||
|
|
3274fabeb3 | ||
|
|
e0f8d79772 | ||
|
|
da496ae2fe | ||
|
|
184426175f | ||
|
|
1f9c4f19cf | ||
|
|
8b291aff96 | ||
|
|
8d004f07a4 | ||
|
|
14a2993796 | ||
|
|
3804376ccc | ||
|
|
bddd1e9e3b | ||
|
|
9e7069fde2 | ||
|
|
94b6a1054e | ||
|
|
d714c8380a | ||
|
|
b7ba2e2c57 | ||
|
|
52de5ea6ad | ||
|
|
78caf6ae86 | ||
|
|
46c499d02a | ||
|
|
dbbde6161d | ||
|
|
a62dcdd5f4 | ||
|
|
f7124be091 | ||
|
|
6499224888 | ||
|
|
252d5b319a | ||
|
|
280251656c | ||
|
|
aa796c23db | ||
|
|
0a41c6f0ec | ||
|
|
66aa54d319 | ||
|
|
288eaacd85 | ||
|
|
ecb776f88e | ||
|
|
1c593f1497 | ||
|
|
b4804a4b26 | ||
|
|
5015759334 | ||
|
|
3c292395ca | ||
|
|
8e654d3f78 | ||
|
|
0e245b3752 | ||
|
|
6fb926a560 | ||
|
|
8f33c65fe0 | ||
|
|
beae60bcd5 | ||
|
|
29e42b78c0 | ||
|
|
54fbda2ba4 | ||
|
|
e694d67366 | ||
|
|
6890de958d | ||
|
|
c855ab5624 | ||
|
|
3ccac74de2 | ||
|
|
ee283843fa | ||
|
|
291f301c10 | ||
|
|
e448e10fd7 | ||
|
|
e72ca08554 | ||
|
|
e87f705497 | ||
|
|
93db4233af | ||
|
|
fe993a6712 | ||
|
|
69bc7e34b9 | ||
|
|
a9ed9e329c | ||
|
|
8a21cb1cd3 | ||
|
|
bd87598af9 | ||
|
|
f4d62da8f0 | ||
|
|
ae7212963e | ||
|
|
24f34c9e8e | ||
|
|
98c4ab6d13 | ||
|
|
4a07205963 | ||
|
|
7c5f41ff5f | ||
|
|
a2a2cdb5bc | ||
|
|
41bf41e416 | ||
|
|
ebb43f5176 | ||
|
|
aff6e8e8da | ||
|
|
1b7e8ff6c5 | ||
|
|
739452361f | ||
|
|
ee63c9c210 | ||
|
|
b4491c00c4 | ||
|
|
12cd48a8a9 | ||
|
|
31316e9ca0 | ||
|
|
853fe788f8 | ||
|
|
1dd7b7bddc | ||
|
|
15ef9f9ee6 | ||
|
|
ad2ab5893e | ||
|
|
144571da9b | ||
|
|
8aa8417aa3 | ||
|
|
1fa674c109 | ||
|
|
710872bc22 | ||
|
|
1ff32236fa | ||
|
|
2a7811176c | ||
|
|
2e5df94f0f | ||
|
|
0c494c238a | ||
|
|
375886c777 | ||
|
|
0aa548457f | ||
|
|
e0cb65e8ac | ||
|
|
6875e6a779 | ||
|
|
ac5538c7df | ||
|
|
0ae8aebb89 | ||
|
|
361040f9d9 | ||
|
|
d2281935b3 | ||
|
|
0a6c334b59 | ||
|
|
38b747bb67 | ||
|
|
993bee38ca | ||
|
|
0b87bf9b72 | ||
|
|
456366b63b | ||
|
|
ec356d3efe | ||
|
|
5786d07450 | ||
|
|
8d8a2045bd | ||
|
|
0e9cfa6b64 | ||
|
|
d47dff3faa | ||
|
|
b81d9c79a8 | ||
|
|
57831d52dc | ||
|
|
b14c9eb748 | ||
|
|
cb1c072534 | ||
|
|
c17ff1703a | ||
|
|
65ad85790a | ||
|
|
17cd6798a6 | ||
|
|
d8d4e18835 | ||
|
|
009dfbe3b6 | ||
|
|
2ed9370bd6 | ||
|
|
e0c005cb75 | ||
|
|
4d6c1be7b6 |
No files matched your search
@@ -15,6 +15,7 @@
|
||||
/tags
|
||||
/TAGS
|
||||
/video/out/x11_icon.inc
|
||||
/video/out/opengl/nnedi3_weights.inc
|
||||
/demux/ebml_defs.c
|
||||
/demux/ebml_types.h
|
||||
/sub/osd_font.h
|
||||
|
||||
@@ -12,6 +12,9 @@ the code to LGPLv2.1+ at a later point without asking the contributor. (This
|
||||
is a safeguard for making potential relicensing of the project to LGPLv2.1+
|
||||
easier.) Using a more liberal license compatible to LGPLv2.1+ is also ok.
|
||||
|
||||
Some GPLv3 or LGPLv3 code in mpv can be enabled by building with --enable-gpl3,
|
||||
and in this case, the resulting binaries must be distributed under GPLv3.
|
||||
|
||||
For information about authors and contributors, consult the git log, which
|
||||
contains the complete SVN and CVS history as well.
|
||||
|
||||
|
||||
@@ -32,7 +32,12 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.12.0 ---
|
||||
1.20 - deprecate "GL_MP_D3D_interfaces"/"glMPGetD3DInterface", and introduce
|
||||
"GL_MP_MPGetNativeDisplay"/"glMPGetNativeDisplay" (this is a
|
||||
backwards-compatible rename)
|
||||
--- mpv 0.11.0 ---
|
||||
--- mpv 0.10.0 ---
|
||||
1.19 - add "GL_MP_D3D_interfaces" pseudo extension to make it possible to
|
||||
use DXVA2 in OpenGL fullscreen mode in some situations
|
||||
- mpv_request_log_messages() now accepts "terminal-default" as parameter
|
||||
|
||||
@@ -1,35 +1,114 @@
|
||||
Client API examples
|
||||
===================
|
||||
# Client API examples
|
||||
|
||||
All these examples use the mpv client API through libmpv.
|
||||
|
||||
cocoa
|
||||
-----
|
||||
## Where are the docs?
|
||||
|
||||
The libmpv C API is documented directly in the header files (on normal Unix
|
||||
systems, this is in `/usr/include/mpv/client.h`.
|
||||
|
||||
libmpv merely gives you access to mpv's command interface, which is documented
|
||||
here:
|
||||
* Options (`mpv_set_option()` and friends): http://mpv.io/manual/master/#options
|
||||
* Commands (`mpv_command()` and friends): http://mpv.io/manual/master/#list-of-input-commands
|
||||
* Properties (`mpv_set_property()` and friends): http://mpv.io/manual/master/#properties
|
||||
|
||||
Essentially everything is done with them, including loading a file, retrieving
|
||||
playback progress, and so on.
|
||||
|
||||
## Methods of embedding the video window
|
||||
|
||||
All of these examples concentrate on how to integrate mpv's video renderers
|
||||
with your own GUI. This is generally the hardest part. libmpv enforces a
|
||||
somewhat efficient video output method, rather than e.g. returning a RGBA
|
||||
surface in memory for each frame. The latter would be prohibitively inefficient,
|
||||
because it would require conversion on the CPU. The goal is also not requiring
|
||||
the API users to reinvent their own video rendering/scaling/presentation
|
||||
mechanisms.
|
||||
|
||||
There are currently 2 methods of embedding video.
|
||||
|
||||
### Native window embedding
|
||||
|
||||
This uses the platform's native method of nesting multiple windows. For example,
|
||||
Linux/X11 can nest a window from a completely different process. The nested
|
||||
window can redraw contents on its own, and receive user input if the user
|
||||
interacts with this window.
|
||||
|
||||
libmpv lets you specify a parent window for its own video window via the `wid`
|
||||
option. Then libmpv will create its window with your window as parent, and
|
||||
render its video inside of it.
|
||||
|
||||
This method is highly OS-dependent. Some behavior is OS-specific. There are
|
||||
problems with focusing on X11 (the ancient X11 focus policy mismatches with
|
||||
that of modern UI toolkits - it's normally worked around, but this is not
|
||||
easily possible with raw window embedding). It seems to have stability problems
|
||||
on OSX when using the Qt toolkit.
|
||||
|
||||
### OpenGL embedding
|
||||
|
||||
This method lets you use libmpv's OpenGL renderer directly. You create an
|
||||
OpenGL context, and then use `mpv_opengl_cb_draw()` to render the video on
|
||||
each frame.
|
||||
|
||||
This is more complicated, because libmpv will work directly on your own OpenGL
|
||||
state. It's also not possible to have mpv automatically receive user input.
|
||||
You will have to simulate this with the `mouse`/`keypress`/`keydown`/`keyup`
|
||||
commands.
|
||||
|
||||
You also get much more flexibility. For example, you can actually render your
|
||||
own OSD on top of the video, something that is not possible with raw window
|
||||
embedding.
|
||||
|
||||
### Which one to use?
|
||||
|
||||
Due to the various platform-specific behavior and problems (in particular on
|
||||
OSX), OpenGL embedding is recommended. If you're not comfortable with requiring
|
||||
OpenGL, or want to support "direct" video output such as vdpau (which might
|
||||
win when it comes to performance and energy-saving), you should probably
|
||||
support both methods if possible.
|
||||
|
||||
## List of examples
|
||||
|
||||
### simple
|
||||
|
||||
Very primitive terminal-only example. Shows some most basic API usage.
|
||||
|
||||
### cocoa
|
||||
|
||||
Shows how to embed the mpv video window in Objective-C/Cocoa.
|
||||
|
||||
qt
|
||||
--
|
||||
### cocoa-openglcb
|
||||
|
||||
Similar to the cocoa sample, but shows how to integrate mpv's OpenGL renderer
|
||||
using libmpv's opengl-cb API. Since it does not require complicated interaction
|
||||
with Cocoa elements from different libraries, it's more robust.
|
||||
|
||||
### qt
|
||||
|
||||
Shows how to embed the mpv video window in Qt (using normal desktop widgets).
|
||||
|
||||
qml
|
||||
---
|
||||
### qt_opengl
|
||||
|
||||
Shows how to use mpv's OpenGL video renderer in QtQuick2 with QML.
|
||||
Shows how to use mpv's OpenGL video renderer in Qt. This uses the opengl-cb API
|
||||
for video. Since it does not rely on embedding "foreign" native Windows, it's
|
||||
usually more robust, potentially faster, and it's easier to control how your
|
||||
GUI interacts with the video. You can do your own OpenGL rendering on top of
|
||||
the video as well.
|
||||
|
||||
qml_direct
|
||||
----------
|
||||
### qml
|
||||
|
||||
Shows how to use mpv's OpenGL video renderer in QtQuick2 with QML. Uses the
|
||||
opengl-cb API for video. Since the video is a normal QML element, it's trivial
|
||||
to create OSD overlays with QML-native graphical elements as well.
|
||||
|
||||
### qml_direct
|
||||
|
||||
Alternative example, which typically avoids a FBO indirection. Might be
|
||||
slightly faster, but is less flexible and harder to use.
|
||||
slightly faster, but is less flexible and harder to use. In particular, the
|
||||
video is not a normal QML element. Uses the opengl-cb API for video.
|
||||
|
||||
sdl
|
||||
---
|
||||
### sdl
|
||||
|
||||
Show how to embed the mpv OpenGL renderer in SDL.
|
||||
|
||||
simple
|
||||
------
|
||||
|
||||
Very primitive terminal-only example. Shows some most basic API usage.
|
||||
Show how to embed the mpv OpenGL renderer in SDL. Uses the opengl-cb API for
|
||||
video.
|
||||
@@ -50,7 +50,7 @@ Item {
|
||||
text: "Make video look like on a Smart TV"
|
||||
onClicked: {
|
||||
if (checkbox.checked) {
|
||||
renderer.command(["vo_cmdline", "scale=sharpen3:scale-param1=5.0"])
|
||||
renderer.command(["vo_cmdline", "sharpen=5.0"])
|
||||
} else {
|
||||
renderer.command(["vo_cmdline", ""])
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <QApplication>
|
||||
#include "mainwindow.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
// Qt sets the locale in the QApplication constructor, but libmpv requires
|
||||
// the LC_NUMERIC category to be set to "C", so change it back.
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
MainWindow w;
|
||||
w.show();
|
||||
return a.exec();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#include "mainwindow.h"
|
||||
#include "mpvwidget.h"
|
||||
#include <QPushButton>
|
||||
#include <QSlider>
|
||||
#include <QLayout>
|
||||
#include <QFileDialog>
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
m_mpv = new MpvWidget(this);
|
||||
m_slider = new QSlider();
|
||||
m_slider->setOrientation(Qt::Horizontal);
|
||||
m_openBtn = new QPushButton("Open");
|
||||
m_playBtn = new QPushButton("Pause");
|
||||
QHBoxLayout *hb = new QHBoxLayout();
|
||||
hb->addWidget(m_openBtn);
|
||||
hb->addWidget(m_playBtn);
|
||||
QVBoxLayout *vl = new QVBoxLayout();
|
||||
vl->addWidget(m_mpv);
|
||||
vl->addWidget(m_slider);
|
||||
vl->addLayout(hb);
|
||||
setLayout(vl);
|
||||
connect(m_slider, SIGNAL(sliderMoved(int)), SLOT(seek(int)));
|
||||
connect(m_openBtn, SIGNAL(clicked()), SLOT(openMedia()));
|
||||
connect(m_playBtn, SIGNAL(clicked()), SLOT(pauseResume()));
|
||||
connect(m_mpv, SIGNAL(positionChanged(int)), m_slider, SLOT(setValue(int)));
|
||||
connect(m_mpv, SIGNAL(durationChanged(int)), this, SLOT(setSliderRange(int)));
|
||||
}
|
||||
|
||||
void MainWindow::openMedia()
|
||||
{
|
||||
QString file = QFileDialog::getOpenFileName(0, "Open a video");
|
||||
if (file.isEmpty())
|
||||
return;
|
||||
m_mpv->command(QStringList() << "loadfile" << file);
|
||||
}
|
||||
|
||||
void MainWindow::seek(int pos)
|
||||
{
|
||||
m_mpv->command(QVariantList() << "seek" << pos << "absolute");
|
||||
}
|
||||
|
||||
void MainWindow::pauseResume()
|
||||
{
|
||||
const bool paused = m_mpv->getProperty("pause").toBool();
|
||||
m_mpv->setProperty("pause", !paused);
|
||||
}
|
||||
|
||||
void MainWindow::setSliderRange(int duration)
|
||||
{
|
||||
m_slider->setRange(0, duration);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef MainWindow_H
|
||||
#define MainWindow_H
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
class MpvWidget;
|
||||
class QSlider;
|
||||
class QPushButton;
|
||||
class MainWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
public Q_SLOTS:
|
||||
void openMedia();
|
||||
void seek(int pos);
|
||||
void pauseResume();
|
||||
private Q_SLOTS:
|
||||
void setSliderRange(int duration);
|
||||
private:
|
||||
MpvWidget *m_mpv;
|
||||
QSlider *m_slider;
|
||||
QPushButton *m_openBtn;
|
||||
QPushButton *m_playBtn;
|
||||
};
|
||||
|
||||
#endif // MainWindow_H
|
||||
@@ -0,0 +1,129 @@
|
||||
#include "mpvwidget.h"
|
||||
#include <stdexcept>
|
||||
#include <QtGui/QOpenGLContext>
|
||||
#include <QtCore/QMetaObject>
|
||||
|
||||
static void wakeup(void *ctx)
|
||||
{
|
||||
QMetaObject::invokeMethod((MpvWidget*)ctx, "on_mpv_events", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
static void *get_proc_address(void *ctx, const char *name) {
|
||||
Q_UNUSED(ctx);
|
||||
QOpenGLContext *glctx = QOpenGLContext::currentContext();
|
||||
if (!glctx)
|
||||
return NULL;
|
||||
return (void *)glctx->getProcAddress(QByteArray(name));
|
||||
}
|
||||
|
||||
MpvWidget::MpvWidget(QWidget *parent, Qt::WindowFlags f)
|
||||
: QOpenGLWidget(parent, f)
|
||||
{
|
||||
mpv = mpv::qt::Handle::FromRawHandle(mpv_create());
|
||||
if (!mpv)
|
||||
throw std::runtime_error("could not create mpv context");
|
||||
|
||||
mpv_set_option_string(mpv, "terminal", "yes");
|
||||
mpv_set_option_string(mpv, "msg-level", "all=v");
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
throw std::runtime_error("could not initialize mpv context");
|
||||
|
||||
// Make use of the MPV_SUB_API_OPENGL_CB API.
|
||||
mpv::qt::set_option_variant(mpv, "vo", "opengl-cb");
|
||||
|
||||
// Request hw decoding, just for testing.
|
||||
mpv::qt::set_option_variant(mpv, "hwdec", "auto");
|
||||
|
||||
mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
|
||||
if (!mpv_gl)
|
||||
throw std::runtime_error("OpenGL not compiled in");
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, MpvWidget::on_update, (void *)this);
|
||||
connect(this, SIGNAL(frameSwapped()), SLOT(swapped()));
|
||||
|
||||
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
|
||||
mpv_set_wakeup_callback(mpv, wakeup, this);
|
||||
}
|
||||
|
||||
MpvWidget::~MpvWidget()
|
||||
{
|
||||
makeCurrent();
|
||||
if (mpv_gl)
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, NULL, NULL);
|
||||
// Until this call is done, we need to make sure the player remains
|
||||
// alive. This is done implicitly with the mpv::qt::Handle instance
|
||||
// in this class.
|
||||
mpv_opengl_cb_uninit_gl(mpv_gl);
|
||||
}
|
||||
|
||||
void MpvWidget::command(const QVariant& params)
|
||||
{
|
||||
mpv::qt::command_variant(mpv, params);
|
||||
}
|
||||
|
||||
void MpvWidget::setProperty(const QString& name, const QVariant& value)
|
||||
{
|
||||
mpv::qt::set_property_variant(mpv, name, value);
|
||||
}
|
||||
|
||||
QVariant MpvWidget::getProperty(const QString &name) const
|
||||
{
|
||||
return mpv::qt::get_property_variant(mpv, name);
|
||||
}
|
||||
|
||||
void MpvWidget::initializeGL()
|
||||
{
|
||||
int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL);
|
||||
if (r < 0)
|
||||
throw std::runtime_error("could not initialize OpenGL");
|
||||
}
|
||||
|
||||
void MpvWidget::paintGL()
|
||||
{
|
||||
mpv_opengl_cb_draw(mpv_gl, defaultFramebufferObject(), width(), -height());
|
||||
}
|
||||
|
||||
void MpvWidget::swapped()
|
||||
{
|
||||
mpv_opengl_cb_report_flip(mpv_gl, 0);
|
||||
}
|
||||
|
||||
void MpvWidget::on_mpv_events()
|
||||
{
|
||||
// Process all events, until the event queue is empty.
|
||||
while (mpv) {
|
||||
mpv_event *event = mpv_wait_event(mpv, 0);
|
||||
if (event->event_id == MPV_EVENT_NONE) {
|
||||
break;
|
||||
}
|
||||
handle_mpv_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
void MpvWidget::handle_mpv_event(mpv_event *event)
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case MPV_EVENT_PROPERTY_CHANGE: {
|
||||
mpv_event_property *prop = (mpv_event_property *)event->data;
|
||||
if (strcmp(prop->name, "time-pos") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double time = *(double *)prop->data;
|
||||
Q_EMIT positionChanged(time);
|
||||
}
|
||||
} else if (strcmp(prop->name, "duration") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double time = *(double *)prop->data;
|
||||
Q_EMIT durationChanged(time);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: ;
|
||||
// Ignore uninteresting or unknown events.
|
||||
}
|
||||
}
|
||||
|
||||
void MpvWidget::on_update(void *ctx)
|
||||
{
|
||||
QMetaObject::invokeMethod((MpvWidget*)ctx, "update");
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef PLAYERWINDOW_H
|
||||
#define PLAYERWINDOW_H
|
||||
|
||||
#include <QtWidgets/QOpenGLWidget>
|
||||
#include <mpv/client.h>
|
||||
#include <mpv/opengl_cb.h>
|
||||
#include <mpv/qthelper.hpp>
|
||||
|
||||
class MpvWidget Q_DECL_FINAL: public QOpenGLWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MpvWidget(QWidget *parent = 0, Qt::WindowFlags f = 0);
|
||||
~MpvWidget();
|
||||
void command(const QVariant& params);
|
||||
void setProperty(const QString& name, const QVariant& value);
|
||||
QVariant getProperty(const QString& name) const;
|
||||
QSize sizeHint() const { return QSize(480, 270);}
|
||||
Q_SIGNALS:
|
||||
void durationChanged(int value);
|
||||
void positionChanged(int value);
|
||||
protected:
|
||||
void initializeGL() Q_DECL_OVERRIDE;
|
||||
void paintGL() Q_DECL_OVERRIDE;
|
||||
private Q_SLOTS:
|
||||
void swapped();
|
||||
void on_mpv_events();
|
||||
private:
|
||||
void handle_mpv_event(mpv_event *event);
|
||||
static void on_update(void *ctx);
|
||||
|
||||
mpv::qt::Handle mpv;
|
||||
mpv_opengl_cb_context *mpv_gl;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // PLAYERWINDOW_H
|
||||
@@ -0,0 +1,13 @@
|
||||
CONFIG -= app_bundle
|
||||
QT += widgets
|
||||
|
||||
QT_CONFIG -= no-pkg-config
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG += mpv
|
||||
|
||||
HEADERS = \
|
||||
mpvwidget.h \
|
||||
mainwindow.h
|
||||
SOURCES = main.cpp \
|
||||
mpvwidget.cpp \
|
||||
mainwindow.cpp
|
||||
@@ -19,6 +19,29 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.14.0 ---
|
||||
- vo_opengl interpolation now requires --video-sync=display-... to be set
|
||||
- change some vo_opengl defaults (including changing tscale)
|
||||
- add "vsync-ratio", "estimated-display-fps" properties
|
||||
- add --rebase-start-time option
|
||||
This is a breaking change to start time handling. Instead of making start
|
||||
time handling an aspect of different options and properties (like
|
||||
"time-pos" vs. "playback-time"), make it dependent on the new option. For
|
||||
compatibility, the "time-start" property now always returns 0, so code
|
||||
which attempted to handle rebasing manually will not break.
|
||||
--- mpv 0.13.0 ---
|
||||
- remove VO opengl-cb frame queue suboptions (no replacement)
|
||||
--- mpv 0.12.0 ---
|
||||
- remove --use-text-osd (useless; fontconfig isn't a requirement anymore,
|
||||
and text rendering is also lazily initialized)
|
||||
- some time properties (at least "playback-time", "time-pos",
|
||||
"time-remaining", "playtime-remaining") now are unavailable if the time
|
||||
is unknown, instead of just assuming that the internal playback position
|
||||
is 0
|
||||
- add --audio-fallback-to-null option
|
||||
- replace vf_format outputlevels suboption with "video-output-levels" global
|
||||
property/option; also remove "colormatrix-output-range" property
|
||||
- vo_opengl: remove sharpen3/sharpen5 scale filters, add sharpen sub-option
|
||||
--- mpv 0.11.0 ---
|
||||
- add "af-metadata" property
|
||||
--- mpv 0.10.0 ---
|
||||
|
||||
+1
-1
@@ -164,7 +164,7 @@ Available filters are:
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``mpv --af=channels=4:[0-1,1-0,0-2,1-3] media.avi``
|
||||
``mpv --af=channels=4:[0-1,1-0,2-2,3-3] media.avi``
|
||||
Would change the number of channels to 4 and set up 4 routes that
|
||||
swap channel 0 and channel 1 and leave channel 2 and 3 intact.
|
||||
Observe that if media containing two channels were played back,
|
||||
|
||||
+3
-6
@@ -139,16 +139,13 @@ Available audio output drivers are:
|
||||
Automatically create connections to output ports (default: enabled).
|
||||
When enabled, the maximum number of output channels will be limited to
|
||||
the number of available output ports.
|
||||
``std-channel-layout=alsa|waveext|any``
|
||||
Select the standard channel layout (default: alsa). JACK itself has no
|
||||
``std-channel-layout=waveext|any``
|
||||
Select the standard channel layout (default: waveext). JACK itself has no
|
||||
notion of channel layouts (i.e. assigning which speaker a given
|
||||
channel is supposed to map to) - it just takes whatever the application
|
||||
outputs, and reroutes it to whatever the user defines. This means the
|
||||
user and the application are in charge of dealing with the channel
|
||||
layout. ``alsa`` uses the old MPlayer layout, which is inspired by
|
||||
ALSA's standard layouts. In this mode, ao_jack will refuse to play 3
|
||||
or 7 channels (because these do not really have a defined meaning in
|
||||
MPlayer). ``waveext`` uses WAVE_FORMAT_EXTENSIBLE order, which, even
|
||||
layout. ``waveext`` uses WAVE_FORMAT_EXTENSIBLE order, which, even
|
||||
though it was defined by Microsoft, is the standard on many systems.
|
||||
The value ``any`` makes JACK accept whatever comes from the audio
|
||||
filter chain, regardless of channel layout and without reordering. This
|
||||
|
||||
+60
-15
@@ -329,9 +329,9 @@ List of Input Commands
|
||||
This is similar to ``sub-step``, except that it seeks video and audio
|
||||
instead of adjusting the subtitle delay.
|
||||
|
||||
Like with ``sub-step``, this works with external text subtitles only. For
|
||||
embedded text subtitles (like with Matroska), this works only with subtitle
|
||||
events that have already been displayed.
|
||||
For embedded subtitles (like with Matroska), this works only with subtitle
|
||||
events that have already been displayed, or are within a short prefetch
|
||||
range.
|
||||
|
||||
``osd [<level>]``
|
||||
Toggle OSD level. If ``<level>`` is specified, set the OSD mode
|
||||
@@ -860,7 +860,11 @@ Property list
|
||||
quantities: fps and possibly rounded timestamps.)
|
||||
|
||||
``path``
|
||||
Full path of the currently played file.
|
||||
Full path of the currently played file. Usually this is exactly the same
|
||||
string you pass on the mpv command line or the ``loadfile`` command, even
|
||||
if it's a relative path. If you expect an absolute path, you will have to
|
||||
determine it yourself, for example by using the ``working-directory``
|
||||
property.
|
||||
|
||||
``media-title``
|
||||
If the currently played file has a ``title`` tag, use that.
|
||||
@@ -913,6 +917,26 @@ Property list
|
||||
``vo-drop-frame-count``
|
||||
Frames dropped by VO (when using ``--framedrop=vo``).
|
||||
|
||||
``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
|
||||
circumstances, such as video rendering being too slow or the graphics
|
||||
driver somehow skipping a vsync. It does not include rounding errors either
|
||||
(which can happen especially with bad source timestamps). For example,
|
||||
using the ``display-desync`` mode should never change this value from 0.
|
||||
|
||||
``vsync-ratio``
|
||||
For how many vsyncs a frame is displayed on average. This is available if
|
||||
display-sync is active only. For 30 FPS video on a 60 Hz screen, this will
|
||||
be 2. This is the moving average of what actually has been scheduled, so
|
||||
24 FPS on 60 Hz will never remain exactly on 2.5, but jitter depending on
|
||||
the last frame displayed.
|
||||
|
||||
``vo-delayed-frame-count``
|
||||
Estimated number of frames delayed due to external circumstances in
|
||||
display-sync mode. Note that in general, mpv has to guess that this is
|
||||
happening, and the guess can be inaccurate.
|
||||
|
||||
``percent-pos`` (RW)
|
||||
Position in current file (0-100). The advantage over using this instead of
|
||||
calculating it out of other properties is that it properly falls back to
|
||||
@@ -923,8 +947,9 @@ Property list
|
||||
Position in current file in seconds.
|
||||
|
||||
``time-start``
|
||||
Return the start time of the file. (Usually 0, but some kind of files,
|
||||
especially transport streams, can have a different start time.)
|
||||
Deprecated. Always returns 0. Before mpv 0.14, this used to return the start
|
||||
time of the file (could affect e.g. transport streams). See
|
||||
``--rebase-start-time`` option.
|
||||
|
||||
``time-remaining``
|
||||
Remaining length of the file in seconds. Note that the file duration is not
|
||||
@@ -934,9 +959,11 @@ Property list
|
||||
``time-remaining`` scaled by the current ``speed``.
|
||||
|
||||
``playback-time`` (RW)
|
||||
The playback time, which is the time relative to playback start. (This can
|
||||
be different from the ``time-pos`` property if the file does not start at
|
||||
position ``0``, in which case ``time-pos`` is the source timestamp.)
|
||||
Position in current file in seconds. Unlike ``time-pos``, the time is
|
||||
clamped to the range of the file. (Inaccurate file durations etc. could
|
||||
make it go out of range. Also helpful when the user attempts to seek
|
||||
outside of the file, as the seek target time is considered the current
|
||||
position during seeking.)
|
||||
|
||||
``chapter`` (RW)
|
||||
Current chapter number. The number of the first chapter is 0.
|
||||
@@ -1171,9 +1198,6 @@ Property list
|
||||
is loaded, or when switching ordered chapter segments. This is because
|
||||
the same underlying code is used for seeking and resyncing.)
|
||||
|
||||
``pts-association-mode`` (RW)
|
||||
See ``--pts-association-mode``.
|
||||
|
||||
``hr-seek`` (RW)
|
||||
See ``--hr-seek``.
|
||||
|
||||
@@ -1260,8 +1284,8 @@ Property list
|
||||
``colormatrix-input-range`` (R)
|
||||
See ``colormatrix``.
|
||||
|
||||
``colormatrix-output-range`` (R)
|
||||
See ``colormatrix``.
|
||||
``video-output-levels`` (RW)
|
||||
See ``--video-output-levels``,
|
||||
|
||||
``colormatrix-primaries`` (R)
|
||||
See ``colormatrix``.
|
||||
@@ -1421,6 +1445,19 @@ Property list
|
||||
|
||||
Has the same sub-properties as ``video-params``.
|
||||
|
||||
``video-frame-info``
|
||||
Approximate information of the current frame. Note that if any of these
|
||||
are used on OSD, the information might be off by a few frames due to OSD
|
||||
redrawing and frame display being somewhat disconnected, and you might
|
||||
have to pause and force a redraw.
|
||||
|
||||
Sub-properties:
|
||||
|
||||
``video-frame-info/picture-type``
|
||||
``video-frame-info/interlaced``
|
||||
``video-frame-info/tff``
|
||||
``video-frame-info/repeat``
|
||||
|
||||
``fps``
|
||||
Container FPS. This can easily contain bogus values. For videos that use
|
||||
modern container formats or video codecs, this will often be incorrect.
|
||||
@@ -1447,13 +1484,21 @@ Property list
|
||||
Names of the displays that the mpv window covers. On X11, these
|
||||
are the xrandr names (LVDS1, HDMI1, DP1, VGA1, etc.).
|
||||
|
||||
``display-fps``
|
||||
``display-fps`` (RW)
|
||||
The refresh rate of the current display. Currently, this is the lowest FPS
|
||||
of any display covered by the video, as retrieved by the underlying system
|
||||
APIs (e.g. xrandr on X11). It is not the measured FPS. It's not necessarily
|
||||
available on all platforms. Note that any of the listed facts may change
|
||||
any time without a warning.
|
||||
|
||||
``estimated-display-fps``
|
||||
Only available if display-sync mode (as selected by ``--video-sync``) is
|
||||
active. Returns the actual rate at which display refreshes seem to occur,
|
||||
measured by system time.
|
||||
|
||||
``vsync-jitter``
|
||||
Estimated deviation factor of the vsync duration.
|
||||
|
||||
``video-aspect`` (RW)
|
||||
Video aspect, see ``--video-aspect``.
|
||||
|
||||
|
||||
+66
-68
@@ -128,7 +128,7 @@ x and z
|
||||
Adjust subtitle delay by +/- 0.1 seconds.
|
||||
|
||||
l
|
||||
Set/clear A-B loop points. See ``ab_loop`` command for details.
|
||||
Set/clear A-B loop points. See ``ab-loop`` command for details.
|
||||
|
||||
L
|
||||
Toggle infinite looping.
|
||||
@@ -192,23 +192,19 @@ corresponding adjustment, or the software equalizer (``--vf=eq``).)
|
||||
7 and 8
|
||||
Adjust saturation.
|
||||
|
||||
(The following keys are valid only on OS X.)
|
||||
|
||||
command + 0
|
||||
Alt+0 (and command+0 on OSX)
|
||||
Resize video window to half its original size.
|
||||
(On other platforms, you can bind keys to change the ``window-scale``
|
||||
property.)
|
||||
|
||||
command + 1
|
||||
Alt+1 (and command+1 on OSX)
|
||||
Resize video window to its original size.
|
||||
|
||||
command + 2
|
||||
Alt+2 (and command+2 on OSX)
|
||||
Resize video window to double its original size.
|
||||
|
||||
command + f
|
||||
command + f (OSX only)
|
||||
Toggle fullscreen (see also ``--fs``).
|
||||
|
||||
command + [ and command + ]
|
||||
command + [ and command + ] (OSX only)
|
||||
Set video window alpha.
|
||||
|
||||
(The following keys are valid if you have a keyboard with multimedia keys.)
|
||||
@@ -262,7 +258,7 @@ parser puts several options into a single string, and passes them to a
|
||||
component at once, instead of using multiple options on the level of the
|
||||
command line.
|
||||
|
||||
The suboption parser can quote strings with ``"``, ``'``, and ``[...]``.
|
||||
The suboption parser can quote strings with ``"`` and ``[...]``.
|
||||
Additionally, there is a special form of quoting with ``%n%`` described below.
|
||||
|
||||
For example, the ``opengl`` VO can take multiple options:
|
||||
@@ -392,10 +388,11 @@ You can put all of the options in configuration files which will be read every
|
||||
time mpv is run. The system-wide configuration file 'mpv.conf' is in your
|
||||
configuration directory (e.g. ``/etc/mpv`` or ``/usr/local/etc/mpv``), the
|
||||
user-specific one is ``~/.config/mpv/mpv.conf``. For details and platform
|
||||
specifics see the `FILES`_ section.
|
||||
specifics (in particular Windows paths) see the `FILES`_ section.
|
||||
|
||||
User-specific options override system-wide options and options given on the
|
||||
command line override either. The syntax of the configuration files is
|
||||
``option=<value>``; everything after a *#* is considered a comment. Options
|
||||
``option=value``. Everything after a *#* is considered a comment. Options
|
||||
that work without values can be enabled by setting them to *yes* and disabled by
|
||||
setting them to *no*. Even suboptions can be specified in this way.
|
||||
|
||||
@@ -413,7 +410,7 @@ Escaping spaces and special characters
|
||||
|
||||
This is done like with command line options. The shell is not involved here,
|
||||
but option values still need to be quoted as a whole if it contains certain
|
||||
characters like spaces. A config entry can be quoted with ``"`` and ``'``,
|
||||
characters like spaces. A config entry can be quoted with ``"``,
|
||||
as well as with the fixed-length syntax (``%n%``) mentioned before. This is like
|
||||
passing the exact contents of the quoted string as command line option. C-style
|
||||
escapes are currently _not_ interpreted on this level, although some options do
|
||||
@@ -460,7 +457,38 @@ description (shown by ``--profile=help``) can be defined with the
|
||||
``profile-desc`` option. To end the profile, start another one or use the
|
||||
profile name ``default`` to continue with normal options.
|
||||
|
||||
.. admonition:: Example mpv profile
|
||||
.. admonition:: Example mpv config file with profiles
|
||||
|
||||
::
|
||||
|
||||
# normal top-level option
|
||||
fullscreen=yes
|
||||
|
||||
# a profile that can be enabled with --profile=big-cache
|
||||
[big-cache]
|
||||
cache=123400
|
||||
demuxer-readahead-secs=20
|
||||
|
||||
[slow]
|
||||
profile-desc="some profile name"
|
||||
vo=opengl:scale=ewa_lanczos:scale-radius=16
|
||||
|
||||
[fast]
|
||||
vo=vdpau
|
||||
|
||||
# using a profile again extends it
|
||||
[slow]
|
||||
framedrop=no
|
||||
# you can also include other profiles
|
||||
profile=big-cache
|
||||
|
||||
|
||||
Auto profiles
|
||||
-------------
|
||||
|
||||
Some profiles are loaded automatically. The following example demonstrates this:
|
||||
|
||||
.. admonition:: Auto profile loading
|
||||
|
||||
::
|
||||
|
||||
@@ -479,6 +507,13 @@ profile name ``default`` to continue with normal options.
|
||||
[ao.alsa]
|
||||
device=spdif
|
||||
|
||||
The profile name follows the schema ``type.name``, where type can be ``vo``
|
||||
to match the value the ``--vo`` option is set to, ``ao`` for ``--ao``,
|
||||
``protocol`` for the input/output protocol in use (see ``--list-protocols``),
|
||||
and ``extension`` for the extension of the path of the currently played file
|
||||
(*not* the file format).
|
||||
|
||||
This feature is very limited, and there are no other auto profiles.
|
||||
|
||||
TAKING SCREENSHOTS
|
||||
==================
|
||||
@@ -527,13 +562,13 @@ listed.
|
||||
this will indicate a problem. (``total-avsync-change`` property.)
|
||||
- Encoding state in ``{...}``, only shown in encoding mode.
|
||||
- Display sync state. If display sync is active (``display-sync-active``
|
||||
property), this shows ``DS: +0.02598%``, where the number is the speed change
|
||||
factor applied to audio to achieve sync to display, expressed in percent
|
||||
deviation from 1.0 (``audio-speed-correction`` property). In sync modes which
|
||||
don't resample, this will always be ``+0.00000%``.
|
||||
- Missed frames, e.g. ``Missed: 4``. (``vo-missed-frame-count`` property.) Shows
|
||||
up in display sync mode only. This is incremented each time a frame took
|
||||
longer to display than intended.
|
||||
property), this shows ``DS: 2.500/13``, where the first number is average
|
||||
number of vsyncs per video frame (e.g. 2.5 when playing 24Hz videos on 60Hz
|
||||
screens), which might jitter if the ratio doesn't round off, or there are
|
||||
mistimed frames (``vsync-ratio``), and the second number of estimated number
|
||||
of vsyncs which took too long (``vo-delayed-frame-count`` property). The
|
||||
latter is a heuristic, as it's generally not possible to determine this with
|
||||
certainty.
|
||||
- Dropped frames, e.g. ``Dropped: 4``. Shows up only if the count is not 0. Can
|
||||
grow if the video framerate is higher than that of the display, or if video
|
||||
rendering is too slow. Also can be incremented on "hiccups" and when the video
|
||||
@@ -688,6 +723,15 @@ The profile always overrides other settings in ``mpv.conf``.
|
||||
|
||||
.. include:: changes.rst
|
||||
|
||||
|
||||
EMBEDDING INTO OTHER PROGRAMS (LIBMPV)
|
||||
======================================
|
||||
|
||||
mpv can be embedded into other programs as video/audio playback backend. The
|
||||
recommended way to to so is using libmpv. See ``libmpv/client.h`` in the mpv
|
||||
source code repository. This provides a C API. Bindings for other languages
|
||||
might be available (see wiki).
|
||||
|
||||
ENVIRONMENT VARIABLES
|
||||
=====================
|
||||
|
||||
@@ -905,49 +949,3 @@ future.
|
||||
|
||||
Note that mpv likes to mix ``/`` and ``\`` path separators for simplicity.
|
||||
kernel32.dll accepts this, but cmd.exe does not.
|
||||
|
||||
EXAMPLES OF MPV USAGE
|
||||
=====================
|
||||
|
||||
Blu-ray playback:
|
||||
- ``mpv bd:////path/to/disc``
|
||||
- ``mpv bd:// --bluray-device=/path/to/disc``
|
||||
|
||||
Play in Japanese with English subtitles:
|
||||
``mpv dvd://1 --alang=ja --slang=en``
|
||||
|
||||
Play only chapters 5, 6, 7:
|
||||
``mpv dvd://1 --chapter=5-7``
|
||||
|
||||
Play only titles 5, 6, 7:
|
||||
``mpv dvd://5-7``
|
||||
|
||||
Play a multi-angle DVD:
|
||||
``mpv dvd://1 --dvd-angle=2``
|
||||
|
||||
Play from a different DVD device:
|
||||
``mpv dvd://1 --dvd-device=/dev/dvd2``
|
||||
|
||||
Play DVD video from a directory with VOB files:
|
||||
``mpv dvd://1 --dvd-device=/path/to/directory/``
|
||||
|
||||
Stream from HTTP:
|
||||
``mpv http://example.com/example.avi``
|
||||
|
||||
Stream using RTSP:
|
||||
``mpv rtsp://server.example.com/streamName``
|
||||
|
||||
Play a libavfilter graph:
|
||||
``mpv avdevice://lavfi:mandelbrot``
|
||||
|
||||
AUTHORS
|
||||
=======
|
||||
|
||||
mpv is a MPlayer fork based on mplayer2, which in turn is a fork of MPlayer.
|
||||
|
||||
MPlayer was initially written by Arpad Gereoffy. See the ``AUTHORS`` file for
|
||||
a list of some of the many other contributors.
|
||||
|
||||
MPlayer is (C) 2000-2013 The MPlayer Team
|
||||
|
||||
This man page was written mainly by Gabucino, Jonas Jermann and Diego Biurrun.
|
||||
+114
-125
@@ -69,7 +69,8 @@ Playback Control
|
||||
|
||||
The general format for absolute times is ``[[hh:]mm:]ss[.ms]``. If the time
|
||||
is given with a prefix of ``+`` or ``-``, the seek is relative from the start
|
||||
or end of the file.
|
||||
or end of the file. (Since mpv 0.14, the start of the file is always
|
||||
considered 0.)
|
||||
|
||||
``pp%`` seeks to percent position pp (0-100).
|
||||
|
||||
@@ -101,6 +102,14 @@ Playback Control
|
||||
Stop after a given time relative to the start time.
|
||||
See ``--start`` for valid option values and examples.
|
||||
|
||||
``--rebase-start-time=<yes|no>``
|
||||
Whether to move the file start time to ``00:00:00`` (default: yes). This
|
||||
is less awkward for files which start at a random timestamp, such as
|
||||
transport streams. On the other hand, if there are timestamp resets, the
|
||||
resulting behavior can be rather weird. For this reason, and in case you
|
||||
are actually interested in the real timestamps, this behavior can be
|
||||
disabled with ``no``.
|
||||
|
||||
``--speed=<0.01-100>``
|
||||
Slow down or speed up playback by the factor given as parameter.
|
||||
|
||||
@@ -456,16 +465,13 @@ Program Behavior
|
||||
|
||||
If the script can't do anything with an URL, it will do nothing.
|
||||
|
||||
(Note: this is the replacement for the now removed libquvi support.)
|
||||
|
||||
``--ytdl-format=<best|worst|mp4|webm|...>``
|
||||
Video format/quality that is directly passed to youtube-dl. The possible
|
||||
values are specific to the website and the video, for a given url the
|
||||
available formats can be found with the command
|
||||
``youtube-dl --list-formats URL``. See youtube-dl's documentation for
|
||||
available aliases. To use experimental DASH support for youtube, use
|
||||
``bestvideo+bestaudio``.
|
||||
(Default: ``best``)
|
||||
available aliases.
|
||||
(Default: youtube-dl's default, currently ``bestvideo+bestaudio/best``)
|
||||
|
||||
``--ytdl-raw-options=<key>=<value>[,<key>=<value>[,...]]``
|
||||
Pass arbitrary options to youtube-dl. Parameter and argument should be
|
||||
@@ -551,11 +557,10 @@ Video
|
||||
differences to other VOs are possible.
|
||||
|
||||
``--display-fps=<fps>``
|
||||
Set the maximum assumed display FPS used with ``--framedrop``. By default
|
||||
a detected value is used (X11 only, not correct on multi-monitor systems),
|
||||
or infinite display FPS if that fails. Infinite FPS means only frames too
|
||||
late are dropped. If a correct FPS is provided, frames that are predicted
|
||||
to be too late are dropped too.
|
||||
Set the display FPS used with the ``--video-sync=display-*`` modes. By
|
||||
default a detected value is used (X11 only, not correct on multi-monitor
|
||||
systems). Keep in mind that setting an incorrect value (even if slightly
|
||||
incorrect) can ruin video playback.
|
||||
|
||||
``--hwdec=<api>``
|
||||
Specify the hardware video decoding API that should be used if possible.
|
||||
@@ -567,12 +572,11 @@ Video
|
||||
:no: always use software decoding (default)
|
||||
:auto: see below
|
||||
:vdpau: requires ``--vo=vdpau`` or ``--vo=opengl`` (Linux only)
|
||||
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux with Intel GPUs only)
|
||||
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux only)
|
||||
:vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
|
||||
:vda: requires ``--vo=opengl`` (OS X only)
|
||||
:videotoolbox: requires ``--vo=opengl`` (newer OS X only)
|
||||
:videotoolbox: requires ``--vo=opengl`` (OS X 10.8 and up only)
|
||||
:dxva2-copy: copies video back to system RAM (Windows only)
|
||||
:rpi: requires ``--vo=rpi`` (Raspberry Pi only - default if available)
|
||||
:rpi: requires ``--vo=rpi`` (Raspberry Pi only - default if available)
|
||||
|
||||
``auto`` tries to automatically enable hardware decoding using the first
|
||||
available method. This still depends what VO you are using. For example,
|
||||
@@ -581,6 +585,12 @@ Video
|
||||
work, it will always fall back to software decoding, instead of trying the
|
||||
next method (might matter on some Linux systems).
|
||||
|
||||
The ``vaapi`` mode, if used with ``--vo=opengl``, requires Mesa 11 and most
|
||||
likely works with Intel GPUs only. It also requires the opengl EGL backend
|
||||
(automatically used if available). You can also try the old GLX backend by
|
||||
forcing it with ``--vo=opengl:backend=x11``, but the vaapi/GLX interop is
|
||||
said to be slower than ``vaapi-copy``.
|
||||
|
||||
The ``vaapi-copy`` mode allows you to use vaapi with any VO. Because
|
||||
this copies the decoded video back to system RAM, it's likely less efficient
|
||||
than the ``vaapi`` mode.
|
||||
@@ -607,6 +617,14 @@ Video
|
||||
to temporarily set the ``hwdec`` option just during OpenGL context
|
||||
initialization with ``mpv_opengl_cb_init_gl()``.
|
||||
|
||||
``--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
|
||||
hand, there doesn't appear to be a good way to detect the best format for
|
||||
the given hardware. ``nv12``, the default, works better on modern hardware,
|
||||
while ``uyvy422`` appears to be better for old hardware. ``rgb0`` also
|
||||
works.
|
||||
|
||||
``--panscan=<0.0-1.0>``
|
||||
Enables pan-and-scan functionality (cropping the sides of e.g. a 16:9
|
||||
video to make it fit a 4:3 display without black bands). The range
|
||||
@@ -745,7 +763,7 @@ Video
|
||||
if supported.
|
||||
|
||||
This behaves exactly like the ``deinterlace`` input property (usually
|
||||
mapped to ``Shift+D``).
|
||||
mapped to ``d``).
|
||||
|
||||
``auto`` is a technicality. Strictly speaking, the default for this option
|
||||
is deinterlacing disabled, but the ``auto`` case is needed if ``yadif`` was
|
||||
@@ -775,6 +793,26 @@ Video
|
||||
For audio-only playback, any value greater than 0 will quit playback
|
||||
immediately after initialization. The value 0 works as with video.
|
||||
|
||||
``--video-output-levels=<outputlevels>``
|
||||
RGB color levels used with YUV to RGB conversion. Normally, output devices
|
||||
such as PC monitors use full range color levels. However, some TVs and
|
||||
video monitors expect studio RGB levels. Providing full range output to a
|
||||
device expecting studio level input results in crushed blacks and whites,
|
||||
the reverse in dim gray blacks and dim whites.
|
||||
|
||||
Not all VOs support this option. Some will silently ignore it.
|
||||
|
||||
Available color ranges are:
|
||||
|
||||
:auto: automatic selection (equals to full range) (default)
|
||||
:limited: limited range (16-235 per component), studio levels
|
||||
:full: full range (0-255 per component), PC levels
|
||||
|
||||
.. note::
|
||||
|
||||
It is advisable to use your graphics driver's color range option
|
||||
instead, if available.
|
||||
|
||||
``--hwdec-codecs=<codec1,codec2,...|all>``
|
||||
Allow hardware decoding for a given list of codecs only. The special value
|
||||
``all`` always allows all codecs.
|
||||
@@ -782,9 +820,9 @@ Video
|
||||
You can get the list of allowed codecs with ``mpv --vd=help``. Remove the
|
||||
prefix, e.g. instead of ``lavc:h264`` use ``h264``.
|
||||
|
||||
By default this is set to ``h264,vc1,wmv3,hevc``. Note that the hardware
|
||||
acceleration special codecs like ``h264_vdpau`` are not relevant anymore,
|
||||
and in fact have been removed from Libav in this form.
|
||||
By default this is set to ``h264,vc1,wmv3,hevc,mpeg2video``. Note that the
|
||||
hardware acceleration special codecs like ``h264_vdpau`` are not relevant
|
||||
anymore, and in fact have been removed from Libav in this form.
|
||||
|
||||
This is usually only needed with broken GPUs, where a codec is reported
|
||||
as supported, but decoding causes more problems than it solves.
|
||||
@@ -801,6 +839,11 @@ Video
|
||||
The result is most likely broken decoding, but may also help if the
|
||||
detected or reported profiles are somehow incorrect.
|
||||
|
||||
``--vd-lavc-software-fallback=<yes|no|N>``
|
||||
Fallback to software decoding if the hardware-accelerated decoder fails
|
||||
(default: 3). If this is a number, then fallback will be triggered if
|
||||
N frames fail to decode in a row. 1 is equivalent to ``yes``.
|
||||
|
||||
``--vd-lavc-bitexact``
|
||||
Only use bit-exact algorithms in all decoding steps (for codec testing).
|
||||
|
||||
@@ -821,7 +864,7 @@ Video
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``--vd--lavc-o=debug=pict``
|
||||
``--vd-lavc-o=debug=pict``
|
||||
|
||||
``--vd-lavc-show-all=<yes|no>``
|
||||
Show even broken/corrupt frames (default: no). If this option is set to
|
||||
@@ -892,6 +935,14 @@ Audio
|
||||
|
||||
Currently not implemented for most AOs.
|
||||
|
||||
``--audio-fallback-to-null=<yes|no>``
|
||||
If no audio device can be opened, behave as if ``--ao=null`` was given. This
|
||||
is useful in combination with ``--audio-device``: instead of causing an
|
||||
error if the selected device does not exist, the client API user (or a
|
||||
Lua script) could let playback continue normally, and check the
|
||||
``current-ao`` and ``audio-device-list`` properties to make high-level
|
||||
decisions about how to continue.
|
||||
|
||||
``--ao=<driver1[:suboption1[=value]:...],driver2,...[,]>``
|
||||
Specify a priority list of audio output drivers to be used. For
|
||||
interactive use one would normally specify a single one to use, but in
|
||||
@@ -1283,8 +1334,8 @@ Subtitles
|
||||
|
||||
``--sub-speed=<0.1-10.0>``
|
||||
Multiply the subtitle event timestamps with the given value. Can be used
|
||||
to fix the playback speed for frame-based subtitle formats. Works for
|
||||
external text subtitles only.
|
||||
to fix the playback speed for frame-based subtitle formats. Affects text
|
||||
subtitles only.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
@@ -1532,17 +1583,17 @@ Subtitles
|
||||
This mode doesn't take language or fallback codepage.
|
||||
|
||||
``--sub-fix-timing``, ``--no-sub-fix-timing``
|
||||
By default, external text subtitles are preprocessed to remove minor gaps
|
||||
or overlaps between subtitles (if the difference is smaller than 200 ms,
|
||||
the gap or overlap is removed). This does not affect image subtitles,
|
||||
subtitles muxed with audio/video, or subtitles in the ASS format.
|
||||
By default, subtitle timing is adjusted to remove minor gaps or overlaps
|
||||
between subtitles (if the difference is smaller than 210 ms, the gap or
|
||||
overlap is removed).
|
||||
|
||||
``--sub-forced-only``
|
||||
Display only forced subtitles for the DVD subtitle stream selected by e.g.
|
||||
``--slang``.
|
||||
|
||||
``--sub-fps=<rate>``
|
||||
Specify the framerate of the subtitle file (default: video fps).
|
||||
Specify the framerate of the subtitle file (default: video fps). Affects
|
||||
text subtitles only.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -1702,6 +1753,10 @@ Window
|
||||
``--ontop``
|
||||
Makes the player window stay on top of other windows.
|
||||
|
||||
On Windows, if combined with fullscreen mode, this causes mpv to be
|
||||
treated as exclusive fullscreen window that bypasses the Desktop Window
|
||||
Manager.
|
||||
|
||||
``--border``, ``--no-border``
|
||||
Play video with window border and decorations. Since this is on by
|
||||
default, use ``--no-border`` to disable the standard window decorations.
|
||||
@@ -1980,6 +2035,10 @@ Window
|
||||
|
||||
This option might be removed in the future.
|
||||
|
||||
``--x11-bypass-compositor=<yes|no>``
|
||||
If set to ``yes`` (default), then ask the compositor to unredirect the
|
||||
mpv window. This uses the ``_NET_WM_BYPASS_COMPOSITOR`` hint.
|
||||
|
||||
|
||||
Disc Devices
|
||||
------------
|
||||
@@ -2210,18 +2269,6 @@ Demuxer
|
||||
file and can make a reliable estimate even without an index present (such
|
||||
as partial files).
|
||||
|
||||
``--demuxer-mkv-fix-timestamps=<yes|no>``
|
||||
Fix rounded Matroska timestamps (disabled by default). Matroska usually
|
||||
stores timestamps rounded to milliseconds. This means timestamps jitter
|
||||
by some amount around the intended timestamp. mpv can correct the timestamps
|
||||
based on the framerate value stored in the file: the timestamp is rounded
|
||||
to the next frame (according to the framerate), unless the new timestamp
|
||||
would deviate more than 1ms from the old one. This should undo the rounding
|
||||
done by the muxer.
|
||||
|
||||
(The allowed deviation can be less than 1ms if the file uses a non-standard
|
||||
timecode scale.)
|
||||
|
||||
``--demuxer-rawaudio-channels=<value>``
|
||||
Number of channels (or channel layout) if ``--demuxer=rawaudio`` is used
|
||||
(default: stereo).
|
||||
@@ -2441,14 +2488,14 @@ OSD
|
||||
``--osd-duration=<time>``
|
||||
Set the duration of the OSD messages in ms (default: 1000).
|
||||
|
||||
``--osd-font=<pattern>``, ``--sub-text-font=<pattern>``
|
||||
``--osd-font=<name>``, ``--sub-text-font=<name>``
|
||||
Specify font to use for OSD and for subtitles that do not themselves
|
||||
specify a particular font. The default is ``sans-serif``.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
- ``--osd-font='Bitstream Vera Sans'``
|
||||
- ``--osd-font='Bitstream Vera Sans:style=Bold'`` (fontconfig pattern)
|
||||
- ``--osd-font='MS Comic Sans'``
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -2456,6 +2503,9 @@ OSD
|
||||
options) are ignored when ASS-subtitles are rendered, unless the
|
||||
``--no-sub-ass`` option is specified.
|
||||
|
||||
This used to support fontconfig patterns. Starting with libass 0.13.0,
|
||||
this stopped working.
|
||||
|
||||
``--osd-font-size=<size>``, ``--sub-text-font-size=<size>``
|
||||
Specify the OSD/sub font size. The unit is the size in scaled pixels at a
|
||||
window height of 720. The actual pixel size is scaled with the window
|
||||
@@ -2636,17 +2686,6 @@ OSD
|
||||
|
||||
Default: 0.
|
||||
|
||||
``--use-text-osd=<yes|no>``
|
||||
Disable text OSD rendering completely. (This includes the complete OSC as
|
||||
well.) This is mostly useful for avoiding loading fontconfig in situations
|
||||
where fontconfig does not behave well, and OSD is unused - this could for
|
||||
example allow GUI programs using libmpv to workaround fontconfig issues.
|
||||
|
||||
Note that selecting subtitles of any kind still initializes fontconfig.
|
||||
|
||||
Default: ``no``.
|
||||
|
||||
|
||||
Screenshot
|
||||
----------
|
||||
|
||||
@@ -3245,6 +3284,13 @@ Network
|
||||
Verify peer certificates when using TLS (e.g. with ``https://...``).
|
||||
(Silently fails with older FFmpeg or Libav versions.)
|
||||
|
||||
``--tls-cert-file``
|
||||
A file containing a certificate to use in the handshake with the
|
||||
peer.
|
||||
|
||||
``--tls-key-file``
|
||||
A file containing the private key for the certificate.
|
||||
|
||||
``--referrer=<string>``
|
||||
Specify a referrer path or URL for HTTP requests.
|
||||
|
||||
@@ -3307,65 +3353,6 @@ DVB
|
||||
|
||||
Default: ``no``
|
||||
|
||||
PVR
|
||||
---
|
||||
|
||||
``--pvr-...``
|
||||
These options tune various encoding properties of the PVR capture module.
|
||||
It has to be used with any hardware MPEG encoder based card supported by
|
||||
the V4L2 driver. The Hauppauge WinTV PVR-150/250/350/500 and all IVTV
|
||||
based cards are known as PVR capture cards. Be aware that only Linux
|
||||
2.6.18 kernel and above is able to handle MPEG stream through V4L2 layer.
|
||||
For hardware capture of an MPEG stream and watching it with mpv, use
|
||||
``pvr://`` as media URL.
|
||||
|
||||
|
||||
``--pvr-aspect=<0-3>``
|
||||
Specify input aspect ratio:
|
||||
|
||||
:0: 1:1
|
||||
:1: 4:3 (default)
|
||||
:2: 16:9
|
||||
:3: 2.21:1
|
||||
|
||||
``--pvr-arate=<32000-48000>``
|
||||
Specify encoding audio rate (default: 48000 Hz, available: 32000,
|
||||
44100 and 48000 Hz).
|
||||
|
||||
``--pvr-alayer=<1-3>``
|
||||
Specify MPEG audio layer encoding (default: 2).
|
||||
|
||||
``--pvr-abitrate=<32-448>``
|
||||
Specify audio encoding bitrate in kbps (default: 384).
|
||||
|
||||
``--pvr-amode=<value>``
|
||||
Specify audio encoding mode. Available preset values are 'stereo',
|
||||
'joint_stereo', 'dual' and 'mono' (default: stereo).
|
||||
|
||||
``--pvr-vbitrate=<value>``
|
||||
Specify average video bitrate encoding in Mbps (default: 6).
|
||||
|
||||
``--pvr-vmode=<value>``
|
||||
Specify video encoding mode:
|
||||
|
||||
:vbr: Variable Bit Rate (default)
|
||||
:cbr: Constant Bit Rate
|
||||
|
||||
``--pvr-vpeak=<value>``
|
||||
Specify peak video bitrate encoding in Mbps (only useful for VBR
|
||||
encoding, default: 9.6).
|
||||
|
||||
``--pvr-fmt=<value>``
|
||||
Choose an MPEG format for encoding:
|
||||
|
||||
:ps: MPEG-2 Program Stream (default)
|
||||
:ts: MPEG-2 Transport Stream
|
||||
:mpeg1: MPEG-1 System Stream
|
||||
:vcd: Video CD compatible stream
|
||||
:svcd: Super Video CD compatible stream
|
||||
:dvd: DVD compatible stream
|
||||
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
|
||||
@@ -3430,6 +3417,11 @@ Miscellaneous
|
||||
:display-vdrop: Drop or repeat video frames to compensate desyncing
|
||||
video. (Although it should have the same effects as
|
||||
``audio``, the implementation is very different.)
|
||||
:display-adrop: Drop or repeat audio data to compensate desyncing
|
||||
video. See ``--video-sync-adrop-size``. This mode will
|
||||
cause severe audio artifacts if the real monitor
|
||||
refresh rate is too different from the reported or
|
||||
forced rate.
|
||||
:display-desync: Sync video to display, and let audio play on its own.
|
||||
:desync: Sync video according to system clock, and let audio play
|
||||
on its own.
|
||||
@@ -3461,6 +3453,13 @@ Miscellaneous
|
||||
frame dropping due to the audio "overshooting" and skipping multiple video
|
||||
frames before the sync logic can react.
|
||||
|
||||
``--video-sync-adrop-size=<value``
|
||||
For the ``--video-sync=display-adrop`` mode. This mode duplicates/drops
|
||||
audio data to keep audio in sync with video. To avoid audio artifacts on
|
||||
jitter (which would add/remove samples all the time), this is done in
|
||||
relatively large, fixed units, controlled by this option. The unit is
|
||||
seconds.
|
||||
|
||||
``--mf-fps=<value>``
|
||||
Framerate used when decoding from multiple PNG or JPEG files with ``mf://``
|
||||
(default: 1).
|
||||
@@ -3492,6 +3491,10 @@ Miscellaneous
|
||||
other options such as e.g. user agent are not available with all protocols,
|
||||
and printing errors for unknown options would end up being too noisy.)
|
||||
|
||||
``--vo-mmcss-profile=<name>``
|
||||
(Windows only.)
|
||||
Set the MMCSS profile for the video renderer thread (default: ``Playback``).
|
||||
|
||||
``--priority=<prio>``
|
||||
(Windows only.)
|
||||
Set process priority for mpv according to the predefined priorities
|
||||
@@ -3502,20 +3505,6 @@ Miscellaneous
|
||||
|
||||
.. warning:: Using realtime priority can cause system lockup.
|
||||
|
||||
``--pts-association-mode=<decode|sort|auto>``
|
||||
Select the method used to determine which container packet timestamp
|
||||
corresponds to a particular output frame from the video decoder. Normally
|
||||
you should not need to change this option.
|
||||
|
||||
:decoder: Use decoder reordering functionality. Unlike in classic MPlayer
|
||||
and mplayer2, this includes a DTS fallback. (Default.)
|
||||
:sort: Maintain a buffer of unused pts values and use the lowest value
|
||||
for the frame.
|
||||
:auto: Try to pick a working mode from the ones above automatically.
|
||||
|
||||
You can also try to use ``--no-correct-pts`` for files with completely
|
||||
broken timestamps.
|
||||
|
||||
``--force-media-title=<string>``
|
||||
Force the contents of the ``media-title`` property to this value. Useful
|
||||
for scripts which want to set a title, without overriding the user's
|
||||
|
||||
+5
-25
@@ -259,26 +259,6 @@ Available filters are:
|
||||
:limited: limited range (16-235 for luma, 16-240 for chroma)
|
||||
:full: full range (0-255 for both luma and chroma)
|
||||
|
||||
``<outputlevels>``
|
||||
RGB color levels used with YUV to RGB conversion. Normally, output devices
|
||||
such as PC monitors use full range color levels. However, some TVs and
|
||||
video monitors expect studio RGB levels. Providing full range output to a
|
||||
device expecting studio level input results in crushed blacks and whites,
|
||||
the reverse in dim gray blacks and dim whites.
|
||||
|
||||
The same limitations as with ``<colormatrix>`` apply.
|
||||
|
||||
Available color ranges are:
|
||||
|
||||
:auto: automatic selection (equals to full range) (default)
|
||||
:limited: limited range (16-235 per component), studio levels
|
||||
:full: full range (0-255 per component), PC levels
|
||||
|
||||
.. note::
|
||||
|
||||
It is advisable to use your graphics driver's color range option
|
||||
instead, if available.
|
||||
|
||||
``<primaries>``
|
||||
RGB primaries the source file was encoded with. Normally this should be set
|
||||
in the file header, but when playing broken or mistagged files this can be
|
||||
@@ -484,16 +464,16 @@ Available filters are:
|
||||
:yes: Only deinterlace frames marked as interlaced (default if this
|
||||
filter is inserted via ``deinterlace`` property).
|
||||
|
||||
This filter, is automatically inserted when using the ``D`` key (or any
|
||||
This filter, is automatically inserted when using the ``d`` key (or any
|
||||
other key that toggles the ``deinterlace`` property or when using the
|
||||
``--deinterlace`` switch), assuming the video output does not have native
|
||||
deinterlacing support.
|
||||
|
||||
If you just want to set the default mode, put this filter and its options
|
||||
into ``--vf-defaults`` instead, and enable deinterlacing with ``D`` or
|
||||
into ``--vf-defaults`` instead, and enable deinterlacing with ``d`` or
|
||||
``--deinterlace``.
|
||||
|
||||
Also note that the ``D`` key is stupid enough to insert an interlacer twice
|
||||
Also note that the ``d`` key is stupid enough to insert a deinterlacer twice
|
||||
when inserting yadif with ``--vf``, so using the above methods is
|
||||
recommended.
|
||||
|
||||
@@ -741,7 +721,7 @@ Available filters are:
|
||||
``vavpp``
|
||||
VA-AP-API video post processing. Works with ``--vo=vaapi`` and ``--vo=opengl``
|
||||
only. Currently deinterlaces. This filter is automatically inserted if
|
||||
deinterlacing is requested (either using the ``D`` key, by default mapped to
|
||||
deinterlacing is requested (either using the ``d`` key, by default mapped to
|
||||
the command ``cycle deinterlace``, or the ``--deinterlace`` option).
|
||||
|
||||
``deint=<method>``
|
||||
@@ -765,7 +745,7 @@ Available filters are:
|
||||
``vdpaupp``
|
||||
VDPAU video post processing. Works with ``--vo=vdpau`` and ``--vo=opengl``
|
||||
only. This filter is automatically inserted if deinterlacing is requested
|
||||
(either using the ``D`` key, by default mapped to the command
|
||||
(either using the ``d`` key, by default mapped to the command
|
||||
``cycle deinterlace``, or the ``--deinterlace`` option). When enabling
|
||||
deinterlacing, it is always preferred over software deinterlacer filters
|
||||
if the ``vdpau`` VO is used, and also if ``opengl`` is used and hardware
|
||||
|
||||
+167
-58
@@ -20,13 +20,10 @@ normal driver parameters.
|
||||
|
||||
See ``--vo=help`` for a list of compiled-in video output drivers.
|
||||
|
||||
The recommended output drivers are ``--vo=vdpau`` and ``--vo=opengl-hq``.
|
||||
All other drivers are just for compatibility or special purposes.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``--vo=opengl,xv,``
|
||||
Try the ``opengl`` driver, then the ``xv`` driver, then others.
|
||||
The recommended output driver is ``--vo=opengl-hq``. All other drivers are
|
||||
for compatibility or special purposes. By default, ``--vo=opengl`` is used,
|
||||
but if that appears not to work, it fallback to other drivers (in the same
|
||||
order as listed by ``--vo=help``).
|
||||
|
||||
Available video output drivers are:
|
||||
|
||||
@@ -75,6 +72,12 @@ Available video output drivers are:
|
||||
not responding quickly enough if video FPS is close to or higher than
|
||||
the display refresh rate.
|
||||
|
||||
``x11`` (X11 only)
|
||||
Shared memory video output driver without hardware acceleration that works
|
||||
whenever X11 is present.
|
||||
|
||||
.. note:: This is a fallback only, and should not be normally used.
|
||||
|
||||
``vdpau`` (X11 only)
|
||||
Uses the VDPAU interface to display and optionally also decode video.
|
||||
Hardware decoding is used with ``--hwdec=vdpau``.
|
||||
@@ -102,7 +105,7 @@ Available video output drivers are:
|
||||
Select deinterlacing mode (default: 0). In older versions (as well as
|
||||
MPlayer/mplayer2) you could use this option to enable deinterlacing.
|
||||
This doesn't work anymore, and deinterlacing is enabled with either
|
||||
the ``D`` key (by default mapped to the command ``cycle deinterlace``),
|
||||
the ``d`` key (by default mapped to the command ``cycle deinterlace``),
|
||||
or the ``--deinterlace`` option. Also, to select the default deint mode,
|
||||
you should use something like ``--vf-defaults=vdpaupp:deint-mode=temporal``
|
||||
instead of this sub-option.
|
||||
@@ -308,7 +311,10 @@ Available video output drivers are:
|
||||
of bad or old hardware.
|
||||
|
||||
This mode is forced automatically if needed, and this option is mostly
|
||||
useful for debugging.
|
||||
useful for debugging. It's also enabled automatically if nothing uses
|
||||
features which require FBOs.
|
||||
|
||||
This option might be silently removed in the future.
|
||||
|
||||
``scale=<filter>``
|
||||
|
||||
@@ -375,10 +381,6 @@ Available video output drivers are:
|
||||
Scale parameter (``t``). Increasing this makes the result blurrier.
|
||||
Defaults to 1.
|
||||
|
||||
sharpen3, sharpen5
|
||||
Sharpening strength. Increasing this makes the image sharper but
|
||||
adds more ringing and aliasing. Defaults to 0.5.
|
||||
|
||||
oversample
|
||||
Minimum distance to an edge before interpolation is used. Setting
|
||||
this to 0 will always interpolate edges, whereas setting it to 0.5
|
||||
@@ -408,8 +410,8 @@ Available video output drivers are:
|
||||
between 0.0 and 1.0. The default value of 0.0 disables antiringing
|
||||
entirely.
|
||||
|
||||
Note that this doesn't affect the special filters ``bilinear``,
|
||||
``bicubic_fast`` or ``sharpen``.
|
||||
Note that this doesn't affect the special filters ``bilinear`` and
|
||||
``bicubic_fast``.
|
||||
|
||||
``scale-window=<window>``
|
||||
(Advanced users only) Choose a custom windowing function for the kernel.
|
||||
@@ -429,6 +431,15 @@ Available video output drivers are:
|
||||
Scale parameter (t). Increasing this makes the window wider.
|
||||
Defaults to 1.
|
||||
|
||||
``scaler-lut-size=<4..10>``
|
||||
Set the size of the lookup texture for scaler kernels (default: 6).
|
||||
The actual size of the texture is ``2^N`` for an option value of ``N``.
|
||||
So the lookup texture with the default setting uses 64 samples.
|
||||
|
||||
All weights are bilinearly interpolated from those samples, so
|
||||
increasing the size of lookup table might improve the accuracy of
|
||||
scaler.
|
||||
|
||||
``scaler-resizes-only``
|
||||
Disable the scaler if the video image is not resized. In that case,
|
||||
``bilinear`` is used instead whatever is set with ``scale``. Bilinear
|
||||
@@ -489,6 +500,10 @@ Available video output drivers are:
|
||||
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.
|
||||
@@ -520,7 +535,7 @@ Available video output drivers are:
|
||||
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 ``oversample``.
|
||||
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
|
||||
@@ -542,13 +557,82 @@ Available video output drivers are:
|
||||
Scale in linear light. It should only be used with a ``fbo-format``
|
||||
that has at least 16 bit precision.
|
||||
|
||||
``fancy-downscaling``
|
||||
``correct-downscaling``
|
||||
When using convolution based filters, extend the filter size
|
||||
when downscaling. Trades quality for reduced downscaling performance.
|
||||
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.
|
||||
|
||||
``prescale=<filter>``
|
||||
This option provides non-convolution-based filters for upscaling. These
|
||||
filters resize the video to multiple of the original size (all currently
|
||||
supported prescalers can only perform image doubling in a single pass).
|
||||
Generally another convolution based filter (the main scaler) will be
|
||||
applied after prescaler to match the target display size.
|
||||
|
||||
``none``
|
||||
Disable all prescalers. This is the default.
|
||||
|
||||
``superxbr``
|
||||
A relatively fast prescaler originally developed for pixel art.
|
||||
|
||||
Some parameters can be tuned with ``superxbr-sharpness`` and
|
||||
``superxbr-edge-strength`` options.
|
||||
|
||||
``nnedi3``
|
||||
An artificial neural network based deinterlacer, which can be used
|
||||
to upscale images.
|
||||
|
||||
Extremely slow and requires a recent mid or high end graphics card
|
||||
to work smoothly (as of 2015).
|
||||
|
||||
Note that all the filters above are designed (or implemented) to process
|
||||
luma plane only and probably won't work as intended for video in RGB
|
||||
format.
|
||||
|
||||
``prescale-passes=<1..5>``
|
||||
The number of passes to apply the prescaler (defaults to be 1). Setting
|
||||
it to 2 will perform a 4x upscaling.
|
||||
|
||||
``prescale-downscaling-threshold=<0..32>``
|
||||
This option prevents "overkill" use of prescalers, which can be caused
|
||||
by misconfiguration, or user trying to play a video with much larger
|
||||
size. With this option, user can specify the maximal allowed downscaling
|
||||
ratio in both dimension. To satisfy it, the number of passes for
|
||||
prescaler will be reduced, and if necessary prescaler could also be
|
||||
disabled.
|
||||
|
||||
The default value is 2.0, and should be able to prevent most seemingly
|
||||
unreasonable use of prescalers. Most user would probably want to set it
|
||||
to a smaller value between 1.0 and 1.5 for better performance.
|
||||
|
||||
A value less than 1.0 will disable the check.
|
||||
|
||||
``nnedi3-neurons=<16|32|64|128>``
|
||||
Specify the neurons for nnedi3 prescaling (defaults to be 32). The
|
||||
rendering time is expected to be linear to the number of neurons.
|
||||
|
||||
``nnedi3-window=<8x4|8x6>``
|
||||
Specify the size of local window for sampling in nnedi3 prescaling
|
||||
(defaults to be ``8x4``). The ``8x6`` window produces sharper images,
|
||||
but is also slower.
|
||||
|
||||
``nnedi3-upload=<ubo|shader>``
|
||||
Specify how to upload the NN weights to GPU. Depending on the graphics
|
||||
card, driver, shader compiler and nnedi3 settings, both method can be
|
||||
faster or slower.
|
||||
|
||||
``ubo``
|
||||
Upload these weights via uniform buffer objects. This is the
|
||||
default. (requires OpenGL 3.1 / GLES 3.0)
|
||||
|
||||
``shader``
|
||||
Hard code all the weights into the shader source code. (requires
|
||||
OpenGL 3.3 / GLES 3.0)
|
||||
|
||||
This is automatically disabled for anamorphic video, because this
|
||||
feature doesn't work correctly with different scale factors in
|
||||
different directions.
|
||||
|
||||
``pre-shaders=<files>``, ``post-shaders=<files>``, ``scale-shader=<file>``
|
||||
Custom GLSL fragment shaders.
|
||||
@@ -612,11 +696,8 @@ Available video output drivers are:
|
||||
``deband-iterations=<1..16>``
|
||||
The number of debanding steps to perform per sample. Each step reduces
|
||||
a bit more banding, but takes time to compute. Note that the strength
|
||||
of each step falls off very quickly, so high numbers are practically
|
||||
useless. (Default 4)
|
||||
|
||||
If the performance hit of debanding is too great, you can reduce this
|
||||
to 2 or 1 with marginal visual quality loss.
|
||||
of each step falls off very quickly, so high numbers (>4) are
|
||||
practically useless. (Default 1)
|
||||
|
||||
``deband-threshold=<0..4096>``
|
||||
The debanding filter's cut-off threshold. Higher numbers increase the
|
||||
@@ -626,7 +707,10 @@ Available video output drivers are:
|
||||
``deband-range=<1..64>``
|
||||
The debanding filter's initial radius. The radius increases linearly
|
||||
for each iteration. A higher radius will find more gradients, but
|
||||
a lower radius will smooth more aggressively. (Default 8)
|
||||
a lower radius will smooth more aggressively. (Default 16)
|
||||
|
||||
If you increase the ``deband-iterations``, you should probably
|
||||
decrease this to compensate.
|
||||
|
||||
``deband-grain=<0..4096>``
|
||||
Add some extra noise to the image. This significantly helps cover up
|
||||
@@ -645,6 +729,16 @@ Available video output drivers are:
|
||||
The slope of the sigmoid curve used for ``sigmoid-upscaling``, must
|
||||
be a float between 1.0 and 20.0. Defaults to 6.5 if not specified.
|
||||
|
||||
``sharpen=<value>``
|
||||
If set to a value other than 0, enable an unsharp masking filter.
|
||||
Positive values will sharpen the image (but add more ringing and
|
||||
aliasing). Negative values will blur the image. If your GPU is powerful
|
||||
enough, consider alternatives like the ``ewa_lanczossharp`` scale
|
||||
filter, or the ``scale-blur`` sub-option.
|
||||
|
||||
(This feature is the replacement for the old ``sharpen3`` and
|
||||
``sharpen5`` scalers.)
|
||||
|
||||
``glfinish``
|
||||
Call ``glFinish()`` before and after swapping buffers (default: disabled).
|
||||
Slower, but might help getting better results when doing framedropping.
|
||||
@@ -658,11 +752,24 @@ Available video output drivers are:
|
||||
|
||||
X11/GLX only.
|
||||
|
||||
``dwmflush=<no|windowed|yes>``
|
||||
Calls ``DwmFlush`` after swapping buffers on Windows (default: no).
|
||||
``vsync-fences=<N>``
|
||||
Synchronize the CPU to the Nth past frame using the ``GL_ARB_sync``
|
||||
extension. A value of 0 disables this behavior (default). A value of
|
||||
1 means it will synchronize to the current frame after rendering it.
|
||||
Like ``glfinish`` and ``waitvsync``, this can lower or ruin performance.
|
||||
Its advantage is that it can span multiple frames, and effectively limit
|
||||
the number of frames the GPU queues ahead (which also has an influence
|
||||
on vsync).
|
||||
|
||||
``dwmflush=<no|windowed|yes|auto>``
|
||||
Calls ``DwmFlush`` after swapping buffers on Windows (default: auto).
|
||||
It also sets ``SwapInterval(0)`` to ignore the OpenGL timing. Values
|
||||
are: no (disabled), windowed (only in windowed mode), yes (also in
|
||||
full screen).
|
||||
|
||||
The value ``auto`` will try to determine whether the compositor is
|
||||
active, and calls ``DwmFlush`` only if it seems to be.
|
||||
|
||||
This may help getting more consistent frame intervals, especially with
|
||||
high-fps clips - which might also reduce dropped frames. Typically a
|
||||
value of ``windowed`` should be enough since full screen may bypass the
|
||||
@@ -684,22 +791,39 @@ Available video output drivers are:
|
||||
Cocoa/OS X
|
||||
win
|
||||
Win32/WGL
|
||||
angle
|
||||
Direct3D11 through the OpenGL ES translation layer ANGLE. This
|
||||
supports almost everything the ``win`` backend does, except ICC
|
||||
profiles, high bit depth video input, and the ``nnedi3`` prescaler.
|
||||
dxinterop (experimental)
|
||||
Win32, using WGL for rendering and Direct3D 9Ex for presentation.
|
||||
Works on Nvidia and AMD only.
|
||||
x11
|
||||
X11/GLX
|
||||
wayland
|
||||
Wayland/EGL
|
||||
drm-egl
|
||||
DRM/EGL
|
||||
x11egl
|
||||
X11/EGL
|
||||
|
||||
``es``
|
||||
Force or prefer GLES2/3 over desktop OpenGL, if supported.
|
||||
``es=<mode>``
|
||||
Select whether to use GLES:
|
||||
|
||||
yes
|
||||
Try to prefer ES over Desktop GL
|
||||
no
|
||||
Try to prefer desktop GL over ES
|
||||
auto
|
||||
Use the default for each backend (default)
|
||||
|
||||
``fbo-format=<fmt>``
|
||||
Selects the internal format of textures used for FBOs. The format can
|
||||
influence performance and quality of the video output.
|
||||
``fmt`` can be one of: rgb, rgba, rgb8, rgb10, rgb10_a2, rgb16, rgb16f,
|
||||
rgb32f, rgba12, rgba16, rgba16f, rgba32f.
|
||||
Default: rgba16.
|
||||
Default: ``auto``, which maps to rgba16 on desktop GL, and rgb10_a2 on
|
||||
GLES (e.g. ANGLE).
|
||||
|
||||
``gamma=<0.1..2.0>``
|
||||
Set a gamma value (default: 1.0). If gamma is adjusted in other ways
|
||||
@@ -779,7 +903,8 @@ Available video output drivers are:
|
||||
Automatically select the ICC display profile currently specified by
|
||||
the display settings of the operating system.
|
||||
|
||||
NOTE: Only implemented on OS X and X11
|
||||
NOTE: On Windows, the default profile must be an ICC profile. WCS
|
||||
profiles are not supported.
|
||||
|
||||
``icc-cache-dir=<dirname>``
|
||||
Store and load the 3D LUTs created from the ICC profile in this directory.
|
||||
@@ -859,7 +984,7 @@ Available video output drivers are:
|
||||
|
||||
This is equivalent to::
|
||||
|
||||
--vo=opengl:scale=spline36:cscale=spline36:dscale=mitchell:dither-depth=auto:fancy-downscaling:sigmoid-upscaling:pbo:deband
|
||||
--vo=opengl:scale=spline36:cscale=spline36:dscale=mitchell:dither-depth=auto:correct-downscaling:sigmoid-upscaling:pbo:deband:es=no
|
||||
|
||||
Note that some cheaper LCDs do dithering that gravely interferes with
|
||||
``opengl``'s dithering. Disabling dithering with ``dither-depth=no`` helps.
|
||||
@@ -899,7 +1024,7 @@ Available video output drivers are:
|
||||
|
||||
``deint-mode=<mode>``
|
||||
Select deinterlacing algorithm. Note that by default deinterlacing is
|
||||
initially always off, and needs to be enabled with the ``D`` key
|
||||
initially always off, and needs to be enabled with the ``d`` key
|
||||
(default key binding for ``cycle deinterlace``).
|
||||
|
||||
This option doesn't apply if libva supports video post processing (vpp).
|
||||
@@ -997,27 +1122,6 @@ Available video output drivers are:
|
||||
``opengl-cb``
|
||||
For use with libmpv direct OpenGL embedding; useless in any other contexts.
|
||||
(See ``<mpv/opengl_cb.h>``.)
|
||||
Usually, ``opengl-cb`` renders frames asynchronously by client and this
|
||||
can cause some frame drops. In order to provide a way to handle this
|
||||
situation, ``opengl-cb`` has its own frame queue and calls update callback
|
||||
more frequently if the queue is not empty regardless of existence of new frame.
|
||||
Once the queue is filled, ``opengl-cb`` drops frames automatically.
|
||||
|
||||
With default options, ``opengl-cb`` renders only the latest frame and drops
|
||||
all frames handed over while waiting render function after update callback.
|
||||
|
||||
``frame-queue-size=<1..100>``
|
||||
The maximum count of frames which the frame queue can hold (default: 1)
|
||||
|
||||
``frame-drop-mode=<pop|clear|block>``
|
||||
Select the behavior when the frame queue is full.
|
||||
|
||||
pop
|
||||
Drop the oldest frame in the frame queue.
|
||||
clear
|
||||
Drop all frames in the frame queue.
|
||||
block
|
||||
Wait for a short time, behave like ``clear`` on timeout. (default)
|
||||
|
||||
This also supports many of the suboptions the ``opengl`` VO has. Run
|
||||
``mpv --vo=opengl-cb:help`` for a list.
|
||||
@@ -1042,10 +1146,15 @@ Available video output drivers are:
|
||||
Normally it's better to kill the console framebuffer instead, which
|
||||
gives better performance.
|
||||
|
||||
``osd=<yes|no>``
|
||||
Enabled by default. If disabled with ``no``, no OSD layer is created.
|
||||
This also means there will be no subtitles rendered.
|
||||
|
||||
``drm`` (Direct Rendering Manager)
|
||||
Video output driver using Kernel Mode Setting / Direct Rendering Manager.
|
||||
Does not support hardware acceleration. Should be used when one doesn't
|
||||
want to install full-blown graphical environment (e.g. no X).
|
||||
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).
|
||||
|
||||
``connector=<number>``
|
||||
Select the connector to use (usually this is a monitor.) If set to -1,
|
||||
|
||||
@@ -26,6 +26,21 @@ variety of video file formats, audio and video codecs, and subtitle types.
|
||||
|
||||
Releases can be found on the [release list][releases].
|
||||
|
||||
## System requirements
|
||||
|
||||
- A not too ancient Linux, or Windows Vista or later, or OSX 10.8 or later.
|
||||
- A somewhat capable CPU. Hardware decoding might sometimes help if the CPU
|
||||
is too slow to decode video realtime, but must be explicitly enabled with
|
||||
the `--hwdec` option. On Windows, a CPU with SSE4 instruction set is required
|
||||
to get decent hardware decoding performance.
|
||||
- A not too crappy GPU. mpv is not intended to be used with bad GPUs. There are
|
||||
many caveats with drivers or system compositors causing tearing, stutter,
|
||||
etc. On Windows, you might want to make sure the graphics drivers are
|
||||
current, especially OpenGL. In some cases, ancient fallback video output
|
||||
methods can help (such as `--vo=xv` on Linux), but this use is not
|
||||
recommended or supported.
|
||||
|
||||
|
||||
## Downloads
|
||||
|
||||
|
||||
@@ -70,7 +85,7 @@ Essential dependencies (incomplete list):
|
||||
- libass (OSD, OSC, text subtitles)
|
||||
- Lua (optional, required for the OSC pseudo-GUI and youtube-dl integration)
|
||||
- libjpeg (optional, used for screenshots only)
|
||||
- Enca (optional, for subtitle charset detection)
|
||||
- uchardet (optional, for subtitle charset detection)
|
||||
- vdpau and vaapi libraries for hardware decoding on Linux (optional)
|
||||
|
||||
Libass dependencies:
|
||||
@@ -103,6 +118,7 @@ OSX.
|
||||
If you want to build a Windows binary, you either have to use MSYS2 and MinGW,
|
||||
or cross-compile from Linux with MinGW. See [Windows compilation][windows_compilation].
|
||||
|
||||
|
||||
## FFmpeg vs. Libav
|
||||
|
||||
|
||||
@@ -187,7 +203,7 @@ only if discretion is required.
|
||||
[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
|
||||
|
||||
## License
|
||||
## License
|
||||
|
||||
|
||||
[GPLv2](https://github.com/mpv-player/mpv/blob/master/LICENSE)
|
||||
Mostly GPLv2 or later. See [details.](https://github.com/mpv-player/mpv/blob/master/Copyright)
|
||||
+259
@@ -1,3 +1,262 @@
|
||||
Release 0.14.0
|
||||
==============
|
||||
|
||||
Build System Changes
|
||||
--------------------
|
||||
|
||||
- build: install scalable svg icon as well
|
||||
- vo_opengl: require --enable-gpl3 for nnedi
|
||||
- win32: enable internal pthreads wrapper by default
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- vo_opengl: add experimental dxinterop backend (renders with OpenGL, displays through Direct3D; broken with Intel drivers)
|
||||
- vo_opengl: add initial ANGLE support
|
||||
- windows: implement icc-profile-auto
|
||||
- windows: support taskbar button progress indicator (issue #2399)
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- Windows XP support
|
||||
- demux: remove old subtitle parser (FFmpeg users are unaffected; Libav loses support for some minor formats)
|
||||
- demux_libass: remove this demuxer (libavformat takes its place)
|
||||
- stream: drop old Linux PVR support
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- command, vo: add estimated-display-fps property
|
||||
- command: add vsync-ratio property
|
||||
- command: export some per-video-frame information (issue #2444)
|
||||
- vo_opengl: make LOOKUP_TEXTURE_SIZE configurable
|
||||
- windows: add option to set VO MMCSS profile
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- --sub-fix-timing now applies to ASS subtitles as well
|
||||
- command: rename vo-missed-frame-count property to vo-delayed-frame-count
|
||||
- input.conf: add default bindings for changing window scale (issue #2500)
|
||||
- videotoolbox: make decoder format customizable
|
||||
- vo_opengl: disable interpolation without display-sync
|
||||
- vo_opengl: make tscale=mitchell:tscale-clamp the default
|
||||
- vo_rpi: add an option to disable OSD
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- af_lavrresample: clamp float output to range
|
||||
- ao: disambiguate default device list entries
|
||||
- ao_alsa: filter audio device list
|
||||
- ao_alsa: list bidirectional devices too
|
||||
- ao_openal: accommodate more sample formats (issue #2494)
|
||||
- ao_openal: fix virtual speaker positioning
|
||||
- ao_wasapi: only report per-app volume in shared mode
|
||||
- ao_wasapi: work around DTS passthrough failure
|
||||
- build: make vaapi-wayland depend on gl-wayland (issue #2476)
|
||||
- demux: fix seeking in .ts
|
||||
- demux_lavf: mark ASS tracks as always UTF-8
|
||||
- demux_mkv: fix incremental indexing with single-keyframe files (issue #2498)
|
||||
- drm: fix setting up connectors
|
||||
- dxva2: reject 10 bit HEVC (issue #2516)
|
||||
- player: replace mistimed-frame-count with vsync-ratio on status line
|
||||
- sd_ass: fix secondary subtitle mode
|
||||
- various display-sync improvements
|
||||
- vo_opengl: enable NNEDI3 prescaler on OpenGL ES 3.0
|
||||
- vo_opengl: enable colormatrix even for RGB input
|
||||
- vo_opengl: fix backend autoprobing, attempt to improve GLX vs. EGL backend detection
|
||||
- vo_opengl: fix backend=x11 on Intel
|
||||
- vo_opengl: fix issues with some obscure pixel formats (e.g. rgb555)
|
||||
- vo_opengl: fix precision loss of fruit dithering matrix
|
||||
- vo_opengl: force dumb mode if RG textures are not available
|
||||
- vo_opengl: improve boundary check for polar filters
|
||||
- vo_opengl: various GLES compatibility improvements
|
||||
- vo_opengl: win32: test for exclusive mode
|
||||
- vo_opengl_cb: do not block on flipping when redrawing
|
||||
- vo_rpi: set aspect ratio
|
||||
- win32: fix console output with raw stdio functions
|
||||
- windows: try to avoid detection as exclusive fullscreen window (issue #2177)
|
||||
- x11: request bypassing compositor (issue #2502)
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API. A complete changelog can be seen by running
|
||||
`git log v0.13.0..v0.14.0` in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.13.0...v0.14.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.13.0..v0.14.0
|
||||
|
||||
|
||||
Release 0.13.0
|
||||
==============
|
||||
|
||||
NOTE: The previous release changed the default format for youtube-dl and
|
||||
removed some workarounds related to MPEG DASH support. However, the required
|
||||
changes to FFmpeg code (FFmpeg commit 4ab56667594842283dc5ae07f0daba2a2cb4d3af)
|
||||
are not in any FFmpeg release yet (as of 2015-11-10), so for now, playing DASH
|
||||
streams requires using FFmpeg git master. You can work around this by using the
|
||||
--ytdl-format=best option.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- SVG version of the icon (with symbolic counterpart)
|
||||
- stream_libarchive: add multivolume support
|
||||
- vo_opengl: add prescaling framework along with Super-xBR and NNEDI3 (currently very slow) prescalers (issue #2230)
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- command: add mistimed-frame-count property
|
||||
- vo_opengl: add vsync-fences option
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- command: make display-fps property writable
|
||||
- options: enable mpeg2 hw decoding by default if hw decoding is requested
|
||||
- vo_opengl: rename fancy-downscaling to correct-downscaling
|
||||
- vo_opengl: correct-downscaling: enable also for anamorphic clips
|
||||
- vo_opengl: rename "drm_egl" to "drm-egl"
|
||||
- vo_opengl: disable drm-egl autopickup
|
||||
- vo_opengl: never load vaapi GLX interop by default
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- ao_alsa: fix 7.1 over HDMI
|
||||
- audio: do not require full audio chain reinit for speed changes
|
||||
- rpi: add support for codecs other than h264 (mpeg2 for now)
|
||||
- vd_lavc: make hwdec fallback more tolerant
|
||||
- video: fix playback of pal8
|
||||
- video: multiple display-sync fixes
|
||||
- vo: fix no-audio mode with interpolation enabled/display-sync disabled
|
||||
- vo_direct3d: fix operation (issue #2434)
|
||||
- vo_drm: handle possible errors from sigaction
|
||||
- vo_drm: show osd in audio only mode
|
||||
- vo_opengl: do not attempt to cache frames in FBO in dumb-mode (issue #2432)
|
||||
- vo_opengl: win32: always request MMCSS for DWM
|
||||
- vo_opengl: win32: try to enable DwmFlush by default
|
||||
- vo_vdpau: check VDP_RGBA_FORMAT_A8 support
|
||||
- win32: request MMCSS "Playback" profile
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API. A complete changelog can be seen by running
|
||||
`git log v0.12.0..v0.13.0` in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.12.0...v0.13.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.12.0..v0.13.0
|
||||
|
||||
|
||||
Release 0.12.0
|
||||
==============
|
||||
|
||||
NOTE: This release changes the default format for youtube-dl and removes some
|
||||
workarounds related to MPEG DASH support. However, the required changes to
|
||||
FFmpeg code are not in any FFmpeg release yet (as of 2015-10-29), so for now,
|
||||
playing DASH streams requires using FFmpeg git master. You can work around
|
||||
this by using the --ytdl-format=best option.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- vo_opengl: support new VAAPI EGL interop (requires Mesa 11)
|
||||
- vo_opengl: vaapi: add Wayland support
|
||||
- bring back the x11 video output
|
||||
- vo_opengl: support all kinds of GBRP formats
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- video: remove VDA support (VideoToolbox is preferred)
|
||||
|
||||
Behavior
|
||||
--------
|
||||
|
||||
- vo_opengl: enable X11 EGL backend by default (disabled for NVIDIA due to driver bugs)
|
||||
- ytdl: don't override user-set format in no-video mode
|
||||
- sub: adjust behavior on mismatching video/subtitle aspect ratio
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- audio: add option for falling back to ao_null
|
||||
- options: add support for client certificate authentication
|
||||
- input: add key name for U+3000 IDEOGRAPHIC SPACE
|
||||
- player: add audio drop/duplicate mode to video-sync
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- vo_opengl: remove sharpen scalers, add sharpen sub-option
|
||||
- vo_opengl: make sw suboption work without explicit backend selection
|
||||
- command: make time properties unavailable if timestamp is unknown
|
||||
- command: do not return 0 for bitrate if unknown (make property unavailable instead)
|
||||
- vo_opengl: make the default debanding settings less excessive
|
||||
- ytdl: disable --all-subs if "sub-lang" is in raw-options
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- video: remove user-controllable PTS sorting (--pts-association-mode)
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- player: fix another --force-window bug
|
||||
- player: add wav to list of external audio file extensions
|
||||
- ao_alsa: fix failure to find any sample format
|
||||
- player: make stop command actually stop in all cases
|
||||
- audio: various fixes related to audio device hotplugging
|
||||
- vo_xv: fix crash with --wid
|
||||
- ytdl: Remove DASH hacks, use DASH by default
|
||||
- player: be slightly less prone to framedrop in display sync mode
|
||||
- sd_lavc: extend subtitle resolution if images go outside video frame
|
||||
- player: offset chapter display by start time
|
||||
- command: make bitrate properties work correctly for external tracks
|
||||
- w32_common: disable IME
|
||||
- player: fix display-sync A/V calculation on high playback speeds
|
||||
- player: fix display sync A/V difference estimation on drops
|
||||
- player: raise display sync desync tolerance
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API. A complete changelog can be seen by running
|
||||
`git log v0.11.0..v0.12.0` in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.11.0...v0.12.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.11.0..v0.12.0
|
||||
|
||||
|
||||
Release 0.11.0
|
||||
==============
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ use warnings;
|
||||
# the exact contents of the original file.
|
||||
# FIXME: why not a char array?
|
||||
|
||||
# treat only alphanumeric and not-" punctuation as safe
|
||||
my $unsafe_chars = qr{[^][A-Za-z0-9!#%&'()*+,./:;<=>?^_{|}~ -]};
|
||||
# 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;
|
||||
|
||||
Regular → Executable
+1
-1
@@ -5,7 +5,7 @@
|
||||
|
||||
conv() {
|
||||
echo
|
||||
identify -format "icon: %w %h" "$1"
|
||||
echo `identify -format "icon: %w %h" "$1"`
|
||||
convert "$1" -depth 8 rgba:-
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,12 @@ function add_files_at(index, files)
|
||||
end
|
||||
|
||||
function get_extension(path)
|
||||
return string.match(path, "%.([^%.]+)$" )
|
||||
match = string.match(path, "%.([^%.]+)$" )
|
||||
if match == nil then
|
||||
return "nomatch"
|
||||
else
|
||||
return match
|
||||
end
|
||||
end
|
||||
|
||||
table.filter = function(t, iter)
|
||||
|
||||
+27
-12
@@ -176,7 +176,6 @@ options_state_machine() {
|
||||
opt_yes_no _tv "TV interface (TV/DVB grabbers)" yes
|
||||
opt_yes_no _tv_v4l2 "Video4Linux2 TV interface"
|
||||
opt_yes_no _libv4l2 "libv4l2"
|
||||
opt_yes_no _pvr "Video4Linux2 MPEG PVR"
|
||||
opt_yes_no _smb "Samba (SMB) input"
|
||||
opt_yes_no _lcms2 "LCMS2 support"
|
||||
opt_yes_no _bluray "Blu-ray support"
|
||||
@@ -610,12 +609,13 @@ define_yes_no $_vdpau HAVE_VDPAU_HWACCEL
|
||||
check_pkg_config "VAAPI" $_vaapi VAAPI 'libva >= 0.32.0 libva-x11 >= 0.32.0'
|
||||
_vaapi=$(defretval)
|
||||
define_yes_no $_vaapi HAVE_VAAPI_HWACCEL
|
||||
define_yes_no $_vaapi HAVE_VAAPI_X11
|
||||
|
||||
_vaapi_wayland=no
|
||||
if test "$_vaapi" = yes ; then
|
||||
check_pkg_config "VAAPI VPP" auto VAAPI_VPP 'libva >= 0.34.0'
|
||||
else
|
||||
check_yes_no no VAAPI_VPP
|
||||
_vaapi_wayland=auto
|
||||
fi
|
||||
check_pkg_config "VAAPI Wayland" $_vaapi_wayland VAAPI_WAYLAND 'libva-wayland >= 0.34.0'
|
||||
|
||||
check_pkg_config "Xinerama" $_xinerama XINERAMA 'xinerama'
|
||||
|
||||
@@ -703,6 +703,11 @@ _vaapi_glx=no
|
||||
(test "$_gl_x11" = yes && test "$_vaapi" = yes) && _vaapi_glx=auto
|
||||
check_pkg_config "VAAPI with OpenGL/X11" $_vaapi_glx VAAPI_GLX 'libva-glx >= 0.32.0'
|
||||
|
||||
_vaapi_x_egl=no
|
||||
(test "$_gl_x11_egl" = yes && test "$_vaapi" = yes) && _vaapi_x_egl=yes
|
||||
check_yes_no $_vaapi_x_egl VAAPI_X_EGL
|
||||
check_yes_no $_vaapi_x_egl VAAPI_EGL
|
||||
|
||||
check_pkg_config "SDL 2.0" $_sdl2 SDL2 'sdl2'
|
||||
|
||||
check_statement_libs "OSS Audio" $_ossaudio OSS_AUDIO $_soundcard_header "int x = SNDCTL_DSP_SETFRAGMENT;"
|
||||
@@ -804,6 +809,18 @@ api_statement_check \
|
||||
libavutil/pixdesc.h \
|
||||
'AVComponentDescriptor d; int x = d.depth'
|
||||
|
||||
api_statement_check \
|
||||
"libavcodec 64 bit AVPacket.duration" \
|
||||
HAVE_AV_AVPACKET_INT64_DURATION \
|
||||
libavcodec/avcodec.h \
|
||||
'int x[(int)sizeof(((AVPacket){0}).duration) - 7]'
|
||||
|
||||
api_statement_check \
|
||||
"libavcodec AVSubtitleRect AVPicture removal" \
|
||||
HAVE_AV_SUBTITLE_NOPICT \
|
||||
libavcodec/avcodec.h \
|
||||
'AVSubtitleRect r = {.linesize={0}}'
|
||||
|
||||
check_pkg_config "libavfilter" $_libavfilter LIBAVFILTER 'libavfilter >= 5.0.0'
|
||||
|
||||
check_pkg_config "libavdevice" $_libavdevice LIBAVDEVICE 'libavdevice >= 55.0.0'
|
||||
@@ -818,9 +835,6 @@ check_trivial "TV audio input" $_tv_v4l2 AUDIO_INPUT
|
||||
test $_tv_v4l2 = no && _libv4l2=no
|
||||
check_pkg_config "libv4l2 support" $_libv4l2 LIBV4L2 'libv4l2'
|
||||
|
||||
test "$_tv_v4l2" = no && _pvr=no
|
||||
check_compile "Video 4 Linux 2 MPEG PVR interface" $_pvr PVR waftools/fragments/pvr.c
|
||||
|
||||
# Note: Lua has no official .pc file, so there are different OS-specific ones.
|
||||
# Also, we support luajit, which is compatible to 5.1.
|
||||
|
||||
@@ -926,10 +940,6 @@ cat > $TMPC << EOF
|
||||
for platforms not supported by this configure script */
|
||||
#define HAVE_BSD_FSTATFS 0
|
||||
#define HAVE_LINUX_FSTATFS 1
|
||||
#define HAVE_VDA_AV_VDA_ALLOC_CONTEXT 0
|
||||
#define HAVE_VDA_HWACCEL 0
|
||||
#define HAVE_VDA_LIBAVCODEC_REFCOUNTING 0
|
||||
#define HAVE_VDA_GL 0
|
||||
#define HAVE_GL_COCOA 0
|
||||
#define HAVE_COCOA 0
|
||||
#define HAVE_COCOA_APPLICATION 0
|
||||
@@ -956,9 +966,14 @@ cat > $TMPC << EOF
|
||||
#define HAVE_RPI_GLES 0
|
||||
#define HAVE_AV_PIX_FMT_MMAL 0
|
||||
#define HAVE_DRM 0
|
||||
#define HAVE_EGL_DRM 0
|
||||
#define HAVE_VIDEOTOOLBOX_HWACCEL 0
|
||||
#define HAVE_VIDEOTOOLBOX_GL 0
|
||||
#define HAVE_VIDEOTOOLBOX_VDA_GL 0
|
||||
#define HAVE_SSE4_INTRINSICS 1
|
||||
#define HAVE_C11_TLS 1
|
||||
#define HAVE_EGL_ANGLE 0
|
||||
#define HAVE_GPL3 1
|
||||
#define HAVE_WIN32 0
|
||||
|
||||
#ifdef __OpenBSD__
|
||||
#define DEFAULT_CDROM_DEVICE "/dev/rcd0c"
|
||||
|
||||
+12
-7
@@ -36,15 +36,12 @@ SOURCES-$(DVDNAV) += stream/stream_dvdnav.c \
|
||||
stream/stream_dvd_common.c
|
||||
|
||||
SOURCES-$(RUBBERBAND) += audio/filter/af_rubberband.c
|
||||
SOURCES-$(LIBASS) += sub/ass_mp.c sub/sd_ass.c \
|
||||
demux/demux_libass.c
|
||||
SOURCES-$(LIBASS) += sub/ass_mp.c sub/sd_ass.c
|
||||
|
||||
SOURCES-$(LIBBLURAY) += stream/stream_bluray.c
|
||||
|
||||
SOURCES-$(LIBSMBCLIENT) += stream/stream_smb.c
|
||||
|
||||
SOURCES-$(PVR) += stream/stream_pvr.c
|
||||
|
||||
SOURCES-$(TV) += stream/stream_tv.c stream/tv.c \
|
||||
stream/frequencies.c stream/tvi_dummy.c \
|
||||
demux/demux_tv.c
|
||||
@@ -64,6 +61,8 @@ SOURCES-$(GL) += video/out/opengl/common.c \
|
||||
video/out/dither.c \
|
||||
video/out/opengl/hwdec.c \
|
||||
video/out/opengl/utils.c \
|
||||
video/out/opengl/superxbr.c \
|
||||
video/out/opengl/nnedi3.c \
|
||||
video/out/vo_opengl.c \
|
||||
video/out/vo_opengl_cb.c
|
||||
|
||||
@@ -88,11 +87,13 @@ SOURCES-$(VDPAU) += video/vdpau.c video/vdpau_mixer.c \
|
||||
SOURCES-$(VDPAU_GL_X11) += video/out/opengl/hwdec_vdpau.c
|
||||
SOURCES-$(VAAPI) += video/out/vo_vaapi.c \
|
||||
video/decode/vaapi.c \
|
||||
video/filter/vf_vavpp.c \
|
||||
video/vaapi.c
|
||||
SOURCES-$(VAAPI_VPP) += video/filter/vf_vavpp.c
|
||||
SOURCES-$(VAAPI_GLX) += video/out/opengl/hwdec_vaglx.c
|
||||
SOURCES-$(VAAPI_X_EGL) += video/out/opengl/hwdec_vaegl.c
|
||||
|
||||
SOURCES-$(XV) += video/out/x11_common.c video/out/vo_xv.c
|
||||
SOURCES-$(X11) += video/out/vo_x11.c video/out/x11_common.c
|
||||
SOURCES-$(XV) += video/out/vo_xv.c
|
||||
SOURCES-$(WAYLAND) += video/out/vo_wayland.c \
|
||||
video/out/wayland_common.c \
|
||||
video/out/wayland/buffer.c \
|
||||
@@ -159,7 +160,6 @@ SOURCES = audio/audio.c \
|
||||
demux/demux_playlist.c \
|
||||
demux/demux_rar.c \
|
||||
demux/demux_raw.c \
|
||||
demux/demux_subreader.c \
|
||||
demux/ebml.c \
|
||||
demux/packet.c \
|
||||
demux/timeline.c \
|
||||
@@ -233,6 +233,7 @@ SOURCES = audio/audio.c \
|
||||
ta/ta_talloc.c \
|
||||
video/csputils.c \
|
||||
video/fmt-conversion.c \
|
||||
video/gpu_memcpy.c \
|
||||
video/image_writer.c \
|
||||
video/img_format.c \
|
||||
video/mp_image.c \
|
||||
@@ -347,6 +348,10 @@ video/out/x11_common.c: video/out/x11_icon.inc
|
||||
video/out/x11_icon.inc: TOOLS/file2string.pl video/out/x11_icon.bin
|
||||
./$^ >$@
|
||||
|
||||
video/out/opengl/nnedi3.c: video/out/opengl/nnedi3_weights.inc
|
||||
video/out/opengl/nnedi3_weights.inc: TOOLS/file2string.pl video/out/opengl/nnedi3_weights.bin
|
||||
./$^ >$@
|
||||
|
||||
sub/osd_libass.c: sub/osd_font.h
|
||||
sub/osd_font.h: TOOLS/file2string.pl sub/osd_font.otf
|
||||
./$^ >$@
|
||||
|
||||
+26
-14
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import matplotlib.pyplot as plot
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
import pyqtgraph as pg
|
||||
import sys
|
||||
import re
|
||||
|
||||
@@ -38,8 +39,8 @@ Currently, the following event types are supported:
|
||||
class G:
|
||||
events = {}
|
||||
start = None
|
||||
# http://matplotlib.org/api/markers_api.html#module-matplotlib.markers
|
||||
markers = ["o", "8", "s", "p", "*", "h", "+", "x", "D"]
|
||||
markers = ["o", "s", "t", "d"]
|
||||
curveno = 0
|
||||
|
||||
def find_marker():
|
||||
if len(G.markers) == 0:
|
||||
@@ -128,20 +129,31 @@ for e, index in zip(G.sevents, range(len(G.sevents))):
|
||||
else:
|
||||
e.vals = [(x, y * (m - index) / m) for (x, y) in e.vals]
|
||||
|
||||
fig = plot.figure()
|
||||
fig.hold(True)
|
||||
pg.setConfigOption('background', 'w')
|
||||
pg.setConfigOption('foreground', 'k')
|
||||
app = QtGui.QApplication([])
|
||||
win = pg.GraphicsWindow()
|
||||
#win.resize(1500, 900)
|
||||
|
||||
ax = [None, None]
|
||||
plots = 2 if hasval else 1
|
||||
ax[0] = fig.add_subplot(plots, 1, 1)
|
||||
ax[0] = win.addPlot()
|
||||
if hasval:
|
||||
ax[1] = fig.add_subplot(plots, 1, 2, sharex=ax[0])
|
||||
legends = [[], []]
|
||||
win.nextRow()
|
||||
ax[1] = win.addPlot()
|
||||
ax[1].setXLink(ax[0])
|
||||
for cur in ax:
|
||||
if cur is not None:
|
||||
cur.addLegend(offset = (-1, 1))
|
||||
for e in G.sevents:
|
||||
cur = ax[1 if e.type == "value" else 0]
|
||||
pl, = cur.plot([x for x,y in e.vals], [y for x,y in e.vals], label=e.name)
|
||||
args = {'name': e.name,'antialias':True}
|
||||
if e.type == "event-signal":
|
||||
plot.setp(pl, marker = e.marker, linestyle = "None")
|
||||
for cur in ax:
|
||||
if cur is not None:
|
||||
cur.legend()
|
||||
plot.show()
|
||||
args['symbol'] = e.marker
|
||||
args['pen'] = None
|
||||
else:
|
||||
args['pen'] = pg.mkPen(pg.intColor(G.curveno), width=0)
|
||||
G.curveno += 1
|
||||
n = cur.plot([x for x,y in e.vals], [y for x,y in e.vals], **args)
|
||||
|
||||
QtGui.QApplication.instance().exec_()
|
||||
+7
-5
@@ -323,21 +323,23 @@ struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe)
|
||||
}
|
||||
|
||||
// If we can't handle the format (e.g. too many channels), bail out.
|
||||
if (!mp_audio_config_valid(new) || avframe->nb_extended_buf)
|
||||
if (!mp_audio_config_valid(new))
|
||||
goto fail;
|
||||
|
||||
for (int n = 0; n < AV_NUM_DATA_POINTERS; n++) {
|
||||
if (!avframe->buf[n])
|
||||
for (int n = 0; n < AV_NUM_DATA_POINTERS + avframe->nb_extended_buf; n++) {
|
||||
AVBufferRef *buf = n < AV_NUM_DATA_POINTERS ? avframe->buf[n]
|
||||
: avframe->extended_buf[n - AV_NUM_DATA_POINTERS];
|
||||
if (!buf)
|
||||
break;
|
||||
if (n >= MP_NUM_CHANNELS)
|
||||
goto fail;
|
||||
new->allocated[n] = av_buffer_ref(avframe->buf[n]);
|
||||
new->allocated[n] = av_buffer_ref(buf);
|
||||
if (!new->allocated[n])
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (int n = 0; n < new->num_planes; n++)
|
||||
new->planes[n] = avframe->data[n];
|
||||
new->planes[n] = avframe->extended_data[n];
|
||||
new->samples = avframe->nb_samples;
|
||||
|
||||
return new;
|
||||
|
||||
@@ -37,6 +37,8 @@ struct mp_audio {
|
||||
int num_planes; // number of planes
|
||||
int bps; // size of sub-samples (af_fmt_to_bytes(format))
|
||||
|
||||
double pts; // currently invalid within the filter chain
|
||||
|
||||
// --- private
|
||||
// These do not necessarily map directly to planes[]. They can have
|
||||
// different order or count. There shouldn't be more buffers than planes.
|
||||
|
||||
@@ -119,6 +119,15 @@ void mp_audio_buffer_prepend_silence(struct mp_audio_buffer *ab, int samples)
|
||||
mp_audio_fill_silence(ab->buffer, 0, samples);
|
||||
}
|
||||
|
||||
void mp_audio_buffer_duplicate(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= ab->buffer->samples);
|
||||
int oldlen = ab->buffer->samples;
|
||||
ab->buffer->samples += samples;
|
||||
mp_audio_realloc_min(ab->buffer, ab->buffer->samples);
|
||||
mp_audio_copy(ab->buffer, oldlen, ab->buffer, oldlen - samples, samples);
|
||||
}
|
||||
|
||||
// Get the start of the current readable buffer.
|
||||
void mp_audio_buffer_peek(struct mp_audio_buffer *ab, struct mp_audio *out_mpa)
|
||||
{
|
||||
|
||||
@@ -35,6 +35,7 @@ void mp_audio_buffer_get_write_buffer(struct mp_audio_buffer *ab, int minsamples
|
||||
void mp_audio_buffer_finish_write(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_append(struct mp_audio_buffer *ab, struct mp_audio *mpa);
|
||||
void mp_audio_buffer_prepend_silence(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_duplicate(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_peek(struct mp_audio_buffer *ab, struct mp_audio *out_mpa);
|
||||
void mp_audio_buffer_skip(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_clear(struct mp_audio_buffer *ab);
|
||||
|
||||
+11
-29
@@ -93,7 +93,7 @@ static const char *const std_layout_names[][2] = {
|
||||
{"7.1(alsa)", "fl-fr-bl-br-fc-lfe-sl-sr"}, // not in lavc
|
||||
{"7.1(wide)", "fl-fr-fc-lfe-bl-br-flc-frc"},
|
||||
{"7.1(wide-side)", "fl-fr-fc-lfe-flc-frc-sl-sr"},
|
||||
{"7.1(rear)", "fl-fr-fc-lfe-bl-br-sdl-sdr"},
|
||||
{"7.1(rear)", "fl-fr-fc-lfe-bl-br-sdl-sdr"}, // not in lavc
|
||||
{"octagonal", "fl-fr-fc-bl-br-bc-sl-sr"},
|
||||
{"downmix", "dl-dr"},
|
||||
{"auto", ""}, // not in lavc
|
||||
@@ -112,19 +112,6 @@ static const struct mp_chmap default_layouts[] = {
|
||||
MP_CHMAP8(FL, FR, FC, LFE, BL, BR, SL, SR), // 7.1
|
||||
};
|
||||
|
||||
// The channel order was lavc/waveex, but differs from lavc for 5, 6 and 8
|
||||
// channels. 3 and 7 channels were likely undefined (no ALSA support).
|
||||
// I'm not sure about the 4 channel case: ALSA uses "quad", while the ffmpeg
|
||||
// default layout is "4.0".
|
||||
static const char *const mplayer_layouts[MP_NUM_CHANNELS + 1] = {
|
||||
[1] = "mono",
|
||||
[2] = "stereo",
|
||||
[4] = "quad",
|
||||
[5] = "5.0(alsa)",
|
||||
[6] = "5.1(alsa)",
|
||||
[8] = "7.1(alsa)",
|
||||
};
|
||||
|
||||
// Returns true if speakers are mapped uniquely, and there's at least 1 channel.
|
||||
bool mp_chmap_is_valid(const struct mp_chmap *src)
|
||||
{
|
||||
@@ -227,19 +214,6 @@ void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels)
|
||||
mp_chmap_set_unknown(dst, num_channels);
|
||||
}
|
||||
|
||||
// Try to do what mplayer/mplayer2/mpv did before channel layouts were
|
||||
// introduced, i.e. get the old default channel order.
|
||||
void mp_chmap_from_channels_alsa(struct mp_chmap *dst, int num_channels)
|
||||
{
|
||||
if (num_channels < 0 || num_channels > MP_NUM_CHANNELS) {
|
||||
*dst = (struct mp_chmap) {0};
|
||||
} else {
|
||||
mp_chmap_from_str(dst, bstr0(mplayer_layouts[num_channels]));
|
||||
if (!dst->num)
|
||||
mp_chmap_from_channels(dst, num_channels);
|
||||
}
|
||||
}
|
||||
|
||||
// Set *dst to an unknown layout for the given numbers of channels.
|
||||
// If the number of channels is invalid, an invalid map is set, and
|
||||
// mp_chmap_is_valid(dst) will return false.
|
||||
@@ -298,12 +272,18 @@ void mp_chmap_remove_useless_channels(struct mp_chmap *map,
|
||||
// Speakers not representable by ffmpeg/libav are dropped.
|
||||
// Warning: this ignores the order of the channels, and will return a channel
|
||||
// mask even if the order is different from libavcodec's.
|
||||
// Also, "unknown" channel maps are translated to non-sense channel
|
||||
// maps with the same number of channels.
|
||||
uint64_t mp_chmap_to_lavc_unchecked(const struct mp_chmap *src)
|
||||
{
|
||||
// lavc has no concept for unknown layouts yet, so pick a default
|
||||
struct mp_chmap t = *src;
|
||||
if (t.num > 64)
|
||||
return 0;
|
||||
// lavc has no concept for unknown layouts yet, so pick something that does
|
||||
// the job of signaling the number of channels, even if it makes no sense
|
||||
// as a proper layout.
|
||||
if (mp_chmap_is_unknown(&t))
|
||||
mp_chmap_from_channels(&t, t.num);
|
||||
return t.num == 64 ? (uint64_t)-1 : (1ULL << t.num) - 1;
|
||||
uint64_t mask = 0;
|
||||
for (int n = 0; n < t.num; n++) {
|
||||
if (t.speaker[n] < 64) // ignore MP_SPEAKER_ID_NA etc.
|
||||
@@ -359,6 +339,8 @@ bool mp_chmap_is_lavc(const struct mp_chmap *src)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Warning: for "unknown" channel maps, this returns something that may not
|
||||
// make sense. Invalid channel maps are not changed.
|
||||
void mp_chmap_reorder_to_lavc(struct mp_chmap *map)
|
||||
{
|
||||
if (!mp_chmap_is_valid(map))
|
||||
|
||||
+1
-2
@@ -22,7 +22,7 @@
|
||||
#include <stdbool.h>
|
||||
#include "misc/bstr.h"
|
||||
|
||||
#define MP_NUM_CHANNELS 8
|
||||
#define MP_NUM_CHANNELS 16
|
||||
|
||||
// Speaker a channel can be assigned to.
|
||||
// This corresponds to WAVEFORMATEXTENSIBLE channel mask bit indexes.
|
||||
@@ -108,7 +108,6 @@ void mp_chmap_fill_na(struct mp_chmap *map, int num);
|
||||
|
||||
void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels);
|
||||
void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels);
|
||||
void mp_chmap_from_channels_alsa(struct mp_chmap *dst, int num_channels);
|
||||
|
||||
void mp_chmap_remove_useless_channels(struct mp_chmap *map,
|
||||
const struct mp_chmap *requested);
|
||||
|
||||
+22
-11
@@ -20,6 +20,7 @@
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
#include "chmap_sel.h"
|
||||
|
||||
static const struct mp_chmap speaker_replacements[][2] = {
|
||||
@@ -94,17 +95,6 @@ void mp_chmap_sel_add_waveext(struct mp_chmap_sel *s)
|
||||
s->allow_waveext = true;
|
||||
}
|
||||
|
||||
// Classic ALSA-based MPlayer layouts.
|
||||
void mp_chmap_sel_add_alsa_def(struct mp_chmap_sel *s)
|
||||
{
|
||||
for (int n = 1; n <= MP_NUM_CHANNELS; n++) {
|
||||
struct mp_chmap t;
|
||||
mp_chmap_from_channels_alsa(&t, n);
|
||||
if (t.num)
|
||||
mp_chmap_sel_add_map(s, &t);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a channel map that should be allowed.
|
||||
void mp_chmap_sel_add_map(struct mp_chmap_sel *s, const struct mp_chmap *map)
|
||||
{
|
||||
@@ -357,3 +347,24 @@ bool mp_chmap_sel_get_def(const struct mp_chmap_sel *s, struct mp_chmap *map,
|
||||
}
|
||||
return map->num > 0;
|
||||
}
|
||||
|
||||
// Print the set of allowed channel layouts.
|
||||
void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev)
|
||||
{
|
||||
if (!mp_msg_test(log, lev))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < s->num_chmaps; i++)
|
||||
mp_msg(log, lev, " - %s\n", mp_chmap_to_str(&s->chmaps[i]));
|
||||
for (int i = 0; i < MP_SPEAKER_ID_COUNT; i++) {
|
||||
if (!s->speakers[i])
|
||||
continue;
|
||||
struct mp_chmap l = {.num = 1, .speaker = { i }};
|
||||
mp_msg(log, lev, " - #%s\n",
|
||||
i == MP_SPEAKER_ID_FC ? "fc" : mp_chmap_to_str_hr(&l));
|
||||
}
|
||||
if (s->allow_waveext)
|
||||
mp_msg(log, lev, " - waveext\n");
|
||||
if (s->allow_any)
|
||||
mp_msg(log, lev, " - anything\n");
|
||||
}
|
||||
+3
-1
@@ -37,7 +37,6 @@ struct mp_chmap_sel {
|
||||
void mp_chmap_sel_add_any(struct mp_chmap_sel *s);
|
||||
void mp_chmap_sel_add_waveext(struct mp_chmap_sel *s);
|
||||
void mp_chmap_sel_add_waveext_def(struct mp_chmap_sel *s);
|
||||
void mp_chmap_sel_add_alsa_def(struct mp_chmap_sel *s);
|
||||
void mp_chmap_sel_add_map(struct mp_chmap_sel *s, const struct mp_chmap *map);
|
||||
void mp_chmap_sel_add_speaker(struct mp_chmap_sel *s, int id);
|
||||
bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map);
|
||||
@@ -45,4 +44,7 @@ bool mp_chmap_sel_fallback(const struct mp_chmap_sel *s, struct mp_chmap *map);
|
||||
bool mp_chmap_sel_get_def(const struct mp_chmap_sel *s, struct mp_chmap *map,
|
||||
int num);
|
||||
|
||||
struct mp_log;
|
||||
void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev);
|
||||
|
||||
#endif
|
||||
+4
-11
@@ -191,12 +191,6 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
|
||||
// If we don't have a PTS yet, use the first packet PTS we can get.
|
||||
if (da->pts == MP_NOPTS_VALUE && mpkt && mpkt->pts != MP_NOPTS_VALUE) {
|
||||
da->pts = mpkt->pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
|
||||
int got_frame = 0;
|
||||
av_frame_unref(priv->avframe);
|
||||
int ret = avcodec_decode_audio4(avctx, priv->avframe, &got_frame, &pkt);
|
||||
@@ -225,10 +219,6 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
return mpkt ? AD_OK : AD_EOF;
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, NULL);
|
||||
if (out_pts != MP_NOPTS_VALUE) {
|
||||
da->pts = out_pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
|
||||
struct mp_audio *mpframe = mp_audio_from_avframe(priv->avframe);
|
||||
if (!mpframe)
|
||||
@@ -244,6 +234,8 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
}
|
||||
mp_audio_set_channels(mpframe, &lavc_chmap);
|
||||
|
||||
mpframe->pts = out_pts;
|
||||
|
||||
#if HAVE_AVFRAME_SKIP_SAMPLES
|
||||
AVFrameSideData *sd =
|
||||
av_frame_get_side_data(priv->avframe, AV_FRAME_DATA_SKIP_SAMPLES);
|
||||
@@ -254,7 +246,8 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
uint32_t skip = MPMIN(priv->skip_samples, mpframe->samples);
|
||||
if (skip) {
|
||||
mp_audio_skip_samples(mpframe, skip);
|
||||
da->pts_offset += skip;
|
||||
if (mpframe->pts != MP_NOPTS_VALUE)
|
||||
mpframe->pts += skip / (double)mpframe->rate;
|
||||
priv->skip_samples -= skip;
|
||||
}
|
||||
if (pad <= mpframe->samples)
|
||||
|
||||
@@ -253,13 +253,11 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
if (!mpkt)
|
||||
return AD_EOF;
|
||||
|
||||
double pts = mpkt->pts;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
pkt.pts = pkt.dts = 0;
|
||||
if (mpkt->pts != MP_NOPTS_VALUE) {
|
||||
da->pts = mpkt->pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
if (!spdif_ctx->lavf_ctx) {
|
||||
if (init_filter(da, &pkt) < 0)
|
||||
return AD_ERR;
|
||||
@@ -276,6 +274,7 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
return AD_ERR;
|
||||
|
||||
memcpy((*out)->planes[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len);
|
||||
(*out)->pts = pts;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/mem.h>
|
||||
@@ -170,14 +171,28 @@ static int decode_new_frame(struct dec_audio *da)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (da->pts == MP_NOPTS_VALUE && da->header->missing_timestamps)
|
||||
da->pts = 0;
|
||||
|
||||
if (da->waiting) {
|
||||
if (da->waiting->pts != MP_NOPTS_VALUE) {
|
||||
if (da->pts != MP_NOPTS_VALUE) {
|
||||
da->pts += da->pts_offset / (double)da->waiting->rate;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
// Keep the interpolated timestamp if it doesn't deviate more
|
||||
// than 1 ms from the real one. (MKV rounded timestamps.)
|
||||
if (da->pts == MP_NOPTS_VALUE || da->pts_offset != 0 ||
|
||||
fabs(da->pts - da->waiting->pts) > 0.001)
|
||||
{
|
||||
da->pts = da->waiting->pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
}
|
||||
da->pts_offset += da->waiting->samples;
|
||||
da->decode_format = *da->waiting;
|
||||
mp_audio_set_null_data(&da->decode_format);
|
||||
}
|
||||
|
||||
if (da->pts == MP_NOPTS_VALUE && da->header->missing_timestamps)
|
||||
da->pts = 0;
|
||||
}
|
||||
return mp_audio_config_valid(da->waiting) ? AD_OK : AD_ERR;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ struct dec_audio {
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
bool spdif_passthrough;
|
||||
bool spdif_passthrough, spdif_failed;
|
||||
const struct ad_functions *ad_driver;
|
||||
struct sh_stream *header;
|
||||
struct af_stream *afilter;
|
||||
|
||||
+2
-1
@@ -461,7 +461,8 @@ static int af_reinit(struct af_stream *s)
|
||||
struct af_instance *aft = af->prev;
|
||||
af_remove(s, af);
|
||||
af = aft->next;
|
||||
break;
|
||||
retry++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
goto negotiate_error;
|
||||
|
||||
@@ -49,7 +49,6 @@ const uint16_t ac3_bitrate_tab[19] = {
|
||||
typedef struct af_ac3enc_s {
|
||||
struct AVCodec *lavc_acodec;
|
||||
struct AVCodecContext *lavc_actx;
|
||||
AVPacket pkt;
|
||||
int bit_rate;
|
||||
struct mp_audio *input; // frame passed to libavcodec
|
||||
struct mp_audio *pending; // unconsumed input data
|
||||
@@ -146,7 +145,6 @@ static void uninit(struct af_instance* af)
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
if (s) {
|
||||
av_free_packet(&s->pkt);
|
||||
if(s->lavc_actx) {
|
||||
avcodec_close(s->lavc_actx);
|
||||
av_free(s->lavc_actx);
|
||||
@@ -212,6 +210,8 @@ static int filter_out(struct af_instance *af)
|
||||
MP_FATAL(af, "Could not allocate memory \n");
|
||||
return -1;
|
||||
}
|
||||
int err = -1;
|
||||
|
||||
frame->nb_samples = s->in_samples;
|
||||
frame->format = s->lavc_actx->sample_fmt;
|
||||
frame->channel_layout = s->lavc_actx->channel_layout;
|
||||
@@ -221,30 +221,33 @@ static int filter_out(struct af_instance *af)
|
||||
frame->data[n] = s->input->planes[n];
|
||||
frame->linesize[0] = s->input->samples * s->input->sstride;
|
||||
|
||||
AVPacket pkt = {0};
|
||||
av_init_packet(&pkt);
|
||||
|
||||
int ok;
|
||||
int lavc_ret = avcodec_encode_audio2(s->lavc_actx, &s->pkt, frame, &ok);
|
||||
int lavc_ret = avcodec_encode_audio2(s->lavc_actx, &pkt, frame, &ok);
|
||||
av_frame_free(&frame);
|
||||
s->input->samples = 0;
|
||||
if (lavc_ret < 0 || !ok) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
return -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n",
|
||||
s->pkt.size, s->pending->samples);
|
||||
pkt.size, s->pending->samples);
|
||||
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, s->out_samples);
|
||||
if (!out)
|
||||
return -1;
|
||||
goto done;
|
||||
mp_audio_copy_attributes(out, s->pending);
|
||||
|
||||
int frame_size = s->pkt.size;
|
||||
int frame_size = pkt.size;
|
||||
int header_len = 0;
|
||||
char hdr[8];
|
||||
|
||||
if (s->cfg_add_iec61937_header && s->pkt.size > 5) {
|
||||
int bsmod = s->pkt.data[5] & 0x7;
|
||||
if (s->cfg_add_iec61937_header && pkt.size > 5) {
|
||||
int bsmod = pkt.data[5] & 0x7;
|
||||
int len = frame_size;
|
||||
|
||||
frame_size = AC3_FRAME_SIZE * 2 * 2;
|
||||
@@ -262,14 +265,18 @@ static int filter_out(struct af_instance *af)
|
||||
|
||||
char *buf = (char *)out->planes[0];
|
||||
memcpy(buf, hdr, header_len);
|
||||
memcpy(buf + header_len, s->pkt.data, s->pkt.size);
|
||||
memset(buf + header_len + s->pkt.size, 0,
|
||||
frame_size - (header_len + s->pkt.size));
|
||||
swap_16((uint16_t *)(buf + header_len), s->pkt.size / 2);
|
||||
memcpy(buf + header_len, pkt.data, pkt.size);
|
||||
memset(buf + header_len + pkt.size, 0,
|
||||
frame_size - (header_len + pkt.size));
|
||||
swap_16((uint16_t *)(buf + header_len), pkt.size / 2);
|
||||
out->samples = frame_size / out->sstride;
|
||||
af_add_output_frame(af, out);
|
||||
update_delay(af);
|
||||
return 0;
|
||||
|
||||
err = 0;
|
||||
done:
|
||||
av_packet_unref(&pkt);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance* af){
|
||||
@@ -307,8 +314,6 @@ static int af_open(struct af_instance* af){
|
||||
MP_VERBOSE(af, "[af_lavcac3enc]: in sample format: %s\n",
|
||||
af_fmt_to_str(s->in_sampleformat));
|
||||
|
||||
av_init_packet(&s->pkt);
|
||||
|
||||
s->input = talloc_zero(s, struct mp_audio);
|
||||
|
||||
if (s->cfg_bit_rate) {
|
||||
|
||||
@@ -195,6 +195,10 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
if (af_to_avformat(in->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT);
|
||||
|
||||
// Removing this requires fixing AVFrame.data vs. AVFrame.extended_data
|
||||
if (in->channels.num > AV_NUM_DATA_POINTERS)
|
||||
return AF_ERROR;
|
||||
|
||||
if (!mp_chmap_is_lavc(&in->channels))
|
||||
mp_chmap_reorder_to_lavc(&in->channels); // will always work
|
||||
|
||||
@@ -211,7 +215,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
mp_chmap_from_lavc(&out_cm, l_out->channel_layout);
|
||||
mp_audio_set_channels(out, &out_cm);
|
||||
|
||||
if (!mp_audio_config_valid(out))
|
||||
if (!mp_audio_config_valid(out) || out->channels.num > AV_NUM_DATA_POINTERS)
|
||||
return AF_ERROR;
|
||||
|
||||
p->timebase_out = l_out->time_base;
|
||||
@@ -349,8 +353,6 @@ static int af_open(struct af_instance *af)
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
// Removing this requires fixing AVFrame.data vs. AVFrame.extended_data
|
||||
assert(MP_NUM_CHANNELS <= AV_NUM_DATA_POINTERS);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#define avresample_convert(ctx, out, out_planesize, out_samples, in, in_planesize, in_samples) \
|
||||
swr_convert(ctx, out, out_samples, (const uint8_t**)(in), in_samples)
|
||||
#define avresample_set_channel_mapping swr_set_channel_mapping
|
||||
#define avresample_set_compensation swr_set_compensation
|
||||
#else
|
||||
#error "config.h broken or no resampler found"
|
||||
#endif
|
||||
@@ -304,14 +305,15 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
av_opt_set_int(s->avrctx, "in_sample_fmt", in_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx, "out_sample_fmt", out_samplefmtp, 0);
|
||||
|
||||
// Just needs the correct number of channels.
|
||||
int fake_out_ch_layout = av_get_default_channel_layout(map_out.num);
|
||||
// Just needs the correct number of channels for deplanarization.
|
||||
struct mp_chmap fake_chmap;
|
||||
mp_chmap_set_unknown(&fake_chmap, map_out.num);
|
||||
uint64_t fake_out_ch_layout = mp_chmap_to_lavc_unchecked(&fake_chmap);
|
||||
if (!fake_out_ch_layout)
|
||||
goto error;
|
||||
|
||||
// Deplanarize if needed.
|
||||
av_opt_set_int(s->avrctx_out, "in_channel_layout", fake_out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_channel_layout", fake_out_ch_layout, 0);
|
||||
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_fmt", out_samplefmtp, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_sample_fmt", out_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_rate", s->out_rate, 0);
|
||||
@@ -386,14 +388,6 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
|
||||
s->playback_speed = *(double *)arg;
|
||||
int new_rate = rate_from_speed(s->in_rate_af, s->playback_speed);
|
||||
if (new_rate != s->in_rate && s->avrctx && af->fmt_out.format) {
|
||||
// Before reconfiguring, drain the audio that is still buffered
|
||||
// in the resampler.
|
||||
af->filter_frame(af, NULL);
|
||||
// Reinitialize resampler.
|
||||
configure_lavrr(af, &af->fmt_in, &af->fmt_out, false);
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
@@ -429,6 +423,18 @@ static void extra_output_conversion(struct af_instance *af, struct mp_audio *mpa
|
||||
}
|
||||
mp_audio_set_format(mpa, AF_FORMAT_S24);
|
||||
}
|
||||
|
||||
for (int p = 0; p < mpa->num_planes; p++) {
|
||||
void *ptr = mpa->planes[p];
|
||||
int total = mpa->samples * mpa->spf;
|
||||
if (af_fmt_from_planar(mpa->format) == AF_FORMAT_FLOAT) {
|
||||
for (int s = 0; s < total; s++)
|
||||
((float *)ptr)[s] = av_clipf(((float *)ptr)[s], -1.0f, 1.0f);
|
||||
} else if (af_fmt_from_planar(mpa->format) == AF_FORMAT_DOUBLE) {
|
||||
for (int s = 0; s < total; s++)
|
||||
((double *)ptr)[s] = MPCLAMP(((double *)ptr)[s], -1.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This relies on the tricky way mpa was allocated.
|
||||
@@ -455,7 +461,7 @@ static void reorder_planes(struct mp_audio *mpa, int *reorder,
|
||||
}
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
static int filter_resample(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
@@ -516,6 +522,39 @@ error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
int new_rate = rate_from_speed(s->in_rate_af, s->playback_speed);
|
||||
bool need_reinit = fabs(new_rate / (double)s->in_rate - 1) > 0.01;
|
||||
|
||||
if (s->avrctx) {
|
||||
AVRational r = av_d2q(s->playback_speed * s->in_rate_af / s->in_rate,
|
||||
INT_MAX / 2);
|
||||
// Essentially, swr/avresample_set_compensation() does 2 things:
|
||||
// - adjust output sample rate by sample_delta/compensation_distance
|
||||
// - reset the adjustment after compensation_distance output samples
|
||||
// Increase the compensation_distance to avoid undesired reset
|
||||
// semantics - we want to keep the ratio for the whole frame we're
|
||||
// feeding it, until the next filter() call.
|
||||
int mult = INT_MAX / 2 / MPMAX(MPMAX(abs(r.num), abs(r.den)), 1);
|
||||
r = (AVRational){ r.num * mult, r.den * mult };
|
||||
if (avresample_set_compensation(s->avrctx, r.den - r.num, r.den) < 0)
|
||||
need_reinit = true;
|
||||
}
|
||||
|
||||
if (need_reinit && new_rate != s->in_rate) {
|
||||
// Before reconfiguring, drain the audio that is still buffered
|
||||
// in the resampler.
|
||||
filter_resample(af, NULL);
|
||||
// Reinitialize resampler.
|
||||
configure_lavrr(af, &af->fmt_in, &af->fmt_out, false);
|
||||
}
|
||||
|
||||
return filter_resample(af, in);
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
@@ -286,9 +286,6 @@ static void update_speed(struct af_instance *af, float speed)
|
||||
|
||||
s->frames_stride_scaled = s->scale * s->frames_stride;
|
||||
s->frames_stride_error = MPMIN(s->frames_stride_error, s->frames_stride_scaled);
|
||||
|
||||
MP_VERBOSE(af, "%.3f speed * %.3f scale_nominal = %.3f\n",
|
||||
s->speed, s->scale_nominal, s->scale);
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ int af_fmt_change_bytes(int format, int bytes)
|
||||
if (af_fmt_to_bytes(fmt) == bytes &&
|
||||
af_fmt_is_float(fmt) == af_fmt_is_float(format) &&
|
||||
af_fmt_is_planar(fmt) == af_fmt_is_planar(format) &&
|
||||
af_fmt_is_spdif(fmt) == af_fmt_is_spdif(format))
|
||||
(fmt == format || (!af_fmt_is_spdif(fmt) && !af_fmt_is_spdif(format))))
|
||||
return fmt;
|
||||
}
|
||||
return 0;
|
||||
|
||||
+58
-51
@@ -240,6 +240,7 @@ static void split_ao_device(void *tmp, char *opt, char **out_ao, char **out_dev)
|
||||
}
|
||||
|
||||
struct ao *ao_init_best(struct mpv_global *global,
|
||||
bool ao_null_fallback,
|
||||
struct input_ctx *input_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels)
|
||||
@@ -248,58 +249,67 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
void *tmp = talloc_new(NULL);
|
||||
struct mp_log *log = mp_log_new(tmp, global->log, "ao");
|
||||
struct ao *ao = NULL;
|
||||
struct m_obj_settings *ao_list = opts->audio_driver_list;
|
||||
struct m_obj_settings *ao_list = NULL;
|
||||
int ao_num = 0;
|
||||
|
||||
for (int n = 0; opts->audio_driver_list && opts->audio_driver_list[n].name; n++)
|
||||
MP_TARRAY_APPEND(tmp, ao_list, ao_num, opts->audio_driver_list[n]);
|
||||
|
||||
bool forced_dev = false;
|
||||
char *pref_ao, *pref_dev;
|
||||
split_ao_device(tmp, opts->audio_device, &pref_ao, &pref_dev);
|
||||
if (!(ao_list && ao_list[0].name) && pref_ao) {
|
||||
if (!ao_num && pref_ao) {
|
||||
// Reuse the autoselection code
|
||||
ao_list = talloc_zero_array(tmp, struct m_obj_settings, 2);
|
||||
ao_list[0].name = pref_ao;
|
||||
MP_TARRAY_APPEND(tmp, ao_list, ao_num,
|
||||
(struct m_obj_settings){.name = pref_ao});
|
||||
forced_dev = true;
|
||||
}
|
||||
|
||||
if (ao_list && ao_list[0].name) {
|
||||
for (int n = 0; ao_list[n].name; n++) {
|
||||
if (strlen(ao_list[n].name) == 0)
|
||||
goto autoprobe;
|
||||
mp_verbose(log, "Trying preferred audio driver '%s'\n",
|
||||
ao_list[n].name);
|
||||
char *dev = NULL;
|
||||
if (pref_ao && strcmp(ao_list[n].name, pref_ao) == 0)
|
||||
dev = pref_dev;
|
||||
if (dev)
|
||||
mp_verbose(log, "Using preferred device '%s'\n", dev);
|
||||
ao = ao_init(false, global, input_ctx, encode_lavc_ctx,
|
||||
samplerate, format, channels, dev,
|
||||
ao_list[n].name, ao_list[n].attribs);
|
||||
if (ao)
|
||||
goto done;
|
||||
mp_err(log, "Failed to initialize audio driver '%s'\n",
|
||||
ao_list[n].name);
|
||||
if (forced_dev) {
|
||||
mp_err(log, "This audio driver/device was forced with the "
|
||||
"--audio-device option.\n"
|
||||
"Try unsetting it.\n");
|
||||
}
|
||||
bool autoprobe = ao_num == 0;
|
||||
|
||||
// Something like "--ao=a,b," means do autoprobing after a and b fail.
|
||||
if (ao_num && strlen(ao_list[ao_num - 1].name) == 0) {
|
||||
ao_num -= 1;
|
||||
autoprobe = true;
|
||||
}
|
||||
|
||||
if (autoprobe) {
|
||||
for (int n = 0; audio_out_drivers[n]; n++) {
|
||||
const struct ao_driver *driver = audio_out_drivers[n];
|
||||
if (driver == &audio_out_null)
|
||||
break;
|
||||
MP_TARRAY_APPEND(tmp, ao_list, ao_num,
|
||||
(struct m_obj_settings){.name = (char *)driver->name});
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
autoprobe: ;
|
||||
// now try the rest...
|
||||
for (int i = 0; audio_out_drivers[i]; i++) {
|
||||
const struct ao_driver *driver = audio_out_drivers[i];
|
||||
if (driver == &audio_out_null)
|
||||
break;
|
||||
ao = ao_init(true, global, input_ctx, encode_lavc_ctx, samplerate,
|
||||
format, channels, NULL, (char *)driver->name, NULL);
|
||||
if (ao_null_fallback) {
|
||||
MP_TARRAY_APPEND(tmp, ao_list, ao_num,
|
||||
(struct m_obj_settings){.name = "null"});
|
||||
}
|
||||
|
||||
for (int n = 0; n < ao_num; n++) {
|
||||
struct m_obj_settings *entry = &ao_list[n];
|
||||
bool probing = n + 1 != ao_num;
|
||||
mp_verbose(log, "Trying audio driver '%s'\n", entry->name);
|
||||
char *dev = NULL;
|
||||
if (pref_ao && pref_dev && strcmp(entry->name, pref_ao) == 0) {
|
||||
dev = pref_dev;
|
||||
mp_verbose(log, "Using preferred device '%s'\n", dev);
|
||||
}
|
||||
ao = ao_init(probing, global, input_ctx, encode_lavc_ctx,
|
||||
samplerate, format, channels, dev,
|
||||
entry->name, entry->attribs);
|
||||
if (ao)
|
||||
goto done;
|
||||
break;
|
||||
if (!probing)
|
||||
mp_err(log, "Failed to initialize audio driver '%s'\n", entry->name);
|
||||
if (dev && forced_dev) {
|
||||
mp_err(log, "This audio driver/device was forced with the "
|
||||
"--audio-device option.\nTry unsetting it.\n");
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
talloc_free(tmp);
|
||||
return ao;
|
||||
}
|
||||
@@ -411,18 +421,11 @@ void ao_hotplug_event(struct ao *ao)
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map)
|
||||
{
|
||||
if (mp_msg_test(ao->log, MSGL_DEBUG)) {
|
||||
for (int i = 0; i < s->num_chmaps; i++) {
|
||||
struct mp_chmap c = s->chmaps[i];
|
||||
MP_DBG(ao, "chmap_sel #%d: %s (%s)\n", i, mp_chmap_to_str(&c),
|
||||
mp_chmap_to_str_hr(&c));
|
||||
}
|
||||
}
|
||||
MP_VERBOSE(ao, "Channel layouts:\n");
|
||||
mp_chmal_sel_log(s, ao->log, MSGL_V);
|
||||
bool r = mp_chmap_sel_adjust(s, map);
|
||||
if (r) {
|
||||
MP_DBG(ao, "result: %s (%s)\n", mp_chmap_to_str(map),
|
||||
mp_chmap_to_str_hr(map));
|
||||
}
|
||||
if (r)
|
||||
MP_VERBOSE(ao, "result: %s\n", mp_chmap_to_str(map));
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -490,8 +493,12 @@ static void get_devices(struct ao *ao, struct ao_device_list *list)
|
||||
if (ao->driver->list_devs)
|
||||
ao->driver->list_devs(ao, list);
|
||||
// Add at least a default entry
|
||||
if (list->num_devices == num)
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){"", "Default"});
|
||||
if (list->num_devices == num) {
|
||||
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});
|
||||
}
|
||||
}
|
||||
|
||||
bool ao_hotplug_check_update(struct ao_hotplug *hp)
|
||||
|
||||
@@ -72,6 +72,7 @@ struct encode_lavc_context;
|
||||
struct mp_audio;
|
||||
|
||||
struct ao *ao_init_best(struct mpv_global *global,
|
||||
bool ao_null_fallback,
|
||||
struct input_ctx *input_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels);
|
||||
|
||||
+281
-202
@@ -50,12 +50,13 @@
|
||||
|
||||
struct priv {
|
||||
snd_pcm_t *alsa;
|
||||
bool device_lost;
|
||||
snd_pcm_format_t alsa_fmt;
|
||||
int can_pause;
|
||||
bool can_pause;
|
||||
snd_pcm_sframes_t prepause_frames;
|
||||
double delay_before_pause;
|
||||
int buffersize; // in frames
|
||||
int outburst; // in frames
|
||||
snd_pcm_uframes_t buffersize;
|
||||
snd_pcm_uframes_t outburst;
|
||||
|
||||
char *cfg_device;
|
||||
char *cfg_mixer_device;
|
||||
@@ -83,6 +84,21 @@ struct priv {
|
||||
MP_WARN(ao, "%s: %s\n", (message), snd_strerror(err)); \
|
||||
} while (0)
|
||||
|
||||
// Common code for handling ENODEV, which happens if a device gets "lost", and
|
||||
// can't be used anymore. Returns true if alsa_err is not ENODEV.
|
||||
static bool check_device_present(struct ao *ao, int alsa_err)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (alsa_err != -ENODEV)
|
||||
return true;
|
||||
if (!p->device_lost) {
|
||||
MP_WARN(ao, "Device lost, trying to recover...\n");
|
||||
ao_request_reload(ao);
|
||||
p->device_lost = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
@@ -211,24 +227,19 @@ static const int mp_to_alsa_format[][2] = {
|
||||
MP_SELECT_LE_BE(SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S24_3BE)},
|
||||
{AF_FORMAT_FLOAT, SND_PCM_FORMAT_FLOAT},
|
||||
{AF_FORMAT_DOUBLE, SND_PCM_FORMAT_FLOAT64},
|
||||
{AF_FORMAT_S_MP3, SND_PCM_FORMAT_MPEG},
|
||||
{AF_FORMAT_UNKNOWN, SND_PCM_FORMAT_UNKNOWN},
|
||||
};
|
||||
|
||||
static int find_alsa_format(int af_format)
|
||||
{
|
||||
if (af_fmt_is_spdif(af_format)) {
|
||||
if (af_format == AF_FORMAT_S_MP3) {
|
||||
return SND_PCM_FORMAT_MPEG;
|
||||
} else {
|
||||
return SND_PCM_FORMAT_S16;
|
||||
}
|
||||
}
|
||||
|
||||
af_format = af_fmt_from_planar(af_format);
|
||||
for (int n = 0; mp_to_alsa_format[n][0] != AF_FORMAT_UNKNOWN; n++) {
|
||||
if (mp_to_alsa_format[n][0] == af_format)
|
||||
return mp_to_alsa_format[n][1];
|
||||
}
|
||||
if (af_fmt_is_spdif(af_format))
|
||||
return SND_PCM_FORMAT_S16;
|
||||
return SND_PCM_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -259,6 +270,7 @@ static const int alsa_to_mp_channels[][2] = {
|
||||
{SND_CHMAP_RLC, MP_SP(SDL)},
|
||||
{SND_CHMAP_MONO, MP_SP(FC)},
|
||||
{SND_CHMAP_NA, MP_SPEAKER_ID_NA},
|
||||
{SND_CHMAP_UNKNOWN, MP_SPEAKER_ID_NA},
|
||||
{SND_CHMAP_LAST, MP_SPEAKER_ID_COUNT}
|
||||
};
|
||||
|
||||
@@ -272,28 +284,48 @@ static int find_mp_channel(int alsa_channel)
|
||||
return MP_SPEAKER_ID_COUNT;
|
||||
}
|
||||
|
||||
static int find_alsa_channel(int mp_channel)
|
||||
{
|
||||
for (int i = 0; alsa_to_mp_channels[i][1] != MP_SPEAKER_ID_COUNT; i++) {
|
||||
if (alsa_to_mp_channels[i][1] == mp_channel)
|
||||
return alsa_to_mp_channels[i][0];
|
||||
}
|
||||
#define CHMAP(n, ...) &(struct mp_chmap) MP_CONCAT(MP_CHMAP, n) (__VA_ARGS__)
|
||||
|
||||
return SND_CHMAP_UNKNOWN;
|
||||
// Replace each channel in a with b (a->num == b->num)
|
||||
static void replace_submap(struct mp_chmap *dst, struct mp_chmap *a,
|
||||
struct mp_chmap *b)
|
||||
{
|
||||
struct mp_chmap t = *dst;
|
||||
if (!mp_chmap_is_valid(&t) || mp_chmap_diffn(a, &t) != 0)
|
||||
return;
|
||||
assert(a->num == b->num);
|
||||
for (int n = 0; n < t.num; n++) {
|
||||
for (int i = 0; i < a->num; i++) {
|
||||
if (t.speaker[n] == a->speaker[i]) {
|
||||
t.speaker[n] = b->speaker[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mp_chmap_is_valid(&t))
|
||||
*dst = t;
|
||||
}
|
||||
|
||||
static int mp_chmap_from_alsa(struct mp_chmap *dst, snd_pcm_chmap_t *src)
|
||||
static bool mp_chmap_from_alsa(struct mp_chmap *dst, snd_pcm_chmap_t *src)
|
||||
{
|
||||
*dst = (struct mp_chmap) {0};
|
||||
|
||||
if (src->channels > MP_NUM_CHANNELS)
|
||||
return -1;
|
||||
return false;
|
||||
|
||||
dst->num = src->channels;
|
||||
for (int c = 0; c < dst->num; c++)
|
||||
dst->speaker[c] = find_mp_channel(src->pos[c]);
|
||||
|
||||
return 0;
|
||||
// Assume anything with 1 channel is mono.
|
||||
if (dst->num == 1)
|
||||
dst->speaker[0] = MP_SP(FC);
|
||||
|
||||
// Remap weird Intel HDA HDMI 7.1 layouts correctly.
|
||||
replace_submap(dst, CHMAP(6, FL, FR, BL, BR, SDL, SDR),
|
||||
CHMAP(6, FL, FR, SL, SR, BL, BR));
|
||||
|
||||
return mp_chmap_is_valid(dst);
|
||||
}
|
||||
|
||||
static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
@@ -306,20 +338,21 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
return false;
|
||||
|
||||
for (int i = 0; maps[i] != NULL; i++) {
|
||||
struct mp_chmap entry;
|
||||
mp_chmap_from_alsa(&entry, &maps[i]->map);
|
||||
char aname[128];
|
||||
if (snd_pcm_chmap_print(&maps[i]->map, sizeof(aname), aname) <= 0)
|
||||
aname[0] = '\0';
|
||||
|
||||
if (mp_chmap_is_valid(&entry)) {
|
||||
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(&entry);
|
||||
MP_VERBOSE(ao, "Got supported channel map: %s (type %s)\n",
|
||||
mp_chmap_to_str(&entry),
|
||||
snd_pcm_chmap_type_name(maps[i]->type));
|
||||
mp_chmap_sel_add_map(&chmap_sel, &entry);
|
||||
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);
|
||||
} else {
|
||||
char tmp[128];
|
||||
if (snd_pcm_chmap_print(&maps[i]->map, sizeof(tmp), tmp) > 0)
|
||||
MP_VERBOSE(ao, "skipping unknown ALSA channel map: %s\n", tmp);
|
||||
MP_VERBOSE(ao, "skipping unknown ALSA channel map: %s\n", aname);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,6 +361,108 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
return ao_chmap_sel_adjust(ao, &chmap_sel, chmap);
|
||||
}
|
||||
|
||||
// Map back our selected channel layout to an ALSA one. This is done this way so
|
||||
// that our ALSA->mp_chmap mapping function only has to go one way.
|
||||
// The return value is to be freed with free().
|
||||
static snd_pcm_chmap_t *map_back_chmap(struct ao *ao, struct mp_chmap *chmap)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!mp_chmap_is_valid(chmap))
|
||||
return NULL;
|
||||
|
||||
snd_pcm_chmap_query_t **maps = snd_pcm_query_chmaps(p->alsa);
|
||||
if (!maps)
|
||||
return NULL;
|
||||
|
||||
snd_pcm_chmap_t *alsa_chmap = NULL;
|
||||
|
||||
for (int i = 0; maps[i] != NULL; i++) {
|
||||
struct mp_chmap entry;
|
||||
if (!mp_chmap_from_alsa(&entry, &maps[i]->map))
|
||||
continue;
|
||||
|
||||
if (mp_chmap_equals(chmap, &entry) ||
|
||||
(mp_chmap_equals_reordered(chmap, &entry) &&
|
||||
maps[i]->type == SND_CHMAP_TYPE_VAR))
|
||||
{
|
||||
alsa_chmap = calloc(1, sizeof(*alsa_chmap) +
|
||||
sizeof(alsa_chmap->pos[0]) * entry.num);
|
||||
if (!alsa_chmap)
|
||||
break;
|
||||
alsa_chmap->channels = entry.num;
|
||||
|
||||
// Undo if mp_chmap_reorder() was called on the result.
|
||||
int reorder[MP_NUM_CHANNELS];
|
||||
mp_chmap_get_reorder(reorder, chmap, &entry);
|
||||
for (int n = 0; n < entry.num; n++)
|
||||
alsa_chmap->pos[n] = maps[i]->map.pos[reorder[n]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
snd_pcm_free_chmaps(maps);
|
||||
return alsa_chmap;
|
||||
}
|
||||
|
||||
|
||||
static int set_chmap(struct ao *ao, struct mp_chmap *dev_chmap, int num_channels)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
snd_pcm_chmap_t *alsa_chmap = map_back_chmap(ao, dev_chmap);
|
||||
if (alsa_chmap) {
|
||||
char tmp[128];
|
||||
if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0)
|
||||
MP_VERBOSE(ao, "trying to set ALSA channel map: %s\n", tmp);
|
||||
|
||||
err = snd_pcm_set_chmap(p->alsa, alsa_chmap);
|
||||
if (err == -ENXIO) {
|
||||
// A device my not be able to set any channel map, even channel maps
|
||||
// that were reported as supported. This is either because the ALSA
|
||||
// device is broken (dmix), or because the driver has only 1
|
||||
// channel map per channel count, and setting the map is not needed.
|
||||
MP_VERBOSE(ao, "device returned ENXIO when setting channel map %s\n",
|
||||
mp_chmap_to_str(dev_chmap));
|
||||
} else {
|
||||
CHECK_ALSA_WARN("Channel map setup failed");
|
||||
}
|
||||
|
||||
free(alsa_chmap);
|
||||
}
|
||||
|
||||
alsa_chmap = snd_pcm_get_chmap(p->alsa);
|
||||
if (alsa_chmap) {
|
||||
char tmp[128];
|
||||
if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0)
|
||||
MP_VERBOSE(ao, "channel map reported by ALSA: %s\n", tmp);
|
||||
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_from_alsa(&chmap, alsa_chmap);
|
||||
|
||||
MP_VERBOSE(ao, "which we understand as: %s\n", mp_chmap_to_str(&chmap));
|
||||
|
||||
if (p->cfg_ignore_chmap) {
|
||||
MP_VERBOSE(ao, "user set ignore-chmap; ignoring the channel map.\n");
|
||||
} else if (af_fmt_is_spdif(ao->format)) {
|
||||
MP_VERBOSE(ao, "using spdif passthrough; ignoring the channel map.\n");
|
||||
} else if (!mp_chmap_is_valid(&chmap)) {
|
||||
MP_WARN(ao, "Got unknown channel map from ALSA.\n");
|
||||
} 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))
|
||||
MP_VERBOSE(ao, "which is what we requested.\n");
|
||||
ao->channels = chmap;
|
||||
}
|
||||
|
||||
free(alsa_chmap);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else /* HAVE_CHMAP_API */
|
||||
|
||||
static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
@@ -335,6 +470,11 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
return false;
|
||||
}
|
||||
|
||||
static int set_chmap(struct ao *ao, struct mp_chmap *dev_chmap, int num_channels)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* else HAVE_CHMAP_API */
|
||||
|
||||
static int map_iec958_srate(int srate)
|
||||
@@ -355,8 +495,8 @@ static int map_iec958_srate(int srate)
|
||||
}
|
||||
|
||||
// ALSA device strings can have parameters. They are usually appended to the
|
||||
// device name. Since there can be various forms, and we (sometimes) want to
|
||||
// append them to unknown device strings, which possibly already include params.
|
||||
// device name. There can be various forms, and we (sometimes) want to append
|
||||
// them to unknown device strings, which possibly already include params.
|
||||
static char *append_params(void *ta_parent, const char *device, const char *p)
|
||||
{
|
||||
if (!p || !p[0])
|
||||
@@ -427,16 +567,14 @@ static void uninit(struct ao *ao)
|
||||
int err;
|
||||
|
||||
err = snd_pcm_close(p->alsa);
|
||||
p->alsa = NULL;
|
||||
CHECK_ALSA_ERROR("pcm close error");
|
||||
}
|
||||
|
||||
alsa_error: ;
|
||||
}
|
||||
|
||||
#define INIT_OK 0
|
||||
#define INIT_ERROR -1
|
||||
#define INIT_BRAINDEATH -2
|
||||
static int init_device(struct ao *ao, bool second_try)
|
||||
static int init_device(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
@@ -454,31 +592,18 @@ static int init_device(struct ao *ao, bool second_try)
|
||||
CHECK_ALSA_WARN("Unable to set blocking mode");
|
||||
|
||||
snd_pcm_hw_params_t *alsa_hwparams;
|
||||
snd_pcm_sw_params_t *alsa_swparams;
|
||||
|
||||
snd_pcm_hw_params_alloca(&alsa_hwparams);
|
||||
snd_pcm_sw_params_alloca(&alsa_swparams);
|
||||
|
||||
err = snd_pcm_hw_params_any(p->alsa, alsa_hwparams);
|
||||
CHECK_ALSA_ERROR("Unable to get initial parameters");
|
||||
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, try_formats);
|
||||
for (int n = 0; try_formats[n]; n++) {
|
||||
ao->format = try_formats[n];
|
||||
p->alsa_fmt = find_alsa_format(ao->format);
|
||||
if (snd_pcm_hw_params_test_format(p->alsa, alsa_hwparams, p->alsa_fmt) >= 0)
|
||||
break;
|
||||
// Some ALSA drivers have broken delay reporting, so disable the ALSA
|
||||
// resampling plugin by default.
|
||||
if (!p->cfg_resample) {
|
||||
err = snd_pcm_hw_params_set_rate_resample(p->alsa, alsa_hwparams, 0);
|
||||
CHECK_ALSA_ERROR("Unable to disable resampling");
|
||||
}
|
||||
|
||||
if (!ao->format) {
|
||||
MP_ERR(ao, "Can't find appropriate sample format.\n");
|
||||
goto alsa_error;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_set_format(p->alsa, alsa_hwparams, p->alsa_fmt);
|
||||
CHECK_ALSA_ERROR("Unable to set format");
|
||||
|
||||
snd_pcm_access_t access = af_fmt_is_planar(ao->format)
|
||||
? SND_PCM_ACCESS_RW_NONINTERLEAVED
|
||||
: SND_PCM_ACCESS_RW_INTERLEAVED;
|
||||
@@ -490,6 +615,29 @@ static int init_device(struct ao *ao, bool second_try)
|
||||
}
|
||||
CHECK_ALSA_ERROR("Unable to set access type");
|
||||
|
||||
bool found_format = false;
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, try_formats);
|
||||
for (int n = 0; try_formats[n]; n++) {
|
||||
if (af_fmt_is_planar(ao->format) != af_fmt_is_planar(try_formats[n]))
|
||||
continue; // implied SND_PCM_ACCESS mismatches
|
||||
p->alsa_fmt = find_alsa_format(try_formats[n]);
|
||||
MP_VERBOSE(ao, "trying format %s\n", af_fmt_to_str(try_formats[n]));
|
||||
if (snd_pcm_hw_params_test_format(p->alsa, alsa_hwparams, p->alsa_fmt) >= 0) {
|
||||
ao->format = try_formats[n];
|
||||
found_format = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_format) {
|
||||
MP_ERR(ao, "Can't find appropriate sample format.\n");
|
||||
goto alsa_error;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_set_format(p->alsa, alsa_hwparams, p->alsa_fmt);
|
||||
CHECK_ALSA_ERROR("Unable to set format");
|
||||
|
||||
struct mp_chmap dev_chmap = ao->channels;
|
||||
if (af_fmt_is_spdif(ao->format) || p->cfg_ignore_chmap) {
|
||||
dev_chmap.num = 0; // disable chmap API
|
||||
@@ -514,157 +662,51 @@ static int init_device(struct ao *ao, bool second_try)
|
||||
goto alsa_error;
|
||||
}
|
||||
|
||||
if (num_channels != ao->channels.num) {
|
||||
int req = ao->channels.num;
|
||||
mp_chmap_from_channels_alsa(&ao->channels, num_channels);
|
||||
if (!mp_chmap_is_valid(&ao->channels))
|
||||
mp_chmap_from_channels(&ao->channels, 2);
|
||||
MP_ERR(ao, "Asked for %d channels, got %d - fallback to %s.\n", req,
|
||||
num_channels, mp_chmap_to_str(&ao->channels));
|
||||
}
|
||||
|
||||
// Some ALSA drivers have broken delay reporting, so disable the ALSA
|
||||
// resampling plugin by default.
|
||||
if (!p->cfg_resample) {
|
||||
err = snd_pcm_hw_params_set_rate_resample(p->alsa, alsa_hwparams, 0);
|
||||
CHECK_ALSA_ERROR("Unable to disable resampling");
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_set_rate_near
|
||||
(p->alsa, alsa_hwparams, &ao->samplerate, NULL);
|
||||
CHECK_ALSA_ERROR("Unable to set samplerate-2");
|
||||
|
||||
snd_pcm_hw_params_t *hwparams_backup;
|
||||
snd_pcm_hw_params_alloca(&hwparams_backup);
|
||||
snd_pcm_hw_params_copy(hwparams_backup, alsa_hwparams);
|
||||
|
||||
// Cargo-culted buffer settings; might still be useful for PulseAudio.
|
||||
err = snd_pcm_hw_params_set_buffer_time_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){BUFFER_TIME}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set buffer time near");
|
||||
|
||||
err = snd_pcm_hw_params_set_periods_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){FRAGCOUNT}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set periods");
|
||||
if (err >= 0) {
|
||||
err = snd_pcm_hw_params_set_periods_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){FRAGCOUNT}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set periods");
|
||||
}
|
||||
if (err < 0)
|
||||
snd_pcm_hw_params_copy(alsa_hwparams, hwparams_backup);
|
||||
|
||||
/* finally install hardware parameters */
|
||||
err = snd_pcm_hw_params(p->alsa, alsa_hwparams);
|
||||
CHECK_ALSA_ERROR("Unable to set hw-parameters");
|
||||
|
||||
/* end setting hw-params */
|
||||
if (set_chmap(ao, &dev_chmap, num_channels) < 0)
|
||||
goto alsa_error;
|
||||
|
||||
#if HAVE_CHMAP_API
|
||||
if (mp_chmap_is_valid(&dev_chmap)) {
|
||||
snd_pcm_chmap_t *alsa_chmap =
|
||||
calloc(1, sizeof(*alsa_chmap) +
|
||||
sizeof(alsa_chmap->pos[0]) * dev_chmap.num);
|
||||
if (!alsa_chmap)
|
||||
goto alsa_error;
|
||||
|
||||
alsa_chmap->channels = dev_chmap.num;
|
||||
for (int c = 0; c < dev_chmap.num; c++)
|
||||
alsa_chmap->pos[c] = find_alsa_channel(dev_chmap.speaker[c]);
|
||||
|
||||
// mpv and ALSA use different conventions for mono
|
||||
if (dev_chmap.num == 1 && dev_chmap.speaker[0] == MP_SP(FC))
|
||||
alsa_chmap->pos[0] = SND_CHMAP_MONO;
|
||||
|
||||
char tmp[128];
|
||||
if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0)
|
||||
MP_VERBOSE(ao, "trying to set ALSA channel map: %s\n", tmp);
|
||||
|
||||
err = snd_pcm_set_chmap(p->alsa, alsa_chmap);
|
||||
if (err == -ENXIO) {
|
||||
// A device my not be able to set any channel map, even channel maps
|
||||
// that were reported as supported. This is either because the ALSA
|
||||
// device is broken (dmix), or because the driver has only 1
|
||||
// channel map per channel count, and setting the map is not needed.
|
||||
MP_VERBOSE(ao, "device returned ENXIO when setting channel map %s\n",
|
||||
mp_chmap_to_str(&dev_chmap));
|
||||
} else {
|
||||
CHECK_ALSA_WARN("Channel map setup failed");
|
||||
}
|
||||
|
||||
free(alsa_chmap);
|
||||
if (num_channels != ao->channels.num) {
|
||||
int req = ao->channels.num;
|
||||
mp_chmap_from_channels(&ao->channels, MPMIN(2, num_channels));
|
||||
MP_ERR(ao, "Asked for %d channels, got %d - fallback to %s.\n", req,
|
||||
num_channels, mp_chmap_to_str(&ao->channels));
|
||||
}
|
||||
|
||||
snd_pcm_chmap_t *alsa_chmap = snd_pcm_get_chmap(p->alsa);
|
||||
if (alsa_chmap) {
|
||||
char tmp[128];
|
||||
if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0)
|
||||
MP_VERBOSE(ao, "channel map reported by ALSA: %s\n", tmp);
|
||||
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_from_alsa(&chmap, alsa_chmap);
|
||||
|
||||
MP_VERBOSE(ao, "which we understand as: %s\n", mp_chmap_to_str(&chmap));
|
||||
|
||||
if (p->cfg_ignore_chmap) {
|
||||
MP_VERBOSE(ao, "user set ignore-chmap; ignoring the channel map.\n");
|
||||
} else if (af_fmt_is_spdif(ao->format)) {
|
||||
MP_VERBOSE(ao, "using spdif passthrough; ignoring the channel map.\n");
|
||||
} else if (mp_chmap_is_valid(&chmap)) {
|
||||
// Is it one that contains NA channels?
|
||||
struct mp_chmap without_na = chmap;
|
||||
mp_chmap_remove_na(&without_na);
|
||||
|
||||
if (mp_chmap_is_valid(&without_na) &&
|
||||
!mp_chmap_equals(&without_na, &chmap) &&
|
||||
!mp_chmap_equals(&chmap, &ao->channels) &&
|
||||
!second_try)
|
||||
{
|
||||
// Sometimes, ALSA will advertise certain chmaps, but it's not
|
||||
// possible to set them. This can happen with dmix: as of
|
||||
// alsa 1.0.28, dmix can do stereo only, but advertises the
|
||||
// surround chmaps of the underlying device. In this case,
|
||||
// e.g. setting 6 channels will succeed, but requesting 5.1
|
||||
// afterwards will fail. Then it will return something like
|
||||
// "FL FR NA NA NA NA" as channel map. This means we would
|
||||
// have to pad stereo output to 6 channels with silence, which
|
||||
// would require lots of extra processing. You can't change
|
||||
// the number of channels to 2 either, because the hw params
|
||||
// are already set! So just fuck it and reopen the device with
|
||||
// the chmap "cleaned out" of NA entries.
|
||||
MP_VERBOSE(ao, "Working around braindead ALSA behavior.\n");
|
||||
err = snd_pcm_close(p->alsa);
|
||||
p->alsa = NULL;
|
||||
CHECK_ALSA_ERROR("pcm close error");
|
||||
ao->channels = without_na;
|
||||
return INIT_BRAINDEATH;
|
||||
}
|
||||
|
||||
if (mp_chmap_equals(&chmap, &ao->channels)) {
|
||||
MP_VERBOSE(ao, "which is what we requested.\n");
|
||||
} else if (chmap.num == ao->channels.num) {
|
||||
MP_VERBOSE(ao, "using the ALSA channel map.\n");
|
||||
ao->channels = chmap;
|
||||
} else {
|
||||
MP_WARN(ao, "ALSA channel map conflicts with channel count!\n");
|
||||
}
|
||||
} else {
|
||||
MP_WARN(ao, "Got unknown channel map from ALSA.\n");
|
||||
}
|
||||
|
||||
// mpv and ALSA use different conventions for mono
|
||||
if (ao->channels.num == 1) {
|
||||
MP_VERBOSE(ao, "assuming we actually got MONO from ALSA.\n");
|
||||
ao->channels.speaker[0] = MP_SP(FC);
|
||||
}
|
||||
|
||||
free(alsa_chmap);
|
||||
}
|
||||
#endif
|
||||
|
||||
snd_pcm_uframes_t bufsize;
|
||||
err = snd_pcm_hw_params_get_buffer_size(alsa_hwparams, &bufsize);
|
||||
err = snd_pcm_hw_params_get_buffer_size(alsa_hwparams, &p->buffersize);
|
||||
CHECK_ALSA_ERROR("Unable to get buffersize");
|
||||
|
||||
p->buffersize = bufsize;
|
||||
MP_VERBOSE(ao, "got buffersize=%i samples\n", p->buffersize);
|
||||
|
||||
snd_pcm_uframes_t chunk_size;
|
||||
err = snd_pcm_hw_params_get_period_size(alsa_hwparams, &chunk_size, NULL);
|
||||
err = snd_pcm_hw_params_get_period_size(alsa_hwparams, &p->outburst, NULL);
|
||||
CHECK_ALSA_ERROR("Unable to get period size");
|
||||
|
||||
MP_VERBOSE(ao, "got period size %li\n", chunk_size);
|
||||
p->outburst = chunk_size;
|
||||
p->can_pause = snd_pcm_hw_params_can_pause(alsa_hwparams);
|
||||
|
||||
snd_pcm_sw_params_t *alsa_swparams;
|
||||
snd_pcm_sw_params_alloca(&alsa_swparams);
|
||||
|
||||
/* setting software parameters */
|
||||
err = snd_pcm_sw_params_current(p->alsa, alsa_swparams);
|
||||
CHECK_ALSA_ERROR("Unable to get sw-parameters");
|
||||
|
||||
@@ -674,7 +716,7 @@ static int init_device(struct ao *ao, bool second_try)
|
||||
|
||||
/* start playing when one period has been written */
|
||||
err = snd_pcm_sw_params_set_start_threshold
|
||||
(p->alsa, alsa_swparams, chunk_size);
|
||||
(p->alsa, alsa_swparams, p->outburst);
|
||||
CHECK_ALSA_ERROR("Unable to set start threshold");
|
||||
|
||||
/* disable underrun reporting */
|
||||
@@ -690,15 +732,15 @@ static int init_device(struct ao *ao, bool second_try)
|
||||
err = snd_pcm_sw_params(p->alsa, alsa_swparams);
|
||||
CHECK_ALSA_ERROR("Unable to set sw-parameters");
|
||||
|
||||
/* end setting sw-params */
|
||||
MP_VERBOSE(ao, "hw pausing supported: %s\n", p->can_pause ? "yes" : "no");
|
||||
MP_VERBOSE(ao, "buffersize: %d samples\n", (int)p->buffersize);
|
||||
MP_VERBOSE(ao, "period size: %d samples\n", (int)p->outburst);
|
||||
|
||||
p->can_pause = snd_pcm_hw_params_can_pause(alsa_hwparams);
|
||||
|
||||
return INIT_OK;
|
||||
return 0;
|
||||
|
||||
alsa_error:
|
||||
uninit(ao);
|
||||
return INIT_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
@@ -709,10 +751,33 @@ static int init(struct ao *ao)
|
||||
|
||||
MP_VERBOSE(ao, "using ALSA version: %s\n", snd_asoundlib_version());
|
||||
|
||||
int r = init_device(ao, false);
|
||||
if (r == INIT_BRAINDEATH)
|
||||
r = init_device(ao, true); // retry with normalized channel layout
|
||||
return r == INIT_OK ? 0 : -1;
|
||||
int r = init_device(ao);
|
||||
|
||||
// Sometimes, ALSA will advertise certain chmaps, but it's not possible to
|
||||
// set them. This can happen with dmix: as of alsa 1.0.29, dmix can do
|
||||
// stereo only, but advertises the surround chmaps of the underlying device.
|
||||
// In this case, e.g. setting 6 channels will succeed, but requesting 5.1
|
||||
// afterwards will fail. Then it will return something like "FL FR NA NA NA NA"
|
||||
// as channel map. This means we would have to pad stereo output to 6
|
||||
// channels with silence, which would require lots of extra processing. You
|
||||
// can't change the number of channels to 2 either, because the hw params
|
||||
// are already set! So just fuck it and reopen the device with the chmap
|
||||
// "cleaned out" of NA entries.
|
||||
if (r >= 0) {
|
||||
struct mp_chmap without_na = ao->channels;
|
||||
mp_chmap_remove_na(&without_na);
|
||||
|
||||
if (mp_chmap_is_valid(&without_na) && without_na.num <= 2 &&
|
||||
ao->channels.num > 2)
|
||||
{
|
||||
MP_VERBOSE(ao, "Working around braindead dmix multichannel behavior.\n");
|
||||
uninit(ao);
|
||||
ao->channels = without_na;
|
||||
r = init_device(ao);
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
@@ -730,6 +795,8 @@ static int get_space(struct ao *ao)
|
||||
snd_pcm_status_alloca(&status);
|
||||
|
||||
err = snd_pcm_status(p->alsa, status);
|
||||
if (!check_device_present(ao, err))
|
||||
goto alsa_error;
|
||||
CHECK_ALSA_ERROR("cannot get pcm status");
|
||||
|
||||
unsigned space = snd_pcm_status_get_avail(status);
|
||||
@@ -773,7 +840,6 @@ static void audio_pause(struct ao *ao)
|
||||
CHECK_ALSA_ERROR("pcm pause error");
|
||||
}
|
||||
} else {
|
||||
MP_VERBOSE(ao, "pause not supported by hardware\n");
|
||||
if (snd_pcm_delay(p->alsa, &p->prepause_frames) < 0
|
||||
|| p->prepause_frames < 0)
|
||||
p->prepause_frames = 0;
|
||||
@@ -856,9 +922,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
|
||||
if (res == -EINTR || res == -EAGAIN) { /* retry */
|
||||
res = 0;
|
||||
} else if (res == -ENODEV) {
|
||||
MP_WARN(ao, "Device lost, trying to recover...\n");
|
||||
ao_request_reload(ao);
|
||||
} else if (!check_device_present(ao, res)) {
|
||||
goto alsa_error;
|
||||
} else if (res < 0) {
|
||||
if (res == -ESTRPIPE) { /* suspend */
|
||||
resume_device(ao);
|
||||
@@ -912,6 +977,20 @@ alsa_error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
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"};
|
||||
for (int i = 0; i < MP_ARRAY_SIZE(crap); i++) {
|
||||
int l = strlen(crap[i]);
|
||||
if (name && strncmp(name, crap[i], l) == 0 &&
|
||||
(!name[l] || name[l] == ':'))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
void **hints;
|
||||
@@ -922,7 +1001,7 @@ static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
char *name = snd_device_name_get_hint(hints[n], "NAME");
|
||||
char *desc = snd_device_name_get_hint(hints[n], "DESC");
|
||||
char *io = snd_device_name_get_hint(hints[n], "IOID");
|
||||
if (io && strcmp(io, "Output") == 0) {
|
||||
if (!is_useless_device(name) && (!io || strcmp(io, "Output") == 0)) {
|
||||
char desc2[1024];
|
||||
snprintf(desc2, sizeof(desc2), "%s", desc ? desc : "");
|
||||
for (int i = 0; desc2[i]; i++) {
|
||||
|
||||
@@ -180,6 +180,8 @@ static void init_physical_format(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
OSErr err;
|
||||
|
||||
void *tmp = talloc_new(NULL);
|
||||
|
||||
AudioStreamBasicDescription asbd;
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
|
||||
@@ -190,6 +192,8 @@ static void init_physical_format(struct ao *ao)
|
||||
&streams, &n_streams);
|
||||
CHECK_CA_ERROR("could not get number of streams");
|
||||
|
||||
talloc_steal(tmp, streams);
|
||||
|
||||
MP_VERBOSE(ao, "Found %zd substream(s).\n", n_streams);
|
||||
|
||||
for (int i = 0; i < n_streams; i++) {
|
||||
@@ -204,6 +208,7 @@ static void init_physical_format(struct ao *ao)
|
||||
if (!CHECK_CA_WARN("could not get number of stream formats"))
|
||||
continue; // try next one
|
||||
|
||||
talloc_steal(tmp, formats);
|
||||
|
||||
uint32_t direction;
|
||||
err = CA_GET(streams[i], kAudioStreamPropertyDirection, &direction);
|
||||
@@ -232,6 +237,12 @@ static void init_physical_format(struct ao *ao)
|
||||
&p->original_asbd);
|
||||
CHECK_CA_WARN("could not get current physical stream format");
|
||||
|
||||
if (ca_asbd_equals(&p->original_asbd, &best_asbd)) {
|
||||
MP_VERBOSE(ao, "Requested format already set, not changing.\n");
|
||||
p->original_asbd.mFormatID = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ca_change_physical_format_sync(ao, streams[i], best_asbd))
|
||||
p->original_asbd = (AudioStreamBasicDescription){0};
|
||||
break;
|
||||
@@ -239,6 +250,7 @@ static void init_physical_format(struct ao *ao)
|
||||
}
|
||||
|
||||
coreaudio_error:
|
||||
talloc_free(tmp);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,9 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
|
||||
AudioChannelLayout *r;
|
||||
OSStatus err;
|
||||
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelDescriptions)
|
||||
return l;
|
||||
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
|
||||
uint32_t psize;
|
||||
err = AudioFormatGetPropertyInfo(
|
||||
@@ -109,7 +112,7 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
|
||||
kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(uint32_t), &l->mChannelBitmap, &psize, r);
|
||||
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (get)");
|
||||
} else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
|
||||
} else {
|
||||
uint32_t psize;
|
||||
err = AudioFormatGetPropertyInfo(
|
||||
kAudioFormatProperty_ChannelLayoutForTag,
|
||||
@@ -120,10 +123,11 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
|
||||
kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize, r);
|
||||
CHECK_CA_ERROR("failed to convert channel tag to descriptions (get)");
|
||||
} else {
|
||||
r = l;
|
||||
}
|
||||
|
||||
MP_VERBOSE(ao, "converted input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_V, l);
|
||||
|
||||
return r;
|
||||
coreaudio_error:
|
||||
return NULL;
|
||||
@@ -134,22 +138,20 @@ static bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
{
|
||||
void *talloc_ctx = talloc_new(NULL);
|
||||
|
||||
MP_DBG(ao, "input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_DEBUG, layout);
|
||||
MP_VERBOSE(ao, "input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_V, layout);
|
||||
|
||||
AudioChannelLayout *l = ca_layout_to_custom_layout(ao, talloc_ctx, layout);
|
||||
if (!l)
|
||||
goto coreaudio_error;
|
||||
|
||||
MP_VERBOSE(ao, "converted input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_V, l);
|
||||
|
||||
if (l->mNumberChannelDescriptions > MP_NUM_CHANNELS) {
|
||||
MP_VERBOSE(ao, "layout has too many descriptions (%u, max: %d)\n",
|
||||
(unsigned) l->mNumberChannelDescriptions, MP_NUM_CHANNELS);
|
||||
return false;
|
||||
}
|
||||
|
||||
chmap->num = l->mNumberChannelDescriptions;
|
||||
for (int n = 0; n < l->mNumberChannelDescriptions; n++) {
|
||||
AudioChannelLabel label = l->mChannelDescriptions[n].mChannelLabel;
|
||||
int speaker = ca_label_to_mp_speaker_id(label);
|
||||
@@ -157,14 +159,13 @@ static bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
MP_VERBOSE(ao, "channel label=%u unusable to build channel "
|
||||
"bitmap, skipping layout\n", (unsigned) label);
|
||||
goto coreaudio_error;
|
||||
} else {
|
||||
chmap->speaker[n] = speaker;
|
||||
chmap->num = n + 1;
|
||||
}
|
||||
chmap->speaker[n] = speaker;
|
||||
}
|
||||
|
||||
talloc_free(talloc_ctx);
|
||||
return chmap->num > 0;
|
||||
MP_VERBOSE(ao, "mp chmap: %s\n", mp_chmap_to_str(chmap));
|
||||
return mp_chmap_is_valid(chmap) && !mp_chmap_is_unknown(chmap);
|
||||
coreaudio_error:
|
||||
MP_VERBOSE(ao, "converted input channel layout (failed):\n");
|
||||
ca_log_layout(ao, MSGL_V, layout);
|
||||
@@ -234,34 +235,68 @@ coreaudio_error:
|
||||
return r;
|
||||
}
|
||||
|
||||
bool ca_init_chmap(struct ao *ao, AudioDeviceID device)
|
||||
static void ca_retrieve_layouts(struct ao *ao, struct mp_chmap_sel *s,
|
||||
AudioDeviceID device)
|
||||
{
|
||||
void *ta_ctx = talloc_new(NULL);
|
||||
|
||||
struct mp_chmap_sel chmap_sel = {.tmp = ta_ctx};
|
||||
struct mp_chmap chmap = {0};
|
||||
|
||||
mp_chmap_sel_add_map(&chmap_sel, &(struct mp_chmap)MP_CHMAP_INIT_MONO);
|
||||
struct mp_chmap chmap;
|
||||
|
||||
AudioChannelLayout *ml = ca_query_layout(ao, device, ta_ctx);
|
||||
if (ml && ca_layout_to_mp_chmap(ao, ml, &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
mp_chmap_sel_add_map(s, &chmap);
|
||||
|
||||
AudioChannelLayout *sl = ca_query_stereo_layout(ao, device, ta_ctx);
|
||||
if (sl && ca_layout_to_mp_chmap(ao, sl, &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
mp_chmap_sel_add_map(s, &chmap);
|
||||
|
||||
talloc_free(ta_ctx);
|
||||
}
|
||||
|
||||
bool ca_init_chmap(struct ao *ao, AudioDeviceID device)
|
||||
{
|
||||
struct mp_chmap_sel chmap_sel = {0};
|
||||
ca_retrieve_layouts(ao, &chmap_sel, device);
|
||||
|
||||
if (!chmap_sel.num_chmaps)
|
||||
mp_chmap_sel_add_map(&chmap_sel, &(struct mp_chmap)MP_CHMAP_INIT_STEREO);
|
||||
|
||||
mp_chmap_sel_add_map(&chmap_sel, &(struct mp_chmap)MP_CHMAP_INIT_MONO);
|
||||
|
||||
if (!ao_chmap_sel_adjust(ao, &chmap_sel, &ao->channels)) {
|
||||
MP_ERR(ao, "could not select a suitable channel map among the "
|
||||
"hardware supported ones. Make sure to configure your "
|
||||
"output device correctly in 'Audio MIDI Setup.app'\n");
|
||||
goto coreaudio_error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ca_get_active_chmap(struct ao *ao, AudioDeviceID device, int channel_count,
|
||||
struct mp_chmap *out_map)
|
||||
{
|
||||
// Apparently, we have to guess by looking back at the supported layouts,
|
||||
// and I haven't found a property that retrieves the actual currently
|
||||
// active channel layout.
|
||||
|
||||
struct mp_chmap_sel chmap_sel = {0};
|
||||
ca_retrieve_layouts(ao, &chmap_sel, device);
|
||||
|
||||
// Use any exact match.
|
||||
for (int n = 0; n < chmap_sel.num_chmaps; n++) {
|
||||
if (chmap_sel.chmaps[n].num == channel_count) {
|
||||
MP_VERBOSE(ao, "mismatching channels - fallback #%d\n", n);
|
||||
*out_map = chmap_sel.chmaps[n];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(ta_ctx);
|
||||
return true;
|
||||
|
||||
coreaudio_error:
|
||||
talloc_free(ta_ctx);
|
||||
return false;
|
||||
// Fall back to stereo or mono, and fill the rest with silence. (We don't
|
||||
// know what the device expects. We could use a larger default layout here,
|
||||
// but let's not.)
|
||||
mp_chmap_from_channels(out_map, MPMIN(2, channel_count));
|
||||
out_map->num = channel_count;
|
||||
for (int n = 2; n < out_map->num; n++)
|
||||
out_map->speaker[n] = MP_SPEAKER_ID_NA;
|
||||
MP_WARN(ao, "mismatching channels - falling back to %s\n",
|
||||
mp_chmap_to_str(out_map));
|
||||
}
|
||||
@@ -20,6 +20,10 @@
|
||||
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
struct mp_chmap;
|
||||
|
||||
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
|
||||
@@ -300,6 +300,12 @@ static int init(struct ao *ao)
|
||||
|
||||
ca_print_asbd(ao, "virtual format", &p->stream_asbd);
|
||||
|
||||
if (p->stream_asbd.mChannelsPerFrame > MP_NUM_CHANNELS) {
|
||||
MP_ERR(ao, "unsupported number of channels: %d > %d.\n",
|
||||
p->stream_asbd.mChannelsPerFrame, MP_NUM_CHANNELS);
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
int new_format = ca_asbd_to_mp_format(&p->stream_asbd);
|
||||
|
||||
// If both old and new formats are spdif, avoid changing it due to the
|
||||
@@ -315,7 +321,10 @@ static int init(struct ao *ao)
|
||||
ao->samplerate = p->stream_asbd.mSampleRate;
|
||||
|
||||
if (ao->channels.num != p->stream_asbd.mChannelsPerFrame) {
|
||||
// We really expect that ca_init_chmap() fixes the layout to the HW's.
|
||||
ca_get_active_chmap(ao, p->device, p->stream_asbd.mChannelsPerFrame,
|
||||
&ao->channels);
|
||||
}
|
||||
if (!ao->channels.num) {
|
||||
MP_ERR(ao, "number of channels changed, and unknown channel layout!\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,9 @@ static bool ca_is_output_device(struct ao *ao, AudioDeviceID dev)
|
||||
size_t n_buffers;
|
||||
AudioBufferList *buffers;
|
||||
const ca_scope scope = kAudioDevicePropertyStreamConfiguration;
|
||||
CA_GET_ARY_O(dev, scope, &buffers, &n_buffers);
|
||||
OSStatus err = CA_GET_ARY_O(dev, scope, &buffers, &n_buffers);
|
||||
if (err != noErr)
|
||||
return false;
|
||||
talloc_free(buffers);
|
||||
return n_buffers > 0;
|
||||
}
|
||||
@@ -71,11 +73,16 @@ void ca_get_device_list(struct ao *ao, struct ao_device_list *list)
|
||||
char *name;
|
||||
char *desc;
|
||||
err = CA_GET_STR(devs[i], kAudioDevicePropertyDeviceUID, &name);
|
||||
if (err != noErr) {
|
||||
MP_VERBOSE(ao, "skipping device %d, which has no UID\n", i);
|
||||
talloc_free(ta_ctx);
|
||||
continue;
|
||||
}
|
||||
talloc_steal(ta_ctx, name);
|
||||
err = CA_GET_STR(devs[i], kAudioObjectPropertyName, &desc);
|
||||
talloc_steal(ta_ctx, desc);
|
||||
if (err != noErr)
|
||||
desc = "Unknown";
|
||||
desc = talloc_strdup(NULL, "Unknown");
|
||||
talloc_steal(ta_ctx, desc);
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc});
|
||||
talloc_free(ta_ctx);
|
||||
}
|
||||
@@ -498,6 +505,8 @@ bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &prev_format);
|
||||
CHECK_CA_ERROR("can't get current physical format");
|
||||
|
||||
ca_print_asbd(ao, "format in use before switching:", &prev_format);
|
||||
|
||||
/* Install the callback. */
|
||||
AudioObjectPropertyAddress p_addr = {
|
||||
.mSelector = kAudioStreamPropertyPhysicalFormat,
|
||||
@@ -516,7 +525,7 @@ bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
|
||||
/* The AudioStreamSetProperty is not only asynchronous,
|
||||
* it is also not Atomic, in its behaviour. */
|
||||
struct timespec timeout = mp_rel_time_to_timespec(0.5);
|
||||
struct timespec timeout = mp_rel_time_to_timespec(2.0);
|
||||
AudioStreamBasicDescription actual_format = {0};
|
||||
while (1) {
|
||||
err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &actual_format);
|
||||
|
||||
+1
-5
@@ -162,10 +162,6 @@ static int init(struct ao *ao)
|
||||
mp_chmap_sel_add_waveext(&sel);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
mp_chmap_sel_add_alsa_def(&sel);
|
||||
break;
|
||||
|
||||
default:
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
}
|
||||
@@ -236,7 +232,7 @@ const struct ao_driver audio_out_jack = {
|
||||
OPT_FLAG("autostart", autostart, 0),
|
||||
OPT_FLAG("connect", connect, 0),
|
||||
OPT_CHOICE("std-channel-layout", stdlayout, 0,
|
||||
({"waveext", 0}, {"alsa", 1}, {"any", 2})),
|
||||
({"waveext", 0}, {"any", 1})),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -164,6 +164,9 @@ static int init(struct ao *ao)
|
||||
|
||||
ao->untimed = true;
|
||||
|
||||
if (ao->channels.num > AV_NUM_DATA_POINTERS)
|
||||
goto fail;
|
||||
|
||||
pthread_mutex_unlock(&ao->encode_lavc_ctx->lock);
|
||||
return 0;
|
||||
|
||||
|
||||
+66
-29
@@ -44,8 +44,7 @@
|
||||
|
||||
#define MAX_CHANS MP_NUM_CHANNELS
|
||||
#define NUM_BUF 128
|
||||
#define CHUNK_SIZE 512
|
||||
#define CHUNK_SAMPLES (CHUNK_SIZE / 2)
|
||||
#define CHUNK_SAMPLES 256
|
||||
static ALuint buffers[MAX_CHANS][NUM_BUF];
|
||||
static ALuint sources[MAX_CHANS];
|
||||
|
||||
@@ -56,6 +55,8 @@ static struct ao *ao_data;
|
||||
|
||||
struct priv {
|
||||
char *cfg_device;
|
||||
ALenum al_format;
|
||||
int chunk_size;
|
||||
};
|
||||
|
||||
static void reset(struct ao *ao);
|
||||
@@ -117,22 +118,51 @@ struct speaker {
|
||||
};
|
||||
|
||||
static const struct speaker speaker_pos[] = {
|
||||
{MP_SPEAKER_ID_FL, {-1, 0, 0.5}},
|
||||
{MP_SPEAKER_ID_FR, { 1, 0, 0.5}},
|
||||
{MP_SPEAKER_ID_FC, { 0, 0, 1}},
|
||||
{MP_SPEAKER_ID_LFE, { 0, 0, 0.1}},
|
||||
{MP_SPEAKER_ID_BL, {-1, 0, -1}},
|
||||
{MP_SPEAKER_ID_BR, { 1, 0, -1}},
|
||||
{MP_SPEAKER_ID_BC, { 0, 0, -1}},
|
||||
{MP_SPEAKER_ID_SL, {-1, 0, 0}},
|
||||
{MP_SPEAKER_ID_SR, { 1, 0, 0}},
|
||||
{MP_SPEAKER_ID_FL, {-0.500, 0, -0.866}}, // -30 deg
|
||||
{MP_SPEAKER_ID_FR, { 0.500, 0, -0.866}}, // 30 deg
|
||||
{MP_SPEAKER_ID_FC, { 0, 0, -1}}, // 0 deg
|
||||
{MP_SPEAKER_ID_LFE, { 0, -1, 0}}, // below
|
||||
{MP_SPEAKER_ID_BL, {-0.609, 0, 0.793}}, // -142.5 deg
|
||||
{MP_SPEAKER_ID_BR, { 0.609, 0, 0.793}}, // 142.5 deg
|
||||
{MP_SPEAKER_ID_BC, { 0, 0, 1}}, // 180 deg
|
||||
{MP_SPEAKER_ID_SL, {-0.985, 0, 0.174}}, // -100 deg
|
||||
{MP_SPEAKER_ID_SR, { 0.985, 0, 0.174}}, // 100 deg
|
||||
{-1},
|
||||
};
|
||||
|
||||
static ALenum get_al_format(int format)
|
||||
{
|
||||
switch (format) {
|
||||
case AF_FORMAT_U8P: return AL_FORMAT_MONO8;
|
||||
case AF_FORMAT_S16P: return AL_FORMAT_MONO16;
|
||||
case AF_FORMAT_FLOATP:
|
||||
if (alIsExtensionPresent((ALchar*)"AL_EXT_float32") == AL_TRUE)
|
||||
return AL_FORMAT_MONO_FLOAT32;
|
||||
break;
|
||||
case AF_FORMAT_DOUBLEP:
|
||||
if (alIsExtensionPresent((ALchar*)"AL_EXT_double") == AL_TRUE)
|
||||
return AL_FORMAT_MONO_DOUBLE_EXT;
|
||||
break;
|
||||
}
|
||||
return AL_FALSE;
|
||||
}
|
||||
|
||||
// close audio device
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
ALCcontext *ctx = alcGetCurrentContext();
|
||||
ALCdevice *dev = alcGetContextsDevice(ctx);
|
||||
reset(ao);
|
||||
alcMakeContextCurrent(NULL);
|
||||
alcDestroyContext(ctx);
|
||||
alcCloseDevice(dev);
|
||||
ao_data = NULL;
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
float position[3] = {0, 0, 0};
|
||||
float direction[6] = {0, 0, 1, 0, -1, 0};
|
||||
float direction[6] = {0, 0, -1, 0, 1, 0};
|
||||
ALCdevice *dev = NULL;
|
||||
ALCcontext *ctx = NULL;
|
||||
ALCint freq = 0;
|
||||
@@ -184,25 +214,31 @@ static int init(struct ao *ao)
|
||||
alcGetIntegerv(dev, ALC_FREQUENCY, 1, &freq);
|
||||
if (alcGetError(dev) == ALC_NO_ERROR && freq)
|
||||
ao->samplerate = freq;
|
||||
ao->format = AF_FORMAT_S16P;
|
||||
|
||||
p->al_format = AL_FALSE;
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, try_formats);
|
||||
for (int n = 0; try_formats[n]; n++) {
|
||||
p->al_format = get_al_format(try_formats[n]);
|
||||
if (p->al_format != AL_FALSE) {
|
||||
ao->format = try_formats[n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (p->al_format == AL_FALSE) {
|
||||
MP_FATAL(ao, "Can't find appropriate sample format.\n");
|
||||
uninit(ao);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
p->chunk_size = CHUNK_SAMPLES * af_fmt_to_bytes(ao->format);
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
return -1;
|
||||
}
|
||||
|
||||
// close audio device
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
ALCcontext *ctx = alcGetCurrentContext();
|
||||
ALCdevice *dev = alcGetContextsDevice(ctx);
|
||||
reset(ao);
|
||||
alcMakeContextCurrent(NULL);
|
||||
alcDestroyContext(ctx);
|
||||
alcCloseDevice(dev);
|
||||
ao_data = NULL;
|
||||
}
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
{
|
||||
ALint state;
|
||||
@@ -274,14 +310,15 @@ static int get_space(struct ao *ao)
|
||||
*/
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
ALint state;
|
||||
int num = samples / CHUNK_SAMPLES;
|
||||
for (int i = 0; i < num; i++) {
|
||||
for (int ch = 0; ch < ao->channels.num; ch++) {
|
||||
int16_t *d = data[ch];
|
||||
d += i * CHUNK_SAMPLES;
|
||||
alBufferData(buffers[ch][cur_buf[ch]], AL_FORMAT_MONO16, d,
|
||||
CHUNK_SIZE, ao->samplerate);
|
||||
char *d = data[ch];
|
||||
d += i * p->chunk_size;
|
||||
alBufferData(buffers[ch][cur_buf[ch]], p->al_format, d,
|
||||
p->chunk_size, ao->samplerate);
|
||||
alSourceQueueBuffers(sources[ch], 1, &buffers[ch][cur_buf[ch]]);
|
||||
cur_buf[ch] = (cur_buf[ch] + 1) % NUM_BUF;
|
||||
}
|
||||
|
||||
@@ -237,8 +237,6 @@ static void uninit(struct ao *ao)
|
||||
MP_ERR(ao, "Audio loop thread refuses to abort\n");
|
||||
return;
|
||||
}
|
||||
if (state->VistaBlob.hAvrt)
|
||||
FreeLibrary(state->VistaBlob.hAvrt);
|
||||
|
||||
SAFE_RELEASE(state->hInitDone, CloseHandle(state->hInitDone));
|
||||
SAFE_RELEASE(state->hWake, CloseHandle(state->hWake));
|
||||
@@ -255,8 +253,6 @@ static int init(struct ao *ao)
|
||||
|
||||
struct wasapi_state *state = ao->priv;
|
||||
state->log = ao->log;
|
||||
if(!wasapi_fill_VistaBlob(state))
|
||||
MP_WARN(ao, "Error loading thread priority functions\n");
|
||||
|
||||
state->hInitDone = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
state->hWake = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
@@ -341,7 +337,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
return state->share_mode == AUDCLNT_SHAREMODE_SHARED ?
|
||||
CONTROL_TRUE : CONTROL_FALSE;
|
||||
case AOCONTROL_UPDATE_STREAM_TITLE: {
|
||||
MP_VERBOSE(state, "Updating stream title to \"%s\"\n", (char*)arg);
|
||||
wchar_t *title = mp_from_utf8(NULL, (char*)arg);
|
||||
|
||||
@@ -79,7 +79,6 @@ typedef struct wasapi_state {
|
||||
|
||||
/* for setting the audio thread priority */
|
||||
HANDLE hTask; /* AV thread */
|
||||
DWORD taskIndex; /* AV task ID */
|
||||
|
||||
/* WASAPI proxy handles, for Single-Threaded Apartment communication.
|
||||
One is needed for each audio thread object that's accessed from the main thread. */
|
||||
@@ -110,16 +109,6 @@ typedef struct wasapi_state {
|
||||
size_t buffer_block_size; /* Size of each block in bytes */
|
||||
UINT32 bufferFrameCount; /* wasapi buffer block size, number of frames, frame size at format.nBlockAlign */
|
||||
|
||||
/* Don't use these functions directly in case
|
||||
they are unimplemented for some reason.
|
||||
(XP shouldn't be an issue since it doesn't support wasapi, maybe wine?)
|
||||
Blob is owned by the main thread */
|
||||
struct {
|
||||
HMODULE hAvrt;
|
||||
HANDLE (WINAPI *pAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
|
||||
WINBOOL (WINAPI *pAvRevertMmThreadCharacteristics)(HANDLE);
|
||||
} VistaBlob;
|
||||
|
||||
change_notify change;
|
||||
} wasapi_state;
|
||||
|
||||
|
||||
+24
-40
@@ -185,36 +185,6 @@ char *mp_HRESULT_to_str_buf(char *buf, size_t buf_size, HRESULT hr)
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool wasapi_fill_VistaBlob(wasapi_state *state)
|
||||
{
|
||||
if (!state)
|
||||
goto exit_label;
|
||||
state->VistaBlob.hAvrt = LoadLibraryW(L"avrt.dll");
|
||||
if (!state->VistaBlob.hAvrt)
|
||||
goto exit_label;
|
||||
|
||||
state->VistaBlob.pAvSetMmThreadCharacteristicsW =
|
||||
(HANDLE (WINAPI *)(LPCWSTR, LPDWORD))
|
||||
GetProcAddress(state->VistaBlob.hAvrt, "AvSetMmThreadCharacteristicsW");
|
||||
if (!state->VistaBlob.pAvSetMmThreadCharacteristicsW)
|
||||
goto exit_label;
|
||||
|
||||
state->VistaBlob.pAvRevertMmThreadCharacteristics =
|
||||
(WINBOOL (WINAPI *)(HANDLE))
|
||||
GetProcAddress(state->VistaBlob.hAvrt, "AvRevertMmThreadCharacteristics");
|
||||
if (!state->VistaBlob.pAvRevertMmThreadCharacteristics)
|
||||
goto exit_label;
|
||||
|
||||
return true;
|
||||
exit_label:
|
||||
if (state->VistaBlob.hAvrt) {
|
||||
FreeLibrary(state->VistaBlob.hAvrt);
|
||||
state->VistaBlob.hAvrt = NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void update_waveformat_datarate(WAVEFORMATEXTENSIBLE *wformat)
|
||||
{
|
||||
WAVEFORMATEX *wf = &wformat->Format;
|
||||
@@ -399,6 +369,23 @@ exit_label:
|
||||
return false;
|
||||
}
|
||||
|
||||
// This works like try_format_exclusive(), but will try to fallback to the AC3
|
||||
// format if the format is a non-AC3 passthrough format. *wformat will be
|
||||
// adjusted accordingly.
|
||||
static bool try_format_exclusive_with_spdif_fallback(struct ao *ao,
|
||||
WAVEFORMATEXTENSIBLE *wformat)
|
||||
{
|
||||
if (try_format_exclusive(ao, wformat))
|
||||
return true;
|
||||
int special_format = special_subtype_to_format(&wformat->SubFormat);
|
||||
if (special_format && special_format != AF_FORMAT_S_AC3) {
|
||||
MP_VERBOSE(ao, "Retrying as AC3.\n");
|
||||
wformat->SubFormat = *format_to_subtype(AF_FORMAT_S_AC3);
|
||||
return try_format_exclusive(ao, wformat);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool search_sample_formats(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat,
|
||||
int samplerate, struct mp_chmap *channels)
|
||||
{
|
||||
@@ -486,10 +473,8 @@ static bool search_channels(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
}
|
||||
} else {
|
||||
change_waveformat_channels(wformat, &entry);
|
||||
if (try_format_exclusive(ao, wformat)) {
|
||||
if (try_format_exclusive(ao, wformat))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &entry);
|
||||
MP_VERBOSE(ao, "%s is supported\n", mp_chmap_to_str(&entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,7 +495,7 @@ static bool find_formats_exclusive(struct ao *ao, bool do_search)
|
||||
|
||||
// Try the requested format as is. If that doesn't work, and the
|
||||
// do_search argument is set, do the pcm format search.
|
||||
if (!try_format_exclusive(ao, &wformat) &&
|
||||
if (!try_format_exclusive_with_spdif_fallback(ao, &wformat) &&
|
||||
(!do_search || !search_channels(ao, &wformat)))
|
||||
return false;
|
||||
|
||||
@@ -723,9 +708,10 @@ reinit:
|
||||
hr = init_session_display(state);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
if (state->VistaBlob.hAvrt) {
|
||||
state->hTask =
|
||||
state->VistaBlob.pAvSetMmThreadCharacteristicsW(L"Pro Audio", &state->taskIndex);
|
||||
state->hTask = AvSetMmThreadCharacteristics(L"Pro Audio", &(DWORD){0});
|
||||
if (!state->hTask) {
|
||||
MP_WARN(state, "Failed to set AV thread to Pro Audio: %s\n",
|
||||
mp_LastError_to_str());
|
||||
}
|
||||
|
||||
MP_VERBOSE(state, "Format fixed. Using %lld byte buffer block size\n",
|
||||
@@ -1179,8 +1165,6 @@ void wasapi_thread_uninit(struct ao *ao)
|
||||
SAFE_RELEASE(state->pAudioClient, IAudioClient_Release(state->pAudioClient));
|
||||
SAFE_RELEASE(state->pDevice, IMMDevice_Release(state->pDevice));
|
||||
SAFE_RELEASE(state->pEnumerator, IMMDeviceEnumerator_Release(state->pEnumerator));
|
||||
|
||||
if (state->hTask)
|
||||
state->VistaBlob.pAvRevertMmThreadCharacteristics(state->hTask);
|
||||
SAFE_RELEASE(state->hTask, AvRevertMmThreadCharacteristics(state->hTask));
|
||||
MP_DBG(ao, "Thread uninit done\n");
|
||||
}
|
||||
@@ -33,6 +33,7 @@ char *mp_HRESULT_to_str_buf(char *buf, size_t buf_size, HRESULT hr);
|
||||
#define mp_GUID_to_str(guid) mp_GUID_to_str_buf((char[40]){0}, 40, (guid))
|
||||
#define mp_PKEY_to_str(pkey) mp_PKEY_to_str_buf((char[42]){0}, 42, (pkey))
|
||||
#define mp_HRESULT_to_str(hr) mp_HRESULT_to_str_buf((char[60]){0}, 60, (hr))
|
||||
#define mp_LastError_to_str() mp_HRESULT_to_str(HRESULT_FROM_WIN32(GetLastError()))
|
||||
|
||||
bool wasapi_fill_VistaBlob(wasapi_state *state);
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st)
|
||||
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;
|
||||
}
|
||||
|
||||
// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it.
|
||||
|
||||
+3
-5
@@ -326,10 +326,8 @@ static void write_msg_to_buffers(struct mp_log *log, int lev, char *text)
|
||||
static void dump_stats(struct mp_log *log, int lev, char *text)
|
||||
{
|
||||
struct mp_log_root *root = log->root;
|
||||
if (lev == MSGL_STATS && root->stats_file) {
|
||||
fprintf(root->stats_file, "%"PRId64" %s #%s\n", mp_time_us(), text,
|
||||
log->verbose_prefix);
|
||||
}
|
||||
if (lev == MSGL_STATS && root->stats_file)
|
||||
fprintf(root->stats_file, "%"PRId64" %s\n", mp_time_us(), text);
|
||||
}
|
||||
|
||||
void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va)
|
||||
@@ -634,7 +632,7 @@ const int mp_mpv_log_levels[MSGL_MAX + 1] = {
|
||||
int mp_msg_find_level(const char *s)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(mp_log_levels); n++) {
|
||||
if (mp_log_levels[n] && mp_log_levels[n] && !strcmp(s, mp_log_levels[n]))
|
||||
if (mp_log_levels[n] && !strcmp(s, mp_log_levels[n]))
|
||||
return n;
|
||||
}
|
||||
return -1;
|
||||
|
||||
+23
-9
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "misc/bstr.h"
|
||||
#include "common/common.h"
|
||||
#include "common/tags.h"
|
||||
|
||||
#include "cue.h"
|
||||
|
||||
@@ -37,6 +38,7 @@ enum cue_command {
|
||||
CUE_TRACK,
|
||||
CUE_INDEX,
|
||||
CUE_TITLE,
|
||||
CUE_PERFORMER,
|
||||
};
|
||||
|
||||
static const struct {
|
||||
@@ -51,7 +53,7 @@ static const struct {
|
||||
{ CUE_UNUSED, "CDTEXTFILE" },
|
||||
{ CUE_UNUSED, "FLAGS" },
|
||||
{ CUE_UNUSED, "ISRC" },
|
||||
{ CUE_UNUSED, "PERFORMER" },
|
||||
{ CUE_PERFORMER, "PERFORMER" },
|
||||
{ CUE_UNUSED, "POSTGAP" },
|
||||
{ CUE_UNUSED, "PREGAP" },
|
||||
{ CUE_UNUSED, "REM" },
|
||||
@@ -160,17 +162,19 @@ bool mp_probe_cue(struct bstr data)
|
||||
struct cue_file *mp_parse_cue(struct bstr data)
|
||||
{
|
||||
struct cue_file *f = talloc_zero(NULL, struct cue_file);
|
||||
f->tags = talloc_zero(f, struct mp_tags);
|
||||
|
||||
data = skip_utf8_bom(data);
|
||||
|
||||
char *filename = NULL;
|
||||
// Global metadata, and copied into new tracks.
|
||||
struct cue_track proto_track = {0};
|
||||
struct cue_track *cur_track = &proto_track;
|
||||
struct cue_track *cur_track = NULL;
|
||||
|
||||
while (data.len) {
|
||||
struct bstr param;
|
||||
switch (read_cmd(&data, ¶m)) {
|
||||
int cmd = read_cmd(&data, ¶m);
|
||||
switch (cmd) {
|
||||
case CUE_ERROR:
|
||||
talloc_free(f);
|
||||
return NULL;
|
||||
@@ -179,19 +183,29 @@ struct cue_file *mp_parse_cue(struct bstr data)
|
||||
f->num_tracks += 1;
|
||||
cur_track = &f->tracks[f->num_tracks - 1];
|
||||
*cur_track = proto_track;
|
||||
cur_track->tags = talloc_zero(f, struct mp_tags);
|
||||
break;
|
||||
}
|
||||
case CUE_TITLE:
|
||||
cur_track->title = read_quoted(f, ¶m);
|
||||
case CUE_PERFORMER: {
|
||||
static const char *metanames[] = {
|
||||
[CUE_TITLE] = "title",
|
||||
[CUE_PERFORMER] = "performer",
|
||||
};
|
||||
struct mp_tags *tags = cur_track ? cur_track->tags : f->tags;
|
||||
mp_tags_set_bstr(tags, bstr0(metanames[cmd]), param);
|
||||
break;
|
||||
}
|
||||
case CUE_INDEX: {
|
||||
int type = read_int_2(¶m);
|
||||
double time = read_time(¶m);
|
||||
if (type == 1) {
|
||||
cur_track->start = time;
|
||||
cur_track->filename = filename;
|
||||
} else if (type == 0) {
|
||||
cur_track->pregap_start = time;
|
||||
if (cur_track) {
|
||||
if (type == 1) {
|
||||
cur_track->start = time;
|
||||
cur_track->filename = filename;
|
||||
} else if (type == 0) {
|
||||
cur_track->pregap_start = time;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+2
-1
@@ -25,6 +25,7 @@
|
||||
struct cue_file {
|
||||
struct cue_track *tracks;
|
||||
int num_tracks;
|
||||
struct mp_tags *tags;
|
||||
};
|
||||
|
||||
struct cue_track {
|
||||
@@ -32,7 +33,7 @@ struct cue_track {
|
||||
double start; // corresponds to INDEX 01
|
||||
char *filename;
|
||||
int source;
|
||||
char *title;
|
||||
struct mp_tags *tags;
|
||||
};
|
||||
|
||||
bool mp_probe_cue(struct bstr data);
|
||||
|
||||
+33
-20
@@ -48,8 +48,6 @@ extern const demuxer_desc_t demuxer_desc_tv;
|
||||
extern const demuxer_desc_t demuxer_desc_mf;
|
||||
extern const demuxer_desc_t demuxer_desc_matroska;
|
||||
extern const demuxer_desc_t demuxer_desc_lavf;
|
||||
extern const demuxer_desc_t demuxer_desc_libass;
|
||||
extern const demuxer_desc_t demuxer_desc_subreader;
|
||||
extern const demuxer_desc_t demuxer_desc_playlist;
|
||||
extern const demuxer_desc_t demuxer_desc_disc;
|
||||
extern const demuxer_desc_t demuxer_desc_rar;
|
||||
@@ -67,9 +65,6 @@ const demuxer_desc_t *const demuxer_list[] = {
|
||||
&demuxer_desc_rawvideo,
|
||||
#if HAVE_TV
|
||||
&demuxer_desc_tv,
|
||||
#endif
|
||||
#if HAVE_LIBASS
|
||||
&demuxer_desc_libass,
|
||||
#endif
|
||||
&demuxer_desc_matroska,
|
||||
#if HAVE_LIBARCHIVE
|
||||
@@ -79,8 +74,6 @@ const demuxer_desc_t *const demuxer_list[] = {
|
||||
&demuxer_desc_lavf,
|
||||
&demuxer_desc_mf,
|
||||
&demuxer_desc_playlist,
|
||||
// Pretty aggressive, so should be last.
|
||||
&demuxer_desc_subreader,
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -128,6 +121,8 @@ struct demux_internal {
|
||||
bool refresh_seeks_enabled;
|
||||
bool start_refresh_seek;
|
||||
|
||||
double ts_offset; // timestamp offset to apply everything
|
||||
|
||||
// Cached state.
|
||||
bool force_cache_update;
|
||||
double time_length;
|
||||
@@ -168,6 +163,8 @@ struct demux_stream {
|
||||
#define MP_PTS_MIN(a, b) MPMIN(PTS_OR_DEF(a, b), PTS_OR_DEF(b, a))
|
||||
#define MP_PTS_MAX(a, b) MPMAX(PTS_OR_DEF(a, b), PTS_OR_DEF(b, a))
|
||||
|
||||
#define MP_ADD_PTS(a, b) ((a) == MP_NOPTS_VALUE ? (a) : ((a) + (b)))
|
||||
|
||||
static void demuxer_sort_chapters(demuxer_t *demuxer);
|
||||
static void *demux_thread(void *pctx);
|
||||
static void update_cache(struct demux_internal *in);
|
||||
@@ -193,6 +190,14 @@ static void ds_flush(struct demux_stream *ds)
|
||||
ds->last_pos = -1;
|
||||
}
|
||||
|
||||
void demux_set_ts_offset(struct demuxer *demuxer, double offset)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
in->ts_offset = offset;
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
struct sh_stream *new_sh_stream(demuxer_t *demuxer, enum stream_type type)
|
||||
{
|
||||
assert(demuxer == demuxer->in->d_thread);
|
||||
@@ -307,13 +312,12 @@ const char *stream_type_name(enum stream_type type)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the same value as demuxer->fill_buffer: 1 ok, 0 EOF/not selected.
|
||||
int demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
||||
void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
||||
{
|
||||
struct demux_stream *ds = stream ? stream->ds : NULL;
|
||||
if (!dp || !ds) {
|
||||
talloc_free(dp);
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
struct demux_internal *in = ds->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
@@ -330,7 +334,7 @@ int demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
||||
if (!ds->selected || in->seeking || drop) {
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
talloc_free(dp);
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
dp->stream = stream->index;
|
||||
@@ -371,7 +375,6 @@ int demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
||||
ds->in->wakeup_cb(ds->in->wakeup_cb_ctx);
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Returns true if there was "progress" (lock was released temporarily).
|
||||
@@ -430,10 +433,8 @@ static bool read_packet(struct demux_internal *in)
|
||||
pthread_mutex_lock(&in->lock);
|
||||
|
||||
if (eof) {
|
||||
for (int n = 0; n < in->d_buffer->num_streams; n++) {
|
||||
struct demux_stream *ds = in->d_buffer->streams[n]->ds;
|
||||
ds->eof = true;
|
||||
}
|
||||
for (int n = 0; n < in->d_buffer->num_streams; n++)
|
||||
in->d_buffer->streams[n]->ds->eof = true;
|
||||
// If we had EOF previously, then don't wakeup (avoids wakeup loop)
|
||||
if (!in->last_eof) {
|
||||
if (in->wakeup_cb)
|
||||
@@ -611,6 +612,9 @@ static struct demux_packet *dequeue_packet(struct demux_stream *ds)
|
||||
if (pkt->pos >= ds->in->d_user->filepos)
|
||||
ds->in->d_user->filepos = pkt->pos;
|
||||
|
||||
pkt->pts = MP_ADD_PTS(pkt->pts, ds->in->ts_offset);
|
||||
pkt->dts = MP_ADD_PTS(pkt->dts, ds->in->ts_offset);
|
||||
|
||||
return pkt;
|
||||
}
|
||||
|
||||
@@ -672,7 +676,7 @@ double demux_get_next_pts(struct sh_stream *sh)
|
||||
pthread_mutex_lock(&sh->ds->in->lock);
|
||||
ds_get_packets(sh->ds);
|
||||
if (sh->ds->head)
|
||||
res = sh->ds->head->pts;
|
||||
res = MP_ADD_PTS(sh->ds->head->pts, sh->ds->in->ts_offset);
|
||||
pthread_mutex_unlock(&sh->ds->in->lock);
|
||||
}
|
||||
return res;
|
||||
@@ -924,7 +928,8 @@ static void demux_init_cuesheet(struct demuxer *demuxer)
|
||||
if (f) {
|
||||
for (int n = 0; n < f->num_tracks; n++) {
|
||||
struct cue_track *t = &f->tracks[n];
|
||||
demuxer_add_chapter(demuxer, t->title, t->start, -1);
|
||||
int idx = demuxer_add_chapter(demuxer, "", t->start, -1);
|
||||
mp_tags_merge(demuxer->chapters[idx].metadata, t->tags);
|
||||
}
|
||||
}
|
||||
talloc_free(f);
|
||||
@@ -938,6 +943,9 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
struct demuxer_params *params,
|
||||
enum demux_check check)
|
||||
{
|
||||
if (mp_cancel_test(stream->cancel))
|
||||
return NULL;
|
||||
|
||||
struct demuxer *demuxer = talloc_ptrtype(NULL, demuxer);
|
||||
*demuxer = (struct demuxer) {
|
||||
.desc = desc,
|
||||
@@ -1152,6 +1160,8 @@ int demux_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
in->seeking = true;
|
||||
in->seek_flags = flags;
|
||||
in->seek_pts = rel_seek_secs;
|
||||
if ((flags & SEEK_ABSOLUTE) && !(flags & SEEK_FACTOR))
|
||||
in->seek_pts = MP_ADD_PTS(in->seek_pts, -in->ts_offset);
|
||||
|
||||
if (!in->threading)
|
||||
execute_seek(in);
|
||||
@@ -1392,10 +1402,11 @@ static int cached_demux_control(struct demux_internal *in, int cmd, void *arg)
|
||||
case DEMUXER_CTRL_GET_BITRATE_STATS: {
|
||||
double *rates = arg;
|
||||
for (int n = 0; n < STREAM_TYPE_COUNT; n++)
|
||||
rates[n] = 0;
|
||||
rates[n] = -1;
|
||||
for (int n = 0; n < in->d_user->num_streams; n++) {
|
||||
struct demux_stream *ds = in->d_user->streams[n]->ds;
|
||||
rates[ds->type] += MPMAX(0, ds->bitrate);
|
||||
if (ds->selected && ds->bitrate >= 0)
|
||||
rates[ds->type] = MPMAX(0, rates[ds->type]) + ds->bitrate;
|
||||
}
|
||||
return DEMUXER_CTRL_OK;
|
||||
}
|
||||
@@ -1422,6 +1433,8 @@ static int cached_demux_control(struct demux_internal *in, int cmd, void *arg)
|
||||
r->ts_duration = MPMAX(0, r->ts_range[1] - r->ts_range[0]);
|
||||
if (!num_packets || in->seeking)
|
||||
r->ts_duration = 0;
|
||||
r->ts_range[0] = MP_ADD_PTS(r->ts_range[0], in->ts_offset);
|
||||
r->ts_range[1] = MP_ADD_PTS(r->ts_range[1], in->ts_offset);
|
||||
return DEMUXER_CTRL_OK;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -242,7 +242,7 @@ typedef struct {
|
||||
void free_demuxer(struct demuxer *demuxer);
|
||||
void free_demuxer_and_stream(struct demuxer *demuxer);
|
||||
|
||||
int demux_add_packet(struct sh_stream *stream, demux_packet_t *dp);
|
||||
void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp);
|
||||
|
||||
struct demux_packet *demux_read_packet(struct sh_stream *sh);
|
||||
int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt);
|
||||
@@ -271,6 +271,7 @@ bool demux_cancel_test(struct demuxer *demuxer);
|
||||
void demux_flush(struct demuxer *demuxer);
|
||||
int demux_seek(struct demuxer *demuxer, double rel_seek_secs, int flags);
|
||||
void demux_set_enable_refresh_seeks(struct demuxer *demuxer, bool enabled);
|
||||
void demux_set_ts_offset(struct demuxer *demuxer, double offset);
|
||||
|
||||
int demux_control(struct demuxer *demuxer, int cmd, void *arg);
|
||||
|
||||
|
||||
+15
-15
@@ -38,7 +38,7 @@
|
||||
#define PROBE_SIZE 512
|
||||
|
||||
struct priv {
|
||||
bstr data;
|
||||
struct cue_file *f;
|
||||
};
|
||||
|
||||
static void add_source(struct timeline *tl, struct demuxer *d)
|
||||
@@ -150,15 +150,8 @@ static void build_timeline(struct timeline *tl)
|
||||
|
||||
add_source(tl, tl->demuxer);
|
||||
|
||||
struct cue_file *f = mp_parse_cue(p->data);
|
||||
if (!f) {
|
||||
MP_ERR(tl, "CUE: error parsing input file!\n");
|
||||
goto out;
|
||||
}
|
||||
talloc_steal(ctx, f);
|
||||
|
||||
struct cue_track *tracks = f->tracks;
|
||||
size_t track_count = f->num_tracks;
|
||||
struct cue_track *tracks = p->f->tracks;
|
||||
size_t track_count = p->f->num_tracks;
|
||||
|
||||
if (track_count == 0) {
|
||||
MP_ERR(tl, "CUE: no tracks found!\n");
|
||||
@@ -224,10 +217,8 @@ static void build_timeline(struct timeline *tl)
|
||||
};
|
||||
chapters[i] = (struct demux_chapter) {
|
||||
.pts = timeline[i].start,
|
||||
.metadata = talloc_zero(tl, struct mp_tags),
|
||||
.metadata = mp_tags_dup(tl, tracks[i].tags),
|
||||
};
|
||||
// might want to include other metadata here
|
||||
mp_tags_set_str(chapters[i].metadata, "title", tracks[i].title);
|
||||
starttime += duration;
|
||||
}
|
||||
|
||||
@@ -261,9 +252,18 @@ static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
struct priv *p = talloc_zero(demuxer, struct priv);
|
||||
demuxer->priv = p;
|
||||
demuxer->fully_read = true;
|
||||
p->data = stream_read_complete(s, demuxer, 1000000);
|
||||
if (p->data.start == NULL)
|
||||
|
||||
bstr data = stream_read_complete(s, p, 1000000);
|
||||
if (data.start == NULL)
|
||||
return -1;
|
||||
p->f = mp_parse_cue(data);
|
||||
talloc_steal(p, p->f);
|
||||
if (!p->f) {
|
||||
MP_ERR(demuxer, "error parsing input file!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
mp_tags_merge(demuxer->metadata, p->f->tags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+4
-5
@@ -93,17 +93,16 @@ static void add_dvd_streams(demuxer_t *demuxer)
|
||||
|
||||
// emulate the extradata
|
||||
struct mp_csp_params csp = MP_CSP_PARAMS_DEFAULTS;
|
||||
csp.int_bits_in = 8;
|
||||
csp.int_bits_out = 8;
|
||||
struct mp_cmat cmatrix;
|
||||
mp_get_yuv2rgb_coeffs(&csp, &cmatrix);
|
||||
mp_get_csp_matrix(&csp, &cmatrix);
|
||||
|
||||
char *s = talloc_strdup(sh, "");
|
||||
s = talloc_asprintf_append(s, "palette: ");
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int color = info.palette[i];
|
||||
int c[3] = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff};
|
||||
mp_map_int_color(&cmatrix, 8, c);
|
||||
int y[3] = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff};
|
||||
int c[3];
|
||||
mp_map_fixp_color(&cmatrix, 8, y, 8, c);
|
||||
color = (c[2] << 16) | (c[1] << 8) | c[0];
|
||||
|
||||
if (i != 0)
|
||||
|
||||
+18
-5
@@ -612,6 +612,10 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
sh_sub->frame_based = 23.976;
|
||||
}
|
||||
}
|
||||
|
||||
if (matches_avinputformat_name(priv, "ass"))
|
||||
sh_sub->is_utf8 = true;
|
||||
|
||||
break;
|
||||
}
|
||||
case AVMEDIA_TYPE_ATTACHMENT: {
|
||||
@@ -635,7 +639,10 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
sh->ff_index = st->index;
|
||||
sh->codec = mp_codec_from_av_codec_id(codec->codec_id);
|
||||
sh->codec_tag = codec->codec_tag;
|
||||
sh->lav_headers = codec;
|
||||
sh->lav_headers = avcodec_alloc_context3(NULL);
|
||||
if (!sh->lav_headers)
|
||||
return;
|
||||
mp_copy_lav_codec_headers(sh->lav_headers, codec);
|
||||
|
||||
if (st->disposition & AV_DISPOSITION_DEFAULT)
|
||||
sh->default_track = true;
|
||||
@@ -850,7 +857,7 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
AVPacket *pkt = &(AVPacket){0};
|
||||
int r = av_read_frame(priv->avfc, pkt);
|
||||
if (r < 0) {
|
||||
av_free_packet(pkt);
|
||||
av_packet_unref(pkt);
|
||||
if (r == AVERROR(EAGAIN))
|
||||
return 1;
|
||||
if (r == AVERROR_EOF)
|
||||
@@ -867,13 +874,13 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
AVStream *st = priv->avfc->streams[pkt->stream_index];
|
||||
|
||||
if (!demux_stream_is_selected(stream)) {
|
||||
av_free_packet(pkt);
|
||||
av_packet_unref(pkt);
|
||||
return 1; // don't signal EOF if skipping a packet
|
||||
}
|
||||
|
||||
struct demux_packet *dp = new_demux_packet_from_avpacket(pkt);
|
||||
if (!dp) {
|
||||
av_free_packet(pkt);
|
||||
av_packet_unref(pkt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -882,8 +889,10 @@ 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 (dp->pts != MP_NOPTS_VALUE) {
|
||||
@@ -891,7 +900,7 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
} else if (dp->dts != MP_NOPTS_VALUE) {
|
||||
priv->last_pts = dp->dts * AV_TIME_BASE;
|
||||
}
|
||||
av_free_packet(pkt);
|
||||
av_packet_unref(pkt);
|
||||
|
||||
if (priv->format_hack.clear_filepos)
|
||||
dp->pos = -1;
|
||||
@@ -1074,6 +1083,10 @@ static void demux_close_lavf(demuxer_t *demuxer)
|
||||
if (priv->pb)
|
||||
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]->lav_headers);
|
||||
}
|
||||
talloc_free(priv);
|
||||
demuxer->priv = NULL;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
if (r == ARCHIVE_EOF)
|
||||
break;
|
||||
if (r < ARCHIVE_OK)
|
||||
MP_ERR(demuxer, "libarchive: %s\n", archive_error_string(mpa->arch));
|
||||
MP_ERR(demuxer, "%s\n", archive_error_string(mpa->arch));
|
||||
if (r < ARCHIVE_WARN)
|
||||
break;
|
||||
if (archive_entry_filetype(entry) != AE_IFREG)
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
// Note: just wraps libass, and makes the subtitle track available though
|
||||
// sh_sub->track. It doesn't produce packets and doesn't support seeking.
|
||||
|
||||
#include <ass/ass.h>
|
||||
#include <ass/ass_types.h>
|
||||
|
||||
#include "options/options.h"
|
||||
#include "common/msg.h"
|
||||
#include "misc/charset_conv.h"
|
||||
#include "stream/stream.h"
|
||||
#include "demux.h"
|
||||
|
||||
#define PROBE_SIZE (8 * 1024)
|
||||
|
||||
static void message_callback(int level, const char *format, va_list va, void *ctx)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
static int d_check_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
const char *user_cp = demuxer->opts->sub_cp;
|
||||
struct stream *s = demuxer->stream;
|
||||
struct mp_log *log = demuxer->log;
|
||||
|
||||
if (!demuxer->params || !demuxer->params->expect_subtitle)
|
||||
return -1;
|
||||
|
||||
if (check >= DEMUX_CHECK_UNSAFE) {
|
||||
ASS_Library *lib = ass_library_init();
|
||||
if (!lib)
|
||||
return -1;
|
||||
ass_set_message_cb(lib, message_callback, NULL);
|
||||
|
||||
// Probe by loading a part of the beginning of the file with libass.
|
||||
// Incomplete scripts are usually ok, and we hope libass is not verbose
|
||||
// when dealing with (from its perspective) completely broken binary
|
||||
// garbage.
|
||||
|
||||
bstr buf = stream_peek(s, PROBE_SIZE);
|
||||
// Older versions of libass will overwrite the input buffer, and despite
|
||||
// passing length, expect a 0 termination.
|
||||
void *tmp = talloc_size(NULL, buf.len + 1);
|
||||
memcpy(tmp, buf.start, buf.len);
|
||||
buf.start = tmp;
|
||||
buf.start[buf.len] = '\0';
|
||||
bstr cbuf = mp_charset_guess_and_conv_to_utf8(log, buf, user_cp,
|
||||
MP_ICONV_ALLOW_CUTOFF);
|
||||
if (cbuf.start == NULL)
|
||||
cbuf = buf;
|
||||
ASS_Track *track = ass_read_memory(lib, cbuf.start, cbuf.len, NULL);
|
||||
bool ok = !!track;
|
||||
if (cbuf.start != buf.start)
|
||||
talloc_free(cbuf.start);
|
||||
talloc_free(buf.start);
|
||||
if (track)
|
||||
ass_free_track(track);
|
||||
ass_library_done(lib);
|
||||
if (!ok)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Actually load the full thing.
|
||||
|
||||
bstr buf = stream_read_complete(s, NULL, 100000000);
|
||||
if (!buf.start) {
|
||||
MP_ERR(demuxer, "Refusing to load subtitle file "
|
||||
"larger than 100 MB: %s\n", demuxer->filename);
|
||||
return -1;
|
||||
}
|
||||
bstr cbuf = mp_charset_guess_and_conv_to_utf8(log, buf, user_cp,
|
||||
MP_ICONV_VERBOSE);
|
||||
if (cbuf.start == NULL)
|
||||
cbuf = buf;
|
||||
if (cbuf.start != buf.start)
|
||||
talloc_free(buf.start);
|
||||
talloc_steal(demuxer, cbuf.start);
|
||||
|
||||
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_SUB);
|
||||
sh->codec = "ass";
|
||||
sh->extradata = cbuf.start;
|
||||
sh->extradata_size = cbuf.len;
|
||||
|
||||
demuxer->seekable = true;
|
||||
demuxer->fully_read = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct demuxer_desc demuxer_desc_libass = {
|
||||
.name = "libass",
|
||||
.desc = "ASS/SSA subtitles (libass)",
|
||||
.open = d_check_file,
|
||||
};
|
||||
+63
-38
@@ -149,6 +149,16 @@ typedef struct mkv_index {
|
||||
uint64_t filepos; // position of the cluster which contains the packet
|
||||
} mkv_index_t;
|
||||
|
||||
struct block_info {
|
||||
uint64_t duration, discardpadding;
|
||||
bool simple, keyframe;
|
||||
uint64_t timecode;
|
||||
mkv_track_t *track;
|
||||
bstr data;
|
||||
void *alloc;
|
||||
int64_t filepos;
|
||||
};
|
||||
|
||||
typedef struct mkv_demuxer {
|
||||
int64_t segment_start, segment_end;
|
||||
|
||||
@@ -183,6 +193,8 @@ typedef struct mkv_demuxer {
|
||||
bool index_has_durations;
|
||||
|
||||
bool eof_warning;
|
||||
|
||||
struct block_info tmp_block;
|
||||
} mkv_demuxer_t;
|
||||
|
||||
#define OPT_BASE_STRUCT struct demux_mkv_opts
|
||||
@@ -190,7 +202,7 @@ struct demux_mkv_opts {
|
||||
int subtitle_preroll;
|
||||
double subtitle_preroll_secs;
|
||||
int probe_duration;
|
||||
int fix_timestamps;
|
||||
int probe_start_time;
|
||||
};
|
||||
|
||||
const struct m_sub_options demux_mkv_conf = {
|
||||
@@ -200,13 +212,13 @@ const struct m_sub_options demux_mkv_conf = {
|
||||
M_OPT_MIN, .min = 0),
|
||||
OPT_CHOICE("probe-video-duration", probe_duration, 0,
|
||||
({"no", 0}, {"yes", 1}, {"full", 2})),
|
||||
OPT_FLAG("fix-timestamps", fix_timestamps, 0),
|
||||
OPT_FLAG("probe-start-time", probe_start_time, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct demux_mkv_opts),
|
||||
.defaults = &(const struct demux_mkv_opts){
|
||||
.subtitle_preroll_secs = 1.0,
|
||||
.fix_timestamps = 0,
|
||||
.probe_start_time = 1,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -219,7 +231,9 @@ const struct m_sub_options demux_mkv_conf = {
|
||||
// (Subtitle packets added before first A/V keyframe packet is found with seek.)
|
||||
#define NUM_SUB_PREROLL_PACKETS 500
|
||||
|
||||
static void probe_last_timestamp(struct demuxer *demuxer);
|
||||
static void probe_last_timestamp(struct demuxer *demuxer, int64_t start_pos);
|
||||
static void probe_first_timestamp(struct demuxer *demuxer);
|
||||
static void free_block(struct block_info *block);
|
||||
|
||||
#define AAC_SYNC_EXTENSION_TYPE 0x02b7
|
||||
static int aac_get_sample_rate_index(uint32_t sample_rate)
|
||||
@@ -362,6 +376,10 @@ static int demux_mkv_read_info(demuxer_t *demuxer)
|
||||
struct ebml_parse_ctx parse_ctx = {demuxer->log};
|
||||
if (ebml_read_element(s, &parse_ctx, &info, &ebml_info_desc) < 0)
|
||||
return -1;
|
||||
if (info.muxing_app)
|
||||
MP_VERBOSE(demuxer, "| + muxing app: %s\n", info.muxing_app);
|
||||
if (info.writing_app)
|
||||
MP_VERBOSE(demuxer, "| + writing app: %s\n", info.writing_app);
|
||||
if (info.n_timecode_scale) {
|
||||
mkv_d->tc_scale = info.timecode_scale;
|
||||
MP_VERBOSE(demuxer, "| + timecode scale: %" PRIu64 "\n", mkv_d->tc_scale);
|
||||
@@ -1884,8 +1902,9 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
add_coverart(demuxer);
|
||||
demuxer->allow_refresh_seeks = true;
|
||||
|
||||
probe_first_timestamp(demuxer);
|
||||
if (opts->demux_mkv->probe_duration)
|
||||
probe_last_timestamp(demuxer);
|
||||
probe_last_timestamp(demuxer, start_pos);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -2095,6 +2114,8 @@ static void mkv_seek_reset(demuxer_t *demuxer)
|
||||
}
|
||||
track->av_parser_codec = NULL;
|
||||
}
|
||||
|
||||
free_block(&mkv_d->tmp_block);
|
||||
}
|
||||
|
||||
// Copied from libavformat/matroskadec.c (FFmpeg 310f9dd / 2013-05-30)
|
||||
@@ -2268,16 +2289,6 @@ static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
|
||||
}
|
||||
}
|
||||
|
||||
struct block_info {
|
||||
uint64_t duration, discardpadding;
|
||||
bool simple, keyframe;
|
||||
uint64_t timecode;
|
||||
mkv_track_t *track;
|
||||
bstr data;
|
||||
void *alloc;
|
||||
int64_t filepos;
|
||||
};
|
||||
|
||||
static void free_block(struct block_info *block)
|
||||
{
|
||||
free(block->alloc);
|
||||
@@ -2348,19 +2359,6 @@ exit:
|
||||
return res;
|
||||
}
|
||||
|
||||
static double fix_timestamp(demuxer_t *demuxer, mkv_track_t *track, double ts)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
if (demuxer->opts->demux_mkv->fix_timestamps && track->default_duration > 0) {
|
||||
// Assume that timestamps have been rounded to the timecode scale.
|
||||
double quant = MPMIN(mkv_d->tc_scale / 1e9, 0.001);
|
||||
double rts = rint(ts / track->default_duration) * track->default_duration;
|
||||
if (fabs(rts - ts) < quant)
|
||||
ts = rts;
|
||||
}
|
||||
return ts;
|
||||
}
|
||||
|
||||
static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
|
||||
@@ -2383,14 +2381,18 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
|
||||
return 0;
|
||||
}
|
||||
|
||||
current_pts = fix_timestamp(demuxer, track, tc / 1e9) - track->codec_delay;
|
||||
current_pts = tc / 1e9 - track->codec_delay;
|
||||
|
||||
if (track->type == MATROSKA_TRACK_AUDIO) {
|
||||
if (mkv_d->a_skip_to_keyframe)
|
||||
use_this_block &= keyframe;
|
||||
} else if (track->type == MATROSKA_TRACK_SUBTITLE) {
|
||||
if (!use_this_block && mkv_d->subtitle_preroll)
|
||||
use_this_block = 1;
|
||||
if (!use_this_block && mkv_d->subtitle_preroll) {
|
||||
int64_t end_time = block_info->timecode + block_info->duration;
|
||||
if (!block_info->duration)
|
||||
end_time = INT64_MAX;
|
||||
use_this_block = end_time > mkv_d->skip_to_timecode;
|
||||
}
|
||||
if (use_this_block) {
|
||||
if (mkv_d->subtitle_preroll) {
|
||||
mkv_d->subtitle_preroll--;
|
||||
@@ -2522,6 +2524,12 @@ static int read_next_block(demuxer_t *demuxer, struct block_info *block)
|
||||
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
|
||||
stream_t *s = demuxer->stream;
|
||||
|
||||
if (mkv_d->tmp_block.alloc) {
|
||||
*block = mkv_d->tmp_block;
|
||||
mkv_d->tmp_block = (struct block_info){0};
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
while (stream_tell(s) < mkv_d->cluster_end) {
|
||||
int64_t start_filepos = stream_tell(s);
|
||||
@@ -2651,8 +2659,7 @@ static int create_index_until(struct demuxer *demuxer, uint64_t timecode)
|
||||
mkv_index_t *index = get_highest_index_entry(demuxer);
|
||||
|
||||
if (!index || index->timecode * mkv_d->tc_scale < timecode) {
|
||||
if (index)
|
||||
stream_seek(s, index->filepos);
|
||||
stream_seek(s, index ? index->filepos : mkv_d->cluster_start);
|
||||
MP_VERBOSE(demuxer, "creating index until TC %" PRIu64 "\n", timecode);
|
||||
for (;;) {
|
||||
int res;
|
||||
@@ -2847,10 +2854,9 @@ static void demux_mkv_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
demux_mkv_fill_buffer(demuxer);
|
||||
}
|
||||
|
||||
static void probe_last_timestamp(struct demuxer *demuxer)
|
||||
static void probe_last_timestamp(struct demuxer *demuxer, int64_t start_pos)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
int64_t old_pos = stream_tell(demuxer->stream);
|
||||
|
||||
if (!demuxer->seekable)
|
||||
return;
|
||||
@@ -2888,10 +2894,12 @@ static void probe_last_timestamp(struct demuxer *demuxer)
|
||||
int64_t size = stream_get_size(demuxer->stream);
|
||||
stream_seek(demuxer->stream, MPMAX(size - 10 * 1024 * 1024, 0));
|
||||
if (ebml_resync_cluster(mp_null_log, demuxer->stream) < 0)
|
||||
stream_seek(demuxer->stream, old_pos); // full scan otherwise
|
||||
stream_seek(demuxer->stream, start_pos); // full scan otherwise
|
||||
}
|
||||
}
|
||||
|
||||
free_block(&mkv_d->tmp_block);
|
||||
|
||||
int64_t last_ts[STREAM_TYPE_COUNT] = {0};
|
||||
while (1) {
|
||||
struct block_info block;
|
||||
@@ -2913,12 +2921,29 @@ static void probe_last_timestamp(struct demuxer *demuxer)
|
||||
last_ts[STREAM_VIDEO] = mkv_d->cluster_tc;
|
||||
|
||||
if (last_ts[STREAM_VIDEO])
|
||||
mkv_d->duration = last_ts[STREAM_VIDEO] / 1e9;
|
||||
mkv_d->duration = last_ts[STREAM_VIDEO] / 1e9 - demuxer->start_time;
|
||||
|
||||
stream_seek(demuxer->stream, old_pos);
|
||||
stream_seek(demuxer->stream, start_pos);
|
||||
mkv_d->cluster_start = mkv_d->cluster_end = 0;
|
||||
}
|
||||
|
||||
static void probe_first_timestamp(struct demuxer *demuxer)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
|
||||
if (!demuxer->opts->demux_mkv->probe_start_time)
|
||||
return;
|
||||
|
||||
struct block_info block;
|
||||
if (read_next_block(demuxer, &block) > 0)
|
||||
mkv_d->tmp_block = block;
|
||||
|
||||
demuxer->start_time = mkv_d->cluster_tc / 1e9;
|
||||
|
||||
if (demuxer->start_time > 0)
|
||||
MP_VERBOSE(demuxer, "Start PTS: %f\n", demuxer->start_time);
|
||||
}
|
||||
|
||||
static int demux_mkv_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
|
||||
|
||||
@@ -1,831 +0,0 @@
|
||||
/*
|
||||
* Subtitle reader with format autodetection
|
||||
*
|
||||
* Copyright (c) 2001 laaz
|
||||
* Some code cleanup & realloc() by A'rpi/ESP-team
|
||||
*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/avstring.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/common.h"
|
||||
#include "options/options.h"
|
||||
#include "stream/stream.h"
|
||||
#include "demux/demux.h"
|
||||
|
||||
#define ERR ((void *) -1)
|
||||
|
||||
// subtitle formats
|
||||
#define SUB_INVALID -1
|
||||
#define SUB_MICRODVD 0
|
||||
#define SUB_SUBRIP 1
|
||||
#define SUB_SUBVIEWER 2
|
||||
#define SUB_SSA 3
|
||||
#define SUB_SUBVIEWER2 4
|
||||
#define SUB_SUBRIP09 5
|
||||
#define SUB_MPL2 6
|
||||
|
||||
#define SUB_MAX_TEXT 12
|
||||
#define SUB_ALIGNMENT_BOTTOMLEFT 1
|
||||
#define SUB_ALIGNMENT_BOTTOMCENTER 2
|
||||
#define SUB_ALIGNMENT_BOTTOMRIGHT 3
|
||||
#define SUB_ALIGNMENT_MIDDLELEFT 4
|
||||
#define SUB_ALIGNMENT_MIDDLECENTER 5
|
||||
#define SUB_ALIGNMENT_MIDDLERIGHT 6
|
||||
#define SUB_ALIGNMENT_TOPLEFT 7
|
||||
#define SUB_ALIGNMENT_TOPCENTER 8
|
||||
#define SUB_ALIGNMENT_TOPRIGHT 9
|
||||
|
||||
typedef struct subtitle {
|
||||
|
||||
int lines;
|
||||
|
||||
unsigned long start;
|
||||
unsigned long end;
|
||||
|
||||
char *text[SUB_MAX_TEXT];
|
||||
unsigned char alignment;
|
||||
} subtitle;
|
||||
|
||||
typedef struct sub_data {
|
||||
const char *codec;
|
||||
subtitle *subtitles;
|
||||
int sub_uses_time;
|
||||
int sub_num; // number of subtitle structs
|
||||
int sub_errs;
|
||||
double fallback_fps;
|
||||
} sub_data;
|
||||
|
||||
// Parameter struct for the format-specific readline functions
|
||||
struct readline_args {
|
||||
struct mp_log *log;
|
||||
int utf16;
|
||||
struct MPOpts *opts;
|
||||
|
||||
// subtitle reader state used by some formats
|
||||
|
||||
float mpsub_multiplier;
|
||||
float mpsub_position;
|
||||
int sub_slacktime;
|
||||
|
||||
int uses_time;
|
||||
|
||||
/*
|
||||
Some subtitling formats, namely AQT and Subrip09, define the end of a
|
||||
subtitle as the beginning of the following. Since currently we read one
|
||||
subtitle at time, for these format we keep two global *subtitle,
|
||||
previous_aqt_sub and previous_subrip09_sub, pointing to previous subtitle,
|
||||
so we can change its end when we read current subtitle starting time.
|
||||
We use a single global unsigned long,
|
||||
previous_sub_end, for both (and even future) formats, to store the end of
|
||||
the previous sub: it is initialized to 0 in sub_read_file and eventually
|
||||
modified by sub_read_aqt_line or sub_read_subrip09_line.
|
||||
*/
|
||||
unsigned long previous_sub_end;
|
||||
};
|
||||
|
||||
/* Maximal length of line of a subtitle */
|
||||
#define LINE_LEN 1000
|
||||
|
||||
static double demux_packet_list_duration(struct demux_packet **pkts, int num_pkts)
|
||||
{
|
||||
if (num_pkts > 0)
|
||||
return pkts[num_pkts - 1]->pts + pkts[num_pkts - 1]->duration;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void demux_packet_list_seek(struct demux_packet **pkts, int num_pkts,
|
||||
int *current, double rel_seek_secs, int flags)
|
||||
{
|
||||
double ref_time = 0;
|
||||
if (*current >= 0 && *current < num_pkts) {
|
||||
ref_time = pkts[*current]->pts;
|
||||
} else if (*current == num_pkts && num_pkts > 0) {
|
||||
ref_time = pkts[num_pkts - 1]->pts + pkts[num_pkts - 1]->duration;
|
||||
}
|
||||
|
||||
if (flags & SEEK_ABSOLUTE)
|
||||
ref_time = 0;
|
||||
|
||||
if (flags & SEEK_FACTOR) {
|
||||
ref_time += demux_packet_list_duration(pkts, num_pkts) * rel_seek_secs;
|
||||
} else {
|
||||
ref_time += rel_seek_secs;
|
||||
}
|
||||
|
||||
// Could do binary search, but it's probably not worth the complexity.
|
||||
int last_index = 0;
|
||||
for (int n = 0; n < num_pkts; n++) {
|
||||
if (pkts[n]->pts > ref_time)
|
||||
break;
|
||||
last_index = n;
|
||||
}
|
||||
*current = last_index;
|
||||
}
|
||||
|
||||
static struct demux_packet *demux_packet_list_fill(struct demux_packet **pkts,
|
||||
int num_pkts, int *current)
|
||||
{
|
||||
if (*current < 0)
|
||||
*current = 0;
|
||||
if (*current >= num_pkts)
|
||||
return NULL;
|
||||
struct demux_packet *new = talloc(NULL, struct demux_packet);
|
||||
*new = *pkts[*current];
|
||||
*current += 1;
|
||||
return new;
|
||||
}
|
||||
|
||||
static int eol(char p) {
|
||||
return p=='\r' || p=='\n' || p=='\0';
|
||||
}
|
||||
|
||||
static const char *sub_readtext(const char *source, char **dest) {
|
||||
int len=0;
|
||||
const char *p=source;
|
||||
|
||||
// printf("src=%p dest=%p \n",source,dest);
|
||||
|
||||
while ( !eol(*p) && *p!= '|' ) {
|
||||
p++,len++;
|
||||
}
|
||||
|
||||
*dest= malloc (len+1);
|
||||
if (!*dest) {return ERR;}
|
||||
|
||||
strncpy(*dest, source, len);
|
||||
(*dest)[len]=0;
|
||||
|
||||
while (*p=='\r' || *p=='\n' || *p=='|') p++;
|
||||
|
||||
if (*p) return p; // not-last text field
|
||||
else return NULL; // last text field
|
||||
}
|
||||
|
||||
static subtitle *set_multiline_text(struct readline_args *arg, subtitle *current,
|
||||
const char *text, int start)
|
||||
{
|
||||
int i = start;
|
||||
while ((text = sub_readtext(text, current->text + i))) {
|
||||
if (current->text[i] == ERR) return ERR;
|
||||
i++;
|
||||
if (i >= SUB_MAX_TEXT) {
|
||||
MP_WARN(arg, "Too many lines in a subtitle\n");
|
||||
current->lines = i;
|
||||
return current;
|
||||
}
|
||||
}
|
||||
current->lines = i + 1;
|
||||
return current;
|
||||
}
|
||||
|
||||
static subtitle *sub_read_line_microdvd(stream_t *st,subtitle *current,
|
||||
struct readline_args *args)
|
||||
{
|
||||
int utf16 = args->utf16;
|
||||
char line[LINE_LEN+1];
|
||||
char line2[LINE_LEN+1];
|
||||
|
||||
do {
|
||||
if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
|
||||
} while ((sscanf (line,
|
||||
"{%ld}{}%[^\r\n]",
|
||||
&(current->start), line2) < 2) &&
|
||||
(sscanf (line,
|
||||
"{%ld}{%ld}%[^\r\n]",
|
||||
&(current->start), &(current->end), line2) < 3));
|
||||
|
||||
return set_multiline_text(args, current, line2, 0);
|
||||
}
|
||||
|
||||
static subtitle *sub_read_line_mpl2(stream_t *st,subtitle *current,
|
||||
struct readline_args *args)
|
||||
{
|
||||
int utf16 = args->utf16;
|
||||
char line[LINE_LEN+1];
|
||||
char line2[LINE_LEN+1];
|
||||
|
||||
do {
|
||||
if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
|
||||
} while ((sscanf (line,
|
||||
"[%ld][%ld]%[^\r\n]",
|
||||
&(current->start), &(current->end), line2) < 3));
|
||||
current->start *= 10;
|
||||
current->end *= 10;
|
||||
|
||||
return set_multiline_text(args, current, line2, 0);
|
||||
}
|
||||
|
||||
static subtitle *sub_read_line_subrip(stream_t* st, subtitle *current,
|
||||
struct readline_args *args)
|
||||
{
|
||||
int utf16 = args->utf16;
|
||||
char line[LINE_LEN+1];
|
||||
int a1,a2,a3,a4,b1,b2,b3,b4;
|
||||
char *p=NULL, *q=NULL;
|
||||
int len;
|
||||
|
||||
while (1) {
|
||||
if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
|
||||
if (sscanf (line, "%d:%d:%d.%d,%d:%d:%d.%d",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4) < 8) continue;
|
||||
current->start = a1*360000+a2*6000+a3*100+a4;
|
||||
current->end = b1*360000+b2*6000+b3*100+b4;
|
||||
|
||||
if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
|
||||
|
||||
p=q=line;
|
||||
for (current->lines=1; current->lines < SUB_MAX_TEXT; current->lines++) {
|
||||
for (q=p,len=0; *p && *p!='\r' && *p!='\n' && *p!='|' && strncmp(p,"[br]",4); p++,len++);
|
||||
current->text[current->lines-1]=malloc (len+1);
|
||||
if (!current->text[current->lines-1]) return ERR;
|
||||
strncpy (current->text[current->lines-1], q, len);
|
||||
current->text[current->lines-1][len]='\0';
|
||||
if (!*p || *p=='\r' || *p=='\n') break;
|
||||
if (*p=='|') p++;
|
||||
else while (*p++!=']');
|
||||
}
|
||||
break;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
static subtitle *sub_read_line_subviewer(stream_t *st, subtitle *current,
|
||||
struct readline_args *args)
|
||||
{
|
||||
int utf16 = args->utf16;
|
||||
int a1, a2, a3, a4, b1, b2, b3, b4, j = 0;
|
||||
|
||||
while (!current->text[0]) {
|
||||
char line[LINE_LEN + 1], full_line[LINE_LEN + 1];
|
||||
int i;
|
||||
|
||||
/* Parse SubRip header */
|
||||
if (!stream_read_line(st, line, LINE_LEN, utf16))
|
||||
return NULL;
|
||||
if (sscanf(line, "%d:%d:%d%*1[,.:]%d --> %d:%d:%d%*1[,.:]%d",
|
||||
&a1, &a2, &a3, &a4, &b1, &b2, &b3, &b4) < 8)
|
||||
continue;
|
||||
|
||||
current->start = a1 * 360000 + a2 * 6000 + a3 * 100 + a4 / 10;
|
||||
current->end = b1 * 360000 + b2 * 6000 + b3 * 100 + b4 / 10;
|
||||
|
||||
/* Concatenate lines */
|
||||
full_line[0] = 0;
|
||||
for (i = 0; i < SUB_MAX_TEXT; i++) {
|
||||
int blank = 1, len = 0;
|
||||
char *p;
|
||||
|
||||
if (!stream_read_line(st, line, LINE_LEN, utf16))
|
||||
break;
|
||||
|
||||
for (p = line; *p != '\n' && *p != '\r' && *p; p++, len++)
|
||||
if (*p != ' ' && *p != '\t')
|
||||
blank = 0;
|
||||
|
||||
if (blank)
|
||||
break;
|
||||
|
||||
*p = 0;
|
||||
|
||||
if (!(j + 1 + len < sizeof(full_line) - 1))
|
||||
break;
|
||||
|
||||
if (j != 0)
|
||||
full_line[j++] = '\n';
|
||||
snprintf(&full_line[j], sizeof(full_line) - j, "%s", line);
|
||||
full_line[LINE_LEN] = '\0';
|
||||
j += len;
|
||||
}
|
||||
|
||||
if (full_line[0]) {
|
||||
current->text[0] = strdup(full_line);
|
||||
current->lines = 1;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
static subtitle *sub_read_line_subviewer2(stream_t *st,subtitle *current,
|
||||
struct readline_args *args)
|
||||
{
|
||||
int utf16 = args->utf16;
|
||||
char line[LINE_LEN+1];
|
||||
int a1,a2,a3,a4;
|
||||
char *p=NULL;
|
||||
int i,len;
|
||||
|
||||
while (!current->text[0]) {
|
||||
if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
|
||||
if (line[0]!='{')
|
||||
continue;
|
||||
if ((len=sscanf (line, "{T %d:%d:%d:%d",&a1,&a2,&a3,&a4)) < 4)
|
||||
continue;
|
||||
current->start = a1*360000+a2*6000+a3*100+a4/10;
|
||||
for (i=0; i<SUB_MAX_TEXT;) {
|
||||
if (!stream_read_line (st, line, LINE_LEN, utf16)) break;
|
||||
if (line[0]=='}') break;
|
||||
len=0;
|
||||
for (p=line; *p!='\n' && *p!='\r' && *p; ++p,++len);
|
||||
if (len) {
|
||||
current->text[i]=malloc (len+1);
|
||||
if (!current->text[i]) return ERR;
|
||||
strncpy (current->text[i], line, len); current->text[i][len]='\0';
|
||||
++i;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
current->lines=i;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
static subtitle *sub_read_line_ssa(stream_t *st,subtitle *current,
|
||||
struct readline_args *args)
|
||||
{
|
||||
/* Instead of hardcoding the expected fields and their order on
|
||||
* each dialogue line, this code should parse the "Format: " line
|
||||
* which lists the fields used in the script. As is, this may not
|
||||
* work correctly with all scripts.
|
||||
*/
|
||||
|
||||
int utf16 = args->utf16;
|
||||
int comma;
|
||||
|
||||
int hour1, min1, sec1, hunsec1,
|
||||
hour2, min2, sec2, hunsec2, nothing;
|
||||
int num;
|
||||
|
||||
char line[LINE_LEN+1],
|
||||
line3[LINE_LEN+1],
|
||||
*line2;
|
||||
char *tmp;
|
||||
|
||||
do {
|
||||
if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL;
|
||||
} while (sscanf (line, "Dialogue: Marked=%d,%d:%d:%d.%d,%d:%d:%d.%d"
|
||||
"%[^\n\r]", ¬hing,
|
||||
&hour1, &min1, &sec1, &hunsec1,
|
||||
&hour2, &min2, &sec2, &hunsec2,
|
||||
line3) < 9
|
||||
&&
|
||||
sscanf (line, "Dialogue: %d,%d:%d:%d.%d,%d:%d:%d.%d"
|
||||
"%[^\n\r]", ¬hing,
|
||||
&hour1, &min1, &sec1, &hunsec1,
|
||||
&hour2, &min2, &sec2, &hunsec2,
|
||||
line3) < 9 );
|
||||
|
||||
line2=strchr(line3, ',');
|
||||
if (!line2) return NULL;
|
||||
|
||||
for (comma = 3; comma < 9; comma ++)
|
||||
if (!(line2 = strchr(++line2, ',')))
|
||||
return NULL;
|
||||
line2++;
|
||||
|
||||
current->lines=0;num=0;
|
||||
current->start = 360000*hour1 + 6000*min1 + 100*sec1 + hunsec1;
|
||||
current->end = 360000*hour2 + 6000*min2 + 100*sec2 + hunsec2;
|
||||
|
||||
while (((tmp=strstr(line2, "\\n")) != NULL) || ((tmp=strstr(line2, "\\N")) != NULL) ){
|
||||
current->text[num]=malloc(tmp-line2+1);
|
||||
strncpy (current->text[num], line2, tmp-line2);
|
||||
current->text[num][tmp-line2]='\0';
|
||||
line2=tmp+2;
|
||||
num++;
|
||||
current->lines++;
|
||||
if (current->lines >= SUB_MAX_TEXT) return current;
|
||||
}
|
||||
|
||||
current->text[num]=strdup(line2);
|
||||
current->lines++;
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
static subtitle *sub_read_line_subrip09(stream_t *st,subtitle *current,
|
||||
struct readline_args *args)
|
||||
{
|
||||
int utf16 = args->utf16;
|
||||
char line[LINE_LEN+1];
|
||||
int a1,a2,a3;
|
||||
int len;
|
||||
|
||||
retry:
|
||||
while (1) {
|
||||
// try to locate next subtitle
|
||||
if (!stream_read_line (st, line, LINE_LEN, utf16))
|
||||
return NULL;
|
||||
if (!((len=sscanf (line, "[%d:%d:%d]",&a1,&a2,&a3)) < 3))
|
||||
break;
|
||||
}
|
||||
|
||||
current->start = a1*360000+a2*6000+a3*100;
|
||||
|
||||
if (!args->previous_sub_end)
|
||||
args->previous_sub_end = (current->start) ? current->start - 1 : 0;
|
||||
|
||||
if (!stream_read_line (st, line, LINE_LEN, utf16))
|
||||
return NULL;
|
||||
|
||||
current->text[0]=""; // just to be sure that string is clear
|
||||
|
||||
if (set_multiline_text(args, current, line, 0) == ERR)
|
||||
return ERR;
|
||||
|
||||
if (!strlen(current->text[0]) && current->lines <= 1)
|
||||
goto retry;
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
static int sub_autodetect (stream_t* st, int *uses_time, int utf16) {
|
||||
char line[LINE_LEN+1];
|
||||
int i,j=0;
|
||||
|
||||
while (j < 100) {
|
||||
j++;
|
||||
if (!stream_read_line (st, line, LINE_LEN, utf16))
|
||||
return SUB_INVALID;
|
||||
|
||||
if (sscanf (line, "{%d}{%d}", &i, &i)==2)
|
||||
{*uses_time=0;return SUB_MICRODVD;}
|
||||
if (sscanf (line, "{%d}{}", &i)==1)
|
||||
{*uses_time=0;return SUB_MICRODVD;}
|
||||
if (sscanf (line, "[%d][%d]", &i, &i)==2)
|
||||
{*uses_time=1;return SUB_MPL2;}
|
||||
if (sscanf (line, "%d:%d:%d.%d,%d:%d:%d.%d", &i, &i, &i, &i, &i, &i, &i, &i)==8)
|
||||
{*uses_time=1;return SUB_SUBRIP;}
|
||||
if (sscanf (line, "%d:%d:%d%*1[,.:]%d --> %d:%d:%d%*1[,.:]%d", &i, &i, &i, &i, &i, &i, &i, &i) == 8)
|
||||
{*uses_time=1;return SUB_SUBVIEWER;}
|
||||
if (sscanf (line, "{T %d:%d:%d:%d",&i, &i, &i, &i)==4)
|
||||
{*uses_time=1;return SUB_SUBVIEWER2;}
|
||||
if (!memcmp(line, "Dialogue: Marked", 16))
|
||||
{*uses_time=1; return SUB_SSA;}
|
||||
if (!memcmp(line, "Dialogue: ", 10))
|
||||
{*uses_time=1; return SUB_SSA;}
|
||||
if (sscanf (line, "[%d:%d:%d]", &i, &i, &i)==3)
|
||||
{*uses_time=1;return SUB_SUBRIP09;}
|
||||
}
|
||||
|
||||
return SUB_INVALID; // too many bad lines
|
||||
}
|
||||
|
||||
struct subreader {
|
||||
subtitle * (*read)(stream_t *st, subtitle *dest,
|
||||
struct readline_args *args);
|
||||
void (*post)(subtitle *dest);
|
||||
const char *name;
|
||||
const char *codec_name;
|
||||
struct readline_args args;
|
||||
};
|
||||
|
||||
static void adjust_subs_time(struct subreader *srp, subtitle* sub,
|
||||
float subtime, float fps,
|
||||
float sub_fps, int block,
|
||||
int sub_num, int sub_uses_time) {
|
||||
int n,m;
|
||||
subtitle* nextsub;
|
||||
int i = sub_num;
|
||||
unsigned long subfms = (sub_uses_time ? 100 : fps) * subtime;
|
||||
|
||||
n=m=0;
|
||||
if (i) for (;;){
|
||||
if (sub->end <= sub->start){
|
||||
sub->end = sub->start + subfms;
|
||||
m++;
|
||||
n++;
|
||||
}
|
||||
if (!--i) break;
|
||||
nextsub = sub + 1;
|
||||
if(block){
|
||||
if (sub->end >= nextsub->start){
|
||||
sub->end = nextsub->start - 1;
|
||||
if (sub->end - sub->start > subfms)
|
||||
sub->end = sub->start + subfms;
|
||||
if (!m)
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
sub = nextsub;
|
||||
m = 0;
|
||||
}
|
||||
if (n) MP_VERBOSE(&srp->args, "Adjusted %d subtitle(s).\n", n);
|
||||
}
|
||||
|
||||
static bool subreader_autodetect(stream_t *fd, struct MPOpts *opts,
|
||||
struct mp_log *log, struct subreader *out)
|
||||
{
|
||||
static const struct subreader sr[]=
|
||||
{
|
||||
{ sub_read_line_microdvd, NULL, "microdvd", "microdvd" },
|
||||
{ sub_read_line_subrip, NULL, "subviewer" },
|
||||
{ sub_read_line_subviewer, NULL, "subrip", "subrip" },
|
||||
{ sub_read_line_ssa, NULL, "ssa", "ass-text" },
|
||||
{ sub_read_line_subviewer2, NULL, "subviewer2" },
|
||||
{ sub_read_line_subrip09, NULL, "subrip9" },
|
||||
{ sub_read_line_mpl2, NULL, "mpl2" }
|
||||
};
|
||||
const struct subreader *srp;
|
||||
|
||||
int sub_format = SUB_INVALID;
|
||||
int utf16;
|
||||
int uses_time = 0;
|
||||
for (utf16 = 0; sub_format == SUB_INVALID && utf16 < 3; utf16++) {
|
||||
sub_format=sub_autodetect (fd, &uses_time, utf16);
|
||||
stream_seek(fd,0);
|
||||
}
|
||||
utf16--;
|
||||
|
||||
if (sub_format==SUB_INVALID) {
|
||||
mp_verbose(log, "Could not determine file format\n");
|
||||
return false;
|
||||
}
|
||||
srp=sr+sub_format;
|
||||
mp_verbose(log, "Detected subtitle file format: %s\n", srp->name);
|
||||
|
||||
*out = *srp;
|
||||
out->args = (struct readline_args) {
|
||||
.log = log,
|
||||
.utf16 = utf16,
|
||||
.opts = opts,
|
||||
.sub_slacktime = 20000, //20 sec
|
||||
.mpsub_multiplier = (uses_time ? 100.0 : 1.0),
|
||||
.uses_time = uses_time,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static sub_data* sub_read_file(stream_t *fd, struct subreader *srp)
|
||||
{
|
||||
struct MPOpts *opts = fd->opts;
|
||||
float fps = 23.976;
|
||||
int n_max, i, j;
|
||||
subtitle *first, *sub, *return_sub, *alloced_sub = NULL;
|
||||
sub_data *subt_data;
|
||||
int sub_num = 0, sub_errs = 0;
|
||||
struct readline_args args = srp->args;
|
||||
|
||||
sub_num=0;n_max=32;
|
||||
first=malloc(n_max*sizeof(subtitle));
|
||||
if (!first)
|
||||
abort();
|
||||
|
||||
alloced_sub =
|
||||
sub = malloc(sizeof(subtitle));
|
||||
//This is to deal with those formats (AQT & Subrip) which define the end of a subtitle
|
||||
//as the beginning of the following
|
||||
args.previous_sub_end = 0;
|
||||
while(1){
|
||||
if(sub_num>=n_max){
|
||||
n_max+=16;
|
||||
first=realloc(first,n_max*sizeof(subtitle));
|
||||
}
|
||||
memset(sub, '\0', sizeof(subtitle));
|
||||
sub=srp->read(fd, sub, &args);
|
||||
if(!sub) break; // EOF
|
||||
|
||||
if ( sub == ERR )
|
||||
{
|
||||
free(first);
|
||||
free(alloced_sub);
|
||||
return NULL;
|
||||
}
|
||||
// Apply any post processing that needs recoding first
|
||||
if ((sub!=ERR) && srp->post) srp->post(sub);
|
||||
if(!sub_num || (first[sub_num - 1].start <= sub->start)){
|
||||
first[sub_num].start = sub->start;
|
||||
first[sub_num].end = sub->end;
|
||||
first[sub_num].lines = sub->lines;
|
||||
first[sub_num].alignment = sub->alignment;
|
||||
for(i = 0; i < sub->lines; ++i){
|
||||
first[sub_num].text[i] = sub->text[i];
|
||||
}
|
||||
if (args.previous_sub_end){
|
||||
first[sub_num - 1].end = args.previous_sub_end;
|
||||
args.previous_sub_end = 0;
|
||||
}
|
||||
} else {
|
||||
for(j = sub_num - 1; j >= 0; --j){
|
||||
first[j + 1].start = first[j].start;
|
||||
first[j + 1].end = first[j].end;
|
||||
first[j + 1].lines = first[j].lines;
|
||||
first[j + 1].alignment = first[j].alignment;
|
||||
for(i = 0; i < first[j].lines; ++i){
|
||||
first[j + 1].text[i] = first[j].text[i];
|
||||
}
|
||||
if(!j || (first[j - 1].start <= sub->start)){
|
||||
first[j].start = sub->start;
|
||||
first[j].end = sub->end;
|
||||
first[j].lines = sub->lines;
|
||||
first[j].alignment = sub->alignment;
|
||||
for(i = 0; i < SUB_MAX_TEXT; ++i){
|
||||
first[j].text[i] = sub->text[i];
|
||||
}
|
||||
if (args.previous_sub_end){
|
||||
first[j].end = first[j - 1].end;
|
||||
first[j - 1].end = args.previous_sub_end;
|
||||
args.previous_sub_end = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(sub==ERR) ++sub_errs; else ++sub_num; // Error vs. Valid
|
||||
}
|
||||
|
||||
free(alloced_sub);
|
||||
|
||||
// printf ("Subtitle format %s time.\n", uses_time?"uses":"doesn't use");
|
||||
MP_VERBOSE(&srp->args, "Read %i subtitles, %i bad line(s).\n",
|
||||
sub_num, sub_errs);
|
||||
|
||||
if(sub_num<=0){
|
||||
free(first);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
adjust_subs_time(srp, first, 6.0, fps, opts->sub_fps, 1, sub_num, args.uses_time);/*~6 secs AST*/
|
||||
return_sub = first;
|
||||
|
||||
if (return_sub == NULL) return NULL;
|
||||
subt_data = talloc_zero(NULL, sub_data);
|
||||
subt_data->codec = srp->codec_name ? srp->codec_name : "text";
|
||||
subt_data->sub_uses_time = args.uses_time;
|
||||
subt_data->sub_num = sub_num;
|
||||
subt_data->sub_errs = sub_errs;
|
||||
subt_data->subtitles = return_sub;
|
||||
subt_data->fallback_fps = fps;
|
||||
return subt_data;
|
||||
}
|
||||
|
||||
static void subdata_free(sub_data *subd)
|
||||
{
|
||||
int i, j;
|
||||
for (i = 0; i < subd->sub_num; i++)
|
||||
for (j = 0; j < subd->subtitles[i].lines; j++)
|
||||
free( subd->subtitles[i].text[j] );
|
||||
free( subd->subtitles );
|
||||
talloc_free(subd);
|
||||
}
|
||||
|
||||
struct priv {
|
||||
struct demux_packet **pkts;
|
||||
int num_pkts;
|
||||
int current;
|
||||
struct sh_stream *sh;
|
||||
};
|
||||
|
||||
static void add_sub_data(struct demuxer *demuxer, struct sub_data *subdata)
|
||||
{
|
||||
struct priv *priv = demuxer->priv;
|
||||
|
||||
for (int i = 0; i < subdata->sub_num; i++) {
|
||||
subtitle *st = &subdata->subtitles[i];
|
||||
// subdata is in 10 ms ticks, pts is in seconds
|
||||
double t = subdata->sub_uses_time ? 0.01 : (1 / subdata->fallback_fps);
|
||||
|
||||
int len = 0;
|
||||
for (int j = 0; j < st->lines; j++)
|
||||
len += st->text[j] ? strlen(st->text[j]) : 0;
|
||||
|
||||
len += 2 * st->lines; // '\N', including the one after the last line
|
||||
len += 6; // {\anX}
|
||||
len += 1; // '\0'
|
||||
|
||||
char *data = talloc_array(NULL, char, len);
|
||||
|
||||
char *p = data;
|
||||
char *end = p + len;
|
||||
|
||||
if (st->alignment)
|
||||
p += snprintf(p, end - p, "{\\an%d}", st->alignment);
|
||||
|
||||
for (int j = 0; j < st->lines; j++)
|
||||
p += snprintf(p, end - p, "%s\\N", st->text[j]);
|
||||
|
||||
if (st->lines > 0)
|
||||
p -= 2; // remove last "\N"
|
||||
*p = 0;
|
||||
|
||||
struct demux_packet *pkt = talloc_ptrtype(priv, pkt);
|
||||
*pkt = (struct demux_packet) {
|
||||
.pts = st->start * t,
|
||||
.duration = (st->end - st->start) * t,
|
||||
.buffer = talloc_steal(pkt, data),
|
||||
.len = strlen(data),
|
||||
};
|
||||
|
||||
MP_TARRAY_APPEND(priv, priv->pkts, priv->num_pkts, pkt);
|
||||
}
|
||||
}
|
||||
|
||||
static struct stream *read_probe_stream(struct stream *s, int max)
|
||||
{
|
||||
// Very roundabout, but only needed for initial probing.
|
||||
bstr probe = stream_peek(s, max);
|
||||
return open_memory_stream(probe.start, probe.len);
|
||||
}
|
||||
|
||||
#define PROBE_SIZE FFMIN(32 * 1024, STREAM_MAX_BUFFER_SIZE)
|
||||
|
||||
static int d_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
if (check > DEMUX_CHECK_REQUEST)
|
||||
return -1;
|
||||
|
||||
if (!demuxer->params || !demuxer->params->expect_subtitle)
|
||||
return -1;
|
||||
|
||||
struct stream *ps = read_probe_stream(demuxer->stream, PROBE_SIZE);
|
||||
|
||||
struct subreader sr;
|
||||
bool res = subreader_autodetect(ps, demuxer->opts, demuxer->log, &sr);
|
||||
|
||||
free_stream(ps);
|
||||
|
||||
if (!res)
|
||||
return -1;
|
||||
|
||||
demuxer->filetype = sr.name;
|
||||
|
||||
sub_data *sd = sub_read_file(demuxer->stream, &sr);
|
||||
if (!sd)
|
||||
return -1;
|
||||
|
||||
struct priv *p = talloc_zero(demuxer, struct priv);
|
||||
demuxer->priv = p;
|
||||
|
||||
p->sh = new_sh_stream(demuxer, STREAM_SUB);
|
||||
p->sh->codec = sd->codec;
|
||||
p->sh->sub->frame_based = sd->sub_uses_time ? 0 : 23.976;
|
||||
p->sh->sub->is_utf8 = sr.args.utf16 != 0; // converted from utf-16 -> utf-8
|
||||
|
||||
add_sub_data(demuxer, sd);
|
||||
subdata_free(sd);
|
||||
|
||||
demuxer->seekable = true;
|
||||
demuxer->fully_read = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int d_fill_buffer(struct demuxer *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
struct demux_packet *dp = demux_packet_list_fill(p->pkts, p->num_pkts,
|
||||
&p->current);
|
||||
return demux_add_packet(p->sh, dp);
|
||||
}
|
||||
|
||||
static void d_seek(struct demuxer *demuxer, double secs, int flags)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
demux_packet_list_seek(p->pkts, p->num_pkts, &p->current, secs, flags);
|
||||
}
|
||||
|
||||
static int d_control(struct demuxer *demuxer, int cmd, void *arg)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
switch (cmd) {
|
||||
case DEMUXER_CTRL_GET_TIME_LENGTH:
|
||||
*((double *) arg) = demux_packet_list_duration(p->pkts, p->num_pkts);
|
||||
return DEMUXER_CTRL_OK;
|
||||
default:
|
||||
return DEMUXER_CTRL_NOTIMPL;
|
||||
}
|
||||
}
|
||||
|
||||
const struct demuxer_desc demuxer_desc_subreader = {
|
||||
.name = "subreader",
|
||||
.desc = "Deprecated MPlayer subreader",
|
||||
.open = d_open_file,
|
||||
.fill_buffer = d_fill_buffer,
|
||||
.seek = d_seek,
|
||||
.control = d_control,
|
||||
};
|
||||
@@ -100,6 +100,9 @@
|
||||
#6 add gamma 1
|
||||
#7 add saturation -1
|
||||
#8 add saturation 1
|
||||
#Alt+0 set window-scale 0.5
|
||||
#Alt+1 set window-scale 1.0
|
||||
#Alt+2 set window-scale 2.0
|
||||
# toggle deinterlacer (automatically inserts or removes required filter)
|
||||
#d cycle deinterlace
|
||||
#r add sub-pos -1 # move subtitles up
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
viewBox="0 0 63.999999 63.999999"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="mpv-gradient.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient4430">
|
||||
<stop
|
||||
style="stop-color:#65376e;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop4432" />
|
||||
<stop
|
||||
style="stop-color:#4c2354;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop4434" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4418"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop4420"
|
||||
offset="0"
|
||||
style="stop-color:#e6e2e8;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop4422"
|
||||
offset="1"
|
||||
style="stop-color:#aaa3ad;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient4402">
|
||||
<stop
|
||||
style="stop-color:#320f38;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop4404" />
|
||||
<stop
|
||||
style="stop-color:#5a2963;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop4406" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient4392">
|
||||
<stop
|
||||
style="stop-color:#6b3c74;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop4394" />
|
||||
<stop
|
||||
style="stop-color:#461b4d;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop4396" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient4382">
|
||||
<stop
|
||||
style="stop-color:#fafafa;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop4384" />
|
||||
<stop
|
||||
style="stop-color:#bababa;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop4386" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4382"
|
||||
id="linearGradient5278"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-86.873198,699.81912)"
|
||||
x1="97.187729"
|
||||
y1="305.67371"
|
||||
x2="140.38228"
|
||||
y2="335.64926" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4392"
|
||||
id="linearGradient5280"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-32.973108,701.2155)"
|
||||
x1="50.828064"
|
||||
y1="298.31949"
|
||||
x2="78.197021"
|
||||
y2="339.65219" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4402"
|
||||
id="linearGradient5282"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-31.48364,700.09839)"
|
||||
x1="53.620815"
|
||||
y1="305.76682"
|
||||
x2="77.824654"
|
||||
y2="331.73938" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4418"
|
||||
id="linearGradient5284"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-87.152474,700.0053)"
|
||||
x1="110.31366"
|
||||
y1="312.28323"
|
||||
x2="126.88398"
|
||||
y2="329.41211" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4430"
|
||||
id="linearGradient5286"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-87.152474,700.0053)"
|
||||
x1="115.97468"
|
||||
y1="317.41763"
|
||||
x2="119.37984"
|
||||
y2="322.43457" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.3710484"
|
||||
inkscape:cx="-19.490294"
|
||||
inkscape:cy="18.643164"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-988.3622)">
|
||||
<circle
|
||||
style="opacity:1;fill:url(#linearGradient5278);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.10161044;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:1;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.99215686"
|
||||
id="path4380-0"
|
||||
cx="32"
|
||||
cy="1020.3622"
|
||||
r="27.949194" />
|
||||
<circle
|
||||
style="opacity:1;fill:url(#linearGradient5280);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0988237;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:1;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.99215686"
|
||||
id="path4390-3"
|
||||
cx="32.727058"
|
||||
cy="1019.5079"
|
||||
r="25.950588" />
|
||||
<circle
|
||||
style="opacity:1;fill:url(#linearGradient5282);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:1;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.99215686"
|
||||
id="path4400-3"
|
||||
cx="34.224396"
|
||||
cy="1017.7957"
|
||||
r="20" />
|
||||
<path
|
||||
style="fill:url(#linearGradient5284);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 44.481446,1020.4807 a 12.848894,12.848894 0 0 1 -12.84889,12.8489 12.848894,12.848894 0 0 1 -12.8489,-12.8489 12.848894,12.848894 0 0 1 12.8489,-12.8489 12.848894,12.848894 0 0 1 12.84889,12.8489 z"
|
||||
id="path4412-5"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:url(#linearGradient5286);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 28.374316,1014.709 0,11.4502 9.21608,-5.8647 z"
|
||||
id="path4426-2"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.6 KiB |
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
viewBox="0 0 63.999999 63.999999"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="mpv-symbolic.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.3710484"
|
||||
inkscape:cx="19.094402"
|
||||
inkscape:cy="44.778512"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-988.3622)">
|
||||
<path
|
||||
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.10161044;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:1;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.99215686"
|
||||
d="M 32.582031 2.9101562 A 27.949194 27.949194 0 0 0 4.6328125 30.859375 A 27.949194 27.949194 0 0 0 32.582031 58.808594 A 27.949194 27.949194 0 0 0 60.53125 30.859375 A 27.949194 27.949194 0 0 0 32.582031 2.9101562 z M 33.308594 4.0546875 A 25.950588 25.950588 0 0 1 59.259766 30.005859 A 25.950588 25.950588 0 0 1 33.308594 55.955078 A 25.950588 25.950588 0 0 1 7.359375 30.005859 A 25.950588 25.950588 0 0 1 33.308594 4.0546875 z "
|
||||
transform="translate(0,988.3622)"
|
||||
id="path4380" />
|
||||
<path
|
||||
id="path4412"
|
||||
transform="translate(0,988.3622)"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 32.214844,18.128906 a 12.848894,12.848894 0 0 0 -12.84961,12.84961 12.848894,12.848894 0 0 0 12.84961,12.847656 12.848894,12.848894 0 0 0 12.849609,-12.847656 12.848894,12.848894 0 0 0 -12.849609,-12.84961 z m -3.257813,7.078125 9.214844,5.583985 -9.214844,5.865234 0,-11.449219 z M 54.806488,28.2928 a 20,20 0 0 1 -20,20 20,20 0 0 1 -20,-20 20,20 0 0 1 20,-19.99997 20,20 0 0 1 20,19.99997 z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
+86
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
viewBox="0 0 63.999999 63.999999"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="mpv.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.3710484"
|
||||
inkscape:cx="10.112865"
|
||||
inkscape:cy="18.643164"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-988.3622)">
|
||||
<circle
|
||||
style="opacity:1;fill:#e5e5e5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.10161044;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:1;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.99215686"
|
||||
id="path4380"
|
||||
cx="32"
|
||||
cy="1020.3622"
|
||||
r="27.949194" />
|
||||
<circle
|
||||
style="opacity:1;fill:#672168;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0988237;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:1;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.99215686"
|
||||
id="path4390"
|
||||
cx="32.727058"
|
||||
cy="1019.5079"
|
||||
r="25.950588" />
|
||||
<circle
|
||||
style="opacity:1;fill:#420143;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:1;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.99215686"
|
||||
id="path4400"
|
||||
cx="34.224396"
|
||||
cy="1017.7957"
|
||||
r="20" />
|
||||
<path
|
||||
style="fill:#dddbdd;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 44.481446,1020.4807 a 12.848894,12.848894 0 0 1 -12.84889,12.8489 12.848894,12.848894 0 0 1 -12.8489,-12.8489 12.848894,12.848894 0 0 1 12.8489,-12.8489 12.848894,12.848894 0 0 1 12.84889,12.8489 z"
|
||||
id="path4412"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#691f69;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 28.374316,1014.709 0,11.4502 9.21608,-5.8647 z"
|
||||
id="path4426"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
@@ -36,6 +36,7 @@ struct key_name {
|
||||
static const struct key_name key_names[] = {
|
||||
{ ' ', "SPACE" },
|
||||
{ '#', "SHARP" },
|
||||
{ 0x3000, "IDEOGRAPHIC_SPACE" },
|
||||
{ MP_KEY_ENTER, "ENTER" },
|
||||
{ MP_KEY_TAB, "TAB" },
|
||||
{ MP_KEY_BACKSPACE, "BS" },
|
||||
|
||||
+20
-4
@@ -49,13 +49,29 @@ extern "C" {
|
||||
* 2) Using mpv as a library with mpv_create(). This basically allows embedding
|
||||
* mpv in other applications.
|
||||
*
|
||||
* Documentation
|
||||
* -------------
|
||||
*
|
||||
* The libmpv C API is documented directly in this header. Note that most
|
||||
* actual interaction with this player is done through
|
||||
* options/commands/properties, which can be accessed through this API.
|
||||
* Essentially everything is done with them, including loading a file,
|
||||
* retrieving playback progress, and so on.
|
||||
*
|
||||
* These are documented elsewhere:
|
||||
* * http://mpv.io/manual/master/#options
|
||||
* * http://mpv.io/manual/master/#list-of-input-commands
|
||||
* * http://mpv.io/manual/master/#properties
|
||||
*
|
||||
* You can also look at the examples in DOCS/client_api_examples/.
|
||||
*
|
||||
* Event loop
|
||||
* ----------
|
||||
*
|
||||
* In general, the API user should run an event loop in order to receive events.
|
||||
* This even loop should call mpv_wait_event(), which will return once a new
|
||||
* mpv client API is available. It should also be possible to integrate client
|
||||
* API usage in other event loops (e.g. GUI toolkits) with the
|
||||
* This event loop should call mpv_wait_event(), which will return once a new
|
||||
* mpv client API is available. It is also possible to integrate client API
|
||||
* usage in other event loops (e.g. GUI toolkits) with the
|
||||
* mpv_set_wakeup_callback() function, and then polling for events by calling
|
||||
* mpv_wait_event() with a 0 timeout.
|
||||
*
|
||||
@@ -198,7 +214,7 @@ extern "C" {
|
||||
* relational operators (<, >, <=, >=).
|
||||
*/
|
||||
#define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL)
|
||||
#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 19)
|
||||
#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 20)
|
||||
|
||||
/**
|
||||
* Return the MPV_CLIENT_API_VERSION the mpv source has been compiled with.
|
||||
|
||||
+43
-11
@@ -108,8 +108,36 @@ extern "C" {
|
||||
* context must have been uninitialized. If this doesn't happen, undefined
|
||||
* behavior will result.
|
||||
*
|
||||
* Special D3D interop considerations
|
||||
* ----------------------------------
|
||||
* Special windowing system interop considerations
|
||||
* ------------------------------------------------
|
||||
*
|
||||
* In some cases, libmpv needs to have access to the windowing system's handles.
|
||||
* This can be a pointer to a X11 "Display" for example. Usually this is needed
|
||||
* only for hardware decoding.
|
||||
*
|
||||
* You can communicate these handles to libmpv by adding a pseudo-OpenGL
|
||||
* extension "GL_MP_MPGetNativeDisplay" to the additional extension string when
|
||||
* calling mpv_opengl_cb_init_gl(). The get_proc_address callback should resolve
|
||||
* a function named "glMPGetNativeDisplay", which has the signature:
|
||||
*
|
||||
* void* GLAPIENTRY glMPGetNativeDisplay(const char* name)
|
||||
*
|
||||
* See below what names are defined. Usually, libmpv will use the native handle
|
||||
* up until mpv_opengl_cb_uninit_gl() is called. If the name is not anything
|
||||
* you know/expected, return NULL from the function.
|
||||
*
|
||||
* Windowing system interop on Linux
|
||||
* ---------------------------------
|
||||
*
|
||||
* The new VAAPI OpenGL interop requires an EGL context. EGL provides no way
|
||||
* to query the X11 Display associated to a specific EGL context, so this API
|
||||
* is used to pass it through.
|
||||
*
|
||||
* glMPGetNativeDisplay("x11") should return a X11 "Display*", which then will
|
||||
* be used to create the hardware decoder state. (On GLX, this is not needed.)
|
||||
*
|
||||
* Windowing system interop on MS win32
|
||||
* ------------------------------------
|
||||
*
|
||||
* If OpenGL switches to fullscreen, most players give it access GPU access,
|
||||
* which means DXVA2 hardware decoding in mpv won't work. This can be worked
|
||||
@@ -117,14 +145,14 @@ extern "C" {
|
||||
* create a decoder. The device can be either the real device used for display,
|
||||
* or a "blank" device created before switching to fullscreen.
|
||||
*
|
||||
* You can do this by adding "GL_MP_D3D_interfaces" to the additional extension
|
||||
* string when calling mpv_opengl_cb_init_gl(). The get_proc_address callback
|
||||
* should resolve a function named "glMPGetD3DInterface", which has the
|
||||
* signature: "void* __stdcall glMPGetD3DInterface(const char* name)". If
|
||||
* name is "IDirect3DDevice9", it should return a IDirect3DDevice9 pointer
|
||||
* (or NULL if not available). libmpv will release this interface when it is
|
||||
* done with it (usually when mpv_opengl_cb_uninit_gl() is called). New
|
||||
* interface names can be added in the future.
|
||||
* You can provide glMPGetNativeDisplay as described in the previous section.
|
||||
* If it is called with name set to "IDirect3DDevice9", it should return a
|
||||
* IDirect3DDevice9 pointer (or NULL if not available). libmpv will release
|
||||
* this interface when it is done with it.
|
||||
*
|
||||
* In previous libmpv releases, this used "GL_MP_D3D_interfaces" and
|
||||
* "glMPGetD3DInterface". This is deprecated; use glMPGetNativeDisplay instead
|
||||
* (the semantics are 100% compatible).
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -214,7 +242,7 @@ int mpv_opengl_cb_init_gl(mpv_opengl_cb_context *ctx, const char *exts,
|
||||
* @param h Height of the framebuffer. Same as with the w parameter, except
|
||||
* that this parameter can be negative. In this case, the video
|
||||
* frame will be rendered flipped.
|
||||
* @return the number of left frames in the internal queue to be rendered
|
||||
* @return 0
|
||||
*/
|
||||
int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int w, int h);
|
||||
|
||||
@@ -235,10 +263,14 @@ int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4]);
|
||||
* Tell the renderer that a frame was flipped at the given time. This is
|
||||
* optional, but can help the player to achieve better timing.
|
||||
*
|
||||
* Note that calling this at least once informs libmpv that you will use this
|
||||
* function. If you use it inconsistently, expect bad video playback.
|
||||
*
|
||||
* If this is called while no video or no OpenGL is initialized, it is ignored.
|
||||
*
|
||||
* @param time The mpv time (using mpv_get_time_us()) at which the flip call
|
||||
* returned. If 0 is passed, mpv_get_time_us() is used instead.
|
||||
* Currently, this parameter is ignored.
|
||||
* @return error code
|
||||
*/
|
||||
int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time);
|
||||
|
||||
@@ -259,24 +259,6 @@ const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf,
|
||||
return res;
|
||||
}
|
||||
|
||||
// Convert the data in buf to UTF-8. The charset argument can be an iconv
|
||||
// codepage, a value returned by mp_charset_conv_guess(), or a special value
|
||||
// that triggers autodetection of the charset (e.g. using ENCA).
|
||||
// The auto-detection is the only difference to mp_iconv_to_utf8().
|
||||
// buf: same as mp_iconv_to_utf8()
|
||||
// user_cp: iconv codepage, special value, NULL
|
||||
// flags: same as mp_iconv_to_utf8()
|
||||
// returns: same as mp_iconv_to_utf8()
|
||||
bstr mp_charset_guess_and_conv_to_utf8(struct mp_log *log, bstr buf,
|
||||
const char *user_cp, int flags)
|
||||
{
|
||||
void *tmp = talloc_new(NULL);
|
||||
const char *cp = mp_charset_guess(tmp, log, buf, user_cp, flags);
|
||||
bstr res = mp_iconv_to_utf8(log, buf, cp, flags);
|
||||
talloc_free(tmp);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Use iconv to convert buf to UTF-8.
|
||||
// Returns buf.start==NULL on error. Returns buf if cp is NULL, or if there is
|
||||
// obviously no conversion required (e.g. if cp is "UTF-8").
|
||||
|
||||
@@ -16,8 +16,6 @@ bool mp_charset_is_utf8(const char *user_cp);
|
||||
bool mp_charset_requires_guess(const char *user_cp);
|
||||
const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf,
|
||||
const char *user_cp, int flags);
|
||||
bstr mp_charset_guess_and_conv_to_utf8(struct mp_log *log, bstr buf,
|
||||
const char *user_cp, int flags);
|
||||
bstr mp_iconv_to_utf8(struct mp_log *log, bstr buf, const char *cp, int flags);
|
||||
|
||||
#endif
|
||||
+22
-25
@@ -35,6 +35,7 @@
|
||||
#include "m_config.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/msg_control.h"
|
||||
|
||||
static const union m_option_value default_value;
|
||||
|
||||
@@ -114,7 +115,6 @@ static int parse_profile(struct m_config *config, const struct m_option *opt,
|
||||
static int show_profile(struct m_config *config, bstr param)
|
||||
{
|
||||
struct m_profile *p;
|
||||
int i, j;
|
||||
if (!param.len)
|
||||
return M_OPT_MISSING_PARAM;
|
||||
if (!(p = m_config_get_profile(config, param))) {
|
||||
@@ -125,25 +125,18 @@ static int show_profile(struct m_config *config, bstr param)
|
||||
MP_INFO(config, "Profile %s: %s\n", p->name,
|
||||
p->desc ? p->desc : "");
|
||||
config->profile_depth++;
|
||||
for (i = 0; i < p->num_opts; i++) {
|
||||
char spc[config->profile_depth + 1];
|
||||
for (j = 0; j < config->profile_depth; j++)
|
||||
spc[j] = ' ';
|
||||
spc[config->profile_depth] = '\0';
|
||||
|
||||
MP_INFO(config, "%s%s=%s\n", spc, p->opts[2 * i], p->opts[2 * i + 1]);
|
||||
for (int i = 0; i < p->num_opts; i++) {
|
||||
MP_INFO(config, "%*s%s=%s\n", config->profile_depth, "",
|
||||
p->opts[2 * i], p->opts[2 * i + 1]);
|
||||
|
||||
if (config->profile_depth < MAX_PROFILE_DEPTH
|
||||
&& !strcmp(p->opts[2*i], "profile")) {
|
||||
char *e, *list = p->opts[2 * i + 1];
|
||||
while ((e = strchr(list, ','))) {
|
||||
int l = e - list;
|
||||
char tmp[l+1];
|
||||
if (!l)
|
||||
continue;
|
||||
memcpy(tmp, list, l);
|
||||
tmp[l] = '\0';
|
||||
show_profile(config, bstr0(tmp));
|
||||
show_profile(config, (bstr){list, e - list});
|
||||
list = e + 1;
|
||||
}
|
||||
if (list[0] != '\0')
|
||||
@@ -557,18 +550,23 @@ static int handle_set_opt_flags(struct m_config *config,
|
||||
return set ? 2 : 1;
|
||||
}
|
||||
|
||||
static void handle_set_from_cmdline(struct m_config *config,
|
||||
struct m_config_option *co)
|
||||
static void handle_on_set(struct m_config *config, struct m_config_option *co,
|
||||
int flags)
|
||||
{
|
||||
co->is_set_from_cmdline = true;
|
||||
// Mark aliases too
|
||||
if (co->data) {
|
||||
for (int n = 0; n < config->num_opts; n++) {
|
||||
struct m_config_option *co2 = &config->opts[n];
|
||||
if (co2->data == co->data)
|
||||
co2->is_set_from_cmdline = true;
|
||||
if (flags & M_SETOPT_FROM_CMDLINE) {
|
||||
co->is_set_from_cmdline = true;
|
||||
// Mark aliases too
|
||||
if (co->data) {
|
||||
for (int n = 0; n < config->num_opts; n++) {
|
||||
struct m_config_option *co2 = &config->opts[n];
|
||||
if (co2->data == co->data)
|
||||
co2->is_set_from_cmdline = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config->global && (co->opt->flags & M_OPT_TERM))
|
||||
mp_msg_update_msglevels(config->global);
|
||||
}
|
||||
|
||||
// The type data points to is as in: m_config_get_co(config, name)->opt
|
||||
@@ -588,8 +586,7 @@ int m_config_set_option_raw(struct m_config *config, struct m_config_option *co,
|
||||
return r;
|
||||
|
||||
m_option_copy(co->opt, co->data, data);
|
||||
if (flags & M_SETOPT_FROM_CMDLINE)
|
||||
handle_set_from_cmdline(config, co);
|
||||
handle_on_set(config, co, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -637,8 +634,8 @@ static int m_config_parse_option(struct m_config *config, struct bstr name,
|
||||
|
||||
r = m_option_parse(config->log, co->opt, name, param, set ? co->data : NULL);
|
||||
|
||||
if (r >= 0 && set && (flags & M_SETOPT_FROM_CMDLINE))
|
||||
handle_set_from_cmdline(config, co);
|
||||
if (r >= 0 && set)
|
||||
handle_on_set(config, co, flags);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ struct m_config_option {
|
||||
/** \ingroup Config */
|
||||
typedef struct m_config {
|
||||
struct mp_log *log;
|
||||
struct mpv_global *global; // can be NULL
|
||||
|
||||
// Registered options.
|
||||
struct m_config_option *opts; // all options, even suboptions
|
||||
|
||||
@@ -363,6 +363,9 @@ struct m_option {
|
||||
// The option expects a file name (or a list of file names)
|
||||
#define M_OPT_FILE (1 << 11)
|
||||
|
||||
// Logging-related option - used to update log/terminal settings eagerly
|
||||
#define M_OPT_TERM (1 << 12)
|
||||
|
||||
// These are kept for compatibility with older code.
|
||||
#define CONF_MIN M_OPT_MIN
|
||||
#define CONF_MAX M_OPT_MAX
|
||||
|
||||
@@ -49,14 +49,12 @@ static struct m_property *m_property_list_find(const struct m_property *list,
|
||||
static int do_action(const struct m_property *prop_list, const char *name,
|
||||
int action, void *arg, void *ctx)
|
||||
{
|
||||
const char *sep;
|
||||
struct m_property *prop;
|
||||
struct m_property_action_arg ka;
|
||||
if ((sep = strchr(name, '/')) && sep[1]) {
|
||||
int len = sep - name;
|
||||
char base[len + 1];
|
||||
memcpy(base, name, len);
|
||||
base[len] = 0;
|
||||
const char *sep = strchr(name, '/');
|
||||
if (sep && sep[1]) {
|
||||
char base[128];
|
||||
snprintf(base, sizeof(base), "%.*s", (int)(sep - name), name);
|
||||
prop = m_property_list_find(prop_list, base);
|
||||
ka = (struct m_property_action_arg) {
|
||||
.key = sep + 1,
|
||||
|
||||
+31
-18
@@ -60,7 +60,6 @@ static void print_help(struct mp_log *log)
|
||||
}
|
||||
|
||||
extern const struct m_sub_options tv_params_conf;
|
||||
extern const struct m_sub_options stream_pvr_conf;
|
||||
extern const struct m_sub_options stream_cdda_conf;
|
||||
extern const struct m_sub_options stream_dvb_conf;
|
||||
extern const struct m_sub_options stream_lavf_conf;
|
||||
@@ -84,7 +83,6 @@ const struct m_opt_choice_alternatives mp_hwdec_names[] = {
|
||||
{"no", HWDEC_NONE},
|
||||
{"auto", HWDEC_AUTO},
|
||||
{"vdpau", HWDEC_VDPAU},
|
||||
{"vda", HWDEC_VDA},
|
||||
{"videotoolbox",HWDEC_VIDEOTOOLBOX},
|
||||
{"vaapi", HWDEC_VAAPI},
|
||||
{"vaapi-copy", HWDEC_VAAPI_COPY},
|
||||
@@ -114,14 +112,14 @@ const m_option_t mp_opts[] = {
|
||||
// ------------------------- common options --------------------
|
||||
OPT_FLAG("quiet", quiet, CONF_GLOBAL),
|
||||
OPT_FLAG_STORE("really-quiet", verbose, CONF_GLOBAL | CONF_PRE_PARSE, -10),
|
||||
OPT_FLAG("terminal", use_terminal, CONF_GLOBAL | CONF_PRE_PARSE),
|
||||
OPT_GENERAL(char**, "msg-level", msg_levels, CONF_GLOBAL|CONF_PRE_PARSE,
|
||||
.type = &m_option_type_msglevels),
|
||||
OPT_FLAG("terminal", use_terminal, CONF_GLOBAL | CONF_PRE_PARSE | M_OPT_TERM),
|
||||
OPT_GENERAL(char**, "msg-level", msg_levels, CONF_GLOBAL|CONF_PRE_PARSE |
|
||||
M_OPT_TERM, .type = &m_option_type_msglevels),
|
||||
OPT_STRING("dump-stats", dump_stats, CONF_GLOBAL | CONF_PRE_PARSE),
|
||||
OPT_FLAG("msg-color", msg_color, CONF_GLOBAL | CONF_PRE_PARSE),
|
||||
OPT_FLAG("msg-color", msg_color, CONF_GLOBAL | CONF_PRE_PARSE | M_OPT_TERM),
|
||||
OPT_STRING("log-file", log_file, CONF_GLOBAL | CONF_PRE_PARSE | M_OPT_FILE),
|
||||
OPT_FLAG("msg-module", msg_module, CONF_GLOBAL),
|
||||
OPT_FLAG("msg-time", msg_time, CONF_GLOBAL),
|
||||
OPT_FLAG("msg-module", msg_module, CONF_GLOBAL | M_OPT_TERM),
|
||||
OPT_FLAG("msg-time", msg_time, CONF_GLOBAL | M_OPT_TERM),
|
||||
#ifdef _WIN32
|
||||
OPT_CHOICE("priority", w32_priority, 0,
|
||||
({"no", 0},
|
||||
@@ -186,6 +184,8 @@ const m_option_t mp_opts[] = {
|
||||
{"http", 3})),
|
||||
OPT_FLAG("tls-verify", network_tls_verify, 0),
|
||||
OPT_STRING("tls-ca-file", network_tls_ca_file, M_OPT_FILE),
|
||||
OPT_STRING("tls-cert-file", network_tls_cert_file, M_OPT_FILE),
|
||||
OPT_STRING("tls-key-file", network_tls_key_file, M_OPT_FILE),
|
||||
OPT_DOUBLE("network-timeout", network_timeout, M_OPT_MIN, .min = 0),
|
||||
|
||||
// ------------------------- demuxer options --------------------
|
||||
@@ -197,6 +197,8 @@ const m_option_t mp_opts[] = {
|
||||
OPT_REL_TIME("end", play_end, 0),
|
||||
OPT_REL_TIME("length", play_length, 0),
|
||||
|
||||
OPT_FLAG("rebase-start-time", rebase_start_time, 0),
|
||||
|
||||
OPT_TIME("ab-loop-a", ab_loop[0], 0, .min = MP_NOPTS_VALUE),
|
||||
OPT_TIME("ab-loop-b", ab_loop[1], 0, .min = MP_NOPTS_VALUE),
|
||||
|
||||
@@ -257,9 +259,6 @@ const m_option_t mp_opts[] = {
|
||||
#if HAVE_TV
|
||||
OPT_SUBSTRUCT("tv", tv_params, tv_params_conf, 0),
|
||||
#endif /* HAVE_TV */
|
||||
#if HAVE_PVR
|
||||
OPT_SUBSTRUCT("pvr", stream_pvr_opts, stream_pvr_conf, 0),
|
||||
#endif /* HAVE_PVR */
|
||||
#if HAVE_DVBIN
|
||||
OPT_SUBSTRUCT("dvbin", stream_dvb_opts, stream_dvb_conf, 0),
|
||||
#endif
|
||||
@@ -305,6 +304,9 @@ const m_option_t mp_opts[] = {
|
||||
OPT_CHOICE_C("hwdec", hwdec_api, 0, mp_hwdec_names),
|
||||
OPT_CHOICE_C("hwdec-preload", vo.hwdec_preload_api, 0, mp_hwdec_names),
|
||||
OPT_STRING("hwdec-codecs", hwdec_codecs, 0),
|
||||
#if HAVE_VIDEOTOOLBOX_HWACCEL
|
||||
OPT_IMAGEFORMAT("videotoolbox-format", videotoolbox_format, 0),
|
||||
#endif
|
||||
|
||||
OPT_SUBSTRUCT("sws", vo.sws_opts, sws_conf, 0),
|
||||
|
||||
@@ -373,7 +375,6 @@ const m_option_t mp_opts[] = {
|
||||
OPT_FLOATRANGE("osd-bar-w", osd_bar_w, 0, 1, 100),
|
||||
OPT_FLOATRANGE("osd-bar-h", osd_bar_h, 0, 0.1, 50),
|
||||
OPT_SUBSTRUCT("osd", osd_style, osd_style_conf, 0),
|
||||
OPT_FLAG("use-text-osd", use_text_osd, CONF_GLOBAL),
|
||||
OPT_SUBSTRUCT("sub-text", sub_text_style, sub_style_conf, 0),
|
||||
OPT_FLAG("sub-clear-on-seek", sub_clear_on_seek, 0),
|
||||
|
||||
@@ -384,6 +385,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_SETTINGSLIST("ao-defaults", ao_defs, 0, &ao_obj_list),
|
||||
OPT_STRING("audio-device", audio_device, 0),
|
||||
OPT_STRING("audio-client-name", audio_client_name, 0),
|
||||
OPT_FLAG("audio-fallback-to-null", ao_null_fallback, 0),
|
||||
OPT_CHOICE("force-window", force_vo, 0,
|
||||
({"no", 0}, {"yes", 1}, {"immediate", 2})),
|
||||
OPT_FLAG("ontop", vo.ontop, M_OPT_FIXED),
|
||||
@@ -448,7 +450,12 @@ const m_option_t mp_opts[] = {
|
||||
#if HAVE_X11
|
||||
OPT_CHOICE("x11-netwm", vo.x11_netwm, 0,
|
||||
({"auto", 0}, {"no", -1}, {"yes", 1})),
|
||||
OPT_FLAG("x11-bypass-compositor", vo.x11_bypass_compositor, 0),
|
||||
#endif
|
||||
#if HAVE_WIN32
|
||||
OPT_STRING("vo-mmcss-profile", vo.mmcss_profile, M_OPT_FIXED),
|
||||
#endif
|
||||
|
||||
OPT_STRING("heartbeat-cmd", heartbeat_cmd, 0),
|
||||
OPT_FLOAT("heartbeat-interval", heartbeat_interval, CONF_MIN, 0),
|
||||
|
||||
@@ -464,6 +471,8 @@ const m_option_t mp_opts[] = {
|
||||
OPT_INTRANGE("contrast", gamma_contrast, 0, -100, 100),
|
||||
OPT_INTRANGE("hue", gamma_hue, 0, -100, 100),
|
||||
OPT_INTRANGE("gamma", gamma_gamma, 0, -100, 100),
|
||||
OPT_CHOICE_C("video-output-levels", video_output_levels, 0,
|
||||
mp_csp_levels_names),
|
||||
OPT_FLAG("keepaspect", vo.keepaspect, 0),
|
||||
OPT_FLAG("keepaspect-window", vo.keepaspect_window, 0),
|
||||
|
||||
@@ -519,14 +528,13 @@ const m_option_t mp_opts[] = {
|
||||
|
||||
// a-v sync stuff:
|
||||
OPT_FLAG("correct-pts", correct_pts, 0),
|
||||
OPT_CHOICE("pts-association-mode", user_pts_assoc_mode, 0,
|
||||
({"auto", 0}, {"decoder", 1}, {"sort", 2})),
|
||||
OPT_FLAG("initial-audio-sync", initial_audio_sync, 0),
|
||||
OPT_CHOICE("video-sync", video_sync, 0,
|
||||
({"audio", VS_DEFAULT},
|
||||
{"display-resample", VS_DISP_RESAMPLE},
|
||||
{"display-resample-vdrop", VS_DISP_RESAMPLE_VDROP},
|
||||
{"display-resample-desync", VS_DISP_RESAMPLE_NONE},
|
||||
{"display-adrop", VS_DISP_ADROP},
|
||||
{"display-vdrop", VS_DISP_VDROP},
|
||||
{"display-desync", VS_DISP_NONE},
|
||||
{"desync", VS_NONE})),
|
||||
@@ -534,6 +542,8 @@ const m_option_t mp_opts[] = {
|
||||
M_OPT_MIN, .min = 0),
|
||||
OPT_DOUBLE("video-sync-max-audio-change", sync_max_audio_change,
|
||||
M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 1),
|
||||
OPT_DOUBLE("video-sync-adrop-size", sync_audio_drop_size,
|
||||
M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 1),
|
||||
OPT_CHOICE("hr-seek", hr_seek, 0,
|
||||
({"no", -1}, {"absolute", 0}, {"yes", 1}, {"always", 1})),
|
||||
OPT_FLOAT("hr-seek-demuxer-offset", hr_seek_demuxer_offset, 0),
|
||||
@@ -690,6 +700,8 @@ const struct MPOpts mp_default_opts = {
|
||||
.border = 1,
|
||||
.WinID = -1,
|
||||
.window_scale = 1.0,
|
||||
.x11_bypass_compositor = 1,
|
||||
.mmcss_profile = "Playback",
|
||||
},
|
||||
.allow_win_drag = 1,
|
||||
.wintitle = "${?media-title:${media-title}}${!media-title:No file} - mpv",
|
||||
@@ -713,11 +725,10 @@ const struct MPOpts mp_default_opts = {
|
||||
.sub_use_margins = 1,
|
||||
.ass_scale_with_window = 0,
|
||||
.sub_scale_with_window = 1,
|
||||
.use_text_osd = 1,
|
||||
#if HAVE_LUA
|
||||
.lua_load_osc = 1,
|
||||
.lua_load_ytdl = 1,
|
||||
.lua_ytdl_format = "best",
|
||||
.lua_ytdl_format = NULL,
|
||||
.lua_ytdl_raw_options = NULL,
|
||||
#endif
|
||||
.auto_load_scripts = 1,
|
||||
@@ -728,6 +739,7 @@ const struct MPOpts mp_default_opts = {
|
||||
.hr_seek_framedrop = 1,
|
||||
.sync_max_video_change = 1,
|
||||
.sync_max_audio_change = 0.125,
|
||||
.sync_audio_drop_size = 0.020,
|
||||
.load_config = 1,
|
||||
.position_resume = 1,
|
||||
.stream_cache = {
|
||||
@@ -752,7 +764,6 @@ const struct MPOpts mp_default_opts = {
|
||||
.edition_id = -1,
|
||||
.default_max_pts_correction = -1,
|
||||
.correct_pts = 1,
|
||||
.user_pts_assoc_mode = 1,
|
||||
.initial_audio_sync = 1,
|
||||
.frame_dropping = 1,
|
||||
.term_osd = 2,
|
||||
@@ -760,6 +771,7 @@ const struct MPOpts mp_default_opts = {
|
||||
.consolecontrols = 1,
|
||||
.playlist_pos = -1,
|
||||
.play_frames = -1,
|
||||
.rebase_start_time = 1,
|
||||
.keep_open = 0,
|
||||
.stream_id = { { [STREAM_AUDIO] = -1,
|
||||
[STREAM_VIDEO] = -1,
|
||||
@@ -797,7 +809,8 @@ const struct MPOpts mp_default_opts = {
|
||||
.sub_cp = "auto",
|
||||
.screenshot_template = "mpv-shot%n",
|
||||
|
||||
.hwdec_codecs = "h264,vc1,wmv3,hevc",
|
||||
.hwdec_codecs = "h264,vc1,wmv3,hevc,mpeg2video",
|
||||
.videotoolbox_format = IMGFMT_NV12,
|
||||
|
||||
.index_mode = 1,
|
||||
|
||||
|
||||
+10
-2
@@ -19,6 +19,7 @@ typedef struct mp_vo_opts {
|
||||
int fs_black_out_screens;
|
||||
char *winname;
|
||||
int x11_netwm;
|
||||
int x11_bypass_compositor;
|
||||
int native_keyrepeat;
|
||||
|
||||
float panscan;
|
||||
@@ -42,6 +43,8 @@ typedef struct mp_vo_opts {
|
||||
float monitor_pixel_aspect;
|
||||
int force_window_position;
|
||||
|
||||
char *mmcss_profile;
|
||||
|
||||
// vo_wayland, vo_drm
|
||||
struct sws_opts *sws_opts;
|
||||
// vo_opengl, vo_opengl_cb
|
||||
@@ -81,6 +84,7 @@ typedef struct MPOpts {
|
||||
struct m_obj_settings *audio_driver_list, *ao_defs;
|
||||
char *audio_device;
|
||||
char *audio_client_name;
|
||||
int ao_null_fallback;
|
||||
int force_vo;
|
||||
int softvol;
|
||||
float mixer_init_volume;
|
||||
@@ -103,6 +107,7 @@ typedef struct MPOpts {
|
||||
int gamma_contrast;
|
||||
int gamma_saturation;
|
||||
int gamma_hue;
|
||||
int video_output_levels;
|
||||
|
||||
int stop_screensaver;
|
||||
int cursor_autohide_delay;
|
||||
@@ -142,11 +147,11 @@ typedef struct MPOpts {
|
||||
int chapterrange[2];
|
||||
int edition_id;
|
||||
int correct_pts;
|
||||
int user_pts_assoc_mode;
|
||||
int initial_audio_sync;
|
||||
int video_sync;
|
||||
double sync_max_video_change;
|
||||
double sync_max_audio_change;
|
||||
double sync_audio_drop_size;
|
||||
int hr_seek;
|
||||
float hr_seek_demuxer_offset;
|
||||
int hr_seek_framedrop;
|
||||
@@ -171,6 +176,7 @@ typedef struct MPOpts {
|
||||
struct m_rel_time play_start;
|
||||
struct m_rel_time play_end;
|
||||
struct m_rel_time play_length;
|
||||
int rebase_start_time;
|
||||
int play_frames;
|
||||
double ab_loop[2];
|
||||
double step_sec;
|
||||
@@ -233,7 +239,6 @@ typedef struct MPOpts {
|
||||
char **sub_paths;
|
||||
int sub_auto;
|
||||
int audiofile_auto;
|
||||
int use_text_osd;
|
||||
int osd_bar_visible;
|
||||
float osd_bar_align_x;
|
||||
float osd_bar_align_y;
|
||||
@@ -266,6 +271,7 @@ typedef struct MPOpts {
|
||||
|
||||
int hwdec_api;
|
||||
char *hwdec_codecs;
|
||||
int videotoolbox_format;
|
||||
|
||||
int w32_priority;
|
||||
|
||||
@@ -276,6 +282,8 @@ typedef struct MPOpts {
|
||||
char **network_http_header_fields;
|
||||
int network_tls_verify;
|
||||
char *network_tls_ca_file;
|
||||
char *network_tls_cert_file;
|
||||
char *network_tls_key_file;
|
||||
double network_timeout;
|
||||
|
||||
struct tv_params *tv_params;
|
||||
|
||||
+5
-3
@@ -226,17 +226,19 @@ char *mp_splitext(const char *path, bstr *root)
|
||||
|
||||
char *mp_path_join_bstr(void *talloc_ctx, struct bstr p1, struct bstr p2)
|
||||
{
|
||||
bool test;
|
||||
if (p1.len == 0)
|
||||
return bstrdup0(talloc_ctx, p2);
|
||||
if (p2.len == 0)
|
||||
return bstrdup0(talloc_ctx, p1);
|
||||
|
||||
#if HAVE_DOS_PATHS
|
||||
if ((p2.len >= 2 && p2.start[1] == ':')
|
||||
|| p2.start[0] == '\\' || p2.start[0] == '/')
|
||||
test = (p2.len >= 2 && p2.start[1] == ':')
|
||||
|| p2.start[0] == '\\' || p2.start[0] == '/';
|
||||
#else
|
||||
if (p2.start[0] == '/')
|
||||
test = p2.start[0] == '/';
|
||||
#endif
|
||||
if (test)
|
||||
return bstrdup0(talloc_ctx, p2); // absolute path
|
||||
|
||||
bool have_separator;
|
||||
|
||||
@@ -254,12 +254,11 @@ static bool is_a_console(HANDLE h)
|
||||
return GetConsoleMode(h, &(DWORD){0});
|
||||
}
|
||||
|
||||
static void reopen_console_handle(DWORD std, FILE *stream)
|
||||
static void reopen_console_handle(DWORD std, int fd, FILE *stream)
|
||||
{
|
||||
HANDLE wstream = GetStdHandle(std);
|
||||
if (is_a_console(wstream)) {
|
||||
int fd = _open_osfhandle((intptr_t)wstream, _O_TEXT);
|
||||
dup2(fd, fileno(stream));
|
||||
if (is_a_console(GetStdHandle(std))) {
|
||||
freopen("CONOUT$", "wt", stream);
|
||||
dup2(fileno(stream), fd);
|
||||
setvbuf(stream, NULL, _IONBF, 0);
|
||||
}
|
||||
}
|
||||
@@ -282,9 +281,9 @@ bool terminal_try_attach(void)
|
||||
return false;
|
||||
|
||||
// We have a console window. Redirect output streams to that console's
|
||||
// low-level handles, so we can actually use WriteConsole later on.
|
||||
reopen_console_handle(STD_OUTPUT_HANDLE, stdout);
|
||||
reopen_console_handle(STD_ERROR_HANDLE, stderr);
|
||||
// low-level handles, so things that use printf directly work later on.
|
||||
reopen_console_handle(STD_OUTPUT_HANDLE, STDOUT_FILENO, stdout);
|
||||
reopen_console_handle(STD_ERROR_HANDLE, STDERR_FILENO, stderr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+154
-65
@@ -29,6 +29,7 @@
|
||||
#include "common/encode.h"
|
||||
#include "options/options.h"
|
||||
#include "common/common.h"
|
||||
#include "osdep/timer.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/audio.h"
|
||||
@@ -42,16 +43,23 @@
|
||||
#include "core.h"
|
||||
#include "command.h"
|
||||
|
||||
static int update_playback_speed_filters(struct MPContext *mpctx)
|
||||
// Use pitch correction only for speed adjustments by the user, not minor sync
|
||||
// correction ones.
|
||||
static int get_speed_method(struct MPContext *mpctx)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
double speed = mpctx->audio_speed;
|
||||
struct af_stream *afs = mpctx->d_audio->afilter;
|
||||
return mpctx->opts->pitch_correction && mpctx->opts->playback_speed != 1.0
|
||||
? AF_CONTROL_SET_PLAYBACK_SPEED : AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE;
|
||||
}
|
||||
|
||||
// Use pitch correction only for speed adjustments by the user, not minor
|
||||
// sync correction ones.
|
||||
bool use_pitch_correction = opts->pitch_correction &&
|
||||
opts->playback_speed != 1.0;
|
||||
// Try to reuse the existing filters to change playback speed. If it works,
|
||||
// return true; if filter recreation is needed, return false.
|
||||
static bool update_speed_filters(struct MPContext *mpctx)
|
||||
{
|
||||
struct af_stream *afs = mpctx->d_audio->afilter;
|
||||
double speed = mpctx->audio_speed;
|
||||
|
||||
if (afs->initialized < 1)
|
||||
return false;
|
||||
|
||||
// Make sure only exactly one filter changes speed; resetting them all
|
||||
// and setting 1 filter is the easiest way to achieve this.
|
||||
@@ -59,54 +67,69 @@ static int update_playback_speed_filters(struct MPContext *mpctx)
|
||||
af_control_all(afs, AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE, &(double){1});
|
||||
|
||||
if (speed == 1.0)
|
||||
return af_remove_by_label(afs, "playback-speed");
|
||||
return !af_find_by_label(afs, "playback-speed");
|
||||
|
||||
// Compatibility: if the user uses --af=scaletempo, always use this
|
||||
// filter to change speed. Don't insert a second filter (any) either.
|
||||
if (!af_find_by_label(afs, "playback-speed") &&
|
||||
af_control_any_rev(afs, AF_CONTROL_SET_PLAYBACK_SPEED, &speed))
|
||||
return 0;
|
||||
return true;
|
||||
|
||||
int method = AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE;
|
||||
if (use_pitch_correction)
|
||||
method = AF_CONTROL_SET_PLAYBACK_SPEED;
|
||||
return !!af_control_any_rev(afs, get_speed_method(mpctx), &speed);
|
||||
}
|
||||
|
||||
if (!af_control_any_rev(afs, method, &speed)) {
|
||||
if (af_remove_by_label(afs, "playback-speed") < 0)
|
||||
return -1;
|
||||
// Update speed, and insert/remove filters if necessary.
|
||||
static void recreate_speed_filters(struct MPContext *mpctx)
|
||||
{
|
||||
struct af_stream *afs = mpctx->d_audio->afilter;
|
||||
|
||||
char *filter = method == AF_CONTROL_SET_PLAYBACK_SPEED
|
||||
? "scaletempo" : "lavrresample";
|
||||
if (af_add(afs, filter, "playback-speed", NULL) < 0)
|
||||
return -1;
|
||||
// Try again.
|
||||
if (!af_control_any_rev(afs, method, &speed))
|
||||
return -1;
|
||||
}
|
||||
if (update_speed_filters(mpctx))
|
||||
return;
|
||||
|
||||
return 0;
|
||||
if (af_remove_by_label(afs, "playback-speed") < 0)
|
||||
goto fail;
|
||||
|
||||
if (mpctx->audio_speed == 1.0)
|
||||
return;
|
||||
|
||||
int method = get_speed_method(mpctx);
|
||||
char *filter = method == AF_CONTROL_SET_PLAYBACK_SPEED
|
||||
? "scaletempo" : "lavrresample";
|
||||
|
||||
if (!af_add(afs, filter, "playback-speed", NULL))
|
||||
goto fail;
|
||||
|
||||
if (!update_speed_filters(mpctx))
|
||||
goto fail;
|
||||
|
||||
return;
|
||||
|
||||
fail:
|
||||
mpctx->opts->playback_speed = 1.0;
|
||||
mpctx->speed_factor_a = 1.0;
|
||||
mpctx->audio_speed = 1.0;
|
||||
mp_notify(mpctx, MP_EVENT_CHANGE_ALL, NULL);
|
||||
}
|
||||
|
||||
static int recreate_audio_filters(struct MPContext *mpctx)
|
||||
{
|
||||
assert(mpctx->d_audio);
|
||||
|
||||
if (update_playback_speed_filters(mpctx) < 0) {
|
||||
mpctx->opts->playback_speed = 1.0;
|
||||
mpctx->speed_factor_a = 1.0;
|
||||
mpctx->audio_speed = 1.0;
|
||||
mp_notify(mpctx, MP_EVENT_CHANGE_ALL, NULL);
|
||||
}
|
||||
|
||||
struct af_stream *afs = mpctx->d_audio->afilter;
|
||||
if (afs->initialized < 1 && af_init(afs) < 0) {
|
||||
MP_ERR(mpctx, "Couldn't find matching filter/ao format!\n");
|
||||
return -1;
|
||||
}
|
||||
if (afs->initialized < 1 && af_init(afs) < 0)
|
||||
goto fail;
|
||||
|
||||
recreate_speed_filters(mpctx);
|
||||
if (afs->initialized < 1 && af_init(afs) < 0)
|
||||
goto fail;
|
||||
|
||||
mixer_reinit_audio(mpctx->mixer, mpctx->ao, afs);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
MP_ERR(mpctx, "Couldn't find matching filter/ao format!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int reinit_audio_filters(struct MPContext *mpctx)
|
||||
@@ -116,33 +139,20 @@ int reinit_audio_filters(struct MPContext *mpctx)
|
||||
return 0;
|
||||
|
||||
af_uninit(mpctx->d_audio->afilter);
|
||||
if (af_init(mpctx->d_audio->afilter) < 0)
|
||||
return -1;
|
||||
if (recreate_audio_filters(mpctx) < 0)
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
return recreate_audio_filters(mpctx) < 0 ? -1 : 1;
|
||||
}
|
||||
|
||||
// Call this if opts->playback_speed or mpctx->speed_correction changes.
|
||||
// Call this if opts->playback_speed or mpctx->speed_factor_* change.
|
||||
void update_playback_speed(struct MPContext *mpctx)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
|
||||
double old_speed_factor_a = mpctx->speed_factor_a;
|
||||
double old_audio_speed = mpctx->audio_speed;
|
||||
|
||||
mpctx->audio_speed = opts->playback_speed * mpctx->speed_factor_a;
|
||||
mpctx->video_speed = opts->playback_speed * mpctx->speed_factor_v;
|
||||
|
||||
if (mpctx->speed_factor_a == old_speed_factor_a &&
|
||||
mpctx->audio_speed == old_audio_speed)
|
||||
return;
|
||||
mpctx->audio_speed = mpctx->opts->playback_speed * mpctx->speed_factor_a;
|
||||
mpctx->video_speed = mpctx->opts->playback_speed * mpctx->speed_factor_v;
|
||||
|
||||
if (!mpctx->d_audio || mpctx->d_audio->afilter->initialized < 1)
|
||||
return;
|
||||
|
||||
recreate_audio_filters(mpctx);
|
||||
if (!update_speed_filters(mpctx))
|
||||
recreate_audio_filters(mpctx);
|
||||
}
|
||||
|
||||
void reset_audio_state(struct MPContext *mpctx)
|
||||
@@ -153,6 +163,8 @@ void reset_audio_state(struct MPContext *mpctx)
|
||||
mp_audio_buffer_clear(mpctx->ao_buffer);
|
||||
mpctx->audio_status = mpctx->d_audio ? STATUS_SYNCING : STATUS_EOF;
|
||||
mpctx->delay = 0;
|
||||
mpctx->audio_drop_throttle = 0;
|
||||
mpctx->audio_stat_start = 0;
|
||||
}
|
||||
|
||||
void uninit_audio_out(struct MPContext *mpctx)
|
||||
@@ -260,11 +272,15 @@ void reinit_audio_chain(struct MPContext *mpctx)
|
||||
}
|
||||
|
||||
if (!mpctx->ao) {
|
||||
bool spdif_fallback = af_fmt_is_spdif(afs->output.format) &&
|
||||
mpctx->d_audio->spdif_passthrough;
|
||||
bool ao_null_fallback = opts->ao_null_fallback && !spdif_fallback;
|
||||
|
||||
mp_chmap_remove_useless_channels(&afs->output.channels,
|
||||
&opts->audio_output_channels);
|
||||
mp_audio_set_channels(&afs->output, &afs->output.channels);
|
||||
|
||||
mpctx->ao = ao_init_best(mpctx->global, mpctx->input,
|
||||
mpctx->ao = ao_init_best(mpctx->global, ao_null_fallback, mpctx->input,
|
||||
mpctx->encode_lavc_ctx, afs->output.rate,
|
||||
afs->output.format, afs->output.channels);
|
||||
|
||||
@@ -283,10 +299,9 @@ void reinit_audio_chain(struct MPContext *mpctx)
|
||||
|
||||
if (!mpctx->ao) {
|
||||
// If spdif was used, try to fallback to PCM.
|
||||
if (af_fmt_is_spdif(afs->output.format) &&
|
||||
mpctx->d_audio->spdif_passthrough)
|
||||
{
|
||||
if (spdif_fallback) {
|
||||
mpctx->d_audio->spdif_passthrough = false;
|
||||
mpctx->d_audio->spdif_failed = true;
|
||||
if (!audio_init_best_codec(mpctx->d_audio))
|
||||
goto init_error;
|
||||
reset_audio_state(mpctx);
|
||||
@@ -369,8 +384,7 @@ double written_audio_pts(struct MPContext *mpctx)
|
||||
// to get the length in original units without speedup or slowdown
|
||||
a_pts -= buffered_output * mpctx->audio_speed;
|
||||
|
||||
return a_pts +
|
||||
get_track_video_offset(mpctx, mpctx->current_track[0][STREAM_AUDIO]);
|
||||
return a_pts;
|
||||
}
|
||||
|
||||
// Return pts value corresponding to currently playing audio.
|
||||
@@ -400,11 +414,31 @@ static int write_to_ao(struct MPContext *mpctx, struct mp_audio *data, int flags
|
||||
if (played > 0) {
|
||||
mpctx->shown_aframes += played;
|
||||
mpctx->delay += played / real_samplerate;
|
||||
mpctx->written_audio += played / (double)out_format.rate;
|
||||
return played;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dump_audio_stats(struct MPContext *mpctx)
|
||||
{
|
||||
if (!mp_msg_test(mpctx->log, MSGL_STATS))
|
||||
return;
|
||||
if (mpctx->audio_status != STATUS_PLAYING || !mpctx->ao || mpctx->paused) {
|
||||
mpctx->audio_stat_start = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
double delay = ao_get_delay(mpctx->ao);
|
||||
if (!mpctx->audio_stat_start) {
|
||||
mpctx->audio_stat_start = mp_time_us();
|
||||
mpctx->written_audio = delay;
|
||||
}
|
||||
double current_audio = mpctx->written_audio - delay;
|
||||
double current_time = (mp_time_us() - mpctx->audio_stat_start) / 1e6;
|
||||
MP_STATS(mpctx, "value %f ao-dev", current_audio - current_time);
|
||||
}
|
||||
|
||||
// Return the number of samples that must be skipped or prepended to reach the
|
||||
// target audio pts after a seek (for A/V sync or hr-seek).
|
||||
// Return value (*skip):
|
||||
@@ -441,7 +475,7 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
|
||||
if (mpctx->video_status < STATUS_READY)
|
||||
return false; // wait until we know a video PTS
|
||||
if (mpctx->video_next_pts != MP_NOPTS_VALUE)
|
||||
sync_pts = mpctx->video_next_pts - (opts->audio_delay - mpctx->delay);
|
||||
sync_pts = mpctx->video_next_pts - opts->audio_delay;
|
||||
} else if (mpctx->hrseek_active) {
|
||||
sync_pts = mpctx->hrseek_pts;
|
||||
}
|
||||
@@ -459,7 +493,7 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
|
||||
}
|
||||
|
||||
int align = af_format_sample_alignment(out_format.format);
|
||||
*skip = (-ptsdiff * play_samplerate) / align * align;
|
||||
*skip = (int)(-ptsdiff * play_samplerate) / align * align;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -468,11 +502,23 @@ void fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
struct dec_audio *d_audio = mpctx->d_audio;
|
||||
|
||||
dump_audio_stats(mpctx);
|
||||
|
||||
if (mpctx->ao && ao_query_and_reset_events(mpctx->ao, AO_EVENT_RELOAD)) {
|
||||
ao_reset(mpctx->ao);
|
||||
uninit_audio_out(mpctx);
|
||||
if (d_audio)
|
||||
if (d_audio) {
|
||||
if (mpctx->d_audio->spdif_failed) {
|
||||
mpctx->d_audio->spdif_failed = false;
|
||||
mpctx->d_audio->spdif_passthrough = true;
|
||||
if (!audio_init_best_codec(mpctx->d_audio)) {
|
||||
MP_ERR(mpctx, "Error reinitializing audio.\n");
|
||||
error_on_track(mpctx, mpctx->current_track[0][STREAM_AUDIO]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mpctx->audio_status = STATUS_SYNCING;
|
||||
}
|
||||
}
|
||||
|
||||
if (!d_audio)
|
||||
@@ -500,6 +546,7 @@ void fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
struct mp_audio out_format = {0};
|
||||
ao_get_format(mpctx->ao, &out_format);
|
||||
double play_samplerate = out_format.rate / mpctx->audio_speed;
|
||||
int align = af_format_sample_alignment(out_format.format);
|
||||
|
||||
// If audio is infinitely fast, somehow try keeping approximate A/V sync.
|
||||
if (mpctx->audio_status == STATUS_PLAYING && ao_untimed(mpctx->ao) &&
|
||||
@@ -516,6 +563,26 @@ void fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
playsize = MPMAX(1, playsize + skip); // silence will be prepended
|
||||
}
|
||||
|
||||
int skip_duplicate = 0; // >0: skip, <0: duplicate
|
||||
double drop_limit =
|
||||
(opts->sync_max_audio_change + opts->sync_max_video_change) / 100;
|
||||
if (mpctx->display_sync_active && opts->video_sync == VS_DISP_ADROP &&
|
||||
fabs(mpctx->last_av_difference) >= opts->sync_audio_drop_size &&
|
||||
mpctx->audio_drop_throttle < drop_limit &&
|
||||
mpctx->audio_status == STATUS_PLAYING)
|
||||
{
|
||||
int samples = ceil(opts->sync_audio_drop_size * play_samplerate);
|
||||
samples = (samples + align / 2) / align * align;
|
||||
|
||||
skip_duplicate = mpctx->last_av_difference >= 0 ? -samples : samples;
|
||||
|
||||
playsize = MPMAX(playsize, samples);
|
||||
|
||||
mpctx->audio_drop_throttle += 1 - drop_limit - samples / play_samplerate;
|
||||
}
|
||||
|
||||
playsize = playsize / align * align;
|
||||
|
||||
int status = AD_OK;
|
||||
bool working = false;
|
||||
if (playsize > mp_audio_buffer_samples(mpctx->ao_buffer)) {
|
||||
@@ -563,6 +630,21 @@ void fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
end_sync = true;
|
||||
}
|
||||
|
||||
if (skip_duplicate) {
|
||||
int max = mp_audio_buffer_samples(mpctx->ao_buffer);
|
||||
if (abs(skip_duplicate) > max)
|
||||
skip_duplicate = skip_duplicate >= 0 ? max : -max;
|
||||
mpctx->last_av_difference += skip_duplicate / play_samplerate;
|
||||
if (skip_duplicate >= 0) {
|
||||
mp_audio_buffer_skip(mpctx->ao_buffer, skip_duplicate);
|
||||
MP_STATS(mpctx, "drop-audio");
|
||||
} else {
|
||||
mp_audio_buffer_duplicate(mpctx->ao_buffer, -skip_duplicate);
|
||||
MP_STATS(mpctx, "duplicate-audio");
|
||||
}
|
||||
MP_VERBOSE(mpctx, "audio skip_duplicate=%d\n", skip_duplicate);
|
||||
}
|
||||
|
||||
if (mpctx->audio_status == STATUS_SYNCING) {
|
||||
if (end_sync)
|
||||
mpctx->audio_status = STATUS_FILLING;
|
||||
@@ -592,7 +674,7 @@ void fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
double samples = (endpts - written_audio_pts(mpctx) - opts->audio_delay)
|
||||
* play_samplerate;
|
||||
if (playsize > samples) {
|
||||
playsize = MPMAX(samples, 0);
|
||||
playsize = MPMAX((int)samples / align * align, 0);
|
||||
audio_eof = true;
|
||||
partial_fill = true;
|
||||
}
|
||||
@@ -612,11 +694,18 @@ void fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
|
||||
struct mp_audio data;
|
||||
mp_audio_buffer_peek(mpctx->ao_buffer, &data);
|
||||
if (audio_eof || data.samples >= align)
|
||||
data.samples = data.samples / align * align;
|
||||
data.samples = MPMIN(data.samples, mpctx->paused ? 0 : playsize);
|
||||
int played = write_to_ao(mpctx, &data, playflags);
|
||||
assert(played >= 0 && played <= data.samples);
|
||||
mp_audio_buffer_skip(mpctx->ao_buffer, played);
|
||||
|
||||
mpctx->audio_drop_throttle =
|
||||
MPMAX(0, mpctx->audio_drop_throttle - played / play_samplerate);
|
||||
|
||||
dump_audio_stats(mpctx);
|
||||
|
||||
mpctx->audio_status = STATUS_PLAYING;
|
||||
if (audio_eof && !playsize) {
|
||||
mpctx->audio_status = STATUS_DRAINING;
|
||||
|
||||
+3
-1
@@ -711,6 +711,8 @@ int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable)
|
||||
{
|
||||
if (!mpv_event_name(event) || enable < 0 || enable > 1)
|
||||
return MPV_ERROR_INVALID_PARAMETER;
|
||||
if (event == MPV_EVENT_SHUTDOWN && !enable)
|
||||
return MPV_ERROR_INVALID_PARAMETER;
|
||||
assert(event < (int)INTERNAL_EVENT_BASE); // excluded above; they have no name
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
uint64_t bit = 1ULL << event;
|
||||
@@ -1601,7 +1603,7 @@ static const char *const err_table[] = {
|
||||
[-MPV_ERROR_LOADING_FAILED] = "loading failed",
|
||||
[-MPV_ERROR_AO_INIT_FAILED] = "audio output initialization failed",
|
||||
[-MPV_ERROR_VO_INIT_FAILED] = "audio output initialization failed",
|
||||
[-MPV_ERROR_NOTHING_TO_PLAY] = "no audio or video data found",
|
||||
[-MPV_ERROR_NOTHING_TO_PLAY] = "no audio or video data played",
|
||||
[-MPV_ERROR_UNKNOWN_FORMAT] = "unrecognized file format",
|
||||
[-MPV_ERROR_UNSUPPORTED] = "not supported",
|
||||
[-MPV_ERROR_NOT_IMPLEMENTED] = "operation not implemented",
|
||||
|
||||
+131
-37
@@ -484,6 +484,9 @@ static int mp_property_stream_end(void *ctx, struct m_property *prop,
|
||||
// Assumes prop is the type of the actual property.
|
||||
static int property_time(int action, void *arg, double time)
|
||||
{
|
||||
if (time == MP_NOPTS_VALUE)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
const struct m_option time_type = {.type = CONF_TYPE_TIME};
|
||||
switch (action) {
|
||||
case M_PROPERTY_GET:
|
||||
@@ -550,8 +553,6 @@ static int mp_property_total_avsync_change(void *ctx, struct m_property *prop,
|
||||
return m_property_double_ro(action, arg, mpctx->total_avsync_change);
|
||||
}
|
||||
|
||||
|
||||
/// Late frames
|
||||
static int mp_property_drop_frame_cnt(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
@@ -562,6 +563,38 @@ static int mp_property_drop_frame_cnt(void *ctx, struct m_property *prop,
|
||||
return m_property_int_ro(action, arg, mpctx->dropped_frames_total);
|
||||
}
|
||||
|
||||
static int mp_property_mistimed_frame_count(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->d_video || !mpctx->display_sync_active)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
return m_property_int_ro(action, arg, mpctx->mistimed_frames_total);
|
||||
}
|
||||
|
||||
static int mp_property_vsync_ratio(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->d_video || !mpctx->display_sync_active)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
int vsyncs = 0, frames = 0;
|
||||
for (int n = 0; n < mpctx->num_past_frames; n++) {
|
||||
int vsync = mpctx->past_frames[n].num_vsyncs;
|
||||
if (vsync < 0)
|
||||
break;
|
||||
vsyncs += vsync;
|
||||
frames += 1;
|
||||
}
|
||||
|
||||
if (!frames)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
return m_property_double_ro(action, arg, vsyncs / (double)frames);
|
||||
}
|
||||
|
||||
static int mp_property_vo_drop_frame_count(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
@@ -572,14 +605,14 @@ static int mp_property_vo_drop_frame_count(void *ctx, struct m_property *prop,
|
||||
return m_property_int_ro(action, arg, vo_get_drop_count(mpctx->video_out));
|
||||
}
|
||||
|
||||
static int mp_property_vo_missed_frame_count(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
static int mp_property_vo_delayed_frame_count(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->d_video)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
return m_property_int_ro(action, arg, vo_get_missed_count(mpctx->video_out));
|
||||
return m_property_int_ro(action, arg, vo_get_delayed_count(mpctx->video_out));
|
||||
}
|
||||
|
||||
/// Current position in percent (RW)
|
||||
@@ -625,11 +658,8 @@ static int mp_property_percent_pos(void *ctx, struct m_property *prop,
|
||||
static int mp_property_time_start(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
double start = get_start_time(mpctx);
|
||||
if (start < 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return property_time(action, arg, start);
|
||||
// minor backwards-compat.
|
||||
return property_time(action, arg, 0);
|
||||
}
|
||||
|
||||
/// Current position in seconds (RW)
|
||||
@@ -652,6 +682,9 @@ static bool time_remaining(MPContext *mpctx, double *remaining)
|
||||
double len = get_time_length(mpctx);
|
||||
double playback = get_playback_time(mpctx);
|
||||
|
||||
if (playback == MP_NOPTS_VALUE)
|
||||
return false;
|
||||
|
||||
*remaining = len - playback;
|
||||
|
||||
return len >= 0;
|
||||
@@ -687,8 +720,7 @@ static int mp_property_playback_time(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
if (action == M_PROPERTY_SET) {
|
||||
double target = get_start_time(mpctx) + *(double *)arg;
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, target, MPSEEK_DEFAULT, true);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, MPSEEK_DEFAULT, true);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return property_time(action, arg, get_playback_time(mpctx));
|
||||
@@ -1611,7 +1643,7 @@ static int mp_property_audio_device(void *ctx, struct m_property *prop,
|
||||
struct ao_device_list *list = ao_hotplug_get_device_list(cmd->hotplug);
|
||||
for (int n = 0; n < list->num_devices; n++) {
|
||||
struct ao_device_desc *dev = &list->devices[n];
|
||||
if (dev->name && strcmp(dev->name, mpctx->opts->audio_device)) {
|
||||
if (dev->name && strcmp(dev->name, mpctx->opts->audio_device) == 0) {
|
||||
*(char **)arg = talloc_strdup(NULL, dev->desc ? dev->desc : "?");
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
@@ -2370,18 +2402,19 @@ static int mp_property_framedrop(void *ctx, struct m_property *prop,
|
||||
static int mp_property_video_color(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
const char *name = prop->priv ? prop->priv : prop->name;
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->d_video)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_SET: {
|
||||
if (video_set_colors(mpctx->d_video, prop->name, *(int *) arg) <= 0)
|
||||
if (video_set_colors(mpctx->d_video, name, *(int *) arg) <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
break;
|
||||
}
|
||||
case M_PROPERTY_GET:
|
||||
if (video_get_colors(mpctx->d_video, prop->name, (int *)arg) <= 0)
|
||||
if (video_get_colors(mpctx->d_video, name, (int *)arg) <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
// Write new value to option variable
|
||||
mp_property_generic_option(mpctx, prop, M_PROPERTY_SET, arg);
|
||||
@@ -2440,8 +2473,6 @@ static int property_imgparams(struct mp_image_params p, int action, void *arg)
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_names, p.colorspace))},
|
||||
{"colorlevels",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_levels_names, p.colorlevels))},
|
||||
{"outputlevels",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_levels_names, p.outputlevels))},
|
||||
{"primaries",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_prim_names, p.primaries))},
|
||||
{"gamma",
|
||||
@@ -2497,6 +2528,30 @@ static int mp_property_vd_imgparams(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
}
|
||||
|
||||
static int mp_property_video_frame_info(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct mp_image *f =
|
||||
mpctx->video_out ? vo_get_current_frame(mpctx->video_out) : NULL;
|
||||
if (!f)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
const char *pict_types[] = {0, "I", "P", "B"};
|
||||
const char *pict_type = f->pict_type >= 1 && f->pict_type <= 3
|
||||
? pict_types[f->pict_type] : NULL;
|
||||
|
||||
struct m_sub_property props[] = {
|
||||
{"picture-type", SUB_PROP_STR(pict_type), .unavailable = !pict_type},
|
||||
{"interlaced", SUB_PROP_FLAG(!!(f->fields & MP_IMGFIELD_INTERLACED))},
|
||||
{"tff", SUB_PROP_FLAG(!!(f->fields & MP_IMGFIELD_TOP_FIRST))},
|
||||
{"repeat", SUB_PROP_FLAG(!!(f->fields & MP_IMGFIELD_REPEAT_FIRST))},
|
||||
{0}
|
||||
};
|
||||
|
||||
return m_property_read_sub(props, action, arg);
|
||||
}
|
||||
|
||||
static int mp_property_window_scale(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
@@ -2559,17 +2614,45 @@ static int mp_property_win_minimized(void *ctx, struct m_property *prop,
|
||||
|
||||
static int mp_property_display_fps(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
double fps = mpctx->opts->frame_drop_fps;
|
||||
struct vo *vo = mpctx->video_out;
|
||||
if (vo)
|
||||
fps = vo_get_display_fps(vo);
|
||||
if (action == M_PROPERTY_SET) {
|
||||
int ret = mp_property_generic_option(mpctx, prop, action, arg);
|
||||
if (vo)
|
||||
vo_event(vo, VO_EVENT_WIN_STATE);
|
||||
return ret;
|
||||
}
|
||||
return m_property_double_ro(action, arg, fps);
|
||||
}
|
||||
|
||||
static int mp_property_estimated_display_fps(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct vo *vo = mpctx->video_out;
|
||||
if (!vo)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
double fps = vo_get_display_fps(vo);
|
||||
if (fps < 1)
|
||||
double interval = vo_get_estimated_vsync_interval(vo);
|
||||
if (interval <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return m_property_double_ro(action, arg, 1.0 / interval);
|
||||
}
|
||||
|
||||
return m_property_double_ro(action, arg, fps);
|
||||
static int mp_property_vsync_jitter(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct vo *vo = mpctx->video_out;
|
||||
if (!vo)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
double stddev = vo_get_estimated_vsync_jitter(vo);
|
||||
if (stddev < 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return m_property_double_ro(action, arg, stddev);
|
||||
}
|
||||
|
||||
static int mp_property_display_names(void *ctx, struct m_property *prop,
|
||||
@@ -2672,10 +2755,10 @@ static int mp_property_vf_fps(void *ctx, struct m_property *prop,
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->d_video)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
double res = stabilize_frame_duration(mpctx, false);
|
||||
if (res <= 0)
|
||||
double avg = calc_average_frame_duration(mpctx);
|
||||
if (avg <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return m_property_double_ro(action, arg, 1 / res);
|
||||
return m_property_double_ro(action, arg, 1.0 / avg);
|
||||
}
|
||||
|
||||
/// Video aspect (RO)
|
||||
@@ -3082,11 +3165,18 @@ static int mp_property_packet_bitrate(void *ctx, struct m_property *prop,
|
||||
int type = (uintptr_t)prop->priv & ~0x100;
|
||||
bool old = (uintptr_t)prop->priv & 0x100;
|
||||
|
||||
if (!mpctx->demuxer)
|
||||
struct demuxer *demuxer = NULL;
|
||||
if (mpctx->current_track[0][type])
|
||||
demuxer = mpctx->current_track[0][type]->demuxer;
|
||||
if (!demuxer)
|
||||
demuxer = mpctx->demuxer;
|
||||
if (!demuxer)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
double r[STREAM_TYPE_COUNT];
|
||||
if (demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_BITRATE_STATS, &r) < 1)
|
||||
if (demux_control(demuxer, DEMUXER_CTRL_GET_BITRATE_STATS, &r) < 1)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
if (r[type] < 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
// r[type] is in bytes/second -> bits
|
||||
@@ -3371,8 +3461,10 @@ static const struct m_property mp_properties[] = {
|
||||
{"avsync", mp_property_avsync},
|
||||
{"total-avsync-change", mp_property_total_avsync_change},
|
||||
{"drop-frame-count", mp_property_drop_frame_cnt},
|
||||
{"mistimed-frame-count", mp_property_mistimed_frame_count},
|
||||
{"vsync-ratio", mp_property_vsync_ratio},
|
||||
{"vo-drop-frame-count", mp_property_vo_drop_frame_count},
|
||||
{"vo-missed-frame-count", mp_property_vo_missed_frame_count},
|
||||
{"vo-delayed-frame-count", mp_property_vo_delayed_frame_count},
|
||||
{"percent-pos", mp_property_percent_pos},
|
||||
{"time-start", mp_property_time_start},
|
||||
{"time-pos", mp_property_time_pos},
|
||||
@@ -3406,7 +3498,6 @@ static const struct m_property mp_properties[] = {
|
||||
{"demuxer-cache-idle", mp_property_demuxer_cache_idle},
|
||||
{"cache-buffering-state", mp_property_cache_buffering},
|
||||
{"paused-for-cache", mp_property_paused_for_cache},
|
||||
{"pts-association-mode", mp_property_generic_option},
|
||||
{"hr-seek", mp_property_generic_option},
|
||||
{"clock", mp_property_clock},
|
||||
{"seekable", mp_property_seekable},
|
||||
@@ -3453,6 +3544,8 @@ static const struct m_property mp_properties[] = {
|
||||
{"contrast", mp_property_video_color},
|
||||
{"saturation", mp_property_video_color},
|
||||
{"hue", mp_property_video_color},
|
||||
{"video-output-levels", mp_property_video_color,
|
||||
.priv = (void *)"output-levels"},
|
||||
{"panscan", panscan_property_helper},
|
||||
{"video-zoom", panscan_property_helper},
|
||||
{"video-align-x", panscan_property_helper},
|
||||
@@ -3463,6 +3556,7 @@ static const struct m_property mp_properties[] = {
|
||||
{"video-out-params", mp_property_vo_imgparams},
|
||||
{"video-params", mp_property_vd_imgparams},
|
||||
{"video-format", mp_property_video_format},
|
||||
{"video-frame-info", mp_property_video_frame_info},
|
||||
{"video-codec", mp_property_video_codec},
|
||||
M_PROPERTY_ALIAS("dwidth", "video-out-params/dw"),
|
||||
M_PROPERTY_ALIAS("dheight", "video-out-params/dh"),
|
||||
@@ -3544,6 +3638,8 @@ static const struct m_property mp_properties[] = {
|
||||
{"window-minimized", mp_property_win_minimized},
|
||||
{"display-names", mp_property_display_names},
|
||||
{"display-fps", mp_property_display_fps},
|
||||
{"estimated-display-fps", mp_property_estimated_display_fps},
|
||||
{"vsync-jitter", mp_property_vsync_jitter},
|
||||
|
||||
{"working-directory", mp_property_cwd},
|
||||
|
||||
@@ -3563,7 +3659,6 @@ static const struct m_property mp_properties[] = {
|
||||
M_PROPERTY_ALIAS("sub", "sid"),
|
||||
M_PROPERTY_ALIAS("colormatrix", "video-params/colormatrix"),
|
||||
M_PROPERTY_ALIAS("colormatrix-input-range", "video-params/colorlevels"),
|
||||
M_PROPERTY_ALIAS("colormatrix-output-range", "video-params/outputlevels"),
|
||||
M_PROPERTY_ALIAS("colormatrix-primaries", "video-params/primaries"),
|
||||
M_PROPERTY_ALIAS("colormatrix-gamma", "video-params/gamma"),
|
||||
|
||||
@@ -3589,7 +3684,8 @@ static const char *const *const mp_event_property_change[] = {
|
||||
"percent-pos", "time-remaining", "playtime-remaining", "playback-time",
|
||||
"estimated-vf-fps", "drop-frame-count", "vo-drop-frame-count",
|
||||
"total-avsync-change", "audio-speed-correction", "video-speed-correction",
|
||||
"vo-missed-frame-count"),
|
||||
"vo-delayed-frame-count", "mistimed-frame-count", "vsync-ratio",
|
||||
"estimated-display-fps", "vsync-jitter"),
|
||||
E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params",
|
||||
"video-format", "video-codec", "video-bitrate", "dwidth", "dheight",
|
||||
"width", "height", "fps", "aspect", "vo-configured", "current-vo",
|
||||
@@ -3734,7 +3830,6 @@ static const struct property_osd_display {
|
||||
{ "chapter", .seek_msg = OSD_SEEK_INFO_CHAPTER_TEXT,
|
||||
.seek_bar = OSD_SEEK_INFO_BAR },
|
||||
{ "edition", .seek_msg = OSD_SEEK_INFO_EDITION },
|
||||
{ "pts-association-mode", "PTS association mode" },
|
||||
{ "hr-seek", "hr-seek" },
|
||||
{ "speed", "Speed" },
|
||||
{ "clock", "Clock" },
|
||||
@@ -4431,14 +4526,13 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
case MP_CMD_SUB_SEEK: {
|
||||
if (!mpctx->playback_initialized)
|
||||
return -1;
|
||||
struct osd_sub_state state;
|
||||
update_osd_sub_state(mpctx, 0, &state);
|
||||
struct dec_sub *sub = mpctx->d_sub[0];
|
||||
double refpts = get_current_time(mpctx);
|
||||
if (state.dec_sub && refpts != MP_NOPTS_VALUE) {
|
||||
if (sub && refpts != MP_NOPTS_VALUE) {
|
||||
double a[2];
|
||||
a[0] = refpts - state.video_offset - opts->sub_delay;
|
||||
a[0] = refpts - opts->sub_delay;
|
||||
a[1] = cmd->args[0].v.i;
|
||||
if (sub_control(state.dec_sub, SD_CTRL_SUB_STEP, a) > 0) {
|
||||
if (sub_control(sub, SD_CTRL_SUB_STEP, a) > 0) {
|
||||
if (cmd->id == MP_CMD_SUB_STEP) {
|
||||
opts->sub_delay -= a[0];
|
||||
osd_changed_all(mpctx->osd);
|
||||
@@ -4590,7 +4684,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
|
||||
case MP_CMD_STOP:
|
||||
playlist_clear(mpctx->playlist);
|
||||
if (!mpctx->stop_play)
|
||||
if (mpctx->stop_play != PT_QUIT)
|
||||
mpctx->stop_play = PT_STOP;
|
||||
break;
|
||||
|
||||
|
||||
@@ -286,10 +286,6 @@ void mp_write_watch_later_conf(struct MPContext *mpctx)
|
||||
goto exit;
|
||||
}
|
||||
|
||||
double pos = get_current_time(mpctx);
|
||||
if (pos == MP_NOPTS_VALUE)
|
||||
goto exit;
|
||||
|
||||
mp_mk_config_dir(mpctx->global, MP_WATCH_LATER_CONF);
|
||||
|
||||
conffile = mp_get_playback_resume_config_filename(mpctx, filename);
|
||||
@@ -307,7 +303,9 @@ void mp_write_watch_later_conf(struct MPContext *mpctx)
|
||||
write_name[n] = (unsigned char)filename[n] < 32 ? '_' : filename[n];
|
||||
fprintf(file, "# %s\n", write_name);
|
||||
}
|
||||
fprintf(file, "start=%f\n", pos);
|
||||
double pos = get_current_time(mpctx);
|
||||
if (pos != MP_NOPTS_VALUE)
|
||||
fprintf(file, "start=%f\n", pos);
|
||||
for (int i = 0; backup_properties[i]; i++) {
|
||||
const char *pname = backup_properties[i];
|
||||
char *val = NULL;
|
||||
|
||||
+28
-15
@@ -76,14 +76,12 @@ enum seek_precision {
|
||||
MPSEEK_VERY_EXACT,
|
||||
};
|
||||
|
||||
// Comes from the assumption that some formats round timestamps to ms.
|
||||
#define FRAME_DURATION_TOLERANCE 0.0011
|
||||
|
||||
enum video_sync {
|
||||
VS_DEFAULT = 0,
|
||||
VS_DISP_RESAMPLE,
|
||||
VS_DISP_RESAMPLE_VDROP,
|
||||
VS_DISP_RESAMPLE_NONE,
|
||||
VS_DISP_ADROP,
|
||||
VS_DISP_VDROP,
|
||||
VS_DISP_NONE,
|
||||
VS_NONE,
|
||||
@@ -92,9 +90,19 @@ enum video_sync {
|
||||
#define VS_IS_DISP(x) ((x) == VS_DISP_RESAMPLE || \
|
||||
(x) == VS_DISP_RESAMPLE_VDROP || \
|
||||
(x) == VS_DISP_RESAMPLE_NONE || \
|
||||
(x) == VS_DISP_ADROP || \
|
||||
(x) == VS_DISP_VDROP || \
|
||||
(x) == VS_DISP_NONE)
|
||||
|
||||
// Information about past video frames that have been sent to the VO.
|
||||
struct frame_info {
|
||||
double pts;
|
||||
double duration; // PTS difference to next frame
|
||||
double approx_duration; // possibly fixed/smoothed out duration
|
||||
double av_diff; // A/V diff at time of scheduling
|
||||
int num_vsyncs; // scheduled vsyncs, if using display-sync
|
||||
};
|
||||
|
||||
struct track {
|
||||
enum stream_type type;
|
||||
|
||||
@@ -173,6 +181,7 @@ typedef struct MPContext {
|
||||
char *term_osd_subs;
|
||||
char *term_osd_contents;
|
||||
char *last_window_title;
|
||||
struct voctrl_playback_state vo_playback_state;
|
||||
|
||||
int add_osd_seek_info; // bitfield of enum mp_osd_seek_info
|
||||
double osd_visible; // for the osd bar only
|
||||
@@ -217,7 +226,6 @@ typedef struct MPContext {
|
||||
int timeline_part;
|
||||
struct demux_chapter *chapters;
|
||||
int num_chapters;
|
||||
double video_offset;
|
||||
|
||||
struct demuxer *demuxer; // can change with timeline
|
||||
struct mp_tags *filtered_tags;
|
||||
@@ -261,12 +269,13 @@ typedef struct MPContext {
|
||||
// update_playback_speed() updates them from the other fields.
|
||||
double audio_speed, video_speed;
|
||||
bool display_sync_active;
|
||||
bool broken_fps_header;
|
||||
double display_sync_frameduration;
|
||||
bool display_sync_broken;
|
||||
int display_sync_drift_dir;
|
||||
// Timing error (in seconds) due to rounding on vsync boundaries
|
||||
double display_sync_error;
|
||||
int display_sync_disable_counter;
|
||||
double audio_drop_throttle;
|
||||
// Number of mistimed frames.
|
||||
int mistimed_frames_total;
|
||||
/* Set if audio should be timed to start with video frame after seeking,
|
||||
* not set when e.g. playing cover art */
|
||||
bool sync_audio_to_video;
|
||||
@@ -302,6 +311,9 @@ typedef struct MPContext {
|
||||
double last_vo_pts;
|
||||
// Video PTS, or audio PTS if video has ended.
|
||||
double playback_pts;
|
||||
// audio stats only
|
||||
int64_t audio_stat_start;
|
||||
double written_audio;
|
||||
|
||||
int last_chapter;
|
||||
|
||||
@@ -313,6 +325,10 @@ typedef struct MPContext {
|
||||
uint64_t vo_pts_history_seek_ts;
|
||||
uint64_t backstep_start_seek_ts;
|
||||
bool backstep_active;
|
||||
// Past timestamps etc. (stupidly duplicated with vo_pts_history).
|
||||
// The newest frame is at index 0.
|
||||
struct frame_info *past_frames;
|
||||
int num_past_frames;
|
||||
|
||||
double next_heartbeat;
|
||||
double last_idle_tick;
|
||||
@@ -415,7 +431,8 @@ void mp_switch_track_n(struct MPContext *mpctx, int order,
|
||||
void mp_deselect_track(struct MPContext *mpctx, struct track *track);
|
||||
struct track *mp_track_by_tid(struct MPContext *mpctx, enum stream_type type,
|
||||
int tid);
|
||||
double timeline_set_from_time(struct MPContext *mpctx, double pts, bool *need_reset);
|
||||
bool timeline_switch_to_time(struct MPContext *mpctx, double pts);
|
||||
int timeline_get_for_time(struct MPContext *mpctx, double pts);
|
||||
void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer);
|
||||
bool mp_remove_track(struct MPContext *mpctx, struct track *track);
|
||||
struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction,
|
||||
@@ -438,15 +455,14 @@ void mp_print_version(struct mp_log *log, int always);
|
||||
void wakeup_playloop(void *ctx);
|
||||
|
||||
// misc.c
|
||||
double get_start_time(struct MPContext *mpctx);
|
||||
double get_main_demux_pts(struct MPContext *mpctx);
|
||||
double get_track_video_offset(struct MPContext *mpctx, struct track *track);
|
||||
double rel_time_to_abs(struct MPContext *mpctx, struct m_rel_time t);
|
||||
double get_play_end_pts(struct MPContext *mpctx);
|
||||
double get_relative_time(struct MPContext *mpctx);
|
||||
void merge_playlist_files(struct playlist *pl);
|
||||
float mp_get_cache_percent(struct MPContext *mpctx);
|
||||
bool mp_get_cache_idle(struct MPContext *mpctx);
|
||||
void update_vo_playback_state(struct MPContext *mpctx);
|
||||
void update_window_title(struct MPContext *mpctx, bool force);
|
||||
void error_on_track(struct MPContext *mpctx, struct track *track);
|
||||
int stream_dump(struct MPContext *mpctx, const char *source_filename);
|
||||
@@ -460,7 +476,7 @@ void set_osd_bar(struct MPContext *mpctx, int type,
|
||||
bool set_osd_msg(struct MPContext *mpctx, int level, int time,
|
||||
const char* fmt, ...) PRINTF_ATTRIBUTE(4,5);
|
||||
void set_osd_function(struct MPContext *mpctx, int osd_function);
|
||||
void set_osd_subtitle(struct MPContext *mpctx, const char *text);
|
||||
void term_osd_set_subs(struct MPContext *mpctx, const char *text);
|
||||
void get_current_osd_sym(struct MPContext *mpctx, char *buf, size_t buf_size);
|
||||
void set_osd_bar_chapters(struct MPContext *mpctx, int type);
|
||||
|
||||
@@ -490,7 +506,6 @@ void mp_idle(struct MPContext *mpctx);
|
||||
void idle_loop(struct MPContext *mpctx);
|
||||
int handle_force_window(struct MPContext *mpctx, bool force);
|
||||
void add_frame_pts(struct MPContext *mpctx, double pts);
|
||||
int get_past_frame_durations(struct MPContext *mpctx, double *fd, int num);
|
||||
void seek_to_last_frame(struct MPContext *mpctx);
|
||||
|
||||
// scripting.c
|
||||
@@ -509,8 +524,6 @@ void uninit_sub_all(struct MPContext *mpctx);
|
||||
void update_osd_msg(struct MPContext *mpctx);
|
||||
void update_subtitles(struct MPContext *mpctx);
|
||||
void uninit_sub_renderer(struct MPContext *mpctx);
|
||||
void update_osd_sub_state(struct MPContext *mpctx, int order,
|
||||
struct osd_sub_state *out_state);
|
||||
|
||||
// video.c
|
||||
void reset_video_state(struct MPContext *mpctx);
|
||||
@@ -520,6 +533,6 @@ void write_video(struct MPContext *mpctx, double endpts);
|
||||
void mp_force_video_refresh(struct MPContext *mpctx);
|
||||
void uninit_video_out(struct MPContext *mpctx);
|
||||
void uninit_video_chain(struct MPContext *mpctx);
|
||||
double stabilize_frame_duration(struct MPContext *mpctx, bool require_exact);
|
||||
double calc_average_frame_duration(struct MPContext *mpctx);
|
||||
|
||||
#endif /* MPLAYER_MP_CORE_H */
|
||||
+6
-21
@@ -19,7 +19,8 @@ static const char *const sub_exts[] = {"utf", "utf8", "utf-8", "idx", "sub", "sr
|
||||
"js", "ass", "mks", "vtt", "sup", NULL};
|
||||
|
||||
static const char *const audio_exts[] = {"mp3", "aac", "mka", "dts", "flac",
|
||||
"ogg", "m4a", "ac3", "opus", NULL};
|
||||
"ogg", "m4a", "ac3", "opus", "wav",
|
||||
NULL};
|
||||
|
||||
static bool test_ext_list(bstr ext, const char *const *list)
|
||||
{
|
||||
@@ -39,25 +40,9 @@ static int test_ext(bstr ext)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static struct bstr strip_ext(struct bstr str)
|
||||
{
|
||||
int dotpos = bstrrchr(str, '.');
|
||||
if (dotpos < 0)
|
||||
return str;
|
||||
return (struct bstr){str.start, dotpos};
|
||||
}
|
||||
|
||||
static struct bstr get_ext(struct bstr s)
|
||||
{
|
||||
int dotpos = bstrrchr(s, '.');
|
||||
if (dotpos < 0)
|
||||
return (struct bstr){NULL, 0};
|
||||
return bstr_splice(s, dotpos + 1, s.len);
|
||||
}
|
||||
|
||||
bool mp_might_be_subtitle_file(const char *filename)
|
||||
{
|
||||
return test_ext(get_ext(bstr0(filename))) == STREAM_SUB;
|
||||
return test_ext(bstr_get_ext(bstr0(filename))) == STREAM_SUB;
|
||||
}
|
||||
|
||||
static int compare_sub_filename(const void *a, const void *b)
|
||||
@@ -112,7 +97,7 @@ static void append_dir_subtitles(struct mpv_global *global,
|
||||
goto out;
|
||||
|
||||
struct bstr f_fname = bstr0(mp_basename(fname));
|
||||
struct bstr f_fname_noext = bstrdup(tmpmem, strip_ext(f_fname));
|
||||
struct bstr f_fname_noext = bstrdup(tmpmem, bstr_strip_ext(f_fname));
|
||||
bstr_lower(f_fname_noext);
|
||||
struct bstr f_fname_trim = bstr_strip(f_fname_noext);
|
||||
|
||||
@@ -131,9 +116,9 @@ static void append_dir_subtitles(struct mpv_global *global,
|
||||
void *tmpmem2 = talloc_new(tmpmem);
|
||||
|
||||
// retrieve various parts of the filename
|
||||
struct bstr tmp_fname_noext = bstrdup(tmpmem2, strip_ext(dename));
|
||||
struct bstr tmp_fname_noext = bstrdup(tmpmem2, bstr_strip_ext(dename));
|
||||
bstr_lower(tmp_fname_noext);
|
||||
struct bstr tmp_fname_ext = get_ext(dename);
|
||||
struct bstr tmp_fname_ext = bstr_get_ext(dename);
|
||||
struct bstr tmp_fname_trim = bstr_strip(tmp_fname_noext);
|
||||
|
||||
// check what it is (most likely)
|
||||
|
||||
+38
-37
@@ -96,8 +96,6 @@ static void uninit_demuxer(struct MPContext *mpctx)
|
||||
talloc_free(mpctx->sources);
|
||||
mpctx->sources = NULL;
|
||||
mpctx->num_sources = 0;
|
||||
|
||||
mpctx->video_offset = 0;
|
||||
}
|
||||
|
||||
static void uninit_stream(struct MPContext *mpctx)
|
||||
@@ -198,10 +196,12 @@ void update_demuxer_properties(struct MPContext *mpctx)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
struct mp_log *log = mp_log_new(NULL, mpctx->log, "!display-tags");
|
||||
if (!had_output)
|
||||
MP_INFO(mpctx, "File tags:\n");
|
||||
MP_INFO(mpctx, " %s: %s\n", info->keys[n], info->values[n]);
|
||||
mp_info(log, "File tags:\n");
|
||||
mp_info(log, " %s: %s\n", info->keys[n], info->values[n]);
|
||||
had_output = true;
|
||||
talloc_free(log);
|
||||
}
|
||||
talloc_free(mpctx->filtered_tags);
|
||||
mpctx->filtered_tags = info;
|
||||
@@ -286,15 +286,25 @@ static void enable_demux_thread(struct MPContext *mpctx)
|
||||
}
|
||||
}
|
||||
|
||||
static bool timeline_set_part(struct MPContext *mpctx, int i, bool initial)
|
||||
// Returns whether reinitialization is required (i.e. it switched to a new part)
|
||||
bool timeline_switch_to_time(struct MPContext *mpctx, double pts)
|
||||
{
|
||||
struct timeline_part *p = mpctx->timeline + mpctx->timeline_part;
|
||||
struct timeline_part *n = mpctx->timeline + i;
|
||||
mpctx->timeline_part = i;
|
||||
mpctx->video_offset = n->start - n->source_start;
|
||||
if (n->source == p->source && !initial)
|
||||
if (!mpctx->timeline)
|
||||
return false;
|
||||
|
||||
int new_part = mpctx->num_timeline_parts - 1;
|
||||
for (int i = 0; i < mpctx->num_timeline_parts; i++) {
|
||||
if (pts < mpctx->timeline[i + 1].start) {
|
||||
new_part = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mpctx->timeline_part == new_part)
|
||||
return false;
|
||||
mpctx->timeline_part = new_part;
|
||||
struct timeline_part *n = mpctx->timeline + mpctx->timeline_part;
|
||||
|
||||
uninit_audio_chain(mpctx);
|
||||
uninit_video_chain(mpctx);
|
||||
uninit_sub_all(mpctx);
|
||||
@@ -309,6 +319,7 @@ static bool timeline_set_part(struct MPContext *mpctx, int i, bool initial)
|
||||
}
|
||||
|
||||
mpctx->demuxer = n->source;
|
||||
demux_set_ts_offset(mpctx->demuxer, n->start - n->source_start);
|
||||
|
||||
// While another timeline was active, the selection of active tracks might
|
||||
// have been changed - possibly we need to update this source.
|
||||
@@ -328,7 +339,7 @@ static bool timeline_set_part(struct MPContext *mpctx, int i, bool initial)
|
||||
}
|
||||
}
|
||||
|
||||
if (!initial) {
|
||||
if (mpctx->playback_initialized) {
|
||||
reselect_demux_streams(mpctx);
|
||||
enable_demux_thread(mpctx);
|
||||
}
|
||||
@@ -336,25 +347,6 @@ static bool timeline_set_part(struct MPContext *mpctx, int i, bool initial)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Given pts, switch playback to the corresponding part.
|
||||
// Return offset within that part.
|
||||
double timeline_set_from_time(struct MPContext *mpctx, double pts, bool *need_reset)
|
||||
{
|
||||
if (pts < 0)
|
||||
pts = 0;
|
||||
|
||||
int new = mpctx->num_timeline_parts - 1;
|
||||
for (int i = 0; i < mpctx->num_timeline_parts; i++) {
|
||||
if (pts < mpctx->timeline[i + 1].start) {
|
||||
new = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*need_reset = timeline_set_part(mpctx, new, false);
|
||||
return pts - mpctx->timeline[new].start + mpctx->timeline[new].source_start;
|
||||
}
|
||||
|
||||
static int find_new_tid(struct MPContext *mpctx, enum stream_type t)
|
||||
{
|
||||
int new_id = 0;
|
||||
@@ -712,6 +704,9 @@ struct track *mp_add_external_file(struct MPContext *mpctx, char *filename,
|
||||
if (!demuxer)
|
||||
goto err_out;
|
||||
|
||||
if (filter != STREAM_SUB && opts->rebase_start_time)
|
||||
demux_set_ts_offset(demuxer, -demuxer->start_time);
|
||||
|
||||
struct track *first = NULL;
|
||||
for (int n = 0; n < demuxer->num_streams; n++) {
|
||||
struct sh_stream *sh = demuxer->streams[n];
|
||||
@@ -908,6 +903,10 @@ static void load_chapters(struct MPContext *mpctx)
|
||||
talloc_free(mpctx->chapters);
|
||||
mpctx->num_chapters = src->num_chapters;
|
||||
mpctx->chapters = demux_copy_chapter_data(src->chapters, src->num_chapters);
|
||||
if (mpctx->opts->rebase_start_time) {
|
||||
for (int n = 0; n < mpctx->num_chapters; n++)
|
||||
mpctx->chapters[n].pts -= src->start_time;
|
||||
}
|
||||
}
|
||||
if (free_src)
|
||||
free_demuxer_and_stream(src);
|
||||
@@ -952,8 +951,11 @@ static void open_demux_thread(void *pctx)
|
||||
args->err = MPV_ERROR_LOADING_FAILED;
|
||||
}
|
||||
}
|
||||
if (args->demux)
|
||||
if (args->demux) {
|
||||
args->tl = timeline_load(global, args->log, args->demux);
|
||||
if (global->opts->rebase_start_time)
|
||||
demux_set_ts_offset(args->demux, -args->demux->start_time);
|
||||
}
|
||||
}
|
||||
|
||||
static void open_demux_reentrant(struct MPContext *mpctx)
|
||||
@@ -1032,9 +1034,7 @@ static void play_current_file(struct MPContext *mpctx)
|
||||
mpctx->max_frames = -1;
|
||||
mpctx->video_speed = mpctx->audio_speed = opts->playback_speed;
|
||||
mpctx->speed_factor_a = mpctx->speed_factor_v = 1.0;
|
||||
mpctx->display_sync_frameduration = 0.0;
|
||||
mpctx->display_sync_error = 0.0;
|
||||
mpctx->broken_fps_header = false;
|
||||
mpctx->display_sync_active = false;
|
||||
mpctx->seek = (struct seek_params){ 0 };
|
||||
|
||||
@@ -1095,7 +1095,7 @@ reopen_file:
|
||||
}
|
||||
|
||||
open_demux_reentrant(mpctx);
|
||||
if (!mpctx->master_demuxer)
|
||||
if (!mpctx->master_demuxer || mpctx->stop_play)
|
||||
goto terminate_playback;
|
||||
mpctx->demuxer = mpctx->master_demuxer;
|
||||
|
||||
@@ -1120,9 +1120,8 @@ reopen_file:
|
||||
load_chapters(mpctx);
|
||||
add_demuxer_tracks(mpctx, mpctx->track_layout);
|
||||
|
||||
mpctx->timeline_part = 0;
|
||||
if (mpctx->timeline)
|
||||
timeline_set_part(mpctx, mpctx->timeline_part, true);
|
||||
mpctx->timeline_part = mpctx->num_timeline_parts;
|
||||
timeline_switch_to_time(mpctx, 0);
|
||||
|
||||
open_subtitles_from_options(mpctx);
|
||||
open_audiofiles_from_options(mpctx);
|
||||
@@ -1181,6 +1180,8 @@ reopen_file:
|
||||
goto terminate_playback;
|
||||
}
|
||||
|
||||
update_playback_speed(mpctx);
|
||||
|
||||
reinit_video_chain(mpctx);
|
||||
reinit_audio_chain(mpctx);
|
||||
reinit_subs(mpctx, 0);
|
||||
|
||||
+46
-57
@@ -3,8 +3,7 @@ local msg = require 'mp.msg'
|
||||
|
||||
local ytdl = {
|
||||
path = "youtube-dl",
|
||||
minver = "2015.02.23.1",
|
||||
vercheck = nil,
|
||||
searched = false
|
||||
}
|
||||
|
||||
local function exec(args)
|
||||
@@ -12,9 +11,9 @@ local function exec(args)
|
||||
return ret.status, ret.stdout, ret
|
||||
end
|
||||
|
||||
-- return if it was explicitly set on the command line
|
||||
-- return true if it was explicitly set on the command line
|
||||
local function option_was_set(name)
|
||||
return mp.get_property_bool("option-info/" .. name .. "/set-from-commandline",
|
||||
return mp.get_property_bool("option-info/" ..name.. "/set-from-commandline",
|
||||
false)
|
||||
end
|
||||
|
||||
@@ -51,41 +50,25 @@ local function append_rtmp_prop(props, name, value)
|
||||
return props..name.."=\""..value.."\""
|
||||
end
|
||||
|
||||
local function edl_escape(url)
|
||||
return "%" .. string.len(url) .. "%" .. url
|
||||
end
|
||||
|
||||
|
||||
mp.add_hook("on_load", 10, function ()
|
||||
local url = mp.get_property("stream-open-filename")
|
||||
|
||||
if (url:find("http://") == 1) or (url:find("https://") == 1)
|
||||
or (url:find("ytdl://") == 1) then
|
||||
|
||||
-- check version of youtube-dl if not done yet
|
||||
if (ytdl.vercheck == nil) then
|
||||
|
||||
-- check for youtube-dl in mpv's config dir
|
||||
-- check for youtube-dl in mpv's config dir
|
||||
if not (ytdl.searched) then
|
||||
local ytdl_mcd = mp.find_config_file("youtube-dl")
|
||||
if not (ytdl_mcd == nil) then
|
||||
msg.verbose("found youtube-dl at: " .. ytdl_mcd)
|
||||
ytdl.path = ytdl_mcd
|
||||
end
|
||||
|
||||
msg.debug("checking ytdl version ...")
|
||||
local es, version = exec({ytdl.path, "--version"})
|
||||
if (es < 0) then
|
||||
msg.warn("youtube-dl not found, not executable, or broken.")
|
||||
ytdl.vercheck = false
|
||||
elseif (version < ytdl.minver) then
|
||||
msg.verbose("found youtube-dl version: " .. version)
|
||||
msg.warn("Your version of youtube-dl is too old! "
|
||||
.. "You need at least version '"..ytdl.minver
|
||||
.. "', try running `youtube-dl -U`.")
|
||||
ytdl.vercheck = false
|
||||
else
|
||||
msg.verbose("found youtube-dl version: " .. version)
|
||||
ytdl.vercheck = true
|
||||
end
|
||||
end
|
||||
|
||||
if not (ytdl.vercheck) then
|
||||
return
|
||||
ytdl.searched = true
|
||||
end
|
||||
|
||||
-- strip ytdl://
|
||||
@@ -95,14 +78,18 @@ mp.add_hook("on_load", 10, function ()
|
||||
|
||||
local format = mp.get_property("options/ytdl-format")
|
||||
local raw_options = mp.get_property_native("options/ytdl-raw-options")
|
||||
local allsubs = true
|
||||
|
||||
local command = {
|
||||
ytdl.path, "--no-warnings", "-J", "--flat-playlist", "--all-subs",
|
||||
ytdl.path, "--no-warnings", "-J", "--flat-playlist",
|
||||
"--sub-format", "ass/srt/best", "--no-playlist"
|
||||
}
|
||||
|
||||
-- Checks if video option is "no", change options accordingly
|
||||
if (mp.get_property("options/vid") == "no") then
|
||||
-- Checks if video option is "no", change format accordingly,
|
||||
-- but only if user didn't explicitly set one
|
||||
if (mp.get_property("options/vid") == "no")
|
||||
and not option_was_set("ytdl-format") then
|
||||
|
||||
format = "bestaudio/best"
|
||||
msg.verbose("Video disabled. Only using audio")
|
||||
end
|
||||
@@ -117,6 +104,13 @@ mp.add_hook("on_load", 10, function ()
|
||||
if (arg ~= "") then
|
||||
table.insert(command, arg)
|
||||
end
|
||||
if (param == "sub-lang") and (arg ~= "") then
|
||||
allsubs = false
|
||||
end
|
||||
end
|
||||
|
||||
if (allsubs == true) then
|
||||
table.insert(command, "--all-subs")
|
||||
end
|
||||
table.insert(command, "--")
|
||||
table.insert(command, url)
|
||||
@@ -145,7 +139,8 @@ mp.add_hook("on_load", 10, function ()
|
||||
msg.verbose("Got direct URL")
|
||||
return
|
||||
elseif not (json["_type"] == nil)
|
||||
and ((json["_type"] == "playlist") or (json["_type"] == "multi_video")) then
|
||||
and ((json["_type"] == "playlist")
|
||||
or (json["_type"] == "multi_video")) then
|
||||
-- a playlist
|
||||
|
||||
if (#json.entries == 0) then
|
||||
@@ -159,13 +154,9 @@ mp.add_hook("on_load", 10, function ()
|
||||
and (json.entries[1]["webpage_url"] == json["webpage_url"]) then
|
||||
msg.verbose("multi-arc video detected, building EDL")
|
||||
|
||||
|
||||
local playlist = "edl://"
|
||||
for i, entry in pairs(json.entries) do
|
||||
|
||||
local urllength = string.len(entry.url)
|
||||
playlist = playlist .. "%" .. urllength .. "%" .. entry.url .. ";"
|
||||
|
||||
playlist = playlist .. edl_escape(entry.url) .. ";"
|
||||
end
|
||||
|
||||
msg.debug("EDL: " .. playlist)
|
||||
@@ -177,7 +168,8 @@ mp.add_hook("on_load", 10, function ()
|
||||
|
||||
mp.set_property("stream-open-filename", playlist)
|
||||
if not (json.title == nil) then
|
||||
mp.set_property("file-local-options/force-media-title", json.title)
|
||||
mp.set_property("file-local-options/force-media-title",
|
||||
json.title)
|
||||
end
|
||||
|
||||
else
|
||||
@@ -206,21 +198,13 @@ mp.add_hook("on_load", 10, function ()
|
||||
|
||||
-- DASH?
|
||||
if not (json["requested_formats"] == nil) then
|
||||
msg.info("Using DASH, expect inaccurate duration.")
|
||||
if not (json.duration == nil) then
|
||||
msg.info("Actual duration: " .. mp.format_time(json.duration))
|
||||
end
|
||||
|
||||
-- video url
|
||||
streamurl = json["requested_formats"][1].url
|
||||
|
||||
-- audio url
|
||||
mp.set_property("file-local-options/audio-file",
|
||||
json["requested_formats"][2].url)
|
||||
|
||||
-- workaround for slow startup (causes inaccurate duration)
|
||||
mp.set_property("file-local-options/demuxer-lavf-o",
|
||||
"fflags=+ignidx")
|
||||
mp.commandv("audio-add", json["requested_formats"][2].url,
|
||||
"select", json["requested_formats"][2]["format_note"])
|
||||
|
||||
elseif not (json.url == nil) then
|
||||
-- normal video
|
||||
@@ -251,7 +235,7 @@ mp.add_hook("on_load", 10, function ()
|
||||
end
|
||||
|
||||
if not (sub == nil) then
|
||||
mp.commandv("sub_add", sub,
|
||||
mp.commandv("sub-add", sub,
|
||||
"auto", sub_info.ext, lang)
|
||||
else
|
||||
msg.verbose("No subtitle data/url for ["..lang.."]")
|
||||
@@ -259,19 +243,24 @@ mp.add_hook("on_load", 10, function ()
|
||||
end
|
||||
end
|
||||
|
||||
-- set start and end time
|
||||
-- set start time
|
||||
if not (json.start_time == nil) then
|
||||
msg.debug("setting start to: " .. json.start_time .. " secs")
|
||||
mp.set_property("file-local-options/start",json.start_time)
|
||||
msg.debug("Setting start to: " .. json.start_time .. " secs")
|
||||
mp.set_property("file-local-options/start", json.start_time)
|
||||
end
|
||||
|
||||
-- for rtmp
|
||||
if not (json.play_path == nil) then
|
||||
local rtmp_prop = append_rtmp_prop(nil, "rtmp_tcurl", streamurl)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop, "rtmp_pageurl", json.page_url)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop, "rtmp_playpath", json.play_path)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop, "rtmp_swfverify", json.player_url)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop, "rtmp_app", json.app)
|
||||
local rtmp_prop = append_rtmp_prop(nil,
|
||||
"rtmp_tcurl", streamurl)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop,
|
||||
"rtmp_pageurl", json.page_url)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop,
|
||||
"rtmp_playpath", json.play_path)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop,
|
||||
"rtmp_swfverify", json.player_url)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop,
|
||||
"rtmp_app", json.app)
|
||||
|
||||
mp.set_property("file-local-options/stream-lavf-o", rtmp_prop)
|
||||
end
|
||||
|
||||
+2
-1
@@ -351,6 +351,7 @@ struct MPContext *mp_create(void)
|
||||
mpctx->mconfig->includefunc_ctx = mpctx;
|
||||
mpctx->mconfig->use_profiles = true;
|
||||
mpctx->mconfig->is_toplevel = true;
|
||||
mpctx->mconfig->global = mpctx->global;
|
||||
m_config_parse(mpctx->mconfig, "", bstr0(def_config), NULL, 0);
|
||||
|
||||
mpctx->global->opts = mpctx->opts;
|
||||
@@ -466,7 +467,7 @@ int mp_initialize(struct MPContext *mpctx, char **options)
|
||||
if (opts->consolecontrols && cas_terminal_owner(mpctx, mpctx))
|
||||
terminal_setup_getch(mpctx->input);
|
||||
|
||||
if (handle_force_window(mpctx, false) < 0)
|
||||
if (opts->force_vo == 2 && handle_force_window(mpctx, false) < 0)
|
||||
return -1;
|
||||
|
||||
#if !defined(__MINGW32__)
|
||||
|
||||
Loaded 100 of 221 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user