Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1d6257731 | ||
|
|
ba9b2f1e49 | ||
|
|
7b4ccb3e9f | ||
|
|
4195a345a5 | ||
|
|
004bc95263 | ||
|
|
24fc176dfb | ||
|
|
056901b2be | ||
|
|
20991a95b8 | ||
|
|
31adc87bde | ||
|
|
2d4e8b623d | ||
|
|
bc1dce5d5b | ||
|
|
0fad73862c | ||
|
|
49690dd54b | ||
|
|
c9204fe3a5 | ||
|
|
24e7fac50b | ||
|
|
554c65e466 | ||
|
|
8128b7c8e2 | ||
|
|
0c9b655477 | ||
|
|
9fb949b49a | ||
|
|
909b8f89d8 | ||
|
|
77bac5b550 | ||
|
|
64067a9b7c | ||
|
|
62acd315ad | ||
|
|
c55b242b4e | ||
|
|
1e46bda0f8 | ||
|
|
16723d4bb1 | ||
|
|
4c3f95708f | ||
|
|
9a88b118b4 | ||
|
|
785eacf4f3 | ||
|
|
5722f93a74 | ||
|
|
bf13bd0d47 | ||
|
|
dd973da108 | ||
|
|
cbda46370c | ||
|
|
a4cdf1a727 | ||
|
|
e420464ba6 | ||
|
|
6fc0fe4426 | ||
|
|
671df54e4d | ||
|
|
81f3b3aafe | ||
|
|
796d5266f1 | ||
|
|
7c804cd796 | ||
|
|
ea442fa047 | ||
|
|
0915a8497b | ||
|
|
8135838018 | ||
|
|
f54ae2031e | ||
|
|
453ea2cb6c | ||
|
|
9fee7077d4 | ||
|
|
30fd858e5e | ||
|
|
9acea8759b | ||
|
|
31a4547187 | ||
|
|
994459e4ce | ||
|
|
a1f949d3b8 | ||
|
|
459b40cc4b | ||
|
|
9628edede0 | ||
|
|
bd5a02d080 | ||
|
|
2a80680d95 | ||
|
|
8e87ddd7fe | ||
|
|
e4a1086cfb | ||
|
|
12b4f177ec | ||
|
|
6a73fcc9ed | ||
|
|
2dd8982f73 | ||
|
|
6eccd4a573 | ||
|
|
27bc881cd8 | ||
|
|
b0c1455aa4 | ||
|
|
e2d90b383b | ||
|
|
c19f634e6c | ||
|
|
82e81421d7 | ||
|
|
0d05038790 | ||
|
|
38636345b1 | ||
|
|
9604c45e24 | ||
|
|
e69d1f2780 | ||
|
|
e9af0592d9 | ||
|
|
ce60c1adf7 | ||
|
|
35f43dfacb | ||
|
|
b7e179f6d3 | ||
|
|
3909e4cdfc | ||
|
|
3e90a5fe81 | ||
|
|
27ccad541a | ||
|
|
bf611ff0f6 | ||
|
|
0c877d2fdc | ||
|
|
8368ead1fa | ||
|
|
d22d24a6d5 | ||
|
|
fb84c6974d | ||
|
|
92ded6c6fd | ||
|
|
3d03298e86 | ||
|
|
c1002f6a28 | ||
|
|
861c126b08 | ||
|
|
8fda7247ff | ||
|
|
172ce98433 | ||
|
|
481d15ae60 | ||
|
|
0b5af5639b | ||
|
|
0015afd059 | ||
|
|
d0cd7fa31b | ||
|
|
a655432bbe | ||
|
|
09f0f68959 | ||
|
|
dac5b598f5 | ||
|
|
cb8b0cc329 | ||
|
|
029e31f1c5 | ||
|
|
efb9943637 | ||
|
|
243a2976a8 | ||
|
|
c725f39bae | ||
|
|
1f38c2107d | ||
|
|
2f4bd58f4a | ||
|
|
fa315705be | ||
|
|
2c3f4850b8 | ||
|
|
13ade4f1bc | ||
|
|
9163bdc38a | ||
|
|
d5f29a3c36 | ||
|
|
de0523ba7b | ||
|
|
ad072b17ce | ||
|
|
2059ba2c40 | ||
|
|
492e3deb95 | ||
|
|
fb185e4e79 | ||
|
|
dcffeaccf1 | ||
|
|
b5d0195023 | ||
|
|
b47bf06f97 | ||
|
|
9b3daa4974 | ||
|
|
4c1deb680d | ||
|
|
97d50538cd | ||
|
|
1f62f38106 | ||
|
|
d85753b79e | ||
|
|
50c379e2d8 | ||
|
|
d9f8b9f7b2 | ||
|
|
d804e7b903 | ||
|
|
7df475f860 | ||
|
|
aa587580c8 | ||
|
|
4d43a0c997 | ||
|
|
b637d6366c | ||
|
|
05444e8135 | ||
|
|
190dea149a | ||
|
|
8d4a179c14 | ||
|
|
ce8524cb47 | ||
|
|
504286b006 | ||
|
|
fb8285c7a7 | ||
|
|
7dff6a2842 | ||
|
|
0710ced079 | ||
|
|
6d9cb89333 | ||
|
|
940007cbf9 | ||
|
|
bbb65ed84b | ||
|
|
30074f8440 | ||
|
|
a042dcc700 | ||
|
|
3973a953df | ||
|
|
082c23515f | ||
|
|
6bec6ac558 | ||
|
|
946bd52a1d | ||
|
|
25497226cf | ||
|
|
f9ba1a3ddf | ||
|
|
b0381d27eb | ||
|
|
3e1aed8f40 | ||
|
|
ff520054c8 | ||
|
|
1a6f3c56ea | ||
|
|
af958221a1 | ||
|
|
2037426a65 | ||
|
|
e8960c69e9 | ||
|
|
23a5329690 | ||
|
|
eac0665b8d | ||
|
|
3de6c9aa42 | ||
|
|
8673343957 | ||
|
|
d1119cc069 | ||
|
|
ad25de5903 | ||
|
|
f8b09658f4 | ||
|
|
d5274d9298 | ||
|
|
5afa68835a | ||
|
|
5360baa49e | ||
|
|
fccc3d3894 | ||
|
|
7fa06e46c4 | ||
|
|
0afb1acab3 | ||
|
|
05b6646d7a | ||
|
|
35296c1f33 | ||
|
|
3ae726e8dd | ||
|
|
c188240ab9 | ||
|
|
099fdde7a4 | ||
|
|
cbc951d491 | ||
|
|
fc7212b214 | ||
|
|
f2187de8bb | ||
|
|
31c29495ca | ||
|
|
a191712169 | ||
|
|
517a35da94 | ||
|
|
821e8fb9d0 | ||
|
|
4b81398b4e | ||
|
|
d1cbff37be | ||
|
|
aa5f04c7a0 | ||
|
|
e15526153e | ||
|
|
48b103cad7 | ||
|
|
7f313afd08 | ||
|
|
7558d1ed7b | ||
|
|
0563eb914f | ||
|
|
abbb43759a | ||
|
|
6205d87a7f | ||
|
|
491958a724 | ||
|
|
808653a9b6 | ||
|
|
d2013b3f50 | ||
|
|
5790bbba61 | ||
|
|
f1a2610c4f | ||
|
|
2da533bec3 | ||
|
|
127da1613f | ||
|
|
c01935986c | ||
|
|
0a0bb9059f | ||
|
|
1f7c099dc0 | ||
|
|
78bfee53dd | ||
|
|
cd24fdcd5a | ||
|
|
47f2f554a3 | ||
|
|
a0519f1d18 | ||
|
|
4cc1861378 | ||
|
|
6154c1d06d | ||
|
|
32cd85bc7e | ||
|
|
3394d37b4e | ||
|
|
d2baaaa7df | ||
|
|
fc9eef3b81 | ||
|
|
58d45f7b3e | ||
|
|
8a9cde4999 | ||
|
|
3925dff2e6 | ||
|
|
f1909bc231 | ||
|
|
5dd5dc66c1 | ||
|
|
00135a87f0 | ||
|
|
687b552db1 | ||
|
|
2c7db48195 | ||
|
|
7137afeb2c | ||
|
|
ad80cc4eee | ||
|
|
f36c17d715 | ||
|
|
04af005c35 | ||
|
|
74c11f0c84 | ||
|
|
e798cf1ff6 | ||
|
|
cab942acae | ||
|
|
c7985fe5f7 | ||
|
|
29226e6a99 | ||
|
|
d0fd68f6df | ||
|
|
8d0a6cd035 | ||
|
|
f24ba544cd | ||
|
|
000285ee8e | ||
|
|
67a4892ee3 | ||
|
|
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 |
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
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
Reporting bugs
|
||||
==============
|
||||
|
||||
- State your platform and mpv version.
|
||||
- Make sure you're actually using the latest mpv release. Linux distributions
|
||||
in particular love redistributing ancient and unmaintained mpv releases.
|
||||
- Attach a log file made with ``-v`` or ``--log-file=output.txt``.
|
||||
- Avoid attaching text files packed in archive files (zip/rar/...), unless
|
||||
explicitly asked so. There are better means, such as using http://sprunge.us
|
||||
- If you think this is file specific, upload a sample file and link it.
|
||||
- Don't report multiple unrelated/barely related bugs in one issue.
|
||||
- Read: https://mpv.io/bug-reports/
|
||||
|
||||
Asking questions
|
||||
================
|
||||
|
||||
Preferably, asking questions should be done via IRC (#mpv on irc.freenode.net),
|
||||
but asking questions by opening a github issue is also barely tolerated.
|
||||
|
||||
When asking on IRC (#mpv on irc.freenode.net), don't ask to ask, just state
|
||||
your problem. Stay long enough until someone gets a chance to reply. Sometimes
|
||||
it can take hours.
|
||||
|
||||
Sending patches
|
||||
===============
|
||||
|
||||
See: https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
|
||||
|
||||
In particular, be aware that all changes to GPL code must come with the
|
||||
implicit agreement that they might be LGPLv2.1+ relicensed at a later point.
|
||||
@@ -12,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.
|
||||
|
||||
|
||||
@@ -48,6 +48,5 @@ void MainWindow::pauseResume()
|
||||
|
||||
void MainWindow::setSliderRange(int duration)
|
||||
{
|
||||
const int time0 = m_mpv->getProperty("time-start").toInt();
|
||||
m_slider->setRange(time0, time0+duration);
|
||||
m_slider->setRange(0, duration);
|
||||
}
|
||||
@@ -80,7 +80,7 @@ void MpvWidget::initializeGL()
|
||||
|
||||
void MpvWidget::paintGL()
|
||||
{
|
||||
mpv_opengl_cb_draw(mpv_gl, QOpenGLContext::currentContext()->defaultFramebufferObject(), width(), -height());
|
||||
mpv_opengl_cb_draw(mpv_gl, defaultFramebufferObject(), width(), -height());
|
||||
}
|
||||
|
||||
void MpvWidget::swapped()
|
||||
|
||||
+1
-38
@@ -68,7 +68,7 @@ echo "MXE_TARGETS := i686-w64-mingw32.static" >> settings.mk
|
||||
# Build required packages. The following provide a minimum required to build
|
||||
# a reasonable mpv binary (though not an absolute minimum).
|
||||
|
||||
make gcc ffmpeg libass jpeg pthreads lua
|
||||
make gcc ffmpeg libass jpeg lua
|
||||
|
||||
# Add MXE binaries to $PATH
|
||||
export PATH=/opt/mxe/usr/bin/:$PATH
|
||||
@@ -173,40 +173,3 @@ mv -f /mingw64/bin/libmpv.dll.a /mingw64/lib/
|
||||
sed -i 's_/mingw64/bin_/mingw64/lib_' /mingw64/lib/pkgconfig/mpv.pc
|
||||
rmdir /mingw64/bin/pkgconfig
|
||||
```
|
||||
|
||||
Additional dependencies
|
||||
=======================
|
||||
|
||||
pthreads
|
||||
--------
|
||||
|
||||
mpv will use a pthreads wrapper by default. Either pthreads-win32 or
|
||||
winpthreads should work. The latter is packaged with most MinGW-w64
|
||||
environments, including MSYS2, so it shouldn't be a problem. If you don't have
|
||||
a pthreads wrapper or you want to build mpv without one, configure with:
|
||||
|
||||
```bash
|
||||
./waf configure --enable-win32-internal-pthreads
|
||||
```
|
||||
|
||||
libwaio
|
||||
-------
|
||||
|
||||
If you want to use ``--input-file``, you need libwaio. It's available from
|
||||
git://midipix.org/waio
|
||||
|
||||
To compile libwaio in MSYS2, run:
|
||||
|
||||
```bash
|
||||
git clone git://midipix.org/waio && cd waio
|
||||
|
||||
# 32-bit build, run from mingw32_shell.bat
|
||||
./build-mingw-nt32 lib-static CC=gcc AR=ar
|
||||
cp -r include/waio /mingw32/include
|
||||
cp lib32/libwaio.a /mingw32/lib
|
||||
|
||||
# 64-bit build, run from mingw64_shell.bat
|
||||
./build-mingw-nt64 lib-static CC=gcc AR=ar
|
||||
cp -r include/waio /mingw64/include
|
||||
cp lib64/libwaio.a /mingw64/lib
|
||||
```
|
||||
+11
-9
@@ -14,9 +14,11 @@ Sending patches
|
||||
``git format-patch``. diffs posted as pastebins (especially if the http link
|
||||
returns HTML) just cause extra work for everyone, because they lack commit
|
||||
message and authorship information.
|
||||
- When creating pull requests, be sure to test your changes. If you didn't, please
|
||||
say so in the pull request message.
|
||||
|
||||
- All new code must be LGPLv2.1+ licensed, or come with the implicit agreement
|
||||
that it will be relicensed to LGPLv2.1+ later (see ``Copyright`` in the
|
||||
repository root directory).
|
||||
- When creating pull requests, be sure to test your changes. If you didn't,
|
||||
please say so in the pull request message.
|
||||
- Write informative commit messages. Use present tense to describe the
|
||||
situation with the patch applied, and past tense for the situation before
|
||||
the change.
|
||||
@@ -27,8 +29,8 @@ Sending patches
|
||||
|
||||
For example, you fixed a crash in af_volume.c:
|
||||
|
||||
Bad: ``fixed the bug (wtf?)``
|
||||
Good: ``af_volume: fix crash due to null pointer access``
|
||||
- Bad: ``fixed the bug (wtf?)``
|
||||
- Good: ``af_volume: fix crash due to null pointer access``
|
||||
|
||||
Having a prefix gives context, and is especially useful when trying to find
|
||||
a specific change by looking at the history, or when running ``git blame``.
|
||||
@@ -53,10 +55,10 @@ Sending patches
|
||||
something like reformatting a whole file, and hiding an actual functional
|
||||
change in the same commit.
|
||||
- If you add a new command line option, document it in options.rst. If you
|
||||
add a new input property, document it in input.rst.
|
||||
- All new code must be LGPLv2.1+ licensed, or come with the implicit agreement
|
||||
that it will be relicensed to LGPLv2.1+ later (see ``Copyright`` in the
|
||||
repository root directory).
|
||||
add a new input property, document it in input.rst. Changes to the user
|
||||
interface (options, properties, commands) should be documented in
|
||||
interface-changes.rst. Changes to libmpv should be documented in
|
||||
client-api-changes.rst.
|
||||
|
||||
Code formatting
|
||||
---------------
|
||||
|
||||
@@ -19,6 +19,18 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.15.0 ---
|
||||
- change "yadif" video filter defaults
|
||||
--- 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 ---
|
||||
|
||||
+6
-20
@@ -20,10 +20,8 @@ normal driver parameters.
|
||||
|
||||
See ``--ao=help`` for a list of compiled-in audio output drivers. The
|
||||
driver ``--ao=alsa`` is preferred. ``--ao=pulse`` is preferred on systems
|
||||
where PulseAudio is used. On Windows, ``--ao=wasapi`` is preferred,
|
||||
though it might cause trouble sometimes, in which case ``--ao=dsound``
|
||||
should be used. On BSD systems, ``--ao=oss`` or `--ao=sndio`` may work
|
||||
(the latter being experimental). On OS X systems, use ``--ao=coreaudio``.
|
||||
where PulseAudio is used. On BSD systems, ``--ao=oss`` or ``--ao=sndio``
|
||||
may work (the latter being experimental).
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
@@ -165,7 +163,7 @@ Available audio output drivers are:
|
||||
will actually work. The disadvantage is that it will change the
|
||||
system-wide audio settings. This is equivalent to changing the ``Format``
|
||||
setting in the ``Audio Devices`` dialog in the ``Audio MIDI Setup``
|
||||
utility. Note that this does not effect the selected speaker setup.
|
||||
utility. Note that this does not affect the selected speaker setup.
|
||||
|
||||
``exclusive``
|
||||
Use exclusive mode access. This merely redirects to
|
||||
@@ -205,19 +203,7 @@ Available audio output drivers are:
|
||||
(it used to be required to get good behavior on old PulseAudio versions).
|
||||
|
||||
If you have stuttering video when using pulse, try to enable this
|
||||
option. (Or alternatively, try to update PulseAudio.)
|
||||
|
||||
``dsound`` (Windows only)
|
||||
DirectX DirectSound audio output driver
|
||||
|
||||
.. note:: This driver is for compatibility with old systems.
|
||||
|
||||
``device=<devicenum>``
|
||||
Sets the device number to use. Playing a file with ``-v`` will show a
|
||||
list of available devices.
|
||||
|
||||
``buffersize=<ms>``
|
||||
DirectSound buffer size in milliseconds (default: 200).
|
||||
option. (Or try to update PulseAudio.)
|
||||
|
||||
``sdl``
|
||||
SDL 1.2+ audio output driver. Should work on any platform supported by SDL
|
||||
@@ -256,7 +242,7 @@ Available audio output drivers are:
|
||||
``speed``
|
||||
Simulated audio playback speed as a multiplier. Usually, a real audio
|
||||
device will not go exactly as fast as the system clock. It will deviate
|
||||
just a little, and this option helps simulating this.
|
||||
just a little, and this option helps to simulate this.
|
||||
|
||||
``latency``
|
||||
Simulated device latency. This is additional to EOF.
|
||||
@@ -325,7 +311,7 @@ Available audio output drivers are:
|
||||
String are valid; the GUID string is guaranteed to not change
|
||||
unless the driver is uninstalled.
|
||||
|
||||
Also supports searching active devices by human readable name. If more
|
||||
Also supports searching active devices by human-readable name. If more
|
||||
than one device matches the name, refuses loading it.
|
||||
|
||||
This option is mostly deprecated in favour of the more general
|
||||
|
||||
+4
-4
@@ -93,8 +93,8 @@ You can encode files from one format/codec to another using this facility.
|
||||
Completely empties the options list.
|
||||
|
||||
``--oafirst``
|
||||
Force the audio stream to become the first stream in the output. By default
|
||||
the order is unspecified.
|
||||
Force the audio stream to become the first stream in the output.
|
||||
By default, the order is unspecified.
|
||||
|
||||
``--ovc=<codec>``
|
||||
Specifies the output video codec. This can be a comma separated list of
|
||||
@@ -134,8 +134,8 @@ You can encode files from one format/codec to another using this facility.
|
||||
Completely empties the options list.
|
||||
|
||||
``--ovfirst``
|
||||
Force the video stream to become the first stream in the output. By default
|
||||
the order is unspecified.
|
||||
Force the video stream to become the first stream in the output.
|
||||
By default, the order is unspecified.
|
||||
|
||||
``--ocopyts``
|
||||
Copies input pts to the output video (not supported by some output
|
||||
|
||||
+82
-22
@@ -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
|
||||
@@ -418,7 +418,7 @@ List of Input Commands
|
||||
|
||||
<reselect> (default)
|
||||
Select the default audio and subtitle streams, which typically selects
|
||||
external files with highest preference. (The implementation is not
|
||||
external files with the highest preference. (The implementation is not
|
||||
perfect, and could be improved on request.)
|
||||
|
||||
<keep-selection>
|
||||
@@ -546,6 +546,12 @@ Input Commands that are Possibly Subject to Change
|
||||
Always bind a key. (The input section that was made active most recently
|
||||
wins if there are ambiguities.)
|
||||
|
||||
This command can be used to dispatch arbitrary keys to a script or a client
|
||||
API user. If the input section defines ``script-binding`` commands, it is
|
||||
also possible to get separate events on key up/down, and relatively detailed
|
||||
information about the key state. The special key name ``unmapped`` can be
|
||||
used to match any unmapped key.
|
||||
|
||||
``overlay-add <id> <x> <y> "<file>" <offset> "<fmt>" <w> <h> <stride>``
|
||||
Add an OSD overlay sourced from raw data. This might be useful for scripts
|
||||
and applications controlling mpv, and which want to display things on top
|
||||
@@ -647,14 +653,20 @@ Input Commands that are Possibly Subject to Change
|
||||
For completeness, here is how this command works internally. The details
|
||||
could change any time. On any matching key event, ``script_message_to``
|
||||
or ``script_message`` is called (depending on whether the script name is
|
||||
included), where the first argument is the string ``key-binding``, the
|
||||
second argument is the name of the binding, and the third argument is the
|
||||
key state as string. The key state consists of a number of letters. The
|
||||
first letter is one of ``d`` (key was pressed down), ``u`` (was released),
|
||||
``r`` (key is still down, and was repeated; only if key repeat is enabled
|
||||
for this binding), ``p`` (key was pressed; happens if up/down can't be
|
||||
tracked). The second letter whether the event originates from the mouse,
|
||||
either ``m`` (mouse button) or ``-`` (something else).
|
||||
included), with the following arguments:
|
||||
|
||||
1. The string ``key-binding``.
|
||||
2. The name of the binding (as established above).
|
||||
3. The key state as string (see below).
|
||||
4. The key name (since mpv 0.15.0).
|
||||
|
||||
The key state consists of 2 letters:
|
||||
|
||||
1. One of ``d`` (key was pressed down), ``u`` (was released), ``r`` (key
|
||||
is still down, and was repeated; only if key repeat is enabled for this
|
||||
binding), ``p`` (key was pressed; happens if up/down can't be tracked).
|
||||
2. Whether the event originates from the mouse, either ``m`` (mouse button)
|
||||
or ``-`` (something else).
|
||||
|
||||
``ab-loop``
|
||||
Cycle through A-B loop states. The first command will set the ``A`` point
|
||||
@@ -782,7 +794,7 @@ Input sections group a set of bindings, and enable or disable them at once.
|
||||
In ``input.conf``, each key binding is assigned to an input section, rather
|
||||
than actually having explicit text sections.
|
||||
|
||||
Also see ``enable_section`` and ``disable_section`` commands.
|
||||
See also: ``enable_section`` and ``disable_section`` commands.
|
||||
|
||||
Predefined bindings:
|
||||
|
||||
@@ -860,7 +872,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.
|
||||
@@ -921,6 +937,18 @@ Property list
|
||||
(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
|
||||
@@ -931,8 +959,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
|
||||
@@ -942,9 +971,10 @@ 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. Useful on 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.
|
||||
@@ -1090,13 +1120,13 @@ Property list
|
||||
|
||||
``vf-metadata/<filter-label>``
|
||||
Metadata added by video filters. Accessed by the filter label,
|
||||
which if not explicitly specified using the ``@filter-label:`` syntax,
|
||||
which, if not explicitly specified using the ``@filter-label:`` syntax,
|
||||
will be ``<filter-name>NN``.
|
||||
|
||||
Works similar to ``metadata`` property. It allows the same access
|
||||
methods (using sub-properties).
|
||||
|
||||
An example of these kind of metadata are the cropping parameters
|
||||
An example of this kind of metadata are the cropping parameters
|
||||
added by ``--vf=lavfi=cropdetect``.
|
||||
|
||||
``af-metadata/<filter-label>``
|
||||
@@ -1123,7 +1153,7 @@ Property list
|
||||
|
||||
``cache-size`` (RW)
|
||||
Network cache size in KB. This is similar to ``--cache``. This allows
|
||||
to set the cache size at runtime. Currently, it's not possible to enable
|
||||
setting the cache size at runtime. Currently, it's not possible to enable
|
||||
or disable the cache at runtime using this property, just to resize an
|
||||
existing cache.
|
||||
|
||||
@@ -1426,6 +1456,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.
|
||||
@@ -1459,6 +1502,14 @@ Property list
|
||||
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``.
|
||||
|
||||
@@ -1491,6 +1542,15 @@ Property list
|
||||
``program`` (W)
|
||||
Switch TS program (write-only).
|
||||
|
||||
``dvb-channel`` (W)
|
||||
Pair of integers: card,channel of current DVB stream.
|
||||
Can be switched to switch to another channel on the same card.
|
||||
|
||||
``dvb-channel-name`` (RW)
|
||||
Name of current DVB program.
|
||||
On write, a channel-switch to the named channel on the same
|
||||
card is performed. Can also be used for channel switching.
|
||||
|
||||
``sid`` (RW)
|
||||
Current subtitle track (similar to ``--sid``).
|
||||
|
||||
|
||||
+5
-3
@@ -111,7 +111,7 @@ rely on this.
|
||||
Commands
|
||||
--------
|
||||
|
||||
Additionally to the commands described in `List of Input Commands`_, a few
|
||||
In addition to the commands described in `List of Input Commands`_, a few
|
||||
extra commands can also be used as part of the protocol:
|
||||
|
||||
``client_name``
|
||||
@@ -189,7 +189,7 @@ extra commands can also be used as part of the protocol:
|
||||
|
||||
``unobserve_property``
|
||||
Undo ``observe_property`` or ``observe_property_string``. This requires the
|
||||
numeric id passed to the observe command as argument.
|
||||
numeric id passed to the observed command as argument.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -228,7 +228,9 @@ extra commands can also be used as part of the protocol:
|
||||
|
||||
``get_version``
|
||||
Returns the client API version the C API of the remote mpv instance
|
||||
provides. (Also see ``DOCS/client-api-changes.rst``.)
|
||||
provides.
|
||||
|
||||
See also: ``DOCS/client-api-changes.rst``.
|
||||
|
||||
UTF-8
|
||||
-----
|
||||
|
||||
+6
-6
@@ -198,7 +198,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
be called (unless the user remapped the key with another binding).
|
||||
|
||||
The ``name`` argument should be a short symbolic string. It allows the user
|
||||
to remap the key binding via input.conf using the ``script_message``
|
||||
to remap the key binding via input.conf using the ``script-message``
|
||||
command, and the name of the key binding (see below for
|
||||
an example). The name should be unique across other bindings in the same
|
||||
script - if not, the previous binding with the same name will be
|
||||
@@ -220,8 +220,8 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
has an ``is_mouse`` entry, which tells whether the event was caused
|
||||
by a mouse button.
|
||||
|
||||
Internally, key bindings are dispatched via the ``script_message_to`` or
|
||||
``script_binding`` input commands and ``mp.register_script_message``.
|
||||
Internally, key bindings are dispatched via the ``script-message-to`` or
|
||||
``script-binding`` input commands and ``mp.register_script_message``.
|
||||
|
||||
Trying to map multiple commands to a key will essentially prefer a random
|
||||
binding, while the other bindings are not called. It is guaranteed that
|
||||
@@ -244,7 +244,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
|
||||
::
|
||||
|
||||
y script_binding something
|
||||
y script-binding something
|
||||
|
||||
|
||||
This will print the message when the key ``y`` is pressed. (``x`` will
|
||||
@@ -255,7 +255,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
|
||||
::
|
||||
|
||||
y script_binding fooscript.something
|
||||
y script-binding fooscript.something
|
||||
|
||||
``mp.add_forced_key_binding(...)``
|
||||
This works almost the same as ``mp.add_key_binding``, but registers the
|
||||
@@ -389,7 +389,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
``mp.get_script_name()``
|
||||
Return the name of the current script. The name is usually made of the
|
||||
filename of the script, with directory and file extension removed. If
|
||||
there are several script which would have the same name, it's made unique
|
||||
there are several scripts which would have the same name, it's made unique
|
||||
by appending a number.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
+32
-33
@@ -50,7 +50,7 @@ UP and DOWN
|
||||
|
||||
Ctrl+LEFT and Ctrl+RIGHT
|
||||
Seek to the previous/next subtitle. Subject to some restrictions and
|
||||
might not work always; see ``sub_seek`` command.
|
||||
might not always work; see ``sub_seek`` command.
|
||||
|
||||
[ and ]
|
||||
Decrease/increase current playback speed by 10%.
|
||||
@@ -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.)
|
||||
@@ -226,7 +222,10 @@ PREVIOUS and NEXT
|
||||
support.)
|
||||
|
||||
h and k
|
||||
Select previous/next channel.
|
||||
Select previous/next tv-channel.
|
||||
|
||||
H and K
|
||||
Select previous/next dvb-channel.
|
||||
|
||||
Mouse Control
|
||||
-------------
|
||||
@@ -262,7 +261,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:
|
||||
@@ -414,7 +413,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
|
||||
@@ -524,8 +523,9 @@ TAKING SCREENSHOTS
|
||||
|
||||
Screenshots of the currently played file can be taken using the 'screenshot'
|
||||
input mode command, which is by default bound to the ``s`` key. Files named
|
||||
``shotNNNN.jpg`` will be saved in the working directory, using the first
|
||||
available number - no files will be overwritten.
|
||||
``mpv-shotNNNN.jpg`` will be saved in the working directory, using the first
|
||||
available number - no files will be overwritten. In pseudo-GUI mode, the
|
||||
screenshot will be saved somewhere else. See `PSEUDO GUI MODE`_.
|
||||
|
||||
A screenshot will usually contain the unscaled video contents at the end of the
|
||||
video filter chain and subtitles. By default, ``S`` takes screenshots without
|
||||
@@ -566,16 +566,16 @@ 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
|
||||
rendering is too slow. May also be incremented on "hiccups" and when the video
|
||||
frame couldn't be displayed on time. (``vo-drop-frame-count`` property.)
|
||||
If the decoder drops frames, the number of decoder-dropped frames is appended
|
||||
to the display as well, e.g.: ``Dropped: 4/34``. This happens only if
|
||||
@@ -606,7 +606,7 @@ PROTOCOLS
|
||||
Play a path from Samba share.
|
||||
|
||||
``bd://[title][/device]`` ``--bluray-device=PATH``
|
||||
Play a Blu-Ray disc. Currently, this does not accept ISO files. Instead,
|
||||
Play a Blu-ray disc. Currently, this does not accept ISO files. Instead,
|
||||
you must mount the ISO file as filesystem, and point ``--bluray-device``
|
||||
to the mounted directory directly.
|
||||
|
||||
@@ -658,9 +658,8 @@ PROTOCOLS
|
||||
absolute path.
|
||||
|
||||
``fd://123``
|
||||
Read data from the given UNIX FD (for example 123). This is similar to
|
||||
piping data to stdin via ``-``, but can use an arbitrary file descriptor.
|
||||
Will not work correctly on MS Windows.
|
||||
Read data from the given file descriptor (for example 123). This is similar
|
||||
to piping data to stdin via ``-``, but can use an arbitrary file descriptor.
|
||||
|
||||
``edl://[edl specification as in edl-mpv.rst]``
|
||||
Stitch together parts of multiple files and play them.
|
||||
@@ -732,7 +731,7 @@ 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
|
||||
recommended way to do 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).
|
||||
|
||||
@@ -807,7 +806,7 @@ libdvdcss:
|
||||
|
||||
key
|
||||
is the default method. libdvdcss will use a set of calculated
|
||||
player keys to try and get the disc key. This can fail if the drive
|
||||
player keys to try to get the disc key. This can fail if the drive
|
||||
does not recognize any of the player keys.
|
||||
|
||||
disc
|
||||
@@ -923,7 +922,7 @@ locations are different. They are generally located under ``%APPDATA%/mpv/``.
|
||||
For example, the path to mpv.conf is ``%APPDATA%/mpv/mpv.conf``, which maps to
|
||||
a system and user-specific path, for example
|
||||
|
||||
``C:\users\USERNAME\Application Data\mpv\mpv.conf``
|
||||
``C:\users\USERNAME\AppData\Roaming\mpv\mpv.conf``
|
||||
|
||||
You can find the exact path by running ``echo %APPDATA%\mpv\mpv.conf`` in cmd.exe.
|
||||
|
||||
@@ -946,8 +945,8 @@ lower priority. Some config files are loaded only once, which means that
|
||||
e.g. of 2 ``input.conf`` files located in two config directories, only the
|
||||
one from the directory with higher priority will be loaded.
|
||||
|
||||
A third config directory with lowest priority is the directory named ``mpv``
|
||||
in the same directory as ``mpv.exe``. This used to be the directory with
|
||||
A third config directory with the lowest priority is the directory named ``mpv``
|
||||
in the same directory as ``mpv.exe``. This used to be the directory with the
|
||||
highest priority, but is now discouraged to use and might be removed in the
|
||||
future.
|
||||
|
||||
|
||||
+100
-102
@@ -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.
|
||||
|
||||
@@ -127,7 +136,9 @@ Playback Control
|
||||
|
||||
``--chapter=<start[-end]>``
|
||||
Specify which chapter to start playing at. Optionally specify which
|
||||
chapter to end playing at. Also see ``--start``.
|
||||
chapter to end playing at.
|
||||
|
||||
See also: ``--start``.
|
||||
|
||||
``--playlist-pos=<no|index>``
|
||||
Set which file on the internal playlist to start playback with. The index
|
||||
@@ -312,7 +323,7 @@ Program Behavior
|
||||
Files explicitly requested by command line options, like
|
||||
``--include`` or ``--use-filedir-conf``, will still be loaded.
|
||||
|
||||
Also see ``--config-dir``.
|
||||
See also: ``--config-dir``.
|
||||
|
||||
``--list-options``
|
||||
Prints all available options.
|
||||
@@ -548,11 +559,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.
|
||||
@@ -605,10 +615,18 @@ Video
|
||||
|
||||
For ``opengl-cb``, if set, load the interop context as soon as the OpenGL
|
||||
context is created. Since ``opengl-cb`` has no on-demand loading, this
|
||||
allows enabling hardware decoding at runtime at all, without having to
|
||||
allows enabling hardware decoding at runtime at all, without having
|
||||
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
|
||||
@@ -659,7 +677,7 @@ Video
|
||||
example ``--video-zoom`` does nothing if this option is enabled.)
|
||||
|
||||
The video and monitor aspects aspect will be ignored. Aspect correction
|
||||
would require to scale the video in the X or Y direction, but this option
|
||||
would require scaling the video in the X or Y direction, but this option
|
||||
disables scaling, disabling all aspect correction.
|
||||
|
||||
Note that the scaler algorithm may still be used, even if the video isn't
|
||||
@@ -679,7 +697,7 @@ Video
|
||||
|
||||
This option is disabled if the ``--no-keepaspect`` option is used.
|
||||
|
||||
``--video-rotate=<0-360|no>``
|
||||
``--video-rotate=<0-359|no>``
|
||||
Rotate the video clockwise, in degrees. Currently supports 90° steps only.
|
||||
If ``no`` is given, the video is never rotated, even if the file has
|
||||
rotation metadata. (The rotation value is added to the rotation metadata,
|
||||
@@ -747,7 +765,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
|
||||
@@ -804,7 +822,7 @@ 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,mpeg2video``. Note that the
|
||||
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.
|
||||
|
||||
@@ -848,7 +866,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
|
||||
@@ -1011,7 +1029,9 @@ Audio
|
||||
|
||||
``--mute=<auto|yes|no>``
|
||||
Set startup audio mute status. ``auto`` (default) will not change the mute
|
||||
status. Also see ``--volume``.
|
||||
status.
|
||||
|
||||
See also: ``--volume``.
|
||||
|
||||
``--softvol=<mode>``
|
||||
Control whether to use the volume controls of the audio output driver or
|
||||
@@ -1136,8 +1156,8 @@ Audio
|
||||
point of file change. Default: ``weak``.
|
||||
|
||||
:no: Disable gapless audio.
|
||||
:yes: The audio device is opened using parameters chosen according to the
|
||||
first file played and is then kept open for gapless playback. This
|
||||
:yes: The audio device is opened using parameters chosen for the first
|
||||
file played and is then kept open for gapless playback. This
|
||||
means that if the first file for example has a low sample rate, then
|
||||
the following files may get resampled to the same low sample rate,
|
||||
resulting in reduced sound quality. If you play files with different
|
||||
@@ -1184,7 +1204,11 @@ Audio
|
||||
:no: Don't automatically load external audio files.
|
||||
:exact: Load the media filename with audio file extension (default).
|
||||
:fuzzy: Load all audio files containing media filename.
|
||||
:all: Load all audio files in the current directory.
|
||||
:all: Load all aufio files in the current and ``--audio-file-paths``
|
||||
directories.
|
||||
|
||||
``--audio-file-paths=<path1:path2:...>``
|
||||
Equivalent to ``--sub-paths`` option, but for auto-loaded audio files.
|
||||
|
||||
``--audio-client-name=<name>``
|
||||
The application name the player reports to the audio API. Can be useful
|
||||
@@ -1221,6 +1245,14 @@ Audio
|
||||
Subtitles
|
||||
---------
|
||||
|
||||
.. note::
|
||||
|
||||
Changing styling and position does not work with all subtitles. Image-based
|
||||
subtitles (DVD, Bluray/PGS, DVB) can not changed for fundamental reasons.
|
||||
Subtitles in ASS format are normally not changed intentionally, but
|
||||
overriding them can be controlled with ``--ass-style-override``.
|
||||
|
||||
|
||||
``--no-sub``
|
||||
Do not select any subtitle when the file is loaded.
|
||||
|
||||
@@ -1318,8 +1350,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
|
||||
|
||||
@@ -1567,27 +1599,27 @@ 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::
|
||||
|
||||
``<rate>`` > video fps speeds the subtitles up for frame-based
|
||||
subtitle files and slows them down for time-based ones.
|
||||
|
||||
Also see ``--sub-speed`` option.
|
||||
See also: ``--sub-speed``.
|
||||
|
||||
``--sub-gauss=<0.0-3.0>``
|
||||
Apply Gaussian blur to image subtitles (default: 0). This can help making
|
||||
Apply Gaussian blur to image subtitles (default: 0). This can help to make
|
||||
pixelated DVD/Vobsubs look nicer. A value other than 0 also switches to
|
||||
software subtitle scaling. Might be slow.
|
||||
|
||||
@@ -1596,7 +1628,7 @@ Subtitles
|
||||
Never applied to text subtitles.
|
||||
|
||||
``--sub-gray``
|
||||
Convert image subtitles to grayscale. Can help making yellow DVD/Vobsubs
|
||||
Convert image subtitles to grayscale. Can help to make yellow DVD/Vobsubs
|
||||
look nicer.
|
||||
|
||||
.. note::
|
||||
@@ -1737,6 +1769,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.
|
||||
@@ -1964,8 +2000,8 @@ Window
|
||||
always re-enabled when the player is paused.
|
||||
|
||||
This is not supported on all video outputs or platforms. Sometimes it is
|
||||
implemented, but does not work (happens often on GNOME). You might be able
|
||||
to to work this around using ``--heartbeat-cmd`` instead.
|
||||
implemented, but does not work (known to happen with GNOME). You might be
|
||||
able to work around this using ``--heartbeat-cmd`` instead.
|
||||
|
||||
``--wid=<ID>``
|
||||
This tells mpv to attach to an existing window. If a VO is selected that
|
||||
@@ -1984,7 +2020,7 @@ Window
|
||||
parent, like with X11.
|
||||
|
||||
On OSX/Cocoa, the ID is interpreted as ``NSView*``. Pass it as value cast
|
||||
to ``intptr_t``. mpv will creates its own sub-view. Because OSX does not
|
||||
to ``intptr_t``. mpv will create its own sub-view. Because OSX does not
|
||||
support window embedding of foreign processes, this works only with libmpv,
|
||||
and will crash when used from the command line.
|
||||
|
||||
@@ -2015,6 +2051,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
|
||||
------------
|
||||
@@ -2189,7 +2229,7 @@ Demuxer
|
||||
Encryption key the demuxer should use. This is the raw binary data of
|
||||
the key converted to a hexadecimal string.
|
||||
|
||||
``--demuxer-mkv-subtitle-preroll``, ``--mkv-subtitle-preroll``
|
||||
``--demuxer-mkv-subtitle-preroll=<yes|index|no>``, ``--mkv-subtitle-preroll``
|
||||
Try harder to show embedded soft subtitles when seeking somewhere. Normally,
|
||||
it can happen that the subtitle at the seek target is not shown due to how
|
||||
some container file formats are designed. The subtitles appear only if
|
||||
@@ -2221,7 +2261,11 @@ Demuxer
|
||||
overlap with a seek target. In these cases, mpv will reduce the amount
|
||||
of data read to a minimum. (Although it will still read *all* data between
|
||||
the cluster that contains the first wanted subtitle packet, and the seek
|
||||
target.)
|
||||
target.) If the ``index`` choice (which is the default) is specified, then
|
||||
prerolling will be done only if this information is actually available. If
|
||||
this method is used, the maximum amount of data to skip can be additionally
|
||||
controlled by ``--demuxer-mkv-subtitle-preroll-secs-index`` (it still uses
|
||||
the value of the option without ``-index`` if that is higher).
|
||||
|
||||
See also ``--hr-seek-demuxer-offset`` option. This option can achieve a
|
||||
similar effect, but only if hr-seek is active. It works with any demuxer,
|
||||
@@ -2233,6 +2277,9 @@ Demuxer
|
||||
``--demuxer-mkv-subtitle-preroll-secs=<value>``
|
||||
See ``--demuxer-mkv-subtitle-preroll``.
|
||||
|
||||
``--demuxer-mkv-subtitle-preroll-secs-index=<value>``
|
||||
See ``--demuxer-mkv-subtitle-preroll``.
|
||||
|
||||
``--demuxer-mkv-probe-video-duration=<yes|no|full>``
|
||||
When opening the file, seek to the end of it, and check what timestamp the
|
||||
last video packet has, and report that as file duration. This is strictly
|
||||
@@ -2357,7 +2404,7 @@ Input
|
||||
|
||||
``--input-key-fifo-size=<2-65000>``
|
||||
Specify the size of the FIFO that buffers key events (default: 7). If it
|
||||
is too small some events may be lost. The main disadvantage of setting it
|
||||
is too small, some events may be lost. The main disadvantage of setting it
|
||||
to a very large value is that if you hold down a key triggering some
|
||||
particularly slow command then the player may be unresponsive while it
|
||||
processes all the queued commands.
|
||||
@@ -2424,7 +2471,7 @@ Input
|
||||
|
||||
On X11, a sub-window with input enabled grabs all keyboard input as long
|
||||
as it is 1. a child of a focused window, and 2. the mouse is inside of
|
||||
the sub-window. The can steal away all keyboard input from the
|
||||
the sub-window. It can steal away all keyboard input from the
|
||||
application embedding the mpv window, and on the other hand, the mpv
|
||||
window will receive no input if the mouse is outside of the mpv window,
|
||||
even though mpv has focus. Modern toolkits work around this weird X11
|
||||
@@ -2694,7 +2741,7 @@ Screenshot
|
||||
Specify the filename template used to save screenshots. The template
|
||||
specifies the filename without file extension, and can contain format
|
||||
specifiers, which will be substituted when taking a screenshot.
|
||||
By default the template is ``mpv-shot%n``, which results in filenames like
|
||||
By default, the template is ``mpv-shot%n``, which results in filenames like
|
||||
``mpv-shot0012.png`` for example.
|
||||
|
||||
The template can start with a relative or absolute path, in order to
|
||||
@@ -2850,7 +2897,7 @@ Terminal
|
||||
Particularly useful on slow terminals or broken ones which do not properly
|
||||
handle carriage return (i.e. ``\r``).
|
||||
|
||||
Also see ``--really-quiet`` and ``--msg-level``.
|
||||
See also: ``--really-quiet`` and ``--msg-level``.
|
||||
|
||||
``--really-quiet``
|
||||
Display even less output and status messages than with ``--quiet``.
|
||||
@@ -2983,8 +3030,9 @@ TV
|
||||
maximum size of the capture buffer in megabytes (default: dynamical)
|
||||
|
||||
``--tv-norm=<value>``
|
||||
See the console output for a list of all available norms, also see the
|
||||
``normid`` option below.
|
||||
See the console output for a list of all available norms.
|
||||
|
||||
See also: ``--tv-normid``.
|
||||
|
||||
``--tv-normid=<value> (v4l2 only)``
|
||||
Sets the TV norm to the given numeric ID. The TV norm depends on the
|
||||
@@ -3194,7 +3242,7 @@ Cache
|
||||
multiple cache streams, and using the same file for them obviously
|
||||
clashes.
|
||||
|
||||
Also see ``--cache-file-size``.
|
||||
See also: ``--cache-file-size``.
|
||||
|
||||
``--cache-file-size=<kBytes>``
|
||||
Maximum size of the file created with ``--cache-file``. For read accesses
|
||||
@@ -3322,72 +3370,18 @@ DVB
|
||||
|
||||
``--dvbin-full-transponder=<yes|no>``
|
||||
Apply no filters on program PIDs, only tune to frequency and pass full
|
||||
transponder to demuxer. This is useful to record multiple programs
|
||||
on a single transponder, or to work around issues in the ``channels.conf``.
|
||||
transponder to demuxer.
|
||||
The player frontend selects the streams from the full TS in this case,
|
||||
so the program which is shown initially may not match the chosen channel.
|
||||
Switching between the programs is possible by cycling the ``program``
|
||||
property.
|
||||
This is useful to record multiple programs on a single transponder,
|
||||
or to work around issues in the ``channels.conf``.
|
||||
It is also recommended to use this for channels which switch PIDs
|
||||
on-the-fly, e.g. for regional news.
|
||||
|
||||
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
|
||||
-------------
|
||||
|
||||
@@ -3415,7 +3409,7 @@ Miscellaneous
|
||||
implement a perfect audio delay measurement. With this value, if large A/V
|
||||
sync offsets occur, they will only take about 1 or 2 seconds to settle
|
||||
out. This delay in reaction time to sudden A/V offsets should be the only
|
||||
side-effect of turning this option on, for all sound drivers.
|
||||
side effect of turning this option on, for all sound drivers.
|
||||
|
||||
``--video-sync=<audio|...>``
|
||||
How the player synchronizes audio and video.
|
||||
@@ -3526,6 +3520,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
|
||||
|
||||
+20
-21
@@ -151,15 +151,15 @@ Available filters are:
|
||||
:yes: Enable accurate rounding.
|
||||
|
||||
``dsize[=w:h:aspect-method:r:aspect]``
|
||||
Changes the intended display size/aspect at an arbitrary point in the
|
||||
Changes the intended display aspect at an arbitrary point in the
|
||||
filter chain. Aspect can be given as a fraction (4/3) or floating point
|
||||
number (1.33). Alternatively, you may specify the exact display width and
|
||||
height desired. Note that this filter does *not* do any scaling itself; it
|
||||
number (1.33). Note that this filter does *not* do any scaling itself; it
|
||||
just affects what later scalers (software or hardware) will do when
|
||||
auto-scaling to the correct aspect.
|
||||
|
||||
``<w>,<h>``
|
||||
New display width and height.
|
||||
New aspect ratio given by a display width and height. Unlike older mpv
|
||||
versions or MPlayer, this does not set the display size.
|
||||
|
||||
Can also be these special values:
|
||||
|
||||
@@ -438,9 +438,8 @@ Available filters are:
|
||||
generating an occasional mismatched frame, but it may also cause an
|
||||
excessive number of frames to be dropped during high motion sequences.
|
||||
Conversely, setting it to -1 will make ``pullup`` match fields more
|
||||
easily. This may help processing of video where there is slight
|
||||
blurring between the fields, but may also cause there to be interlaced
|
||||
frames in the output.
|
||||
easily. This may help process video with slight blurring between the
|
||||
fields, but may also cause interlaced frames in the output.
|
||||
|
||||
``mp`` (metric plane)
|
||||
This option may be set to ``u`` or ``v`` to use a chroma plane instead of the
|
||||
@@ -455,25 +454,24 @@ Available filters are:
|
||||
|
||||
``<mode>``
|
||||
:frame: Output 1 frame for each frame.
|
||||
:field: Output 1 frame for each field.
|
||||
:field: Output 1 frame for each field (default).
|
||||
:frame-nospatial: Like ``frame`` but skips spatial interlacing check.
|
||||
:field-nospatial: Like ``field`` but skips spatial interlacing check.
|
||||
|
||||
``<interlaced-only>``
|
||||
:no: Deinterlace all frames (default).
|
||||
:yes: Only deinterlace frames marked as interlaced (default if this
|
||||
filter is inserted via ``deinterlace`` property).
|
||||
:no: Deinterlace all frames.
|
||||
:yes: Only deinterlace frames marked as interlaced (default).
|
||||
|
||||
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.
|
||||
|
||||
@@ -645,10 +643,11 @@ Available filters are:
|
||||
``buffered-frames``
|
||||
Maximum number of decoded video frames that should be buffered before
|
||||
the filter (default: 4). This specifies the maximum number of frames
|
||||
the script can requests backwards. E.g. if ``buffered-frames=5``, and
|
||||
the script just requested frame 15, it can still request frame 10, but
|
||||
frame 9 is not available anymore. If it requests frame 30, mpv will
|
||||
decode 15 more frames, and keep only frames 25-30.
|
||||
the script can request in reverse direction.
|
||||
E.g. if ``buffered-frames=5``, and the script just requested frame 15,
|
||||
it can still request frame 10, but frame 9 is not available anymore.
|
||||
If it requests frame 30, mpv will decode 15 more frames, and keep only
|
||||
frames 25-30.
|
||||
|
||||
The actual number of buffered frames also depends on the value of the
|
||||
``concurrent-frames`` option. Currently, both option values are
|
||||
@@ -721,7 +720,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>``
|
||||
@@ -745,7 +744,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
|
||||
@@ -803,6 +802,6 @@ Available filters are:
|
||||
|
||||
``buffer=<num>``
|
||||
Buffer ``<num>`` frames in the filter chain. This filter is probably pretty
|
||||
useless, except for debugging. (Note that this won't help smoothing out
|
||||
useless, except for debugging. (Note that this won't help to smooth out
|
||||
latencies with decoding, because the filter will never output a frame if
|
||||
the buffer isn't full, except on EOF.)
|
||||
+65
-24
@@ -105,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.
|
||||
@@ -311,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>``
|
||||
|
||||
@@ -428,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
|
||||
@@ -435,10 +447,12 @@ Available video output drivers are:
|
||||
Note that this option never affects ``cscale``.
|
||||
|
||||
``pbo``
|
||||
Enable use of PBOs. This is slightly faster, but can sometimes lead to
|
||||
sporadic and temporary image corruption (in theory, because reupload
|
||||
is not retried when it fails), and perhaps actually triggers slower
|
||||
paths with drivers that don't support PBOs properly.
|
||||
Enable use of PBOs. On some drivers this can be faster, especially if
|
||||
the source video size is huge (e.g. so called "4K" video). On other
|
||||
drivers it might be slower or cause latency issues.
|
||||
|
||||
In theory, this can sometimes lead to sporadic and temporary image
|
||||
corruption (because reupload is not retried when it fails).
|
||||
|
||||
``dither-depth=<N|no|auto>``
|
||||
Set dither target depth to N. Default: no.
|
||||
@@ -469,7 +483,7 @@ Available video output drivers are:
|
||||
|
||||
``temporal-dither``
|
||||
Enable temporal dithering. (Only active if dithering is enabled in
|
||||
general.) This changes between 8 different dithering pattern on each
|
||||
general.) This changes between 8 different dithering patterns on each
|
||||
frame by changing the orientation of the tiled dithering matrix.
|
||||
Unfortunately, this can lead to flicker on LCD displays, since these
|
||||
have a high reaction time.
|
||||
@@ -480,7 +494,7 @@ Available video output drivers are:
|
||||
video frame, 2 on every other frame, etc.
|
||||
|
||||
``debug``
|
||||
Check for OpenGL errors, i.e. call ``glGetError()``. Also request a
|
||||
Check for OpenGL errors, i.e. call ``glGetError()``. Also, request a
|
||||
debug OpenGL context (which does nothing with current graphics drivers
|
||||
as of this writing).
|
||||
|
||||
@@ -488,6 +502,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.
|
||||
@@ -519,7 +537,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
|
||||
@@ -543,7 +561,8 @@ Available video output drivers are:
|
||||
|
||||
``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
|
||||
@@ -610,11 +629,11 @@ Available video output drivers are:
|
||||
|
||||
``ubo``
|
||||
Upload these weights via uniform buffer objects. This is the
|
||||
default. (requires OpenGL 3.1)
|
||||
default. (requires OpenGL 3.1 / GLES 3.0)
|
||||
|
||||
``shader``
|
||||
Hard code all the weights into the shader source code. (requires
|
||||
OpenGL 3.3)
|
||||
OpenGL 3.3 / GLES 3.0)
|
||||
|
||||
|
||||
``pre-shaders=<files>``, ``post-shaders=<files>``, ``scale-shader=<file>``
|
||||
@@ -724,7 +743,7 @@ Available video output drivers are:
|
||||
|
||||
``glfinish``
|
||||
Call ``glFinish()`` before and after swapping buffers (default: disabled).
|
||||
Slower, but might help getting better results when doing framedropping.
|
||||
Slower, but might improve results when doing framedropping.
|
||||
Can completely ruin performance. The details depend entirely on the
|
||||
OpenGL driver.
|
||||
|
||||
@@ -753,9 +772,9 @@ Available video output drivers are:
|
||||
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
|
||||
This may help to get 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
|
||||
DWM.
|
||||
|
||||
Windows only.
|
||||
@@ -774,6 +793,13 @@ 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
|
||||
@@ -783,15 +809,23 @@ Available video output drivers are:
|
||||
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
|
||||
@@ -871,7 +905,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.
|
||||
@@ -922,9 +957,11 @@ Available video output drivers are:
|
||||
things like softsubbed ASS signs to match the video colors,
|
||||
but may cause SRT subtitles or similar to look slightly off.
|
||||
|
||||
``alpha=<blend|yes|no>``
|
||||
Decides what to do if the input has an alpha component (default: blend).
|
||||
``alpha=<blend-tiles|blend|yes|no>``
|
||||
Decides what to do if the input has an alpha component.
|
||||
|
||||
blend-tiles
|
||||
Blend the frame against a 16x16 gray/white tiles background (default).
|
||||
blend
|
||||
Blend the frame against a black background.
|
||||
yes
|
||||
@@ -951,7 +988,7 @@ Available video output drivers are:
|
||||
|
||||
This is equivalent to::
|
||||
|
||||
--vo=opengl:scale=spline36:cscale=spline36:dscale=mitchell:dither-depth=auto:correct-downscaling:sigmoid-upscaling:pbo:deband
|
||||
--vo=opengl:scale=spline36:cscale=spline36:dscale=mitchell:dither-depth=auto:correct-downscaling:sigmoid-upscaling:deband:es=no
|
||||
|
||||
Note that some cheaper LCDs do dithering that gravely interferes with
|
||||
``opengl``'s dithering. Disabling dithering with ``dither-depth=no`` helps.
|
||||
@@ -991,7 +1028,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).
|
||||
@@ -1113,6 +1150,10 @@ 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.
|
||||
Should be used when one doesn't want to install full-blown graphical
|
||||
|
||||
@@ -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:
|
||||
@@ -96,13 +111,10 @@ the separately available build wrapper ([mpv-build][mpv-build]) that first compi
|
||||
libraries and libass, and then compiles the player statically linked against
|
||||
those.
|
||||
|
||||
If you are running Mac OSX and using homebrew we provide [homebrew-mpv][homebrew-mpv], an up
|
||||
to date formula that compiles mpv with sensible dependencies and defaults for
|
||||
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
|
||||
|
||||
|
||||
|
||||
+178
@@ -1,3 +1,181 @@
|
||||
Release 0.15.0
|
||||
==============
|
||||
|
||||
Build System Changes
|
||||
--------------------
|
||||
|
||||
- OS X bundle: remove git sha from the Info.plist version (issue #2677)
|
||||
- add "lua51" ("51obsd") to list of possible lua names
|
||||
- add option to customize config files system path (issue #2704)
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- vo_opengl: implement support for transparent video display on OS X (alpha=yes suboption)
|
||||
- vo_opengl: use a checkerboard pattern as background for transparent video by default
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- add --audio-file-paths (issue #2632)
|
||||
- player, stream_dvb: implement dvb-channel-name property, add switch binding
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- vf_stereo3d: add alternating modes
|
||||
- vo_opengl: disable pbo by default for opengl-hq due to driver problems
|
||||
- vf_yadif: change defaults (issue #2539)
|
||||
- command: change heuristic for files with 1 chapter (issue #2550)
|
||||
- demux_mkv: adjust subtitle preroll defaults
|
||||
- exclude 360 from --video-rotate range (issue #2647)
|
||||
- osd: make osd-width/height properties watchable
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- ao_pulse: check for sample rate bounds, attempt fallback (issue #2654)
|
||||
- ao_wasapi: remove volume "restore" on exit
|
||||
- demux_cue: better error resilience
|
||||
- mixer: fix volume initialization with --af=volume
|
||||
- mpv.desktop: add audio/mp4 mime type
|
||||
- player: detect audio PTS jumps, make video PTS heuristic less aggressive
|
||||
- player: make watch later/resume work when "playing" directories
|
||||
- player: reset playback abort when reloading a file (issue #2568)
|
||||
- recognize frame sequenced 3D Matroska video
|
||||
- stream_dvb: fix channel switching
|
||||
- vaapi: add VP9 profile (requires VA-API 0.38.1 or newer)
|
||||
- vo_opengl: dxinterop: prevent crash after lost device
|
||||
- vo_opengl: enable brightness/contrast controls for RGB
|
||||
- vo_opengl: fix operation on GLES 2.0
|
||||
- vo_opengl: fix operation on GLSL versions earlier than 1.30
|
||||
- vo_opengl: flip screenshot image if backend uses flipped rendering (issue #2635)
|
||||
- vo_opengl: reset nnedi3 weights properly (issue #2661)
|
||||
- vo_rpi: handle rotation
|
||||
- vo_rpi: work around firmware oddness leading to incorrect video rect
|
||||
- windows: fix fd://
|
||||
- ytdl: Include Referer header as well
|
||||
- TOOLS/zsh.pl: add .opus extension in zsh completions
|
||||
|
||||
|
||||
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.14.0..v0.15.0` in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.14.0...v0.15.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.14.0..v0.15.0
|
||||
|
||||
|
||||
|
||||
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
|
||||
==============
|
||||
|
||||
|
||||
-1014
File diff suppressed because it is too large.
Load diff
@@ -1,492 +0,0 @@
|
||||
# MPlayer Makefile
|
||||
#
|
||||
# copyright (c) 2008 Diego Biurrun
|
||||
# Rewritten entirely from a set of Makefiles written by Arpi and many others.
|
||||
#
|
||||
# This file is part of MPlayer.
|
||||
#
|
||||
# MPlayer is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# MPlayer is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
include old_build/config.mak
|
||||
|
||||
###### variable declarations #######
|
||||
|
||||
SOURCES_AUDIO_INPUT-$(ALSA) += stream/ai_alsa1x.c
|
||||
SOURCES_AUDIO_INPUT-$(OSS_AUDIO)+= stream/ai_oss.c
|
||||
SOURCES_AUDIO_INPUT-$(SNDIO) += stream/ai_sndio.c
|
||||
SOURCES-$(AUDIO_INPUT) += $(SOURCES_AUDIO_INPUT-yes)
|
||||
SOURCES-$(CDDA) += stream/stream_cdda.c
|
||||
SOURCES-$(DVBIN) += stream/dvb_tune.c \
|
||||
stream/stream_dvb.c
|
||||
SOURCES-$(DVDREAD) += stream/stream_dvd.c \
|
||||
stream/stream_dvd_common.c
|
||||
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-$(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
|
||||
|
||||
SOURCES-$(TV_V4L2) += stream/tvi_v4l2.c stream/audio_in.c
|
||||
SOURCES-$(DUMMY_OSD) += sub/osd_dummy.c
|
||||
SOURCES-$(LIBASS_OSD) += sub/osd_libass.c
|
||||
|
||||
SOURCES-$(ALSA) += audio/out/ao_alsa.c
|
||||
SOURCES-$(CACA) += video/out/vo_caca.c
|
||||
SOURCES-$(SDL2) += audio/out/ao_sdl.c video/out/vo_sdl.c
|
||||
SOURCES-$(GL) += video/out/opengl/common.c \
|
||||
video/out/opengl/osd.c \
|
||||
video/out/opengl/lcms.c \
|
||||
video/out/opengl/video.c \
|
||||
video/out/opengl/video_shaders.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
|
||||
|
||||
SOURCES-$(ENCODING) += video/out/vo_lavc.c audio/out/ao_lavc.c \
|
||||
common/encode_lavc.c
|
||||
|
||||
SOURCES-$(GL_X11) += video/out/x11_common.c video/out/opengl/x11.c
|
||||
SOURCES-$(EGL_X11) += video/out/x11_common.c video/out/opengl/x11egl.c
|
||||
SOURCES-$(GL_WAYLAND) += video/out/wayland_common.c \
|
||||
video/out/opengl/wayland.c
|
||||
|
||||
SOURCES-$(JACK) += audio/out/ao_jack.c
|
||||
SOURCES-$(OPENAL) += audio/out/ao_openal.c
|
||||
SOURCES-$(OSS_AUDIO) += audio/out/ao_oss.c
|
||||
SOURCES-$(PULSE) += audio/out/ao_pulse.c
|
||||
SOURCES-$(RSOUND) += audio/out/ao_rsound.c
|
||||
SOURCES-$(SNDIO) += audio/out/ao_sndio.c
|
||||
SOURCES-$(VDPAU) += video/vdpau.c video/vdpau_mixer.c \
|
||||
video/out/vo_vdpau.c video/decode/vdpau.c \
|
||||
video/filter/vf_vdpaupp.c \
|
||||
video/filter/vf_vdpaurb.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_GLX) += video/out/opengl/hwdec_vaglx.c
|
||||
SOURCES-$(VAAPI_X_EGL) += video/out/opengl/hwdec_vaegl.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 \
|
||||
video/out/wayland/memfile.c
|
||||
|
||||
SOURCES-$(LIBAVFILTER) += video/filter/vf_lavfi.c \
|
||||
video/filter/vf_gradfun.c \
|
||||
video/filter/vf_pullup.c \
|
||||
video/filter/vf_rotate.c \
|
||||
video/filter/vf_yadif.c \
|
||||
audio/filter/af_lavfi.c
|
||||
|
||||
SOURCES-$(LUA) += player/lua.c
|
||||
SOURCES-$(VAPOURSYNTH_CORE) += video/filter/vf_vapoursynth.c
|
||||
SOURCES-$(LIBARCHIVE) += demux/demux_libarchive.c \
|
||||
stream/stream_libarchive.c
|
||||
SOURCES-$(DLOPEN) += video/filter/vf_dlopen.c
|
||||
|
||||
SOURCES = audio/audio.c \
|
||||
audio/audio_buffer.c \
|
||||
audio/chmap.c \
|
||||
audio/chmap_sel.c \
|
||||
audio/fmt-conversion.c \
|
||||
audio/format.c \
|
||||
audio/mixer.c \
|
||||
audio/decode/ad_lavc.c \
|
||||
audio/decode/ad_spdif.c \
|
||||
audio/decode/dec_audio.c \
|
||||
audio/filter/af.c \
|
||||
audio/filter/af_channels.c \
|
||||
audio/filter/af_delay.c \
|
||||
audio/filter/af_equalizer.c \
|
||||
audio/filter/af_format.c \
|
||||
audio/filter/af_lavcac3enc.c \
|
||||
audio/filter/af_lavrresample.c \
|
||||
audio/filter/af_pan.c \
|
||||
audio/filter/af_scaletempo.c \
|
||||
audio/filter/af_drc.c \
|
||||
audio/filter/af_volume.c \
|
||||
audio/filter/tools.c \
|
||||
audio/out/ao.c \
|
||||
audio/out/ao_null.c \
|
||||
audio/out/ao_pcm.c \
|
||||
audio/out/pull.c \
|
||||
audio/out/push.c \
|
||||
common/av_common.c \
|
||||
common/av_log.c \
|
||||
common/codecs.c \
|
||||
common/common.c \
|
||||
common/msg.c \
|
||||
common/playlist.c \
|
||||
common/tags.c \
|
||||
common/version.c \
|
||||
demux/codec_tags.c \
|
||||
demux/cue.c \
|
||||
demux/demux.c \
|
||||
demux/demux_edl.c \
|
||||
demux/demux_cue.c \
|
||||
demux/demux_disc.c \
|
||||
demux/demux_lavf.c \
|
||||
demux/demux_mf.c \
|
||||
demux/demux_mkv.c \
|
||||
demux/demux_mkv_timeline.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 \
|
||||
input/cmd_list.c \
|
||||
input/cmd_parse.c \
|
||||
input/event.c \
|
||||
input/input.c \
|
||||
input/ipc.c \
|
||||
input/keycodes.c \
|
||||
misc/bstr.c \
|
||||
misc/charset_conv.c \
|
||||
misc/dispatch.c \
|
||||
misc/json.c \
|
||||
misc/rendezvous.c \
|
||||
misc/ring.c \
|
||||
options/m_config.c \
|
||||
options/m_option.c \
|
||||
options/m_property.c \
|
||||
options/options.c \
|
||||
options/parse_commandline.c \
|
||||
options/parse_configfile.c \
|
||||
options/path.c \
|
||||
osdep/io.c \
|
||||
osdep/path-unix.c \
|
||||
osdep/semaphore_osx.c \
|
||||
osdep/subprocess.c \
|
||||
osdep/subprocess-posix.c \
|
||||
osdep/terminal-unix.c \
|
||||
osdep/timer.c \
|
||||
osdep/timer-linux.c \
|
||||
osdep/threads.c \
|
||||
player/audio.c \
|
||||
player/client.c \
|
||||
player/configfiles.c \
|
||||
player/command.c \
|
||||
player/external_files.c \
|
||||
player/loadfile.c \
|
||||
player/main.c \
|
||||
player/misc.c \
|
||||
player/osd.c \
|
||||
player/playloop.c \
|
||||
player/screenshot.c \
|
||||
player/scripting.c \
|
||||
player/sub.c \
|
||||
player/video.c \
|
||||
stream/cache.c \
|
||||
stream/cache_file.c \
|
||||
stream/cookies.c \
|
||||
stream/rar.c \
|
||||
stream/stream.c \
|
||||
stream/stream_avdevice.c \
|
||||
stream/stream_edl.c \
|
||||
stream/stream_file.c \
|
||||
stream/stream_lavf.c \
|
||||
stream/stream_memory.c \
|
||||
stream/stream_mf.c \
|
||||
stream/stream_null.c \
|
||||
stream/stream_rar.c \
|
||||
sub/dec_sub.c \
|
||||
sub/draw_bmp.c \
|
||||
sub/img_convert.c \
|
||||
sub/osd.c \
|
||||
sub/sd_lavc.c \
|
||||
sub/sd_lavc_conv.c \
|
||||
sub/sd_lavf_srt.c \
|
||||
sub/sd_microdvd.c \
|
||||
sub/sd_movtext.c \
|
||||
sub/sd_srt.c \
|
||||
ta/ta.c \
|
||||
ta/ta_utils.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 \
|
||||
video/mp_image_pool.c \
|
||||
video/sws_utils.c \
|
||||
video/decode/dec_video.c \
|
||||
video/decode/vd_lavc.c \
|
||||
video/filter/vf.c \
|
||||
video/filter/vf_buffer.c \
|
||||
video/filter/vf_crop.c \
|
||||
video/filter/vf_dsize.c \
|
||||
video/filter/vf_eq.c \
|
||||
video/filter/vf_expand.c \
|
||||
video/filter/vf_flip.c \
|
||||
video/filter/vf_format.c \
|
||||
video/filter/vf_mirror.c \
|
||||
video/filter/vf_noformat.c \
|
||||
video/filter/vf_scale.c \
|
||||
video/filter/vf_stereo3d.c \
|
||||
video/filter/vf_sub.c \
|
||||
video/out/bitmap_packer.c \
|
||||
video/out/aspect.c \
|
||||
video/out/filter_kernels.c \
|
||||
video/out/vo.c \
|
||||
video/out/vo_null.c \
|
||||
video/out/vo_image.c \
|
||||
video/out/win_state.c \
|
||||
$(SOURCES-yes)
|
||||
|
||||
OBJECTS += $(addsuffix .o, $(basename $(SOURCES)))
|
||||
OBJECTS += $(OBJECTS-yes)
|
||||
|
||||
DEP_FILES = $(patsubst %.S,%.d,$(patsubst %.cpp,%.d,$(patsubst %.c,%.d,$(SOURCES:.m=.d) $(SOURCES:.m=.d))))
|
||||
|
||||
ALL_TARGETS += mpv
|
||||
|
||||
INSTALL_BIN += install-mpv
|
||||
INSTALL_BIN_STRIP += install-mpv-strip
|
||||
INSTALL_MAN =
|
||||
|
||||
ifeq ($(BUILD_MAN),yes)
|
||||
INSTALL_MAN += install-mpv-man
|
||||
ALL_TARGETS += DOCS/man/mpv.1
|
||||
endif
|
||||
|
||||
DIRS = . \
|
||||
audio \
|
||||
audio/decode \
|
||||
audio/filter \
|
||||
audio/out \
|
||||
common \
|
||||
compat \
|
||||
input \
|
||||
player/timeline \
|
||||
demux \
|
||||
misc \
|
||||
options \
|
||||
osdep \
|
||||
osdep/ar \
|
||||
player \
|
||||
stream \
|
||||
sub \
|
||||
ta \
|
||||
video \
|
||||
video/decode \
|
||||
video/filter \
|
||||
video/out
|
||||
|
||||
|
||||
ADDSUFFIXES = $(foreach suf,$(1),$(addsuffix $(suf),$(2)))
|
||||
ADD_ALL_DIRS = $(call ADDSUFFIXES,$(1),$(DIRS))
|
||||
|
||||
###### brief build output #######
|
||||
|
||||
ifndef V
|
||||
$(eval override CC = @printf "CC\t$$@\n"; $(CC))
|
||||
$(eval override RM = @$(RM))
|
||||
endif
|
||||
|
||||
###### generic rules #######
|
||||
|
||||
all: $(ALL_TARGETS)
|
||||
|
||||
%.1: %.rst
|
||||
$(RST2MAN) $< $@
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(DEPFLAGS) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
mpv: $(OBJECTS) osdep/main-fn-unix.o
|
||||
$(CC) -o $@ $^ $(EXTRALIBS)
|
||||
|
||||
input/input.c: input/input_conf.h
|
||||
input/input_conf.h: TOOLS/file2string.pl etc/input.conf
|
||||
./$^ >$@
|
||||
|
||||
MKVLIB_DEPS = TOOLS/lib/Parse/Matroska.pm \
|
||||
TOOLS/lib/Parse/Matroska/Definitions.pm \
|
||||
TOOLS/lib/Parse/Matroska/Element.pm \
|
||||
TOOLS/lib/Parse/Matroska/Reader.pm \
|
||||
TOOLS/lib/Parse/Matroska/Utils.pm \
|
||||
|
||||
demux/ebml.c demux/demux_mkv.c: demux/ebml_types.h
|
||||
demux/ebml_types.h: TOOLS/matroska.pl $(MKVLIB_DEPS)
|
||||
./$< --generate-header > $@
|
||||
|
||||
demux/ebml.c: demux/ebml_defs.c
|
||||
demux/ebml_defs.c: TOOLS/matroska.pl $(MKVLIB_DEPS)
|
||||
./$< --generate-definitions > $@
|
||||
|
||||
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
|
||||
./$^ >$@
|
||||
|
||||
player/lua/%.inc: TOOLS/file2string.pl player/lua/%.lua
|
||||
./$^ >$@
|
||||
|
||||
player/lua.c: player/lua/defaults.inc \
|
||||
player/lua/assdraw.inc \
|
||||
player/lua/osc.inc \
|
||||
player/lua/ytdl_hook.inc \
|
||||
player/lua/options.inc
|
||||
|
||||
etc/_mpv: TOOLS/zsh.pl ./mpv
|
||||
./$< > $@
|
||||
|
||||
# ./configure must be rerun if it changed
|
||||
config.mak: configure
|
||||
@echo "############################################################"
|
||||
@echo "####### Please run ./configure again - it's changed! #######"
|
||||
@echo "############################################################"
|
||||
|
||||
old_build/version.h .version: version.sh
|
||||
./version.sh --versionh=old_build/version.h
|
||||
|
||||
# Force version.sh to run to potentially regenerate version.h
|
||||
-include .version
|
||||
|
||||
%: %.c
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
|
||||
###### dependency declarations / specific CFLAGS ######
|
||||
|
||||
common/version.c: old_build/version.h
|
||||
|
||||
DOCS/man/mpv.1: DOCS/man/af.rst \
|
||||
DOCS/man/ao.rst \
|
||||
DOCS/man/changes.rst \
|
||||
DOCS/man/encode.rst \
|
||||
DOCS/man/input.rst \
|
||||
DOCS/man/options.rst \
|
||||
DOCS/man/vf.rst \
|
||||
DOCS/man/vo.rst
|
||||
|
||||
###### installation / clean / generic rules #######
|
||||
|
||||
install: $(INSTALL_BIN) install-data $(INSTALL_MAN)
|
||||
install-no-man: $(INSTALL_BIN) install-data
|
||||
install-strip: $(INSTALL_BIN_STRIP) install-data $(INSTALL_MAN)
|
||||
install-strip-no-man: $(INSTALL_BIN_STRIP) install-data
|
||||
|
||||
install-dirs:
|
||||
if test ! -d $(BINDIR) ; then $(INSTALL) -d $(BINDIR) ; fi
|
||||
|
||||
install-%: % install-dirs
|
||||
$(INSTALL) -m 755 $< $(BINDIR)
|
||||
|
||||
install-%-strip: % install-dirs
|
||||
$(INSTALL) -m 755 -s $< $(BINDIR)
|
||||
|
||||
install-mpv-man: install-mpv-man-en
|
||||
|
||||
install-mpv-man-en: DOCS/man/mpv.1
|
||||
if test ! -d $(MANDIR)/man1 ; then $(INSTALL) -d $(MANDIR)/man1 ; fi
|
||||
$(INSTALL) -m 644 DOCS/man/mpv.1 $(MANDIR)/man1/
|
||||
|
||||
ICONSIZES = 16x16 32x32 64x64
|
||||
|
||||
define ICON_INSTALL_RULE
|
||||
install-mpv-icon-$(size): etc/mpv-icon-8bit-$(size).png
|
||||
$(INSTALL) -d $(prefix)/share/icons/hicolor/$(size)/apps
|
||||
$(INSTALL) -m 644 etc/mpv-icon-8bit-$(size).png $(prefix)/share/icons/hicolor/$(size)/apps/mpv.png
|
||||
endef
|
||||
|
||||
$(foreach size,$(ICONSIZES),$(eval $(ICON_INSTALL_RULE)))
|
||||
|
||||
install-mpv-icons: $(foreach size,$(ICONSIZES),install-mpv-icon-$(size))
|
||||
|
||||
install-mpv-desktop: etc/mpv.desktop
|
||||
$(INSTALL) -d $(prefix)/share/applications
|
||||
$(INSTALL) -m 644 etc/mpv.desktop $(prefix)/share/applications/
|
||||
|
||||
install-mpv-config: etc/encoding-profiles.conf
|
||||
$(INSTALL) -d $(CONFDIR)
|
||||
$(INSTALL) -m 644 etc/encoding-profiles.conf $(CONFDIR)
|
||||
|
||||
install-mpv-zsh: etc/_mpv
|
||||
$(INSTALL) -d $(prefix)/share/zsh/vendor-completions
|
||||
$(INSTALL) -m 644 etc/_mpv $(prefix)/share/zsh/vendor-completions/
|
||||
|
||||
install-data: install-mpv-icons install-mpv-desktop install-mpv-config install-mpv-zsh
|
||||
|
||||
uninstall:
|
||||
$(RM) $(BINDIR)/mpv
|
||||
$(RM) $(MANDIR)/man1/mpv.1 $(MANDIR)/man1/mpv.1
|
||||
$(RM) $(prefix)/share/applications/mpv.desktop
|
||||
$(RM) $(prefix)/share/zsh/vendor-completions/_mpv
|
||||
$(RM) $(foreach size,$(ICONSIZES),$(prefix)/share/icons/hicolor/$(size)/apps/mpv.png)
|
||||
|
||||
clean:
|
||||
-$(RM) $(call ADD_ALL_DIRS,/*.o /*.d /*.a /*.ho /*~)
|
||||
-$(RM) $(call ADD_ALL_DIRS,/*.o /*.a /*.ho /*~)
|
||||
-$(RM) mpv
|
||||
-$(RM) DOCS/man/*/mpv.1
|
||||
-$(RM) old_build/version.h version.h
|
||||
-$(RM) input/input_conf.h
|
||||
-$(RM) video/out/vdpau_template.c
|
||||
-$(RM) demux/ebml_types.h demux/ebml_defs.c
|
||||
-$(RM) video/out/x11_icon.inc
|
||||
-$(RM) sub/osd_font.h
|
||||
-$(RM) player/lua/defaults.inc
|
||||
-$(RM) player/lua/assdraw.inc
|
||||
-$(RM) player/lua/osc.inc
|
||||
-$(RM) player/lua/ytdl_hook.inc
|
||||
-$(RM) player/lua/options.inc
|
||||
|
||||
distclean: clean
|
||||
-$(RM) config.log old_build/config.h old_build/config.mak Makefile
|
||||
-rmdir old_build/
|
||||
|
||||
-include $(DEP_FILES)
|
||||
|
||||
.PHONY: all *install* *clean .version
|
||||
|
||||
# Disable suffix rules. Most of the builtin rules are suffix rules,
|
||||
# so this saves some time on slow systems.
|
||||
.SUFFIXES:
|
||||
|
||||
# If a command returns failure but changed its target file, delete the
|
||||
# (presumably malformed) file. Otherwise the file would be considered to
|
||||
# be up to date if make is restarted.
|
||||
|
||||
.DELETE_ON_ERROR:
|
||||
+10
-1
@@ -39,8 +39,17 @@ def apply_plist_template(plist_file, version):
|
||||
for line in fileinput.input(plist_file, inplace=1):
|
||||
print (line.rstrip().replace('${VERSION}', version))
|
||||
|
||||
def bundle_version():
|
||||
if os.path.exists('VERSION'):
|
||||
x = open('VERSION')
|
||||
version = x.read()
|
||||
x.close()
|
||||
else:
|
||||
version = sh("./version.sh").strip()
|
||||
return version
|
||||
|
||||
def main():
|
||||
version = sh("./version.sh").strip()
|
||||
version = bundle_version().rstrip()
|
||||
|
||||
usage = "usage: %prog [options] arg"
|
||||
parser = OptionParser(usage)
|
||||
|
||||
+9
-3
@@ -40,7 +40,7 @@ class G:
|
||||
events = {}
|
||||
start = None
|
||||
markers = ["o", "s", "t", "d"]
|
||||
curveno = 0
|
||||
curveno = {}
|
||||
|
||||
def find_marker():
|
||||
if len(G.markers) == 0:
|
||||
@@ -66,6 +66,10 @@ def get_event(event, evtype):
|
||||
G.events[event] = e
|
||||
return G.events[event]
|
||||
|
||||
colors = [(0.0, 0.5, 0.0), (0.0, 0.0, 1.0), (0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.75, 0.75, 0), (0.0, 0.75, 0.75), (0.75, 0, 0.75)]
|
||||
def mkColor(t):
|
||||
return pg.mkColor(int(t[0] * 255), int(t[1] * 255), int(t[2] * 255))
|
||||
|
||||
SCALE = 1e6 # microseconds to seconds
|
||||
|
||||
for line in [line.split("#")[0].strip() for line in open(filename, "r")]:
|
||||
@@ -152,8 +156,10 @@ for e in G.sevents:
|
||||
args['symbol'] = e.marker
|
||||
args['pen'] = None
|
||||
else:
|
||||
args['pen'] = pg.mkPen(pg.intColor(G.curveno), width=0)
|
||||
G.curveno += 1
|
||||
if not cur in G.curveno:
|
||||
G.curveno[cur] = 0
|
||||
args['pen'] = pg.mkPen(mkColor(colors[G.curveno[cur] % len(colors)]), width=0)
|
||||
G.curveno[cur] += 1
|
||||
n = cur.plot([x for x,y in e.vals], [y for x,y in e.vals], **args)
|
||||
|
||||
QtGui.QApplication.instance().exec_()
|
||||
+1
-1
@@ -120,7 +120,7 @@ $profile_comp
|
||||
_tags files urls
|
||||
while _tags; do
|
||||
_requested files expl 'media file' _files -g \\
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|qt|rm|ts|vob|wav|webm|wma|wmv)(-.)" && rc=0
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|opus|qt|rm|ts|vob|wav|webm|wma|wmv)(-.)" && rc=0
|
||||
if _requested urls; then
|
||||
while _next_label urls expl URL; do
|
||||
_urls "\$expl[@]" && rc=0
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
#include <libavutil/frame.h>
|
||||
#include <libavutil/version.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "common/common.h"
|
||||
#include "fmt-conversion.h"
|
||||
#include "audio.h"
|
||||
|
||||
+15
-9
@@ -274,19 +274,25 @@ static bool mp_chmap_is_better(struct mp_chmap *req, struct mp_chmap *old,
|
||||
if (new_lost_r != old_lost_r)
|
||||
return new_lost_r < old_lost_r;
|
||||
|
||||
int old_lost = mp_chmap_diffn(req, old);
|
||||
int new_lost = mp_chmap_diffn(req, new);
|
||||
|
||||
// If the situation is equal with replaced speakers, but one of them loses
|
||||
// less if no replacements are performed, pick the better one, even if it
|
||||
// means an upmix. This prefers exact supersets over inexact equivalents.
|
||||
if (new_lost != old_lost)
|
||||
return new_lost < old_lost;
|
||||
|
||||
struct mp_chmap old_p = *old, new_p = *new;
|
||||
mp_chmap_remove_na(&old_p);
|
||||
mp_chmap_remove_na(&new_p);
|
||||
|
||||
// If the situation is equal with replaced speakers, but the replacement is
|
||||
// perfect for only one of them, let the better one win. This prefers
|
||||
// inexact equivalents over exact supersets.
|
||||
bool perfect_r_new = !new_lost_r && new_p.num <= old_p.num;
|
||||
bool perfect_r_old = !old_lost_r && old_p.num <= new_p.num;
|
||||
if (perfect_r_new != perfect_r_old)
|
||||
return perfect_r_new;
|
||||
|
||||
int old_lost = mp_chmap_diffn(req, old);
|
||||
int new_lost = mp_chmap_diffn(req, new);
|
||||
// If the situation is equal with replaced speakers, pick the better one,
|
||||
// even if it means an upmix.
|
||||
if (new_lost != old_lost)
|
||||
return new_lost < old_lost;
|
||||
|
||||
// Some kind of upmix. If it's perfect, prefer the smaller one. Even if not,
|
||||
// both have equal loss, so also prefer the smaller one.
|
||||
// Drop padding channels (NA) for the sake of this check, as the number of
|
||||
|
||||
+16
-17
@@ -26,7 +26,7 @@
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/intreadwrite.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "common/av_common.h"
|
||||
@@ -79,12 +79,12 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
AVCodecContext *lavc_context;
|
||||
AVCodec *lavc_codec;
|
||||
struct sh_stream *sh = da->header;
|
||||
struct sh_audio *sh_audio = sh->audio;
|
||||
struct mp_codec_params *c = sh->codec;
|
||||
|
||||
struct priv *ctx = talloc_zero(NULL, struct priv);
|
||||
da->priv = ctx;
|
||||
|
||||
ctx->force_channel_map = sh_audio->force_channels;
|
||||
ctx->force_channel_map = c->force_channels;
|
||||
|
||||
lavc_codec = avcodec_find_decoder_by_name(decoder);
|
||||
if (!lavc_codec) {
|
||||
@@ -116,20 +116,20 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
|
||||
mp_set_avopts(da->log, lavc_context, opts->avopts);
|
||||
|
||||
lavc_context->codec_tag = sh->codec_tag;
|
||||
lavc_context->sample_rate = sh_audio->samplerate;
|
||||
lavc_context->bit_rate = sh_audio->bitrate;
|
||||
lavc_context->block_align = sh_audio->block_align;
|
||||
lavc_context->bits_per_coded_sample = sh_audio->bits_per_coded_sample;
|
||||
lavc_context->channels = sh_audio->channels.num;
|
||||
if (!mp_chmap_is_unknown(&sh_audio->channels))
|
||||
lavc_context->channel_layout = mp_chmap_to_lavc(&sh_audio->channels);
|
||||
lavc_context->codec_tag = c->codec_tag;
|
||||
lavc_context->sample_rate = c->samplerate;
|
||||
lavc_context->bit_rate = c->bitrate;
|
||||
lavc_context->block_align = c->block_align;
|
||||
lavc_context->bits_per_coded_sample = c->bits_per_coded_sample;
|
||||
lavc_context->channels = c->channels.num;
|
||||
if (!mp_chmap_is_unknown(&c->channels))
|
||||
lavc_context->channel_layout = mp_chmap_to_lavc(&c->channels);
|
||||
|
||||
// demux_mkv
|
||||
mp_lavc_set_extradata(lavc_context, sh->extradata, sh->extradata_size);
|
||||
mp_lavc_set_extradata(lavc_context, c->extradata, c->extradata_size);
|
||||
|
||||
if (sh->lav_headers)
|
||||
mp_copy_lav_codec_headers(lavc_context, sh->lav_headers);
|
||||
if (c->lav_headers)
|
||||
mp_copy_lav_codec_headers(lavc_context, c->lav_headers);
|
||||
|
||||
mp_set_avcodec_threads(da->log, lavc_context, opts->threads);
|
||||
|
||||
@@ -228,9 +228,8 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
if (lavc_chmap.num != avctx->channels)
|
||||
mp_chmap_from_channels(&lavc_chmap, avctx->channels);
|
||||
if (priv->force_channel_map) {
|
||||
struct sh_audio *sh_audio = da->header->audio;
|
||||
if (lavc_chmap.num == sh_audio->channels.num)
|
||||
lavc_chmap = sh_audio->channels;
|
||||
if (lavc_chmap.num == da->header->codec->channels.num)
|
||||
lavc_chmap = da->header->codec->channels;
|
||||
}
|
||||
mp_audio_set_channels(mpframe, &lavc_chmap);
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ struct mp_decoder_list *audio_decoder_list(void)
|
||||
static struct mp_decoder_list *audio_select_decoders(struct dec_audio *d_audio)
|
||||
{
|
||||
struct MPOpts *opts = d_audio->opts;
|
||||
const char *codec = d_audio->header->codec;
|
||||
const char *codec = d_audio->header->codec->codec;
|
||||
|
||||
struct mp_decoder_list *list = audio_decoder_list();
|
||||
struct mp_decoder_list *new =
|
||||
@@ -146,7 +146,7 @@ int audio_init_best_codec(struct dec_audio *d_audio)
|
||||
MP_VERBOSE(d_audio, "Selected audio codec: %s\n", d_audio->decoder_desc);
|
||||
} else {
|
||||
MP_ERR(d_audio, "Failed to initialize an audio decoder for codec '%s'.\n",
|
||||
d_audio->header->codec ? d_audio->header->codec : "<unknown>");
|
||||
d_audio->header->codec->codec);
|
||||
}
|
||||
|
||||
talloc_free(list);
|
||||
@@ -177,11 +177,21 @@ static int decode_new_frame(struct dec_audio *da)
|
||||
da->pts += da->pts_offset / (double)da->waiting->rate;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
double newpts = da->waiting->pts;
|
||||
// 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)
|
||||
fabs(da->pts - newpts) > 0.001)
|
||||
{
|
||||
// Attempt to detect jumps in PTS. Even for the lowest
|
||||
// sample rates and with worst container rounded timestamp,
|
||||
// this should be a margin more than enough.
|
||||
if (da->pts != MP_NOPTS_VALUE && fabs(newpts - da->pts) > 0.1)
|
||||
{
|
||||
MP_WARN(da, "Invalid audio PTS: %f -> %f\n",
|
||||
da->pts, newpts);
|
||||
da->pts_reset = true;
|
||||
}
|
||||
da->pts = da->waiting->pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
@@ -274,6 +284,7 @@ void audio_reset_decoding(struct dec_audio *d_audio)
|
||||
af_seek_reset(d_audio->afilter);
|
||||
d_audio->pts = MP_NOPTS_VALUE;
|
||||
d_audio->pts_offset = 0;
|
||||
d_audio->pts_reset = false;
|
||||
if (d_audio->waiting) {
|
||||
talloc_free(d_audio->waiting);
|
||||
d_audio->waiting = NULL;
|
||||
|
||||
@@ -45,6 +45,8 @@ struct dec_audio {
|
||||
double pts;
|
||||
// number of samples output by decoder after last known pts
|
||||
int pts_offset;
|
||||
// set every time a jump in timestamps is encountered
|
||||
bool pts_reset;
|
||||
// For free use by the ad_driver
|
||||
void *priv;
|
||||
};
|
||||
|
||||
@@ -91,8 +91,6 @@ struct af_resample {
|
||||
int out_rate;
|
||||
int out_format;
|
||||
struct mp_chmap out_channels;
|
||||
|
||||
double missing_samples; // fractional samples not yet output
|
||||
};
|
||||
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
@@ -226,8 +224,6 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
s->out_channels= out->channels;
|
||||
s->in_channels = in->channels;
|
||||
|
||||
s->missing_samples = 0;
|
||||
|
||||
av_opt_set_int(s->avrctx, "filter_size", s->opts.filter_size, 0);
|
||||
av_opt_set_int(s->avrctx, "phase_shift", s->opts.phase_shift, 0);
|
||||
av_opt_set_int(s->avrctx, "linear_interp", s->opts.linear, 0);
|
||||
@@ -427,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.
|
||||
@@ -521,15 +529,18 @@ static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
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 (!need_reinit && s->avrctx) {
|
||||
double speed_factor = s->playback_speed * s->in_rate_af / s->in_rate;
|
||||
int in_samples = in ? in->samples : 0;
|
||||
double wanted_samples = in_samples / speed_factor + s->missing_samples;
|
||||
int wanted_samples_i = lrint(wanted_samples);
|
||||
s->missing_samples = wanted_samples - wanted_samples_i;
|
||||
if (avresample_set_compensation(s->avrctx,
|
||||
(wanted_samples_i - in_samples) * s->out_rate / s->in_rate,
|
||||
wanted_samples_i * s->out_rate / s->in_rate) < 0)
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
+10
-5
@@ -27,7 +27,7 @@
|
||||
#include "audio/filter/af.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "mixer.h"
|
||||
|
||||
struct mixer {
|
||||
@@ -120,8 +120,11 @@ void mixer_getvolume(struct mixer *mixer, float *l, float *r)
|
||||
*r = mixer->vol_r;
|
||||
}
|
||||
|
||||
static void setvolume_internal(struct mixer *mixer, float l, float r)
|
||||
static void setvolume_internal(struct mixer *mixer)
|
||||
{
|
||||
float l = mixer->vol_l, r = mixer->vol_r;
|
||||
if (mixer->emulate_mute && mixer->muted)
|
||||
l = r = 0;
|
||||
if (!mixer->softvol) {
|
||||
MP_DBG(mixer, "Setting volume on AO.\n");
|
||||
struct ao_control_vol vol = {.left = l, .right = r};
|
||||
@@ -147,8 +150,8 @@ void mixer_setvolume(struct mixer *mixer, float l, float r)
|
||||
float max = mixer_getmaxvolume(mixer);
|
||||
mixer->vol_l = MPCLAMP(l, 0, max);
|
||||
mixer->vol_r = MPCLAMP(r, 0, max);
|
||||
if (mixer->ao && !(mixer->emulate_mute && mixer->muted))
|
||||
setvolume_internal(mixer, mixer->vol_l, mixer->vol_r);
|
||||
if (mixer->ao)
|
||||
setvolume_internal(mixer);
|
||||
}
|
||||
|
||||
void mixer_getbothvolume(struct mixer *mixer, float *b)
|
||||
@@ -167,7 +170,7 @@ void mixer_setmute(struct mixer *mixer, bool mute)
|
||||
mixer->muted = mute;
|
||||
mixer->muted_by_us = mute;
|
||||
if (mixer->emulate_mute) {
|
||||
setvolume_internal(mixer, mixer->vol_l*!mute, mixer->vol_r*!mute);
|
||||
setvolume_internal(mixer);
|
||||
} else {
|
||||
ao_control(mixer->ao, AOCONTROL_SET_MUTE, &mute);
|
||||
}
|
||||
@@ -293,6 +296,8 @@ static void restore_volume(struct mixer *mixer)
|
||||
|
||||
const char *prev_driver = mixer->driver;
|
||||
mixer->driver = mixer->softvol ? "softvol" : ao_get_name(ao);
|
||||
if (!prev_driver[0])
|
||||
prev_driver = mixer->driver;
|
||||
|
||||
// Restore old parameters if volume won't survive reinitialization.
|
||||
// But not if volume scale is possibly different.
|
||||
|
||||
+7
-7
@@ -20,7 +20,7 @@
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "ao.h"
|
||||
@@ -45,7 +45,6 @@ extern const struct ao_driver audio_out_jack;
|
||||
extern const struct ao_driver audio_out_openal;
|
||||
extern const struct ao_driver audio_out_null;
|
||||
extern const struct ao_driver audio_out_alsa;
|
||||
extern const struct ao_driver audio_out_dsound;
|
||||
extern const struct ao_driver audio_out_wasapi;
|
||||
extern const struct ao_driver audio_out_pcm;
|
||||
extern const struct ao_driver audio_out_lavc;
|
||||
@@ -65,9 +64,6 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#if HAVE_WASAPI
|
||||
&audio_out_wasapi,
|
||||
#endif
|
||||
#if HAVE_DSOUND
|
||||
&audio_out_dsound,
|
||||
#endif
|
||||
#if HAVE_OSS_AUDIO
|
||||
&audio_out_oss,
|
||||
#endif
|
||||
@@ -493,8 +489,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)
|
||||
|
||||
+15
-1
@@ -977,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;
|
||||
@@ -987,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++) {
|
||||
|
||||
@@ -384,8 +384,8 @@ static int hotplug_init(struct ao *ao)
|
||||
err = AudioObjectAddPropertyListener(
|
||||
kAudioObjectSystemObject, &addr, hotplug_cb, (void *)ao);
|
||||
if (err != noErr) {
|
||||
char *c1 = fourcc_repr(hotplug_properties[i]);
|
||||
char *c2 = fourcc_repr(err);
|
||||
char *c1 = mp_tag_str(hotplug_properties[i]);
|
||||
char *c2 = mp_tag_str(err);
|
||||
MP_ERR(ao, "failed to set device listener %s (%s)", c1, c2);
|
||||
goto coreaudio_error;
|
||||
}
|
||||
@@ -409,8 +409,8 @@ static void hotplug_uninit(struct ao *ao)
|
||||
err = AudioObjectRemovePropertyListener(
|
||||
kAudioObjectSystemObject, &addr, hotplug_cb, (void *)ao);
|
||||
if (err != noErr) {
|
||||
char *c1 = fourcc_repr(hotplug_properties[i]);
|
||||
char *c2 = fourcc_repr(err);
|
||||
char *c1 = mp_tag_str(hotplug_properties[i]);
|
||||
char *c2 = mp_tag_str(err);
|
||||
MP_ERR(ao, "failed to set device listener %s (%s)", c1, c2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
OSStatus ca_get(AudioObjectID id, ca_scope scope, ca_sel selector,
|
||||
uint32_t size, void *data)
|
||||
|
||||
@@ -136,36 +136,11 @@ coreaudio_error:
|
||||
return err;
|
||||
}
|
||||
|
||||
char *fourcc_repr_buf(char *buf, size_t buf_size, uint32_t code)
|
||||
{
|
||||
// Extract FourCC letters from the uint32_t and finde out if it's a valid
|
||||
// code that is made of letters.
|
||||
unsigned char fcc[4] = {
|
||||
(code >> 24) & 0xFF,
|
||||
(code >> 16) & 0xFF,
|
||||
(code >> 8) & 0xFF,
|
||||
code & 0xFF,
|
||||
};
|
||||
|
||||
bool valid_fourcc = true;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (fcc[i] < 32 || fcc[i] >= 128)
|
||||
valid_fourcc = false;
|
||||
}
|
||||
|
||||
if (valid_fourcc)
|
||||
snprintf(buf, buf_size, "'%c%c%c%c'", fcc[0], fcc[1], fcc[2], fcc[3]);
|
||||
else
|
||||
snprintf(buf, buf_size, "%u", (unsigned int)code);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
|
||||
{
|
||||
if (code == noErr) return true;
|
||||
|
||||
mp_msg(ao->log, level, "%s (%s)\n", message, fourcc_repr(code));
|
||||
mp_msg(ao->log, level, "%s (%s)\n", message, mp_tag_str(code));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -258,7 +233,7 @@ void ca_print_asbd(struct ao *ao, const char *description,
|
||||
const AudioStreamBasicDescription *asbd)
|
||||
{
|
||||
uint32_t flags = asbd->mFormatFlags;
|
||||
char *format = fourcc_repr(asbd->mFormatID);
|
||||
char *format = mp_tag_str(asbd->mFormatID);
|
||||
int mpfmt = ca_asbd_to_mp_format(asbd);
|
||||
|
||||
MP_VERBOSE(ao,
|
||||
@@ -470,7 +445,7 @@ int64_t ca_get_device_latency_us(struct ao *ao, AudioDeviceID device)
|
||||
if (err == noErr) {
|
||||
latency_frames += temp;
|
||||
MP_VERBOSE(ao, "Latency property %s: %d frames\n",
|
||||
fourcc_repr(latency_properties[n]), (int)temp);
|
||||
mp_tag_str(latency_properties[n]), (int)temp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,6 @@
|
||||
CFStringRef cfstr_from_cstr(char *str);
|
||||
char *cfstr_get_cstr(CFStringRef cfstr);
|
||||
|
||||
char *fourcc_repr_buf(char *buf, size_t buf_size, uint32_t code);
|
||||
#define fourcc_repr(code) fourcc_repr_buf((char[40]){0}, 40, code)
|
||||
|
||||
bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message);
|
||||
|
||||
#define CHECK_CA_ERROR_L(label, message) \
|
||||
|
||||
@@ -1,707 +0,0 @@
|
||||
/*
|
||||
* Windows DirectSound interface
|
||||
*
|
||||
* Copyright (c) 2004 Gabor Szecsi <deje@miki.hu>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
\todo verify/extend multichannel support
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <windows.h>
|
||||
#define DIRECTSOUND_VERSION 0x0600
|
||||
#include <dsound.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "audio/format.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "common/msg.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/io.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
/**
|
||||
\todo use the definitions from the win32 api headers when they define these
|
||||
*/
|
||||
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
|
||||
#define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092
|
||||
#define WAVE_FORMAT_EXTENSIBLE 0xFFFE
|
||||
|
||||
static const GUID KSDATAFORMAT_SUBTYPE_PCM = {
|
||||
0x1, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
|
||||
};
|
||||
|
||||
#if 0
|
||||
#define DSSPEAKER_HEADPHONE 0x00000001
|
||||
#define DSSPEAKER_MONO 0x00000002
|
||||
#define DSSPEAKER_QUAD 0x00000003
|
||||
#define DSSPEAKER_STEREO 0x00000004
|
||||
#define DSSPEAKER_SURROUND 0x00000005
|
||||
#define DSSPEAKER_5POINT1 0x00000006
|
||||
#endif
|
||||
|
||||
#ifndef _WAVEFORMATEXTENSIBLE_
|
||||
typedef struct {
|
||||
WAVEFORMATEX Format;
|
||||
union {
|
||||
WORD wValidBitsPerSample; /* bits of precision */
|
||||
WORD wSamplesPerBlock; /* valid if wBitsPerSample==0 */
|
||||
WORD wReserved; /* If neither applies, set to zero. */
|
||||
} Samples;
|
||||
DWORD dwChannelMask; /* which channels are */
|
||||
/* present in stream */
|
||||
GUID SubFormat;
|
||||
} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;
|
||||
#endif
|
||||
|
||||
struct priv {
|
||||
HINSTANCE hdsound_dll; ///handle to the dll
|
||||
LPDIRECTSOUND hds; ///direct sound object
|
||||
LPDIRECTSOUNDBUFFER hdspribuf; ///primary direct sound buffer
|
||||
LPDIRECTSOUNDBUFFER hdsbuf; ///secondary direct sound buffer (stream buffer)
|
||||
int buffer_size; ///size in bytes of the direct sound buffer
|
||||
int write_offset; ///offset of the write cursor in the direct sound buffer
|
||||
int min_free_space; ///if the free space is below this value get_space() will return 0
|
||||
///there will always be at least this amout of free space to prevent
|
||||
///get_space() from returning wrong values when buffer is 100% full.
|
||||
///will be replaced with nBlockAlign in init()
|
||||
int underrun_check; ///0 or last reported free space (underrun detection)
|
||||
int device_num; ///wanted device number
|
||||
GUID device; ///guid of the device
|
||||
int audio_volume;
|
||||
|
||||
int device_index;
|
||||
|
||||
int outburst; ///play in multiple of chunks of this size
|
||||
|
||||
int cfg_device;
|
||||
int cfg_buffersize;
|
||||
|
||||
struct ao_device_list *listing; ///temporary during list_devs()
|
||||
};
|
||||
|
||||
/***************************************************************************************/
|
||||
|
||||
/**
|
||||
\brief output error message
|
||||
\param err error code
|
||||
\return string with the error message
|
||||
*/
|
||||
static char * dserr2str(int err)
|
||||
{
|
||||
switch (err) {
|
||||
case DS_OK: return "DS_OK";
|
||||
case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION";
|
||||
case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION";
|
||||
case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL";
|
||||
case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM";
|
||||
case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL";
|
||||
case DSERR_GENERIC: return "DSERR_GENERIC";
|
||||
case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED";
|
||||
case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY";
|
||||
case DSERR_BADFORMAT: return "DSERR_BADFORMAT";
|
||||
case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED";
|
||||
case DSERR_NODRIVER: return "DSERR_NODRIVER";
|
||||
case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED";
|
||||
case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION";
|
||||
case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST";
|
||||
case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO";
|
||||
case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED";
|
||||
case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE";
|
||||
case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
\brief uninitialize direct sound
|
||||
*/
|
||||
static void UninitDirectSound(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
// finally release the DirectSound object
|
||||
if (p->hds) {
|
||||
IDirectSound_Release(p->hds);
|
||||
p->hds = NULL;
|
||||
}
|
||||
// free DSOUND.DLL
|
||||
if (p->hdsound_dll) {
|
||||
FreeLibrary(p->hdsound_dll);
|
||||
p->hdsound_dll = NULL;
|
||||
}
|
||||
MP_VERBOSE(ao, "DirectSound uninitialized\n");
|
||||
}
|
||||
|
||||
/**
|
||||
\brief enumerate direct sound devices
|
||||
\return TRUE to continue with the enumeration
|
||||
*/
|
||||
static BOOL CALLBACK DirectSoundEnum(LPGUID guid, LPCSTR desc, LPCSTR module,
|
||||
LPVOID context)
|
||||
{
|
||||
struct ao *ao = context;
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
MP_VERBOSE(ao, "%i %s ", p->device_index, desc);
|
||||
if (p->device_num == p->device_index) {
|
||||
MP_VERBOSE(ao, "<--");
|
||||
if (guid)
|
||||
memcpy(&p->device, guid, sizeof(GUID));
|
||||
}
|
||||
char *guidstr = talloc_strdup(NULL, "");
|
||||
if (guid) {
|
||||
wchar_t guidwstr[80] = {0};
|
||||
StringFromGUID2(guid, guidwstr, MP_ARRAY_SIZE(guidwstr));
|
||||
char *nstr = mp_to_utf8(NULL, guidwstr);
|
||||
if (nstr) {
|
||||
talloc_free(guidstr);
|
||||
guidstr = nstr;
|
||||
}
|
||||
}
|
||||
if (p->device_num < 0 && ao->device) {
|
||||
if (strcmp(ao->device, guidstr) == 0) {
|
||||
MP_VERBOSE(ao, "<--");
|
||||
p->device_num = p->device_index;
|
||||
if (guid)
|
||||
memcpy(&p->device, guid, sizeof(GUID));
|
||||
}
|
||||
}
|
||||
if (p->listing) {
|
||||
struct ao_device_desc e = {guidstr, desc};
|
||||
ao_device_list_add(p->listing, ao, &e);
|
||||
}
|
||||
talloc_free(guidstr);
|
||||
|
||||
MP_VERBOSE(ao, "\n");
|
||||
p->device_index++;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void EnumDevs(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
p->device_index = 0;
|
||||
p->device_num = p->cfg_device;
|
||||
|
||||
HRESULT (WINAPI *OurDirectSoundEnumerate)(LPDSENUMCALLBACKA, LPVOID);
|
||||
OurDirectSoundEnumerate = (void *)GetProcAddress(p->hdsound_dll,
|
||||
"DirectSoundEnumerateA");
|
||||
|
||||
if (OurDirectSoundEnumerate == NULL) {
|
||||
MP_ERR(ao, "GetProcAddress FAILED\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Enumerate all directsound p->devices
|
||||
MP_VERBOSE(ao, "Output Devices:\n");
|
||||
OurDirectSoundEnumerate(DirectSoundEnum, ao);
|
||||
}
|
||||
|
||||
static int LoadDirectSound(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
// initialize directsound
|
||||
p->hdsound_dll = LoadLibrary(L"DSOUND.DLL");
|
||||
if (p->hdsound_dll == NULL) {
|
||||
MP_ERR(ao, "cannot load DSOUND.DLL\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
bool need_init = !p->hdsound_dll;
|
||||
if (need_init && !LoadDirectSound(ao))
|
||||
return;
|
||||
|
||||
p->listing = list;
|
||||
EnumDevs(ao);
|
||||
p->listing = NULL;
|
||||
|
||||
if (need_init)
|
||||
UninitDirectSound(ao);
|
||||
}
|
||||
|
||||
/**
|
||||
\brief initilize direct sound
|
||||
\return 0 if error, 1 if ok
|
||||
*/
|
||||
static int InitDirectSound(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
DSCAPS dscaps;
|
||||
|
||||
if (!LoadDirectSound(ao))
|
||||
return 0;
|
||||
|
||||
HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
|
||||
OurDirectSoundCreate =
|
||||
(void *)GetProcAddress(p->hdsound_dll, "DirectSoundCreate");
|
||||
|
||||
if (OurDirectSoundCreate == NULL) {
|
||||
MP_ERR(ao, "GetProcAddress FAILED\n");
|
||||
FreeLibrary(p->hdsound_dll);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EnumDevs(ao);
|
||||
|
||||
// Create the direct sound object
|
||||
if (FAILED(OurDirectSoundCreate((p->device_num > 0) ? &p->device : NULL,
|
||||
&p->hds, NULL)))
|
||||
{
|
||||
MP_ERR(ao, "cannot create a DirectSound device\n");
|
||||
FreeLibrary(p->hdsound_dll);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set DirectSound Cooperative level, ie what control we want over Windows
|
||||
* sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
|
||||
* settings of the primary buffer, but also that only the sound of our
|
||||
* application will be audible when it will have the focus.
|
||||
* !!! (this is not really working as intended yet because to set the
|
||||
* cooperative level you need the window handle of your application, and
|
||||
* I don't know of any easy way to get it. Especially since we might play
|
||||
* sound without any video, and so what window handle should we use ???
|
||||
* The hack for now is to use the Desktop window handle - it seems to be
|
||||
* working */
|
||||
if (IDirectSound_SetCooperativeLevel(p->hds, GetDesktopWindow(),
|
||||
DSSCL_EXCLUSIVE))
|
||||
{
|
||||
MP_ERR(ao, "cannot set direct sound cooperative level\n");
|
||||
IDirectSound_Release(p->hds);
|
||||
FreeLibrary(p->hdsound_dll);
|
||||
return 0;
|
||||
}
|
||||
MP_VERBOSE(ao, "DirectSound initialized\n");
|
||||
|
||||
memset(&dscaps, 0, sizeof(DSCAPS));
|
||||
dscaps.dwSize = sizeof(DSCAPS);
|
||||
if (DS_OK == IDirectSound_GetCaps(p->hds, &dscaps)) {
|
||||
if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
|
||||
MP_VERBOSE(ao, "DirectSound is emulated\n");
|
||||
} else {
|
||||
MP_VERBOSE(ao, "cannot get device capabilities\n");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief destroy the direct sound buffer
|
||||
*/
|
||||
static void DestroyBuffer(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (p->hdsbuf) {
|
||||
IDirectSoundBuffer_Release(p->hdsbuf);
|
||||
p->hdsbuf = NULL;
|
||||
}
|
||||
if (p->hdspribuf) {
|
||||
IDirectSoundBuffer_Release(p->hdspribuf);
|
||||
p->hdspribuf = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
\brief fill sound buffer
|
||||
\param data pointer to the sound data to copy
|
||||
\param len length of the data to copy in bytes
|
||||
\return number of copyed bytes
|
||||
*/
|
||||
static int write_buffer(struct ao *ao, unsigned char *data, int len)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
HRESULT res;
|
||||
LPVOID lpvPtr1;
|
||||
DWORD dwBytes1;
|
||||
LPVOID lpvPtr2;
|
||||
DWORD dwBytes2;
|
||||
|
||||
p->underrun_check = 0;
|
||||
|
||||
// Lock the buffer
|
||||
res = IDirectSoundBuffer_Lock(p->hdsbuf, p->write_offset, len, &lpvPtr1,
|
||||
&dwBytes1, &lpvPtr2, &dwBytes2, 0);
|
||||
// If the buffer was lost, restore and retry lock.
|
||||
if (DSERR_BUFFERLOST == res) {
|
||||
IDirectSoundBuffer_Restore(p->hdsbuf);
|
||||
res = IDirectSoundBuffer_Lock(p->hdsbuf, p->write_offset, len, &lpvPtr1,
|
||||
&dwBytes1, &lpvPtr2, &dwBytes2, 0);
|
||||
}
|
||||
|
||||
|
||||
if (SUCCEEDED(res)) {
|
||||
memcpy(lpvPtr1, data, dwBytes1);
|
||||
if (NULL != lpvPtr2)
|
||||
memcpy(lpvPtr2, data + dwBytes1, dwBytes2);
|
||||
p->write_offset += dwBytes1 + dwBytes2;
|
||||
if (p->write_offset >= p->buffer_size)
|
||||
p->write_offset = dwBytes2;
|
||||
|
||||
// Release the data back to DirectSound.
|
||||
res = IDirectSoundBuffer_Unlock(p->hdsbuf, lpvPtr1, dwBytes1, lpvPtr2,
|
||||
dwBytes2);
|
||||
if (SUCCEEDED(res)) {
|
||||
// Success.
|
||||
DWORD status;
|
||||
IDirectSoundBuffer_GetStatus(p->hdsbuf, &status);
|
||||
if (!(status & DSBSTATUS_PLAYING))
|
||||
res = IDirectSoundBuffer_Play(p->hdsbuf, 0, 0, DSBPLAY_LOOPING);
|
||||
return dwBytes1 + dwBytes2;
|
||||
}
|
||||
}
|
||||
// Lock, Unlock, or Restore failed.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/***************************************************************************************/
|
||||
|
||||
/**
|
||||
\brief handle control commands
|
||||
\param cmd command
|
||||
\param arg argument
|
||||
\return CONTROL_OK or CONTROL_UNKNOWN in case the command is not supported
|
||||
*/
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
DWORD volume;
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME: {
|
||||
ao_control_vol_t *vol = (ao_control_vol_t *)arg;
|
||||
vol->left = vol->right = p->audio_volume;
|
||||
return CONTROL_OK;
|
||||
}
|
||||
case AOCONTROL_SET_VOLUME: {
|
||||
ao_control_vol_t *vol = (ao_control_vol_t *)arg;
|
||||
volume = p->audio_volume = vol->right;
|
||||
if (volume < 1)
|
||||
volume = 1;
|
||||
volume = (DWORD)(log10(volume) * 5000.0) - 10000;
|
||||
IDirectSoundBuffer_SetVolume(p->hdsbuf, volume);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief setup sound device
|
||||
\param rate samplerate
|
||||
\param channels number of channels
|
||||
\param format format
|
||||
\param flags unused
|
||||
\return 0=success -1=fail
|
||||
*/
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int res;
|
||||
|
||||
if (!InitDirectSound(ao))
|
||||
return -1;
|
||||
|
||||
p->audio_volume = 100;
|
||||
|
||||
// ok, now create the buffers
|
||||
WAVEFORMATEXTENSIBLE wformat;
|
||||
DSBUFFERDESC dsbpridesc;
|
||||
DSBUFFERDESC dsbdesc;
|
||||
int format = af_fmt_from_planar(ao->format);
|
||||
int rate = ao->samplerate;
|
||||
|
||||
if (!af_fmt_is_spdif(format)) {
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_waveext(&sel);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
return -1;
|
||||
}
|
||||
switch (format) {
|
||||
case AF_FORMAT_S24:
|
||||
case AF_FORMAT_S16:
|
||||
case AF_FORMAT_U8:
|
||||
break;
|
||||
default:
|
||||
if (af_fmt_is_spdif(format))
|
||||
break;
|
||||
MP_VERBOSE(ao, "format %s not supported defaulting to Signed 16-bit Little-Endian\n",
|
||||
af_fmt_to_str(format));
|
||||
format = AF_FORMAT_S16;
|
||||
}
|
||||
//set our audio parameters
|
||||
ao->samplerate = rate;
|
||||
ao->format = format;
|
||||
ao->bps = ao->channels.num * rate * af_fmt_to_bytes(format);
|
||||
int buffersize = ao->bps * p->cfg_buffersize / 1000;
|
||||
MP_VERBOSE(ao, "Samplerate:%iHz Channels:%i Format:%s\n", rate,
|
||||
ao->channels.num, af_fmt_to_str(format));
|
||||
MP_VERBOSE(ao, "Buffersize:%d bytes (%f msec)\n",
|
||||
buffersize, buffersize * 1000.0 / ao->bps);
|
||||
|
||||
//fill waveformatex
|
||||
ZeroMemory(&wformat, sizeof(WAVEFORMATEXTENSIBLE));
|
||||
wformat.Format.cbSize = (ao->channels.num > 2)
|
||||
? sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) : 0;
|
||||
wformat.Format.nChannels = ao->channels.num;
|
||||
wformat.Format.nSamplesPerSec = rate;
|
||||
if (af_fmt_is_spdif(format)) {
|
||||
// Whether it also works with e.g. DTS is unknown, but probably does.
|
||||
wformat.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF;
|
||||
wformat.Format.wBitsPerSample = 16;
|
||||
wformat.Format.nBlockAlign = 4;
|
||||
} else {
|
||||
wformat.Format.wFormatTag = (ao->channels.num > 2)
|
||||
? WAVE_FORMAT_EXTENSIBLE : WAVE_FORMAT_PCM;
|
||||
int bps = af_fmt_to_bytes(format);
|
||||
wformat.Format.wBitsPerSample = bps * 8;
|
||||
wformat.Format.nBlockAlign = wformat.Format.nChannels * bps;
|
||||
}
|
||||
|
||||
// fill in primary sound buffer descriptor
|
||||
memset(&dsbpridesc, 0, sizeof(DSBUFFERDESC));
|
||||
dsbpridesc.dwSize = sizeof(DSBUFFERDESC);
|
||||
dsbpridesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
||||
dsbpridesc.dwBufferBytes = 0;
|
||||
dsbpridesc.lpwfxFormat = NULL;
|
||||
|
||||
// fill in the secondary sound buffer (=stream buffer) descriptor
|
||||
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
|
||||
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
|
||||
dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */
|
||||
| DSBCAPS_GLOBALFOCUS /** Allows background playing */
|
||||
| DSBCAPS_CTRLVOLUME; /** volume control enabled */
|
||||
|
||||
if (ao->channels.num > 2) {
|
||||
wformat.dwChannelMask = mp_chmap_to_waveext(&ao->channels);
|
||||
wformat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
wformat.Samples.wValidBitsPerSample = wformat.Format.wBitsPerSample;
|
||||
// Needed for 5.1 on emu101k - shit soundblaster
|
||||
dsbdesc.dwFlags |= DSBCAPS_LOCHARDWARE;
|
||||
}
|
||||
wformat.Format.nAvgBytesPerSec = wformat.Format.nSamplesPerSec *
|
||||
wformat.Format.nBlockAlign;
|
||||
|
||||
dsbdesc.dwBufferBytes = buffersize;
|
||||
dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wformat;
|
||||
p->buffer_size = dsbdesc.dwBufferBytes;
|
||||
p->write_offset = 0;
|
||||
p->min_free_space = wformat.Format.nBlockAlign;
|
||||
p->outburst = wformat.Format.nBlockAlign * 512;
|
||||
|
||||
// create primary buffer and set its format
|
||||
|
||||
res = IDirectSound_CreateSoundBuffer(p->hds, &dsbpridesc, &p->hdspribuf, NULL);
|
||||
if (res != DS_OK) {
|
||||
UninitDirectSound(ao);
|
||||
MP_ERR(ao, "cannot create primary buffer (%s)\n", dserr2str(res));
|
||||
return -1;
|
||||
}
|
||||
res = IDirectSoundBuffer_SetFormat(p->hdspribuf, (WAVEFORMATEX *)&wformat);
|
||||
if (res != DS_OK) {
|
||||
MP_WARN(ao, "cannot set primary buffer format (%s), using "
|
||||
"standard setting (bad quality)", dserr2str(res));
|
||||
}
|
||||
|
||||
MP_VERBOSE(ao, "primary buffer created\n");
|
||||
|
||||
// now create the stream buffer
|
||||
|
||||
res = IDirectSound_CreateSoundBuffer(p->hds, &dsbdesc, &p->hdsbuf, NULL);
|
||||
if (res != DS_OK) {
|
||||
if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE) {
|
||||
// Try without DSBCAPS_LOCHARDWARE
|
||||
dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE;
|
||||
res = IDirectSound_CreateSoundBuffer(p->hds, &dsbdesc, &p->hdsbuf, NULL);
|
||||
}
|
||||
if (res != DS_OK) {
|
||||
UninitDirectSound(ao);
|
||||
MP_ERR(ao, "cannot create secondary (stream)buffer (%s)\n",
|
||||
dserr2str(res));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
MP_VERBOSE(ao, "secondary (stream)buffer created\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
\brief stop playing and empty buffers (for seeking/pause)
|
||||
*/
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
IDirectSoundBuffer_Stop(p->hdsbuf);
|
||||
// reset directsound buffer
|
||||
IDirectSoundBuffer_SetCurrentPosition(p->hdsbuf, 0);
|
||||
p->write_offset = 0;
|
||||
p->underrun_check = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief stop playing, keep buffers (for pause)
|
||||
*/
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
IDirectSoundBuffer_Stop(p->hdsbuf);
|
||||
}
|
||||
|
||||
/**
|
||||
\brief resume playing, after audio_pause()
|
||||
*/
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
IDirectSoundBuffer_Play(p->hdsbuf, 0, 0, DSBPLAY_LOOPING);
|
||||
}
|
||||
|
||||
/**
|
||||
\brief close audio device
|
||||
\param immed stop playback immediately
|
||||
*/
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
reset(ao);
|
||||
|
||||
DestroyBuffer(ao);
|
||||
UninitDirectSound(ao);
|
||||
}
|
||||
|
||||
// return exact number of free (safe to write) bytes
|
||||
static int check_free_buffer_size(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int space;
|
||||
DWORD play_offset;
|
||||
IDirectSoundBuffer_GetCurrentPosition(p->hdsbuf, &play_offset, NULL);
|
||||
space = p->buffer_size - (p->write_offset - play_offset);
|
||||
// | | <-- const --> | | |
|
||||
// buffer start play_cursor write_cursor p->write_offset buffer end
|
||||
// play_cursor is the actual position of the play cursor
|
||||
// write_cursor is the position after which it is assumed to be save to write data
|
||||
// p->write_offset is the position where we actually write the data to
|
||||
if (space > p->buffer_size)
|
||||
space -= p->buffer_size; // p->write_offset < play_offset
|
||||
// Check for buffer underruns. An underrun happens if DirectSound
|
||||
// started to play old data beyond the current p->write_offset. Detect this
|
||||
// by checking whether the free space shrinks, even though no data was
|
||||
// written (i.e. no write_buffer). Doesn't always work, but the only
|
||||
// reason we need this is to deal with the situation when playback ends,
|
||||
// and the buffer is only half-filled.
|
||||
if (space < p->underrun_check) {
|
||||
// there's no useful data in the buffers
|
||||
space = p->buffer_size;
|
||||
reset(ao);
|
||||
}
|
||||
p->underrun_check = space;
|
||||
return space;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief find out how many bytes can be written into the audio buffer without
|
||||
\return free space in bytes, has to return 0 if the buffer is almost full
|
||||
*/
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
int space = check_free_buffer_size(ao);
|
||||
if (space < p->min_free_space)
|
||||
return 0;
|
||||
return (space - p->min_free_space) / p->outburst * p->outburst / ao->sstride;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief play 'len' bytes of 'data'
|
||||
\param data pointer to the data to play
|
||||
\param len size in bytes of the data buffer, gets rounded down to outburst*n
|
||||
\param flags currently unused
|
||||
\return number of played bytes
|
||||
*/
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int len = samples * ao->sstride;
|
||||
|
||||
int space = check_free_buffer_size(ao);
|
||||
if (space < len)
|
||||
len = space;
|
||||
|
||||
if (!(flags & AOPLAY_FINAL_CHUNK))
|
||||
len = (len / p->outburst) * p->outburst;
|
||||
return write_buffer(ao, data[0], len) / ao->sstride;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief get the delay between the first and last sample in the buffer
|
||||
\return delay in seconds
|
||||
*/
|
||||
static double get_delay(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
int space = check_free_buffer_size(ao);
|
||||
return (p->buffer_size - space) / (double)ao->bps;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_dsound = {
|
||||
.description = "Windows DirectSound audio output",
|
||||
.name = "dsound",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.get_space = get_space,
|
||||
.play = play,
|
||||
.get_delay = get_delay,
|
||||
.pause = audio_pause,
|
||||
.resume = audio_resume,
|
||||
.reset = reset,
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INT("device", cfg_device, 0, OPTDEF_INT(-1)),
|
||||
OPT_INTRANGE("buffersize", cfg_buffersize, 0, 1, 10000, OPTDEF_INT(200)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+1
-1
@@ -31,7 +31,7 @@
|
||||
#include "common/common.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "osdep/timer.h"
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
+33
-22
@@ -376,22 +376,8 @@ fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
static bool set_format(struct ao *ao, pa_format_info *format)
|
||||
{
|
||||
struct pa_channel_map map;
|
||||
pa_proplist *proplist = NULL;
|
||||
pa_format_info *format = NULL;
|
||||
struct priv *priv = ao->priv;
|
||||
char *sink = priv->cfg_sink && priv->cfg_sink[0] ? priv->cfg_sink : ao->device;
|
||||
|
||||
if (pa_init_boilerplate(ao) < 0)
|
||||
return -1;
|
||||
|
||||
pa_threaded_mainloop_lock(priv->mainloop);
|
||||
|
||||
if (!(format = pa_format_info_new()))
|
||||
goto unlock_and_fail;
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
format->encoding = map_digital_format(ao->format);
|
||||
@@ -411,8 +397,29 @@ static int init(struct ao *ao)
|
||||
pa_format_info_set_sample_format(format, fmt_map->pa_format);
|
||||
}
|
||||
|
||||
struct pa_channel_map map;
|
||||
|
||||
if (!select_chmap(ao, &map))
|
||||
goto unlock_and_fail;
|
||||
return false;
|
||||
|
||||
pa_format_info_set_rate(format, ao->samplerate);
|
||||
pa_format_info_set_channels(format, ao->channels.num);
|
||||
pa_format_info_set_channel_map(format, &map);
|
||||
|
||||
return ao->samplerate < PA_RATE_MAX && pa_format_info_valid(format);
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
pa_proplist *proplist = NULL;
|
||||
pa_format_info *format = NULL;
|
||||
struct priv *priv = ao->priv;
|
||||
char *sink = priv->cfg_sink && priv->cfg_sink[0] ? priv->cfg_sink : ao->device;
|
||||
|
||||
if (pa_init_boilerplate(ao) < 0)
|
||||
return -1;
|
||||
|
||||
pa_threaded_mainloop_lock(priv->mainloop);
|
||||
|
||||
if (!(proplist = pa_proplist_new())) {
|
||||
MP_ERR(ao, "Failed to allocate proplist\n");
|
||||
@@ -420,13 +427,17 @@ static int init(struct ao *ao)
|
||||
}
|
||||
(void)pa_proplist_sets(proplist, PA_PROP_MEDIA_ICON_NAME, ao->client_name);
|
||||
|
||||
pa_format_info_set_rate(format, ao->samplerate);
|
||||
pa_format_info_set_channels(format, ao->channels.num);
|
||||
pa_format_info_set_channel_map(format, &map);
|
||||
|
||||
if (!pa_format_info_valid(format)) {
|
||||
MP_ERR(ao, "Invalid audio format\n");
|
||||
if (!(format = pa_format_info_new()))
|
||||
goto unlock_and_fail;
|
||||
|
||||
if (!set_format(ao, format)) {
|
||||
ao->channels = (struct mp_chmap) MP_CHMAP_INIT_STEREO;
|
||||
ao->samplerate = 48000;
|
||||
ao->format = AF_FORMAT_FLOAT;
|
||||
if (!set_format(ao, format)) {
|
||||
MP_ERR(ao, "Invalid audio format\n");
|
||||
goto unlock_and_fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(priv->stream = pa_stream_new_extended(priv->context, "audio stream",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <unistd.h>
|
||||
#include <rsound.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "osdep/timer.h"
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "audio/format.h"
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "common/common.h"
|
||||
|
||||
+192
-119
@@ -17,49 +17,61 @@
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <inttypes.h>
|
||||
#include <process.h>
|
||||
#include <initguid.h>
|
||||
#include <audioclient.h>
|
||||
#include <endpointvolume.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <avrt.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
|
||||
#include "audio/out/ao_wasapi.h"
|
||||
#include "audio/out/ao_wasapi_utils.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "options/m_option.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/io.h"
|
||||
#include "ao_wasapi.h"
|
||||
|
||||
static HRESULT get_device_delay(struct wasapi_state *state, double *delay) {
|
||||
// naive av_rescale for unsigned
|
||||
static UINT64 uint64_scale(UINT64 x, UINT64 num, UINT64 den)
|
||||
{
|
||||
return (x / den) * num
|
||||
+ ((x % den) * (num / den))
|
||||
+ ((x % den) * (num % den)) / den;
|
||||
}
|
||||
|
||||
static HRESULT get_device_delay(struct wasapi_state *state, double *delay_us) {
|
||||
UINT64 sample_count = atomic_load(&state->sample_count);
|
||||
UINT64 position, qpc_position;
|
||||
HRESULT hr;
|
||||
|
||||
hr = IAudioClock_GetPosition(state->pAudioClock, &position, &qpc_position);
|
||||
/* GetPosition succeeded, but the result may be inaccurate due to the length of the call */
|
||||
/* http://msdn.microsoft.com/en-us/library/windows/desktop/dd370889%28v=vs.85%29.aspx */
|
||||
// GetPosition succeeded, but the result may be
|
||||
// inaccurate due to the length of the call
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd370889%28v=vs.85%29.aspx
|
||||
if (hr == S_FALSE) {
|
||||
MP_DBG(state, "Possibly inaccurate device position.\n");
|
||||
MP_VERBOSE(state, "Possibly inaccurate device position.\n");
|
||||
hr = S_OK;
|
||||
}
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
LARGE_INTEGER qpc_count;
|
||||
QueryPerformanceCounter(&qpc_count);
|
||||
double qpc_diff = (qpc_count.QuadPart * 1e7 / state->qpc_frequency.QuadPart) - qpc_position;
|
||||
// convert position to number of samples careful to avoid overflow
|
||||
UINT64 sample_position = uint64_scale(position,
|
||||
state->format.Format.nSamplesPerSec,
|
||||
state->clock_frequency);
|
||||
INT64 diff = sample_count - sample_position;
|
||||
*delay_us = diff * 1e6 / state->format.Format.nSamplesPerSec;
|
||||
|
||||
position += state->clock_frequency * (uint64_t) (qpc_diff / 1e7);
|
||||
// Correct for any delay in IAudioClock_GetPosition above.
|
||||
// This should normally be very small (<1 us), but just in case. . .
|
||||
LARGE_INTEGER qpc;
|
||||
QueryPerformanceCounter(&qpc);
|
||||
INT64 qpc_diff = av_rescale(qpc.QuadPart, 10000000, state->qpc_frequency.QuadPart)
|
||||
- qpc_position;
|
||||
// ignore the above calculation if it yeilds more than 10 seconds (due to
|
||||
// possible overflow inside IAudioClock_GetPosition)
|
||||
if (qpc_diff < 10 * 10000000) {
|
||||
*delay_us -= qpc_diff / 10.0; // convert to us
|
||||
} else {
|
||||
MP_VERBOSE(state, "Insane qpc delay correction of %g seconds. "
|
||||
"Ignoring it.\n", qpc_diff / 10000000.0);
|
||||
}
|
||||
|
||||
/* convert position to the same base as sample_count */
|
||||
position = position * state->format.Format.nSamplesPerSec / state->clock_frequency;
|
||||
|
||||
double diff = sample_count - position;
|
||||
*delay = diff / state->format.Format.nSamplesPerSec;
|
||||
|
||||
MP_TRACE(state, "Device delay: %g samples (%g ms)\n", diff, *delay * 1000);
|
||||
MP_TRACE(state, "Device delay: %g us\n", *delay_us);
|
||||
|
||||
return S_OK;
|
||||
exit_label:
|
||||
@@ -80,11 +92,14 @@ static void thread_feed(struct ao *ao)
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
frame_count -= padding;
|
||||
MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n", frame_count, padding);
|
||||
MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n",
|
||||
frame_count, padding);
|
||||
}
|
||||
double delay;
|
||||
hr = get_device_delay(state, &delay);
|
||||
double delay_us;
|
||||
hr = get_device_delay(state, &delay_us);
|
||||
EXIT_ON_ERROR(hr);
|
||||
// add the buffer delay
|
||||
delay_us += frame_count * 1e6 / state->format.Format.nSamplesPerSec;
|
||||
|
||||
BYTE *pData;
|
||||
hr = IAudioRenderClient_GetBuffer(state->pRenderClient,
|
||||
@@ -93,10 +108,11 @@ static void thread_feed(struct ao *ao)
|
||||
|
||||
BYTE *data[1] = {pData};
|
||||
|
||||
ao_read_data(ao, (void**)data, frame_count, (int64_t) (
|
||||
mp_time_us() + delay * 1e6 +
|
||||
frame_count * 1e6 / state->format.Format.nSamplesPerSec));
|
||||
ao_read_data(ao, (void **)data, frame_count,
|
||||
mp_time_us() + (int64_t)llrint(delay_us));
|
||||
|
||||
// note, we can't use ao_read_data return value here since we already
|
||||
// commited to frame_count above in the GetBuffer call
|
||||
hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient,
|
||||
frame_count, 0);
|
||||
EXIT_ON_ERROR(hr);
|
||||
@@ -124,17 +140,21 @@ static void thread_resume(struct ao *ao)
|
||||
mp_HRESULT_to_str(hr));
|
||||
}
|
||||
|
||||
/* Fill the buffer before starting, but only if there is no audio queued to play. */
|
||||
/* This prevents overfilling the buffer, which leads to problems in exclusive mode */
|
||||
// Fill the buffer before starting, but only if there is no audio queued to
|
||||
// play. This prevents overfilling the buffer, which leads to problems in
|
||||
// exclusive mode
|
||||
if (padding < (UINT32) state->bufferFrameCount)
|
||||
thread_feed(ao);
|
||||
|
||||
// start feeding next wakeup if something else hasn't been requested
|
||||
int expected = WASAPI_THREAD_RESUME;
|
||||
atomic_compare_exchange_strong(&state->thread_state, &expected, WASAPI_THREAD_FEED);
|
||||
atomic_compare_exchange_strong(&state->thread_state, &expected,
|
||||
WASAPI_THREAD_FEED);
|
||||
hr = IAudioClient_Start(state->pAudioClient);
|
||||
if (hr != S_OK)
|
||||
MP_ERR(state, "IAudioClient_Start returned %s\n", mp_HRESULT_to_str(hr));
|
||||
if (hr != S_OK) {
|
||||
MP_ERR(state, "IAudioClient_Start returned %s\n",
|
||||
mp_HRESULT_to_str(hr));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -145,11 +165,11 @@ static void thread_reset(struct ao *ao)
|
||||
HRESULT hr;
|
||||
MP_DBG(state, "Thread Reset\n");
|
||||
hr = IAudioClient_Stop(state->pAudioClient);
|
||||
/* we may get S_FALSE if the stream is already stopped */
|
||||
// we may get S_FALSE if the stream is already stopped
|
||||
if (hr != S_OK && hr != S_FALSE)
|
||||
MP_ERR(state, "IAudioClient_Stop returned: %s\n", mp_HRESULT_to_str(hr));
|
||||
|
||||
/* we may get S_FALSE if the stream is already reset */
|
||||
// we may get S_FALSE if the stream is already reset
|
||||
hr = IAudioClient_Reset(state->pAudioClient);
|
||||
if (hr != S_OK && hr != S_FALSE)
|
||||
MP_ERR(state, "IAudioClient_Reset returned: %s\n", mp_HRESULT_to_str(hr));
|
||||
@@ -157,7 +177,8 @@ static void thread_reset(struct ao *ao)
|
||||
atomic_store(&state->sample_count, 0);
|
||||
// start feeding next wakeup if something else hasn't been requested
|
||||
int expected = WASAPI_THREAD_RESET;
|
||||
atomic_compare_exchange_strong(&state->thread_state, &expected, WASAPI_THREAD_FEED);
|
||||
atomic_compare_exchange_strong(&state->thread_state, &expected,
|
||||
WASAPI_THREAD_FEED);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -173,11 +194,12 @@ static DWORD __stdcall AudioThread(void *lpParameter)
|
||||
goto exit_label;
|
||||
|
||||
MP_DBG(ao, "Entering dispatch loop\n");
|
||||
while (true) { /* watch events */
|
||||
while (true) { // watch events
|
||||
HANDLE events[] = {state->hWake};
|
||||
switch (MsgWaitForMultipleObjects(MP_ARRAY_SIZE(events), events, FALSE, INFINITE,
|
||||
switch (MsgWaitForMultipleObjects(MP_ARRAY_SIZE(events), events,
|
||||
FALSE, INFINITE,
|
||||
QS_POSTMESSAGE | QS_SENDMESSAGE)) {
|
||||
/* AudioThread wakeup */
|
||||
// AudioThread wakeup
|
||||
case WAIT_OBJECT_0:
|
||||
switch (atomic_load(&state->thread_state)) {
|
||||
case WASAPI_THREAD_FEED:
|
||||
@@ -198,7 +220,7 @@ static DWORD __stdcall AudioThread(void *lpParameter)
|
||||
goto exit_label;
|
||||
}
|
||||
break;
|
||||
/* messages to dispatch (COM marshalling) */
|
||||
// messages to dispatch (COM marshalling)
|
||||
case (WAIT_OBJECT_0 + MP_ARRAY_SIZE(events)):
|
||||
wasapi_dispatch(ao);
|
||||
break;
|
||||
@@ -215,7 +237,8 @@ exit_label:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void set_thread_state(struct ao *ao, enum wasapi_thread_state thread_state)
|
||||
static void set_thread_state(struct ao *ao,
|
||||
enum wasapi_thread_state thread_state)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
atomic_store(&state->thread_state, thread_state);
|
||||
@@ -230,20 +253,22 @@ static void uninit(struct ao *ao)
|
||||
if (state->hWake)
|
||||
set_thread_state(ao, WASAPI_THREAD_SHUTDOWN);
|
||||
|
||||
/* wait up to 10 seconds */
|
||||
// wait up to 10 seconds
|
||||
if (state->hAudioThread &&
|
||||
WaitForSingleObject(state->hAudioThread, 10000) == WAIT_TIMEOUT)
|
||||
{
|
||||
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));
|
||||
SAFE_RELEASE(state->hAudioThread,CloseHandle(state->hAudioThread));
|
||||
|
||||
wasapi_change_uninit(ao);
|
||||
|
||||
talloc_free(state->deviceID);
|
||||
|
||||
CoUninitialize();
|
||||
MP_DBG(ao, "Uninit wasapi done\n");
|
||||
}
|
||||
@@ -255,8 +280,14 @@ 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->deviceID = find_deviceID(ao);
|
||||
if (!state->deviceID) {
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
wasapi_change_init(ao, false);
|
||||
|
||||
state->hInitDone = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
state->hWake = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
@@ -274,7 +305,7 @@ static int init(struct ao *ao)
|
||||
return -1;
|
||||
}
|
||||
|
||||
WaitForSingleObject(state->hInitDone, INFINITE); /* wait on init complete */
|
||||
WaitForSingleObject(state->hInitDone, INFINITE); // wait on init complete
|
||||
SAFE_RELEASE(state->hInitDone,CloseHandle(state->hInitDone));
|
||||
if (state->init_ret != S_OK) {
|
||||
if (!ao->probing)
|
||||
@@ -283,88 +314,135 @@ static int init(struct ao *ao)
|
||||
return -1;
|
||||
}
|
||||
|
||||
wasapi_setup_proxies(state);
|
||||
wasapi_receive_proxies(state);
|
||||
MP_DBG(ao, "Init wasapi done\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
if (!state->pEndpointVolumeProxy ||
|
||||
!(state->vol_hw_support & ENDPOINT_HARDWARE_SUPPORT_VOLUME)) {
|
||||
return CONTROL_FALSE;
|
||||
}
|
||||
|
||||
float volume;
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
IAudioEndpointVolume_GetMasterVolumeLevelScalar(
|
||||
state->pEndpointVolumeProxy,
|
||||
&volume);
|
||||
*(ao_control_vol_t *)arg = (ao_control_vol_t){
|
||||
.left = 100.0f * volume,
|
||||
.right = 100.0f * volume,
|
||||
};
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
volume = ((ao_control_vol_t *)arg)->left / 100.f;
|
||||
IAudioEndpointVolume_SetMasterVolumeLevelScalar(
|
||||
state->pEndpointVolumeProxy,
|
||||
volume, NULL);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
case AOCONTROL_GET_MUTE:
|
||||
case AOCONTROL_SET_MUTE:
|
||||
if (!state->pEndpointVolumeProxy ||
|
||||
!(state->vol_hw_support & ENDPOINT_HARDWARE_SUPPORT_MUTE)) {
|
||||
return CONTROL_FALSE;
|
||||
}
|
||||
|
||||
BOOL mute;
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_MUTE:
|
||||
IAudioEndpointVolume_GetMute(state->pEndpointVolumeProxy,
|
||||
&mute);
|
||||
*(bool *)arg = mute;
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_MUTE:
|
||||
mute = *(bool *)arg;
|
||||
IAudioEndpointVolume_SetMute(state->pEndpointVolumeProxy,
|
||||
mute, NULL);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_FALSE;
|
||||
default:
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static int control_shared(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
if (!state->pAudioVolumeProxy)
|
||||
return CONTROL_UNKNOWN;
|
||||
|
||||
float volume;
|
||||
BOOL mute;
|
||||
switch(cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
ISimpleAudioVolume_GetMasterVolume(state->pAudioVolumeProxy,
|
||||
&volume);
|
||||
*(ao_control_vol_t *)arg = (ao_control_vol_t){
|
||||
.left = 100.0f * volume,
|
||||
.right = 100.0f * volume,
|
||||
};
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
volume = ((ao_control_vol_t *)arg)->left / 100.f;
|
||||
ISimpleAudioVolume_SetMasterVolume(state->pAudioVolumeProxy,
|
||||
volume, NULL);
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_GET_MUTE:
|
||||
ISimpleAudioVolume_GetMute(state->pAudioVolumeProxy, &mute);
|
||||
*(bool *)arg = mute;
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_MUTE:
|
||||
mute = *(bool *)arg;
|
||||
ISimpleAudioVolume_SetMute(state->pAudioVolumeProxy, mute, NULL);
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
default:
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
ao_control_vol_t *vol = arg;
|
||||
BOOL mute;
|
||||
|
||||
// common to exclusive and shared
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
if (state->opt_exclusive)
|
||||
IAudioEndpointVolume_GetMasterVolumeLevelScalar(state->pEndpointVolumeProxy,
|
||||
&state->audio_volume);
|
||||
else
|
||||
ISimpleAudioVolume_GetMasterVolume(state->pAudioVolumeProxy,
|
||||
&state->audio_volume);
|
||||
case AOCONTROL_UPDATE_STREAM_TITLE:
|
||||
if (!state->pSessionControlProxy)
|
||||
return CONTROL_FALSE;
|
||||
|
||||
/* check to see if user manually changed volume through mixer;
|
||||
this information is used in exclusive mode for restoring the mixer volume on uninit */
|
||||
if (state->audio_volume != state->previous_volume) {
|
||||
MP_VERBOSE(state, "Mixer difference: %.2g now, expected %.2g\n",
|
||||
state->audio_volume, state->previous_volume);
|
||||
state->initial_volume = state->audio_volume;
|
||||
}
|
||||
|
||||
vol->left = vol->right = 100.0f * state->audio_volume;
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
state->audio_volume = vol->left / 100.f;
|
||||
if (state->opt_exclusive)
|
||||
IAudioEndpointVolume_SetMasterVolumeLevelScalar(state->pEndpointVolumeProxy,
|
||||
state->audio_volume, NULL);
|
||||
else
|
||||
ISimpleAudioVolume_SetMasterVolume(state->pAudioVolumeProxy,
|
||||
state->audio_volume, NULL);
|
||||
|
||||
state->previous_volume = state->audio_volume;
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_GET_MUTE:
|
||||
if (state->opt_exclusive)
|
||||
IAudioEndpointVolume_GetMute(state->pEndpointVolumeProxy, &mute);
|
||||
else
|
||||
ISimpleAudioVolume_GetMute(state->pAudioVolumeProxy, &mute);
|
||||
*(bool*)arg = mute;
|
||||
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_MUTE:
|
||||
mute = *(bool*)arg;
|
||||
if (state->opt_exclusive)
|
||||
IAudioEndpointVolume_SetMute(state->pEndpointVolumeProxy, mute, NULL);
|
||||
else
|
||||
ISimpleAudioVolume_SetMute(state->pAudioVolumeProxy, mute, NULL);
|
||||
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
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);
|
||||
|
||||
wchar_t *tmp = NULL;
|
||||
|
||||
/* There is a weird race condition in the IAudioSessionControl itself --
|
||||
it seems that *sometimes* the SetDisplayName does not take effect and it still shows
|
||||
the old title. Use this loop to insist until it works. */
|
||||
// There is a weird race condition in the IAudioSessionControl itself --
|
||||
// it seems that *sometimes* the SetDisplayName does not take effect and
|
||||
// it still shows the old title. Use this loop to insist until it works.
|
||||
do {
|
||||
IAudioSessionControl_SetDisplayName(state->pSessionControlProxy, title, NULL);
|
||||
IAudioSessionControl_SetDisplayName(state->pSessionControlProxy,
|
||||
title, NULL);
|
||||
|
||||
SAFE_RELEASE(tmp, CoTaskMemFree(tmp));
|
||||
IAudioSessionControl_GetDisplayName(state->pSessionControlProxy, &tmp);
|
||||
IAudioSessionControl_GetDisplayName(state->pSessionControlProxy,
|
||||
&tmp);
|
||||
} while (lstrcmpW(title, tmp));
|
||||
SAFE_RELEASE(tmp, CoTaskMemFree(tmp));
|
||||
talloc_free(title);
|
||||
|
||||
return CONTROL_OK;
|
||||
}
|
||||
default:
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
return state->opt_exclusive ?
|
||||
control_exclusive(ao, cmd, arg) : control_shared(ao, cmd, arg);
|
||||
}
|
||||
|
||||
static void audio_reset(struct ao *ao)
|
||||
@@ -380,9 +458,7 @@ static void audio_resume(struct ao *ao)
|
||||
static void hotplug_uninit(struct ao *ao)
|
||||
{
|
||||
MP_DBG(ao, "Hotplug uninit\n");
|
||||
struct wasapi_state *state = ao->priv;
|
||||
wasapi_change_uninit(ao);
|
||||
SAFE_RELEASE(state->pEnumerator, IMMDeviceEnumerator_Release(state->pEnumerator));
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
@@ -392,10 +468,7 @@ static int hotplug_init(struct ao *ao)
|
||||
struct wasapi_state *state = ao->priv;
|
||||
state->log = ao->log;
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
||||
&IID_IMMDeviceEnumerator, (void **)&state->pEnumerator);
|
||||
EXIT_ON_ERROR(hr);
|
||||
hr = wasapi_change_init(ao, true);
|
||||
HRESULT hr = wasapi_change_init(ao, true);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
return 0;
|
||||
|
||||
Executable → Regular
+63
-58
@@ -20,17 +20,24 @@
|
||||
#ifndef MP_AO_WASAPI_H_
|
||||
#define MP_AO_WASAPI_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <windows.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <audioclient.h>
|
||||
#include <audiopolicy.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <avrt.h>
|
||||
#include <endpointvolume.h>
|
||||
|
||||
#include "common/msg.h"
|
||||
#include "osdep/atomics.h"
|
||||
#include "osdep/windows_utils.h"
|
||||
#include "internal.h"
|
||||
#include "ao.h"
|
||||
|
||||
typedef struct change_notify {
|
||||
IMMNotificationClient client; /* this must be first in the structure! */
|
||||
LPWSTR monitored; /* Monitored device */
|
||||
IMMNotificationClient client; // this must be first in the structure!
|
||||
IMMDeviceEnumerator *pEnumerator; // object where client is registered
|
||||
LPWSTR monitored; // Monitored device
|
||||
bool is_hotplug;
|
||||
struct ao *ao;
|
||||
} change_notify;
|
||||
@@ -52,75 +59,73 @@ enum wasapi_thread_state {
|
||||
|
||||
typedef struct wasapi_state {
|
||||
struct mp_log *log;
|
||||
/* Init phase */
|
||||
HRESULT init_ret;
|
||||
HANDLE hInitDone;
|
||||
int share_mode;
|
||||
|
||||
/* volume control */
|
||||
DWORD vol_hw_support;
|
||||
float audio_volume;
|
||||
float previous_volume;
|
||||
float initial_volume;
|
||||
// Thread handles
|
||||
HRESULT init_ret; // status of init phase
|
||||
HANDLE hInitDone; // set when init is complete in audio thread
|
||||
HANDLE hAudioThread; // the audio thread itself
|
||||
HANDLE hWake; // thread wakeup event
|
||||
atomic_int thread_state; // enum wasapi_thread_state (what to do on wakeup)
|
||||
|
||||
/* WASAPI handles, owned by audio thread */
|
||||
// for setting the audio thread priority
|
||||
HANDLE hTask;
|
||||
|
||||
// ID of the device to use
|
||||
LPWSTR deviceID;
|
||||
// WASAPI object handles owned and used by audio thread
|
||||
IMMDevice *pDevice;
|
||||
IAudioClient *pAudioClient;
|
||||
IAudioRenderClient *pRenderClient;
|
||||
ISimpleAudioVolume *pAudioVolume;
|
||||
IAudioEndpointVolume *pEndpointVolume;
|
||||
IAudioSessionControl *pSessionControl;
|
||||
IMMDeviceEnumerator *pEnumerator;
|
||||
|
||||
/* thread handles */
|
||||
HANDLE hAudioThread; /* the thread itself */
|
||||
HANDLE hWake; /* thread wakeup event */
|
||||
atomic_int thread_state; /* enum wasapi_thread_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. */
|
||||
ISimpleAudioVolume *pAudioVolumeProxy;
|
||||
IAudioEndpointVolume *pEndpointVolumeProxy;
|
||||
IAudioSessionControl *pSessionControlProxy;
|
||||
|
||||
/* Streams used to marshal the proxy objects. The thread owning the actual objects
|
||||
needs to marshal proxy objects into these streams, and the thread that wants the
|
||||
proxies unmarshals them from here. */
|
||||
IStream *sAudioVolume;
|
||||
IStream *sEndpointVolume;
|
||||
IStream *sSessionControl;
|
||||
|
||||
/* WASAPI internal clock information, for estimating delay */
|
||||
// WASAPI internal clock information, for estimating delay
|
||||
IAudioClock *pAudioClock;
|
||||
UINT64 clock_frequency; /* scale for the "samples" returned by the clock */
|
||||
atomic_ullong sample_count; /* the amount of samples per channel written to a GetBuffer buffer */
|
||||
LARGE_INTEGER qpc_frequency; /* frequency of windows' high resolution timer */
|
||||
atomic_ullong sample_count; // samples per channel written by GetBuffer
|
||||
UINT64 clock_frequency; // scale for position returned by GetPosition
|
||||
LARGE_INTEGER qpc_frequency; // frequency of Windows' high resolution timer
|
||||
|
||||
/* ao options */
|
||||
// WASAPI control (handles owned by audio thread but used by main thread)
|
||||
IAudioSessionControl *pSessionControl; // setting the stream title
|
||||
IAudioEndpointVolume *pEndpointVolume; // exclusive mode volume/mute
|
||||
ISimpleAudioVolume *pAudioVolume; // shared mode volume/mute
|
||||
DWORD vol_hw_support; // is hardware volume supported for exclusive-mode?
|
||||
|
||||
// Streams used to marshal the proxy objects. The thread owning the actual
|
||||
// objects needs to marshal proxy objects into these streams, and the thread
|
||||
// that wants the proxies unmarshals them from here.
|
||||
IStream *sSessionControl;
|
||||
IStream *sEndpointVolume;
|
||||
IStream *sAudioVolume;
|
||||
|
||||
// WASAPI proxy handles, for Single-Threaded Apartment communication. One is
|
||||
// needed for each audio thread object that's accessed from the main thread.
|
||||
IAudioSessionControl *pSessionControlProxy;
|
||||
IAudioEndpointVolume *pEndpointVolumeProxy;
|
||||
ISimpleAudioVolume *pAudioVolumeProxy;
|
||||
|
||||
// ao options
|
||||
int opt_exclusive;
|
||||
int opt_list;
|
||||
char *opt_device;
|
||||
|
||||
/* format info */
|
||||
// format info
|
||||
WAVEFORMATEXTENSIBLE format;
|
||||
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;
|
||||
AUDCLNT_SHAREMODE share_mode; // AUDCLNT_SHAREMODE_EXCLUSIVE / SHARED
|
||||
UINT32 bufferFrameCount; // number of frames in buffer
|
||||
|
||||
change_notify change;
|
||||
} wasapi_state;
|
||||
|
||||
char *mp_PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey);
|
||||
#define mp_PKEY_to_str(pkey) mp_PKEY_to_str_buf((char[42]){0}, 42, (pkey))
|
||||
|
||||
void wasapi_list_devs(struct ao *ao, struct ao_device_list *list);
|
||||
LPWSTR find_deviceID(struct ao *ao);
|
||||
|
||||
void wasapi_dispatch(struct ao *ao);
|
||||
HRESULT wasapi_thread_init(struct ao *ao);
|
||||
void wasapi_thread_uninit(struct ao *ao);
|
||||
|
||||
void wasapi_receive_proxies(wasapi_state *state);
|
||||
void wasapi_release_proxies(wasapi_state *state);
|
||||
|
||||
#endif
|
||||
Executable → Regular
+33
-33
@@ -17,20 +17,14 @@
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <initguid.h>
|
||||
#include <audioclient.h>
|
||||
#include <endpointvolume.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <wchar.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "ao_wasapi.h"
|
||||
#include "ao_wasapi_utils.h"
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_QueryInterface(
|
||||
IMMNotificationClient* This, REFIID riid, void **ppvObject)
|
||||
{
|
||||
/* Compatible with IMMNotificationClient and IUnknown */
|
||||
// Compatible with IMMNotificationClient and IUnknown
|
||||
if (IsEqualGUID(&IID_IMMNotificationClient, riid) ||
|
||||
IsEqualGUID(&IID_IUnknown, riid))
|
||||
{
|
||||
@@ -42,14 +36,14 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_QueryInterface(
|
||||
}
|
||||
}
|
||||
|
||||
/* these are required, but not actually used */
|
||||
// these are required, but not actually used
|
||||
static ULONG STDMETHODCALLTYPE sIMMNotificationClient_AddRef(
|
||||
IMMNotificationClient *This)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* MSDN says it should free itself, but we're static */
|
||||
// MSDN says it should free itself, but we're static
|
||||
static ULONG STDMETHODCALLTYPE sIMMNotificationClient_Release(
|
||||
IMMNotificationClient *This)
|
||||
{
|
||||
@@ -65,7 +59,8 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceStateChanged(
|
||||
struct ao *ao = change->ao;
|
||||
|
||||
if (change->is_hotplug) {
|
||||
MP_VERBOSE(ao, "OnDeviceStateChanged triggered: sending hotplug event\n");
|
||||
MP_VERBOSE(ao,
|
||||
"OnDeviceStateChanged triggered: sending hotplug event\n");
|
||||
ao_hotplug_event(ao);
|
||||
} else if (pwstrDeviceId && !wcscmp(pwstrDeviceId, change->monitored)) {
|
||||
switch (dwNewState) {
|
||||
@@ -99,7 +94,7 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceAdded(
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/* maybe MPV can go over to the prefered device once it is plugged in? */
|
||||
// maybe MPV can go over to the prefered device once it is plugged in?
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceRemoved(
|
||||
IMMNotificationClient *This,
|
||||
LPCWSTR pwstrDeviceId)
|
||||
@@ -129,29 +124,31 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDefaultDeviceChanged(
|
||||
struct ao *ao = change->ao;
|
||||
struct wasapi_state *state = ao->priv;
|
||||
|
||||
/* don't care about "eCapture" or non-"eMultimedia" roles */
|
||||
// don't care about "eCapture" or non-"eMultimedia" roles
|
||||
if (flow == eCapture || role != eMultimedia) return S_OK;
|
||||
|
||||
if (change->is_hotplug) {
|
||||
MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered: sending hotplug event\n");
|
||||
MP_VERBOSE(ao,
|
||||
"OnDefaultDeviceChanged triggered: sending hotplug event\n");
|
||||
ao_hotplug_event(ao);
|
||||
} else {
|
||||
/* stay on the device the user specified */
|
||||
// stay on the device the user specified
|
||||
if (state->opt_device) {
|
||||
MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered: "
|
||||
"staying on specified device %s\n", state->opt_device);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/* don't reload if already on the new default */
|
||||
// don't reload if already on the new default
|
||||
if (pwstrDeviceId && !wcscmp(pwstrDeviceId, change->monitored)) {
|
||||
MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered: "
|
||||
"already using default device, no reload required\n");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/* if we got here, we need to reload */
|
||||
MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered: requesting ao reload\n");
|
||||
// if we got here, we need to reload
|
||||
MP_VERBOSE(ao,
|
||||
"OnDefaultDeviceChanged triggered: requesting ao reload\n");
|
||||
ao_request_reload(ao);
|
||||
}
|
||||
|
||||
@@ -184,7 +181,7 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnPropertyValueChanged(
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static CONST_VTBL IMMNotificationClientVtbl sIMMDeviceEnumeratorVtbl_vtbl = {
|
||||
static CONST_VTBL IMMNotificationClientVtbl sIMMNotificationClientVtbl = {
|
||||
.QueryInterface = sIMMNotificationClient_QueryInterface,
|
||||
.AddRef = sIMMNotificationClient_AddRef,
|
||||
.Release = sIMMNotificationClient_Release,
|
||||
@@ -200,27 +197,30 @@ HRESULT wasapi_change_init(struct ao *ao, bool is_hotplug)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
struct change_notify *change = &state->change;
|
||||
HRESULT hr;
|
||||
/* COM voodoo to emulate c++ class */
|
||||
change->client.lpVtbl = &sIMMDeviceEnumeratorVtbl_vtbl;
|
||||
|
||||
/* register the change notification client */
|
||||
hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(
|
||||
state->pEnumerator, (IMMNotificationClient *)change);
|
||||
HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
||||
&IID_IMMDeviceEnumerator,
|
||||
(void **)&change->pEnumerator);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
/* so the callbacks can access the ao */
|
||||
// COM voodoo to emulate c++ class
|
||||
change->client.lpVtbl = &sIMMNotificationClientVtbl;
|
||||
|
||||
// register the change notification client
|
||||
hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(
|
||||
change->pEnumerator, (IMMNotificationClient *)change);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
// so the callbacks can access the ao
|
||||
change->ao = ao;
|
||||
|
||||
/* whether or not this is the hotplug instance */
|
||||
// whether or not this is the hotplug instance
|
||||
change->is_hotplug = is_hotplug;
|
||||
|
||||
if (is_hotplug) {
|
||||
MP_DBG(ao, "Monitoring for hotplug events\n");
|
||||
} else {
|
||||
/* Get the device string to compare with the pwstrDeviceId */
|
||||
hr = IMMDevice_GetId(state->pDevice, &change->monitored);
|
||||
EXIT_ON_ERROR(hr);
|
||||
// Get the device string to compare with the pwstrDeviceId
|
||||
change->monitored = state->deviceID;
|
||||
MP_VERBOSE(ao, "Monitoring changes in device %S\n", change->monitored);
|
||||
}
|
||||
|
||||
@@ -237,10 +237,10 @@ void wasapi_change_uninit(struct ao *ao)
|
||||
struct wasapi_state *state = ao->priv;
|
||||
struct change_notify *change = &state->change;
|
||||
|
||||
if (state->pEnumerator && change->client.lpVtbl) {
|
||||
if (change->pEnumerator && change->client.lpVtbl) {
|
||||
IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(
|
||||
state->pEnumerator, (IMMNotificationClient *)change);
|
||||
change->pEnumerator, (IMMNotificationClient *)change);
|
||||
}
|
||||
|
||||
if (change->monitored) CoTaskMemFree(change->monitored);
|
||||
SAFE_RELEASE(change->pEnumerator, IMMDeviceEnumerator_Release(change->pEnumerator));
|
||||
}
|
||||
Executable → Regular
+390
-495
File diff suppressed because it is too large.
Load diff
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* Original author: Jonathan Yong <10walls@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef MP_AO_WASAPI_UTILS_H_
|
||||
#define MP_AO_WASAPI_UTILS_H_
|
||||
|
||||
#include "audio/out/ao_wasapi.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "common/msg.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
|
||||
char *mp_GUID_to_str_buf(char *buf, size_t buf_size, const GUID *guid);
|
||||
char *mp_PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey);
|
||||
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))
|
||||
|
||||
bool wasapi_fill_VistaBlob(wasapi_state *state);
|
||||
|
||||
void wasapi_list_devs(struct ao *ao, struct ao_device_list *list);
|
||||
|
||||
void wasapi_dispatch(struct ao *ao);
|
||||
HRESULT wasapi_thread_init(struct ao *ao);
|
||||
void wasapi_thread_uninit(struct ao *ao);
|
||||
|
||||
HRESULT wasapi_setup_proxies(wasapi_state *state);
|
||||
void wasapi_release_proxies(wasapi_state *state);
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -113,7 +113,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
// rest of the user-provided buffer with silence.
|
||||
// This basically assumes that the audio device doesn't care about underruns.
|
||||
// If this is called in paused mode, it will always return 0.
|
||||
// The caller should set out_time_us to the expected delay the last sample
|
||||
// The caller should set out_time_us to the expected delay until the last sample
|
||||
// reaches the speakers, in microseconds, using mp_time_us() as reference.
|
||||
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
|
||||
{
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "common/msg.h"
|
||||
#include "codecs.h"
|
||||
|
||||
+20
-2
@@ -21,8 +21,9 @@
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/error.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "misc/ctype.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#define appendf(ptr, ...) \
|
||||
@@ -148,7 +149,7 @@ void mp_append_utf8_bstr(void *talloc_ctx, struct bstr *buf, uint32_t codepoint)
|
||||
bstr_xappend(talloc_ctx, buf, (bstr){data, output - data});
|
||||
}
|
||||
|
||||
// Parse a C-style escape beginning at code, and append the result to *str
|
||||
// Parse a C/JSON-style escape beginning at code, and append the result to *str
|
||||
// using talloc. The input string (*code) must point to the first character
|
||||
// after the initial '\', and after parsing *code is set to the first character
|
||||
// after the current escape.
|
||||
@@ -161,6 +162,7 @@ static bool mp_parse_escape(void *talloc_ctx, bstr *dst, bstr *code)
|
||||
switch (code->start[0]) {
|
||||
case '"': replace = '"'; break;
|
||||
case '\\': replace = '\\'; break;
|
||||
case '/': replace = '/'; break;
|
||||
case 'b': replace = '\b'; break;
|
||||
case 'f': replace = '\f'; break;
|
||||
case 'n': replace = '\n'; break;
|
||||
@@ -257,3 +259,19 @@ char *mp_strerror_buf(char *buf, size_t buf_size, int errnum)
|
||||
av_strerror(AVERROR(errnum), buf, buf_size);
|
||||
return buf;
|
||||
}
|
||||
|
||||
char *mp_tag_str_buf(char *buf, size_t buf_size, uint32_t tag)
|
||||
{
|
||||
if (buf_size < 1)
|
||||
return buf;
|
||||
buf[0] = '\0';
|
||||
for (int n = 0; n < 4; n++) {
|
||||
uint8_t val = (tag >> (n * 8)) & 0xFF;
|
||||
if (mp_isalnum(val) || val == '_' || val == ' ') {
|
||||
mp_snprintf_cat(buf, buf_size, "%c", val);
|
||||
} else {
|
||||
mp_snprintf_cat(buf, buf_size, "[%d]", val);
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
+4
-3
@@ -24,7 +24,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "osdep/compiler.h"
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
// double should be able to represent this exactly
|
||||
#define MP_NOPTS_VALUE (-0x1p+63)
|
||||
@@ -32,8 +32,6 @@
|
||||
#define MP_CONCAT_(a, b) a ## b
|
||||
#define MP_CONCAT(a, b) MP_CONCAT_(a, b)
|
||||
|
||||
#define ROUND(x) ((int)((x) < 0 ? (x) - 0.5 : (x) + 0.5))
|
||||
|
||||
#define MPMAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
#define MPMIN(a, b) ((a) > (b) ? (b) : (a))
|
||||
#define MPCLAMP(a, min, max) (((a) < (min)) ? (min) : (((a) > (max)) ? (max) : (a)))
|
||||
@@ -92,4 +90,7 @@ bool mp_append_escaped_string(void *talloc_ctx, struct bstr *dst,
|
||||
char *mp_strerror_buf(char *buf, size_t buf_size, int errnum);
|
||||
#define mp_strerror(e) mp_strerror_buf((char[80]){0}, 80, e)
|
||||
|
||||
char *mp_tag_str_buf(char *buf, size_t buf_size, uint32_t tag);
|
||||
#define mp_tag_str(t) mp_tag_str_buf((char[22]){0}, 22, t)
|
||||
|
||||
#endif /* MPLAYER_MPCOMMON_H */
|
||||
@@ -29,7 +29,7 @@
|
||||
#include "options/options.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "video/out/vo.h"
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "stream/stream.h"
|
||||
|
||||
#define OPT_BASE_STRUCT struct encode_opts
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
struct mpv_global {
|
||||
struct MPOpts *opts;
|
||||
struct mp_log *log;
|
||||
struct mp_client_api *client_api;
|
||||
};
|
||||
|
||||
#endif
|
||||
+2
-2
@@ -24,7 +24,7 @@
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "misc/bstr.h"
|
||||
#include "osdep/atomics.h"
|
||||
@@ -632,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;
|
||||
|
||||
+13
-1
@@ -21,7 +21,7 @@
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "options/path.h"
|
||||
|
||||
#include "demux/demux.h"
|
||||
@@ -206,6 +206,18 @@ void playlist_add_base_path(struct playlist *pl, bstr base_path)
|
||||
}
|
||||
}
|
||||
|
||||
// Add redirected_from as new redirect entry to each item in pl.
|
||||
void playlist_add_redirect(struct playlist *pl, const char *redirected_from)
|
||||
{
|
||||
for (struct playlist_entry *e = pl->first; e; e = e->next) {
|
||||
if (e->num_redirects >= 10) // arbitrary limit for sanity
|
||||
break;
|
||||
char *s = talloc_strdup(e, redirected_from);
|
||||
if (s)
|
||||
MP_TARRAY_APPEND(e, e->redirects, e->num_redirects, s);
|
||||
}
|
||||
}
|
||||
|
||||
// Move all entries from source_pl to pl, appending them after the current entry
|
||||
// of pl. source_pl will be empty, and all entries have changed ownership to pl.
|
||||
void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl)
|
||||
|
||||
@@ -36,6 +36,11 @@ struct playlist_entry {
|
||||
|
||||
char *title;
|
||||
|
||||
// If the user plays a playlist, then the playlist's URL will be appended
|
||||
// as redirect to each entry. (Same for directories etc.)
|
||||
char **redirects;
|
||||
int num_redirects;
|
||||
|
||||
// Set to true if playback didn't seem to work, or if the file could be
|
||||
// played only for a very short time. This is used to make playlist
|
||||
// navigation just work in case the user has unplayable files in the
|
||||
@@ -88,6 +93,7 @@ void playlist_add_file(struct playlist *pl, const char *filename);
|
||||
void playlist_shuffle(struct playlist *pl);
|
||||
struct playlist_entry *playlist_get_next(struct playlist *pl, int direction);
|
||||
void playlist_add_base_path(struct playlist *pl, bstr base_path);
|
||||
void playlist_add_redirect(struct playlist *pl, const char *redirected_from);
|
||||
void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl);
|
||||
void playlist_append_entries(struct playlist *pl, struct playlist *source_pl);
|
||||
|
||||
|
||||
+8
-8
@@ -70,20 +70,20 @@ static const char *map_audio_pcm_tag(uint32_t tag, int bits)
|
||||
}
|
||||
}
|
||||
|
||||
void mp_set_codec_from_tag(struct sh_stream *sh)
|
||||
void mp_set_codec_from_tag(struct mp_codec_params *c)
|
||||
{
|
||||
sh->codec = lookup_tag(sh->type, sh->codec_tag);
|
||||
c->codec = lookup_tag(c->type, c->codec_tag);
|
||||
|
||||
if (sh->audio && sh->audio->bits_per_coded_sample) {
|
||||
if (c->type == STREAM_AUDIO && c->bits_per_coded_sample) {
|
||||
const char *codec =
|
||||
map_audio_pcm_tag(sh->codec_tag, sh->audio->bits_per_coded_sample);
|
||||
map_audio_pcm_tag(c->codec_tag, c->bits_per_coded_sample);
|
||||
if (codec)
|
||||
sh->codec = codec;
|
||||
c->codec = codec;
|
||||
}
|
||||
}
|
||||
|
||||
void mp_set_pcm_codec(struct sh_stream *sh, bool sign, bool is_float, int bits,
|
||||
bool is_be)
|
||||
void mp_set_pcm_codec(struct mp_codec_params *c, bool sign, bool is_float,
|
||||
int bits, bool is_be)
|
||||
{
|
||||
// This uses libavcodec pcm codec names, e.g. "pcm_u16le".
|
||||
char codec[64] = "pcm_";
|
||||
@@ -95,7 +95,7 @@ void mp_set_pcm_codec(struct sh_stream *sh, bool sign, bool is_float, int bits,
|
||||
mp_snprintf_cat(codec, sizeof(codec), "%d", bits);
|
||||
if (bits != 8)
|
||||
mp_snprintf_cat(codec, sizeof(codec), is_be ? "be" : "le");
|
||||
sh->codec = talloc_strdup(sh->audio, codec);
|
||||
c->codec = talloc_strdup(c, codec);
|
||||
}
|
||||
|
||||
static const char *const mimetype_to_codec[][2] = {
|
||||
|
||||
+4
-4
@@ -21,12 +21,12 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct sh_stream;
|
||||
struct mp_codec_params;
|
||||
|
||||
void mp_set_codec_from_tag(struct sh_stream *sh);
|
||||
void mp_set_codec_from_tag(struct mp_codec_params *c);
|
||||
|
||||
void mp_set_pcm_codec(struct sh_stream *sh, bool sign, bool is_float, int bits,
|
||||
bool is_be);
|
||||
void mp_set_pcm_codec(struct mp_codec_params *c, bool sign, bool is_float,
|
||||
int bits, bool is_be);
|
||||
|
||||
const char *mp_map_mimetype_to_video_codec(const char *mimetype);
|
||||
|
||||
|
||||
+16
-1
@@ -20,7 +20,7 @@
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "misc/bstr.h"
|
||||
#include "common/common.h"
|
||||
@@ -218,3 +218,18 @@ struct cue_file *mp_parse_cue(struct bstr data)
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
int mp_check_embedded_cue(struct cue_file *f)
|
||||
{
|
||||
char *fn0 = f->tracks[0].filename;
|
||||
for (int n = 1; n < f->num_tracks; n++) {
|
||||
char *fn = f->tracks[n].filename;
|
||||
// both filenames have the same address (including NULL)
|
||||
if (fn0 == fn)
|
||||
continue;
|
||||
// only one filename is NULL, or the strings don't match
|
||||
if (!fn0 || !fn || strcmp(fn0, fn) != 0)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -38,5 +38,6 @@ struct cue_track {
|
||||
|
||||
bool mp_probe_cue(struct bstr data);
|
||||
struct cue_file *mp_parse_cue(struct bstr data);
|
||||
int mp_check_embedded_cue(struct cue_file *f);
|
||||
|
||||
#endif
|
||||
+174
-101
@@ -29,7 +29,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "options/options.h"
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/global.h"
|
||||
#include "osdep/threads.h"
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -110,6 +103,11 @@ struct demux_internal {
|
||||
void (*wakeup_cb)(void *ctx);
|
||||
void *wakeup_cb_ctx;
|
||||
|
||||
struct sh_stream **streams;
|
||||
int num_streams;
|
||||
|
||||
int events;
|
||||
|
||||
bool warned_queue_overflow;
|
||||
bool last_eof; // last actual global EOF status
|
||||
bool eof; // whether we're in EOF state (reset for retry)
|
||||
@@ -128,6 +126,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 +168,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,42 +195,92 @@ static void ds_flush(struct demux_stream *ds)
|
||||
ds->last_pos = -1;
|
||||
}
|
||||
|
||||
struct sh_stream *new_sh_stream(demuxer_t *demuxer, enum stream_type type)
|
||||
void demux_set_ts_offset(struct demuxer *demuxer, double offset)
|
||||
{
|
||||
assert(demuxer == demuxer->in->d_thread);
|
||||
struct demux_internal *in = demuxer->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
in->ts_offset = offset;
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
if (demuxer->num_streams > MAX_SH_STREAMS) {
|
||||
MP_WARN(demuxer, "Too many streams.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int demuxer_id = 0;
|
||||
for (int n = 0; n < demuxer->num_streams; n++) {
|
||||
if (demuxer->streams[n]->type == type)
|
||||
demuxer_id++;
|
||||
}
|
||||
|
||||
struct sh_stream *sh = talloc_ptrtype(demuxer, sh);
|
||||
// Allocate a new sh_stream of the given type. It either has to be released
|
||||
// with talloc_free(), or added to a demuxer with demux_add_sh_stream(). You
|
||||
// cannot add or read packets from the stream before it has been added.
|
||||
struct sh_stream *demux_alloc_sh_stream(enum stream_type type)
|
||||
{
|
||||
struct sh_stream *sh = talloc_ptrtype(NULL, sh);
|
||||
*sh = (struct sh_stream) {
|
||||
.type = type,
|
||||
.index = demuxer->num_streams,
|
||||
.ff_index = demuxer->num_streams,
|
||||
.demuxer_id = demuxer_id, // may be overwritten by demuxer
|
||||
.ds = talloc(sh, struct demux_stream),
|
||||
.index = -1,
|
||||
.ff_index = -1, // may be overwritten by demuxer
|
||||
.demuxer_id = -1, // ... same
|
||||
.codec = talloc_zero(sh, struct mp_codec_params),
|
||||
};
|
||||
sh->codec->type = type;
|
||||
return sh;
|
||||
}
|
||||
|
||||
// Add a new sh_stream to the demuxer. Note that as soon as the stream has been
|
||||
// added, it must be immutable, and must not be released (this will happen when
|
||||
// the demuxer is destroyed).
|
||||
void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
|
||||
assert(!sh->ds); // must not be added yet
|
||||
|
||||
sh->ds = talloc(sh, struct demux_stream);
|
||||
*sh->ds = (struct demux_stream) {
|
||||
.in = demuxer->in,
|
||||
.in = in,
|
||||
.type = sh->type,
|
||||
.selected = demuxer->in->autoselect,
|
||||
.selected = in->autoselect,
|
||||
};
|
||||
MP_TARRAY_APPEND(demuxer, demuxer->streams, demuxer->num_streams, sh);
|
||||
switch (sh->type) {
|
||||
case STREAM_VIDEO: sh->video = talloc_zero(demuxer, struct sh_video); break;
|
||||
case STREAM_AUDIO: sh->audio = talloc_zero(demuxer, struct sh_audio); break;
|
||||
case STREAM_SUB: sh->sub = talloc_zero(demuxer, struct sh_sub); break;
|
||||
|
||||
if (!sh->codec->codec)
|
||||
sh->codec->codec = "";
|
||||
|
||||
sh->index = in->num_streams;
|
||||
if (sh->ff_index < 0)
|
||||
sh->ff_index = sh->index;
|
||||
if (sh->demuxer_id < 0) {
|
||||
sh->demuxer_id = 0;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
if (in->streams[n]->type == sh->type)
|
||||
sh->demuxer_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return sh;
|
||||
MP_TARRAY_APPEND(in, in->streams, in->num_streams, sh);
|
||||
|
||||
in->events |= DEMUX_EVENT_STREAMS;
|
||||
if (in->wakeup_cb)
|
||||
in->wakeup_cb(in->wakeup_cb_ctx);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
// Return a stream with the given index. Since streams can only be added during
|
||||
// the lifetime of the demuxer, it is guaranteed that an index within the valid
|
||||
// range [0, demux_get_num_stream()) always returns a valid sh_stream pointer,
|
||||
// which will be valid until the demuxer is destroyed.
|
||||
struct sh_stream *demux_get_stream(struct demuxer *demuxer, int index)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
assert(index >= 0 && index < in->num_streams);
|
||||
struct sh_stream *r = in->streams[index];
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
// See demux_get_stream().
|
||||
int demux_get_num_stream(struct demuxer *demuxer)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
int r = in->num_streams;
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
void free_demuxer(demuxer_t *demuxer)
|
||||
@@ -242,8 +294,10 @@ void free_demuxer(demuxer_t *demuxer)
|
||||
|
||||
if (demuxer->desc->close)
|
||||
demuxer->desc->close(in->d_thread);
|
||||
for (int n = 0; n < demuxer->num_streams; n++)
|
||||
ds_flush(demuxer->streams[n]->ds);
|
||||
for (int n = in->num_streams - 1; n >= 0; n--) {
|
||||
ds_flush(in->streams[n]->ds);
|
||||
talloc_free(in->streams[n]);
|
||||
}
|
||||
pthread_mutex_destroy(&in->lock);
|
||||
pthread_cond_destroy(&in->wakeup);
|
||||
talloc_free(demuxer);
|
||||
@@ -383,8 +437,8 @@ static bool read_packet(struct demux_internal *in)
|
||||
// safe-guards against packet queue overflow.
|
||||
bool active = false, read_more = false;
|
||||
size_t packs = 0, bytes = 0;
|
||||
for (int n = 0; n < in->d_buffer->num_streams; n++) {
|
||||
struct demux_stream *ds = in->d_buffer->streams[n]->ds;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
active |= ds->active;
|
||||
read_more |= ds->active && !ds->head;
|
||||
packs += ds->packs;
|
||||
@@ -399,16 +453,16 @@ static bool read_packet(struct demux_internal *in)
|
||||
if (!in->warned_queue_overflow) {
|
||||
in->warned_queue_overflow = true;
|
||||
MP_ERR(in, "Too many packets in the demuxer packet queues:\n");
|
||||
for (int n = 0; n < in->d_buffer->num_streams; n++) {
|
||||
struct demux_stream *ds = in->d_buffer->streams[n]->ds;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
if (ds->selected) {
|
||||
MP_ERR(in, " %s/%d: %zd packets, %zd bytes\n",
|
||||
stream_type_name(ds->type), n, ds->packs, ds->bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int n = 0; n < in->d_buffer->num_streams; n++) {
|
||||
struct demux_stream *ds = in->d_buffer->streams[n]->ds;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
ds->eof |= !ds->head;
|
||||
}
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
@@ -428,8 +482,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++)
|
||||
in->d_buffer->streams[n]->ds->eof = true;
|
||||
for (int n = 0; n < in->num_streams; n++)
|
||||
in->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)
|
||||
@@ -473,8 +527,8 @@ static void start_refreshing(struct demux_internal *in)
|
||||
in->start_refresh_seek = false;
|
||||
|
||||
double start_ts = MP_NOPTS_VALUE;
|
||||
for (int n = 0; n < demux->num_streams; n++) {
|
||||
struct demux_stream *ds = demux->streams[n]->ds;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
if (ds->type == STREAM_VIDEO || ds->type == STREAM_AUDIO)
|
||||
start_ts = MP_PTS_MIN(start_ts, ds->base_ts);
|
||||
}
|
||||
@@ -483,8 +537,8 @@ static void start_refreshing(struct demux_internal *in)
|
||||
demux->partially_seekable || !demux->allow_refresh_seeks)
|
||||
return;
|
||||
|
||||
for (int n = 0; n < demux->num_streams; n++) {
|
||||
struct demux_stream *ds = demux->streams[n]->ds;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
// Streams which didn't read any packets yet can return all packets,
|
||||
// or they'd be stuck forever; affects newly selected streams too.
|
||||
if (ds->last_pos != -1)
|
||||
@@ -607,6 +661,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;
|
||||
}
|
||||
|
||||
@@ -627,15 +684,33 @@ struct demux_packet *demux_read_packet(struct sh_stream *sh)
|
||||
return pkt;
|
||||
}
|
||||
|
||||
// Sparse packets (Subtitles) interleaved with other non-sparse packets (video,
|
||||
// audio) should never be read actively, meaning the demuxer thread does not
|
||||
// try to exceed default readahead in order to find a new packet.
|
||||
static bool use_lazy_subtitle_reading(struct demux_stream *ds)
|
||||
{
|
||||
if (ds->type != STREAM_SUB)
|
||||
return false;
|
||||
for (int n = 0; n < ds->in->num_streams; n++) {
|
||||
struct demux_stream *s = ds->in->streams[n]->ds;
|
||||
if (s->type != STREAM_SUB && s->selected && !s->eof)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Poll the demuxer queue, and if there's a packet, return it. Otherwise, just
|
||||
// make the demuxer thread read packets for this stream, and if there's at
|
||||
// least one packet, call the wakeup callback.
|
||||
// Unlike demux_read_packet(), this always enables readahead (which means you
|
||||
// must not use it on interleaved subtitle streams).
|
||||
// Unlike demux_read_packet(), this always enables readahead (except for
|
||||
// interleaved subtitles).
|
||||
// Returns:
|
||||
// < 0: EOF was reached, *out_pkt=NULL
|
||||
// == 0: no new packet yet, but maybe later, *out_pkt=NULL
|
||||
// > 0: new packet read, *out_pkt is set
|
||||
// Note: when reading interleaved subtitles, the demuxer won't try to forcibly
|
||||
// read ahead to get the next subtitle packet (as the next packet could be
|
||||
// minutes away). In this situation, this function will just return -1.
|
||||
int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt)
|
||||
{
|
||||
struct demux_stream *ds = sh ? sh->ds : NULL;
|
||||
@@ -645,10 +720,14 @@ int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt)
|
||||
if (ds->in->threading) {
|
||||
pthread_mutex_lock(&ds->in->lock);
|
||||
*out_pkt = dequeue_packet(ds);
|
||||
r = *out_pkt ? 1 : ((ds->eof || !ds->selected) ? -1 : 0);
|
||||
ds->active = ds->selected; // enable readahead
|
||||
ds->in->eof = false; // force retry
|
||||
pthread_cond_signal(&ds->in->wakeup); // possibly read more
|
||||
if (use_lazy_subtitle_reading(ds)) {
|
||||
r = *out_pkt ? 1 : -1;
|
||||
} else {
|
||||
r = *out_pkt ? 1 : ((ds->eof || !ds->selected) ? -1 : 0);
|
||||
ds->active = ds->selected; // enable readahead
|
||||
ds->in->eof = false; // force retry
|
||||
pthread_cond_signal(&ds->in->wakeup); // possibly read more
|
||||
}
|
||||
pthread_mutex_unlock(&ds->in->lock);
|
||||
} else {
|
||||
*out_pkt = demux_read_packet(sh);
|
||||
@@ -658,22 +737,6 @@ int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt)
|
||||
return r;
|
||||
}
|
||||
|
||||
// Return the pts of the next packet that demux_read_packet() would return.
|
||||
// Might block. Sometimes used to force a packet read, without removing any
|
||||
// packets from the queue.
|
||||
double demux_get_next_pts(struct sh_stream *sh)
|
||||
{
|
||||
double res = MP_NOPTS_VALUE;
|
||||
if (sh) {
|
||||
pthread_mutex_lock(&sh->ds->in->lock);
|
||||
ds_get_packets(sh->ds);
|
||||
if (sh->ds->head)
|
||||
res = sh->ds->head->pts;
|
||||
pthread_mutex_unlock(&sh->ds->in->lock);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Return whether a packet is queued. Never blocks, never forces any reads.
|
||||
bool demux_has_packet(struct sh_stream *sh)
|
||||
{
|
||||
@@ -689,21 +752,22 @@ bool demux_has_packet(struct sh_stream *sh)
|
||||
// Read and return any packet we find.
|
||||
struct demux_packet *demux_read_any_packet(struct demuxer *demuxer)
|
||||
{
|
||||
assert(!demuxer->in->threading); // doesn't work with threading
|
||||
struct demux_internal *in = demuxer->in;
|
||||
assert(!in->threading); // doesn't work with threading
|
||||
bool read_more = true;
|
||||
while (read_more) {
|
||||
for (int n = 0; n < demuxer->num_streams; n++) {
|
||||
struct sh_stream *sh = demuxer->streams[n];
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct sh_stream *sh = in->streams[n];
|
||||
sh->ds->active = sh->ds->selected; // force read_packet() to read
|
||||
struct demux_packet *pkt = dequeue_packet(sh->ds);
|
||||
if (pkt)
|
||||
return pkt;
|
||||
}
|
||||
// retry after calling this
|
||||
pthread_mutex_lock(&demuxer->in->lock);
|
||||
read_more = read_packet(demuxer->in);
|
||||
read_more &= !demuxer->in->eof;
|
||||
pthread_mutex_unlock(&demuxer->in->lock);
|
||||
pthread_mutex_lock(&in->lock); // lock only because read_packet unlocks
|
||||
read_more = read_packet(in);
|
||||
read_more &= !in->eof;
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -787,13 +851,14 @@ static int decode_peak(demuxer_t *demuxer, const char *tag, float *out)
|
||||
|
||||
static void apply_replaygain(demuxer_t *demuxer, struct replaygain_data *rg)
|
||||
{
|
||||
for (int n = 0; n < demuxer->num_streams; n++) {
|
||||
struct sh_stream *sh = demuxer->streams[n];
|
||||
if (sh->audio && !sh->audio->replaygain_data) {
|
||||
struct demux_internal *in = demuxer->in;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct sh_stream *sh = in->streams[n];
|
||||
if (sh->type == STREAM_AUDIO && !sh->codec->replaygain_data) {
|
||||
MP_VERBOSE(demuxer, "Replaygain: Track=%f/%f Album=%f/%f\n",
|
||||
rg->track_gain, rg->track_peak,
|
||||
rg->album_gain, rg->album_peak);
|
||||
sh->audio->replaygain_data = talloc_memdup(demuxer, rg, sizeof(*rg));
|
||||
sh->codec->replaygain_data = talloc_memdup(in, rg, sizeof(*rg));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -845,11 +910,6 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
dst->start_time = src->start_time;
|
||||
dst->priv = src->priv;
|
||||
}
|
||||
if (src->events & DEMUX_EVENT_STREAMS) {
|
||||
// The stream structs themselves are immutable.
|
||||
for (int n = dst->num_streams; n < src->num_streams; n++)
|
||||
MP_TARRAY_APPEND(dst, dst->streams, dst->num_streams, src->streams[n]);
|
||||
}
|
||||
if (src->events & DEMUX_EVENT_METADATA) {
|
||||
talloc_free(dst->metadata);
|
||||
dst->metadata = mp_tags_dup(dst, src->metadata);
|
||||
@@ -897,6 +957,8 @@ void demux_update(demuxer_t *demuxer)
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
demux_copy(demuxer, in->d_buffer);
|
||||
demuxer->events |= in->events;
|
||||
in->events = 0;
|
||||
if (in->stream_metadata && (demuxer->events & DEMUX_EVENT_METADATA))
|
||||
mp_tags_merge(demuxer->metadata, in->stream_metadata);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
@@ -918,10 +980,15 @@ static void demux_init_cuesheet(struct demuxer *demuxer)
|
||||
if (cue && !demuxer->num_chapters) {
|
||||
struct cue_file *f = mp_parse_cue(bstr0(cue));
|
||||
if (f) {
|
||||
for (int n = 0; n < f->num_tracks; n++) {
|
||||
struct cue_track *t = &f->tracks[n];
|
||||
int idx = demuxer_add_chapter(demuxer, "", t->start, -1);
|
||||
mp_tags_merge(demuxer->chapters[idx].metadata, t->tags);
|
||||
if (mp_check_embedded_cue(f) < 0) {
|
||||
MP_WARN(demuxer, "Embedded cue sheet references more than one file. "
|
||||
"Ignoring it.\n");
|
||||
} else {
|
||||
for (int n = 0; n < f->num_tracks; n++) {
|
||||
struct cue_track *t = &f->tracks[n];
|
||||
int idx = demuxer_add_chapter(demuxer, "", t->start, -1);
|
||||
mp_tags_merge(demuxer->chapters[idx].metadata, t->tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
talloc_free(f);
|
||||
@@ -1100,8 +1167,8 @@ struct demuxer *demux_open_url(const char *url,
|
||||
|
||||
static void flush_locked(demuxer_t *demuxer)
|
||||
{
|
||||
for (int n = 0; n < demuxer->num_streams; n++)
|
||||
ds_flush(demuxer->streams[n]->ds);
|
||||
for (int n = 0; n < demuxer->in->num_streams; n++)
|
||||
ds_flush(demuxer->in->streams[n]->ds);
|
||||
demuxer->in->warned_queue_overflow = false;
|
||||
demuxer->in->eof = false;
|
||||
demuxer->in->last_eof = false;
|
||||
@@ -1152,6 +1219,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);
|
||||
@@ -1177,10 +1246,11 @@ void demux_set_enable_refresh_seeks(struct demuxer *demuxer, bool enabled)
|
||||
struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
|
||||
enum stream_type t, int id)
|
||||
{
|
||||
for (int n = 0; n < d->num_streams; n++) {
|
||||
struct sh_stream *s = d->streams[n];
|
||||
int num = demux_get_num_stream(d);
|
||||
for (int n = 0; n < num; n++) {
|
||||
struct sh_stream *s = demux_get_stream(d, n);
|
||||
if (s->type == t && s->demuxer_id == id)
|
||||
return d->streams[n];
|
||||
return s;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -1190,8 +1260,9 @@ void demuxer_switch_track(struct demuxer *demuxer, enum stream_type type,
|
||||
{
|
||||
assert(!stream || stream->type == type);
|
||||
|
||||
for (int n = 0; n < demuxer->num_streams; n++) {
|
||||
struct sh_stream *cur = demuxer->streams[n];
|
||||
int num = demux_get_num_stream(demuxer);
|
||||
for (int n = 0; n < num; n++) {
|
||||
struct sh_stream *cur = demux_get_stream(demuxer, n);
|
||||
if (cur->type == type)
|
||||
demuxer_select_track(demuxer, cur, cur == stream);
|
||||
}
|
||||
@@ -1393,8 +1464,8 @@ static int cached_demux_control(struct demux_internal *in, int cmd, void *arg)
|
||||
double *rates = arg;
|
||||
for (int n = 0; n < STREAM_TYPE_COUNT; n++)
|
||||
rates[n] = -1;
|
||||
for (int n = 0; n < in->d_user->num_streams; n++) {
|
||||
struct demux_stream *ds = in->d_user->streams[n]->ds;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
if (ds->selected && ds->bitrate >= 0)
|
||||
rates[ds->type] = MPMAX(0, rates[ds->type]) + ds->bitrate;
|
||||
}
|
||||
@@ -1408,8 +1479,8 @@ static int cached_demux_control(struct demux_internal *in, int cmd, void *arg)
|
||||
.ts_duration = -1,
|
||||
};
|
||||
int num_packets = 0;
|
||||
for (int n = 0; n < in->d_user->num_streams; n++) {
|
||||
struct demux_stream *ds = in->d_user->streams[n]->ds;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
if (ds->active) {
|
||||
r->underrun |= !ds->head && !ds->eof;
|
||||
r->ts_range[0] = MP_PTS_MAX(r->ts_range[0], ds->base_ts);
|
||||
@@ -1423,6 +1494,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;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-7
@@ -86,8 +86,6 @@ enum demux_event {
|
||||
DEMUX_EVENT_ALL = 0xFFFF,
|
||||
};
|
||||
|
||||
#define MAX_SH_STREAMS 256
|
||||
|
||||
struct demuxer;
|
||||
struct timeline;
|
||||
|
||||
@@ -199,9 +197,6 @@ typedef struct demuxer {
|
||||
// Bitmask of DEMUX_EVENT_*
|
||||
int events;
|
||||
|
||||
struct sh_stream **streams;
|
||||
int num_streams;
|
||||
|
||||
struct demux_edition *editions;
|
||||
int num_editions;
|
||||
int edition;
|
||||
@@ -247,11 +242,14 @@ 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);
|
||||
bool demux_stream_is_selected(struct sh_stream *stream);
|
||||
double demux_get_next_pts(struct sh_stream *sh);
|
||||
bool demux_has_packet(struct sh_stream *sh);
|
||||
struct demux_packet *demux_read_any_packet(struct demuxer *demuxer);
|
||||
|
||||
struct sh_stream *new_sh_stream(struct demuxer *demuxer, enum stream_type type);
|
||||
struct sh_stream *demux_get_stream(struct demuxer *demuxer, int index);
|
||||
int demux_get_num_stream(struct demuxer *demuxer);
|
||||
|
||||
struct sh_stream *demux_alloc_sh_stream(enum stream_type type);
|
||||
void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh);
|
||||
|
||||
struct demuxer *demux_open(struct stream *stream, struct demuxer_params *params,
|
||||
struct mpv_global *global);
|
||||
@@ -271,6 +269,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);
|
||||
|
||||
|
||||
+13
-3
@@ -23,7 +23,7 @@
|
||||
#include <dirent.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "misc/bstr.h"
|
||||
#include "common/msg.h"
|
||||
@@ -150,8 +150,18 @@ static void build_timeline(struct timeline *tl)
|
||||
|
||||
add_source(tl, tl->demuxer);
|
||||
|
||||
struct cue_track *tracks = p->f->tracks;
|
||||
size_t track_count = p->f->num_tracks;
|
||||
struct cue_track *tracks = NULL;
|
||||
size_t track_count = 0;
|
||||
|
||||
for (size_t n = 0; n < p->f->num_tracks; n++) {
|
||||
struct cue_track *track = &p->f->tracks[n];
|
||||
if (track->filename) {
|
||||
MP_TARRAY_APPEND(ctx, tracks, track_count, *track);
|
||||
} else {
|
||||
MP_WARN(tl->demuxer, "No file specified for track entry %zd. "
|
||||
"It will be removed\n", n + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (track_count == 0) {
|
||||
MP_ERR(tl, "CUE: no tracks found!\n");
|
||||
|
||||
+28
-26
@@ -23,6 +23,7 @@
|
||||
#include "common/msg.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
#include "video/mp_image.h"
|
||||
#include "demux.h"
|
||||
#include "stheader.h"
|
||||
|
||||
@@ -53,9 +54,10 @@ struct priv {
|
||||
static void reselect_streams(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
for (int n = 0; n < MPMIN(p->slave->num_streams, p->num_streams); n++) {
|
||||
int num_slave = demux_get_num_stream(p->slave);
|
||||
for (int n = 0; n < MPMIN(num_slave, p->num_streams); n++) {
|
||||
if (p->streams[n]) {
|
||||
demuxer_select_track(p->slave, p->slave->streams[n],
|
||||
demuxer_select_track(p->slave, demux_get_stream(p->slave, n),
|
||||
demux_stream_is_selected(p->streams[n]));
|
||||
}
|
||||
}
|
||||
@@ -80,11 +82,9 @@ static void add_dvd_streams(demuxer_t *demuxer)
|
||||
struct stream_dvd_info_req info;
|
||||
if (stream_control(stream, STREAM_CTRL_GET_DVD_INFO, &info) > 0) {
|
||||
for (int n = 0; n < MPMIN(32, info.num_subs); n++) {
|
||||
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_SUB);
|
||||
if (!sh)
|
||||
break;
|
||||
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_SUB);
|
||||
sh->demuxer_id = n + 0x20;
|
||||
sh->codec = "dvd_subtitle";
|
||||
sh->codec->codec = "dvd_subtitle";
|
||||
get_disc_lang(stream, sh);
|
||||
// p->streams _must_ match with p->slave->streams, so we can't add
|
||||
// it yet - it has to be done when the real stream appears, which
|
||||
@@ -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)
|
||||
@@ -112,8 +111,10 @@ static void add_dvd_streams(demuxer_t *demuxer)
|
||||
}
|
||||
s = talloc_asprintf_append(s, "\n");
|
||||
|
||||
sh->extradata = s;
|
||||
sh->extradata_size = strlen(s);
|
||||
sh->codec->extradata = s;
|
||||
sh->codec->extradata_size = strlen(s);
|
||||
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,9 +123,9 @@ static void add_streams(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
for (int n = p->num_streams; n < p->slave->num_streams; n++) {
|
||||
struct sh_stream *src = p->slave->streams[n];
|
||||
if (src->sub) {
|
||||
for (int n = p->num_streams; n < demux_get_num_stream(p->slave); n++) {
|
||||
struct sh_stream *src = demux_get_stream(p->slave, n);
|
||||
if (src->type == STREAM_SUB) {
|
||||
struct sh_stream *sub = NULL;
|
||||
if (src->demuxer_id >= 0x20 && src->demuxer_id <= 0x3F)
|
||||
sub = p->dvd_subs[src->demuxer_id - 0x20];
|
||||
@@ -134,25 +135,26 @@ static void add_streams(demuxer_t *demuxer)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
struct sh_stream *sh = new_sh_stream(demuxer, src->type);
|
||||
if (!sh)
|
||||
break;
|
||||
struct sh_stream *sh = demux_alloc_sh_stream(src->type);
|
||||
assert(p->num_streams == n); // directly mapped
|
||||
MP_TARRAY_APPEND(p, p->streams, p->num_streams, sh);
|
||||
// Copy all stream fields that might be relevant
|
||||
sh->codec = talloc_strdup(sh, src->codec);
|
||||
sh->codec_tag = src->codec_tag;
|
||||
sh->lav_headers = src->lav_headers;
|
||||
*sh->codec = *src->codec;
|
||||
sh->demuxer_id = src->demuxer_id;
|
||||
if (src->video) {
|
||||
if (src->type == STREAM_VIDEO) {
|
||||
double ar;
|
||||
if (stream_control(demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar)
|
||||
== STREAM_OK)
|
||||
sh->video->aspect = ar;
|
||||
{
|
||||
struct mp_image_params f = {.w = src->codec->disp_w,
|
||||
.h = src->codec->disp_h};
|
||||
mp_image_params_set_dsize(&f, 1728 * ar, 1728);
|
||||
sh->codec->par_w = f.p_w;
|
||||
sh->codec->par_h = f.p_h;
|
||||
}
|
||||
}
|
||||
if (src->audio)
|
||||
sh->audio = src->audio;
|
||||
get_disc_lang(demuxer->stream, sh);
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
}
|
||||
reselect_streams(demuxer);
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "demux.h"
|
||||
#include "timeline.h"
|
||||
|
||||
+107
-78
@@ -40,6 +40,7 @@
|
||||
#include "common/tags.h"
|
||||
#include "common/av_common.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "misc/charset_conv.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
#include "demux.h"
|
||||
@@ -108,6 +109,7 @@ struct format_hack {
|
||||
bool no_stream : 1; // do not wrap struct stream as AVIOContext
|
||||
bool use_stream_ids : 1; // export the native stream IDs
|
||||
bool fully_read : 1; // set demuxer.fully_read flag
|
||||
bool detect_charset : 1; // format is a small text file, possibly not UTF8
|
||||
bool image_format : 1; // expected to contain exactly 1 frame
|
||||
// Do not confuse player's position estimation (position is into external
|
||||
// segment, with e.g. HLS, player knows about the playlist main file only).
|
||||
@@ -115,8 +117,8 @@ struct format_hack {
|
||||
};
|
||||
|
||||
#define BLACKLIST(fmt) {fmt, .ignore = true}
|
||||
#define TEXTSUB(fmt) {fmt, .fully_read = true}
|
||||
#define IMAGEFMT(fmt) {fmt, .image_format = true}
|
||||
#define TEXTSUB(fmt) {fmt, .fully_read = true, .detect_charset = true}
|
||||
#define TEXTSUB_UTF8(fmt) {fmt, .fully_read = true}
|
||||
|
||||
static const struct format_hack format_hacks[] = {
|
||||
// for webradios
|
||||
@@ -135,10 +137,12 @@ static const struct format_hack format_hacks[] = {
|
||||
{"h264", .if_flags = AVFMT_NOTIMESTAMPS },
|
||||
{"hevc", .if_flags = AVFMT_NOTIMESTAMPS },
|
||||
|
||||
TEXTSUB("aqtitle"), TEXTSUB("ass"), TEXTSUB("jacosub"), TEXTSUB("microdvd"),
|
||||
TEXTSUB("aqtitle"), TEXTSUB("jacosub"), TEXTSUB("microdvd"),
|
||||
TEXTSUB("mpl2"), TEXTSUB("mpsub"), TEXTSUB("pjs"), TEXTSUB("realtext"),
|
||||
TEXTSUB("sami"), TEXTSUB("srt"), TEXTSUB("stl"), TEXTSUB("subviewer"),
|
||||
TEXTSUB("subviewer1"), TEXTSUB("vplayer"), TEXTSUB("webvtt"),
|
||||
TEXTSUB("subviewer1"), TEXTSUB("vplayer"), TEXTSUB("ass"),
|
||||
|
||||
TEXTSUB_UTF8("webvtt"),
|
||||
|
||||
// Useless non-sense, sometimes breaks MLP2 subreader.c fallback
|
||||
BLACKLIST("tty"),
|
||||
@@ -148,11 +152,12 @@ static const struct format_hack format_hacks[] = {
|
||||
// Useless, does not work with custom streams.
|
||||
BLACKLIST("image2"),
|
||||
// Image demuxers ("<name>_pipe" is detected explicitly)
|
||||
IMAGEFMT("image2pipe"),
|
||||
{"image2pipe", .image_format = true},
|
||||
{0}
|
||||
};
|
||||
|
||||
typedef struct lavf_priv {
|
||||
struct stream *stream;
|
||||
char *filename;
|
||||
struct format_hack format_hack;
|
||||
AVInputFormat *avif;
|
||||
@@ -187,7 +192,8 @@ static bool matches_avinputformat_name(struct lavf_priv *priv,
|
||||
static int mp_read(void *opaque, uint8_t *buf, int size)
|
||||
{
|
||||
struct demuxer *demuxer = opaque;
|
||||
struct stream *stream = demuxer->stream;
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
struct stream *stream = priv->stream;
|
||||
int ret;
|
||||
|
||||
ret = stream_read(stream, buf, size);
|
||||
@@ -200,9 +206,13 @@ static int mp_read(void *opaque, uint8_t *buf, int size)
|
||||
static int64_t mp_seek(void *opaque, int64_t pos, int whence)
|
||||
{
|
||||
struct demuxer *demuxer = opaque;
|
||||
struct stream *stream = demuxer->stream;
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
struct stream *stream = priv->stream;
|
||||
int64_t current_pos;
|
||||
MP_TRACE(demuxer, "mp_seek(%p, %"PRId64", %d)\n", stream, pos, whence);
|
||||
MP_TRACE(demuxer, "mp_seek(%p, %"PRId64", %s)\n", stream, pos,
|
||||
whence == SEEK_END ? "end" :
|
||||
whence == SEEK_CUR ? "cur" :
|
||||
whence == SEEK_SET ? "set" : "size");
|
||||
if (whence == SEEK_END || whence == AVSEEK_SIZE) {
|
||||
int64_t end = stream_get_size(stream);
|
||||
if (end < 0)
|
||||
@@ -230,7 +240,8 @@ static int64_t mp_seek(void *opaque, int64_t pos, int whence)
|
||||
static int64_t mp_read_seek(void *opaque, int stream_idx, int64_t ts, int flags)
|
||||
{
|
||||
struct demuxer *demuxer = opaque;
|
||||
struct stream *stream = demuxer->stream;
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
struct stream *stream = priv->stream;
|
||||
|
||||
struct stream_avseek cmd = {
|
||||
.stream_index = stream_idx,
|
||||
@@ -253,6 +264,32 @@ static void list_formats(struct demuxer *demuxer)
|
||||
MP_INFO(demuxer, "%15s : %s\n", fmt->name, fmt->long_name);
|
||||
}
|
||||
|
||||
static void convert_charset(struct demuxer *demuxer)
|
||||
{
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
char *cp = demuxer->opts->sub_cp;
|
||||
if (!cp || mp_charset_is_utf8(cp))
|
||||
return;
|
||||
bstr data = stream_read_complete(priv->stream, NULL, 128 * 1024 * 1024);
|
||||
if (!data.start) {
|
||||
MP_WARN(demuxer, "File too big (or error reading) - skip charset probing.\n");
|
||||
return;
|
||||
}
|
||||
void *alloc = data.start;
|
||||
cp = (char *)mp_charset_guess(priv, demuxer->log, data, cp, 0);
|
||||
if (cp && !mp_charset_is_utf8(cp))
|
||||
MP_INFO(demuxer, "Using subtitle charset: %s\n", cp);
|
||||
// libavformat transparently converts UTF-16 to UTF-8
|
||||
if (!mp_charset_is_utf16(cp) && !mp_charset_is_utf8(cp)) {
|
||||
bstr conv = mp_iconv_to_utf8(demuxer->log, data, cp, MP_ICONV_VERBOSE);
|
||||
if (conv.start)
|
||||
data = conv;
|
||||
}
|
||||
if (data.start)
|
||||
priv->stream = open_memory_stream(data.start, data.len);
|
||||
talloc_free(alloc);
|
||||
}
|
||||
|
||||
static char *remove_prefix(char *s, const char *const *prefixes)
|
||||
{
|
||||
for (int n = 0; prefixes[n]; n++) {
|
||||
@@ -270,12 +307,8 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
struct MPOpts *opts = demuxer->opts;
|
||||
struct demux_lavf_opts *lavfdopts = opts->demux_lavf;
|
||||
struct stream *s = demuxer->stream;
|
||||
lavf_priv_t *priv;
|
||||
|
||||
assert(!demuxer->priv);
|
||||
demuxer->priv = talloc_zero(NULL, lavf_priv_t);
|
||||
priv = demuxer->priv;
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
struct stream *s = priv->stream;
|
||||
|
||||
priv->filename = remove_prefix(s->url, prefixes);
|
||||
|
||||
@@ -292,7 +325,7 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
priv->filename = sep + 1;
|
||||
}
|
||||
|
||||
char *mime_type = demuxer->stream->mime_type;
|
||||
char *mime_type = s->mime_type;
|
||||
if (!lavfdopts->allow_mimetype || !mime_type)
|
||||
mime_type = "";
|
||||
|
||||
@@ -393,6 +426,9 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
demuxer->filetype = priv->avif->name;
|
||||
|
||||
if (priv->format_hack.detect_charset)
|
||||
convert_charset(demuxer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -464,7 +500,8 @@ static void select_tracks(struct demuxer *demuxer, int start)
|
||||
}
|
||||
}
|
||||
|
||||
static void export_replaygain(demuxer_t *demuxer, sh_audio_t *sh, AVStream *st)
|
||||
static void export_replaygain(demuxer_t *demuxer, struct mp_codec_params *c,
|
||||
AVStream *st)
|
||||
{
|
||||
for (int i = 0; i < st->nb_side_data; i++) {
|
||||
AVReplayGain *av_rgain;
|
||||
@@ -489,7 +526,7 @@ static void export_replaygain(demuxer_t *demuxer, sh_audio_t *sh, AVStream *st)
|
||||
rgain->album_peak = (av_rgain->album_peak != 0.0) ?
|
||||
av_rgain->album_peak / 100000.0f : 1.0;
|
||||
|
||||
sh->replaygain_data = rgain;
|
||||
c->replaygain_data = rgain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,7 +543,7 @@ static int dict_get_decimal(AVDictionary *dict, const char *entry, int def)
|
||||
return def;
|
||||
}
|
||||
|
||||
static void handle_stream(demuxer_t *demuxer, int i)
|
||||
static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
{
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
AVFormatContext *avfc = priv->avfc;
|
||||
@@ -516,27 +553,21 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
|
||||
switch (codec->codec_type) {
|
||||
case AVMEDIA_TYPE_AUDIO: {
|
||||
sh = new_sh_stream(demuxer, STREAM_AUDIO);
|
||||
if (!sh)
|
||||
break;
|
||||
sh_audio_t *sh_audio = sh->audio;
|
||||
sh = demux_alloc_sh_stream(STREAM_AUDIO);
|
||||
|
||||
// probably unneeded
|
||||
mp_chmap_set_unknown(&sh_audio->channels, codec->channels);
|
||||
mp_chmap_set_unknown(&sh->codec->channels, codec->channels);
|
||||
if (codec->channel_layout)
|
||||
mp_chmap_from_lavc(&sh_audio->channels, codec->channel_layout);
|
||||
sh_audio->samplerate = codec->sample_rate;
|
||||
sh_audio->bitrate = codec->bit_rate;
|
||||
mp_chmap_from_lavc(&sh->codec->channels, codec->channel_layout);
|
||||
sh->codec->samplerate = codec->sample_rate;
|
||||
sh->codec->bitrate = codec->bit_rate;
|
||||
|
||||
export_replaygain(demuxer, sh_audio, st);
|
||||
export_replaygain(demuxer, sh->codec, st);
|
||||
|
||||
break;
|
||||
}
|
||||
case AVMEDIA_TYPE_VIDEO: {
|
||||
sh = new_sh_stream(demuxer, STREAM_VIDEO);
|
||||
if (!sh)
|
||||
break;
|
||||
sh_video_t *sh_video = sh->video;
|
||||
sh = demux_alloc_sh_stream(STREAM_VIDEO);
|
||||
|
||||
if (st->disposition & AV_DISPOSITION_ATTACHED_PIC) {
|
||||
sh->attached_picture = new_demux_packet_from(st->attached_pic.data,
|
||||
@@ -548,8 +579,8 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
}
|
||||
}
|
||||
|
||||
sh_video->disp_w = codec->width;
|
||||
sh_video->disp_h = codec->height;
|
||||
sh->codec->disp_w = codec->width;
|
||||
sh->codec->disp_h = codec->height;
|
||||
/* Try to make up some frame rate value, even if it's not reliable.
|
||||
* FPS information is needed to support subtitle formats which base
|
||||
* timing on frame numbers.
|
||||
@@ -564,39 +595,31 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
fps = 1.0 / FFMAX(av_q2d(st->time_base),
|
||||
av_q2d(st->codec->time_base) *
|
||||
st->codec->ticks_per_frame);
|
||||
sh_video->fps = fps;
|
||||
sh->codec->fps = fps;
|
||||
if (priv->format_hack.image_format)
|
||||
sh_video->fps = demuxer->opts->mf_fps;
|
||||
if (st->sample_aspect_ratio.num)
|
||||
sh_video->aspect = codec->width * st->sample_aspect_ratio.num
|
||||
/ (float)(codec->height * st->sample_aspect_ratio.den);
|
||||
else
|
||||
sh_video->aspect = codec->width * codec->sample_aspect_ratio.num
|
||||
/ (float)(codec->height * codec->sample_aspect_ratio.den);
|
||||
sh->codec->fps = demuxer->opts->mf_fps;
|
||||
sh->codec->par_w = st->sample_aspect_ratio.num;
|
||||
sh->codec->par_h = st->sample_aspect_ratio.den;
|
||||
|
||||
uint8_t *sd = av_stream_get_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, NULL);
|
||||
if (sd) {
|
||||
double r = av_display_rotation_get((uint32_t *)sd);
|
||||
if (!isnan(r))
|
||||
sh_video->rotate = (((int)(-r) % 360) + 360) % 360;
|
||||
sh->codec->rotate = (((int)(-r) % 360) + 360) % 360;
|
||||
}
|
||||
|
||||
// This also applies to vfw-muxed mkv, but we can't detect these easily.
|
||||
sh_video->avi_dts = matches_avinputformat_name(priv, "avi");
|
||||
sh->codec->avi_dts = matches_avinputformat_name(priv, "avi");
|
||||
|
||||
break;
|
||||
}
|
||||
case AVMEDIA_TYPE_SUBTITLE: {
|
||||
sh_sub_t *sh_sub;
|
||||
sh = new_sh_stream(demuxer, STREAM_SUB);
|
||||
if (!sh)
|
||||
break;
|
||||
sh_sub = sh->sub;
|
||||
sh = demux_alloc_sh_stream(STREAM_SUB);
|
||||
|
||||
if (codec->extradata_size) {
|
||||
sh->extradata = talloc_size(sh, codec->extradata_size);
|
||||
memcpy(sh->extradata, codec->extradata, codec->extradata_size);
|
||||
sh->extradata_size = codec->extradata_size;
|
||||
sh->codec->extradata = talloc_size(sh, codec->extradata_size);
|
||||
memcpy(sh->codec->extradata, codec->extradata, codec->extradata_size);
|
||||
sh->codec->extradata_size = codec->extradata_size;
|
||||
}
|
||||
|
||||
if (matches_avinputformat_name(priv, "microdvd")) {
|
||||
@@ -604,12 +627,12 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
if (av_opt_get_q(avfc, "subfps", AV_OPT_SEARCH_CHILDREN, &r) >= 0) {
|
||||
// File headers don't have a FPS set.
|
||||
if (r.num < 1 || r.den < 1)
|
||||
sh_sub->frame_based = av_q2d(av_inv_q(codec->time_base));
|
||||
sh->codec->frame_based = av_q2d(av_inv_q(codec->time_base));
|
||||
} else {
|
||||
// Older libavformat versions. If the FPS matches the microdvd
|
||||
// reader's default, assume it uses frame based timing.
|
||||
if (codec->time_base.num == 125 && codec->time_base.den == 2997)
|
||||
sh_sub->frame_based = 23.976;
|
||||
sh->codec->frame_based = 23.976;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -633,12 +656,11 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
|
||||
if (sh) {
|
||||
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 = avcodec_alloc_context3(NULL);
|
||||
if (!sh->lav_headers)
|
||||
return;
|
||||
mp_copy_lav_codec_headers(sh->lav_headers, codec);
|
||||
sh->codec->codec = mp_codec_from_av_codec_id(codec->codec_id);
|
||||
sh->codec->codec_tag = codec->codec_tag;
|
||||
sh->codec->lav_headers = avcodec_alloc_context3(NULL);
|
||||
if (sh->codec->lav_headers)
|
||||
mp_copy_lav_codec_headers(sh->codec->lav_headers, codec);
|
||||
|
||||
if (st->disposition & AV_DISPOSITION_DEFAULT)
|
||||
sh->default_track = true;
|
||||
@@ -656,10 +678,10 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
if (!sh->title && sh->hls_bitrate > 0)
|
||||
sh->title = talloc_asprintf(sh, "bitrate %d", sh->hls_bitrate);
|
||||
sh->missing_timestamps = !!(priv->avif_flags & AVFMT_NOTIMESTAMPS);
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
}
|
||||
|
||||
select_tracks(demuxer, i);
|
||||
demux_changed(demuxer, DEMUX_EVENT_STREAMS);
|
||||
}
|
||||
|
||||
// Add any new streams that might have been added
|
||||
@@ -667,7 +689,7 @@ static void add_new_streams(demuxer_t *demuxer)
|
||||
{
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
while (priv->num_streams < priv->avfc->nb_streams)
|
||||
handle_stream(demuxer, priv->num_streams);
|
||||
handle_new_stream(demuxer, priv->num_streams);
|
||||
}
|
||||
|
||||
static void update_metadata(demuxer_t *demuxer, AVPacket *pkt)
|
||||
@@ -693,7 +715,8 @@ static void update_metadata(demuxer_t *demuxer, AVPacket *pkt)
|
||||
static int interrupt_cb(void *ctx)
|
||||
{
|
||||
struct demuxer *demuxer = ctx;
|
||||
return mp_cancel_test(demuxer->stream->cancel);
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
return mp_cancel_test(priv->stream->cancel);
|
||||
}
|
||||
|
||||
static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
@@ -703,15 +726,13 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
AVFormatContext *avfc;
|
||||
AVDictionaryEntry *t = NULL;
|
||||
float analyze_duration = 0;
|
||||
int i;
|
||||
lavf_priv_t *priv = talloc_zero(NULL, lavf_priv_t);
|
||||
demuxer->priv = priv;
|
||||
priv->stream = demuxer->stream;
|
||||
|
||||
if (lavf_check_file(demuxer, check) < 0)
|
||||
return -1;
|
||||
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
if (!priv)
|
||||
return -1;
|
||||
|
||||
avfc = avformat_alloc_context();
|
||||
if (!avfc)
|
||||
return -1;
|
||||
@@ -750,7 +771,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
AVDictionary *dopts = NULL;
|
||||
|
||||
if ((priv->avif_flags & AVFMT_NOFILE) ||
|
||||
demuxer->stream->type == STREAMTYPE_AVDEVICE ||
|
||||
priv->stream->type == STREAMTYPE_AVDEVICE ||
|
||||
priv->format_hack.no_stream)
|
||||
{
|
||||
mp_setup_av_network_options(&dopts, demuxer->global, demuxer->log, opts);
|
||||
@@ -769,7 +790,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
priv->pb->read_seek = mp_read_seek;
|
||||
priv->pb->seekable = demuxer->seekable ? AVIO_SEEKABLE_NORMAL : 0;
|
||||
avfc->pb = priv->pb;
|
||||
if (stream_control(demuxer->stream, STREAM_CTRL_HAS_AVSEEK, NULL) > 0)
|
||||
if (stream_control(priv->stream, STREAM_CTRL_HAS_AVSEEK, NULL) > 0)
|
||||
demuxer->seekable = true;
|
||||
}
|
||||
|
||||
@@ -809,9 +830,9 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
}
|
||||
|
||||
MP_VERBOSE(demuxer, "avformat_find_stream_info() finished after %"PRId64
|
||||
" bytes.\n", stream_tell(demuxer->stream));
|
||||
" bytes.\n", stream_tell(priv->stream));
|
||||
|
||||
for (i = 0; i < avfc->nb_chapters; i++) {
|
||||
for (int i = 0; i < avfc->nb_chapters; i++) {
|
||||
AVChapter *c = avfc->chapters[i];
|
||||
t = av_dict_get(c->metadata, "title", NULL, 0);
|
||||
int index = demuxer_add_chapter(demuxer, t ? t->value : "",
|
||||
@@ -823,7 +844,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
// Often useful with OGG audio-only files, which have metadata in the audio
|
||||
// track metadata instead of the main metadata.
|
||||
if (demuxer->num_streams == 1) {
|
||||
if (demux_get_num_stream(demuxer) == 1) {
|
||||
priv->merge_track_metadata = true;
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
if (priv->streams[n])
|
||||
@@ -921,7 +942,7 @@ static void demux_seek_lavf(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
avsflags = AVSEEK_FLAG_BACKWARD;
|
||||
|
||||
if (flags & SEEK_FACTOR) {
|
||||
struct stream *s = demuxer->stream;
|
||||
struct stream *s = priv->stream;
|
||||
int64_t end = stream_get_size(s);
|
||||
if (end > 0 && demuxer->ts_resets_possible &&
|
||||
!(priv->avif_flags & AVFMT_NO_BYTE_SEEK))
|
||||
@@ -1041,6 +1062,12 @@ redo:
|
||||
goto redo;
|
||||
}
|
||||
priv->cur_program = prog->progid = program->id;
|
||||
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, priv->avfc->programs[p]->metadata);
|
||||
update_metadata(demuxer, NULL);
|
||||
// Enforce metadata update even if no explicit METADATA_UPDATED since we switched program.
|
||||
demux_changed(demuxer, DEMUX_EVENT_METADATA);
|
||||
|
||||
return DEMUXER_CTRL_OK;
|
||||
}
|
||||
case DEMUXER_CTRL_RESYNC:
|
||||
@@ -1057,10 +1084,10 @@ redo:
|
||||
*/
|
||||
// avio_flush() is designed for write-only streams, and does the wrong
|
||||
// thing when reading. Flush it manually instead.
|
||||
stream_drop_buffers(demuxer->stream);
|
||||
stream_drop_buffers(priv->stream);
|
||||
priv->avfc->pb->buf_ptr = priv->avfc->pb->buf_end = priv->avfc->pb->buffer;
|
||||
priv->avfc->pb->pos = stream_tell(demuxer->stream);
|
||||
av_seek_frame(priv->avfc, 0, stream_tell(demuxer->stream),
|
||||
priv->avfc->pb->pos = stream_tell(priv->stream);
|
||||
av_seek_frame(priv->avfc, 0, stream_tell(priv->stream),
|
||||
AVSEEK_FLAG_BYTE);
|
||||
return DEMUXER_CTRL_OK;
|
||||
default:
|
||||
@@ -1081,8 +1108,10 @@ static void demux_close_lavf(demuxer_t *demuxer)
|
||||
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);
|
||||
avcodec_free_context(&priv->streams[n]->codec->lav_headers);
|
||||
}
|
||||
if (priv->stream != demuxer->stream)
|
||||
free_stream(priv->stream);
|
||||
talloc_free(priv);
|
||||
demuxer->priv = NULL;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
+15
-14
@@ -24,7 +24,7 @@
|
||||
|
||||
#include "osdep/io.h"
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/options.h"
|
||||
#include "options/path.h"
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
typedef struct mf {
|
||||
struct mp_log *log;
|
||||
struct sh_video *sh;
|
||||
struct sh_stream *sh;
|
||||
int curr_frame;
|
||||
int nr_of_files;
|
||||
char **names;
|
||||
@@ -165,7 +165,7 @@ static void demux_seek_mf(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
if (flags & SEEK_FACTOR)
|
||||
newpos += rel_seek_secs * (mf->nr_of_files - 1);
|
||||
else
|
||||
newpos += rel_seek_secs * mf->sh->fps;
|
||||
newpos += rel_seek_secs * mf->sh->codec->fps;
|
||||
if (newpos < 0)
|
||||
newpos = 0;
|
||||
if (newpos >= mf->nr_of_files)
|
||||
@@ -199,9 +199,9 @@ static int demux_mf_fill_buffer(demuxer_t *demuxer)
|
||||
demux_packet_t *dp = new_demux_packet(data.len);
|
||||
if (dp) {
|
||||
memcpy(dp->buffer, data.start, data.len);
|
||||
dp->pts = mf->curr_frame / mf->sh->fps;
|
||||
dp->pts = mf->curr_frame / mf->sh->codec->fps;
|
||||
dp->keyframe = true;
|
||||
demux_add_packet(demuxer->streams[0], dp);
|
||||
demux_add_packet(mf->sh, dp);
|
||||
}
|
||||
}
|
||||
talloc_free(data.start);
|
||||
@@ -291,7 +291,6 @@ static const char *probe_format(mf_t *mf, char *type, enum demux_check check)
|
||||
|
||||
static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
sh_video_t *sh_video = NULL;
|
||||
mf_t *mf;
|
||||
|
||||
if (strncmp(demuxer->stream->url, "mf://", 5) == 0 &&
|
||||
@@ -316,15 +315,17 @@ static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
|
||||
mf->curr_frame = 0;
|
||||
|
||||
// create a new video stream header
|
||||
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_VIDEO);
|
||||
sh_video = sh->video;
|
||||
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO);
|
||||
struct mp_codec_params *c = sh->codec;
|
||||
|
||||
sh->codec = codec;
|
||||
sh_video->disp_w = 0;
|
||||
sh_video->disp_h = 0;
|
||||
sh_video->fps = demuxer->opts->mf_fps;
|
||||
c->codec = codec;
|
||||
c->disp_w = 0;
|
||||
c->disp_h = 0;
|
||||
c->fps = demuxer->opts->mf_fps;
|
||||
|
||||
mf->sh = sh_video;
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
|
||||
mf->sh = sh;
|
||||
demuxer->priv = (void *)mf;
|
||||
demuxer->seekable = true;
|
||||
|
||||
@@ -344,7 +345,7 @@ static int demux_control_mf(demuxer_t *demuxer, int cmd, void *arg)
|
||||
|
||||
switch (cmd) {
|
||||
case DEMUXER_CTRL_GET_TIME_LENGTH:
|
||||
*((double *)arg) = (double)mf->nr_of_files / mf->sh->fps;
|
||||
*((double *)arg) = (double)mf->nr_of_files / mf->sh->codec->fps;
|
||||
return DEMUXER_CTRL_OK;
|
||||
|
||||
default:
|
||||
|
||||
+85
-70
@@ -41,13 +41,14 @@
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "common/av_common.h"
|
||||
#include "options/options.h"
|
||||
#include "options/m_option.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "stream/stream.h"
|
||||
#include "video/csputils.h"
|
||||
#include "video/mp_image.h"
|
||||
#include "demux.h"
|
||||
#include "stheader.h"
|
||||
#include "ebml.h"
|
||||
@@ -201,15 +202,19 @@ typedef struct mkv_demuxer {
|
||||
struct demux_mkv_opts {
|
||||
int subtitle_preroll;
|
||||
double subtitle_preroll_secs;
|
||||
double subtitle_preroll_secs_index;
|
||||
int probe_duration;
|
||||
int probe_start_time;
|
||||
};
|
||||
|
||||
const struct m_sub_options demux_mkv_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
OPT_FLAG("subtitle-preroll", subtitle_preroll, 0),
|
||||
OPT_CHOICE("subtitle-preroll", subtitle_preroll, 0,
|
||||
({"no", 0}, {"yes", 1}, {"index", 2})),
|
||||
OPT_DOUBLE("subtitle-preroll-secs", subtitle_preroll_secs,
|
||||
M_OPT_MIN, .min = 0),
|
||||
OPT_DOUBLE("subtitle-preroll-secs-index", subtitle_preroll_secs_index,
|
||||
M_OPT_MIN, .min = 0),
|
||||
OPT_CHOICE("probe-video-duration", probe_duration, 0,
|
||||
({"no", 0}, {"yes", 1}, {"full", 2})),
|
||||
OPT_FLAG("probe-start-time", probe_start_time, 0),
|
||||
@@ -217,7 +222,9 @@ const struct m_sub_options demux_mkv_conf = {
|
||||
},
|
||||
.size = sizeof(struct demux_mkv_opts),
|
||||
.defaults = &(const struct demux_mkv_opts){
|
||||
.subtitle_preroll = 2,
|
||||
.subtitle_preroll_secs = 1.0,
|
||||
.subtitle_preroll_secs_index = 10.0,
|
||||
.probe_start_time = 1,
|
||||
},
|
||||
};
|
||||
@@ -1192,11 +1199,9 @@ static void add_coverart(struct demuxer *demuxer)
|
||||
const char *codec = mp_map_mimetype_to_video_codec(att->type);
|
||||
if (!codec)
|
||||
continue;
|
||||
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_VIDEO);
|
||||
if (!sh)
|
||||
break;
|
||||
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO);
|
||||
sh->demuxer_id = -1 - sh->index; // don't clash with mkv IDs
|
||||
sh->codec = codec;
|
||||
sh->codec->codec = codec;
|
||||
sh->attached_picture = new_demux_packet_from(att->data, att->data_size);
|
||||
if (sh->attached_picture) {
|
||||
sh->attached_picture->pts = 0;
|
||||
@@ -1204,6 +1209,7 @@ static void add_coverart(struct demuxer *demuxer)
|
||||
sh->attached_picture->keyframe = true;
|
||||
}
|
||||
sh->title = att->name;
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1265,18 +1271,16 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
{
|
||||
unsigned char *extradata = NULL;
|
||||
unsigned int extradata_size = 0;
|
||||
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_VIDEO);
|
||||
if (!sh)
|
||||
return 1;
|
||||
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO);
|
||||
init_track(demuxer, track, sh);
|
||||
sh_video_t *sh_v = sh->video;
|
||||
struct mp_codec_params *sh_v = sh->codec;
|
||||
|
||||
sh_v->bits_per_coded_sample = 24;
|
||||
|
||||
if (!strcmp(track->codec_id, "V_MS/VFW/FOURCC")) { /* AVI compatibility mode */
|
||||
// The private_data contains a BITMAPINFOHEADER struct
|
||||
if (track->private_data == NULL || track->private_size < 40)
|
||||
return 1;
|
||||
goto done;
|
||||
|
||||
unsigned char *h = track->private_data;
|
||||
if (track->v_width == 0)
|
||||
@@ -1284,11 +1288,11 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
if (track->v_height == 0)
|
||||
track->v_height = AV_RL32(h + 8); // biHeight
|
||||
sh_v->bits_per_coded_sample = AV_RL16(h + 14); // biBitCount
|
||||
sh->codec_tag = AV_RL32(h + 16); // biCompression
|
||||
sh_v->codec_tag = AV_RL32(h + 16); // biCompression
|
||||
|
||||
extradata = track->private_data + 40;
|
||||
extradata_size = track->private_size - 40;
|
||||
mp_set_codec_from_tag(sh);
|
||||
mp_set_codec_from_tag(sh_v);
|
||||
sh_v->avi_dts = true;
|
||||
} else if (track->private_size >= RVPROPERTIES_SIZE
|
||||
&& (!strcmp(track->codec_id, "V_REAL/RV10")
|
||||
@@ -1304,10 +1308,10 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
cnt = track->private_size - RVPROPERTIES_SIZE;
|
||||
uint32_t t2 = AV_RB32(src - 4);
|
||||
switch (t2 == 0x10003000 || t2 == 0x10003001 ? '1' : track->codec_id[9]) {
|
||||
case '1': sh->codec = "rv10"; break;
|
||||
case '2': sh->codec = "rv20"; break;
|
||||
case '3': sh->codec = "rv30"; break;
|
||||
case '4': sh->codec = "rv40"; break;
|
||||
case '1': sh_v->codec = "rv10"; break;
|
||||
case '2': sh_v->codec = "rv20"; break;
|
||||
case '3': sh_v->codec = "rv30"; break;
|
||||
case '4': sh_v->codec = "rv40"; break;
|
||||
}
|
||||
// copy type1 and type2 info from rv properties
|
||||
extradata_size = cnt + 8;
|
||||
@@ -1316,8 +1320,8 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
track->parse_timebase = 1e3;
|
||||
} else if (strcmp(track->codec_id, "V_UNCOMPRESSED") == 0) {
|
||||
// raw video, "like AVI" - this is a FourCC
|
||||
sh->codec_tag = track->colorspace;
|
||||
sh->codec = "rawvideo";
|
||||
sh_v->codec_tag = track->colorspace;
|
||||
sh_v->codec = "rawvideo";
|
||||
} else if (strcmp(track->codec_id, "V_QUICKTIME") == 0) {
|
||||
uint32_t fourcc1 = 0, fourcc2 = 0;
|
||||
if (track->private_size >= 8) {
|
||||
@@ -1327,14 +1331,14 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
if (fourcc1 == MP_FOURCC('S', 'V', 'Q', '3') ||
|
||||
fourcc2 == MP_FOURCC('S', 'V', 'Q', '3'))
|
||||
{
|
||||
sh->codec = "svq3";
|
||||
sh_v->codec = "svq3";
|
||||
extradata = track->private_data;
|
||||
extradata_size = track->private_size;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; mkv_video_tags[i][0]; i++) {
|
||||
if (!strcmp(mkv_video_tags[i][0], track->codec_id)) {
|
||||
sh->codec = mkv_video_tags[i][1];
|
||||
sh_v->codec = mkv_video_tags[i][1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1344,22 +1348,22 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
}
|
||||
}
|
||||
|
||||
const char *codec = sh->codec ? sh->codec : "";
|
||||
const char *codec = sh_v->codec ? sh_v->codec : "";
|
||||
if (!strcmp(codec, "vp9")) {
|
||||
track->parse = true;
|
||||
track->parse_timebase = 1e9;
|
||||
} else if (!strcmp(codec, "mjpeg")) {
|
||||
sh->codec_tag = MP_FOURCC('m', 'j', 'p', 'g');
|
||||
sh_v->codec_tag = MP_FOURCC('m', 'j', 'p', 'g');
|
||||
}
|
||||
|
||||
if (extradata_size > 0x1000000) {
|
||||
MP_WARN(demuxer, "Invalid CodecPrivate\n");
|
||||
return 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
sh->extradata = talloc_memdup(sh_v, extradata, extradata_size);
|
||||
sh->extradata_size = extradata_size;
|
||||
if (!sh->codec) {
|
||||
sh_v->extradata = talloc_memdup(sh_v, extradata, extradata_size);
|
||||
sh_v->extradata_size = extradata_size;
|
||||
if (!sh_v->codec) {
|
||||
MP_WARN(demuxer, "Unknown/unsupported CodecID (%s) or missing/bad "
|
||||
"CodecPrivate data (track %u).\n",
|
||||
track->codec_id, track->tnum);
|
||||
@@ -1367,12 +1371,19 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
sh_v->fps = track->v_frate;
|
||||
sh_v->disp_w = track->v_width;
|
||||
sh_v->disp_h = track->v_height;
|
||||
uint32_t dw = track->v_dwidth_set ? track->v_dwidth : track->v_width;
|
||||
uint32_t dh = track->v_dheight_set ? track->v_dheight : track->v_height;
|
||||
sh_v->aspect = (dw && dh) ? (double) dw / dh : 0;
|
||||
MP_VERBOSE(demuxer, "Aspect: %f\n", sh_v->aspect);
|
||||
|
||||
int dw = track->v_dwidth_set ? track->v_dwidth : track->v_width;
|
||||
int dh = track->v_dheight_set ? track->v_dheight : track->v_height;
|
||||
struct mp_image_params p = {.w = track->v_width, .h = track->v_height};
|
||||
mp_image_params_set_dsize(&p, dw, dh);
|
||||
sh_v->par_w = p.p_w;
|
||||
sh_v->par_h = p.p_h;
|
||||
|
||||
sh_v->stereo_mode = track->stereo_mode;
|
||||
|
||||
done:
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1464,11 +1475,9 @@ static const char *const mkv_audio_tags[][2] = {
|
||||
|
||||
static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
{
|
||||
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_AUDIO);
|
||||
if (!sh)
|
||||
return 1;
|
||||
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_AUDIO);
|
||||
init_track(demuxer, track, sh);
|
||||
sh_audio_t *sh_a = sh->audio;
|
||||
struct mp_codec_params *sh_a = sh->codec;
|
||||
|
||||
if (track->private_size > 0x1000000)
|
||||
goto error;
|
||||
@@ -1483,7 +1492,7 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
|
||||
for (int i = 0; mkv_audio_tags[i][0]; i++) {
|
||||
if (!strcmp(mkv_audio_tags[i][0], track->codec_id)) {
|
||||
sh->codec = mkv_audio_tags[i][1];
|
||||
sh_a->codec = mkv_audio_tags[i][1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1494,7 +1503,7 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
goto error;
|
||||
MP_VERBOSE(demuxer, "track with MS compat audio.\n");
|
||||
unsigned char *h = track->private_data;
|
||||
sh->codec_tag = AV_RL16(h + 0); // wFormatTag
|
||||
sh_a->codec_tag = AV_RL16(h + 0); // wFormatTag
|
||||
if (track->a_channels == 0)
|
||||
track->a_channels = AV_RL16(h + 2); // nChannels
|
||||
if (sh_a->samplerate == 0)
|
||||
@@ -1506,15 +1515,15 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
extradata = track->private_data + 18;
|
||||
extradata_len = track->private_size - 18;
|
||||
sh_a->bits_per_coded_sample = track->a_bps;
|
||||
mp_set_codec_from_tag(sh);
|
||||
mp_set_codec_from_tag(sh_a);
|
||||
} else if (!strcmp(track->codec_id, "A_PCM/INT/LIT")) {
|
||||
bool sign = sh_a->bits_per_coded_sample > 8;
|
||||
mp_set_pcm_codec(sh, sign, false, sh_a->bits_per_coded_sample, false);
|
||||
mp_set_pcm_codec(sh_a, sign, false, sh_a->bits_per_coded_sample, false);
|
||||
} else if (!strcmp(track->codec_id, "A_PCM/INT/BIG")) {
|
||||
bool sign = sh_a->bits_per_coded_sample > 8;
|
||||
mp_set_pcm_codec(sh, sign, false, sh_a->bits_per_coded_sample, true);
|
||||
mp_set_pcm_codec(sh_a, sign, false, sh_a->bits_per_coded_sample, true);
|
||||
} else if (!strcmp(track->codec_id, "A_PCM/FLOAT/IEEE")) {
|
||||
sh->codec = sh_a->bits_per_coded_sample == 64 ? "pcm_f64le" : "pcm_f32le";
|
||||
sh_a->codec = sh_a->bits_per_coded_sample == 64 ? "pcm_f64le" : "pcm_f32le";
|
||||
} else if (!strncmp(track->codec_id, "A_REAL/", 7)) {
|
||||
if (track->private_size < RAPROPERTIES4_SIZE)
|
||||
goto error;
|
||||
@@ -1554,29 +1563,29 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
extradata = src + offset;
|
||||
|
||||
if (!strcmp(track->codec_id, "A_REAL/ATRC")) {
|
||||
sh->codec = "atrac3";
|
||||
sh_a->codec = "atrac3";
|
||||
if (flavor >= MP_ARRAY_SIZE(atrc_fl2bps))
|
||||
goto error;
|
||||
sh_a->bitrate = atrc_fl2bps[flavor] * 8;
|
||||
sh_a->block_align = track->sub_packet_size;
|
||||
} else if (!strcmp(track->codec_id, "A_REAL/COOK")) {
|
||||
sh->codec = "cook";
|
||||
sh_a->codec = "cook";
|
||||
if (flavor >= MP_ARRAY_SIZE(cook_fl2bps))
|
||||
goto error;
|
||||
sh_a->bitrate = cook_fl2bps[flavor] * 8;
|
||||
sh_a->block_align = track->sub_packet_size;
|
||||
} else if (!strcmp(track->codec_id, "A_REAL/SIPR")) {
|
||||
sh->codec = "sipr";
|
||||
sh_a->codec = "sipr";
|
||||
if (flavor >= MP_ARRAY_SIZE(sipr_fl2bps))
|
||||
goto error;
|
||||
sh_a->bitrate = sipr_fl2bps[flavor] * 8;
|
||||
sh_a->block_align = track->coded_framesize;
|
||||
} else if (!strcmp(track->codec_id, "A_REAL/28_8")) {
|
||||
sh->codec = "ra_288";
|
||||
sh_a->codec = "ra_288";
|
||||
sh_a->bitrate = 3600 * 8;
|
||||
sh_a->block_align = track->coded_framesize;
|
||||
} else if (!strcmp(track->codec_id, "A_REAL/DNET")) {
|
||||
sh->codec = "ac3";
|
||||
sh_a->codec = "ac3";
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
@@ -1586,7 +1595,7 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
track->audio_timestamp =
|
||||
talloc_array(track, double, track->sub_packet_h);
|
||||
} else if (!strncmp(track->codec_id, "A_AAC/", 6)) {
|
||||
sh->codec = "aac";
|
||||
sh_a->codec = "aac";
|
||||
|
||||
/* Recreate the 'private data' (not needed for plain A_AAC) */
|
||||
int srate_idx = aac_get_sample_rate_index(track->a_sfreq);
|
||||
@@ -1618,17 +1627,17 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
track->default_duration = 1024.0 / sh_a->samplerate;
|
||||
}
|
||||
} else if (!strncmp(track->codec_id, "A_AC3/", 6)) {
|
||||
sh->codec = "ac3";
|
||||
sh_a->codec = "ac3";
|
||||
} else if (!strncmp(track->codec_id, "A_EAC3/", 7)) {
|
||||
sh->codec = "eac3";
|
||||
sh_a->codec = "eac3";
|
||||
}
|
||||
|
||||
if (!sh->codec)
|
||||
if (!sh_a->codec)
|
||||
goto error;
|
||||
|
||||
mp_chmap_set_unknown(&sh_a->channels, track->a_channels);
|
||||
|
||||
const char *codec = sh->codec;
|
||||
const char *codec = sh_a->codec;
|
||||
if (!strcmp(codec, "mp3") || !strcmp(codec, "truehd")) {
|
||||
track->parse = true;
|
||||
} else if (!strcmp(codec, "flac")) {
|
||||
@@ -1672,8 +1681,10 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
if (sh_a->samplerate == 8000 && strcmp(codec, "ac3") == 0)
|
||||
track->default_duration = 0;
|
||||
|
||||
sh->extradata = extradata;
|
||||
sh->extradata_size = extradata_len;
|
||||
sh_a->extradata = extradata;
|
||||
sh_a->extradata_size = extradata_len;
|
||||
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1681,6 +1692,7 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
MP_WARN(demuxer, "Unknown/unsupported audio "
|
||||
"codec ID '%s' for track %u or missing/faulty\n"
|
||||
"private codec data.\n", track->codec_id, track->tnum);
|
||||
demux_add_sh_stream(demuxer, sh); // add it anyway
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1712,12 +1724,10 @@ static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track)
|
||||
if (track->private_size > 0x10000000)
|
||||
return 1;
|
||||
|
||||
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_SUB);
|
||||
if (!sh)
|
||||
return 1;
|
||||
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_SUB);
|
||||
init_track(demuxer, track, sh);
|
||||
|
||||
sh->codec = subtitle_type;
|
||||
sh->codec->codec = subtitle_type;
|
||||
bstr in = (bstr){track->private_data, track->private_size};
|
||||
bstr buffer = demux_mkv_decode(demuxer->log, track, in, 2);
|
||||
if (buffer.start && buffer.start != track->private_data) {
|
||||
@@ -1726,8 +1736,10 @@ static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track)
|
||||
track->private_data = buffer.start;
|
||||
track->private_size = buffer.len;
|
||||
}
|
||||
sh->extradata = track->private_data;
|
||||
sh->extradata_size = track->private_size;
|
||||
sh->codec->extradata = track->private_data;
|
||||
sh->codec->extradata_size = track->private_size;
|
||||
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
|
||||
if (!subtitle_type)
|
||||
MP_ERR(demuxer, "Subtitle type '%s' is not supported.\n", track->codec_id);
|
||||
@@ -2015,7 +2027,7 @@ static bool handle_realaudio(demuxer_t *demuxer, mkv_track_t *track,
|
||||
if (!track->audio_buf || !track->audio_timestamp || !track->stream)
|
||||
return false;
|
||||
|
||||
const char *codec = track->stream->codec ? track->stream->codec : "";
|
||||
const char *codec = track->stream->codec->codec ? track->stream->codec->codec : "";
|
||||
if (!strcmp(codec, "ra_288")) {
|
||||
for (int x = 0; x < sph / 2; x++) {
|
||||
uint64_t dst_offset = x * 2 * w + spc * (uint64_t)cfs;
|
||||
@@ -2075,7 +2087,7 @@ static bool handle_realaudio(demuxer_t *demuxer, mkv_track_t *track,
|
||||
if (++(track->sub_packet_cnt) == sph) {
|
||||
track->sub_packet_cnt = 0;
|
||||
// apk_usize has same range as coded_framesize in worst case
|
||||
uint32_t apk_usize = track->stream->audio->block_align;
|
||||
uint32_t apk_usize = track->stream->codec->block_align;
|
||||
if (apk_usize > audiobuf_size)
|
||||
goto error;
|
||||
// Release all the audio packets
|
||||
@@ -2210,7 +2222,7 @@ static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
|
||||
if (stream->type == STREAM_AUDIO && handle_realaudio(demuxer, track, dp))
|
||||
return;
|
||||
|
||||
if (stream->codec && strcmp(stream->codec, "wavpack") == 0) {
|
||||
if (strcmp(stream->codec->codec, "wavpack") == 0) {
|
||||
int size = dp->len;
|
||||
uint8_t *parsed;
|
||||
if (libav_parse_wavpack(track, dp->buffer, &parsed, &size) >= 0) {
|
||||
@@ -2224,7 +2236,7 @@ static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
|
||||
}
|
||||
}
|
||||
|
||||
if (stream->codec && strcmp(stream->codec, "prores") == 0) {
|
||||
if (strcmp(stream->codec->codec, "prores") == 0) {
|
||||
size_t newlen = dp->len + 8;
|
||||
struct demux_packet *new = new_demux_packet(newlen);
|
||||
if (new) {
|
||||
@@ -2239,7 +2251,7 @@ static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
|
||||
}
|
||||
|
||||
if (track->parse && !track->av_parser) {
|
||||
int id = mp_codec_to_av_codec_id(track->stream->codec);
|
||||
int id = mp_codec_to_av_codec_id(track->stream->codec->codec);
|
||||
const AVCodec *codec = avcodec_find_decoder(id);
|
||||
track->av_parser = av_parser_init(id);
|
||||
if (codec)
|
||||
@@ -2435,7 +2447,7 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
|
||||
* packets resulting from parsing. */
|
||||
if (i == 0 || track->default_duration)
|
||||
dp->pts = mkv_d->last_pts + i * track->default_duration;
|
||||
if (stream->video && stream->video->avi_dts)
|
||||
if (stream->codec->avi_dts)
|
||||
MPSWAP(double, dp->pts, dp->dts);
|
||||
if (i == 0)
|
||||
dp->duration = block_duration / 1e9;
|
||||
@@ -2597,7 +2609,7 @@ static int read_next_block(demuxer_t *demuxer, struct block_info *block)
|
||||
}
|
||||
// For the sake of robustness, consider even unknown level 1
|
||||
// elements the same as unknown/broken IDs.
|
||||
if (!ebml_is_mkv_level1_id(id) ||
|
||||
if ((!ebml_is_mkv_level1_id(id) && id != EBML_ID_VOID) ||
|
||||
ebml_read_skip(demuxer->log, -1, s) != 0)
|
||||
{
|
||||
ebml_resync_cluster(demuxer->log, s);
|
||||
@@ -2659,8 +2671,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;
|
||||
@@ -2719,6 +2730,8 @@ static struct mkv_index *seek_with_cues(struct demuxer *demuxer, int seek_id,
|
||||
// Find the cluster with the highest filepos, that has a timestamp
|
||||
// still lower than min_tc.
|
||||
double secs = opts->demux_mkv->subtitle_preroll_secs;
|
||||
if (mkv_d->index_has_durations)
|
||||
secs = MPMAX(secs, opts->demux_mkv->subtitle_preroll_secs_index);
|
||||
uint64_t pre = MPMIN(INT64_MAX, secs * 1e9 / mkv_d->tc_scale);
|
||||
uint64_t min_tc = pre < index->timecode ? index->timecode - pre : 0;
|
||||
uint64_t prev_target = 0;
|
||||
@@ -2781,8 +2794,10 @@ static void demux_mkv_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
int cueflags = (flags & SEEK_BACKWARD) ? FLAG_BACKWARD : 0;
|
||||
|
||||
mkv_d->subtitle_preroll = NUM_SUB_PREROLL_PACKETS;
|
||||
if (((flags & SEEK_HR) || demuxer->opts->demux_mkv->subtitle_preroll) &&
|
||||
st_active[STREAM_SUB] && st_active[STREAM_VIDEO])
|
||||
int preroll_opt = demuxer->opts->demux_mkv->subtitle_preroll;
|
||||
if (((flags & SEEK_HR) || preroll_opt == 1 ||
|
||||
(preroll_opt == 2 && mkv_d->index_has_durations))
|
||||
&& st_active[STREAM_SUB] && st_active[STREAM_VIDEO])
|
||||
cueflags |= FLAG_SUBPREROLL;
|
||||
|
||||
// Adjust the target a little bit to catch cases where the target position
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
#include "osdep/io.h"
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "common/msg.h"
|
||||
#include "common/global.h"
|
||||
@@ -458,8 +458,9 @@ static void check_track_compatibility(struct timeline *tl)
|
||||
if (p->source == mainsrc)
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < p->source->num_streams; i++) {
|
||||
struct sh_stream *s = p->source->streams[i];
|
||||
int num_source_streams = demux_get_num_stream(p->source);
|
||||
for (int i = 0; i < num_source_streams; i++) {
|
||||
struct sh_stream *s = demux_get_stream(p->source, i);
|
||||
if (s->attached_picture)
|
||||
continue;
|
||||
|
||||
@@ -473,8 +474,9 @@ static void check_track_compatibility(struct timeline *tl)
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < mainsrc->num_streams; i++) {
|
||||
struct sh_stream *m = mainsrc->streams[i];
|
||||
int num_main_streams = demux_get_num_stream(mainsrc);
|
||||
for (int i = 0; i < num_main_streams; i++) {
|
||||
struct sh_stream *m = demux_get_stream(mainsrc, i);
|
||||
if (m->attached_picture)
|
||||
continue;
|
||||
|
||||
@@ -483,7 +485,7 @@ static void check_track_compatibility(struct timeline *tl)
|
||||
if (s) {
|
||||
// There are actually many more things that in theory have to
|
||||
// match (though mpv's implementation doesn't care).
|
||||
if (s->codec && m->codec && strcmp(s->codec, m->codec) != 0)
|
||||
if (strcmp(s->codec->codec, m->codec->codec) != 0)
|
||||
MP_WARN(tl, "Timeline segments have mismatching codec.\n");
|
||||
} else {
|
||||
MP_WARN(tl, "Source %s lacks %s stream with TID=%d, which "
|
||||
|
||||
+25
-23
@@ -117,6 +117,7 @@ const struct m_sub_options demux_rawvideo_conf = {
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct sh_stream *sh;
|
||||
int frame_size;
|
||||
int read_frames;
|
||||
double frame_rate;
|
||||
@@ -125,29 +126,30 @@ struct priv {
|
||||
static int demux_rawaudio_open(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
struct demux_rawaudio_opts *opts = demuxer->opts->demux_rawaudio;
|
||||
struct sh_stream *sh;
|
||||
sh_audio_t *sh_audio;
|
||||
|
||||
if (check != DEMUX_CHECK_REQUEST && check != DEMUX_CHECK_FORCE)
|
||||
return -1;
|
||||
|
||||
sh = new_sh_stream(demuxer, STREAM_AUDIO);
|
||||
sh_audio = sh->audio;
|
||||
sh_audio->channels = opts->channels;
|
||||
sh_audio->force_channels = true;
|
||||
sh_audio->samplerate = opts->samplerate;
|
||||
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_AUDIO);
|
||||
struct mp_codec_params *c = sh->codec;
|
||||
c->channels = opts->channels;
|
||||
c->force_channels = true;
|
||||
c->samplerate = opts->samplerate;
|
||||
|
||||
int f = opts->aformat;
|
||||
// See PCM(): sign float bits endian
|
||||
mp_set_pcm_codec(sh, f & 1, f & 2, f >> 3, f & 4);
|
||||
// See PCM(): sign float bits endian
|
||||
mp_set_pcm_codec(sh->codec, f & 1, f & 2, f >> 3, f & 4);
|
||||
int samplesize = ((f >> 3) + 7) / 8;
|
||||
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
|
||||
struct priv *p = talloc_ptrtype(demuxer, p);
|
||||
demuxer->priv = p;
|
||||
*p = (struct priv) {
|
||||
.frame_size = samplesize * sh_audio->channels.num,
|
||||
.frame_rate = sh_audio->samplerate,
|
||||
.read_frames = sh_audio->samplerate / 8,
|
||||
.sh = sh,
|
||||
.frame_size = samplesize * c->channels.num,
|
||||
.frame_rate = c->samplerate,
|
||||
.read_frames = c->samplerate / 8,
|
||||
};
|
||||
|
||||
return 0;
|
||||
@@ -156,8 +158,6 @@ static int demux_rawaudio_open(demuxer_t *demuxer, enum demux_check check)
|
||||
static int demux_rawvideo_open(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
struct demux_rawvideo_opts *opts = demuxer->opts->demux_rawvideo;
|
||||
struct sh_stream *sh;
|
||||
sh_video_t *sh_video;
|
||||
|
||||
if (check != DEMUX_CHECK_REQUEST && check != DEMUX_CHECK_FORCE)
|
||||
return -1;
|
||||
@@ -219,19 +219,21 @@ static int demux_rawvideo_open(demuxer_t *demuxer, enum demux_check check)
|
||||
imgsize = width * height * bpp / 8;
|
||||
}
|
||||
|
||||
sh = new_sh_stream(demuxer, STREAM_VIDEO);
|
||||
sh_video = sh->video;
|
||||
sh->codec = decoder;
|
||||
sh->codec_tag = imgfmt;
|
||||
sh_video->fps = opts->fps;
|
||||
sh_video->disp_w = width;
|
||||
sh_video->disp_h = height;
|
||||
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO);
|
||||
struct mp_codec_params *c = sh->codec;
|
||||
c->codec = decoder;
|
||||
c->codec_tag = imgfmt;
|
||||
c->fps = opts->fps;
|
||||
c->disp_w = width;
|
||||
c->disp_h = height;
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
|
||||
struct priv *p = talloc_ptrtype(demuxer, p);
|
||||
demuxer->priv = p;
|
||||
*p = (struct priv) {
|
||||
.sh = sh,
|
||||
.frame_size = imgsize,
|
||||
.frame_rate = sh_video->fps,
|
||||
.frame_rate = c->fps,
|
||||
.read_frames = 1,
|
||||
};
|
||||
|
||||
@@ -256,7 +258,7 @@ static int raw_fill_buffer(demuxer_t *demuxer)
|
||||
|
||||
int len = stream_read(demuxer->stream, dp->buffer, dp->len);
|
||||
demux_packet_shorten(dp, len);
|
||||
demux_add_packet(demuxer->streams[0], dp);
|
||||
demux_add_packet(p->sh, dp);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1,837 +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 LIBAVUTIL_VERSION_MICRO >= 100
|
||||
if (check != DEMUX_CHECK_FORCE)
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
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);
|
||||
int have_packet = !!dp;
|
||||
demux_add_packet(p->sh, dp);
|
||||
return have_packet;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
+23
-22
@@ -20,8 +20,6 @@
|
||||
static int demux_open_tv(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
tvi_handle_t *tvh;
|
||||
sh_video_t *sh_video;
|
||||
sh_audio_t *sh_audio = NULL;
|
||||
const tvi_functions_t *funcs;
|
||||
|
||||
if (check > DEMUX_CHECK_REQUEST || demuxer->stream->type != STREAMTYPE_TV)
|
||||
@@ -50,31 +48,30 @@ static int demux_open_tv(demuxer_t *demuxer, enum demux_check check)
|
||||
funcs = tvh->functions;
|
||||
demuxer->priv=tvh;
|
||||
|
||||
struct sh_stream *sh_v = new_sh_stream(demuxer, STREAM_VIDEO);
|
||||
sh_video = sh_v->video;
|
||||
struct sh_stream *sh_v = demux_alloc_sh_stream(STREAM_VIDEO);
|
||||
|
||||
/* get IMAGE FORMAT */
|
||||
int fourcc;
|
||||
funcs->control(tvh->priv, TVI_CONTROL_VID_GET_FORMAT, &fourcc);
|
||||
if (fourcc == MP_FOURCC_MJPEG) {
|
||||
sh_v->codec = "mjpeg";
|
||||
sh_v->codec->codec = "mjpeg";
|
||||
} else {
|
||||
sh_v->codec = "rawvideo";
|
||||
sh_v->codec_tag = fourcc;
|
||||
sh_v->codec->codec = "rawvideo";
|
||||
sh_v->codec->codec_tag = fourcc;
|
||||
}
|
||||
|
||||
/* set FPS and FRAMETIME */
|
||||
|
||||
if(!sh_video->fps)
|
||||
if(!sh_v->codec->fps)
|
||||
{
|
||||
float tmp;
|
||||
if (funcs->control(tvh->priv, TVI_CONTROL_VID_GET_FPS, &tmp) != TVI_CONTROL_TRUE)
|
||||
sh_video->fps = 25.0f; /* on PAL */
|
||||
else sh_video->fps = tmp;
|
||||
sh_v->codec->fps = 25.0f; /* on PAL */
|
||||
else sh_v->codec->fps = tmp;
|
||||
}
|
||||
|
||||
if (tvh->tv_param->fps != -1.0f)
|
||||
sh_video->fps = tvh->tv_param->fps;
|
||||
sh_v->codec->fps = tvh->tv_param->fps;
|
||||
|
||||
/* If playback only mode, go to immediate mode, fail silently */
|
||||
if(tvh->tv_param->immediate == 1)
|
||||
@@ -84,10 +81,12 @@ static int demux_open_tv(demuxer_t *demuxer, enum demux_check check)
|
||||
}
|
||||
|
||||
/* set width */
|
||||
funcs->control(tvh->priv, TVI_CONTROL_VID_GET_WIDTH, &sh_video->disp_w);
|
||||
funcs->control(tvh->priv, TVI_CONTROL_VID_GET_WIDTH, &sh_v->codec->disp_w);
|
||||
|
||||
/* set height */
|
||||
funcs->control(tvh->priv, TVI_CONTROL_VID_GET_HEIGHT, &sh_video->disp_h);
|
||||
funcs->control(tvh->priv, TVI_CONTROL_VID_GET_HEIGHT, &sh_v->codec->disp_h);
|
||||
|
||||
demux_add_sh_stream(demuxer, sh_v);
|
||||
|
||||
demuxer->seekable = 0;
|
||||
|
||||
@@ -115,21 +114,22 @@ static int demux_open_tv(demuxer_t *demuxer, enum demux_check check)
|
||||
goto no_audio;
|
||||
}
|
||||
|
||||
struct sh_stream *sh_a = new_sh_stream(demuxer, STREAM_AUDIO);
|
||||
sh_audio = sh_a->audio;
|
||||
struct sh_stream *sh_a = demux_alloc_sh_stream(STREAM_AUDIO);
|
||||
|
||||
funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_SAMPLERATE,
|
||||
&sh_audio->samplerate);
|
||||
int nchannels = sh_audio->channels.num;
|
||||
&sh_a->codec->samplerate);
|
||||
int nchannels = sh_a->codec->channels.num;
|
||||
funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_CHANNELS,
|
||||
&nchannels);
|
||||
mp_chmap_from_channels(&sh_audio->channels, nchannels);
|
||||
mp_chmap_from_channels(&sh_a->codec->channels, nchannels);
|
||||
|
||||
// s16ne
|
||||
mp_set_pcm_codec(sh_a, true, false, 16, BYTE_ORDER == BIG_ENDIAN);
|
||||
mp_set_pcm_codec(sh_a->codec, true, false, 16, BYTE_ORDER == BIG_ENDIAN);
|
||||
|
||||
demux_add_sh_stream(demuxer, sh_a);
|
||||
|
||||
MP_VERBOSE(tvh, " TV audio: %d channels, %d bits, %d Hz\n",
|
||||
nchannels, 16, sh_audio->samplerate);
|
||||
nchannels, 16, sh_a->codec->samplerate);
|
||||
}
|
||||
no_audio:
|
||||
|
||||
@@ -168,8 +168,9 @@ static int demux_tv_fill_buffer(demuxer_t *demux)
|
||||
unsigned int len=0;
|
||||
struct sh_stream *want_audio = NULL, *want_video = NULL;
|
||||
|
||||
for (int n = 0; n < demux->num_streams; n++) {
|
||||
struct sh_stream *sh = demux->streams[n];
|
||||
int num_streams = demux_get_num_stream(demux);
|
||||
for (int n = 0; n < num_streams; n++) {
|
||||
struct sh_stream *sh = demux_get_stream(demux, n);
|
||||
if (!demux_has_packet(sh) && demux_stream_is_selected(sh)) {
|
||||
if (sh->type == STREAM_AUDIO)
|
||||
want_audio = sh;
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
|
||||
#include <libavutil/intfloat.h>
|
||||
#include <libavutil/common.h>
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "ebml.h"
|
||||
#include "stream/stream.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
+26
-29
@@ -37,23 +37,8 @@ struct sh_stream {
|
||||
int demuxer_id;
|
||||
// FFmpeg stream index (AVFormatContext.streams[index]), or equivalent.
|
||||
int ff_index;
|
||||
// One of these is non-NULL, the others are NULL, depending on the stream
|
||||
// type.
|
||||
struct sh_audio *audio;
|
||||
struct sh_video *video;
|
||||
struct sh_sub *sub;
|
||||
|
||||
// E.g. "h264" (usually corresponds to AVCodecDescriptor.name)
|
||||
const char *codec;
|
||||
|
||||
// Usually a FourCC, exact meaning depends on codec.
|
||||
unsigned int codec_tag;
|
||||
|
||||
unsigned char *extradata; // codec specific per-stream header
|
||||
int extradata_size;
|
||||
|
||||
// Codec specific header data (set by demux_lavf.c only)
|
||||
struct AVCodecContext *lav_headers;
|
||||
struct mp_codec_params *codec;
|
||||
|
||||
char *title;
|
||||
char *lang; // language code
|
||||
@@ -70,31 +55,43 @@ struct sh_stream {
|
||||
struct demux_stream *ds;
|
||||
};
|
||||
|
||||
typedef struct sh_audio {
|
||||
struct mp_codec_params {
|
||||
enum stream_type type;
|
||||
|
||||
// E.g. "h264" (usually corresponds to AVCodecDescriptor.name)
|
||||
const char *codec;
|
||||
|
||||
// Usually a FourCC, exact meaning depends on codec.
|
||||
unsigned int codec_tag;
|
||||
|
||||
unsigned char *extradata; // codec specific per-stream header
|
||||
int extradata_size;
|
||||
|
||||
// Codec specific header data (set by demux_lavf.c only)
|
||||
struct AVCodecContext *lav_headers;
|
||||
|
||||
// STREAM_AUDIO
|
||||
int samplerate;
|
||||
struct mp_chmap channels;
|
||||
bool force_channels;
|
||||
int bitrate; // compressed bits/sec
|
||||
int block_align;
|
||||
int bits_per_coded_sample;
|
||||
struct replaygain_data *replaygain_data;
|
||||
} sh_audio_t;
|
||||
|
||||
typedef struct sh_video {
|
||||
// STREAM_VIDEO
|
||||
bool avi_dts; // use DTS timing; first frame and DTS is 0
|
||||
float fps; // frames per second (set only if constant fps)
|
||||
float aspect; // aspect ratio stored in the file (for prescaling)
|
||||
int bits_per_coded_sample;
|
||||
int par_w, par_h; // pixel aspect ratio (0 if unknown/square)
|
||||
int disp_w, disp_h; // display size
|
||||
int rotate; // intended display rotation, in degrees, [0, 359]
|
||||
int stereo_mode; // mp_stereo3d_mode (0 if none/unknown)
|
||||
} sh_video_t;
|
||||
|
||||
typedef struct sh_sub {
|
||||
double frame_based; // timestamps are frame-based (and this is the
|
||||
// fallback framerate used for timestamps)
|
||||
bool is_utf8; // if false, subtitle packet charset is unknown
|
||||
struct dec_sub *dec_sub; // decoder context
|
||||
} sh_sub_t;
|
||||
// STREAM_VIDEO + STREAM_AUDIO
|
||||
int bits_per_coded_sample;
|
||||
|
||||
// STREAM_SUB
|
||||
double frame_based; // timestamps are frame-based (and this is the
|
||||
// fallback framerate used for timestamps)
|
||||
};
|
||||
|
||||
#endif /* MPLAYER_STHEADER_H */
|
||||
+7
-4
@@ -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
|
||||
@@ -163,6 +166,10 @@
|
||||
#h cycle tv-channel -1 # previous channel
|
||||
#k cycle tv-channel +1 # next channel
|
||||
|
||||
# For dvb://
|
||||
#H cycle dvb-channel-name -1 # previous channel
|
||||
#K cycle dvb-channel-name +1 # next channel
|
||||
|
||||
#
|
||||
# Legacy bindings (may or may not be removed in the future)
|
||||
#
|
||||
@@ -178,10 +185,6 @@
|
||||
# ? add sub-scale -0.1 # decrease subtitle font size
|
||||
# ? sub-step -1 # immediately display next subtitle
|
||||
# ? sub-step +1 # previous
|
||||
# ? cycle-values window-scale 0.5 2 1 # switch between 1/2, 2x, unresized window size
|
||||
# ? cycle colormatrix
|
||||
# ? add audio-delay 0.100 # this changes audio/video sync
|
||||
# ? add audio-delay -0.100
|
||||
# ? cycle angle # switch DVD/Bluray angle
|
||||
# ? add balance -0.1 # adjust audio balance in favor of left
|
||||
# ? add balance 0.1 # right
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
##################
|
||||
|
||||
# Specify default video driver (see --vo=help for a list).
|
||||
#vo=vdpau
|
||||
#vo=opengl-hq
|
||||
|
||||
# Start in fullscreen mode by default.
|
||||
#fs=yes
|
||||
@@ -54,9 +54,8 @@
|
||||
# Disable softvol usage, and always use the system mixer if available.
|
||||
#softvol=no
|
||||
|
||||
# Scale audio tempo by playback speed without altering pitch. (By default does
|
||||
# nothing if playback speed is not changed. May introduce artifacts.)
|
||||
#af=scaletempo
|
||||
# Do not filter audio to keep pitch when changing playback speed.
|
||||
audio-pitch-correction=no
|
||||
|
||||
# Output 5.1 audio natively, and upmix/downmix audio with a different format.
|
||||
#audio-channels=5.1
|
||||
@@ -75,15 +74,11 @@
|
||||
|
||||
# cache settings
|
||||
#
|
||||
# Use 8MB input cache by default. The cache is enabled for network streams only.
|
||||
#cache-default=8192
|
||||
# Use 150MB input cache by default. The cache is enabled for network streams only.
|
||||
#cache-default=153600
|
||||
#
|
||||
# Use 8MB input cache for everything, even local files.
|
||||
#cache=8192
|
||||
#
|
||||
# If a seek is issued, and the target is 1024KB past the cached range, then
|
||||
# keep reading until the seek target is hit, instead of doing a real seek.
|
||||
#cache-seek-min=1024
|
||||
# Use 150MB input cache for everything, even local files.
|
||||
#cache=153600
|
||||
#
|
||||
# Disable the behavior that the player will pause if the cache goes below a
|
||||
# certain fill size.
|
||||
@@ -91,6 +86,9 @@
|
||||
#
|
||||
# Read ahead about 5 seconds of audio and video packets.
|
||||
#demuxer-readahead-secs=5.0
|
||||
#
|
||||
# Raise readahead from demuxer-readahead-secs to this value if a cache is active.
|
||||
#cache-secs=50.0
|
||||
|
||||
# Display English subtitles if available.
|
||||
#slang=en
|
||||
+1
-1
@@ -27,5 +27,5 @@ TryExec=mpv
|
||||
Exec=mpv --profile=pseudo-gui -- %U
|
||||
Terminal=false
|
||||
Categories=AudioVideo;Audio;Video;Player;TV;
|
||||
MimeType=application/ogg;application/x-ogg;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/m4a;audio/x-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/x-mpeg;audio/mpegurl;audio/x-mpegurl;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/ogg;audio/scpls;audio/x-scpls;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-pls;audio/x-wav;video/mpeg;video/x-mpeg;video/x-mpeg2;video/mp4;video/msvideo;video/x-msvideo;video/ogg;video/quicktime;video/vnd.rn-realvideo;video/x-ms-afs;video/x-ms-asf;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/x-fli;video/x-flv;video/x-theora;video/x-matroska;video/webm;audio/x-flac;audio/x-vorbis+ogg;video/x-ogm+ogg;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;video/mp2t;audio/flac;
|
||||
MimeType=application/ogg;application/x-ogg;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/m4a;audio/x-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/x-mpeg;audio/mpegurl;audio/x-mpegurl;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/ogg;audio/scpls;audio/x-scpls;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-pls;audio/x-wav;video/mpeg;video/x-mpeg;video/x-mpeg2;video/mp4;video/msvideo;video/x-msvideo;video/ogg;video/quicktime;video/vnd.rn-realvideo;video/x-ms-afs;video/x-ms-asf;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/x-fli;video/x-flv;video/x-theora;video/x-matroska;video/webm;audio/x-flac;audio/x-vorbis+ogg;video/x-ogm+ogg;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;video/mp2t;audio/flac;audio/mp4;
|
||||
X-KDE-Protocols=ftp,http,https,mms,rtmp,rtsp,sftp,smb
|
||||
@@ -417,6 +417,7 @@ mp_cmd_t *mp_cmd_clone(mp_cmd_t *cmd)
|
||||
m_option_copy(ret->args[i].type, &ret->args[i].v, &cmd->args[i].v);
|
||||
}
|
||||
ret->original = bstrdup(ret, cmd->original);
|
||||
ret->key_name = talloc_strdup(ret, ret->key_name);
|
||||
|
||||
if (cmd->id == MP_CMD_COMMAND_LIST) {
|
||||
struct mp_cmd *prev = NULL;
|
||||
|
||||
+7
-12
@@ -48,7 +48,7 @@
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
#include "options/path.h"
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "options/options.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "stream/stream.h"
|
||||
@@ -446,7 +446,9 @@ static mp_cmd_t *get_cmd_from_keys(struct input_ctx *ictx, char *force_section,
|
||||
return handle_test(ictx, code);
|
||||
|
||||
struct cmd_bind *cmd = find_any_bind_for_key(ictx, force_section, code);
|
||||
if (cmd == NULL) {
|
||||
if (!cmd)
|
||||
cmd = find_any_bind_for_key(ictx, force_section, MP_KEY_UNMAPPED);
|
||||
if (!cmd) {
|
||||
if (code == MP_KEY_CLOSE_WIN)
|
||||
return mp_input_parse_cmd_strv(ictx->log, (const char*[]){"quit", 0});
|
||||
int msgl = MSGL_WARN;
|
||||
@@ -460,12 +462,9 @@ static mp_cmd_t *get_cmd_from_keys(struct input_ctx *ictx, char *force_section,
|
||||
mp_cmd_t *ret = mp_input_parse_cmd(ictx, bstr0(cmd->cmd), cmd->location);
|
||||
if (ret) {
|
||||
ret->input_section = cmd->owner->section;
|
||||
if (mp_msg_test(ictx->log, MSGL_DEBUG)) {
|
||||
char *keyname = mp_input_get_key_combo_name(&code, 1);
|
||||
MP_DBG(ictx, "key '%s' -> '%s' in '%s'\n",
|
||||
keyname, cmd->cmd, ret->input_section);
|
||||
talloc_free(keyname);
|
||||
}
|
||||
ret->key_name = talloc_steal(ret, mp_input_get_key_combo_name(&code, 1));
|
||||
MP_DBG(ictx, "key '%s' -> '%s' in '%s'\n",
|
||||
ret->key_name, cmd->cmd, ret->input_section);
|
||||
ret->is_mouse_button = code & MP_KEY_EMIT_ON_UP;
|
||||
} else {
|
||||
char *key_buf = mp_input_get_key_combo_name(&code, 1);
|
||||
@@ -1265,11 +1264,7 @@ void mp_input_load(struct input_ctx *ictx)
|
||||
|
||||
#if defined(__MINGW32__)
|
||||
if (ictx->global->opts->input_file && *ictx->global->opts->input_file)
|
||||
#if HAVE_WAIO
|
||||
mp_input_pipe_add(ictx, ictx->global->opts->input_file);
|
||||
#else
|
||||
MP_ERR(ictx, "Pipes not available.\n");
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ typedef struct mp_cmd {
|
||||
double scale; // for scaling numeric arguments
|
||||
const struct mp_cmd_def *def;
|
||||
char *sender; // name of the client API user which sent this
|
||||
char *key_name; // string representation of the key binding
|
||||
} mp_cmd_t;
|
||||
|
||||
struct mp_input_src {
|
||||
|
||||
@@ -171,6 +171,8 @@ static const struct key_name key_names[] = {
|
||||
{ MP_KEY_MOUSE_LEAVE, "MOUSE_LEAVE" },
|
||||
{ MP_KEY_MOUSE_ENTER, "MOUSE_ENTER" },
|
||||
|
||||
{ MP_KEY_UNMAPPED, "UNMAPPED" },
|
||||
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
|
||||
@@ -200,6 +200,9 @@
|
||||
#define MP_KEY_IS_MOUSE(code) \
|
||||
(MP_KEY_IS_MOUSE_CLICK(code) || MP_KEY_IS_MOUSE_MOVE(code))
|
||||
|
||||
// No input source should generate this.
|
||||
#define MP_KEY_UNMAPPED (MP_KEY_INTERN+4)
|
||||
|
||||
// Emit a command even on key-up (normally key-up is ignored). This means by
|
||||
// default they binding will be triggered on key-up instead of key-down.
|
||||
// This is a fixed part of the keycode, not a modifier than can change.
|
||||
|
||||
+65
-56
@@ -1,98 +1,107 @@
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <waio/waio.h>
|
||||
|
||||
#include "common/msg.h"
|
||||
#include "osdep/atomics.h"
|
||||
#include "osdep/io.h"
|
||||
#include "input.h"
|
||||
|
||||
struct priv {
|
||||
atomic_bool cancel_requested;
|
||||
int fd;
|
||||
bool close_fd;
|
||||
HANDLE file;
|
||||
HANDLE thread;
|
||||
};
|
||||
|
||||
static void request_cancel(struct mp_input_src *src)
|
||||
{
|
||||
HANDLE terminate = src->priv;
|
||||
struct priv *p = src->priv;
|
||||
|
||||
MP_VERBOSE(src, "Exiting...\n");
|
||||
SetEvent(terminate);
|
||||
atomic_store(&p->cancel_requested, true);
|
||||
|
||||
// The thread might not be peforming I/O at the exact moment when
|
||||
// CancelIoEx is called, so call it in a loop until it succeeds or the
|
||||
// thread exits
|
||||
do {
|
||||
if (CancelIoEx(p->file, NULL))
|
||||
break;
|
||||
} while (WaitForSingleObject(p->thread, 1) != WAIT_OBJECT_0);
|
||||
}
|
||||
|
||||
static void uninit(struct mp_input_src *src)
|
||||
{
|
||||
HANDLE terminate = src->priv;
|
||||
struct priv *p = src->priv;
|
||||
|
||||
CloseHandle(p->thread);
|
||||
if (p->close_fd)
|
||||
close(p->fd);
|
||||
|
||||
CloseHandle(terminate);
|
||||
MP_VERBOSE(src, "Exited.\n");
|
||||
}
|
||||
|
||||
static void read_pipe_thread(struct mp_input_src *src, void *param)
|
||||
{
|
||||
char *filename = talloc_strdup(src, param);
|
||||
struct priv *p = talloc_zero(src, struct priv);
|
||||
|
||||
struct waio_cx_interface *waio = NULL;
|
||||
int mode = O_RDONLY;
|
||||
int fd = -1;
|
||||
bool close_fd = true;
|
||||
p->fd = -1;
|
||||
p->close_fd = true;
|
||||
if (strcmp(filename, "/dev/stdin") == 0) { // for symmetry with unix
|
||||
fd = STDIN_FILENO;
|
||||
close_fd = false;
|
||||
p->fd = STDIN_FILENO;
|
||||
p->close_fd = false;
|
||||
}
|
||||
if (fd < 0)
|
||||
fd = open(filename, mode);
|
||||
if (fd < 0) {
|
||||
if (p->fd < 0)
|
||||
p->fd = open(filename, O_RDONLY);
|
||||
if (p->fd < 0) {
|
||||
MP_ERR(src, "Can't open %s.\n", filename);
|
||||
goto done;
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're reading from stdin, unset it. All I/O on synchronous handles is
|
||||
// serialized, so stupid DLLs that call GetFileType on stdin can hang the
|
||||
// process if they do it while we're reading from it. At least, the
|
||||
// VirtualBox OpenGL ICD is affected by this, but only on Windows XP.
|
||||
// GetFileType works differently in later versions of Windows. See:
|
||||
// https://support.microsoft.com/kb/2009703
|
||||
// http://blogs.msdn.com/b/oldnewthing/archive/2011/12/02/10243553.aspx
|
||||
if ((void*)_get_osfhandle(fd) == GetStdHandle(STD_INPUT_HANDLE))
|
||||
SetStdHandle(STD_INPUT_HANDLE, NULL);
|
||||
|
||||
waio = waio_alloc((void *)_get_osfhandle(fd), 0, NULL, NULL);
|
||||
if (!waio) {
|
||||
MP_ERR(src, "Can't initialize win32 file reader.\n");
|
||||
goto done;
|
||||
p->file = (HANDLE)_get_osfhandle(p->fd);
|
||||
if (!p->file || p->file == INVALID_HANDLE_VALUE) {
|
||||
MP_ERR(src, "Can't open %s.\n", filename);
|
||||
return;
|
||||
}
|
||||
|
||||
HANDLE terminate = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (!terminate)
|
||||
goto done;
|
||||
atomic_store(&p->cancel_requested, false);
|
||||
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
|
||||
GetCurrentProcess(), &p->thread, SYNCHRONIZE, FALSE, 0))
|
||||
return;
|
||||
|
||||
src->priv = terminate;
|
||||
src->priv = p;
|
||||
src->cancel = request_cancel;
|
||||
src->uninit = uninit;
|
||||
mp_input_src_init_done(src);
|
||||
|
||||
char buffer[128];
|
||||
struct waio_aiocb cb = {
|
||||
.aio_buf = buffer,
|
||||
.aio_nbytes = sizeof(buffer),
|
||||
.hsignal = terminate,
|
||||
};
|
||||
while (1) {
|
||||
if (waio_read(waio, &cb)) {
|
||||
MP_ERR(src, "Read operation failed.\n");
|
||||
char buffer[4096];
|
||||
while (!atomic_load(&p->cancel_requested)) {
|
||||
DWORD r;
|
||||
if (!ReadFile(p->file, buffer, 4096, &r, NULL)) {
|
||||
if (GetLastError() != ERROR_OPERATION_ABORTED)
|
||||
MP_ERR(src, "Read operation failed.\n");
|
||||
break;
|
||||
}
|
||||
if (waio_suspend(waio, (const struct waio_aiocb *[]){&cb}, 1, NULL))
|
||||
break;
|
||||
ssize_t r = waio_return(waio, &cb);
|
||||
if (r <= 0)
|
||||
break; // EOF or error
|
||||
mp_input_src_feed_cmd_text(src, buffer, r);
|
||||
}
|
||||
|
||||
done:
|
||||
waio_free(waio);
|
||||
if (close_fd)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void mp_input_pipe_add(struct input_ctx *ictx, const char *filename)
|
||||
|
||||
+1
-1
@@ -602,7 +602,7 @@ typedef enum mpv_format {
|
||||
* Example for writing:
|
||||
*
|
||||
* int flag = 1;
|
||||
* mpv_set_property(ctx, "property", MPV_FORMAT_STRING, &flag);
|
||||
* mpv_set_property(ctx, "property", MPV_FORMAT_FLAG, &flag);
|
||||
*/
|
||||
MPV_FORMAT_FLAG = 3,
|
||||
/**
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "common/common.h"
|
||||
#include "misc/ctype.h"
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "osdep/compiler.h"
|
||||
|
||||
/* NOTE: 'len' is size_t, but most string-handling functions below assume
|
||||
|
||||
+9
-18
@@ -52,6 +52,13 @@ bool mp_charset_is_utf8(const char *user_cp)
|
||||
strcasecmp(user_cp, "utf-8") == 0);
|
||||
}
|
||||
|
||||
bool mp_charset_is_utf16(const char *user_cp)
|
||||
{
|
||||
bstr s = bstr0(user_cp);
|
||||
return bstr_case_startswith(s, bstr0("utf16")) ||
|
||||
bstr_case_startswith(s, bstr0("utf-16"));
|
||||
}
|
||||
|
||||
// Split the string on ':' into components.
|
||||
// out_arr is at least max entries long.
|
||||
// Return number of out_arr entries filled.
|
||||
@@ -179,6 +186,8 @@ static const char *mp_uchardet(void *talloc_ctx, struct mp_log *log, bstr buf)
|
||||
iconv_close(icdsc);
|
||||
}
|
||||
}
|
||||
if (!res && bstr_validate_utf8(buf) >= 0)
|
||||
res = "utf-8";
|
||||
uchardet_delete(det);
|
||||
return res;
|
||||
}
|
||||
@@ -259,24 +268,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").
|
||||
|
||||
+1
-2
@@ -13,11 +13,10 @@ enum {
|
||||
};
|
||||
|
||||
bool mp_charset_is_utf8(const char *user_cp);
|
||||
bool mp_charset_is_utf16(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
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <libavutil/common.h>
|
||||
#include <assert.h>
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "osdep/atomics.h"
|
||||
#include "ring.h"
|
||||
|
||||
|
||||
File renamed without changes.
+1
-1
@@ -30,7 +30,7 @@
|
||||
|
||||
#include "libmpv/client.h"
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "m_config.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@
|
||||
#include "libmpv/client.h"
|
||||
#include "player/client.h"
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/msg_control.h"
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
#include "libmpv/client.h"
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "m_option.h"
|
||||
#include "m_property.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
+18
-7
@@ -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;
|
||||
@@ -198,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),
|
||||
|
||||
@@ -258,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
|
||||
@@ -273,7 +271,7 @@ const m_option_t mp_opts[] = {
|
||||
|
||||
// force video/audio rate:
|
||||
OPT_DOUBLE("fps", force_fps, CONF_MIN, .min = 0),
|
||||
OPT_INTRANGE("audio-samplerate", force_srate, 0, 1000, 8*48000),
|
||||
OPT_INTRANGE("audio-samplerate", force_srate, 0, 1000, 16*48000),
|
||||
OPT_CHMAP("audio-channels", audio_output_channels, CONF_MIN, .min = 0),
|
||||
OPT_AUDIOFORMAT("audio-format", audio_output_format, 0),
|
||||
OPT_DOUBLE("speed", playback_speed, M_OPT_RANGE | M_OPT_FIXED,
|
||||
@@ -306,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),
|
||||
|
||||
@@ -331,6 +332,7 @@ const m_option_t mp_opts[] = {
|
||||
|
||||
OPT_STRING_APPEND_LIST("sub-file", sub_name, M_OPT_FILE),
|
||||
OPT_PATHLIST("sub-paths", sub_paths, 0),
|
||||
OPT_PATHLIST("audio-file-paths", audiofile_paths, 0),
|
||||
OPT_STRING("sub-codepage", sub_cp, 0),
|
||||
OPT_FLOAT("sub-delay", sub_delay, 0),
|
||||
OPT_FLOAT("sub-fps", sub_fps, 0),
|
||||
@@ -436,7 +438,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_FLOATRANGE("video-align-y", vo.align_y, 0, -1.0, 1.0),
|
||||
OPT_FLAG("video-unscaled", vo.unscaled, 0),
|
||||
OPT_FLAG("force-rgba-osd-rendering", force_rgba_osd, 0),
|
||||
OPT_CHOICE_OR_INT("video-rotate", video_rotate, 0, 0, 360,
|
||||
OPT_CHOICE_OR_INT("video-rotate", video_rotate, 0, 0, 359,
|
||||
({"no", -1})),
|
||||
OPT_CHOICE_C("video-stereo-mode", video_stereo_mode, 0, mp_stereo3d_names),
|
||||
|
||||
@@ -449,7 +451,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),
|
||||
|
||||
@@ -694,6 +701,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",
|
||||
@@ -763,6 +772,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,
|
||||
@@ -801,6 +811,7 @@ const struct MPOpts mp_default_opts = {
|
||||
.screenshot_template = "mpv-shot%n",
|
||||
|
||||
.hwdec_codecs = "h264,vc1,wmv3,hevc,mpeg2video",
|
||||
.videotoolbox_format = IMGFMT_NV12,
|
||||
|
||||
.index_mode = 1,
|
||||
|
||||
@@ -814,7 +825,7 @@ const struct MPOpts mp_default_opts = {
|
||||
|
||||
.display_tags = (char **)(const char*[]){
|
||||
"Artist", "Album", "Album_Artist", "Comment", "Composer", "Genre",
|
||||
"Performer", "Title", "Track", "icy-title",
|
||||
"Performer", "Title", "Track", "icy-title", "service_name",
|
||||
NULL
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -173,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,6 +237,7 @@ typedef struct MPOpts {
|
||||
int field_dominance;
|
||||
char **sub_name;
|
||||
char **sub_paths;
|
||||
char **audiofile_paths;
|
||||
int sub_auto;
|
||||
int audiofile_auto;
|
||||
int osd_bar_visible;
|
||||
@@ -267,6 +272,7 @@ typedef struct MPOpts {
|
||||
|
||||
int hwdec_api;
|
||||
char *hwdec_codecs;
|
||||
int videotoolbox_format;
|
||||
|
||||
int w32_priority;
|
||||
|
||||
|
||||
Loaded 100 of 264 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user