summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlessandro Ghedini <alessandro@ghedini.me>2015-09-03 21:22:56 +0200
committerAlessandro Ghedini <alessandro@ghedini.me>2015-09-03 21:22:56 +0200
commit97616326aa35c74f085c8b7b5bc4a497e049febc (patch)
tree789f334c888d3f0cdb42d202efe76622b0d2afcc
parent69a0ce1df673d26271df9c1f58f14b6314538210 (diff)
Imported Upstream version 0.10.0
-rw-r--r--Copyright12
-rw-r--r--DOCS/client-api-changes.rst22
-rw-r--r--DOCS/client_api_examples/README.md5
-rw-r--r--DOCS/client_api_examples/cocoa-openglcb/cocoa-openglcb.m304
-rw-r--r--DOCS/client_api_examples/cocoa/cocoabasic.m10
-rw-r--r--DOCS/client_api_examples/sdl/main.c157
-rw-r--r--DOCS/compile-windows.md4
-rw-r--r--DOCS/contribute.md3
-rw-r--r--DOCS/interface-changes.rst88
-rw-r--r--DOCS/man/af.rst11
-rw-r--r--DOCS/man/ao.rst21
-rw-r--r--DOCS/man/encode.rst2
-rw-r--r--DOCS/man/input.rst400
-rw-r--r--DOCS/man/ipc.rst18
-rw-r--r--DOCS/man/lua.rst35
-rw-r--r--DOCS/man/mpv.rst67
-rw-r--r--DOCS/man/options.rst367
-rw-r--r--DOCS/man/osc.rst10
-rw-r--r--DOCS/man/vf.rst31
-rw-r--r--DOCS/man/vo.rst162
-rw-r--r--DOCS/mplayer-changes.rst8
-rw-r--r--DOCS/tech-overview.txt3
-rw-r--r--DOCS/waf-buildsystem.rst4
-rw-r--r--README.md9
-rw-r--r--RELEASE_NOTES289
-rwxr-xr-xTOOLS/idet.sh2
-rw-r--r--TOOLS/lib/Parse/Matroska/Definitions.pm2
-rw-r--r--TOOLS/lua/autodeint.lua71
-rw-r--r--TOOLS/lua/autoload.lua11
-rw-r--r--TOOLS/lua/status-line.lua4
-rw-r--r--TOOLS/lua/youtube-starttime.lua34
-rw-r--r--TOOLS/lua/zones.lua74
-rwxr-xr-xTOOLS/old-configure (renamed from old-configure)32
-rw-r--r--TOOLS/old-makefile (renamed from old-makefile)21
-rwxr-xr-xTOOLS/osxbundle.py2
-rw-r--r--TOOLS/osxbundle/mpv.app/Contents/Resources/mpv.conf1
-rwxr-xr-xTOOLS/stats-conv.py18
-rwxr-xr-xTOOLS/zsh.pl15
-rw-r--r--VERSION2
-rw-r--r--audio/audio.c33
-rw-r--r--audio/audio.h8
-rw-r--r--audio/chmap.c48
-rw-r--r--audio/chmap.h5
-rw-r--r--audio/chmap_sel.c121
-rw-r--r--audio/decode/ad_lavc.c8
-rw-r--r--audio/decode/ad_spdif.c105
-rw-r--r--audio/decode/dec_audio.c28
-rw-r--r--audio/decode/dec_audio.h3
-rw-r--r--audio/filter/af.c121
-rw-r--r--audio/filter/af.h4
-rw-r--r--audio/filter/af_bs2b.c4
-rw-r--r--audio/filter/af_convert24.c122
-rw-r--r--audio/filter/af_convertsignendian.c107
-rw-r--r--audio/filter/af_drc.c8
-rw-r--r--audio/filter/af_dummy.c61
-rw-r--r--audio/filter/af_export.c2
-rw-r--r--audio/filter/af_hrtf.c2
-rw-r--r--audio/filter/af_ladspa.c2
-rw-r--r--audio/filter/af_lavcac3enc.c23
-rw-r--r--audio/filter/af_lavrresample.c261
-rw-r--r--audio/filter/af_volume.c26
-rw-r--r--audio/filter/tools.c18
-rw-r--r--audio/format.c181
-rw-r--r--audio/format.h112
-rw-r--r--audio/mixer.c31
-rw-r--r--audio/mixer.h2
-rw-r--r--audio/out/ao.c17
-rw-r--r--audio/out/ao_alsa.c102
-rw-r--r--audio/out/ao_coreaudio.c328
-rw-r--r--audio/out/ao_coreaudio_chmap.c267
-rw-r--r--audio/out/ao_coreaudio_chmap.h25
-rw-r--r--audio/out/ao_coreaudio_exclusive.c536
-rw-r--r--audio/out/ao_coreaudio_utils.c262
-rw-r--r--audio/out/ao_coreaudio_utils.h11
-rw-r--r--audio/out/ao_dsound.c16
-rw-r--r--audio/out/ao_lavc.c7
-rw-r--r--audio/out/ao_null.c18
-rw-r--r--audio/out/ao_oss.c30
-rw-r--r--audio/out/ao_pcm.c6
-rw-r--r--audio/out/ao_pulse.c2
-rw-r--r--audio/out/ao_rsound.c17
-rw-r--r--audio/out/ao_sdl.c2
-rw-r--r--audio/out/ao_sndio.c23
-rw-r--r--audio/out/ao_wasapi.c1
-rwxr-xr-xaudio/out/ao_wasapi_utils.c16
-rw-r--r--audio/out/pull.c29
-rw-r--r--audio/out/push.c2
-rwxr-xr-xbootstrap.py8
-rw-r--r--common/av_common.c3
-rw-r--r--common/av_log.c35
-rw-r--r--common/codecs.c22
-rw-r--r--common/codecs.h7
-rw-r--r--common/msg.c23
-rw-r--r--common/msg_control.h3
-rw-r--r--common/playlist.c6
-rw-r--r--common/playlist.h2
-rw-r--r--demux/codec_tags.c49
-rw-r--r--demux/cue.c206
-rw-r--r--demux/cue.h41
-rw-r--r--demux/demux.c151
-rw-r--r--demux/demux.h20
-rw-r--r--demux/demux_cue.c217
-rw-r--r--demux/demux_disc.c13
-rw-r--r--demux/demux_edl.c11
-rw-r--r--demux/demux_lavf.c84
-rw-r--r--demux/demux_libarchive.c106
-rw-r--r--demux/demux_libass.c4
-rw-r--r--demux/demux_mkv.c794
-rw-r--r--demux/demux_mkv_timeline.c12
-rw-r--r--demux/demux_playlist.c29
-rw-r--r--demux/demux_raw.c2
-rw-r--r--demux/demux_subreader.c2
-rw-r--r--demux/demux_tv.c2
-rw-r--r--demux/ebml.c51
-rw-r--r--demux/matroska.h76
-rw-r--r--demux/stheader.h12
-rw-r--r--etc/example.conf5
-rw-r--r--etc/input.conf73
-rw-r--r--etc/mplayer-input.conf25
-rw-r--r--etc/mpv.desktop2
-rw-r--r--etc/restore-old-bindings.conf6
-rw-r--r--input/cmd_list.c121
-rw-r--r--input/cmd_list.h8
-rw-r--r--input/cmd_parse.c10
-rw-r--r--input/event.c12
-rw-r--r--input/event.h10
-rw-r--r--input/input.c34
-rw-r--r--input/input.h13
-rw-r--r--input/ipc.c23
-rw-r--r--libmpv/client.h22
-rw-r--r--libmpv/opengl_cb.h20
-rw-r--r--misc/charset_conv.c70
-rw-r--r--misc/charset_conv.h4
-rw-r--r--options/m_config.c2
-rw-r--r--options/m_config.h1
-rw-r--r--options/m_option.c38
-rw-r--r--options/m_property.c49
-rw-r--r--options/m_property.h2
-rw-r--r--options/options.c136
-rw-r--r--options/options.h32
-rw-r--r--options/path.c178
-rw-r--r--options/path.h9
-rw-r--r--osdep/atomics.h5
-rw-r--r--osdep/macosx_application.m11
-rw-r--r--osdep/macosx_events.m67
-rw-r--r--osdep/macosx_events_objc.h10
-rw-r--r--osdep/path-macosx.m2
-rw-r--r--osdep/path-unix.c2
-rw-r--r--osdep/path-win.c51
-rw-r--r--osdep/path.h3
-rw-r--r--osdep/subprocess-posix.c15
-rw-r--r--osdep/subprocess-win.c1
-rw-r--r--osdep/subprocess.h2
-rw-r--r--osdep/terminal-unix.c10
-rw-r--r--osdep/terminal-win.c2
-rw-r--r--osdep/timer-win2.c30
-rw-r--r--osdep/timer.c5
-rw-r--r--osdep/win32/include/pthread.h5
-rw-r--r--osdep/win32/pthread.c33
-rw-r--r--player/audio.c112
-rw-r--r--player/client.c25
-rw-r--r--player/command.c708
-rw-r--r--player/command.h9
-rw-r--r--player/configfiles.c37
-rw-r--r--player/core.h74
-rw-r--r--player/discnav.c376
-rw-r--r--player/loadfile.c293
-rw-r--r--player/lua.c94
-rw-r--r--player/lua/defaults.lua30
-rw-r--r--player/lua/options.lua7
-rw-r--r--player/lua/osc.lua60
-rw-r--r--player/lua/ytdl_hook.lua43
-rw-r--r--player/main.c96
-rw-r--r--player/misc.c19
-rw-r--r--player/osd.c21
-rw-r--r--player/playloop.c114
-rw-r--r--player/screenshot.c30
-rw-r--r--player/scripting.c26
-rw-r--r--player/sub.c14
-rw-r--r--player/video.c500
-rw-r--r--stream/cache.c30
-rw-r--r--stream/cache_file.c3
-rw-r--r--stream/discnav.h86
-rw-r--r--stream/stream.c60
-rw-r--r--stream/stream.h6
-rw-r--r--stream/stream_bluray.c375
-rw-r--r--stream/stream_dvdnav.c268
-rw-r--r--stream/stream_file.c51
-rw-r--r--stream/stream_libarchive.c271
-rw-r--r--stream/stream_libarchive.h13
-rw-r--r--sub/dec_sub.c17
-rw-r--r--sub/dec_sub.h4
-rw-r--r--sub/find_subfiles.c10
-rw-r--r--sub/osd.c11
-rw-r--r--sub/osd.h8
-rw-r--r--sub/osd_state.h3
-rw-r--r--sub/sd.h1
-rw-r--r--sub/sd_ass.c42
-rw-r--r--sub/sd_lavc.c2
-rw-r--r--sub/sd_lavc_conv.c16
-rw-r--r--ta/ta_talloc.h11
-rw-r--r--test/chmap.c6
-rw-r--r--test/chmap_sel.c53
-rw-r--r--test/gl_video.c12
-rwxr-xr-xversion.sh24
-rw-r--r--video/csputils.c9
-rw-r--r--video/d3d.h13
-rw-r--r--video/decode/dec_video.c46
-rw-r--r--video/decode/dec_video.h2
-rw-r--r--video/decode/dxva2.c113
-rw-r--r--video/decode/lavc.h10
-rw-r--r--video/decode/rpi.c2
-rw-r--r--video/decode/vaapi.c78
-rw-r--r--video/decode/vd_lavc.c193
-rw-r--r--video/decode/vda.c26
-rw-r--r--video/decode/vdpau.c145
-rw-r--r--video/decode/videotoolbox.c115
-rw-r--r--video/filter/vf.c4
-rw-r--r--video/filter/vf.h3
-rw-r--r--video/filter/vf_dlopen.h4
-rw-r--r--video/filter/vf_scale.c15
-rw-r--r--video/filter/vf_stereo3d.c408
-rw-r--r--video/filter/vf_sub.c4
-rw-r--r--video/filter/vf_vapoursynth.c14
-rw-r--r--video/filter/vf_vavpp.c305
-rw-r--r--video/filter/vf_vdpaupp.c14
-rw-r--r--video/filter/vf_vdpaurb.c116
-rw-r--r--video/filter/vf_yadif.c14
-rw-r--r--video/fmt-conversion.c3
-rw-r--r--video/hwdec.h9
-rw-r--r--video/image_writer.c53
-rw-r--r--video/image_writer.h2
-rw-r--r--video/img_format.c1
-rw-r--r--video/img_format.h6
-rw-r--r--video/mp_image.c221
-rw-r--r--video/mp_image.h11
-rw-r--r--video/mp_image_pool.c32
-rw-r--r--video/out/aspect.c19
-rw-r--r--video/out/cocoa_common.h19
-rw-r--r--video/out/cocoa_common.m350
-rw-r--r--video/out/drm_common.c8
-rw-r--r--video/out/filter_kernels.c11
-rw-r--r--video/out/filter_kernels.h1
-rw-r--r--video/out/gl_cocoa.c18
-rw-r--r--video/out/gl_common.c207
-rw-r--r--video/out/gl_common.h79
-rw-r--r--video/out/gl_hwdec.c18
-rw-r--r--video/out/gl_hwdec.h1
-rw-r--r--video/out/gl_hwdec_dxva2.c64
-rw-r--r--video/out/gl_hwdec_vaglx.c2
-rw-r--r--video/out/gl_hwdec_vda.c151
-rw-r--r--video/out/gl_hwdec_vdpau.c2
-rw-r--r--video/out/gl_lcms.c82
-rw-r--r--video/out/gl_lcms.h2
-rw-r--r--video/out/gl_osd.c22
-rw-r--r--video/out/gl_osd.h1
-rw-r--r--video/out/gl_rpi.c156
-rw-r--r--video/out/gl_rpi.h17
-rw-r--r--video/out/gl_utils.c92
-rw-r--r--video/out/gl_utils.h8
-rw-r--r--video/out/gl_video.c657
-rw-r--r--video/out/gl_video.h20
-rw-r--r--video/out/gl_w32.c5
-rw-r--r--video/out/gl_wayland.c32
-rw-r--r--video/out/gl_x11.c69
-rw-r--r--video/out/gl_x11egl.c75
-rw-r--r--video/out/vo.c356
-rw-r--r--video/out/vo.h71
-rw-r--r--video/out/vo_direct3d.c35
-rw-r--r--video/out/vo_drm.c104
-rw-r--r--video/out/vo_image.c2
-rw-r--r--video/out/vo_null.c38
-rw-r--r--video/out/vo_opengl.c149
-rw-r--r--video/out/vo_opengl_cb.c165
-rw-r--r--video/out/vo_rpi.c330
-rw-r--r--video/out/vo_sdl.c2
-rw-r--r--video/out/vo_vaapi.c50
-rw-r--r--video/out/vo_vdpau.c215
-rw-r--r--video/out/vo_wayland.c46
-rw-r--r--video/out/vo_x11.c633
-rw-r--r--video/out/vo_xv.c65
-rw-r--r--video/out/w32_common.c53
-rw-r--r--video/out/wayland_common.c67
-rw-r--r--video/out/wayland_common.h7
-rw-r--r--video/out/x11_common.c108
-rw-r--r--video/out/x11_common.h5
-rw-r--r--video/vaapi.c64
-rw-r--r--video/vaapi.h70
-rw-r--r--video/vdpau.c45
-rw-r--r--video/vdpau.h3
-rw-r--r--video/vdpau_functions.inc3
-rw-r--r--video/vdpau_mixer.c26
-rw-r--r--video/vdpau_mixer.h4
-rw-r--r--waftools/checks/custom.py5
-rw-r--r--waftools/checks/generic.py20
-rw-r--r--waftools/dependencies.py9
-rw-r--r--waftools/detections/compiler.py5
-rw-r--r--waftools/generators/headers.py30
-rw-r--r--waftools/inflector.py22
-rw-r--r--waftools/inflectors.py26
-rw-r--r--wscript88
-rw-r--r--wscript_build.py22
302 files changed, 10859 insertions, 8978 deletions
diff --git a/Copyright b/Copyright
index ba2612d..51a1a16 100644
--- a/Copyright
+++ b/Copyright
@@ -2,9 +2,15 @@ mpv is a fork of mplayer2, which is a fork of MPlayer.
mpv as a whole is licensed as GPL version 2 or later (see LICENSE). Most source
files are GPLv2+, but some files are available under a more liberal license,
-such as LGPLv2+, BSD, MIT, ISC, and possibly others. Look at the copyright
+such as LGPLv2.1+, BSD, MIT, ISC, and possibly others. Look at the copyright
header of each source file, and grep the sources for "Copyright" if you need
-to know details. Files without Copyright notice are licensed as LGPLv2+.
+to know details. Files without Copyright notice are licensed as LGPLv2.1+.
+
+All new contributions must be LGPLv2.1+ licensed, or if the changes are done on
+GPL code, must come with the implicit agreement that the project can relicense
+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.
For information about authors and contributors, consult the git log, which
contains the complete SVN and CVS history as well.
@@ -17,3 +23,5 @@ MPlayer as a whole is licensed under GPLv2 (incompatible to GPLv3!), because
some files are licensed to GPLv2 (and _not_ any later version of the license).
In particular, this affects the file libmpdemux/demux_ty_osd.c. It is disabled
under mplayer2, and has been removed from mpv.
+
+"v2.1+" in this context means "version 2.1 or later".
diff --git a/DOCS/client-api-changes.rst b/DOCS/client-api-changes.rst
index 009aab7..8af045d 100644
--- a/DOCS/client-api-changes.rst
+++ b/DOCS/client-api-changes.rst
@@ -4,9 +4,7 @@ Introduction
This file lists all changes that can cause compatibility issues when using
mpv through the client API (libmpv and ``client.h``). Since the client API
interfaces to input handling (commands, properties) as well as command line
-options, this list is interesting for other uses of mpv, such as the Lua
-scripting interface, key bindings in ``input.rst``, or plain command line
-usage.
+options, you should also look at ``interface-changes.rst``.
Normally, changes to the C API that are incompatible to previous iterations
receive a major version bump (i.e. the first version number is increased),
@@ -20,15 +18,31 @@ The version number is the same as used for MPV_CLIENT_API_VERSION (see
Also, read the section ``Compatibility`` in ``client.h``.
+Options, commands, properties
+=============================
+
+Changes to these are not listed here, but in ``interface-changes.rst``. (Before
+client API version 1.17, they were listed here partially.)
+
+This listing includes changes to the bare C API and behavior only, not what
+you can access with them.
+
API changes
===========
::
+ ... - add "GL_MP_D3D_interfaces" pseudo extension to make it possible to
+ use DXVA2 in OpenGL fullscreen mode in some situations
+ 1.19 - mpv_request_log_messages() now accepts "terminal-default" as parameter
+ 1.18 - add MPV_END_FILE_REASON_REDIRECT, and change behavior of
+ MPV_EVENT_END_FILE accordingly
+ - a bunch of interface-changes.rst changes
+ 1.17 - mpv_initialize() now blocks SIGPIPE (details see client.h)
--- mpv 0.9.0 is released ---
- 1.17 - add MPV_FORMAT_BYTE_ARRAY
1.16 - add mpv_opengl_cb_report_flip()
- introduce mpv_opengl_cb_draw() and deprecate mpv_opengl_cb_render()
+ - add MPV_FORMAT_BYTE_ARRAY
1.15 - mpv_initialize() will now load config files. This requires setting
the "config" and "config-dir" options. In particular, it will load
mpv.conf.
diff --git a/DOCS/client_api_examples/README.md b/DOCS/client_api_examples/README.md
index 1b6b40b..013098b 100644
--- a/DOCS/client_api_examples/README.md
+++ b/DOCS/client_api_examples/README.md
@@ -24,6 +24,11 @@ qml_direct
Alternative example, which typically avoids a FBO indirection. Might be
slightly faster, but is less flexible and harder to use.
+sdl
+---
+
+Show how to embed the mpv OpenGL renderer in SDL.
+
simple
------
diff --git a/DOCS/client_api_examples/cocoa-openglcb/cocoa-openglcb.m b/DOCS/client_api_examples/cocoa-openglcb/cocoa-openglcb.m
new file mode 100644
index 0000000..4a4e8aa
--- /dev/null
+++ b/DOCS/client_api_examples/cocoa-openglcb/cocoa-openglcb.m
@@ -0,0 +1,304 @@
+// Plays a video from the command line in an opengl view in its own window.
+
+// Build with: clang -o cocoa-openglcb cocoa-openglcb.m `pkg-config --libs --cflags mpv` -framework Cocoa -framework OpenGL
+
+#import <mpv/client.h>
+#import <mpv/opengl_cb.h>
+
+#import <stdio.h>
+#import <stdlib.h>
+#import <OpenGL/gl.h>
+
+#import <Cocoa/Cocoa.h>
+
+
+static inline void check_error(int status)
+{
+ if (status < 0) {
+ printf("mpv API error: %s\n", mpv_error_string(status));
+ exit(1);
+ }
+}
+
+static void *get_proc_address(void *ctx, const char *name)
+{
+ CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault, name, kCFStringEncodingASCII);
+ void *addr = CFBundleGetFunctionPointerForName(CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")), symbolName);
+ CFRelease(symbolName);
+ return addr;
+}
+
+static void glupdate(void *ctx);
+
+@interface MpvClientOGLView : NSOpenGLView
+@property mpv_opengl_cb_context *mpvGL;
+- (instancetype)initWithFrame:(NSRect)frame;
+- (void)drawRect;
+- (void)fillBlack;
+@end
+
+@implementation MpvClientOGLView
+- (instancetype)initWithFrame:(NSRect)frame
+{
+ // make sure the pixel format is double buffered so we can use
+ // [[self openGLContext] flushBuffer].
+ NSOpenGLPixelFormatAttribute attributes[] = {
+ NSOpenGLPFADoubleBuffer,
+ 0
+ };
+ self = [super initWithFrame:frame
+ pixelFormat:[[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]];
+
+ if (self) {
+ [self setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
+ // swap on vsyncs
+ GLint swapInt = 1;
+ [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
+ [[self openGLContext] makeCurrentContext];
+ self.mpvGL = nil;
+ }
+ return self;
+}
+
+- (void)fillBlack
+{
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+}
+
+- (void)drawRect
+{
+ if (self.mpvGL)
+ mpv_opengl_cb_draw(self.mpvGL, 0, self.bounds.size.width, -self.bounds.size.height);
+ else
+ [self fillBlack];
+ [[self openGLContext] flushBuffer];
+}
+
+- (void)drawRect:(NSRect)dirtyRect
+{
+ [self drawRect];
+}
+@end
+
+@interface CocoaWindow : NSWindow
+@property(retain, readonly) MpvClientOGLView *glView;
+@property(retain, readonly) NSButton *pauseButton;
+@end
+
+@implementation CocoaWindow
+- (BOOL)canBecomeMainWindow { return YES; }
+- (BOOL)canBecomeKeyWindow { return YES; }
+- (void)initOGLView {
+ NSRect bounds = [[self contentView] bounds];
+ // window coordinate origin is bottom left
+ NSRect glFrame = NSMakeRect(bounds.origin.x, bounds.origin.y + 30, bounds.size.width, bounds.size.height - 30);
+ _glView = [[MpvClientOGLView alloc] initWithFrame:glFrame];
+ [self.contentView addSubview:_glView];
+
+ NSRect buttonFrame = NSMakeRect(bounds.origin.x, bounds.origin.y, 60, 30);
+ _pauseButton = [[NSButton alloc] initWithFrame:buttonFrame];
+ _pauseButton.buttonType = NSToggleButton;
+ // button target has to be the delegate (it holds the mpv context
+ // pointer), so that's set later.
+ _pauseButton.action = @selector(togglePause:);
+ _pauseButton.title = @"Pause";
+ _pauseButton.alternateTitle = @"Play";
+ [self.contentView addSubview:_pauseButton];
+}
+@end
+
+@interface AppDelegate : NSObject <NSApplicationDelegate>
+{
+ mpv_handle *mpv;
+ dispatch_queue_t queue;
+ CocoaWindow *window;
+}
+@end
+
+static void wakeup(void *);
+
+@implementation AppDelegate
+
+- (void)createWindow {
+
+ int mask = NSTitledWindowMask|NSClosableWindowMask|
+ NSMiniaturizableWindowMask|NSResizableWindowMask;
+
+ window = [[CocoaWindow alloc]
+ initWithContentRect:NSMakeRect(0, 0, 1280, 720)
+ styleMask:mask
+ backing:NSBackingStoreBuffered
+ defer:NO];
+
+ // force a minimum size to stop opengl from exploding.
+ [window setMinSize:NSMakeSize(200, 200)];
+ [window initOGLView];
+ [window setTitle:@"cocoa-openglcb example"];
+ [window makeMainWindow];
+ [window makeKeyAndOrderFront:nil];
+
+ NSMenu *m = [[NSMenu alloc] initWithTitle:@"AMainMenu"];
+ NSMenuItem *item = [m addItemWithTitle:@"Apple" action:nil keyEquivalent:@""];
+ NSMenu *sm = [[NSMenu alloc] initWithTitle:@"Apple"];
+ [m setSubmenu:sm forItem:item];
+ [sm addItemWithTitle: @"quit" action:@selector(terminate:) keyEquivalent:@"q"];
+ [NSApp setMenu:m];
+ [NSApp activateIgnoringOtherApps:YES];
+}
+
+- (void) applicationDidFinishLaunching:(NSNotification *)notification {
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+ atexit_b(^{
+ // Because activation policy has just been set to behave like a real
+ // application, that policy must be reset on exit to prevent, among
+ // other things, the menubar created here from remaining on screen.
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
+ });
+
+ // Read filename
+ NSArray *args = [NSProcessInfo processInfo].arguments;
+ if (args.count < 2) {
+ NSLog(@"Expected filename on command line");
+ exit(1);
+ }
+ NSString *filename = args[1];
+
+ [self createWindow];
+ window.pauseButton.target = self;
+
+ mpv = mpv_create();
+ if (!mpv) {
+ printf("failed creating context\n");
+ exit(1);
+ }
+
+ check_error(mpv_set_option_string(mpv, "input-media-keys", "yes"));
+ // request important errors
+ check_error(mpv_request_log_messages(mpv, "warn"));
+
+ check_error(mpv_initialize(mpv));
+ check_error(mpv_set_option_string(mpv, "vo", "opengl-cb"));
+ mpv_opengl_cb_context *mpvGL = mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
+ if (!mpvGL) {
+ puts("libmpv does not have the opengl-cb sub-API.");
+ exit(1);
+ }
+ // pass the mpvGL context to our view
+ window.glView.mpvGL = mpvGL;
+ int r = mpv_opengl_cb_init_gl(mpvGL, NULL, get_proc_address, NULL);
+ if (r < 0) {
+ puts("gl init has failed.");
+ exit(1);
+ }
+ mpv_opengl_cb_set_update_callback(mpvGL, glupdate, (__bridge void *)window.glView);
+
+ // Deal with MPV in the background.
+ queue = dispatch_queue_create("mpv", DISPATCH_QUEUE_SERIAL);
+ dispatch_async(queue, ^{
+ // Register to be woken up whenever mpv generates new events.
+ mpv_set_wakeup_callback(mpv, wakeup, (__bridge void *)self);
+ // Load the indicated file
+ const char *cmd[] = {"loadfile", filename.UTF8String, NULL};
+ check_error(mpv_command(mpv, cmd));
+ });
+}
+
+static void glupdate(void *ctx)
+{
+ MpvClientOGLView *glView = (__bridge MpvClientOGLView *)ctx;
+ // I'm still not sure what the best way to handle this is, but this
+ // works.
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [glView drawRect];
+ });
+}
+
+- (void) handleEvent:(mpv_event *)event
+{
+ switch (event->event_id) {
+ case MPV_EVENT_SHUTDOWN: {
+ mpv_detach_destroy(mpv);
+ mpv_opengl_cb_uninit_gl(window.glView.mpvGL);
+ mpv = NULL;
+ printf("event: shutdown\n");
+ break;
+ }
+
+ case MPV_EVENT_LOG_MESSAGE: {
+ struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
+ printf("[%s] %s: %s", msg->prefix, msg->level, msg->text);
+ }
+
+ default:
+ printf("event: %s\n", mpv_event_name(event->event_id));
+ }
+}
+
+- (void)togglePause:(NSButton *)button
+{
+ if (mpv) {
+ switch (button.state) {
+ case NSOffState:
+ {
+ int pause = 0;
+ mpv_set_property(mpv, "pause", MPV_FORMAT_FLAG, &pause);
+ }
+ break;
+ case NSOnState:
+ {
+ int pause = 1;
+ mpv_set_property(mpv, "pause", MPV_FORMAT_FLAG, &pause);
+ }
+ break;
+ default:
+ NSLog(@"This should never happen.");
+ }
+ }
+}
+
+- (void) readEvents
+{
+ dispatch_async(queue, ^{
+ while (mpv) {
+ mpv_event *event = mpv_wait_event(mpv, 0);
+ if (event->event_id == MPV_EVENT_NONE)
+ break;
+ [self handleEvent:event];
+ }
+ });
+}
+
+static void wakeup(void *context)
+{
+ AppDelegate *a = (__bridge AppDelegate *) context;
+ [a readEvents];
+}
+
+// quit when the window is closed.
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app
+{
+ return YES;
+}
+
+- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
+{
+ NSLog(@"Terminating.");
+ const char *args[] = {"quit", NULL};
+ mpv_command(mpv, args);
+ [window.glView clearGLContext];
+ return NSTerminateNow;
+}
+
+@end
+
+// Delete this if you already have a main.m.
+int main(int argc, const char * argv[]) {
+ @autoreleasepool {
+ NSApplication *app = [NSApplication sharedApplication];
+ AppDelegate *delegate = [AppDelegate new];
+ app.delegate = delegate;
+ [app run];
+ }
+ return 0;
+}
diff --git a/DOCS/client_api_examples/cocoa/cocoabasic.m b/DOCS/client_api_examples/cocoa/cocoabasic.m
index f2d9059..5a78250 100644
--- a/DOCS/client_api_examples/cocoa/cocoabasic.m
+++ b/DOCS/client_api_examples/cocoa/cocoabasic.m
@@ -1,9 +1,7 @@
-// Plays a video from the command line in a window provided by mpv.
-// You likely want to play the video in your own window instead,
-// but that's not quite ready yet.
-// You may need a basic Info.plist and MainMenu.xib to make this work.
+// Plays a video from the command line in a view provided by the client
+// application.
-// Build with: clang -o cocoabasic cocoabasic.m `pkg-config --libs --cflags mpv`
+// Build with: clang -o cocoabasic cocoabasic.m `pkg-config --libs --cflags mpv` -framework cocoa
#include <mpv/client.h>
@@ -207,5 +205,5 @@ int main(int argc, const char * argv[]) {
app.delegate = delegate;
[app run];
}
- return EXIT_SUCCESS;
+ return 0;
}
diff --git a/DOCS/client_api_examples/sdl/main.c b/DOCS/client_api_examples/sdl/main.c
new file mode 100644
index 0000000..ae4b2b2
--- /dev/null
+++ b/DOCS/client_api_examples/sdl/main.c
@@ -0,0 +1,157 @@
+// Build with: gcc -o main main.c `pkg-config --libs --cflags mpv sdl2`
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <SDL.h>
+
+#include <mpv/client.h>
+#include <mpv/opengl_cb.h>
+
+static Uint32 wakeup_on_mpv_redraw, wakeup_on_mpv_events;
+
+static void die(const char *msg)
+{
+ fprintf(stderr, "%s\n", msg);
+ exit(1);
+}
+
+static void *get_proc_address_mpv(void *fn_ctx, const char *name)
+{
+ return SDL_GL_GetProcAddress(name);
+}
+
+static void on_mpv_events(void *ctx)
+{
+ SDL_Event event = {.type = wakeup_on_mpv_events};
+ SDL_PushEvent(&event);
+}
+
+static void on_mpv_redraw(void *ctx)
+{
+ SDL_Event event = {.type = wakeup_on_mpv_redraw};
+ SDL_PushEvent(&event);
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc != 2)
+ die("pass a single media file as argument");
+
+ mpv_handle *mpv = mpv_create();
+ if (!mpv)
+ die("context init failed");
+
+ // Some minor options can only be set before mpv_initialize().
+ if (mpv_initialize(mpv) < 0)
+ die("mpv init failed");
+
+ if (SDL_Init(SDL_INIT_VIDEO) < 0)
+ die("SDL init failed");
+
+ SDL_Window *window =
+ SDL_CreateWindow("hi", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
+ 1000, 500, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
+ if (!window)
+ die("failed to create SDL window");
+
+ // The OpenGL API is somewhat separate from the normal mpv API. This only
+ // returns NULL if no OpenGL support is compiled.
+ mpv_opengl_cb_context *mpv_gl = mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
+ if (!mpv_gl)
+ die("failed to create mpv GL API handle");
+
+ SDL_GLContext glcontext = SDL_GL_CreateContext(window);
+ if (!glcontext)
+ die("failed to create SDL GL context");
+
+ // This makes mpv use the currently set GL context. It will use the callback
+ // to resolve GL builtin functions, as well as extensions.
+ if (mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address_mpv, NULL) < 0)
+ die("failed to initialize mpv GL context");
+
+ // Actually using the opengl_cb state has to be explicitly requested.
+ // Otherwise, mpv will create a separate platform window.
+ if (mpv_set_option_string(mpv, "vo", "opengl-cb") < 0)
+ die("failed to set VO");
+
+ // We use events for thread-safe notification of the SDL main loop.
+ // Generally, the wakeup callbacks (set further below) should do as least
+ // work as possible, and merely wake up another thread to do actual work.
+ // On SDL, waking up the mainloop is the ideal course of action. SDL's
+ // SDL_PushEvent() is thread-safe, so we use that.
+ wakeup_on_mpv_redraw = SDL_RegisterEvents(1);
+ wakeup_on_mpv_events = SDL_RegisterEvents(1);
+ if (wakeup_on_mpv_redraw == (Uint32)-1 || wakeup_on_mpv_events == (Uint32)-1)
+ die("could not register events");
+
+ // When normal mpv events are available.
+ mpv_set_wakeup_callback(mpv, on_mpv_events, NULL);
+
+ // When a new frame should be drawn with mpv_opengl_cb_draw().
+ // (Separate from the normal event handling mechanism for the sake of
+ // users which run OpenGL on a different thread.)
+ mpv_opengl_cb_set_update_callback(mpv_gl, on_mpv_redraw, NULL);
+
+ // Play this file. Note that this starts playback asynchronously.
+ const char *cmd[] = {"loadfile", argv[1], NULL};
+ mpv_command(mpv, cmd);
+
+ while (1) {
+ SDL_Event event;
+ if (SDL_WaitEvent(&event) != 1)
+ die("event loop error");
+ int redraw = 0;
+ switch (event.type) {
+ case SDL_QUIT:
+ goto done;
+ case SDL_WINDOWEVENT:
+ if (event.window.event == SDL_WINDOWEVENT_EXPOSED)
+ redraw = 1;
+ break;
+ case SDL_KEYDOWN:
+ if (event.key.keysym.sym == SDLK_SPACE)
+ mpv_command_string(mpv, "cycle pause");
+ break;
+ default:
+ // Happens when a new video frame should be rendered, or if the
+ // current frame has to be redrawn e.g. due to OSD changes.
+ if (event.type == wakeup_on_mpv_redraw)
+ redraw = 1;
+ // Happens when at least 1 new event is in the mpv event queue.
+ if (event.type == wakeup_on_mpv_events) {
+ // Handle all remaining mpv events.
+ while (1) {
+ mpv_event *mp_event = mpv_wait_event(mpv, 0);
+ if (mp_event->event_id == MPV_EVENT_NONE)
+ break;
+ printf("event: %s\n", mpv_event_name(mp_event->event_id));
+ }
+ }
+ }
+ if (redraw) {
+ int w, h;
+ SDL_GetWindowSize(window, &w, &h);
+ // Note:
+ // - The 0 is the FBO to use; 0 is the default framebuffer (i.e.
+ // render to the window directly.
+ // - The negative height tells mpv to flip the coordinate system.
+ // - If you do not want the video to cover the whole screen, or want
+ // to apply any form of fancy transformation, you will have to
+ // render to a FBO.
+ // - See opengl_cb.h on what OpenGL environment mpv expects, and
+ // other API details.
+ mpv_opengl_cb_draw(mpv_gl, 0, w, -h);
+ SDL_GL_SwapWindow(window);
+ }
+ }
+done:
+
+ // Destroy the GL renderer and all of the GL objects it allocated. If video
+ // is still running, the video track will be deselected.
+ mpv_opengl_cb_uninit_gl(mpv_gl);
+
+ mpv_terminate_destroy(mpv);
+ return 0;
+}
diff --git a/DOCS/compile-windows.md b/DOCS/compile-windows.md
index af161fd..9acd6e2 100644
--- a/DOCS/compile-windows.md
+++ b/DOCS/compile-windows.md
@@ -134,10 +134,10 @@ pacman -S git pkg-config python3 mingw-w64-x86_64-gcc
# Install the most important MinGW-w64 dependencies. libass, libbluray and
# lcms2 are also pulled in as dependencies of ffmpeg.
-pacman -S mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-lua
+pacman -S mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-lua51
# Install additional (optional) dependencies
-pacman -S mingw-w64-x86_64-libdvdnav mingw-w64-x86_64-mpg123 mingw-w64-x86_64-libguess
+pacman -S mingw-w64-x86_64-libdvdnav mingw-w64-x86_64-libguess
```
For a 32-bit build, install ``mingw-w64-i686-*`` packages instead.
diff --git a/DOCS/contribute.md b/DOCS/contribute.md
index 19563e9..d3bf1d4 100644
--- a/DOCS/contribute.md
+++ b/DOCS/contribute.md
@@ -54,6 +54,9 @@ Sending patches
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).
Code formatting
---------------
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
new file mode 100644
index 0000000..2d103eb
--- /dev/null
+++ b/DOCS/interface-changes.rst
@@ -0,0 +1,88 @@
+Introduction
+============
+
+mpv provides access to its internal via the following means:
+
+- options
+- commands
+- properties
+- events
+
+All of these are important for interfacing both with end users and API users
+(which include Lua scripts, libmpv, and the JSON IPC). As such, they constitute
+a large part of the user interface and APIs.
+
+This document lists changes to them. New changes are added to the top.
+
+Interface changes
+=================
+
+::
+
+ --- mpv 0.10.0 will be released ---
+ - add --video-aspect-method option
+ - add --playlist-pos option
+ - add --video-sync* options
+ "display-sync-active" property
+ "vo-missed-frame-count" property
+ "audio-speed-correction" and "video-speed-correction" properties
+ - remove --demuxer-readahead-packets and --demuxer-readahead-bytes
+ add --demuxer-max-packets and --demuxer-max-bytes
+ (the new options are not replacement and have very different semantics)
+ - change "video-aspect" property: always settable, even if no video is
+ running; always return the override - if no override is set, return
+ the video's aspect ratio
+ - remove disc-nav (DVD, BD) related properties and commands
+ - add "option-info/<name>/set-locally" property
+ - add --cache-backbuffer; change --cache-default default to 75MB
+ the new total cache size is the sum of backbuffer and the cache size
+ specified by --cache-default or --cache
+ - add ``track-list/N/audio-channels`` property
+ - change --screenshot-tag-colorspace default value
+ - add --stretch-image-subs-to-screen
+ - add "playlist/N/title" property
+ - add --video-stereo-mode=no to disable auto-conversions
+ - add --force-seekable, and change default seekability in some cases
+ - add vf yadif/vavpp/vdpaupp interlaced-only suboptions
+ Also, the option is enabled by default (Except vf_yadif, which has
+ it enabled only if it's inserted by the deinterlace property.)
+ - add --hwdec-preload
+ - add ao coreaudio exclusive suboption
+ - add ``track-list/N/forced`` property
+ - add audio-params/channel-count and ``audio-params-out/channel-count props.
+ - add af volume replaygain-fallback suboption
+ - add video-params/stereo-in property
+ - add "keypress", "keydown", and "keyup" commands
+ - deprecate --ad-spdif-dtshd and enabling passthrough via --ad
+ add --audio-spdif as replacement
+ - remove "get_property" command
+ - remove --slave-broken
+ - add vo opengl custom shader suboptions (source-shader, scale-shader,
+ pre-shaders, post-shaders)
+ - completely change how the hwdec properties work:
+ - "hwdec" now reflects the --hwdec option
+ - "hwdec-detected" does partially what the old "hwdec" property did
+ (and also, "detected-hwdec" is removed)
+ - "hwdec-active" is added
+ - add protocol-list property
+ - deprecate audio-samplerate and audio-channels properties
+ (audio-params sub-properties are the replacement)
+ - add audio-params and audio-out-params properties
+ - deprecate "audio-format" property, replaced with "audio-codec-name"
+ - deprecate --media-title, replaced with --force-media-title
+ - deprecate "length" property, replaced with "duration"
+ - change volume property:
+ - the value 100 is now always "unchanged volume" - with softvol, the
+ range is 0 to --softvol-max, without it is 0-100
+ - the minimum value of --softvol-max is raised to 100
+ - remove vo opengl npot suboption
+ - add relative seeking by percentage to "seek" command
+ - add playlist_shuffle command
+ - add --force-window=immediate
+ - add ao coreaudio change-physical-format suboption
+ - remove vo opengl icc-cache suboption, add icc-cache-dir suboption
+ - add --screenshot-directory
+ - add --screenshot-high-bit-depth
+ - add --screenshot-jpeg-source-chroma
+ - default action for "rescan_external_files" command changes
+ --- mpv 0.9.0 is released ---
diff --git a/DOCS/man/af.rst b/DOCS/man/af.rst
index 96e5068..15bc305 100644
--- a/DOCS/man/af.rst
+++ b/DOCS/man/af.rst
@@ -263,13 +263,6 @@ Available filters are:
used to do conversion itself, unlike this one which lets the filter system
handle the conversion.
-``convert24``
- Filter for internal use only. Converts between 24-bit and 32-bit sample
- formats.
-
-``convertsign``
- Filter for internal use only. Converts between signed/unsigned formats.
-
``volume[=<volumedb>[:...]]``
Implements software volume control. Use this filter with caution since it
can reduce the signal to noise ratio of the sound. In most cases it is
@@ -294,6 +287,10 @@ Available filters are:
``replaygain-clip=yes|no``
Prevent clipping caused by replaygain by automatically lowering the
gain (default). Use ``replaygain-clip=no`` to disable this.
+ ``replaygain-fallback``
+ Gain in dB to apply if the file has no replay gain tags. This option
+ is always applied if the replaygain logic is somehow inactive. If this
+ is applied, no other replaygain options are applied.
``softclip``
Turns soft clipping on. Soft-clipping can make the
sound more smooth if very high volume levels are used. Enable this
diff --git a/DOCS/man/ao.rst b/DOCS/man/ao.rst
index 534f625..d2e8685 100644
--- a/DOCS/man/ao.rst
+++ b/DOCS/man/ao.rst
@@ -55,7 +55,7 @@ Available audio output drivers are:
Allow output of non-interleaved formats (if the audio decoder uses
this format). Currently disabled by default, because some popular
ALSA plugins are utterly broken with non-interleaved formats.
- ``ingore-chmap``
+ ``ignore-chmap``
Don't read or set the channel map of the ALSA device - only request the
required number of channels, and then pass the audio as-is to it. This
option most likely should not be used. It can be useful for debugging,
@@ -162,12 +162,23 @@ Available audio output drivers are:
Automatically redirects to ``coreaudio_exclusive`` when playing compressed
formats.
+ ``change-physical-format=<yes|no>``
+ Change the physical format to one similar to the requested audio format
+ (default: no). This has the advantage that multichannel audio output
+ 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.
+
+ ``exclusive``
+ Use exclusive mode access. This merely redirects to
+ ``coreaudio_exclusive``, but should be preferred over using that AO
+ directly.
+
``coreaudio_exclusive`` (Mac OS X only)
Native Mac OS X audio output driver using direct device access and
exclusive mode (bypasses the sound server).
- Supports only compressed formats (AC3 and DTS).
-
``openal``
Experimental OpenAL audio output driver
@@ -260,6 +271,10 @@ Available audio output drivers are:
``broken-delay``
Simulate broken audio drivers, which don't report latency correctly.
+ ``channel-layouts``
+ If not empty, this is a ``,`` separated list of channel layouts the
+ AO allows. This can be used to test channel layout selection.
+
``pcm``
Raw PCM/WAVE file writer audio output
diff --git a/DOCS/man/encode.rst b/DOCS/man/encode.rst
index 661fb4a..b3a2123 100644
--- a/DOCS/man/encode.rst
+++ b/DOCS/man/encode.rst
@@ -111,7 +111,7 @@ You can encode files from one format/codec to another using this facility.
.. admonition:: Examples
- ``"--ovc=mpeg4 --oacopts=qscale=5"``
+ ``"--ovc=mpeg4 --ovcopts=qscale=5"``
selects constant quantizer scale 5 for MPEG-4 encoding.
``"--ovc=libx264 --ovcopts=crf=23"``
diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst
index efcd324..c49587e 100644
--- a/DOCS/man/input.rst
+++ b/DOCS/man/input.rst
@@ -65,11 +65,11 @@ C-style escaping can be used.
You can bind multiple commands to one key. For example:
-| a show_text "command 1" ; show_text "command 2"
+| a show-text "command 1" ; show-text "command 2"
It's also possible to bind a command to a sequence of keys:
-| a-b-c show_text "command run after a, b, c have been pressed"
+| a-b-c show-text "command run after a, b, c have been pressed"
(This is not shown in the general command syntax.)
@@ -86,7 +86,7 @@ List of Input Commands
disabling default bindings, without disabling all bindings with
``--no-input-default-bindings``.
-``seek <seconds> [relative|absolute|absolute-percent|exact|keyframes]``
+``seek <seconds> [relative|absolute|absolute-percent|relative-percent|exact|keyframes]``
Change the playback position. By default, seeks by a relative amount of
seconds.
@@ -98,6 +98,8 @@ List of Input Commands
Seek to a given time.
absolute-percent
Seek to a given percent position.
+ relative-percent
+ Seek relative to current position in percent.
keyframes
Always restart playback at keyframe boundaries (fast).
exact
@@ -112,25 +114,25 @@ List of Input Commands
3rd parameter (essentially using a space instead of ``+``). The 3rd
parameter is still parsed, but is considered deprecated.
-``revert_seek [mode]``
+``revert-seek [mode]``
Undoes the ``seek`` command, and some other commands that seek (but not
necessarily all of them). Calling this command once will jump to the
playback position before the seek. Calling it a second time undoes the
- ``revert_seek`` command itself. This only works within a single file.
+ ``revert-seek`` command itself. This only works within a single file.
The first argument is optional, and can change the behavior:
mark
- Mark the current time position. The next normal ``revert_seek`` command
+ Mark the current time position. The next normal ``revert-seek`` command
will seek back to this point, no matter how many seeks happened since
last time.
Using it without any arguments gives you the default behavior.
-``frame_step``
+``frame-step``
Play one frame, then pause. Does nothing with audio-only playback.
-``frame_back_step``
+``frame-back-step``
Go back by one frame, then pause. Note that this can be very slow (it tries
to be precise, not fast), and sometimes fails to behave as expected. How
well this works depends on whether precise seeking works correctly (e.g.
@@ -180,7 +182,7 @@ List of Input Commands
frame was dropped. This flag can be combined with the other flags,
e.g. ``video+each-frame``.
-``screenshot_to_file "<filename>" [subtitles|video|window]``
+``screenshot-to-file "<filename>" [subtitles|video|window]``
Take a screenshot and save it to a given file. The format of the file will
be guessed by the extension (and ``--screenshot-format`` is ignored - the
behavior when the extension is missing or unknown is arbitrary).
@@ -192,7 +194,7 @@ List of Input Commands
Like all input command parameters, the filename is subject to property
expansion as described in `Property Expansion`_.
-``playlist_next [weak|force]``
+``playlist-next [weak|force]``
Go to the next entry on the playlist.
weak (default)
@@ -200,7 +202,7 @@ List of Input Commands
force
Terminate playback if there are no more files on the playlist.
-``playlist_prev [weak|force]``
+``playlist-prev [weak|force]``
Go to the previous entry on the playlist.
weak (default)
@@ -230,22 +232,26 @@ List of Input Commands
``loadlist "<playlist>" [replace|append]``
Load the given playlist file (like ``--playlist``).
-``playlist_clear``
+``playlist-clear``
Clear the playlist, except the currently played file.
-``playlist_remove current|<index>``
+``playlist-remove current|<index>``
Remove the playlist entry at the given index. Index values start counting
with 0. The special value ``current`` removes the current entry. Note that
removing the current entry also stops playback and starts playing the next
entry.
-``playlist_move <index1> <index2>``
+``playlist-move <index1> <index2>``
Move the playlist entry at index1, so that it takes the place of the
entry index2. (Paradoxically, the moved playlist entry will not have
the index value index2 after moving if index1 was lower than index2,
because index2 refers to the target entry, not the index the entry
will have after moving.)
+``playlist-shuffle``
+ Shuffle the playlist. This is similar to what is done on start if the
+ ``--shuffle`` option is used.
+
``run "command" "arg1" "arg2" ...``
Run the given command. Unlike in MPlayer/mplayer2 and earlier versions of
mpv (0.2.x and older), this doesn't call the shell. Instead, the command
@@ -271,12 +277,12 @@ List of Input Commands
``quit [<code>]``
Exit the player. If an argument is given, it's used as process exit code.
-``quit_watch_later [<code>]``
+``quit-watch-later [<code>]``
Exit player, and store current playback position. Playing that file later
will seek to the previous position on start. The (optional) argument is
exactly as in the ``quit`` command.
-``sub_add "<file>" [<flags> [<title> [<lang>]]]``
+``sub-add "<file>" [<flags> [<title> [<lang>]]]``
Load the given subtitle file. It is selected as current subtitle after
loading.
@@ -303,27 +309,27 @@ List of Input Commands
The ``lang`` argument sets the track language, and can also influence
stream selection with ``flags`` set to ``auto``.
-``sub_remove [<id>]``
+``sub-remove [<id>]``
Remove the given subtitle track. If the ``id`` argument is missing, remove
the current track. (Works on external subtitle files only.)
-``sub_reload [<id>]``
+``sub-reload [<id>]``
Reload the given subtitle tracks. If the ``id`` argument is missing, reload
the current track. (Works on external subtitle files only.)
This works by unloading and re-adding the subtitle track.
-``sub_step <skip>``
+``sub-step <skip>``
Change subtitle timing such, that the subtitle event after the next
``<skip>`` subtitle events is displayed. ``<skip>`` can be negative to step
backwards.
-``sub_seek <skip>``
+``sub-seek <skip>``
Seek to the next (skip set to 1) or the previous (skip set to -1) subtitle.
- This is similar to ``sub_step``, except that it seeks video and audio
+ 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
+ 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.
@@ -331,11 +337,11 @@ List of Input Commands
Toggle OSD level. If ``<level>`` is specified, set the OSD mode
(see ``--osd-level`` for valid values).
-``print_text "<string>"``
+``print-text "<string>"``
Print text to stdout. The string can contain properties (see
`Property Expansion`_).
-``show_text "<string>" [<duration>|- [<level>]]``
+``show-text "<string>" [<duration>|- [<level>]]``
Show text on the OSD. The string can contain properties, which are expanded
as described in `Property Expansion`_. This can be used to show playback
time, filename, and so on.
@@ -347,24 +353,12 @@ List of Input Commands
<level>
The minimum OSD level to show the text at (see ``--osd-level``).
-``show_progress``
+``show-progress``
Show the progress bar, the elapsed time and the total duration of the file
on the OSD.
-``discnav "<command>"``
- Send a menu control command to the DVD/BD menu implementation. The following
- commands are defined: ``up``, ``down``, ``left``, ``right``,
- ``menu`` (request to enter menu), ``prev`` (previous screen),
- ``select`` (activate current button), ``mouse`` (the mouse was clicked),
- ``mouse_move`` (the mouse cursor changed position).
-
- ``mouse_move`` will use the current mouse position.
-
- Note that while the menu is active, the input section ``discnav-menu`` will
- be enabled, so different key bindings can be mapped for menu mode.
-
-``write_watch_later_config``
- Write the resume config file that the ``quit_watch_later`` command writes,
+``write-watch-later-config``
+ Write the resume config file that the ``quit-watch-later`` command writes,
but continue playback normally.
``stop``
@@ -389,30 +383,47 @@ List of Input Commands
<double>
The mouse event represents double-click.
-``audio_add "<file>" [<flags> [<title> [<lang>]]]``
- Load the given audio file. See ``sub_add`` command.
+``keypress <key_name>``
+ Send a key event through mpv's input handler, triggering whatever
+ behavior is configured to that key. ``key_name`` uses the ``input.conf``
+ naming scheme for keys and modifiers. Useful for the client API: key events
+ can be sent to libmpv to handle internally.
+
+``keydown <key_name>``
+ Similar to ``keypress``, but sets the ``KEYDOWN`` flag so that if the key is
+ bound to a repeatable command, it will be run repeatedly with mpv's key
+ repeat timing until the ``keyup`` command is called.
+
+``keyup [<key_name>]``
+ Set the ``KEYUP`` flag, stopping any repeated behavior that had been
+ triggered. ``key_name`` is optional. If ``key_name`` is not given or is an
+ empty string, ``KEYUP`` will be set on all keys. Otherwise, ``KEYUP`` will
+ only be set on the key specified by ``key_name``.
+
+``audio-add "<file>" [<flags> [<title> [<lang>]]]``
+ Load the given audio file. See ``sub-add`` command.
-``audio_remove [<id>]``
- Remove the given audio track. See ``sub_remove`` command.
+``audio-remove [<id>]``
+ Remove the given audio track. See ``sub-remove`` command.
-``audio_reload [<id>]``
- Reload the given audio tracks. See ``sub_reload`` command.
+``audio-reload [<id>]``
+ Reload the given audio tracks. See ``sub-reload`` command.
-``rescan_external_files [<mode>]``
+``rescan-external-files [<mode>]``
Rescan external files according to the current ``--sub-auto`` and
``--audio-file-auto`` settings. This can be used to auto-load external
files *after* the file was loaded.
The ``mode`` argument is one of the following:
- <keep-selection> (default)
- Do not change current track selections.
-
- <reselect>
- Select the default audio and video streams, which typically selects
+ <reselect> (default)
+ Select the default audio and subtitle streams, which typically selects
external files with highest preference. (The implementation is not
perfect, and could be improved on request.)
+ <keep-selection>
+ Do not change current track selections.
+
Input Commands that are Possibly Subject to Change
--------------------------------------------------
@@ -455,7 +466,7 @@ Input Commands that are Possibly Subject to Change
The ``vf`` command shows the list of requested filters on the OSD after
changing the filter chain. This is roughly equivalent to
- ``show_text ${vf}``. Note that auto-inserted filters for format conversion
+ ``show-text ${vf}``. Note that auto-inserted filters for format conversion
are not shown on the list, only what was requested by the user.
Normally, the commands will check whether the video chain is recreated
@@ -470,7 +481,7 @@ Input Commands that are Possibly Subject to Change
- ``b vf set ""`` remove all video filters on ``b``
- ``c vf toggle lavfi=gradfun`` toggle debanding on ``c``
-``cycle_values ["!reverse"] <property> "<value1>" "<value2>" ...``
+``cycle-values ["!reverse"] <property> "<value1>" "<value2>" ...``
Cycle through a list of values. Each invocation of the command will set the
given property to the next value in the list. The command maintains an
internal counter which value to pick next, and which is initially 0. It is
@@ -488,7 +499,7 @@ Input Commands that are Possibly Subject to Change
Note that there is a static limit of (as of this writing) 10 arguments
(this limit could be raised on demand).
-``enable_section "<section>" [default|exclusive]``
+``enable-section "<section>" [flags]``
Enable all key bindings in the named input section.
The enabled input sections form a stack. Bindings in sections on the top of
@@ -497,14 +508,45 @@ Input Commands that are Possibly Subject to Change
implicitly removed beforehand. (A section cannot be on the stack more than
once.)
- If ``exclusive`` is specified as second argument, all sections below the
- newly enabled section are disabled. They will be re-enabled as soon as
- all exclusive sections above them are removed.
+ The ``flags`` parameter can be a combination (separated by ``+``) of the
+ following flags:
+
+ <exclusive>
+ All sections enabled before the newly enabled section are disabled.
+ They will be re-enabled as soon as all exclusive sections above them
+ are removed. In other words, the new section shadows all previous
+ sections.
+ <allow-hide-cursor>
+ This feature can't be used through the public API.
+ <allow-vo-dragging>
+ Same.
+
+``disable-section "<section>"``
+ Disable the named input section. Undoes ``enable-section``.
-``disable_section "<section>"``
- Disable the named input section. Undoes ``enable_section``.
+``define-section "<section>" "<contents>" [default|forced]``
+ Create a named input section, or replace the contents of an already existing
+ input section. The ``contents`` parameter uses the same syntax as the
+ ``input.conf`` file (except that using the section syntax in it is not
+ allowed), including the need to separate bindings with a newline character.
-``overlay_add <id> <x> <y> "<file>" <offset> "<fmt>" <w> <h> <stride>``
+ If the ``contents`` parameter is an empty string, the section is removed.
+
+ The section with the name ``default`` is the normal input section.
+
+ In general, input sections have to be enabled with the ``enable-section``
+ command, or they are ignored.
+
+ The last parameter has the following meaning:
+
+ <default> (also used if parameter omitted)
+ Use a key binding defined by this section only if the user hasn't
+ already bound this key to a command.
+ <forced>
+ Always bind a key. (The input section that was made active most recently
+ wins if there are ambiguities.)
+
+``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
of the video window.
@@ -520,7 +562,7 @@ Input Commands that are Possibly Subject to Change
``id`` is an integer between 0 and 63 identifying the overlay element. The
ID can be used to add multiple overlay parts, update a part by using this
command with an already existing ID, or to remove a part with
- ``overlay_remove``. Using a previously unused ID will add a new overlay,
+ ``overlay-remove``. Using a previously unused ID will add a new overlay,
while reusing an ID will update it. (Future directions: there should be
something to ensure different programs wanting to create overlays don't
conflict with each others, should that ever be needed.)
@@ -534,11 +576,11 @@ Input Commands that are Possibly Subject to Change
vdpau), so no actual copying is involved. Truncating the source file while
the overlay is active will crash the player. You shouldn't change the data
while the overlay is active, because the data is essentially accessed at
- random points. Instead, call ``overlay_add`` again (preferably with a
+ random points. Instead, call ``overlay-add`` again (preferably with a
different memory region to prevent tearing).
It is also possible to pass a raw memory address for use as bitmap memory
- by passing a memory address as integer prefixed with a ``&`` character.
+ by passing a memory address as integer prefixed with an ``&`` character.
Passing the wrong thing here will crash the player. This mode might be
useful for use with libmpv. The ``offset`` parameter is simply added to the
memory address (since mpv 0.8.0, ignored before).
@@ -578,22 +620,22 @@ Input Commands that are Possibly Subject to Change
an overlay's memory at random times whenever it feels the need to do
so, for example when redrawing the screen.
-``overlay_remove <id>``
- Remove an overlay added with ``overlay_add`` and the same ID. Does nothing
+``overlay-remove <id>``
+ Remove an overlay added with ``overlay-add`` and the same ID. Does nothing
if no overlay with this ID exists.
-``script_message "<arg1>" "<arg2>" ...``
+``script-message "<arg1>" "<arg2>" ...``
Send a message to all clients, and pass it the following list of arguments.
What this message means, how many arguments it takes, and what the arguments
mean is fully up to the receiver and the sender. Every client receives the
message, so be careful about name clashes (or use ``script_message_to``).
-``script_message_to "<target>" "<arg1>" "<arg2>" ...``
+``script-message-to "<target>" "<arg1>" "<arg2>" ...``
Same as ``script_message``, but send it only to the client named
``<target>``. Each client (scripts etc.) has a unique name. For example,
Lua scripts can get their name via ``mp.get_script_name()``.
-``script_binding "<name>"``
+``script-binding "<name>"``
Invoke a script-provided key binding. This can be used to remap key
bindings provided by external Lua scripts.
@@ -614,24 +656,24 @@ Input Commands that are Possibly Subject to Change
tracked). The second letter whether the event originates from the mouse,
either ``m`` (mouse button) or ``-`` (something else).
-``ab_loop``
+``ab-loop``
Cycle through A-B loop states. The first command will set the ``A`` point
(the ``ab-loop-a`` property); the second the ``B`` point, and the third
will clear both points.
-``vo_cmdline "<args>"``
+``vo-cmdline "<args>"``
Reset the sub-option of the current VO. Currently works with ``opengl``
(including ``opengl-hq``). The argument is the sub-option string usually
passed to the VO on the command line. Not all sub-options can be set, but
those which can will be reset even if they don't appear in the argument.
This command might be changed or removed in the future.
-``drop_buffers``
+``drop-buffers``
Drop audio/video/demuxer buffers, and restart from fresh. Might help with
unseekable streams that are going out of sync.
This command might be changed or removed in the future.
-``screenshot_raw [subtitles|video|window]``
+``screenshot-raw [subtitles|video|window]``
Return a screenshot in memory. This can be used only through the client
API. The MPV_FORMAT_NODE_MAP returned by this command has the ``w``, ``h``,
``stride`` fields set to obvious contents. A ``format`` field is set to
@@ -640,14 +682,14 @@ Input Commands that are Possibly Subject to Change
field is of type MPV_FORMAT_BYTE_ARRAY with the actual image data. The image
is freed as soon as the result node is freed.
-Undocumented commands: ``tv_last_channel`` (TV/DVB only),
-``get_property`` (deprecated), ``ao_reload`` (experimental/internal).
+Undocumented commands: ``tv-last-channel`` (TV/DVB only),
+``ao-reload`` (experimental/internal).
Hooks
~~~~~
Hooks are synchronous events between player core and a script or similar. This
-applies to the Lua scripting interface and the client API and only. Normally,
+applies to client API (including the Lua scripting interface). Normally,
events are supposed to be asynchronous, and the hook API provides an awkward
and obscure way to handle events that require stricter coordination. There are
no API stability guarantees made. Not following the protocol exactly can make
@@ -656,7 +698,7 @@ the player freeze randomly. Basically, nobody should use this API.
There are two special commands involved. Also, the client must listen for
client messages (``MPV_EVENT_CLIENT_MESSAGE`` in the C API).
-``hook_add <hook-name> <id> <priority>``
+``hook-add <hook-name> <id> <priority>``
Subscribe to the hook identified by the first argument (basically, the
name of event). The ``id`` argument is an arbitrary integer chosen by the
user. ``priority`` is used to sort all hook handlers globally across all
@@ -681,9 +723,9 @@ client messages (``MPV_EVENT_CLIENT_MESSAGE`` in the C API).
typically be stopped.
When the client is done, it must continue the core's hook execution by
- running the ``hook_ack`` command.
+ running the ``hook-ack`` command.
-``hook_ack <string>``
+``hook-ack <string>``
Run the next hook in the global chain of hooks. The argument is the 3rd
argument of the client message that starts hook execution for the
current client.
@@ -756,7 +798,7 @@ Properties
Properties are used to set mpv options during runtime, or to query arbitrary
information. They can be manipulated with the ``set``/``add``/``cycle``
-commands, and retrieved with ``show_text``, or anything else that uses property
+commands, and retrieved with ``show-text``, or anything else that uses property
expansion. (See `Property Expansion`_.)
The property name is annotated with RW to indicate whether the property is
@@ -784,6 +826,16 @@ Property list
``speed`` (RW)
See ``--speed``.
+``audio-speed-correction``, ``video-speed-correction``
+ Factor multiplied with ``speed`` at which the player attempts to play the
+ file. Usually it's exactly 1. (Display sync mode will make this useful.)
+
+ OSD formatting will display it in the form of ``+1.23456%``, with the number
+ being ``(raw - 1) * 100`` for the given raw property value.
+
+``display-sync-active``
+ Return whether ``--video-sync=display`` is actually active.
+
``filename``
Currently played file, with path stripped. If this is an URL, try to undo
percent encoding as well. (The result is not necessarily correct, but
@@ -836,11 +888,14 @@ Property list
``stream-end``
Raw end position in bytes in source stream.
-``length``
- Length of the current file in seconds. If the length is unknown, the
+``duration``
+ Duration of the current file in seconds. If the duration is unknown, the
property is unavailable. Note that the file duration is not always exactly
known, so this is an estimate.
+ This replaces the ``length`` property, which was deprecated after the
+ mpv 0.9 release. (The semantics are the same.)
+
``avsync``
Last A/V synchronization difference. Unavailable if audio or video is
disabled.
@@ -876,10 +931,12 @@ Property list
always exactly known, so this is an estimate.
``playtime-remaining``
- ``time-remaining`` scaled by the the current ``speed``.
+ ``time-remaining`` scaled by the current ``speed``.
-``playback-time``
- Return the playback time, which is the time difference between start PTS and current PTS.
+``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.)
``chapter`` (RW)
Current chapter number. The number of the first chapter is 0.
@@ -922,16 +979,6 @@ Property list
Current BD/DVD title number. Writing works only for ``dvdnav://`` and
``bd://`` (and aliases for these).
-``disc-menu-active``
- Return ``yes`` if the BD/DVD menu is active, or ``no`` on normal video
- playback. The property is unavailable when playing something that is not
- a BD or DVD. Use the ``discnav menu`` command to actually enter or leave
- menu mode.
-
-``disc-mouse-on-button``
- Return ``yes`` when the mouse cursor is located on a button, or ``no``
- when cursor is outside of any button for disc navigation.
-
``chapters``
Number of chapters.
@@ -1092,7 +1139,7 @@ Property list
``demuxer-cache-time``
Approximate time of video buffered in the demuxer, in seconds. Same as
- ``demuxer-cache-duration`` but returns the last timestamp of bufferred
+ ``demuxer-cache-duration`` but returns the last timestamp of buffered
data in demuxer.
``demuxer-cache-idle``
@@ -1126,7 +1173,7 @@ Property list
See ``--hr-seek``.
``volume`` (RW)
- Current volume (0-100).
+ Current volume (see ``--volume`` for details).
``mute`` (RW)
Current mute status (``yes``/``no``).
@@ -1134,18 +1181,52 @@ Property list
``audio-delay`` (RW)
See ``--audio-delay``.
-``audio-format``
- Audio format as string.
-
``audio-codec``
Audio codec selected for decoding.
-``audio-samplerate``
- Audio samplerate.
+``audio-codec-name``
+ Audio codec.
-``audio-channels``
- Number of audio channels. The OSD value of this property is actually the
- channel layout, while the raw value returns the number of channels only.
+``audio-params``
+ Audio format as output by the audio decoder.
+ This has a number of sub-properties:
+
+ ``audio-params/format``
+ The sample format as string. This uses the same names as used in other
+ places of mpv.
+
+ ``audio-params/samplerate``
+ Samplerate.
+
+ ``audio-params/channels``
+ The channel layout as a string. This is similar to what the
+ ``--audio-channels`` accepts.
+
+ ``audio-params/hr-channels``
+ As ``channels``, but instead of the possibly cryptic actual layout
+ sent to the audio device, return a hopefully more human readable form.
+ (Usually only ``audio-out-params/hr-channels`` makes sense.)
+
+ ``audio-params/channel-count``
+ Number of audio channels. This is redundant to the ``channels`` field
+ described above.
+
+ When querying the property with the client API using ``MPV_FORMAT_NODE``,
+ or with Lua ``mp.get_property_native``, this will return a mpv_node with
+ the following contents:
+
+ ::
+
+ MPV_FORMAT_NODE_MAP
+ "format" MPV_FORMAT_STRING
+ "samplerate" MPV_FORMAT_INT64
+ "channels" MPV_FORMAT_STRING
+ "channel-count" MPV_FORMAT_INT64
+ "hr-channels" MPV_FORMAT_STRING
+
+``audio-out-params``
+ Same as ``audio-params``, but the format of the data written to the audio
+ API.
``aid`` (RW)
Current audio track (similar to ``--aid``).
@@ -1156,7 +1237,7 @@ Property list
``balance`` (RW)
Audio channel balance. (The implementation of this feature is rather odd.
It doesn't change the volumes of each channel, but instead sets up a pan
- matrix to mix the the left and right channels.)
+ matrix to mix the left and right channels.)
``fullscreen`` (RW)
See ``--fullscreen``.
@@ -1208,25 +1289,29 @@ Property list
See ``--hue``.
``hwdec`` (RW)
- Return the current hardware decoder that is used. This uses the same values
- as the ``--hwdec`` option. If software decoding is active, this returns
- ``no``. You can write this property. Then the ``--hwdec`` option is set to
- the new value, and video decoding will be reinitialized (internally, the
- player will perform a seek to refresh the video properly).
-
- Note that you don't know the success of the operation immediately after
- writing this property. It happens with a delay as video is reinitialized.
-
-``detected-hwdec``
- Return the current hardware decoder that was detected and opened. Returns
- the same values as ``hwdec``.
-
- This is known only once the VO has opened (and possibly later). With some
- VOs (like ``opengl``), this is never known in advance, but only when the
- decoder attempted to create the hw decoder successfully. Also, hw decoders
- with ``-copy`` suffix are returned only while hw decoding is active (and
- unset afterwards). All this reflects how detecting hw decoders are
- detected and used internally in mpv.
+ Reflects the ``--hwdec`` option.
+
+ Writing to it may change the currently used hardware decoder, if possible.
+ (Internally, the player may reinitialize the decoder, and will perform a
+ seek to refresh the video properly.) You can watch the other hwdec
+ properties to see whether this was successful.
+
+ Unlike in mpv 0.9.x and before, this does not return the currently active
+ hardware decoder.
+
+``hwdec-active``
+ Return ``yes`` or ``no``, depending on whether any type of hardware decoding
+ is actually in use.
+
+``hwdec-detected``
+ If software decoding is active, this returns the hardware decoder in use.
+ Otherwise, it returns either ``no``, or if applicable, the currently loaded
+ hardware decoding API. This is known only once the VO has opened (and
+ possibly later). With some VOs (like ``opengl``), this is never known in
+ advance, but only when the decoder attempted to create the hw decoder
+ successfully. Also, hw decoders with ``-copy`` suffix will return ``no``
+ while no video is being decoded. All this reflects how detecting hw decoders
+ are detected and used internally in mpv.
``panscan`` (RW)
See ``--panscan``.
@@ -1290,26 +1375,29 @@ Property list
``video-params/rotate``
Intended display rotation in degrees (clockwise).
+ ``video-params/stereo-in``
+ Source file stereo 3D mode. (See ``--video-stereo-mode`` option.)
+
When querying the property with the client API using ``MPV_FORMAT_NODE``,
or with Lua ``mp.get_property_native``, this will return a mpv_node with
the following contents:
::
- MPV_FORMAT_NODE_ARRAY
- MPV_FORMAT_NODE_MAP (for each track)
- "pixelformat" MPV_FORMAT_STRING
- "w" MPV_FORMAT_INT64
- "h" MPV_FORMAT_INT64
- "dw" MPV_FORMAT_INT64
- "dh" MPV_FORMAT_INT64
- "aspect" MPV_FORMAT_DOUBLE
- "par" MPV_FORMAT_DOUBLE
- "colormatrix" MPV_FORMAT_STRING
- "colorlevels" MPV_FORMAT_STRING
- "primaries" MPV_FORMAT_STRING
- "chroma-location" MPV_FORMAT_STRING
- "rotate" MPV_FORMAT_INT64
+ MPV_FORMAT_NODE_MAP
+ "pixelformat" MPV_FORMAT_STRING
+ "w" MPV_FORMAT_INT64
+ "h" MPV_FORMAT_INT64
+ "dw" MPV_FORMAT_INT64
+ "dh" MPV_FORMAT_INT64
+ "aspect" MPV_FORMAT_DOUBLE
+ "par" MPV_FORMAT_DOUBLE
+ "colormatrix" MPV_FORMAT_STRING
+ "colorlevels" MPV_FORMAT_STRING
+ "primaries" MPV_FORMAT_STRING
+ "chroma-location" MPV_FORMAT_STRING
+ "rotate" MPV_FORMAT_INT64
+ "stereo-in" MPV_FORMAT_STRING
``dwidth``, ``dheight``
Video display size. This is the video size after filters and aspect scaling
@@ -1463,6 +1551,11 @@ Property list
been unloaded yet; in this case, ``current`` refers to the new
selection. (Since mpv 0.7.0.)
+ ``playlist/N/title``
+ Name of the Nth entry. Only available if the playlist file contains
+ such fields, and only if mpv's parser supports it for the given
+ playlist format.
+
When querying the property with the client API using ``MPV_FORMAT_NODE``,
or with Lua ``mp.get_property_native``, this will return a mpv_node with
the following contents:
@@ -1474,6 +1567,7 @@ Property list
"filename" MPV_FORMAT_STRING
"current" MPV_FORMAT_FLAG (might be missing; since mpv 0.7.0)
"playing" MPV_FORMAT_FLAG (same)
+ "title" MPV_FORMAT_STRING (optional)
``track-list``
List of audio/video/sub tracks, current entry marked. Currently, the raw
@@ -1501,6 +1595,10 @@ Property list
``track-list/N/lang``
Track language as identified by the file. Not always available.
+ ``track-list/N/audio-channels``
+ For audio tracks, the number of audio channels in the audio stream.
+ Not always accurate (depends on container hints). Not always available.
+
``track-list/N/albumart``
``yes`` if this is a video track that consists of a single picture,
``no`` or unavailable otherwise. This is used for video tracks that are
@@ -1510,6 +1608,10 @@ Property list
``yes`` if the track has the default flag set in the file, ``no``
otherwise.
+ ``track-list/N/forced``
+ ``yes`` if the track has the forced flag set in the file, ``no``
+ otherwise.
+
``track-list/N/codec``
The codec name used by this track, for example ``h264``. Unavailable
in some rare cases.
@@ -1545,8 +1647,10 @@ Property list
"src-id" MPV_FORMAT_INT64
"title" MPV_FORMAT_STRING
"lang" MPV_FORMAT_STRING
+ "audio-channels" MPV_FORMAT_INT64
"albumart" MPV_FORMAT_FLAG
"default" MPV_FORMAT_FLAG
+ "forced" MPV_FORMAT_FLAG
"external" MPV_FORMAT_FLAG
"external-filename" MPV_FORMAT_STRING
"codec" MPV_FORMAT_STRING
@@ -1625,7 +1729,7 @@ Property list
``osd-sym-cc``
Inserts the current OSD symbol as opaque OSD control code (cc). This makes
- sense only with the ``show_text`` command or options which set OSD messages.
+ sense only with the ``show-text`` command or options which set OSD messages.
The control code is implementation specific and is useless for anything else.
``osd-ass-cc``
@@ -1633,13 +1737,13 @@ Property list
``${osd-ass-cc/1}`` enables it again. By default, ASS sequences are
escaped to avoid accidental formatting, and this property can disable
this behavior. Note that the properties return an opaque OSD control
- code, which only makes sense for the ``show_text`` command or options
+ code, which only makes sense for the ``show-text`` command or options
which set OSD messages.
.. admonition:: Example
- ``--osd-status-msg='This is ${osd-ass-cc/0}{\\b1}bold text'``
- - ``show_text "This is ${osd-ass-cc/0}{\b1}bold text"``
+ - ``show-text "This is ${osd-ass-cc/0}{\b1}bold text"``
Any ASS override tags as understood by libass can be used.
@@ -1740,6 +1844,12 @@ Property list
Return the working directory of the mpv process. Can be useful for JSON IPC
users, because the command line player usually works with relative paths.
+``protocol-list``
+ List of protocol prefixes potentially recognized by the player. They are
+ returned without trailing ``://`` suffix (which is still always required).
+ In some cases, the protocol will not actually be supported (consider
+ ``https`` if ffmpeg is not compiled with TLS support).
+
``mpv-version``
Return the mpv version/copyright string. Depending on how the binary was
built, it might contain either a release version, or just a git hash.
@@ -1783,6 +1893,12 @@ Property list
``no`` otherwise. What this is set to if the option is e.g. changed
at runtime is left undefined (meaning it could change in the future).
+ ``option-info/<name>/set-locally``
+ Return ``yes`` if the option was set per-file. This is the case with
+ automatically loaded profiles, file-dir configs, and other cases. It
+ means the option value will be restored to the value before playback
+ start when playback ends.
+
``option-info/<name>/default-value``
The default value of the option. May not always be available.
@@ -1812,7 +1928,7 @@ command is an exception and not a general rule.)
.. admonition:: Example for input.conf
- ``i show_text "Filename: ${filename}"``
+ ``i show-text "Filename: ${filename}"``
shows the filename of the current file when pressing the ``i`` key
Within ``input.conf``, property expansion can be inhibited by putting the
diff --git a/DOCS/man/ipc.rst b/DOCS/man/ipc.rst
index bbe07f0..31333a2 100644
--- a/DOCS/man/ipc.rst
+++ b/DOCS/man/ipc.rst
@@ -79,6 +79,24 @@ mpv will also send events to clients with JSON messages of the following form:
where ``event_name`` is the name of the event. Additional event-specific fields
can also be present. See `List of events`_ for a list of all supported events.
+Because events can occur at any time, it may be difficult at times to determine
+which response goes with which command. Commands may optionally include a
+``request_id`` which, if provided in the command request, will be copied
+verbatim into the response. mpv does not intrepret the ``request_id`` in any
+way; it is solely for the use of the requester.
+
+For example, this request:
+
+::
+
+ { "command": ["get_property", "time-pos"], "request_id": 100 }
+
+Would generate this response:
+
+::
+
+ { "error": "success", "data": 1.468135, "request_id": 100 }
+
All commands, replies, and events are separated from each other with a line
break character (``\n``).
diff --git a/DOCS/man/lua.rst b/DOCS/man/lua.rst
index d5c9b31..d51a6d9 100644
--- a/DOCS/man/lua.rst
+++ b/DOCS/man/lua.rst
@@ -99,7 +99,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
functions.
Unlike ``mp.command``, this will not use OSD by default either (except
- for some OSd-specific commands).
+ for some OSD-specific commands).
``mp.command_native(table [,def])``
Similar to ``mp.commandv``, but pass the argument list as table. This has
@@ -519,7 +519,7 @@ Example implementation::
optionC = true,
}
read_options(options, "myscript")
- print(option.optionA)
+ print(options.optionA)
The config file will be stored in ``lua-settings/identifier.conf`` in mpv's user
@@ -630,6 +630,10 @@ strictly part of the guaranteed API.
On Windows, ``killed`` is only returned when the process has been
killed by mpv as a result of ``cancellable`` being set to ``true``.
+ ``killed_by_us``
+ Set to ``true`` if the process has been killed by mpv as a result
+ of ``cancellable`` being set to ``true``.
+
In all cases, ``mp.resume_all()`` is implicitly called.
``utils.parse_json(str [, trail])``
@@ -690,6 +694,33 @@ List of events
Happens after a file was unloaded. Typically, the player will load the
next file right away, or quit if this was the last file.
+ The event has the ``reason`` field, which takes one of these values:
+
+ ``eof``
+ The file has ended. This can (but doesn't have to) include
+ incomplete files or broken network connections under
+ circumstances.
+
+ ``stop``
+ Playback was ended by a command.
+
+ ``quit``
+ Playback was ended by sending the quit command.
+
+ ``error``
+ An error happened. In this case, an ``error`` field is present with
+ the error string.
+
+ ``redirect``
+ Happens with playlists and similar. Details see
+ ``MPV_END_FILE_REASON_REDIRECT`` in the C API.
+
+ ``unknown``
+ Unknown. Normally doesn't happen, unless the Lua API is out of sync
+ with the C API. (Likewise, it could happen that your script gets
+ reason strings that did not exist yet at the time your script was
+ written.)
+
``file-loaded``
Happens after a file was loaded and begins playback.
diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst
index 82deedf..aa2a044 100644
--- a/DOCS/man/mpv.rst
+++ b/DOCS/man/mpv.rst
@@ -116,11 +116,7 @@ o (also P)
Show progression bar, elapsed time and total duration on the OSD.
O
- Toggle OSD states: none / seek / seek + timer / seek + timer + total time.
-
-d
- Toggle frame dropping states: none / skip display / skip decoding (see
- ``--framedrop``).
+ Toggle OSD states between normal and playback time/duration.
v
Toggle subtitle visibility.
@@ -134,6 +130,9 @@ x and z
l
Set/clear A-B loop points. See ``ab_loop`` command for details.
+L
+ Toggle infinite looping.
+
Ctrl + and Ctrl -
Adjust audio delay by +/- 0.1 seconds.
@@ -156,6 +155,10 @@ S
Take a screenshot, without subtitles. (Whether this works depends on VO
driver support.)
+Ctrl s
+ Take a screenshot, as the window shows it (with subtitles, OSD, and scaled
+ video).
+
I
Show filename on the OSD.
@@ -168,7 +171,7 @@ Shift+PGUP and Shift+PGDWN
Seek backward or forward by 10 minutes. (This used to be mapped to
PGUP/PGDWN without Shift.)
-D
+d
Activate/deactivate deinterlacer.
A
@@ -273,7 +276,7 @@ spaces or characters like ``,`` or ``:``, you need to quote them:
``mpv '--vo=opengl:icc-profile="file with spaces.icc",xv'``
Shells may actually strip some quotes from the string passed to the commandline,
-so the example quotes the string twice, ensuring that mpv recieves the ``"``
+so the example quotes the string twice, ensuring that mpv receives the ``"``
quotes.
The ``[...]`` form of quotes wraps everything between ``[`` and ``]``. It's
@@ -302,7 +305,7 @@ Suboptions passed to the client API are also subject to escaping. Using
command line (but without shell processing of the string). Some options
support passing values in a more structured way instead of flat strings, and
can avoid the suboption parsing mess. For example, ``--vf`` supports
-``MPV_FORMAT_NODE``, which let's you pass suboptions as a nested data structure
+``MPV_FORMAT_NODE``, which lets you pass suboptions as a nested data structure
of maps and arrays. (``--vo`` supports this in the same way, although this
fact is undocumented.)
@@ -327,7 +330,17 @@ additionally wrapped in the fixed-length syntax, e.g. ``%n%string_of_length_n``
Some mpv options interpret paths starting with ``~``. Currently, the prefix
``~~/`` expands to the mpv configuration directory (usually ``~/.config/mpv/``).
``~/`` expands to the user's home directory. (The trailing ``/`` is always
-required.)
+required.) There are the following paths as well:
+
+================ ===============================================================
+Name Meaning
+================ ===============================================================
+``~~home/`` same as ``~~/``
+``~~global/`` the global config path, if available (not on win32)
+``~~osxbundle/`` the OSX bundle resource path (OSX only)
+``~~desktop/`` the path to the desktop (win32, OSX)
+================ ===============================================================
+
Per-File Options
----------------
@@ -403,7 +416,7 @@ 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 ``'``,
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 to
+escapes are currently _not_ interpreted on this level, although some options do
this manually. (This is a mess and should probably be changed at some point.)
Putting Command Line Options into the Configuration File
@@ -513,13 +526,21 @@ listed.
if there is audio "missing", or not enough frames can be dropped. Usually
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.
- 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
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
- decoder-framedropping is enabled with the ``--framedrop`` options.
+ decoder frame dropping is enabled with the ``--framedrop`` options.
(``drop-frame-count`` property.)
- Cache state, e.g. ``Cache: 2s+134KB``. Visible if the stream cache is enabled.
The first value shows the amount of video buffered in the demuxer in seconds,
@@ -550,13 +571,9 @@ PROTOCOLS
you must mount the ISO file as filesystem, and point ``--bluray-device``
to the mounted directory directly.
-``bdnav://[title][/device]``
- Play a Blu-Ray disc, with navigation features enabled. This feature is
- permanently experimental.
-
``dvd://[title|[starttitle]-endtitle][/device]`` ``--dvd-device=PATH``
- Play a DVD. If you want dvdnav menus, use ``dvd://menu``. If no title
- is given, the longest title is auto-selected.
+ Play a DVD. DVD menus are not supported. If no title is given, the longest
+ title is auto-selected.
``dvdnav://`` is an old alias for ``dvd://`` and does exactly the same
thing.
@@ -601,6 +618,11 @@ PROTOCOLS
``PATH`` itself should start with a third ``/`` to make the path an
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.
+
``edl://[edl specification as in edl-mpv.rst]``
Stitch together parts of multiple files and play them.
@@ -635,6 +657,7 @@ the ``pseudo-gui`` profile being predefined with the following contents:
terminal=no
force-window=yes
idle=once
+ screenshot-directory=~~desktop/
This follows the mpv config file format. To customize pseudo-GUI mode, you can
put your own ``pseudo-gui`` profile into your ``mpv.conf``. This profile will
@@ -789,6 +812,8 @@ If errors happen, the following exit codes can be returned:
immediately after initialization.
:3: There were some files that could be played, and some files which
couldn't (using the definition of success from above).
+ :4: Quit due to a signal, Ctrl+c in a VO window (by default), or from the
+ default quit key bindings in encoding mode.
Note that quitting the player manually will always lead to exit code 0,
overriding the exit code that would be returned normally. Also, the ``quit``
@@ -860,6 +885,14 @@ Other config files (such as ``input.conf``) are in the same directory. See the
The environment variable ``$MPV_HOME`` completely overrides these, like on
UNIX.
+If a directory named ``portable_config`` next to the mpv.exe exists, all
+config will be loaded from this directory only. Watch later config files are
+written to this directory as well. (This exists on Windows only and is redundant
+with ``$MPV_HOME``. However, since Windows is very scripting unfriendly, a
+wrapper script just setting ``$MPV_HOME``, like you could do it on other
+systems, won't work. ``portable_config`` is provided for convenience to get
+around this restriction.)
+
Config files located in the same directory as ``mpv.exe`` are loaded with
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
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 923556c..f1d0456 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -129,6 +129,17 @@ Playback Control
Specify which chapter to start playing at. Optionally specify which
chapter to end playing at. Also see ``--start``.
+``--playlist-pos=<no|index>``
+ Set which file on the internal playlist to start playback with. The index
+ is an integer, with 0 meaning the first file. The value ``no`` means that
+ the selection of the entry to play is left to the playback resume mechanism
+ (default). If an entry with the given index doesn't exist, the behavior is
+ unspecified and might change in future mpv versions. The same applies if
+ the playlist contains further playlists (don't expect any reasonable
+ behavior). Passing a playlist file to mpv should work with this option,
+ though. E.g. ``mpv playlist.m3u --playlist-pos=123`` will work as expected,
+ as long as ``playlist.m3u`` does not link to further playlists.
+
``--playlist=<filename>``
Play files according to a playlist file (Supports some common formats. If
no format is detected, it will be treated as list of files, separated by
@@ -348,7 +359,7 @@ Program Behavior
``--idle=<no|yes|once>``
Makes mpv wait idly instead of quitting when there is no file to play.
Mostly useful in slave mode, where mpv can be controlled through input
- commands (see also ``--slave-broken``).
+ commands.
``once`` will only idle at start and let the player close once the
first playlist has finished playing back.
@@ -457,7 +468,7 @@ Program Behavior
(Default: ``best``)
``--ytdl-raw-options=<key>=<value>[,<key>=<value>[,...]]``
- Pass arbitraty options to youtube-dl. Parameter and argument should be
+ Pass arbitrary options to youtube-dl. Parameter and argument should be
passed as a key-value pair. Options without argument must include ``=``.
There is no sanity checking so it's possible to break things (i.e.
@@ -528,7 +539,7 @@ Video
Old, decoder-based framedrop mode. (This is the same as ``--framedrop=yes``
in mpv 0.5.x and before.) This tells the decoder to skip frames (unless
they are needed to decode future frames). May help with slow systems,
- but can produce unwatchably choppy output, or even freeze the display
+ but can produce unwatchable choppy output, or even freeze the display
completely. Not recommended.
The ``--vd-lavc-framedrop`` option controls what frames to drop.
<decoder+vo>
@@ -559,6 +570,7 @@ Video
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux with Intel GPUs only)
:vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
:vda: requires ``--vo=opengl`` (OS X only)
+ :videotoolbox: requires ``--vo=opengl`` (newer OS X only)
:dxva2-copy: copies video back to system RAM (Windows only)
:rpi: requires ``--vo=rpi`` (Raspberry Pi only - default if available)
@@ -579,6 +591,22 @@ Video
codecs. See ``--hwdec-codecs`` to enable hardware decoding for more
codecs.
+``--hwdec-preload=<api>``
+ This is useful for the ``opengl`` and ``opengl-cb`` VOs for creating the
+ hardware decoding OpenGL interop context, but without actually enabling
+ hardware decoding itself (like ``--hwdec`` does).
+
+ If set to ``no`` (default), the ``--hwdec`` option is used.
+
+ For ``opengl``, if set, do not create the interop context on demand, but
+ when the VO is created.
+
+ 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
+ to temporarily set the ``hwdec`` option just during OpenGL context
+ initialization with ``mpv_opengl_cb_init_gl()``.
+
``--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
@@ -605,6 +633,23 @@ Video
Ignore aspect ratio information from video file and assume the video has
square pixels. See also ``--video-aspect``.
+``--video-aspect-method=<hybrid|bitstream|container>``
+ This sets the default video aspect determination method (if the aspect is
+ _not_ overridden by the user with ``--video-aspect`` or others).
+
+ :hybrid: Prefer the container aspect ratio. If the bitstream aspect
+ switches mid-stream, switch to preferring the bitstream aspect.
+ This is the default behavior in mpv and mplayer2.
+ :container: Strictly prefer the container aspect ratio. This is apparently
+ the default behavior with VLC, at least with Matroska.
+ :bitstream: Strictly prefer the bitstream aspect ratio, unless the bitstream
+ aspect ratio is not set. This is apparently the default behavior
+ with XBMC/kodi, at least with Matroska.
+
+ Normally you should not set this. Try the ``container`` and ``bitstream``
+ choices if you encounter video that has the wrong aspect ratio in mpv,
+ but seems to be correct in other players.
+
``--video-unscaled``
Disable scaling of the video. If the window is larger than the video,
black bars are added. Otherwise, the video is cropped. The video still
@@ -639,17 +684,17 @@ Video
which means the value ``0`` would rotate the video according to the
rotation metadata.)
-``--video-stereo-mode=<mode>``
+``--video-stereo-mode=<no|mode>``
Set the stereo 3D output mode (default: ``mono``). This is done by inserting
the ``stereo3d`` conversion filter.
+ The pseudo-mode ``no`` disables automatic conversion completely.
+
The mode ``mono`` is an alias to ``ml``, which refers to the left frame in
2D. This is the default, which means mpv will try to show 3D movies in 2D,
instead of the mangled 3D image not intended for consumption (such as
showing the left and right frame side by side, etc.).
- The pseudo-mode ``none`` disables automatic conversion completely.
-
Use ``--video-stereo-mode=help`` to list all available modes. Check with
the ``stereo3d`` filter documentation to see what the names mean. Note that
some names refer to modes not supported by ``stereo3d`` - these modes can
@@ -733,7 +778,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``. Note that the hardware
+ By default this is set to ``h264,vc1,wmv3,hevc``. Note that the hardware
acceleration special codecs like ``h264_vdpau`` are not relevant anymore,
and in fact have been removed from Libav in this form.
@@ -808,10 +853,11 @@ Video
Set framedropping mode used with ``--framedrop`` (see skiploopfilter for
available skip values).
-``--vd-lavc-threads=<0-16>``
+``--vd-lavc-threads=<N>``
Number of threads to use for decoding. Whether threading is actually
- supported depends on codec. 0 means autodetect number of cores on the
- machine and use that, up to the maximum of 16 (default: 0).
+ supported depends on codec (default: 0). 0 means autodetect number of cores
+ on the machine and use that, up to the maximum of 16. You can set more than
+ 16 threads manually.
@@ -838,7 +884,7 @@ Audio
Note that many AOs have a ``device`` sub-option, which overrides the
device selection of this option (but not the audio output selection).
Likewise, forcing an AO with ``--ao`` will override the audio output
- selection of ``--audio-device`` (but not the device selecton).
+ selection of ``--audio-device`` (but not the device selection).
Currently not implemented for most AOs.
@@ -855,6 +901,25 @@ Audio
``--af-clr`` exist to modify a previously specified list, but you
should not need these for typical use.
+``--audio-spdif=<codecs>``
+ List of codecs for which compressed audio passthrough should be used. This
+ works for both classic S/PDIF and HDMI.
+
+ Possible codecs are ``ac3``, ``dts``, ``dts-hd``. Multiple codecs can be
+ specified by separating them with ``,``. ``dts`` refers to low bitrate DTS
+ core, while ``dts-hd`` refers to DTS MA (receiver and OS support varies).
+ You should only use either ``dts`` or ``dts-hd`` (if both are specified,
+ and ``dts`` comes first, only ``dts`` will be used).
+
+ In general, all codecs in the ``spdif`` family listed with ``--ad=help``
+ are supported in theory.
+
+ .. admonition:: Warning
+
+ There is not much reason to use this. HDMI supports uncompressed
+ multichannel PCM, and mpv supports lossless DTS-HD decoding via
+ FFmpeg's libdcadec wrapper.
+
``--ad=<[+|-]family1:(*|decoder1),[+|-]family2:(*|decoder2),...[-]>``
Specify a priority list of audio decoders to be used, according to their
family and decoder name. Entries like ``family:*`` prioritize all decoders
@@ -882,9 +947,21 @@ Audio
``--ad=help``
List all available decoders.
-``--volume=<-1-100>``
- Set the startup volume. A value of -1 (the default) will not change the
- volume. See also ``--softvol``.
+ .. admonition:: Warning
+
+ Enabling compressed audio passthrough (AC3 and DTS via SPDIF/HDMI) with
+ this option is deprecated. Use ``--audio-spdif`` instead.
+
+``--volume=<value>``
+ Set the startup volume. 0 means silence, 100 means no volume reduction or
+ amplification. A value of -1 (the default) will not change the volume. See
+ also ``--softvol``.
+
+ .. note::
+
+ This was changed after the mpv 0.9 release. Before that, 100 actually
+ meant maximum volume. At the same time, the volume scale was made cubic,
+ so the old values won't match up with the new ones anyway.
``--audio-delay=<sec>``
Audio delay in seconds (positive or negative float value). Positive values
@@ -946,17 +1023,12 @@ Audio
welcome. A full list of AVOptions can be found in the FFmpeg manual.
``--ad-spdif-dtshd=<yes|no>``, ``--dtshd``, ``--no-dtshd``
- When using DTS pass-through, output any DTS-HD track as-is.
- With ``ad-spdif-dtshd=no`` (the default), only the DTS Core parts will be
- output.
+ If DTS is passed through, use DTS-HD.
- DTS-HD tracks can be sent over HDMI but not over the original
- coax/TOSLINK S/PDIF system.
-
- Some receivers don't accept DTS core-only when ``--ad-spdif-dtshd=yes`` is
- used, even though they accept DTS-HD.
+ .. admonition:: Warning
- ``--dtshd`` and ``--no-dtshd`` are deprecated aliases.
+ This and enabling passthrough via ``--ad`` are deprecated in favor of
+ using ``--audio-spdif=dts-hd``.
``--audio-channels=<number|layout>``
Request a channel layout for audio output (default: auto). This will ask
@@ -985,6 +1057,14 @@ Audio
If the channel layout of the media file (i.e. the decoder) and the AO's
channel layout don't match, mpv will attempt to insert a conversion filter.
+ .. admonition:: Warning
+
+ Using ``auto`` can cause issues when using audio over HDMI. The OS will
+ typically report all channel layouts that _can_ go over HDMI, even if
+ the receiver does not support them. If a receiver gets an unsupported
+ channel layout, random things can happen, such as dropping the
+ additional channels, or adding noise.
+
``--audio-display=<no|attachment>``
Setting this option to ``attachment`` (default) will display image
attachments (e.g. album cover art) when playing audio files. It will
@@ -1053,19 +1133,9 @@ Audio
their start timestamps differ, and then video timing is gradually adjusted
if necessary to reach correct synchronization later.
-``--softvol-max=<10.0-10000.0>``
- Set the maximum amplification level in percent (default: 200). A value of
- 200 will allow you to adjust the volume up to a maximum of double the
- current level. With values below 100 the initial volume (which is 100%)
- will be above the maximum, which e.g. the OSD cannot display correctly.
-
- .. admonition:: Note
-
- The maximum value of ``--volume`` as well as the ``volume`` property
- is always 100. Likewise, the volume OSD bar always goes from 0 to 100.
- This means that with ``--softvol-max=200``, ``--volume=100`` sets
- maximum amplification, i.e. amplify by 200%. The default volume (no
- change in volume) will be ``50`` in this case.
+``--softvol-max=<100.0-1000.0>``
+ Set the maximum amplification level in percent (default: 130). A value of
+ 130 will allow you to adjust the volume up to about double the normal level.
``--audio-file-auto=<no|exact|fuzzy|all>``, ``--no-audio-file-auto``
Load additional audio files matching the video filename. The parameter
@@ -1140,7 +1210,7 @@ Subtitles
the top of the screen) alongside the normal subtitle, and provides a way
to render two subtitles at once.
- there are some caveats associated with this feature. For example, bitmap
+ There are some caveats associated with this feature. For example, bitmap
subtitles will always be rendered in their usual position, so selecting a
bitmap subtitle as secondary subtitle will result in overlapping subtitles.
Secondary subtitles are never shown on the terminal if video is disabled.
@@ -1356,6 +1426,18 @@ Subtitles
Disabled by default.
+``--stretch-image-subs-to-screen=<yes|no>``
+ Stretch DVD and other image subtitles to the screen, ignoring the video
+ margins. This has a similar effect as ``--sub-use-margins`` for text
+ subtitles, except that the text itself will be stretched, not only just
+ repositioned. (At least in general it is unavoidable, as an image bitmap
+ can in theory consist of a single bitmap covering the whole screen, and
+ the player won't know where exactly the text parts are located.)
+
+ This option does not display subtitles correctly. Use with care.
+
+ Disabled by default.
+
``--sub-ass``, ``--no-sub-ass``
Render ASS subtitles natively (enabled by default).
@@ -1385,10 +1467,11 @@ Subtitles
``--sub-codepage=<codepage>``
If your system supports ``iconv(3)``, you can use this option to specify
- the subtitle codepage. By default, ENCA will be used to guess the charset.
- If mpv is not compiled with ENCA, ``UTF-8:UTF-8-BROKEN`` is the default,
- which means it will try to use UTF-8, otherwise the ``UTF-8-BROKEN``
- pseudo codepage (see below).
+ the subtitle codepage. By default, uchardet will be used to guess the
+ charset. If mpv is not compiled with uchardet, enca will be used.
+ If mpv is compiled with neither uchardet nor enca, ``UTF-8:UTF-8-BROKEN``
+ is the default, which means it will try to use UTF-8, otherwise the
+ ``UTF-8-BROKEN`` pseudo codepage (see below).
The default value for this option is ``auto``, whose actual effect depends
on whether ENCA is compiled.
@@ -1438,6 +1521,12 @@ Subtitles
mode. Use ``--sub-codepage=guess:help`` to get a list of
languages subject to the same caveat as with ENCA above.
+ If the player was compiled with uchardet support you can use it with:
+
+ ``--sub-codepage=uchardet``
+
+ This mode doesn't take language or fallback codepage.
+
``--sub-fix-timing``, ``--no-sub-fix-timing``
By default, external text subtitles are preprocessed to remove minor gaps
or overlaps between subtitles (if the difference is smaller than 200 ms,
@@ -1590,7 +1679,7 @@ Window
file.mkv normally, then fail to open ``/dev/null``, then exit). (In
mpv 0.8.0, ``always`` was introduced, which restores the old behavior.)
-``--force-window``
+``--force-window=<yes|no|immediate>``
Create a video output window even if there is no video. This can be useful
when pretending that mpv is a GUI application. Currently, the window
always has the size 640x480, and is subject to ``--geometry``,
@@ -1602,7 +1691,9 @@ Window
window placement still works if the video size is different from the
``--force-window`` default window size). This can be a problem if
initialization doesn't work perfectly, such as when opening URLs with
- bad network connection, or opening broken video files.
+ bad network connection, or opening broken video files. The ``immediate``
+ mode can be used to create the window always on program start, but this
+ may cause other issues.
``--ontop``
Makes the player window stay on top of other windows.
@@ -1853,7 +1944,7 @@ Window
``intptr_t``. mpv will create its own window, and set the wid window as
parent, like with X11.
- On OSX/Cocoa. the ID is interpreted as ``NSView*``. Pass it as value cast
+ 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
support window embedding of foreign processes, this works only with libmpv,
and will crash when used from the command line.
@@ -1947,7 +2038,7 @@ Disc Devices
(Never) accept imperfect data reconstruction.
``--cdda-cdtext=<yes|no>``
- Print CD text. This is disabled by default, because it ruins perfomance
+ Print CD text. This is disabled by default, because it ruins performance
with CD-ROM drives for unknown reasons.
``--dvd-speed=<speed>``
@@ -2078,7 +2169,7 @@ Demuxer
seeks only.
You can use the ``--demuxer-mkv-subtitle-preroll-secs`` option to specify
- how mach data the demuxer should pre-read at most in order to find subtitle
+ how much data the demuxer should pre-read at most in order to find subtitle
packets that may overlap. Setting this to 0 will effectively disable this
preroll mechanism. Setting a very large value can make seeking very slow,
and an extremely large value would completely reread the entire file from
@@ -2103,15 +2194,20 @@ Demuxer
``--demuxer-mkv-subtitle-preroll-secs=<value>``
See ``--demuxer-mkv-subtitle-preroll``.
-``--demuxer-mkv-probe-video-duration``
+``--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
for compatibility with Haali only. In this mode, it's possible that opening
will be slower (especially when playing over http), or that behavior with
broken files is much worse. So don't use this option.
+ The ``yes`` mode merely uses the index and reads a small number of blocks
+ from the end of the file. The ``full`` mode actually traverses the entire
+ file and can make a reliable estimate even without an index present (such
+ as partial files).
+
``--demuxer-mkv-fix-timestamps=<yes|no>``
- Fix rounded Matroska timestamps (enabled by default). Matroska usually
+ Fix rounded Matroska timestamps (disabled by default). Matroska usually
stores timestamps rounded to milliseconds. This means timestamps jitter
by some amount around the intended timestamp. mpv can correct the timestamps
based on the framerate value stored in the file: the timestamp is rounded
@@ -2162,6 +2258,20 @@ Demuxer
``--demuxer-rawvideo-size=<value>``
Frame size in bytes when using ``--demuxer=rawvideo``.
+``--demuxer-max-packets=<packets>``, ``--demuxer-max-bytes=<bytes>``
+ This controls how much the demuxer is allowed to buffer ahead. The demuxer
+ will normally try to read ahead as much as necessary, or as much is
+ requested with ``--demuxer-readahead-secs``. The ``--demuxer-max-...``
+ options can be used to restrict the maximum readahead. This limits excessive
+ readahead in case of broken files or desynced playback. The demuxer will
+ stop reading additional packets as soon as one of the limits is reached.
+ (The limits still can be slightly overstepped due to technical reasons.)
+
+ Set these limits highher if you get a packet queue overflow warning, and
+ you think normal playback would be possible with a larger packet queue.
+
+ See ``--list-options`` for defaults and value range.
+
``--demuxer-thread=<yes|no>``
Run the demuxer in a separate thread, and let it prefetch a certain amount
of packets (default: yes). Having this enabled may lead to smoother
@@ -2180,21 +2290,11 @@ Demuxer
(This value tends to be fuzzy, because many file formats don't store linear
timestamps.)
-``--demuxer-readahead-packets=<packets>``
- If ``--demuxer-thread`` is enabled, this controls how much the demuxer
- should buffer ahead. As long as the number of packets in the packet queue
- doesn't exceed ``--demuxer-readahead-packets``, and the total number of
- bytes doesn't exceed ``--demuxer-readahead-bytes``, the thread keeps
- reading ahead.
-
- Note that if you set these options near the maximum, you might get a
- packet queue overflow warning.
-
- See ``--list-options`` for defaults and value range.
-
-``--demuxer-readahead-bytes=<bytes>``
- See ``--demuxer-readahead-packets``.
-
+``--force-seekable=<yes|no>``
+ If the player thinks that the media is not seekable (e.g. playing from a
+ pipe, or it's a http stream with a server that doesn't support range
+ requests), seeking will be disabled. This option can forcibly enable it.
+ For seeks within the cache, there's a good chance of success.
Input
-----
@@ -2251,8 +2351,6 @@ Input
This can also specify a direct file descriptor with ``fd://N`` (UNIX only).
In this case, JSON replies will be written if the FD is writable.
- See also ``--slave-broken``.
-
.. note::
When the given file is a FIFO mpv opens both ends, so you can do several
@@ -2566,14 +2664,19 @@ Screenshot
Note that not all formats are supported.
- Default: ``yes``.
+ Default: ``no``.
+
+``--screenshot-high-bit-depth=<yes|no>``
+ If possible, write screenshots with a bit depth similar to the source
+ video (default: yes). This is interesting in particular for PNG, as this
+ sometimes triggers writing 16 bit PNGs with huge file sizes.
``--screenshot-template=<template>``
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 ``shot%n``, which results in filenames like
- ``shot0012.png`` for example.
+ 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
specify a directory location where screenshots should be saved.
@@ -2616,7 +2719,7 @@ Screenshot
.. note::
- This is a simple way for getting unique per-frame timestamps. Frame
+ This is a simple way for getting unique per-frame timestamps. (Frame
numbers would be more intuitive, but are not easily implementable
because container formats usually use time stamps for identifying
frames.)
@@ -2655,9 +2758,26 @@ Screenshot
``%%``
Replaced with the ``%`` character itself.
+``--screenshot-directory=<path>``
+ Store screenshots in this directory. This path is joined with the filename
+ generated by ``--screenshot-template``. If the template filename is already
+ absolute, the directory is ignored.
+
+ If the directory does not exist, it is created on the first screenshot. If
+ it is not a directory, an error is generated when trying to write a
+ screenshot.
+
+ This option is not set by default, and thus will write screenshots to the
+ directory from which mpv was started. In pseudo-gui mode
+ (see `PSEUDO GUI MODE`_), this is set to the desktop.
+
``--screenshot-jpeg-quality=<0-100>``
Set the JPEG quality level. Higher means better quality. The default is 90.
+``--screenshot-jpeg-source-chroma=<yes|no>``
+ Write JPEG files with the same chroma subsampling as the video
+ (default: yes). If disabled, the libjpeg default is used.
+
``--screenshot-png-compression=<0-9>``
Set the PNG compression level. Higher means better compression. This will
affect the file size of the written screenshot file and the time it takes
@@ -2988,13 +3108,13 @@ Cache
seeking, such as MP4.
Note that half the cache size will be used to allow fast seeking back. This
- is also the reason why a full cache is usually reported as 50% full. The
- cache fill display does not include the part of the cache reserved for
- seeking back. Likewise, when starting a file the cache will be at 100%,
- because no space is reserved for seeking back yet.
+ is also the reason why a full cache is usually not reported as 100% full.
+ The cache fill display does not include the part of the cache reserved for
+ seeking back. The actual maximum percentage will usually be the ratio
+ between readahead and backbuffer sizes.
``--cache-default=<kBytes|no>``
- Set the size of the cache in kilobytes (default: 150000 KB). Using ``no``
+ Set the size of the cache in kilobytes (default: 75000 KB). Using ``no``
will not automatically enable the cache e.g. when playing from a network
stream. Note that using ``--cache`` will always override this option.
@@ -3014,6 +3134,12 @@ Cache
on the situation, either of these might be slower than the other method.
This option allows control over this.
+``--cache-backbuffer=<kBytes>``
+ Size of the cache back buffer (default: 75000 KB). This will add to the total
+ cache size, and reserved the amount for seeking back. The reserved amount
+ will not be used for readahead, and instead preserves already read data to
+ enable fast seeking back.
+
``--cache-file=<TMP|path>``
Create a cache file on the filesystem.
@@ -3128,7 +3254,7 @@ Network
network transport when playing ``rtsp://...`` URLs. The value ``lavf``
leaves the decision to libavformat.
-``--hls-bitrate=<no|min|max>``
+``--hls-bitrate=<no|min|max|<rate>>``
If HLS streams are played, this option controls what streams are selected
by default. The option allows the following parameters:
@@ -3137,6 +3263,9 @@ Network
:min: Pick the streams with the lowest bitrate.
:max: Same, but highest bitrate. (Default.)
+ Additionally, if the option is a number, the stream with the highest rate
+ equal or below the option value is selected.
+
The bitrate as used is sent by the server, and there's no guarantee it's
actually meaningful.
@@ -3262,6 +3391,72 @@ Miscellaneous
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.
+``--video-sync=<audio|...>``
+ How the player synchronizes audio and video.
+
+ The modes starting with ``display-`` try to output video frames completely
+ synchronously to the display, using the detected display vertical refresh
+ rate as a hint how fast frames will be displayed on average. These modes
+ change video speed slightly to match the display. See ``--video-sync-...``
+ options for fine tuning. The robustness of this mode is further reduced by
+ making a some idealized assumptions, which may not always apply in reality.
+ Behavior can depend on the VO and the system's video and audio drivers.
+ Media files must use constant framerate. Section-wise VFR might work as well
+ with some container formats (but not e.g. mkv). If the sync code detects
+ severe A/V desync, or the framerate cannot be detected, the player
+ automatically reverts to ``audio`` mode for some time or permanently.
+
+ The modes with ``desync`` in their names do not attempt to keep audio/video
+ in sync. They will slowly (or quickly) desync, until e.g. the next seek
+ happens. These modes are meant for testing, not serious use.
+
+ :audio: Time video frames to audio. This is the most robust
+ mode, because the player doesn't have to assume anything
+ about how the display behaves. The disadvantage is that
+ it can lead to occasional frame drops or repeats. If
+ audio is disabled, this uses the system clock. This is
+ the default mode.
+ :display-resample: Resample audio to match the video. This mode will also
+ try to adjust audio speed to compensate for other drift.
+ (This means it will play the audio at a different speed
+ every once in a while to reduce the A/V difference.)
+ :display-resample-vdrop: Resample audio to match the video. Drop video
+ frames to compensate for drift.
+ :display-resample-desync: Like the previous mode, but no A/V compensation.
+ :display-vdrop: Drop or repeat video frames to compensate desyncing
+ video. (Although it should have the same effects as
+ ``audio``, the implementation is very different.)
+ :display-desync: Sync video to display, and let audio play on its own.
+ :desync: Sync video according to system clock, and let audio play
+ on its own.
+
+``--video-sync-max-video-change=<value>``
+ Maximum speed difference in percent that is applied to video with
+ ``--video-sync=display-...`` (default: 1). Display sync mode will be
+ disabled if the monitor and video refresh way do not match within the
+ given range. It tries multiples as well: playing 30 fps video on a 60 Hz
+ screen will duplicate every second frame. Playing 24 fps video on a 60 Hz
+ screen will play video in a 2-3-2-3-... pattern.
+
+ The default settings are not loose enough to speed up 23.976 fps video to
+ 25 fps. We consider the pitch change too extreme to allow this behavior
+ by default. Set this option to a value of ``5`` to enable it.
+
+ Note that in the ``--video-sync=display-resample`` mode, audio speed will
+ additionally be changed by a small amount if necessary for A/V sync. See
+ ``--video-sync-max-audio-change``.
+
+``--video-sync-max-audio-change=<value>``
+ Maximum *additional* speed difference in percent that is applied to audio
+ with ``--video-sync=display-...`` (default: 0.125). Normally, the player
+ play the audio at the speed of the video. But if the difference between
+ audio and video position is too high, e.g. due to drift or other timing
+ errors, it will attempt to speed up or slow down audio by this additional
+ factor. Too low values could lead to video frame dropping or repeating if
+ the A/V desync cannot be compensated, too high values could lead to chaotic
+ frame dropping due to the audio "overshooting" and skipping multiple video
+ frames before the sync logic can react.
+
``--mf-fps=<value>``
Framerate used when decoding from multiple PNG or JPEG files with ``mf://``
(default: 1).
@@ -3309,7 +3504,7 @@ Miscellaneous
you should not need to change this option.
:decoder: Use decoder reordering functionality. Unlike in classic MPlayer
- and mplayer2, this includes a dTS fallback. (Default.)
+ and mplayer2, this includes a DTS fallback. (Default.)
:sort: Maintain a buffer of unused pts values and use the lowest value
for the frame.
:auto: Try to pick a working mode from the ones above automatically.
@@ -3317,25 +3512,7 @@ Miscellaneous
You can also try to use ``--no-correct-pts`` for files with completely
broken timestamps.
-``--media-title=<string>``
+``--force-media-title=<string>``
Force the contents of the ``media-title`` property to this value. Useful
for scripts which want to set a title, without overriding the user's
setting in ``--title``.
-
-``--slave-broken``
- Switches on the old slave mode. This is for testing only, and incompatible
- to the removed ``--slave`` switch.
-
- .. attention::
- Changes incompatible to slave mode applications have been made. In
- particular, the status line output was changed, which is used by some
- applications to determine the current playback position. This switch
- has been renamed to prevent these applications from working with this
- version of mpv, because it would lead to buggy and confusing behavior
- only. Moreover, the slave mode protocol is so horribly bad that it
- should not be used for new programs, nor should existing programs
- attempt to adapt to the changed output and use the ``--slave-broken``
- switch. Instead, a new, saner protocol should be developed (and will be,
- if there is enough interest).
-
- This affects most third-party GUI frontends.
diff --git a/DOCS/man/osc.rst b/DOCS/man/osc.rst
index 4978964..f4f8d66 100644
--- a/DOCS/man/osc.rst
+++ b/DOCS/man/osc.rst
@@ -5,7 +5,7 @@ The On Screen Controller (short: OSC) is a minimal GUI integrated with mpv to
offer basic mouse-controllability. It is intended to make interaction easier
for new users and to enable precise and direct seeking.
-The OSC is enabled by default if mpv was compiled with lua support. It can be
+The OSC is enabled by default if mpv was compiled with Lua support. It can be
disabled entirely using the ``--osc=no`` option.
Using the OSC
@@ -235,6 +235,14 @@ Configurable Options
| Default: slider
| Sets the style of the seekbar, slider (diamond marker) or bar (fill)
+``timetotal``
+ | Default: no
+ | Show total time instead of time remaining
+
+``timems``
+ | Default: no
+ | Display timecodes with milliseconds
+
Script Commands
~~~~~~~~~~~~~~~
diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst
index 91632cf..6479560 100644
--- a/DOCS/man/vf.rst
+++ b/DOCS/man/vf.rst
@@ -97,7 +97,7 @@ Available filters are:
All parameters are optional.
- ``<w>,<h>``
+ ``<w>:<h>``
scaled width/height (default: original width/height)
:0: scaled d_width/d_height
@@ -152,7 +152,7 @@ Available filters are:
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
just affects what later scalers (software or hardware) will do when
- auto-scaling to correct aspect.
+ auto-scaling to the correct aspect.
``<w>,<h>``
New display width and height.
@@ -518,7 +518,7 @@ Available filters are:
video. The main purpose of setting ``mp`` to a chroma plane is to reduce
CPU load and make pullup usable in realtime on slow machines.
-``yadif=[mode[:enabled=yes|no]]``
+``yadif=[mode:interlaced-only]``
Yet another deinterlacing filter
``<mode>``
@@ -527,10 +527,10 @@ Available filters are:
:frame-nospatial: Like ``frame`` but skips spatial interlacing check.
:field-nospatial: Like ``field`` but skips spatial interlacing check.
- ``<enabled>``
- :yes: Filter is active (default).
- :no: Filter is not active, but can be activated with the ``D`` key
- (or any other key that toggles the ``deinterlace`` property).
+ ``<interlaced-only>``
+ :no: Deinterlace all frames (default).
+ :yes: Only deinterlace frames marked as interlaced (default if this
+ filter is inserted via ``deinterlace`` property).
This filter, is automatically inserted when using the ``D`` key (or any
other key that toggles the ``deinterlace`` property or when using the
@@ -669,6 +669,8 @@ Available filters are:
Loads an external library to filter the image. The library interface
is the ``vf_dlopen`` interface specified using ``libmpcodecs/vf_dlopen.h``.
+ .. warning:: This filter is deprecated.
+
``dll=<library>``
Specify the library to load. This may require a full file system path
in some cases. This argument is required.
@@ -778,7 +780,7 @@ Available filters are:
``vapoursynth-lazy``
The same as ``vapoursynth``, but doesn't load Python scripts. Instead, a
custom backend using Lua and the raw VapourSynth API is used. The syntax
- is completely different, and absolutely no conveniencve features are
+ is completely different, and absolutely no convenience features are
provided. There's no type checking either, and you can trigger crashes.
.. admonition:: Example:
@@ -820,6 +822,10 @@ Available filters are:
depends on the GPU hardware, the GPU drivers, driver bugs, and
mpv bugs.
+ ``<interlaced-only>``
+ :no: Deinterlace all frames.
+ :yes: Only deinterlace frames marked as interlaced (default).
+
``vdpaupp``
VDPAU video post processing. Works with ``--vo=vdpau`` and ``--vo=opengl``
only. This filter is automatically inserted if deinterlacing is requested
@@ -864,12 +870,21 @@ Available filters are:
``pullup``
Try to apply inverse telecine, needs motion adaptive temporal
deinterlacing.
+ ``interlaced-only=<yes|no>``
+ If ``yes`` (default), only deinterlace frames marked as interlaced.
``hqscaling=<0-9>``
0
Use default VDPAU scaling (default).
1-9
Apply high quality VDPAU scaling (needs capable hardware).
+``vdpaurb``
+ VDPAU video read back. Works with ``--vo=vdpau`` and ``--vo=opengl`` only.
+ This filter will read back frames decoded by VDPAU so that other filters,
+ which are not normally compatible with VDPAU, can be used like normal.
+ This filter must be specified before ``vdpaupp`` in the filter chain if
+ ``vdpaupp`` is used.
+
``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
diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst
index 28e54f0..ef4a677 100644
--- a/DOCS/man/vo.rst
+++ b/DOCS/man/vo.rst
@@ -69,11 +69,11 @@ Available video output drivers are:
``no-colorkey``
Disables color-keying.
-``x11`` (X11 only)
- Shared memory video output driver without hardware acceleration that works
- whenever X11 is present.
-
- .. note:: This is a fallback only, and should not be normally used.
+ ``buffers=<number>``
+ Number of image buffers to use for the internal ringbuffer (default: 2).
+ Increasing this will use more memory, but might help with the X server
+ not responding quickly enough if video FPS is close to or higher than
+ the display refresh rate.
``vdpau`` (X11 only)
Uses the VDPAU interface to display and optionally also decode video.
@@ -336,6 +336,9 @@ Available video output drivers are:
exchange for adding some blur. This filter is good at temporal
interpolation, and also known as "smoothmotion" (see ``tscale``).
+ ``custom``
+ A user-defined custom shader (see ``scale-shader``).
+
There are some more filters, but most are not as useful. For a complete
list, pass ``help`` as value, e.g.::
@@ -452,6 +455,11 @@ Available video output drivers are:
Unfortunately, this can lead to flicker on LCD displays, since these
have a high reaction time.
+ ``temporal-dither-period=<1-128>``
+ Determines how often the dithering pattern is updated when
+ ``temporal-dither`` is in use. 1 (the default) will update on every
+ video frame, 2 on every other frame, etc.
+
``debug``
Check for OpenGL errors, i.e. call ``glGetError()``. Also request a
debug OpenGL context (which does nothing with current graphics drivers
@@ -494,10 +502,15 @@ Available video output drivers are:
for ``tscale`` are separable convolution filters (use ``tscale=help``
to get a list). The default is ``oversample``.
- Note that the maximum supported filter radius is currently 3, and that
- using filters with larger radius may introduce issues when pausing or
- framestepping, proportional to the radius used. It is recommended to
- stick to a radius of 1 or 2.
+ Note that the maximum supported filter radius is currently 3, due to
+ limitations in the number of video textures that can be loaded
+ simultaneously.
+
+ ``tscale-clamp``
+ Clamp the ``tscale`` filter kernel's value range to [0-1]. This reduces
+ excessive ringing artifacts in the temporal domain (which typically
+ manifest themselves as short flashes or fringes of black, mostly
+ around moving edges) in exchange for potentially adding more blur.
``dscale-radius``, ``cscale-radius``, ``tscale-radius``, etc.
Set filter parameters for ``dscale``, ``cscale`` and ``tscale``,
@@ -506,10 +519,8 @@ Available video output drivers are:
See the corresponding options for ``scale``.
``linear-scaling``
- Scale in linear light. This is automatically enabled if
- ``target-prim``, ``target-trc``, ``icc-profile`` or
- ``sigmoid-upscaling`` is set. It should only be used with a
- ``fbo-format`` that has at least 16 bit precision.
+ Scale in linear light. It should only be used with a ``fbo-format``
+ that has at least 16 bit precision.
``fancy-downscaling``
When using convolution based filters, extend the filter size
@@ -519,9 +530,73 @@ Available video output drivers are:
feature doesn't work correctly with different scale factors in
different directions.
+ ``source-shader=<file>``, ``scale-shader=<file>``, ``pre-shaders=<files>``, ``post-shaders=<files>``
+ Custom GLSL fragment shaders.
+
+ source-shader
+ This gets applied directly onto the source planes, before
+ any sort of upscaling or conversion whatsoever. For YCbCr content,
+ this means it gets applied on the luma and chroma planes
+ separately. In general, this shader shouldn't be making any
+ assumptions about the colorspace. It could be RGB, YCbCr, XYZ or
+ something else entirely. It's used purely for fixing numerical
+ quirks of the input, eg. debanding or deblocking.
+ pre-shaders (list)
+ These get applied after conversion to RGB and before linearization
+ and upscaling. Operates on non-linear RGB (same as input). This is
+ the best place to put things like sharpen filters.
+ scale-shader
+ This gets used instead of scale/cscale when those options are set
+ to ``custom``. The colorspace it operates on depends on the values
+ of ``linear-scaling`` and ``sigmoid-upscaling``, so no assumptions
+ should be made here.
+ post-shaders (list)
+ These get applied after upscaling and subtitle blending (when
+ ``blend-subtitles`` is enabled), but before color management.
+ Operates on linear RGB if ``linear-scaling`` is in effect,
+ otherwise non-linear RGB. This is the best place for colorspace
+ transformations (eg. saturation mapping).
+
+ These files must define a function with the following signature::
+
+ vec4 sample(sampler2D tex, vec2 pos, vec2 tex_size)
+
+ The meanings of the parameters are as follows:
+
+ sampler2D tex
+ The source texture for the shader.
+ vec2 pos
+ The position to be sampled, in coordinate space [0-1].
+ vec2 tex_size
+ The size of the texture, in pixels. This may differ from image_size,
+ eg. for subsampled content or for post-shaders.
+
+ In addition to these parameters, the following uniforms are also
+ globally available:
+
+ float random
+ A random number in the range [0-1], different per frame.
+ int frame
+ A simple count of frames rendered, increases by one per frame and
+ never resets (regardless of seeks).
+ vec2 image_size
+ The size in pixels of the input image.
+ float cmul (source-shader only)
+ The multiplier needed to pull colors up to the right bit depth. The
+ source-shader must multiply any sampled colors by this, in order
+ to normalize them to the full scale.
+
+ For example, a shader that inverts the colors could look like this::
+
+ vec4 sample(sampler2D tex, vec2 pos, vec2 tex_size)
+ {
+ vec4 color = texture(tex, pos);
+ return vec4(1.0 - color.rgb, color.a);
+ }
+
``sigmoid-upscaling``
When upscaling, use a sigmoidal color transform to avoid emphasizing
- ringing artifacts. This also enables ``linear-scaling``.
+ ringing artifacts. This also implies ``linear-scaling``.
``sigmoid-center``
The center of the sigmoid curve used for ``sigmoid-upscaling``, must
@@ -531,10 +606,6 @@ Available video output drivers are:
The slope of the sigmoid curve used for ``sigmoid-upscaling``, must
be a float between 1.0 and 20.0. Defaults to 6.5 if not specified.
- ``no-npot``
- Force use of power-of-2 texture sizes. For debugging only.
- Borders will be distorted due to filtering.
-
``glfinish``
Call ``glFinish()`` before and after swapping buffers (default: disabled).
Slower, but might help getting better results when doing framedropping.
@@ -555,7 +626,8 @@ Available video output drivers are:
full screen).
This may help getting more consistent frame intervals, especially with
high-fps clips - which might also reduce dropped frames. Typically a
- value of 1 should be enough since full screen may bypass the DWM.
+ value of ``windowed`` should be enough since full screen may bypass the
+ DWM.
Windows only.
@@ -573,12 +645,15 @@ Available video output drivers are:
Cocoa/OS X
win
Win32/WGL
- x11, x11es
- X11/GLX (the ``es`` variant forces GLES)
+ x11
+ X11/GLX
wayland
Wayland/EGL
- x11egl, x11egles
- X11/EGL (the ``es`` variant forces GLES)
+ x11egl
+ X11/EGL
+
+ ``es``
+ Force or prefer GLES2/3 over desktop OpenGL, if supported.
``fbo-format=<fmt>``
Selects the internal format of textures used for FBOs. The format can
@@ -660,8 +735,7 @@ Available video output drivers are:
``icc-profile=<file>``
Load an ICC profile and use it to transform linear RGB to screen output.
Needs LittleCMS 2 support compiled in. This option overrides the
- ``target-prim`` and ``target-trc`` options. It also enables
- ``linear-scaling``.
+ ``target-prim``, ``target-trc`` and ``icc-profile-auto`` options.
``icc-profile-auto``
Automatically select the ICC display profile currently specified by
@@ -669,11 +743,14 @@ Available video output drivers are:
NOTE: Only implemented on OS X and X11
- ``icc-cache=<file>``
- Store and load the 3D LUT created from the ICC profile in this file.
+ ``icc-cache-dir=<dirname>``
+ Store and load the 3D LUTs created from the ICC profile in this directory.
This can be used to speed up loading, since LittleCMS 2 can take a while
- to create the 3D LUT. Note that this file contains an uncompressed LUT.
- Its size depends on the ``3dlut-size``, and can be very big.
+ to create a 3D LUT. Note that these files contain uncompressed LUTs.
+ Their size depends on the ``3dlut-size``, and can be very big.
+
+ NOTE: This is not cleaned automatically, so old, unused cache files
+ may stick around indefinitely.
``icc-intent=<value>``
Specifies the ICC intent used for the color transformation (when using
@@ -697,8 +774,8 @@ Available video output drivers are:
Blend subtitles directly onto upscaled video frames, before
interpolation and/or color management (default: no). Enabling this
causes subtitles to be affected by ``icc-profile``, ``target-prim``,
- ``target-trc``, ``interpolation``, ``gamma`` and ``linear-scaling``.
- It also increases subtitle performance when using ``interpolation``.
+ ``target-trc``, ``interpolation``, ``gamma`` and ``post-shader``. It
+ also increases subtitle performance when using ``interpolation``.
The downside of enabling this is that it restricts subtitles to the
visible portion of the video, so you can't have subtitles exist in the
@@ -744,7 +821,7 @@ Available video output drivers are:
This is equivalent to::
- --vo=opengl:scale=spline36:cscale=spline36:dscale=mitchell:dither-depth=auto:fancy-downscaling:sigmoid-upscaling
+ --vo=opengl:scale=spline36:cscale=spline36:dscale=mitchell:dither-depth=auto:fancy-downscaling:sigmoid-upscaling:pbo
Note that some cheaper LCDs do dithering that gravely interferes with
``opengl``'s dithering. Disabling dithering with ``dither-depth=no`` helps.
@@ -816,6 +893,12 @@ Available video output drivers are:
``null``
Produces no video output. Useful for benchmarking.
+ Usually, it's better to disable video with ``--no-video`` instead.
+
+ ``fps=<value>``
+ Simulate display FPS. This artificially limits how many frames the
+ VO accepts per second.
+
``caca``
Color ASCII art video output driver that works on a text console.
@@ -895,13 +978,15 @@ Available video output drivers are:
``frame-queue-size=<1..100>``
The maximum count of frames which the frame queue can hold (default: 1)
- ``frame-drop-mode=<pop|clear>``
+ ``frame-drop-mode=<pop|clear|block>``
Select the behavior when the frame queue is full.
pop
- Drop the oldest frame in the frame queue. (default)
+ Drop the oldest frame in the frame queue.
clear
Drop all frames in the frame queue.
+ block
+ Wait for a short time, behave like ``clear`` on timeout. (default)
This also supports many of the suboptions the ``opengl`` VO has. Run
``mpv --vo=opengl-cb:help`` for a list.
@@ -921,6 +1006,11 @@ Available video output drivers are:
selected layer, to handle the window background and OSD. Actual video
rendering will happen on the layer above the selected layer.
+ ``background=<yes|no>``
+ Whether to render a black background behind the video (default: no).
+ Normally it's better to kill the console framebuffer instead, which
+ gives better performance.
+
``drm`` (Direct Rendering Manager)
Video output driver using Kernel Mode Setting / Direct Rendering Manager.
Does not support hardware acceleration. Should be used when one doesn't
@@ -933,3 +1023,7 @@ Available video output drivers are:
``devpath=<filename>``
Path to graphic card device.
(default: /dev/dri/card0)
+
+ ``mode=<number>``
+ Mode ID to use (resolution, bit depth and frame rate).
+ (default: 0)
diff --git a/DOCS/mplayer-changes.rst b/DOCS/mplayer-changes.rst
index 84f7e22..8f29baa 100644
--- a/DOCS/mplayer-changes.rst
+++ b/DOCS/mplayer-changes.rst
@@ -52,6 +52,7 @@ Input
Also see: http://github.com/mpv-player/mpv/wiki/IR-remotes
* Joystick support was removed. It was considered useless and was the cause
of some problems (e.g. a laptop's accelerator being recognized as joystick).
+* Support for relative seeking by percentage.
Audio
~~~~~
@@ -93,6 +94,8 @@ Video
* Image subtitles (DVDs etc.) are rendered in color and use more correct
positioning (color for image subs can be disabled with ``--sub-gray``).
+* Support for the X11 video output is removed, since it was considered
+ deprecated. SDL video output can still be used as a fallback.
OSD and terminal
~~~~~~~~~~~~~~~~
@@ -276,7 +279,7 @@ Command Line Switches
``-xineramascreen`` ``--screen`` (different values)
``-xy W`` ``--autofit=W``
``-zoom`` Inverse available as ``--video-unscaled``
- ``dvdnav://`` ``dvdnav://menu``
+ ``dvdnav://`` Removed.
``dvd://1`` ``dvd://0`` (0-based offset)
=========================== ========================================
@@ -396,7 +399,8 @@ Slave mode
Assuming the system supports ``/dev/stdin``.
- (The option was readded in 0.5.1 and sets exactly these options.)
+ (The option was added back in 0.5.1 and sets exactly these options. It was
+ removed in 0.10.x again.)
* A JSON RPC protocol giving access to the client API is also supported. See
`JSON IPC`_ for more information.
diff --git a/DOCS/tech-overview.txt b/DOCS/tech-overview.txt
index fd6a7a1..914b222 100644
--- a/DOCS/tech-overview.txt
+++ b/DOCS/tech-overview.txt
@@ -75,8 +75,7 @@ talloc.h & talloc.c:
allocation call will never return NULL.
One very useful feature of talloc is fast tracking of memory leaks. ("Fast"
- as in it doesn't require valgrind.) You can enable it by passing the option
- --leak-report as first parameter, or better, setting the
+ as in it doesn't require valgrind.) You can enable it by setting the
MPV_LEAK_REPORT environment variable to "1":
export MPV_LEAK_REPORT=1
This will list all unfree'd allocations on exit.
diff --git a/DOCS/waf-buildsystem.rst b/DOCS/waf-buildsystem.rst
index fa71cbb..adb1bc2 100644
--- a/DOCS/waf-buildsystem.rst
+++ b/DOCS/waf-buildsystem.rst
@@ -1,7 +1,7 @@
waf build system overview
=========================
-mpv's new build system is based on waf and it should completly replace the
+mpv's new build system is based on waf and it should completely replace the
custom ./configure + Makefile based system inherited from MPlayer.
Goals and the choice of waf
@@ -65,7 +65,7 @@ This defines a feature called ``vdpau`` which can be enabled or disabled by
the users with configure flags (that's the meaning of ``--``). This feature
depends on another feature whose name is ``x11``, and the autodetection check
consists of running ``pkg-config`` and looking for ``vdpau`` with version
-``>= 0.2``. If the check succeds a ``#define HAVE_VDPAU 1`` will be added to
+``>= 0.2``. If the check succeeds a ``#define HAVE_VDPAU 1`` will be added to
``config.h``, if not ``#define HAVE_VDPAU 0`` will be added.
The defines names are automatically prepended with ``HAVE_``, capitalized and
diff --git a/README.md b/README.md
index bb3dd71..11d3252 100644
--- a/README.md
+++ b/README.md
@@ -57,9 +57,6 @@ To build the software you can use `./waf build`: the result of the compilation
will be located in `build/mpv`. You can use `./waf install` to install mpv
to the *prefix* after it is compiled.
-NOTE: Using the old build system (with `./old-configure`) should still work,
-but will be removed in a future version of mpv.
-
Essential dependencies (incomplete list):
- gcc or clang
@@ -174,11 +171,9 @@ list of changes is located [here][mplayer-changes].
Most activity happens on the IRC channel and the github issue tracker. The
mailing lists are mostly unused.
- - **Github issue tracker**: [issue tracker][issue-tracker] (report bugs here)
+ - **GitHub issue tracker**: [issue tracker][issue-tracker] (report bugs here)
- **User IRC Channel**: `#mpv` on `irc.freenode.net`
- **Developer IRC Channel**: `#mpv-devel` on `irc.freenode.net`
- - **Users Mailing List**: `mpv-users@googlegroups.com` ([Archive / Subscribe][mpv-users]).
- - **Devel Mailing List**: `mpv-devel@googlegroups.com` ([Archive / Subscribe][mpv-devel])
To contact the `mpv` team in private write to `mpv-team@googlegroups.com`. Use
only if discretion is required.
@@ -187,8 +182,6 @@ only if discretion is required.
[mpv-build]: https://github.com/mpv-player/mpv-build
[homebrew-mpv]: https://github.com/mpv-player/homebrew-mpv
[issue-tracker]: https://github.com/mpv-player/mpv/issues
-[mpv-users]: https://groups.google.com/forum/?hl=en#!forum/mpv-users
-[mpv-devel]: https://groups.google.com/forum/?hl=en#!forum/mpv-devel
[ffmpeg_vs_libav]: https://github.com/mpv-player/mpv/wiki/FFmpeg-versus-Libav
[release-policy]: https://github.com/mpv-player/mpv/blob/master/DOCS/release-policy.md
[windows_compilation]: https://github.com/mpv-player/mpv/blob/master/DOCS/compile-windows.md
diff --git a/RELEASE_NOTES b/RELEASE_NOTES
index ce89edf..ed6efbe 100644
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -1,3 +1,288 @@
+Release 0.10.0
+==============
+
+
+Features
+--------
+
+New
+~~~
+
+- uchardet support
+- Matroska: reading cue sheets embedded in tags
+- Support for VideoToolbox hardware decoding
+- Display sync mode (--display-sync)
+- --force-window=immediate mode
+- fd:// protocol
+- libarchive wrapper for reading compressed archives
+- TOOLS/lua: zones.lua
+- Support for the "new" libavcodec VDPAU API
+- vf_vdpaurb, Add a new filter for reading back VDPAU decoded frames
+- DXVA2: HEVC support
+- Enabled HEVC profiles with VA API
+- HEVC added to whitelist of hwdec codecs
+- vo_null: framerate emulation
+- vo_opengl: support for custom shaders
+- vo_opengl: temporal-dither-period option
+- vo_opengl: tscale-clamp option
+- vo_opengl: option to attach target-prim/target-csp to window screenshots
+- vo_opengl_cb: "block" framedrop mode (now default)
+- vo_opengl_cb: support for interpolation
+- vo_vdpau: rotation support
+- ytdl_hook: support for 'multi_video' results
+
+
+Removed
+~~~~~~~
+
+- af_convert24 (af_lavrresample does this now)
+- af_dummy
+- audio: S8, U16, U24, U32 formats
+- DVD and BD menu support
+- TOOLS: youtube-starttime.lua
+- VA API: compatibility crap (< 0.34.0) and vo_vaapi deinterlacer
+- vo_x11
+
+
+Deprecated
+~~~~~~~~~~
+
+- vf_dlopen
+
+
+Behavior
+--------
+
+- audio: softvol scale is now cubic
+- cache: readahead size is limited to half the cache size at the beginning
+- charset_conv: "auto" encoding detection now prefers uchardet
+- demux_playlist: skip hidden directories
+- input.conf: O toggles between 2 states only
+- input.conf: Ctrl+s key binding for window screenshots
+- input.conf: mouse volume control (horizontal scrolling) inverted
+- input.conf: L to toggle infinite looping
+- input.conf: remap d/D keys (dropped framedrop cycle, replaced with deinterlacing toggle)
+- player: disabled seeking on unseekable streams even if the cache is enabled
+- player: parses and exposes m3u playlist titles
+- player: --term-playing-msg is now in a separate log category
+- player: removed automatic DVB channel advancement on no data
+- player: now restores video-aspect on playback resume
+- player: now uses exit code 0 by default for quit, 4 for signals, etc.
+- player: warns against using HLS URLs with --playlist
+- screenshots: changed the default directory in pseudo-gui mode to desktop
+- screenshots: screenshot directory is now created automatically
+- screenshots: default template is now prefixed with "mpv-"
+- TOOLS/lua/autoload: adds all files on start
+- vo: vo_wayland moved up in autoprobe list
+- vo_opengl: enabled pbo by default with opengl-hq
+- vo_opengl: cache dir for ICC profiles is now created automatically
+- w32: shift drag and drop appends
+- x11: shift drag and drop appends
+
+
+Options and Commands
+--------------------
+
+Added
+~~~~~
+
+- af_volume: replaygain-fallback option
+- ao_coreaudio: change-physical-format option
+- ao_coreaudio: exclusive option
+- ao_null: channel-layouts option for testing channel layout selection
+- audio: --audio-spdif as new method for enabling passthrough
+- cache: --cache-backbuffer to configure cache backbuffer size
+- command: define-section command for defining input bindings
+- command: audio-params and audio-out-params properties
+- command: keypress, keydown, and keyup commands
+- command: playlist_shuffle command
+- command: option-info/N/set-locally property indicating per-file options
+- command: protocol-list property
+- command: track-list/N/audio-channels property
+- demux: --demuxer-max-packets and --demuxer-max-bytes options to control maximum queue size
+- input: relative percentage seek
+- osc: time display configuration options
+- player: --playlist-pos option
+- screenshots: --screenshot-jpeg-source-chroma option to disable JPEG 4:4:4 output
+- screenshots: --screenshot-high-bit-depth option to allow or disallow 16 bit output
+- screenshots: --screenshot-directory option
+- sub: --stretch-image-subs-to-screen option for stretching image subtitles to screen
+- TOOLS/stats-conv: allow passing regex via command line
+- video: --video-aspect-method option to configure container vs. bitstream aspect ratio
+- vo_drm: mode suboption to set the mode ID to use
+- vo_opengl_cb, vo_opengl: --hwdec-preload option for preloading hwdec context
+- vo_rpi: background disabled by default
+- vo_xv: buffers suboption to configure number of buffers
+- win32: portable config mode
+
+
+Changed
+~~~~~~~
+
+- audio: changed the range of the volume option/property (0 is still silence, and 100 now always means unchanged volume)
+- command: allow changing deinterlace property any time
+- command: allow changing track properties while no file is loaded
+- command: always make video-aspect property accessible
+- command: better choice when to allow playback-related commands
+- command: change OSD symbol for absolute perc. seek
+- command: change the default action for rescan_external_files
+- command: change the hwdec properties
+- command: define-section with empty contents removes a section
+- command: export stereo 3D tags
+- command: make auto-deinterlacing output at field rate
+- command: make deinterlace property use interlaced-only yadif mode
+- command: make property event mask matching more restrictive
+- command: make the playback-time property writable
+- input: allow - as separator between commands, instead of _
+- options: --loop without argument means looping forever
+- options: make keyvalue list parsing less strict
+- player: extend --hls-bitrate option
+- vf_yadif: expose interlaced frame mode
+- video: --video-stereo-mode=no to disable automatic stereo conversion
+- vo_opengl_cb, vo_opengl: --hwdec-preload for preloading hwdec context
+- vo_opengl: replace icc-cache with icc-cache-dir
+- vo_opengl: icc-profile overrides icc-profile-auto
+
+
+Renamed
+~~~~~~~
+
+- command: rename audio-format property to audio-codec-name
+- options: rename --media-title option to --force-media-title
+- vo_opengl: rename use_full_range to use_normalized_range
+
+
+Deprecated
+~~~~~~~~~~
+
+- --ad-spdif-dtshd (use --audio-spdif=dts-hd)
+- audio-samplerate property
+- length property
+
+
+Removed
+~~~~~~~
+
+- get_property command
+- --demuxer-readahead-packets and --demuxer-readahead-bytes
+- image_writer: don't use jpeg baseline, and remove useless jpeg-optimize and jpeg-baseline options
+- --leak-report
+- --slave-broken
+- vo_opengl: npot suboption
+
+
+Fixes and Minor Enhancements
+----------------------------
+
+- ad_spdif: use DTS-HD passthrough only if the audio is really DTS-HD
+- af: fix behavior with filter chains that require a large number of auto-inserted conversion filters
+- af_lavcac3enc: fix A/V sync
+- ao_alsa: accept 7.1 over HDMI
+- ao_alsa: refuse to use spdif if AES flags can't be set
+- ao_wasapi: fix crash on hotplug init error
+- audio: avoid wasting CPU due to continuous wakeup
+- audio: do not exit when loading small files in paused mode
+- audio: fix channel map fallback selection
+- audio: fix crash on uninit
+- audio: fix --end handling
+- audio: fix EOF state with --keep-open
+- audio: fix restoring volume
+- charset_conv: fix switched parameters
+- charset_conv: use our own UTF-8 check with ENCA only
+- cocoa: don't load hardcoded icon if running from bundle
+- cocoa: hide cursor using a blank image instead of a system-wide API
+- command: do not exit playback if the B point of A-B loop is past EOF
+- command: fix audio-out-detected-device property
+- command: fix track property when no file is loaded
+- command: fix video-aspect property update notification
+- command: let track properties return option value in idle mode
+- demux: don't get stuck on some cases of timestamp resets
+- demux: handle Matroska-style replaygain tags as well
+- demux_lavf: do a better job at guessing the vobsub .sub filename
+- demux_mkv: disable ordered chapters if ChapterTimeEnd is missing
+- demux_mkv: discard broken index
+- demux_mkv: fix mpeg2 mapping
+- demux_mkv: ignore deprecated FrameRate, do not assume PAL
+- demux_mkv: improve video duration detection heuristic
+- demux_mkv: parse FLAC channel layouts
+- demux_playlist: make mime type comparison case-insensitive
+- dxva2: fix handling of cropped video
+- idet.sh: Support larger files
+- mp_image: fix vf_vdpaupp references
+- options: fix conversion of flags to strings
+- options: move program name to end of window title
+- options: remove the period at the end of "No file."
+- osc: completely disable if no VO window exists
+- osc: exit tick immediately if disabled
+- osc: reinit on playlist changes
+- osx: add NULL check for input context in a missing case
+- player: fix crashes when adding external tracks before loading main file
+- player: increase tick event update frequency
+- player: make decoding cover art more robust
+- player: raise maximum idle time
+- player: return better guess for playback time during seeks
+- player: show larger cache sizes in MB on status line
+- player: slim down A/V desync warning
+- sd_ass: assume negative durations are unknown durations, and handle them
+- terminal: disable terminal foreground state polling
+- terminal-unix: set terminal mode on init
+- timer: fix a corner case on clock changes
+- TOOLS: make autodeint detect telecine in parallel
+- TOOLS/zsh.pl: die loudly if mpv fails to run
+- vaapi: prefer direct display over copy-back
+- vaapi: fix some videos only showing up green
+- vaapi: treat cropped decoder output slightly more correctly
+- vda: add support for nv12 image formats
+- vd_lavc: fix a hw decoding fallback case
+- vf_stereo3d: drop internal implementation
+- vf_vavpp: don't attempt to deinterlace progressive frames
+- vf_vavpp: fix bob deinterlacing for bottom field first videos
+- vf_vdpaupp: Don't crash when evaluating interlacing of NULL mpi
+- video: always re-probe auto deint filter on filter reconfig
+- video: better heuristic for timestamp resets
+- video: fix panscan in vertical case
+- video: fix VideoToolbox/VDA autodetection
+- video: unbreak EOF with video-only files that have timestamp resets
+- vo_direct3d: fix broken pseudo GUI drag and drop hint
+- vo_drm: fix centering with regard to stride
+- vo_drm: fix crashes with --profile=pseudo-gui
+- vo_drm: fix resolution not restored after exiting
+- vo_drm: fix stride problem for certain devices
+- vo_drm: make VT switching non mandatory
+- vo: free frames before killing VO
+- vo_opengl: avoid broken shader if hwdec fails to provide textures
+- vo_opengl_cb: drop frames eagerly if frames are not rendered
+- vo_opengl: CMS no longer implies linear scaling
+- vo_opengl: fix alpha video in one case
+- vo_opengl: fix dangling pointers with vo_cmdline
+- vo_opengl: fix framestepping/pausing + interpolation
+- vo_opengl: fix "freezes" after seeking with interpolation on
+- vo_opengl: fix scale=oversample's threshold calculations
+- vo_opengl: framebuffers work under GLES 2
+- vo_opengl: improve robustness against PBO failure
+- vo_opengl: reimplement tscale=oversample
+- vo_opengl: reject future images in different formats
+- vo_opengl: X11: don't leak when GL init fails
+- vo: restore frame-drop logic for high-fps clips
+- vo_rpi: fix blackscreen before the first subtitle/OSD is rendered
+- vo_rpi, vo_opengl: do not globally terminate EGL on VO uninit
+- vo_sdl: fix glaring memory leak
+- vo_vdpau: check maximum video size
+- vo_vdpau: limit output surfaces to allowed maximum dimensions
+- win32: fix window resize logic
+- win32: fix crashes when changing system time
+- x11: Handle external fullscreen toggles
+- ytdl: catch bogus extractor info
+- ytdl: do not use deprecated media-title option
+- ytdl: don't print failure warning when youtube-dl was killed by us
+- ytdl: get start_time
+
+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.9.2..v0.10.0` in the git repository or by visiting
+https://github.com/mpv-player/mpv/compare/v0.9.2...v0.10.0.
+
+
Release 0.9.2
=============
@@ -39,8 +324,8 @@ Bug fixes
`libmpv` from creating the singleton.
This listing is not complete. A complete changelog can be seen by running
-`git log v0.9.1..` in the git repository or by visiting
-https://github.com/mpv-player/mpv/compare/v0.9.1...release/0.9.
+`git log v0.9.1..v0.9.2` in the git repository or by visiting
+https://github.com/mpv-player/mpv/compare/v0.9.1...v0.9.2.
Release 0.9.1
=============
diff --git a/TOOLS/idet.sh b/TOOLS/idet.sh
index e9214ef..ae7a361 100755
--- a/TOOLS/idet.sh
+++ b/TOOLS/idet.sh
@@ -47,7 +47,7 @@ judge()
progressive=$((progressive + progressive1))
undetermined=$((undetermined + undetermined1))
done <<EOF
-$(testfun "$@")
+$(testfun "$@" | sed 's/:/: /g')
EOF
interlaced=$((bff + tff))
diff --git a/TOOLS/lib/Parse/Matroska/Definitions.pm b/TOOLS/lib/Parse/Matroska/Definitions.pm
index adb13d0..a73c7b1 100644
--- a/TOOLS/lib/Parse/Matroska/Definitions.pm
+++ b/TOOLS/lib/Parse/Matroska/Definitions.pm
@@ -84,7 +84,7 @@ sub elem_by_hexid {
# used by elem when setting the 'valname' key
use constant TYPE_MAP => {
uint => 'uint64_t',
- str => 'struct bstr',
+ str => 'char *',
binary => 'struct bstr',
ebml_id => 'uint32_t',
float => 'double',
diff --git a/TOOLS/lua/autodeint.lua b/TOOLS/lua/autodeint.lua
index 80767f2..cde30db 100644
--- a/TOOLS/lua/autodeint.lua
+++ b/TOOLS/lua/autodeint.lua
@@ -2,21 +2,16 @@
-- appropriate deinterlacing filter based on a short section of the
-- currently playing video.
--
--- It registers the key-binding ctrl+d, which when pressed, inserts
--- the filter vf=lavfi=idet. After 4 second, it examines the results
--- to determine whether the content progressive or interlaced and the
--- interlacing field dominance. It immediately sets the latter within
--- mpv.
+-- It registers the key-binding ctrl+d, which when pressed, inserts the filters
+-- ``vf=lavfi=idet,pullup,vf=lavfi=idet``. After 4 seconds, it removes these
+-- filters and decides whether the content is progressive, interlaced, or
+-- telecined and the interlacing field dominance.
--
--- If the content is judged not to be progressive, it may be either
--- interlaced or telecined. To determine which one, the video is
--- seeked back to the detection starting point with the "pullup"
--- filter inserted. After another four seconds, if idet thinks that
--- the pulled up viedo is "progressive", then pullup is the correct
--- filter and this script leaves it in places while removing the idet
--- filter. Otherwise, the content plain interlaced and mpv's deinterlace
--- property is set. This will usually insert the yadif deinterlacing filter.
+-- Based on this information, it may set mpv's ``deinterlace`` property (which
+-- usually inserts the yadif filter), or insert the ``pullup`` filter if the
+-- content is telecined. It also sets mpv's ``field-dominance`` property.
--
+-- OPTIONS:
-- The default detection time may be overridden by adding
--
-- --script-opts=autodeint.detect_seconds=<number of seconds>
@@ -37,6 +32,7 @@ require "mp.msg"
script_name = mp.get_script_name()
detect_label = string.format("%s-detect", script_name)
pullup_label = string.format("%s", script_name)
+ivtc_detect_label = string.format("%s-ivtc-detect", script_name)
-- number of seconds to gather cropdetect data
detect_seconds = tonumber(mp.get_opt(string.format("%s.detect_seconds", script_name)))
@@ -69,30 +65,29 @@ function start_detect()
mp.set_property("deinterlace","no")
del_filter_if_present(pullup_label)
- percent_pos = mp.get_property("percent-pos")
-
-- insert the detection filter
- local cmd = string.format('vf add @%s:lavfi=graph="idet"', detect_label)
+ local cmd = string.format('vf add @%s:lavfi=graph="idet",@%s:pullup,@%s:lavfi=graph="idet"',
+ detect_label, pullup_label, ivtc_detect_label)
if not mp.command(cmd) then
- mp.msg.error("failed to insert detection filter")
+ mp.msg.error("failed to insert detection filters")
return
end
-- wait to gather data
- timer = mp.add_timeout(detect_seconds, judge_field_dominance)
+ timer = mp.add_timeout(detect_seconds, select_filter)
end
function stop_detect()
del_filter_if_present(detect_label)
+ del_filter_if_present(ivtc_detect_label)
timer = nil
- mp.set_property("playback-pos", percent_pos)
end
progressive, interlaced_tff, interlaced_bff, interlaced = 0, 1, 2, 3, 4
-function judge()
+function judge(label)
-- get the metadata
- local result = mp.get_property_native(string.format("vf-metadata/%s", detect_label))
+ local result = mp.get_property_native(string.format("vf-metadata/%s", label))
num_tff = tonumber(result["lavfi.idet.multiple.tff"])
num_bff = tonumber(result["lavfi.idet.multiple.bff"])
num_progressive = tonumber(result["lavfi.idet.multiple.progressive"])
@@ -100,10 +95,10 @@ function judge()
num_interlaced = num_tff + num_bff
num_determined = num_interlaced + num_progressive
- mp.msg.verbose("progressive = "..num_progressive)
- mp.msg.verbose("interlaced-tff = "..num_tff)
- mp.msg.verbose("interlaced-bff = "..num_bff)
- mp.msg.verbose("undetermined = "..num_undetermined)
+ mp.msg.verbose(label.." progressive = "..num_progressive)
+ mp.msg.verbose(label.." interlaced-tff = "..num_tff)
+ mp.msg.verbose(label.." interlaced-bff = "..num_bff)
+ mp.msg.verbose(label.." undetermined = "..num_undetermined)
if num_determined < num_undetermined then
mp.msg.warn("majority undetermined frames")
@@ -119,11 +114,12 @@ function judge()
end
end
-function judge_field_dominance()
- verdict = judge()
+function select_filter()
+ -- handle the first detection filter results
+ verdict = judge(detect_label)
if verdict == progressive then
- stop_detect()
mp.msg.info("progressive: doing nothing")
+ stop_detect()
return
elseif verdict == interlaced_tff then
mp.set_property("field-dominance", "top")
@@ -133,21 +129,8 @@ function judge_field_dominance()
mp.set_property("field-dominance", "auto")
end
- local cmd = string.format("vf pre @%s:pullup", pullup_label)
- if not mp.command(cmd) then
- mp.msg.error("failed to insert pullup filter")
- stop_detect()
- return
- end
-
- -- redo the detection with the pullup filter to see if it's telecined
- mp.set_property("percent-pos", percent_pos)
- timer = mp.add_timeout(detect_seconds, judge_ivtc)
-end
-
-function judge_ivtc()
- verdict = judge()
-
+ -- handle the ivtc detection filter results
+ verdict = judge(ivtc_detect_label)
if verdict == progressive then
mp.msg.info(string.format("telecinied with %s field dominance: using pullup", mp.get_property("field-dominance")))
stop_detect()
@@ -157,8 +140,6 @@ function judge_ivtc()
mp.set_property("deinterlace","yes")
stop_detect()
end
-
- return
end
mp.add_key_binding("ctrl+d", script_name, start_detect)
diff --git a/TOOLS/lua/autoload.lua b/TOOLS/lua/autoload.lua
index 0730397..2737418 100644
--- a/TOOLS/lua/autoload.lua
+++ b/TOOLS/lua/autoload.lua
@@ -4,8 +4,8 @@
-- alphabetically, and adds entries before and after the current file to
-- the internal playlist. (It stops if the it would add an already existing
-- playlist entry at the same position - this makes it "stable".)
--- Add at most 5 * 2 files when starting a file (before + after).
-MAXENTRIES = 5
+-- Add at most 5000 * 2 files when starting a file (before + after).
+MAXENTRIES = 5000
function Set (t)
local set = {}
@@ -47,6 +47,13 @@ function find_and_add_entries()
if #dir == 0 then
return
end
+ local pl_count = mp.get_property_number("playlist-count", 1)
+ if (pl_count > 1 and autoload == nil) or
+ (pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] == nil) then
+ return
+ else
+ autoload = true
+ end
local files = mputils.readdir(dir, "files")
if files == nil then
diff --git a/TOOLS/lua/status-line.lua b/TOOLS/lua/status-line.lua
index 48597c3..26ae8af 100644
--- a/TOOLS/lua/status-line.lua
+++ b/TOOLS/lua/status-line.lua
@@ -1,5 +1,7 @@
-- Rebuild the terminal status line as a lua script
-- Be aware that this will require more cpu power!
+-- Also, this is based on a rather old version of the
+-- builtin mpv status line.
-- Add a string to the status line
function atsl(s)
@@ -28,7 +30,7 @@ function update_status_line()
atsl(mp.get_property_osd("time-pos"))
atsl(" / ");
- atsl(mp.get_property_osd("length"));
+ atsl(mp.get_property_osd("duration"));
atsl(" (")
atsl(mp.get_property_osd("percent-pos", -1))
diff --git a/TOOLS/lua/youtube-starttime.lua b/TOOLS/lua/youtube-starttime.lua
deleted file mode 100644
index ea8e9ab..0000000
--- a/TOOLS/lua/youtube-starttime.lua
+++ /dev/null
@@ -1,34 +0,0 @@
---sets the startime of a youtube video as specified in the "t=HHhMMmSSs" part of the url
---NOTE: This might become obsolete once youtube-dl adds the functionality
-
-local msg = require 'mp.msg'
-
-function youtube_starttime()
- url = mp.get_property("path", "")
- start = 0
-
- if string.find(url, "youtu%.?be") and
- ((url:find("http://") == 1) or (url:find("https://") == 1)) then
- time = string.match(url, "[#&%?]t=%d*h?%d*m?%d+s?m?h?")
- --the time-string can start with #, & or ? followed by t= and the timing parameters
- --at least one number needs to be present after t=, followed by h, m, s or nothing (>implies s)
-
- if time then
- for pos in string.gmatch(time,"%d+%a?") do
- if string.match(pos,"%d+h") then --find out multiplier for
- multiplier = 60*60 --hours
- elseif string.match(pos,"%d+m") then
- multiplier = 60 --minutes
- else multiplier = 1 end --seconds
-
- start = start + (string.match(pos,"%d+") * multiplier)
- end
-
- msg.info("parsed '" .. time .. "' into '" .. start .. "' seconds")
- end
-
- mp.set_property("file-local-options/start",start)
- end
-end
-
-mp.add_hook("on_load", 50, youtube_starttime)
diff --git a/TOOLS/lua/zones.lua b/TOOLS/lua/zones.lua
new file mode 100644
index 0000000..5b4924c
--- /dev/null
+++ b/TOOLS/lua/zones.lua
@@ -0,0 +1,74 @@
+-- zones.lua: mpv script for handling commands depending on where the mouse pointer is at,
+-- mostly for mouse wheel handling, by configuring it via input.conf, e.g.:
+--
+-- Ported from avih's ( https://github.com/avih ) zones.js
+--
+-- Vertical positions can be top, middle, bottom or "*" to represent the whole column.
+-- Horizontal positions can be left, middle, bottom or "*" to represent the whole row.
+-- "default" will be the fallback command to be used if no command is assigned to that area.
+--
+-- input.conf example of use:
+-- # wheel up/down with mouse
+-- MOUSE_BTN3 script_message_to zones commands "middle-right: add brightness 1" "*-left: add volume 5" "default: seek 10"
+-- MOUSE_BTN4 script_message_to zones commands "middle-right: add brightness -1" "*-left: add volume -5" "default: seek -10"
+
+local ZONE_THRESH_PERCENTAGE = 20;
+-- sides get 20% each, mid gets 60%, same vertically
+
+local msg = mp.msg
+
+function getMouseZone()
+ -- returns the mouse zone as two strings [top/middle/bottom], [left/middle/right], e.g. "middle", "right"
+
+ local screenW, screenH = mp.get_osd_resolution()
+ local mouseX, mouseY = mp.get_mouse_pos()
+
+ local threshY = screenH * ZONE_THRESH_PERCENTAGE / 100
+ local threshX = screenW * ZONE_THRESH_PERCENTAGE / 100
+
+ local yZone = (mouseY < threshY) and "top" or (mouseY < (screenH - threshY)) and "middle" or "bottom"
+ local xZone = (mouseX < threshX) and "left" or (mouseX < (screenW - threshX)) and "middle" or "right"
+
+ return yZone, xZone
+end
+
+function main (...)
+ local arg={...}
+ msg.debug('commands: \n\t'..table.concat(arg,'\n\t'))
+
+ local keyY, keyX = getMouseZone()
+ msg.debug("mouse at: " .. keyY .. '-' .. keyX)
+
+ local fallback = nil
+
+ for i, v in ipairs(arg) do
+ cmdY = v:match("^([%w%*]+)%-?[%w%*]*:")
+ cmdX = v:match("^[%w%*]*%-([%w%*]+)%s*:")
+ cmd = v:match("^[%S]-%s*:%s*(.+)")
+ msg.debug('cmdY: '..tostring(cmdY))
+ msg.debug('cmdX: '..tostring(cmdX))
+ msg.debug('cmd : '..tostring(cmd))
+
+ if (cmdY == keyY and cmdX == keyX) then
+ msg.verbose("running cmd: "..cmd)
+ mp.command(cmd)
+ return
+ elseif (cmdY == "*" and cmdX == keyX) or
+ (cmdY == keyY and cmdX == "*") then
+ msg.verbose("running cmd: "..cmd)
+ mp.command(cmd)
+ return
+ elseif cmdY == "default" then
+ fallback = cmd
+ end
+ end
+ if fallback ~= nil then
+ msg.verbose("running cmd: "..fallback)
+ mp.command(fallback)
+ return
+ else
+ msg.debug("no command assigned for "..keyY .. '-' .. keyX)
+ return
+ end
+end
+mp.register_script_message("commands", main)
diff --git a/old-configure b/TOOLS/old-configure
index a36d83e..60575a8 100755
--- a/old-configure
+++ b/TOOLS/old-configure
@@ -4,6 +4,13 @@
# Cleanups all over the place (c) 2001 pl
# Rewritten for mpv in 2014.
#
+#
+# Warning: this is not officially supported. Use on your own risk. Might require
+# modifications when used on systems other than Linux. Contributors
+# are not expected to update these files when making changes to the
+# normal/preferred waf build system.
+#
+#
# This configure script is *not* autoconf-based and has different semantics.
# It attempts to autodetect all settings and options where possible. It is
# possible to override autodetection with the --enable-option/--disable-option
@@ -176,6 +183,7 @@ options_state_machine() {
opt_yes_no _dvdread "libdvdread"
opt_yes_no _dvdnav "libdvdnav"
opt_yes_no _enca "ENCA charset oracle library"
+ opt_yes_no _uchardet "uchardet charset detection library"
opt_yes_no _libass "subtitle rendering with libass"
opt_yes_no _libavdevice "libavdevice demuxers"
opt_yes_no _libavfilter "libavfilter"
@@ -208,6 +216,7 @@ options_state_machine() {
opt_yes_no _lua "Lua scripting"
opt_yes_no _vapoursynth "VapourSynth filter bridge (Python)"
opt_yes_no _vapoursynth_lazy "VapourSynth filter bridge (Lua)"
+ opt_yes_no _libarchive "libarchive"
opt_yes_no _encoding "encoding functionality" yes
opt_yes_no _build_man "building manpage"
}
@@ -376,7 +385,7 @@ addcflags() { cflag_check "$@" && OURCFLAGS="$OURCFLAGS $@" ; }
OURCFLAGS="-std=c99 -Wall $_opt"
addcflags -g -g3 -ggdb
-addcflags -Wundef -Wmissing-prototypes -Wshadow -Wno-switch -Wparentheses -Wpointer-arith -Wredundant-decls -Wno-pointer-sign -Werror=implicit-function-declaration -Wno-error=deprecated-declarations -Wno-error=unused-function
+addcflags -Wundef -Wmissing-prototypes -Wshadow -Wno-switch -Wparentheses -Wpointer-arith -Wno-redundant-decls -Wno-pointer-sign -Werror=implicit-function-declaration -Wno-error=deprecated-declarations -Wno-error=unused-function
# clang
addcflags -Wno-logical-op-parentheses -fcolor-diagnostics -Wno-tautological-compare -Wno-tautological-constant-out-of-range-compare
# extra
@@ -506,9 +515,12 @@ check_statement_libs "compiler support for __sync built-ins" $_sync SYNC_BUILTIN
stdint.h 'int64_t test = 0; test = __sync_add_and_fetch(&test, 1)'
_sync=$(defretval)
+_any_atomic=yes
if test "$_atomic" = no && test "$_sync" = no && test "$_stdatomic" = no ; then
echo "your compiler must support either stdatomic.h, or __atomic, or __sync built-ins."
+ _any_atomic=no
fi
+define_yes_no $_any_atomic HAVE_ATOMICS
check_compile "iconv" $_iconv ICONV waftools/fragments/iconv.c " " "-liconv" "-liconv $_ld_dl"
_iconv=$(defretval)
@@ -732,6 +744,7 @@ echo "LIBASS_OSD = $_libass" >> $CONFIG_MAK
echo "DUMMY_OSD = $_dummy_osd" >> $CONFIG_MAK
check_pkg_config "ENCA" $_enca ENCA 'enca'
+check_pkg_config "uchardet" $_uchardet UCHARDET 'uchardet'
check_pkg_config "zlib" auto ZLIB 'zlib'
test $(defretval) = no && die "Unable to find development files for zlib."
@@ -786,6 +799,12 @@ api_statement_check \
libavutil/frame.h \
'enum AVFrameSideDataType type = AV_FRAME_DATA_SKIP_SAMPLES'
+api_statement_check \
+ "libavutil av_version_info()" \
+ HAVE_AV_VERSION_INFO \
+ libavutil/avutil.h \
+ 'const char *x = av_version_info()'
+
check_pkg_config "libavfilter" $_libavfilter LIBAVFILTER 'libavfilter >= 5.0.0'
check_pkg_config "libavdevice" $_libavdevice LIBAVDEVICE 'libavdevice >= 55.0.0'
@@ -817,9 +836,9 @@ test_lua "lua >= 5.1.0 lua < 5.2.0"
test_lua "lua5.1 >= 5.1.0" # debian
test_lua "lua51 >= 5.1.0" # OpenBSD
test_lua "luajit >= 2.0.0"
-test_lua "lua >= 5.2.0"
test_lua "lua5.2 >= 5.2.0" # debian
test_lua "lua52 >= 5.2.0" # OpenBSD
+test_lua "lua >= 5.2.0"
test "$_lua" != yes && check_yes_no no LUA
@@ -841,6 +860,8 @@ if test "$_vapoursynth" = no && test "$_vapoursynth_lazy" = no ; then
fi
check_trivial "VapourSynth core" $_vapoursynth_core VAPOURSYNTH_CORE
+check_pkg_config "libarchive support" $_libarchive LIBARCHIVE 'libarchive >= 3.0.0'
+
check_trivial "encoding" $_encoding ENCODING
# needs dlopen on unix
@@ -858,7 +879,7 @@ CFLAGS="$CFLAGS -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE
# in-tree builds.
if test ! -f Makefile ; then
- ln -s old-makefile Makefile
+ ln -s TOOLS/old-makefile Makefile
fi
cat > old_build/config.mak << EOF
@@ -905,7 +926,7 @@ cat > $TMPC << EOF
/* we didn't bother to add actual config checks for this, or they are
for platforms not supported by this configure script */
#define HAVE_BSD_FSTATFS 0
-#define HAVE_LINUX_FSTATFS 0
+#define HAVE_LINUX_FSTATFS 1
#define HAVE_VDA_AV_VDA_ALLOC_CONTEXT 0
#define HAVE_VDA_HWACCEL 0
#define HAVE_VDA_LIBAVCODEC_REFCOUNTING 0
@@ -936,6 +957,9 @@ cat > $TMPC << EOF
#define HAVE_RPI_GLES 0
#define HAVE_AV_PIX_FMT_MMAL 0
#define HAVE_DRM 0
+#define HAVE_VIDEOTOOLBOX_HWACCEL 0
+#define HAVE_VIDEOTOOLBOX_GL 0
+#define HAVE_VIDEOTOOLBOX_VDA_GL 0
#ifdef __OpenBSD__
#define DEFAULT_CDROM_DEVICE "/dev/rcd0c"
diff --git a/old-makefile b/TOOLS/old-makefile
index 1cbde2f..4f57375 100644
--- a/old-makefile
+++ b/TOOLS/old-makefile
@@ -80,7 +80,8 @@ 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_vdpaupp.c \
+ video/filter/vf_vdpaurb.c
SOURCES-$(VDPAU_GL_X11) += video/out/gl_hwdec_vdpau.c
SOURCES-$(VAAPI) += video/out/vo_vaapi.c \
video/decode/vaapi.c \
@@ -88,8 +89,7 @@ SOURCES-$(VAAPI) += video/out/vo_vaapi.c \
SOURCES-$(VAAPI_VPP) += video/filter/vf_vavpp.c
SOURCES-$(VAAPI_GLX) += video/out/gl_hwdec_vaglx.c
-SOURCES-$(X11) += video/out/vo_x11.c video/out/x11_common.c
-SOURCES-$(XV) += video/out/vo_xv.c
+SOURCES-$(XV) += video/out/x11_common.c video/out/vo_xv.c
SOURCES-$(WAYLAND) += video/out/vo_wayland.c \
video/out/wayland_common.c \
video/out/wayland/buffer.c \
@@ -108,6 +108,8 @@ SOURCES-$(LIBAVFILTER) += video/filter/vf_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 \
@@ -123,10 +125,7 @@ SOURCES = audio/audio.c \
audio/filter/af.c \
audio/filter/af_center.c \
audio/filter/af_channels.c \
- audio/filter/af_convert24.c \
- audio/filter/af_convertsignendian.c \
audio/filter/af_delay.c \
- audio/filter/af_dummy.c \
audio/filter/af_equalizer.c \
audio/filter/af_export.c \
audio/filter/af_extrastereo.c \
@@ -160,6 +159,7 @@ SOURCES = audio/audio.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 \
@@ -207,7 +207,6 @@ SOURCES = audio/audio.c \
player/client.c \
player/configfiles.c \
player/command.c \
- player/discnav.c \
player/loadfile.c \
player/main.c \
player/misc.c \
@@ -382,8 +381,8 @@ config.mak: configure
@echo "####### Please run ./configure again - it's changed! #######"
@echo "############################################################"
-version.h .version: version.sh
- ./$<
+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
@@ -394,7 +393,7 @@ version.h .version: version.sh
###### dependency declarations / specific CFLAGS ######
-common/version.c: version.h
+common/version.c: old_build/version.h
DOCS/man/mpv.1: DOCS/man/af.rst \
DOCS/man/ao.rst \
@@ -465,7 +464,7 @@ clean:
-$(RM) $(call ADD_ALL_DIRS,/*.o /*.a /*.ho /*~)
-$(RM) mpv
-$(RM) DOCS/man/*/mpv.1
- -$(RM) version.h
+ -$(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
diff --git a/TOOLS/osxbundle.py b/TOOLS/osxbundle.py
index b2695e1..3ad52cb 100755
--- a/TOOLS/osxbundle.py
+++ b/TOOLS/osxbundle.py
@@ -40,7 +40,7 @@ def apply_plist_template(plist_file, version):
print (line.rstrip().replace('${VERSION}', version))
def main():
- version = sh("./version.sh --print").strip()
+ version = sh("./version.sh").strip()
usage = "usage: %prog [options] arg"
parser = OptionParser(usage)
diff --git a/TOOLS/osxbundle/mpv.app/Contents/Resources/mpv.conf b/TOOLS/osxbundle/mpv.app/Contents/Resources/mpv.conf
index 7527de0..d23926b 100644
--- a/TOOLS/osxbundle/mpv.app/Contents/Resources/mpv.conf
+++ b/TOOLS/osxbundle/mpv.app/Contents/Resources/mpv.conf
@@ -1,2 +1 @@
-screenshot-template=~/Desktop/shot%n
profile=pseudo-gui
diff --git a/TOOLS/stats-conv.py b/TOOLS/stats-conv.py
index 0390a52..5884af5 100755
--- a/TOOLS/stats-conv.py
+++ b/TOOLS/stats-conv.py
@@ -5,7 +5,10 @@ import re
filename = sys.argv[1]
-event_regex = re.compile(".*")
+events = ".*"
+if len(sys.argv) > 2:
+ events = sys.argv[2]
+event_regex = re.compile(events)
"""
This script is meant to display stats written by mpv --dump-stats=filename.
@@ -26,8 +29,8 @@ Currently, the following event types are supported:
'end' <name> end of the named event
'value' <float> <name> a normal value (as opposed to event)
'event-timed' <ts> <name> singular event at the given timestamp
- 'value-timed' <ts> <float> <name>
- a value for an event at the given timestamp
+ 'value-timed' <ts> <float> <name> a value for an event at the given timestamp
+ 'range-timed' <ts1> <ts2> <name> like start/end, but explicit times
<name> singular event (same as 'signal')
"""
@@ -91,6 +94,15 @@ for line in [line.split("#")[0].strip() for line in open(filename, "r")]:
val = int(val) / SCALE - G.start
e = get_event(name, "event-signal")
e.vals.append((val, 1))
+ elif event.startswith("range-timed "):
+ _, ts1, ts2, name = event.split(" ", 3)
+ ts1 = int(ts1) / SCALE - G.start
+ ts2 = int(ts2) / SCALE - G.start
+ e = get_event(name, "event")
+ e.vals.append((ts1, 0))
+ e.vals.append((ts1, 1))
+ e.vals.append((ts2, 1))
+ e.vals.append((ts2, 0))
elif event.startswith("value-timed "):
_, tsval, val, name = event.split(" ", 3)
tsval = int(tsval) / SCALE - G.start
diff --git a/TOOLS/zsh.pl b/TOOLS/zsh.pl
index 6548d85..91b15bb 100755
--- a/TOOLS/zsh.pl
+++ b/TOOLS/zsh.pl
@@ -141,7 +141,7 @@ sub parse_main_opts {
my ($cmd, $regex) = @_;
my @list;
- my @lines = split /\n/, `"$mpv" --no-config $cmd`;
+ my @lines = call_mpv($cmd);
foreach my $line (@lines) {
my ($name, $desc) = ($line =~ /^$regex/) or next;
@@ -206,7 +206,7 @@ sub parse_opts {
my ($cmd, $regex) = @_;
my @list;
- my @lines = split /\n/, `"$mpv" --no-config $cmd`;
+ my @lines = call_mpv($cmd);
foreach my $line (@lines) {
if ($line !~ /^$regex/) {
@@ -226,3 +226,14 @@ sub parse_opts {
return @list;
}
+
+sub call_mpv {
+ my ($cmd) = @_;
+ my $output = `"$mpv" --no-config $cmd`;
+ if ($? == -1) {
+ die "Could not run mpv: $!";
+ } elsif ($? != 0) {
+ die "mpv returned " . ($? >> 8) . " with output:\n$output";
+ }
+ return split /\n/, $output;
+}
diff --git a/VERSION b/VERSION
index 2003b63..78bc1ab 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.9.2
+0.10.0
diff --git a/audio/audio.c b/audio/audio.c
index 2634702..f84d605 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -34,8 +34,8 @@ static void update_redundant_info(struct mp_audio *mpa)
assert(mp_chmap_is_empty(&mpa->channels) ||
mp_chmap_is_valid(&mpa->channels));
mpa->nch = mpa->channels.num;
- mpa->bps = af_fmt2bps(mpa->format);
- if (AF_FORMAT_IS_PLANAR(mpa->format)) {
+ mpa->bps = af_fmt_to_bytes(mpa->format);
+ if (af_fmt_is_planar(mpa->format)) {
mpa->spf = 1;
mpa->num_planes = mpa->nch;
mpa->sstride = mpa->bps;
@@ -93,15 +93,19 @@ bool mp_audio_config_valid(const struct mp_audio *mpa)
char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa)
{
+ char ch[128];
+ mp_chmap_to_str_buf(ch, sizeof(ch), &mpa->channels);
+ char *hr_ch = mp_chmap_to_str_hr(&mpa->channels);
+ if (strcmp(hr_ch, ch) != 0)
+ mp_snprintf_cat(ch, sizeof(ch), " (%s)", hr_ch);
snprintf(buf, buf_sz, "%dHz %s %dch %s", mpa->rate,
- mp_chmap_to_str(&mpa->channels), mpa->channels.num,
- af_fmt_to_str(mpa->format));
+ ch, mpa->channels.num, af_fmt_to_str(mpa->format));
return buf;
}
void mp_audio_force_interleaved_format(struct mp_audio *mpa)
{
- if (AF_FORMAT_IS_PLANAR(mpa->format))
+ if (af_fmt_is_planar(mpa->format))
mp_audio_set_format(mpa, af_fmt_from_planar(mpa->format));
}
@@ -118,7 +122,6 @@ void mp_audio_set_null_data(struct mp_audio *mpa)
mpa->allocated[n] = NULL;
}
mpa->samples = 0;
- mpa->readonly = false;
}
static int get_plane_size(const struct mp_audio *mpa, int samples)
@@ -160,8 +163,8 @@ void mp_audio_realloc(struct mp_audio *mpa, int samples)
if (!mpa->allocated[n] || size != mpa->allocated[n]->size) {
if (av_buffer_realloc(&mpa->allocated[n], size) < 0)
abort(); // OOM
- mpa->planes[n] = mpa->allocated[n]->data;
}
+ mpa->planes[n] = mpa->allocated[n]->data;
}
for (int n = mpa->num_planes; n < MP_NUM_CHANNELS; n++) {
av_buffer_unref(&mpa->allocated[n]);
@@ -192,8 +195,18 @@ int mp_audio_get_allocated_size(struct mp_audio *mpa)
{
int size = 0;
for (int n = 0; n < mpa->num_planes; n++) {
- int s = mpa->allocated[n] ? mpa->allocated[n]->size / mpa->sstride : 0;
- size = n == 0 ? s : MPMIN(size, s);
+ for (int i = 0; i < MP_NUM_CHANNELS && mpa->allocated[i]; i++) {
+ uint8_t *start = mpa->allocated[i]->data;
+ uint8_t *end = start + mpa->allocated[i]->size;
+ uint8_t *plane = mpa->planes[n];
+ if (plane >= start && plane < end) {
+ int s = MPMIN((end - plane) / mpa->sstride, INT_MAX);
+ size = n == 0 ? s : MPMIN(size, s);
+ goto next;
+ }
+ }
+ return 0; // plane is not covered by any buffer
+ next: ;
}
return size;
}
@@ -251,7 +264,7 @@ void mp_audio_skip_samples(struct mp_audio *data, int samples)
// Will return true for non-refcounted frames.
bool mp_audio_is_writeable(struct mp_audio *data)
{
- bool ok = !data->readonly;
+ bool ok = true;
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
if (data->allocated[n])
ok &= av_buffer_is_writable(data->allocated[n]);
diff --git a/audio/audio.h b/audio/audio.h
index a633a15..a0ecb2d 100644
--- a/audio/audio.h
+++ b/audio/audio.h
@@ -35,10 +35,12 @@ struct mp_audio {
int nch; // number of channels (redundant with chmap)
int spf; // sub-samples per sample on each plane
int num_planes; // number of planes
- int bps; // size of sub-samples (af_fmt2bps(format))
+ int bps; // size of sub-samples (af_fmt_to_bytes(format))
- // private
- bool readonly;
+ // --- private
+ // These do not necessarily map directly to planes[]. They can have
+ // different order or count. There shouldn't be more buffers than planes.
+ // If allocated[n] is NULL, allocated[n+1] must also be NULL.
struct AVBufferRef *allocated[MP_NUM_CHANNELS];
};
diff --git a/audio/chmap.c b/audio/chmap.c
index 4fd43e3..82f748b 100644
--- a/audio/chmap.c
+++ b/audio/chmap.c
@@ -18,6 +18,8 @@
#include <stdlib.h>
#include <assert.h>
+#include <libavutil/common.h>
+
#include "common/common.h"
#include "common/msg.h"
#include "chmap.h"
@@ -68,6 +70,7 @@ static const char *const std_layout_names[][2] = {
{"quad", "fl-fr-bl-br"},
{"quad(side)", "fl-fr-sl-sr"},
{"3.1", "fl-fr-fc-lfe"},
+ {"3.1(back)", "fl-fr-lfe-bc"}, // not in lavc
{"5.0", "fl-fr-fc-bl-br"},
{"5.0(alsa)", "fl-fr-bl-br-fc"}, // not in lavc
{"5.0(side)", "fl-fr-fc-sl-sr"},
@@ -81,9 +84,11 @@ static const char *const std_layout_names[][2] = {
{"hexagonal", "fl-fr-fc-bl-br-bc"},
{"6.1", "fl-fr-fc-lfe-bc-sl-sr"},
{"6.1(back)", "fl-fr-fc-lfe-bl-br-bc"}, // lavc calls this "6.1" too
+ {"6.1(top)", "fl-fr-fc-lfe-bl-br-tc"}, // not in lavc
{"6.1(front)", "fl-fr-lfe-flc-frc-sl-sr"},
{"7.0", "fl-fr-fc-bl-br-sl-sr"},
{"7.0(front)", "fl-fr-fc-flc-frc-sl-sr"},
+ {"7.0(rear)", "fl-fr-fc-bl-br-sdl-sdr"}, // not in lavc
{"7.1", "fl-fr-fc-lfe-bl-br-sl-sr"},
{"7.1(alsa)", "fl-fr-bl-br-fc-lfe-sl-sr"}, // not in lavc
{"7.1(wide)", "fl-fr-fc-lfe-bl-br-flc-frc"},
@@ -95,7 +100,7 @@ static const char *const std_layout_names[][2] = {
{0}
};
-static const struct mp_chmap default_layouts[MP_NUM_CHANNELS + 1] = {
+static const struct mp_chmap default_layouts[] = {
{0}, // empty
MP_CHMAP_INIT_MONO, // mono
MP_CHMAP2(FL, FR), // stereo
@@ -215,11 +220,11 @@ void mp_chmap_fill_na(struct mp_chmap *map, int num)
// mp_chmap_is_valid(dst) will return false.
void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels)
{
- if (num_channels < 0 || num_channels > MP_NUM_CHANNELS) {
- *dst = (struct mp_chmap) {0};
- } else {
+ *dst = (struct mp_chmap) {0};
+ if (num_channels >= 0 && num_channels < MP_ARRAY_SIZE(default_layouts))
*dst = default_layouts[num_channels];
- }
+ if (!dst->num)
+ mp_chmap_set_unknown(dst, num_channels);
}
// Try to do what mplayer/mplayer2/mpv did before channel layouts were
@@ -382,8 +387,8 @@ void mp_chmap_get_reorder(int src[MP_NUM_CHANNELS], const struct mp_chmap *from,
return;
}
- for (int n = 0; n < from->num; n++) {
- for (int i = 0; i < to->num; i++) {
+ for (int n = 0; n < to->num; n++) {
+ for (int i = 0; i < from->num; i++) {
if (to->speaker[n] == from->speaker[i]) {
src[n] = i;
break;
@@ -395,20 +400,12 @@ void mp_chmap_get_reorder(int src[MP_NUM_CHANNELS], const struct mp_chmap *from,
assert(src[n] < 0 || (to->speaker[n] == from->speaker[src[n]]));
}
-static int popcount64(uint64_t bits)
-{
- int r = 0;
- for (int n = 0; n < 64; n++)
- r += !!(bits & (1ULL << n));
- return r;
-}
-
// Return the number of channels only in a.
int mp_chmap_diffn(const struct mp_chmap *a, const struct mp_chmap *b)
{
uint64_t a_mask = mp_chmap_to_lavc_unchecked(a);
uint64_t b_mask = mp_chmap_to_lavc_unchecked(b);
- return popcount64((a_mask ^ b_mask) & a_mask);
+ return av_popcount64((a_mask ^ b_mask) & a_mask);
}
// Returns something like "fl-fr-fc". If there's a standard layout in lavc
@@ -511,6 +508,25 @@ bool mp_chmap_from_str(struct mp_chmap *dst, bstr src)
return true;
}
+// Output a human readable "canonical" channel map string. Converting this from
+// a string back to a channel map can yield a different map, but the string
+// looks nicer. E.g. "fc-fl-fr-na" becomes "3.0".
+char *mp_chmap_to_str_hr_buf(char *buf, size_t buf_size, const struct mp_chmap *src)
+{
+ struct mp_chmap map = *src;
+ mp_chmap_remove_na(&map);
+ for (int n = 0; std_layout_names[n][0]; n++) {
+ struct mp_chmap s;
+ if (mp_chmap_from_str(&s, bstr0(std_layout_names[n][0])) &&
+ mp_chmap_equals_reordered(&s, &map))
+ {
+ map = s;
+ break;
+ }
+ }
+ return mp_chmap_to_str_buf(buf, buf_size, &map);
+}
+
void mp_chmap_print_help(struct mp_log *log)
{
mp_info(log, "Speakers:\n");
diff --git a/audio/chmap.h b/audio/chmap.h
index adb7481..b27ec3b 100644
--- a/audio/chmap.h
+++ b/audio/chmap.h
@@ -47,7 +47,7 @@ enum mp_speaker_id {
MP_SPEAKER_ID_TBL, // TOP_BACK_LEFT
MP_SPEAKER_ID_TBC, // TOP_BACK_CENTER
MP_SPEAKER_ID_TBR, // TOP_BACK_RIGHT
- // Inofficial/libav* extensions
+ // Unofficial/libav* extensions
MP_SPEAKER_ID_DL = 29, // STEREO_LEFT (stereo downmix special speakers)
MP_SPEAKER_ID_DR, // STEREO_RIGHT
MP_SPEAKER_ID_WL, // WIDE_LEFT
@@ -128,6 +128,9 @@ int mp_chmap_diffn(const struct mp_chmap *a, const struct mp_chmap *b);
char *mp_chmap_to_str_buf(char *buf, size_t buf_size, const struct mp_chmap *src);
#define mp_chmap_to_str(m) mp_chmap_to_str_buf((char[64]){0}, 64, (m))
+char *mp_chmap_to_str_hr_buf(char *buf, size_t buf_size, const struct mp_chmap *src);
+#define mp_chmap_to_str_hr(m) mp_chmap_to_str_hr_buf((char[128]){0}, 128, (m))
+
bool mp_chmap_from_str(struct mp_chmap *dst, bstr src);
struct mp_log;
diff --git a/audio/chmap_sel.c b/audio/chmap_sel.c
index 3d93a11..292aa8e 100644
--- a/audio/chmap_sel.c
+++ b/audio/chmap_sel.c
@@ -58,6 +58,29 @@ static bool replace_speakers(struct mp_chmap *map, struct mp_chmap list[2])
return false;
}
+// These go strictly from the first to the second entry and always use the
+// full layout (possibly reordered and/or padding channels added).
+static const struct mp_chmap preferred_remix[][2] = {
+ // mono can be perfectly played as stereo
+ { MP_CHMAP_INIT_MONO, MP_CHMAP_INIT_STEREO },
+};
+
+// Conversion from src to dst is explicitly encouraged and should be preferred
+// over "mathematical" upmixes or downmixes (which minimize lost channels).
+static bool test_preferred_remix(const struct mp_chmap *src,
+ const struct mp_chmap *dst)
+{
+ struct mp_chmap src_p = *src, dst_p = *dst;
+ mp_chmap_remove_na(&src_p);
+ mp_chmap_remove_na(&dst_p);
+ for (int n = 0; n < MP_ARRAY_SIZE(preferred_remix); n++) {
+ if (mp_chmap_equals_reordered(&src_p, &preferred_remix[n][0]) &&
+ mp_chmap_equals_reordered(&dst_p, &preferred_remix[n][1]))
+ return true;
+ }
+ return false;
+}
+
// Allow all channel layouts that can be expressed with mp_chmap.
// (By default, all layouts are rejected.)
void mp_chmap_sel_add_any(struct mp_chmap_sel *s)
@@ -176,6 +199,10 @@ bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map)
return true;
}
}
+
+ if (mp_chmap_sel_fallback(s, map))
+ return true;
+
for (int i = 0; i < MP_ARRAY_SIZE(speaker_replacements); i++) {
struct mp_chmap t = *map;
struct mp_chmap *r = (struct mp_chmap *)speaker_replacements[i];
@@ -185,9 +212,6 @@ bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map)
}
}
- if (mp_chmap_sel_fallback(s, map))
- return true;
-
// Fallback to mono/stereo as last resort
*map = (struct mp_chmap) MP_CHMAP_INIT_STEREO;
if (test_layout(s, map))
@@ -199,40 +223,94 @@ bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map)
return false;
}
+// Like mp_chmap_diffn(), but find the minimum difference with all possible
+// speaker replacements considered.
+static int mp_chmap_diffn_r(const struct mp_chmap *a, const struct mp_chmap *b)
+{
+ int mindiff = INT_MAX;
+
+ for (int i = -1; i < (int)MP_ARRAY_SIZE(speaker_replacements); i++) {
+ struct mp_chmap ar = *a;
+ if (i >= 0) {
+ struct mp_chmap *r = (struct mp_chmap *)speaker_replacements[i];
+ if (!replace_speakers(&ar, r))
+ continue;
+ }
+ int d = mp_chmap_diffn(&ar, b);
+ if (d < mindiff)
+ mindiff = d;
+ }
+
+ // Special-case: we consider stereo a replacement for mono. (This is not
+ // true in the other direction. Also, fl-fr is generally not a replacement
+ // for fc. Thus it's not part of the speaker replacement list.)
+ struct mp_chmap mono = MP_CHMAP_INIT_MONO;
+ struct mp_chmap stereo = MP_CHMAP_INIT_STEREO;
+ if (mp_chmap_equals(&mono, b) && mp_chmap_equals(&stereo, a))
+ mindiff = 0;
+
+ return mindiff;
+}
+
// Decide whether we should prefer old or new for the requested layout.
// Return true if new should be used, false if old should be used.
// If old is empty, always return new (initial case).
static bool mp_chmap_is_better(struct mp_chmap *req, struct mp_chmap *old,
struct mp_chmap *new)
{
- int old_lost = mp_chmap_diffn(req, old); // num. channels only in req
- int new_lost = mp_chmap_diffn(req, new);
-
// Initial case
if (!old->num)
return true;
+ // Exact pick - this also ensures that the best layout is chosen if the
+ // layouts are the same, but with different order of channels.
+ if (mp_chmap_equals(req, old))
+ return false;
+ if (mp_chmap_equals(req, new))
+ return true;
+
+ // If there's no exact match, strictly do a preferred conversion.
+ bool old_pref = test_preferred_remix(req, old);
+ bool new_pref = test_preferred_remix(req, new);
+ if (old_pref && !new_pref)
+ return false;
+ if (!old_pref && new_pref)
+ return true;
+
+ int old_lost_r = mp_chmap_diffn_r(req, old); // num. channels only in req
+ int new_lost_r = mp_chmap_diffn_r(req, new);
+
// Imperfect upmix (no real superset) - minimize lost channels
+ 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);
+
// 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
+ // padding channels isn't really meaningful.
+ if (new_p.num != old_p.num)
+ return new_p.num < old_p.num;
+
+ // Again, with physical channels (minimizes number of NA channels).
return new->num < old->num;
}
// Determine which channel map to fallback to given a source channel map.
bool mp_chmap_sel_fallback(const struct mp_chmap_sel *s, struct mp_chmap *map)
{
- // special case: if possible always fallback mono to stereo (instead of
- // looking for a multichannel upmix)
- struct mp_chmap mono = MP_CHMAP_INIT_MONO;
- struct mp_chmap stereo = MP_CHMAP_INIT_STEREO;
- if (mp_chmap_equals(&mono, map) && test_layout(s, &stereo)) {
- *map = stereo;
- return true;
- }
-
struct mp_chmap best = {0};
for (int n = 0; n < s->num_chmaps; n++) {
@@ -241,17 +319,8 @@ bool mp_chmap_sel_fallback(const struct mp_chmap_sel *s, struct mp_chmap *map)
if (mp_chmap_is_unknown(&e))
continue;
- // in case we didn't match any fallback retry after replacing speakers
- for (int i = -1; i < (int)MP_ARRAY_SIZE(speaker_replacements); i++) {
- struct mp_chmap t = *map;
- if (i >= 0) {
- struct mp_chmap *r = (struct mp_chmap *)speaker_replacements[i];
- if (!replace_speakers(&t, r))
- continue;
- }
- if (mp_chmap_is_better(&t, &best, &e))
- best = e;
- }
+ if (mp_chmap_is_better(map, &best, &e))
+ best = e;
}
if (best.num) {
diff --git a/audio/decode/ad_lavc.c b/audio/decode/ad_lavc.c
index 5a2392e..ce62887 100644
--- a/audio/decode/ad_lavc.c
+++ b/audio/decode/ad_lavc.c
@@ -116,7 +116,7 @@ static int init(struct dec_audio *da, const char *decoder)
mp_set_avopts(da->log, lavc_context, opts->avopts);
- lavc_context->codec_tag = sh->format;
+ 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;
@@ -126,11 +126,7 @@ static int init(struct dec_audio *da, const char *decoder)
lavc_context->channel_layout = mp_chmap_to_lavc(&sh_audio->channels);
// demux_mkv
- if (sh_audio->codecdata_len && sh_audio->codecdata &&
- !lavc_context->extradata) {
- mp_lavc_set_extradata(lavc_context, sh_audio->codecdata,
- sh_audio->codecdata_len);
- }
+ mp_lavc_set_extradata(lavc_context, sh->extradata, sh->extradata_size);
if (sh->lav_headers)
mp_copy_lav_codec_headers(lavc_context, sh->lav_headers);
diff --git a/audio/decode/ad_spdif.c b/audio/decode/ad_spdif.c
index 5265f4f..88f3f2b 100644
--- a/audio/decode/ad_spdif.c
+++ b/audio/decode/ad_spdif.c
@@ -34,10 +34,12 @@
struct spdifContext {
struct mp_log *log;
+ enum AVCodecID codec_id;
AVFormatContext *lavf_ctx;
int out_buffer_len;
uint8_t out_buffer[OUTBUF_SIZE];
bool need_close;
+ bool use_dts_hd;
struct mp_audio fmt;
};
@@ -76,17 +78,78 @@ static int init(struct dec_audio *da, const char *decoder)
struct spdifContext *spdif_ctx = talloc_zero(NULL, struct spdifContext);
da->priv = spdif_ctx;
spdif_ctx->log = da->log;
+ spdif_ctx->use_dts_hd = da->opts->dtshd;
+
+ if (strcmp(decoder, "dts-hd") == 0) {
+ decoder = "dts";
+ spdif_ctx->use_dts_hd = true;
+ }
+
+ spdif_ctx->codec_id = mp_codec_to_av_codec_id(decoder);
+ return spdif_ctx->codec_id != AV_CODEC_ID_NONE;
+}
+
+static int determine_codec_profile(struct dec_audio *da, AVPacket *pkt)
+{
+ struct spdifContext *spdif_ctx = da->priv;
+ int profile = FF_PROFILE_UNKNOWN;
+ AVCodecContext *ctx = NULL;
+ AVFrame *frame = NULL;
+
+ AVCodec *codec = avcodec_find_decoder(spdif_ctx->codec_id);
+ if (!codec)
+ goto done;
+
+ frame = av_frame_alloc();
+ if (!frame)
+ goto done;
+
+ ctx = avcodec_alloc_context3(codec);
+ if (!ctx)
+ goto done;
+
+ if (avcodec_open2(ctx, codec, NULL) < 0) {
+ av_free(ctx); // don't attempt to avcodec_close() an unopened ctx
+ ctx = NULL;
+ goto done;
+ }
+
+ int got_frame = 0;
+ if (avcodec_decode_audio4(ctx, frame, &got_frame, pkt) < 1 || !got_frame)
+ goto done;
+
+ profile = ctx->profile;
+
+done:
+ av_frame_free(&frame);
+ if (ctx)
+ avcodec_close(ctx);
+ avcodec_free_context(&ctx);
+
+ if (profile == FF_PROFILE_UNKNOWN)
+ MP_WARN(da, "Failed to parse codec profile.\n");
+
+ return profile;
+}
+
+static int init_filter(struct dec_audio *da, AVPacket *pkt)
+{
+ struct spdifContext *spdif_ctx = da->priv;
+
+ int profile = FF_PROFILE_UNKNOWN;
+ if (spdif_ctx->codec_id == AV_CODEC_ID_DTS)
+ profile = determine_codec_profile(da, pkt);
AVFormatContext *lavf_ctx = avformat_alloc_context();
if (!lavf_ctx)
goto fail;
+ spdif_ctx->lavf_ctx = lavf_ctx;
+
lavf_ctx->oformat = av_guess_format("spdif", NULL, NULL);
if (!lavf_ctx->oformat)
goto fail;
- spdif_ctx->lavf_ctx = lavf_ctx;
-
void *buffer = av_mallocz(OUTBUF_SIZE);
if (!buffer)
abort();
@@ -106,14 +169,14 @@ static int init(struct dec_audio *da, const char *decoder)
if (!stream)
goto fail;
- stream->codec->codec_id = mp_codec_to_av_codec_id(decoder);
+ stream->codec->codec_id = spdif_ctx->codec_id;
AVDictionary *format_opts = NULL;
int num_channels = 0;
int sample_format = 0;
int samplerate = 0;
- switch (stream->codec->codec_id) {
+ switch (spdif_ctx->codec_id) {
case AV_CODEC_ID_AAC:
sample_format = AF_FORMAT_S_AAC;
samplerate = 48000;
@@ -124,18 +187,21 @@ static int init(struct dec_audio *da, const char *decoder)
samplerate = 48000;
num_channels = 2;
break;
- case AV_CODEC_ID_DTS:
- if (da->opts->dtshd) {
+ case AV_CODEC_ID_DTS: {
+ bool is_hd = profile == FF_PROFILE_DTS_HD_HRA ||
+ profile == FF_PROFILE_DTS_HD_MA;
+ if (spdif_ctx->use_dts_hd && is_hd) {
av_dict_set(&format_opts, "dtshd_rate", "768000", 0); // 4*192000
- sample_format = AF_FORMAT_S_DTSHD;
- samplerate = 192000;
- num_channels = 2*4;
+ sample_format = AF_FORMAT_S_DTSHD;
+ samplerate = 192000;
+ num_channels = 2*4;
} else {
- sample_format = AF_FORMAT_S_DTS;
- samplerate = 48000;
- num_channels = 2;
+ sample_format = AF_FORMAT_S_DTS;
+ samplerate = 48000;
+ num_channels = 2;
}
break;
+ }
case AV_CODEC_ID_EAC3:
sample_format = AF_FORMAT_S_EAC3;
samplerate = 192000;
@@ -167,17 +233,16 @@ static int init(struct dec_audio *da, const char *decoder)
spdif_ctx->need_close = true;
- return 1;
+ return 0;
fail:
uninit(da);
- return 0;
+ return -1;
}
static int decode_packet(struct dec_audio *da, struct mp_audio **out)
{
struct spdifContext *spdif_ctx = da->priv;
- AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
spdif_ctx->out_buffer_len = 0;
@@ -195,9 +260,13 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
da->pts = mpkt->pts;
da->pts_offset = 0;
}
- int ret = av_write_frame(lavf_ctx, &pkt);
+ if (!spdif_ctx->lavf_ctx) {
+ if (init_filter(da, &pkt) < 0)
+ return AD_ERR;
+ }
+ int ret = av_write_frame(spdif_ctx->lavf_ctx, &pkt);
talloc_free(mpkt);
- avio_flush(lavf_ctx->pb);
+ avio_flush(spdif_ctx->lavf_ctx->pb);
if (ret < 0)
return AD_ERR;
@@ -235,6 +304,8 @@ static void add_decoders(struct mp_decoder_list *list)
"libavformat/spdifenc audio pass-through decoder");
}
}
+ mp_add_decoder(list, "spdif", "dts", "dts-hd",
+ "libavformat/spdifenc audio pass-through decoder");
}
const struct ad_functions ad_spdif = {
diff --git a/audio/decode/dec_audio.c b/audio/decode/dec_audio.c
index dfbe32c..edf2695 100644
--- a/audio/decode/dec_audio.c
+++ b/audio/decode/dec_audio.c
@@ -52,6 +52,7 @@ static const struct ad_functions * const ad_drivers[] = {
static void uninit_decoder(struct dec_audio *d_audio)
{
+ audio_reset_decoding(d_audio);
if (d_audio->ad_driver) {
MP_VERBOSE(d_audio, "Uninit audio decoder.\n");
d_audio->ad_driver->uninit(d_audio);
@@ -59,6 +60,8 @@ static void uninit_decoder(struct dec_audio *d_audio)
d_audio->ad_driver = NULL;
talloc_free(d_audio->priv);
d_audio->priv = NULL;
+ d_audio->afilter->initialized = -1;
+ d_audio->decode_format = (struct mp_audio){0};
}
static int init_audio_codec(struct dec_audio *d_audio, const char *decoder)
@@ -81,11 +84,21 @@ struct mp_decoder_list *audio_decoder_list(void)
return list;
}
-static struct mp_decoder_list *audio_select_decoders(const char *codec,
- char *selection)
+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;
+
struct mp_decoder_list *list = audio_decoder_list();
- struct mp_decoder_list *new = mp_select_decoders(list, codec, selection);
+ struct mp_decoder_list *new =
+ mp_select_decoders(list, codec, opts->audio_decoders);
+ if (d_audio->spdif_passthrough) {
+ struct mp_decoder_list *spdif =
+ mp_select_decoder_list(list, codec, "spdif", opts->audio_spdif);
+ mp_append_decoders(spdif, new);
+ talloc_free(new);
+ new = spdif;
+ }
talloc_free(list);
return new;
}
@@ -99,14 +112,13 @@ static const struct ad_functions *find_driver(const char *name)
return NULL;
}
-int audio_init_best_codec(struct dec_audio *d_audio, char *audio_decoders)
+int audio_init_best_codec(struct dec_audio *d_audio)
{
+ uninit_decoder(d_audio);
assert(!d_audio->ad_driver);
- audio_reset_decoding(d_audio);
struct mp_decoder_entry *decoder = NULL;
- struct mp_decoder_list *list =
- audio_select_decoders(d_audio->header->codec, audio_decoders);
+ struct mp_decoder_list *list = audio_select_decoders(d_audio);
mp_print_decoders(d_audio->log, MSGL_V, "Codec list:", list);
@@ -145,8 +157,8 @@ void audio_uninit(struct dec_audio *d_audio)
if (!d_audio)
return;
MP_VERBOSE(d_audio, "Uninit audio filters...\n");
- af_destroy(d_audio->afilter);
uninit_decoder(d_audio);
+ af_destroy(d_audio->afilter);
talloc_free(d_audio->waiting);
talloc_free(d_audio);
}
diff --git a/audio/decode/dec_audio.h b/audio/decode/dec_audio.h
index 11e4a24..6d7dd18 100644
--- a/audio/decode/dec_audio.h
+++ b/audio/decode/dec_audio.h
@@ -30,6 +30,7 @@ struct dec_audio {
struct mp_log *log;
struct MPOpts *opts;
struct mpv_global *global;
+ bool spdif_passthrough;
const struct ad_functions *ad_driver;
struct sh_stream *header;
struct af_stream *afilter;
@@ -57,7 +58,7 @@ enum {
};
struct mp_decoder_list *audio_decoder_list(void);
-int audio_init_best_codec(struct dec_audio *d_audio, char *audio_decoders);
+int audio_init_best_codec(struct dec_audio *d_audio);
int audio_decode(struct dec_audio *d_audio, struct mp_audio_buffer *outbuf,
int minsamples);
int initial_audio_decode(struct dec_audio *d_audio);
diff --git a/audio/filter/af.c b/audio/filter/af.c
index 21cf866..6a5b1f4 100644
--- a/audio/filter/af.c
+++ b/audio/filter/af.c
@@ -31,7 +31,6 @@
#include "af.h"
// Static list of filters
-extern const struct af_info af_info_dummy;
extern const struct af_info af_info_delay;
extern const struct af_info af_info_channels;
extern const struct af_info af_info_format;
@@ -55,12 +54,9 @@ extern const struct af_info af_info_karaoke;
extern const struct af_info af_info_scaletempo;
extern const struct af_info af_info_bs2b;
extern const struct af_info af_info_lavfi;
-extern const struct af_info af_info_convert24;
-extern const struct af_info af_info_convertsignendian;
extern const struct af_info af_info_rubberband;
static const struct af_info *const filter_list[] = {
- &af_info_dummy,
&af_info_delay,
&af_info_channels,
&af_info_format,
@@ -92,9 +88,6 @@ static const struct af_info *const filter_list[] = {
#if HAVE_LIBAVFILTER
&af_info_lavfi,
#endif
- // Must come last, because they're fallback format conversion filter
- &af_info_convert24,
- &af_info_convertsignendian,
NULL
};
@@ -334,89 +327,9 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
MP_MSG(s, msg_level, " [ao] %s\n", mp_audio_config_to_str(&s->output));
}
-static int af_count_filters(struct af_stream *s)
-{
- int count = 0;
- for (struct af_instance *af = s->first; af; af = af->next)
- count++;
- return count;
-}
-
-// Finds the first conversion filter on the way from srcfmt to dstfmt.
-// Conversions form a DAG: each node is a format/filter pair, and possible
-// conversions are edges. We search the DAG for the shortest path.
-// Some cases visit the same filter multiple times, but with different formats
-// (like u24le->s8), so one node per format or filter separate is not enough.
-// Returns the filter and dest. format for the first conversion step.
-// (So we know what conversion filter with what format to insert next.)
-static char *af_find_conversion_filter(int srcfmt, int *dstfmt)
-{
-#define MAX_NODES (64 * 32)
- int num_fmt = 0, num_filt = 0;
- for (int n = 0; filter_list[n]; n++)
- num_filt = n + 1;
- for (int n = 0; af_fmtstr_table[n].format; n++)
- num_fmt = n + 1;
-
- int num_nodes = num_fmt * num_filt;
- assert(num_nodes < MAX_NODES);
-
- bool visited[MAX_NODES] = {0};
- unsigned char distance[MAX_NODES];
- short previous[MAX_NODES] = {0};
- for (int n = 0; n < num_nodes; n++) {
- distance[n] = 255;
- if (af_fmtstr_table[n % num_fmt].format == srcfmt)
- distance[n] = 0;
- }
-
- while (1) {
- int next = -1;
- for (int n = 0; n < num_nodes; n++) {
- if (!visited[n] && (next < 0 || (distance[n] < distance[next])))
- next = n;
- }
- if (next < 0 || distance[next] == 255)
- return NULL;
- visited[next] = true;
-
- int fmt = next % num_fmt;
- if (af_fmtstr_table[fmt].format == *dstfmt) {
- // Best match found
- for (int cur = next; cur >= 0; cur = previous[cur] - 1) {
- if (distance[cur] == 1) {
- *dstfmt = af_fmtstr_table[cur % num_fmt].format;
- return (char *)filter_list[cur / num_fmt]->name;
- }
- }
- return NULL;
- }
-
- for (int n = 0; filter_list[n]; n++) {
- const struct af_info *af = filter_list[n];
- if (!af->test_conversion)
- continue;
- for (int i = 0; af_fmtstr_table[i].format; i++) {
- if (i != fmt && af->test_conversion(af_fmtstr_table[fmt].format,
- af_fmtstr_table[i].format))
- {
- int other = n * num_fmt + i;
- int ndist = distance[next] + 1;
- if (ndist < distance[other]) {
- distance[other] = ndist;
- previous[other] = next + 1;
- }
- }
- }
- }
- }
- assert(0);
-#undef MAX_NODES
-}
-
static bool af_is_conversion_filter(struct af_instance *af)
{
- return af && af->info->test_conversion != NULL;
+ return af && strcmp(af->info->name, "lavrresample") == 0;
}
// in is what af can take as input - insert a conversion filter if the actual
@@ -436,21 +349,24 @@ static int af_fix_format_conversion(struct af_stream *s,
if (actual.format == in.format)
return AF_FALSE;
int dstfmt = in.format;
- char *filter = af_find_conversion_filter(actual.format, &dstfmt);
- if (!filter)
+ char *filter = "lavrresample";
+ if (!af_lavrresample_test_conversion(actual.format, dstfmt))
return AF_ERROR;
if (strcmp(filter, prev->info->name) == 0) {
if (prev->control(prev, AF_CONTROL_SET_FORMAT, &dstfmt) == AF_OK) {
*p_af = prev;
return AF_OK;
}
+ return AF_ERROR;
}
struct af_instance *new = af_prepend(s, af, filter, NULL);
if (new == NULL)
return AF_ERROR;
new->auto_inserted = true;
- if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_FORMAT, &dstfmt)))
+ if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_FORMAT, &dstfmt))) {
+ af_remove(s, new);
return rv;
+ }
*p_af = new;
return AF_OK;
}
@@ -528,8 +444,8 @@ static int af_reinit(struct af_stream *s)
// Start with the second filter, as the first filter is the special input
// filter which needs no initialization.
struct af_instance *af = s->first->next;
- // Up to 7 retries per filter (channel, rate, 5x format conversions)
- int max_retry = af_count_filters(s) * 7;
+ // Up to 4 retries per filter (channel, rate, format conversions)
+ int max_retry = 4;
int retry = 0;
while (af) {
if (retry >= max_retry)
@@ -575,8 +491,8 @@ static int af_reinit(struct af_stream *s)
int fmt_in1 = af->prev->data->format;
int fmt_in2 = in.format;
if (af_fmt_is_valid(fmt_in1) && af_fmt_is_valid(fmt_in2)) {
- bool spd1 = AF_FORMAT_IS_IEC61937(fmt_in1);
- bool spd2 = AF_FORMAT_IS_IEC61937(fmt_in2);
+ bool spd1 = af_fmt_is_spdif(fmt_in1);
+ bool spd2 = af_fmt_is_spdif(fmt_in2);
if (spd1 != spd2 && af->next) {
MP_WARN(af, "Filter %s apparently cannot be used due to "
"spdif passthrough - removing it.\n",
@@ -601,6 +517,8 @@ static int af_reinit(struct af_stream *s)
af->info->name, rv);
goto error;
}
+ if (af && !af->auto_inserted)
+ retry = 0;
}
/* Set previously unset fields in s->output to those of the filter chain
@@ -733,7 +651,7 @@ struct af_instance *af_add(struct af_stream *s, char *name, char *label,
return NULL;
new->label = talloc_strdup(new, label);
- // Reinitalize the filter list
+ // Reinitialize the filter list
if (af_reinit(s) != AF_OK) {
af_remove_by_label(s, label);
return NULL;
@@ -832,6 +750,16 @@ static struct mp_audio *af_dequeue_output_frame(struct af_instance *af)
return res;
}
+static void read_remaining(struct af_instance *af)
+{
+ int num_frames;
+ do {
+ num_frames = af->num_out_queued;
+ if (!af->filter_out || af->filter_out(af) < 0)
+ break;
+ } while (num_frames != af->num_out_queued);
+}
+
static int af_do_filter(struct af_instance *af, struct mp_audio *frame)
{
if (frame)
@@ -871,6 +799,7 @@ int af_output_frame(struct af_stream *s, bool eof)
// Flush remaining frames on EOF, but do that only if the previous
// filters have been flushed (i.e. they have no more output).
if (eof && !last) {
+ read_remaining(cur);
int r = af_do_filter(cur, NULL);
if (r < 0)
return r;
diff --git a/audio/filter/af.h b/audio/filter/af.h
index fb5f74b..88cbbc0 100644
--- a/audio/filter/af.h
+++ b/audio/filter/af.h
@@ -48,7 +48,6 @@ struct af_info {
const char *name;
const int flags;
int (*open)(struct af_instance *vf);
- bool (*test_conversion)(int src_format, int dst_format);
int priv_size;
const void *priv_defaults;
const struct m_option *options;
@@ -158,8 +157,9 @@ double af_calc_delay(struct af_stream *s);
int af_test_output(struct af_instance *af, struct mp_audio *out);
-int af_from_dB(int n, float *in, float *out, float k, float mi, float ma);
int af_from_ms(int n, float *in, int *out, int rate, float mi, float ma);
float af_softclip(float a);
+bool af_lavrresample_test_conversion(int src_format, int dst_format);
+
#endif /* MPLAYER_AF_H */
diff --git a/audio/filter/af_bs2b.c b/audio/filter/af_bs2b.c
index 10d6f4e..beb4dc9 100644
--- a/audio/filter/af_bs2b.c
+++ b/audio/filter/af_bs2b.c
@@ -57,12 +57,8 @@ static int filter_##name(struct af_instance *af, struct mp_audio *data) \
#define FILTERS \
FILTER(FLOAT, f) \
FILTER(S32, s32) \
- FILTER(U32, u32) \
FILTER(S24, s24) \
- FILTER(U24, u24) \
FILTER(S16, s16) \
- FILTER(U16, u16) \
- FILTER(S8, s8) \
FILTER(U8, u8)
#define FILTER DEF_FILTER
diff --git a/audio/filter/af_convert24.c b/audio/filter/af_convert24.c
deleted file mode 100644
index ab04c93..0000000
--- a/audio/filter/af_convert24.c
+++ /dev/null
@@ -1,122 +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/>.
- */
-
-#include <stdlib.h>
-#include <assert.h>
-
-#include "audio/format.h"
-#include "af.h"
-#include "osdep/endian.h"
-
-static bool test_conversion(int src_format, int dst_format)
-{
- return (src_format == AF_FORMAT_U24 && dst_format == AF_FORMAT_U32) ||
- (src_format == AF_FORMAT_S24 && dst_format == AF_FORMAT_S32) ||
- (src_format == AF_FORMAT_U32 && dst_format == AF_FORMAT_U24) ||
- (src_format == AF_FORMAT_S32 && dst_format == AF_FORMAT_S24);
-}
-
-static int control(struct af_instance *af, int cmd, void *arg)
-{
- switch (cmd) {
- case AF_CONTROL_REINIT: {
- struct mp_audio *in = arg;
- struct mp_audio orig_in = *in;
- struct mp_audio *out = af->data;
-
- if (!test_conversion(in->format, out->format))
- return AF_DETACH;
-
- if ((in->format & AF_FORMAT_BITS_MASK) == AF_FORMAT_24BIT) {
- mp_audio_set_format(out, af_fmt_change_bits(in->format, 32));
- } else if ((in->format & AF_FORMAT_BITS_MASK) == AF_FORMAT_32BIT) {
- mp_audio_set_format(out, af_fmt_change_bits(in->format, 24));
- } else {
- abort();
- }
-
- out->rate = in->rate;
- mp_audio_set_channels(out, &in->channels);
-
- assert(test_conversion(in->format, out->format));
-
- return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
- }
- case AF_CONTROL_SET_FORMAT: {
- mp_audio_set_format(af->data, *(int*)arg);
- return AF_OK;
- }
- }
- return AF_UNKNOWN;
-}
-
-// The LSB is always ignored.
-#if BYTE_ORDER == BIG_ENDIAN
-#define SHIFT(x) ((3-(x))*8)
-#else
-#define SHIFT(x) (((x)+1)*8)
-#endif
-
-static int filter(struct af_instance *af, struct mp_audio *data)
-{
- if (!data)
- return 0;
- struct mp_audio *out =
- mp_audio_pool_get(af->out_pool, af->data, data->samples);
- if (!out) {
- talloc_free(data);
- return -1;
- }
- mp_audio_copy_attributes(out, data);
-
- size_t len = mp_audio_psize(data) / data->bps;
- if (data->bps == 4) {
- for (int s = 0; s < len; s++) {
- uint32_t val = *((uint32_t *)data->planes[0] + s);
- uint8_t *ptr = (uint8_t *)out->planes[0] + s * 3;
- ptr[0] = val >> SHIFT(0);
- ptr[1] = val >> SHIFT(1);
- ptr[2] = val >> SHIFT(2);
- }
- } else {
- for (int s = 0; s < len; s++) {
- uint8_t *ptr = (uint8_t *)data->planes[0] + s * 3;
- uint32_t val = ptr[0] << SHIFT(0)
- | ptr[1] << SHIFT(1)
- | ptr[2] << SHIFT(2);
- *((uint32_t *)out->planes[0] + s) = val;
- }
- }
-
- talloc_free(data);
- af_add_output_frame(af, out);
- return 0;
-}
-
-static int af_open(struct af_instance *af)
-{
- af->control = control;
- af->filter_frame = filter;
- return AF_OK;
-}
-
-const struct af_info af_info_convert24 = {
- .info = "Convert between 24 and 32 bit sample format",
- .name = "convert24",
- .open = af_open,
- .test_conversion = test_conversion,
-};
diff --git a/audio/filter/af_convertsignendian.c b/audio/filter/af_convertsignendian.c
deleted file mode 100644
index abbd260..0000000
--- a/audio/filter/af_convertsignendian.c
+++ /dev/null
@@ -1,107 +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/>.
- */
-
-#include <stdlib.h>
-#include <assert.h>
-
-#include "af.h"
-#include "audio/format.h"
-#include "osdep/endian.h"
-
-static bool test_conversion(int src_format, int dst_format)
-{
- if ((src_format & AF_FORMAT_PLANAR) ||
- (dst_format & AF_FORMAT_PLANAR))
- return false;
- if (((src_format & ~AF_FORMAT_SIGN_MASK) ==
- (dst_format & ~AF_FORMAT_SIGN_MASK)) &&
- ((src_format & AF_FORMAT_TYPE_MASK) == AF_FORMAT_I))
- return true;
- return false;
-}
-
-static int control(struct af_instance *af, int cmd, void *arg)
-{
- switch (cmd) {
- case AF_CONTROL_REINIT: {
- struct mp_audio *in = arg;
- struct mp_audio orig_in = *in;
- struct mp_audio *out = af->data;
-
- if (!test_conversion(in->format, out->format))
- return AF_DETACH;
-
- out->rate = in->rate;
- mp_audio_set_channels(out, &in->channels);
-
- return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
- }
- case AF_CONTROL_SET_FORMAT: {
- mp_audio_set_format(af->data, *(int*)arg);
- return AF_OK;
- }
- }
- return AF_UNKNOWN;
-}
-
-static void si2us(void *data, int len, int bps)
-{
- ptrdiff_t i = -(len * bps);
- uint8_t *p = &((uint8_t *)data)[len * bps];
- if (BYTE_ORDER == LITTLE_ENDIAN && bps > 1)
- p += bps - 1;
- if (len <= 0)
- return;
- do {
- p[i] ^= 0x80;
- } while (i += bps);
-}
-
-static int filter(struct af_instance *af, struct mp_audio *data)
-{
- if (!data)
- return 0;
- if (af_make_writeable(af, data) < 0) {
- talloc_free(data);
- return -1;
- }
-
- int infmt = data->format;
- int outfmt = af->data->format;
- size_t len = data->samples * data->nch;
-
- if ((infmt & AF_FORMAT_SIGN_MASK) != (outfmt & AF_FORMAT_SIGN_MASK))
- si2us(data->planes[0], len, data->bps);
-
- mp_audio_set_format(data, outfmt);
- af_add_output_frame(af, data);
- return 0;
-}
-
-static int af_open(struct af_instance *af)
-{
- af->control = control;
- af->filter_frame = filter;
- return AF_OK;
-}
-
-const struct af_info af_info_convertsignendian = {
- .info = "Convert between sample format sign",
- .name = "convertsign",
- .open = af_open,
- .test_conversion = test_conversion,
-};
diff --git a/audio/filter/af_drc.c b/audio/filter/af_drc.c
index 4344766..472758c 100644
--- a/audio/filter/af_drc.c
+++ b/audio/filter/af_drc.c
@@ -131,7 +131,7 @@ static void method1_int16(af_drc_t *s, struct mp_audio *c)
data[i] = tmp;
}
- // Evaulation of newavg (not 100% accurate because of values clamping)
+ // Evaluation of newavg (not 100% accurate because of values clamping)
newavg = s->mul * curavg;
// Stores computed values for future smoothing
@@ -168,7 +168,7 @@ static void method1_float(af_drc_t *s, struct mp_audio *c)
for (i = 0; i < len; i++)
data[i] *= s->mul;
- // Evaulation of newavg (not 100% accurate because of values clamping)
+ // Evaluation of newavg (not 100% accurate because of values clamping)
newavg = s->mul * curavg;
// Stores computed values for future smoothing
@@ -216,7 +216,7 @@ static void method2_int16(af_drc_t *s, struct mp_audio *c)
data[i] = tmp;
}
- // Evaulation of newavg (not 100% accurate because of values clamping)
+ // Evaluation of newavg (not 100% accurate because of values clamping)
newavg = s->mul * curavg;
// Stores computed values for future smoothing
@@ -262,7 +262,7 @@ static void method2_float(af_drc_t *s, struct mp_audio *c)
for (i = 0; i < len; i++)
data[i] *= s->mul;
- // Evaulation of newavg (not 100% accurate because of values clamping)
+ // Evaluation of newavg (not 100% accurate because of values clamping)
newavg = s->mul * curavg;
// Stores computed values for future smoothing
diff --git a/audio/filter/af_dummy.c b/audio/filter/af_dummy.c
deleted file mode 100644
index 341ec7e..0000000
--- a/audio/filter/af_dummy.c
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * The name speaks for itself. This filter is a dummy and will
- * not blow up regardless of what you do with it.
- *
- * Original author: Anders
- *
- * 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 "af.h"
-
-// Initialization and runtime control
-static int control(struct af_instance* af, int cmd, void* arg)
-{
- switch(cmd){
- case AF_CONTROL_REINIT: ;
- *af->data = *(struct mp_audio*)arg;
- MP_VERBOSE(af, "Was reinitialized: %iHz/%ich/%s\n",
- af->data->rate,af->data->nch,af_fmt_to_str(af->data->format));
- return AF_OK;
- }
- return AF_UNKNOWN;
-}
-
-// Filter data through filter
-static int filter(struct af_instance* af, struct mp_audio* data)
-{
- af_add_output_frame(af, data);
- return 0;
-}
-
-// Allocate memory and set function pointers
-static int af_open(struct af_instance* af){
- af->control=control;
- af->filter_frame=filter;
- return AF_OK;
-}
-
-// Description of this filter
-const struct af_info af_info_dummy = {
- .info = "dummy",
- .name = "dummy",
- .open = af_open,
-};
diff --git a/audio/filter/af_export.c b/audio/filter/af_export.c
index f261353..6020d9d 100644
--- a/audio/filter/af_export.c
+++ b/audio/filter/af_export.c
@@ -167,7 +167,7 @@ static int filter(struct af_instance *af, struct mp_audio *data)
return 0;
struct mp_audio* c = data; // Current working data
af_export_t* s = af->priv; // Setup for this instance
- int16_t* a = c->planes[0]; // Incomming sound
+ int16_t* a = c->planes[0]; // Incoming sound
int nch = c->nch; // Number of channels
int len = c->samples*c->nch; // Number of sample in data chunk
int sz = s->sz; // buffer size (in samples)
diff --git a/audio/filter/af_hrtf.c b/audio/filter/af_hrtf.c
index 94a1599..3c8a89c 100644
--- a/audio/filter/af_hrtf.c
+++ b/audio/filter/af_hrtf.c
@@ -206,7 +206,7 @@ static inline void matrix_decode(short *in, const int k, const int il,
information about Lt, Rt correlation. This effectively reshapes
the front and rear "cones" to concentrate Lt + Rt to C and
introduce Lt - Rt in L, R. */
- /* 0.67677 is the emprical lower bound for lpr_gain. */
+ /* 0.67677 is the empirical lower bound for lpr_gain. */
c_gain = 8 * (*adapt_lpr_gain - 0.67677);
c_gain = c_gain > 0 ? c_gain : 0;
/* c_gain should not be too high, not even reaching full
diff --git a/audio/filter/af_ladspa.c b/audio/filter/af_ladspa.c
index bd54dbb..edde6a6 100644
--- a/audio/filter/af_ladspa.c
+++ b/audio/filter/af_ladspa.c
@@ -144,7 +144,7 @@ static int af_ladspa_parse_plugin(struct af_instance *af) {
LADSPA_PortRangeHint hint;
if (!setup->libhandle)
- return AF_ERROR; /* only call parse after a succesful load */
+ return AF_ERROR; /* only call parse after a successful load */
if (!setup->plugin_descriptor)
return AF_ERROR; /* same as above */
diff --git a/audio/filter/af_lavcac3enc.c b/audio/filter/af_lavcac3enc.c
index a1651a0..21595a6 100644
--- a/audio/filter/af_lavcac3enc.c
+++ b/audio/filter/af_lavcac3enc.c
@@ -26,7 +26,6 @@
#include <assert.h>
#include <libavcodec/avcodec.h>
-#include <libavutil/audioconvert.h>
#include <libavutil/intreadwrite.h>
#include <libavutil/common.h>
#include <libavutil/bswap.h>
@@ -75,7 +74,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
struct mp_audio *in = arg;
struct mp_audio orig_in = *in;
- if (AF_FORMAT_IS_SPECIAL(in->format) || in->nch < s->cfg_min_channel_num)
+ if (!af_fmt_is_pcm(in->format) || in->nch < s->cfg_min_channel_num)
return AF_DETACH;
mp_audio_set_format(in, s->in_sampleformat);
@@ -156,6 +155,13 @@ static void uninit(struct af_instance* af)
}
}
+static void update_delay(struct af_instance *af)
+{
+ af_ac3enc_t *s = af->priv;
+ af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples) /
+ (double)s->input->rate;
+}
+
static int filter_frame(struct af_instance *af, struct mp_audio *audio)
{
af_ac3enc_t *s = af->priv;
@@ -166,6 +172,7 @@ static int filter_frame(struct af_instance *af, struct mp_audio *audio)
talloc_free(s->pending);
s->pending = audio;
+ update_delay(af);
return 0;
}
@@ -177,22 +184,27 @@ static void swap_16(uint16_t *ptr, size_t size)
// Copy data from input frame to encode frame (because libavcodec wants a full
// AC3 frame for encoding, while filter input frames can be smaller or larger).
-// Return true if the
-static bool fill_buffer(af_ac3enc_t *s)
+// Return true if the frame is complete.
+static bool fill_buffer(struct af_instance *af)
{
+ af_ac3enc_t *s = af->priv;
+
+ af->delay = 0;
+
if (s->pending) {
int copy = MPMIN(s->in_samples - s->input->samples, s->pending->samples);
s->input->samples += copy;
mp_audio_copy(s->input, s->input->samples - copy, s->pending, 0, copy);
mp_audio_skip_samples(s->pending, copy);
}
+ update_delay(af);
return s->input->samples >= s->in_samples;
}
static int filter_out(struct af_instance *af)
{
af_ac3enc_t *s = af->priv;
- if (!fill_buffer(s))
+ if (!fill_buffer(af))
return 0; // need more input
AVFrame *frame = av_frame_alloc();
@@ -256,6 +268,7 @@ static int filter_out(struct af_instance *af)
swap_16((uint16_t *)(buf + header_len), s->pkt.size / 2);
out->samples = frame_size / out->sstride;
af_add_output_frame(af, out);
+ update_delay(af);
return 0;
}
diff --git a/audio/filter/af_lavrresample.c b/audio/filter/af_lavrresample.c
index ba67eb1..39f3943 100644
--- a/audio/filter/af_lavrresample.c
+++ b/audio/filter/af_lavrresample.c
@@ -28,7 +28,6 @@
#include <assert.h>
#include <libavutil/opt.h>
-#include <libavutil/audioconvert.h>
#include <libavutil/common.h>
#include <libavutil/samplefmt.h>
#include <libavutil/mathematics.h>
@@ -58,6 +57,7 @@
#include "options/m_option.h"
#include "audio/filter/af.h"
#include "audio/fmt-conversion.h"
+#include "osdep/endian.h"
struct af_resample_opts {
int filter_size;
@@ -78,10 +78,10 @@ struct af_resample {
int allow_detach;
char **avopts;
double playback_speed;
- struct mp_audio *pending;
- bool avrctx_ok;
struct AVAudioResampleContext *avrctx;
struct mp_audio avrctx_fmt; // output format of avrctx
+ struct mp_audio pool_fmt; // format used to allocate frames for avrctx output
+ struct mp_audio pre_out_fmt; // format before final conversion (S24)
struct AVAudioResampleContext *avrctx_out; // for output channel reordering
struct af_resample_opts ctx; // opts in the context
struct af_resample_opts opts; // opts requested by the user
@@ -92,33 +92,52 @@ struct af_resample {
};
#if HAVE_LIBAVRESAMPLE
-static int get_delay(struct af_resample *s)
+static double get_delay(struct af_resample *s)
{
- return avresample_get_delay(s->avrctx);
+ return avresample_get_delay(s->avrctx) / (double)s->ctx.in_rate +
+ avresample_available(s->avrctx) / (double)s->ctx.out_rate;
}
static void drop_all_output(struct af_resample *s)
{
while (avresample_read(s->avrctx, NULL, 1000) > 0) {}
}
-static int get_drain_samples(struct af_resample *s)
+static int get_out_samples(struct af_resample *s, int in_samples)
{
- return avresample_get_out_samples(s->avrctx, 0);
+ return avresample_get_out_samples(s->avrctx, in_samples);
}
#else
-static int get_delay(struct af_resample *s)
+static double get_delay(struct af_resample *s)
{
- return swr_get_delay(s->avrctx, s->ctx.in_rate);
+ int64_t base = s->ctx.in_rate * (int64_t)s->ctx.out_rate;
+ return swr_get_delay(s->avrctx, base) / (double)base;
}
static void drop_all_output(struct af_resample *s)
{
while (swr_drop_output(s->avrctx, 1000) > 0) {}
}
-static int get_drain_samples(struct af_resample *s)
+static int get_out_samples(struct af_resample *s, int in_samples)
{
- return 4096; // libswscale does not have this
+#if LIBSWRESAMPLE_VERSION_MAJOR > 1 || LIBSWRESAMPLE_VERSION_MINOR >= 2
+ return swr_get_out_samples(s->avrctx, in_samples);
+#else
+ return av_rescale_rnd(in_samples, s->ctx.out_rate, s->ctx.in_rate, AV_ROUND_UP)
+ + swr_get_delay(s->avrctx, s->ctx.out_rate);
+#endif
}
#endif
+static void close_lavrr(struct af_instance *af)
+{
+ struct af_resample *s = af->priv;
+
+ if (s->avrctx)
+ avresample_close(s->avrctx);
+ avresample_free(&s->avrctx);
+ if (s->avrctx_out)
+ avresample_close(s->avrctx_out);
+ avresample_free(&s->avrctx_out);
+}
+
static int resample_frame(struct AVAudioResampleContext *r,
struct mp_audio *out, struct mp_audio *in)
{
@@ -141,27 +160,21 @@ static int rate_from_speed(int rate, double speed)
return lrint(rate * speed);
}
-static bool needs_lavrctx_reconfigure(struct af_resample *s,
- struct mp_audio *in,
- struct mp_audio *out)
+// Return the format libavresample should convert to, given the final output
+// format mp_format. In some cases (S24) we perform an extra conversion step,
+// and signal here what exactly libavresample should output. It will be the
+// input to the final conversion to mp_format.
+static int check_output_conversion(int mp_format)
{
- return s->ctx.in_rate_af != in->rate ||
- s->ctx.in_format != in->format ||
- !mp_chmap_equals(&s->ctx.in_channels, &in->channels) ||
- s->ctx.out_rate != out->rate ||
- s->ctx.out_format != out->format ||
- !mp_chmap_equals(&s->ctx.out_channels, &out->channels) ||
- s->ctx.filter_size != s->opts.filter_size ||
- s->ctx.phase_shift != s->opts.phase_shift ||
- s->ctx.linear != s->opts.linear ||
- s->ctx.cutoff != s->opts.cutoff;
-
+ if (mp_format == AF_FORMAT_S24)
+ return AV_SAMPLE_FMT_S32;
+ return af_to_avformat(mp_format);
}
-static bool test_conversion(int src_format, int dst_format)
+bool af_lavrresample_test_conversion(int src_format, int dst_format)
{
return af_to_avformat(src_format) != AV_SAMPLE_FMT_NONE &&
- af_to_avformat(dst_format) != AV_SAMPLE_FMT_NONE;
+ check_output_conversion(dst_format) != AV_SAMPLE_FMT_NONE;
}
// mp_chmap_get_reorder() performs:
@@ -181,27 +194,25 @@ static void transpose_order(int *map, int num)
}
static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
- struct mp_audio *out)
+ struct mp_audio *out, bool verbose)
{
struct af_resample *s = af->priv;
- s->avrctx_ok = false;
+ close_lavrr(af);
+
+ s->avrctx = avresample_alloc_context();
+ s->avrctx_out = avresample_alloc_context();
+ if (!s->avrctx || !s->avrctx_out)
+ goto error;
enum AVSampleFormat in_samplefmt = af_to_avformat(in->format);
- enum AVSampleFormat out_samplefmt = af_to_avformat(out->format);
- enum AVSampleFormat out_samplefmtp =
- af_to_avformat(af_fmt_to_planar(out->format));
+ enum AVSampleFormat out_samplefmt = check_output_conversion(out->format);
+ enum AVSampleFormat out_samplefmtp = av_get_planar_sample_fmt(out_samplefmt);
if (in_samplefmt == AV_SAMPLE_FMT_NONE ||
out_samplefmt == AV_SAMPLE_FMT_NONE ||
out_samplefmtp == AV_SAMPLE_FMT_NONE)
- return AF_ERROR;
-
- avresample_close(s->avrctx);
- avresample_close(s->avrctx_out);
-
- talloc_free(s->pending);
- s->pending = NULL;
+ goto error;
s->ctx.out_rate = out->rate;
s->ctx.in_rate_af = in->rate;
@@ -226,7 +237,7 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
#endif
if (mp_set_avopts(af->log, s->avrctx, s->avopts) < 0)
- return AF_ERROR;
+ goto error;
struct mp_chmap map_in = in->channels;
struct mp_chmap map_out = out->channels;
@@ -241,19 +252,24 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
uint64_t in_ch_layout = mp_chmap_to_lavc_unchecked(&map_in);
uint64_t out_ch_layout = mp_chmap_to_lavc_unchecked(&map_out);
- struct mp_chmap in_lavc;
+ struct mp_chmap in_lavc, out_lavc;
mp_chmap_from_lavc(&in_lavc, in_ch_layout);
+ mp_chmap_from_lavc(&out_lavc, out_ch_layout);
+
+ if (verbose && !mp_chmap_equals(&in_lavc, &out_lavc)) {
+ MP_VERBOSE(af, "Remix: %s -> %s\n", mp_chmap_to_str(&in_lavc),
+ mp_chmap_to_str(&out_lavc));
+ }
+
if (in_lavc.num != map_in.num) {
// For handling NA channels, we would have to add a planarization step.
MP_FATAL(af, "Unsupported channel remapping.\n");
- return AF_ERROR;
+ goto error;
}
mp_chmap_get_reorder(s->reorder_in, &map_in, &in_lavc);
transpose_order(s->reorder_in, map_in.num);
- struct mp_chmap out_lavc;
- mp_chmap_from_lavc(&out_lavc, out_ch_layout);
if (mp_chmap_equals(&out_lavc, &map_out)) {
// No intermediate step required - output new format directly.
out_samplefmtp = out_samplefmt;
@@ -262,14 +278,25 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
struct mp_chmap withna = out_lavc;
mp_chmap_fill_na(&withna, map_out.num);
if (withna.num != map_out.num)
- return AF_ERROR;
- mp_chmap_get_reorder(s->reorder_out, &out_lavc, &map_out);
+ goto error;
}
+ mp_chmap_get_reorder(s->reorder_out, &out_lavc, &map_out);
s->avrctx_fmt = *out;
mp_audio_set_channels(&s->avrctx_fmt, &out_lavc);
mp_audio_set_format(&s->avrctx_fmt, af_from_avformat(out_samplefmtp));
+ s->pre_out_fmt = *out;
+ mp_audio_set_format(&s->pre_out_fmt, af_from_avformat(out_samplefmt));
+
+ // If there are NA channels, the final output will have more channels than
+ // the avrctx output. Also, avrctx will output planar (out_samplefmtp was
+ // not overwritten). Allocate the output frame with more channels, so the
+ // NA channels can be trivially added.
+ s->pool_fmt = s->avrctx_fmt;
+ if (map_out.num > out_lavc.num)
+ mp_audio_set_channels(&s->pool_fmt, &map_out);
+
// Real conversion; output is input to avrctx_out.
av_opt_set_int(s->avrctx, "in_channel_layout", in_ch_layout, 0);
av_opt_set_int(s->avrctx, "out_channel_layout", out_ch_layout, 0);
@@ -281,7 +308,7 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
// Just needs the correct number of channels.
int fake_out_ch_layout = av_get_default_channel_layout(map_out.num);
if (!fake_out_ch_layout)
- return AF_ERROR;
+ goto error;
// Deplanarize if needed.
av_opt_set_int(s->avrctx_out, "in_channel_layout", fake_out_ch_layout, 0);
@@ -296,14 +323,15 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
// * Also, the input channel layout must have already been set.
avresample_set_channel_mapping(s->avrctx, s->reorder_in);
- if (avresample_open(s->avrctx) < 0 ||
- avresample_open(s->avrctx_out) < 0)
- {
+ if (avresample_open(s->avrctx) < 0 || avresample_open(s->avrctx_out) < 0) {
MP_ERR(af, "Cannot open Libavresample Context. \n");
- return AF_ERROR;
+ goto error;
}
- s->avrctx_ok = true;
return AF_OK;
+
+error:
+ close_lavrr(af);
+ return AF_ERROR;
}
@@ -331,20 +359,20 @@ static int control(struct af_instance *af, int cmd, void *arg)
if (af_to_avformat(in->format) == AV_SAMPLE_FMT_NONE)
mp_audio_set_format(in, AF_FORMAT_FLOAT);
- if (af_to_avformat(out->format) == AV_SAMPLE_FMT_NONE)
+ if (check_output_conversion(out->format) == AV_SAMPLE_FMT_NONE)
mp_audio_set_format(out, in->format);
int r = ((in->format == orig_in.format) &&
mp_chmap_equals(&in->channels, &orig_in.channels))
? AF_OK : AF_FALSE;
- if (r == AF_OK && needs_lavrctx_reconfigure(s, in, out))
- r = configure_lavrr(af, in, out);
+ if (r == AF_OK)
+ r = configure_lavrr(af, in, out, true);
return r;
}
case AF_CONTROL_SET_FORMAT: {
int format = *(int *)arg;
- if (format && af_to_avformat(format) == AV_SAMPLE_FMT_NONE)
+ if (format && check_output_conversion(format) == AV_SAMPLE_FMT_NONE)
return AF_FALSE;
mp_audio_set_format(af->data, format);
@@ -360,88 +388,89 @@ static int control(struct af_instance *af, int cmd, void *arg)
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
s->playback_speed = *(double *)arg;
int new_rate = rate_from_speed(s->ctx.in_rate_af, s->playback_speed);
- if (new_rate != s->ctx.in_rate && s->avrctx_ok && af->fmt_out.format) {
+ if (new_rate != s->ctx.in_rate && s->avrctx && af->fmt_out.format) {
// Before reconfiguring, drain the audio that is still buffered
// in the resampler.
- struct mp_audio *pending = talloc_zero(NULL, struct mp_audio);
- mp_audio_copy_config(pending, &af->fmt_out);
- pending->samples = get_drain_samples(s);
- if (pending->samples > 0) {
- mp_audio_realloc_min(pending, pending->samples);
- int r = resample_frame(s->avrctx, pending, NULL);
- pending->samples = MPMAX(r, 0);
- }
+ af->filter_frame(af, NULL);
// Reinitialize resampler.
- configure_lavrr(af, &af->fmt_in, &af->fmt_out);
- s->pending = pending;
+ configure_lavrr(af, &af->fmt_in, &af->fmt_out, false);
}
return AF_OK;
}
case AF_CONTROL_RESET:
- drop_all_output(s);
+ if (s->avrctx)
+ drop_all_output(s);
return AF_OK;
}
return AF_UNKNOWN;
}
-#undef ctx_opt_set_int
-#undef ctx_opt_set_dbl
-
static void uninit(struct af_instance *af)
{
- struct af_resample *s = af->priv;
- if (s->avrctx)
- avresample_close(s->avrctx);
- avresample_free(&s->avrctx);
- if (s->avrctx_out)
- avresample_close(s->avrctx_out);
- avresample_free(&s->avrctx_out);
- talloc_free(s->pending);
+ close_lavrr(af);
+}
+
+// The LSB is always ignored.
+#if BYTE_ORDER == BIG_ENDIAN
+#define SHIFT24(x) ((3-(x))*8)
+#else
+#define SHIFT24(x) (((x)+1)*8)
+#endif
+
+static void extra_output_conversion(struct af_instance *af, struct mp_audio *mpa)
+{
+ if (mpa->format == AF_FORMAT_S32 && af->data->format == AF_FORMAT_S24) {
+ size_t len = mp_audio_psize(mpa) / mpa->bps;
+ for (int s = 0; s < len; s++) {
+ uint32_t val = *((uint32_t *)mpa->planes[0] + s);
+ uint8_t *ptr = (uint8_t *)mpa->planes[0] + s * 3;
+ ptr[0] = val >> SHIFT24(0);
+ ptr[1] = val >> SHIFT24(1);
+ ptr[2] = val >> SHIFT24(2);
+ }
+ mp_audio_set_format(mpa, AF_FORMAT_S24);
+ }
}
+// This relies on the tricky way mpa was allocated.
static void reorder_planes(struct mp_audio *mpa, int *reorder,
struct mp_chmap *newmap)
{
struct mp_audio prev = *mpa;
+ mp_audio_set_channels(mpa, newmap);
- for (int n = 0; n < newmap->num; n++) {
+ // The trailing planes were never written by avrctx, they're the NA channels.
+ int next_na = prev.num_planes;
+
+ for (int n = 0; n < mpa->num_planes; n++) {
int src = reorder[n];
- if (src < 0) {
- src = 0; // Use the first plane for padding channels.
- mpa->readonly = true;
+ assert(src >= -1 && src < prev.num_planes);
+ if (src >= 0) {
+ mpa->planes[n] = prev.planes[src];
+ } else {
+ assert(next_na < mpa->num_planes);
+ mpa->planes[n] = prev.planes[next_na++];
+ af_fill_silence(mpa->planes[n], mpa->sstride * mpa->samples,
+ mpa->format);
}
- assert(src < mpa->num_planes);
- mpa->planes[n] = prev.planes[src];
}
-
- mp_audio_set_channels(mpa, newmap);
}
static int filter(struct af_instance *af, struct mp_audio *in)
{
struct af_resample *s = af->priv;
- if (s->pending) {
- if (s->pending->samples) {
- af_add_output_frame(af, s->pending);
- } else {
- talloc_free(s->pending);
- }
- s->pending = NULL;
- }
+ int samples = get_out_samples(s, in ? in->samples : 0);
- int samples = avresample_available(s->avrctx) +
- av_rescale_rnd(get_delay(s) + (in ? in->samples : 0),
- s->ctx.out_rate, s->ctx.in_rate, AV_ROUND_UP);
-
- struct mp_audio out_format = s->avrctx_fmt;
+ struct mp_audio out_format = s->pool_fmt;
struct mp_audio *out = mp_audio_pool_get(af->out_pool, &out_format, samples);
if (!out)
goto error;
if (in)
mp_audio_copy_attributes(out, in);
- af->delay = get_delay(s) / (double)s->ctx.in_rate;
+ if (!s->avrctx)
+ goto error;
if (out->samples) {
out->samples = resample_frame(s->avrctx, out, in);
@@ -449,11 +478,15 @@ static int filter(struct af_instance *af, struct mp_audio *in)
goto error;
}
- if (out->samples && !mp_audio_config_equals(out, af->data)) {
- assert(AF_FORMAT_IS_PLANAR(out->format));
- reorder_planes(out, s->reorder_out, &af->data->channels);
- if (!mp_audio_config_equals(out, af->data)) {
- struct mp_audio *new = mp_audio_pool_get(s->reorder_buffer, af->data,
+ struct mp_audio real_out = *out;
+ mp_audio_copy_config(out, &s->avrctx_fmt);
+
+ if (out->samples && !mp_audio_config_equals(out, &s->pre_out_fmt)) {
+ assert(af_fmt_is_planar(out->format) && out->format == real_out.format);
+ reorder_planes(out, s->reorder_out, &s->pool_fmt.channels);
+ if (!mp_audio_config_equals(out, &s->pre_out_fmt)) {
+ struct mp_audio *new = mp_audio_pool_get(s->reorder_buffer,
+ &s->pre_out_fmt,
out->samples);
if (!new)
goto error;
@@ -466,12 +499,17 @@ static int filter(struct af_instance *af, struct mp_audio *in)
}
}
+ extra_output_conversion(af, out);
+
talloc_free(in);
if (out->samples) {
af_add_output_frame(af, out);
} else {
talloc_free(out);
}
+
+ af->delay = get_delay(s);
+
return 0;
error:
talloc_free(in);
@@ -490,17 +528,9 @@ static int af_open(struct af_instance *af)
if (s->opts.cutoff <= 0.0)
s->opts.cutoff = af_resample_default_cutoff(s->opts.filter_size);
- s->avrctx = avresample_alloc_context();
- s->avrctx_out = avresample_alloc_context();
s->reorder_buffer = mp_audio_pool_create(s);
- if (s->avrctx && s->avrctx_out) {
- return AF_OK;
- } else {
- MP_ERR(af, "Cannot initialize Libavresample Context. \n");
- uninit(af);
- return AF_ERROR;
- }
+ return AF_OK;
}
#define OPT_BASE_STRUCT struct af_resample
@@ -509,7 +539,6 @@ const struct af_info af_info_lavrresample = {
.info = "Sample frequency conversion using libavresample",
.name = "lavrresample",
.open = af_open,
- .test_conversion = test_conversion,
.priv_size = sizeof(struct af_resample),
.priv_defaults = &(const struct af_resample) {
.opts = {
diff --git a/audio/filter/af_volume.c b/audio/filter/af_volume.c
index a0b47e7..1b564f0 100644
--- a/audio/filter/af_volume.c
+++ b/audio/filter/af_volume.c
@@ -30,18 +30,28 @@
#include "demux/demux.h"
struct priv {
+ float vol; // User-specified non-linear volume
float level; // User-specified gain level for each channel
float rgain; // Replaygain level
int rgain_track; // Enable/disable track based replaygain
int rgain_album; // Enable/disable album based replaygain
float rgain_preamp; // Set replaygain pre-amplification
int rgain_clip; // Enable/disable clipping prevention
+ float replaygain_fallback;
int soft; // Enable/disable soft clipping
int fast; // Use fix-point volume control
int detach; // Detach if gain volume is neutral
float cfg_volume;
};
+// Convert to gain value from dB. input <= -200dB will become 0 gain.
+static float from_dB(float in, float k, float mi, float ma)
+{
+ if (in <= -200)
+ return 0.0;
+ return pow(10.0, MPCLAMP(in, mi, ma) / k);
+}
+
static int control(struct af_instance *af, int cmd, void *arg)
{
struct priv *s = af->priv;
@@ -58,7 +68,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
} else {
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
}
- if (AF_FORMAT_IS_PLANAR(in->format))
+ if (af_fmt_is_planar(in->format))
mp_audio_set_format(af->data, af_fmt_to_planar(af->data->format));
s->rgain = 1.0;
if ((s->rgain_track || s->rgain_album) && af->replaygain_data) {
@@ -73,7 +83,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
}
gain += s->rgain_preamp;
- af_from_dB(1, &gain, &s->rgain, 20.0, -200.0, 60.0);
+ s->rgain = from_dB(gain, 20.0, -200.0, 60.0);
MP_VERBOSE(af, "Applying replay-gain: %f\n", s->rgain);
@@ -81,16 +91,21 @@ static int control(struct af_instance *af, int cmd, void *arg)
s->rgain = MPMIN(s->rgain, 1.0 / peak);
MP_VERBOSE(af, "...with clipping prevention: %f\n", s->rgain);
}
+ } else if (s->replaygain_fallback) {
+ s->rgain = from_dB(s->replaygain_fallback, 20.0, -200.0, 60.0);
+ MP_VERBOSE(af, "Applying fallback gain: %f\n", s->rgain);
}
if (s->detach && fabs(s->level * s->rgain - 1.0) < 0.00001)
return AF_DETACH;
return af_test_output(af, in);
}
case AF_CONTROL_SET_VOLUME:
- s->level = *(float *)arg;
+ s->vol = *(float *)arg;
+ s->level = pow(s->vol, 3);
+ MP_VERBOSE(af, "volume gain: %f\n", s->level);
return AF_OK;
case AF_CONTROL_GET_VOLUME:
- *(float *)arg = s->level;
+ *(float *)arg = s->vol;
return AF_OK;
}
return AF_UNKNOWN;
@@ -143,7 +158,7 @@ static int af_open(struct af_instance *af)
struct priv *s = af->priv;
af->control = control;
af->filter_frame = filter;
- af_from_dB(1, &s->cfg_volume, &s->level, 20.0, -200.0, 60.0);
+ s->level = from_dB(s->cfg_volume, 20.0, -200.0, 60.0);
return AF_OK;
}
@@ -162,6 +177,7 @@ const struct af_info af_info_volume = {
OPT_FLAG("replaygain-album", rgain_album, 0),
OPT_FLOATRANGE("replaygain-preamp", rgain_preamp, 0, -15, 15),
OPT_FLAG("replaygain-clip", rgain_clip, 0),
+ OPT_FLOATRANGE("replaygain-fallback", replaygain_fallback, 0, -200, 60),
OPT_FLAG("softclip", soft, 0),
OPT_FLAG("s16", fast, 0),
OPT_FLAG("detach", detach, 0),
diff --git a/audio/filter/tools.c b/audio/filter/tools.c
index a465e7e..4ebea64 100644
--- a/audio/filter/tools.c
+++ b/audio/filter/tools.c
@@ -21,24 +21,6 @@
#include "common/common.h"
#include "af.h"
-/* Convert to gain value from dB. Returns AF_OK if of and AF_ERROR if
- * fail. input <= -200dB will become 0 gain. */
-int af_from_dB(int n, float* in, float* out, float k, float mi, float ma)
-{
- int i = 0;
- // Sanity check
- if(!in || !out)
- return AF_ERROR;
-
- for(i=0;i<n;i++){
- if(in[i]<=-200)
- out[i]=0.0;
- else
- out[i]=pow(10.0,MPCLAMP(in[i],mi,ma)/k);
- }
- return AF_OK;
-}
-
/* Convert from ms to sample time */
int af_from_ms(int n, float* in, int* out, int rate, float mi, float ma)
{
diff --git a/audio/format.c b/audio/format.c
index 4012142..36cfb9b 100644
--- a/audio/format.c
+++ b/audio/format.c
@@ -27,42 +27,68 @@
#include "common/common.h"
#include "audio/filter/af.h"
-int af_fmt2bps(int format)
-{
- switch (format & AF_FORMAT_BITS_MASK) {
- case AF_FORMAT_8BIT: return 1;
- case AF_FORMAT_16BIT: return 2;
- case AF_FORMAT_24BIT: return 3;
- case AF_FORMAT_32BIT: return 4;
- case AF_FORMAT_64BIT: return 8;
+// number of bytes per sample, 0 if invalid/unknown
+int af_fmt_to_bytes(int format)
+{
+ switch (af_fmt_from_planar(format)) {
+ case AF_FORMAT_U8: return 1;
+ case AF_FORMAT_S16: return 2;
+ case AF_FORMAT_S24: return 3;
+ case AF_FORMAT_S32: return 4;
+ case AF_FORMAT_FLOAT: return 4;
+ case AF_FORMAT_DOUBLE: return 8;
}
+ if (af_fmt_is_spdif(format))
+ return 2;
return 0;
}
-int af_fmt2bits(int format)
+int af_fmt_change_bytes(int format, int bytes)
{
- return af_fmt2bps(format) * 8;
+ if (!af_fmt_is_valid(format) || !bytes)
+ return 0;
+ for (int fmt = 1; fmt < AF_FORMAT_COUNT; fmt++) {
+ if (af_fmt_to_bytes(fmt) == bytes &&
+ af_fmt_is_float(fmt) == af_fmt_is_float(format) &&
+ af_fmt_is_planar(fmt) == af_fmt_is_planar(format) &&
+ af_fmt_is_spdif(fmt) == af_fmt_is_spdif(format))
+ return fmt;
+ }
+ return 0;
}
-static int bits_to_mask(int bits)
+// All formats are considered signed, except explicitly unsigned int formats.
+bool af_fmt_is_unsigned(int format)
{
- switch (bits) {
- case 8: return AF_FORMAT_8BIT;
- case 16: return AF_FORMAT_16BIT;
- case 24: return AF_FORMAT_24BIT;
- case 32: return AF_FORMAT_32BIT;
- case 64: return AF_FORMAT_64BIT;
- }
- return 0;
+ return format == AF_FORMAT_U8 || format == AF_FORMAT_U8P;
}
-int af_fmt_change_bits(int format, int bits)
+bool af_fmt_is_float(int format)
{
- if (!af_fmt_is_valid(format))
- return 0;
- int mask = bits_to_mask(bits);
- format = (format & ~AF_FORMAT_BITS_MASK) | mask;
- return af_fmt_is_valid(format) ? format : 0;
+ format = af_fmt_from_planar(format);
+ return format == AF_FORMAT_FLOAT || format == AF_FORMAT_DOUBLE;
+}
+
+// true for both unsigned and signed ints
+bool af_fmt_is_int(int format)
+{
+ return format && !af_fmt_is_spdif(format) && !af_fmt_is_float(format);
+}
+
+// false for interleaved and AF_FORMAT_UNKNOWN
+bool af_fmt_is_planar(int format)
+{
+ return format && af_fmt_to_planar(format) == format;
+}
+
+bool af_fmt_is_spdif(int format)
+{
+ return af_format_sample_alignment(format) > 1;
+}
+
+bool af_fmt_is_pcm(int format)
+{
+ return af_fmt_is_valid(format) && !af_fmt_is_spdif(format);
}
static const int planar_formats[][2] = {
@@ -99,58 +125,40 @@ int af_fmt_from_planar(int format)
return format;
}
-const struct af_fmt_entry af_fmtstr_table[] = {
- {"u8", AF_FORMAT_U8},
- {"s8", AF_FORMAT_S8},
- {"u16", AF_FORMAT_U16},
- {"s16", AF_FORMAT_S16},
- {"u24", AF_FORMAT_U24},
- {"s24", AF_FORMAT_S24},
- {"u32", AF_FORMAT_U32},
- {"s32", AF_FORMAT_S32},
- {"float", AF_FORMAT_FLOAT},
- {"double", AF_FORMAT_DOUBLE},
-
- {"u8p", AF_FORMAT_U8P},
- {"s16p", AF_FORMAT_S16P},
- {"s32p", AF_FORMAT_S32P},
- {"floatp", AF_FORMAT_FLOATP},
- {"doublep", AF_FORMAT_DOUBLEP},
-
- {"spdif-aac", AF_FORMAT_S_AAC},
- {"spdif-ac3", AF_FORMAT_S_AC3},
- {"spdif-dts", AF_FORMAT_S_DTS},
- {"spdif-dtshd", AF_FORMAT_S_DTSHD},
- {"spdif-eac3", AF_FORMAT_S_EAC3},
- {"spdif-mp3", AF_FORMAT_S_MP3},
- {"spdif-truehd",AF_FORMAT_S_TRUEHD},
-
- {0}
-};
-
bool af_fmt_is_valid(int format)
{
- for (int i = 0; af_fmtstr_table[i].name; i++) {
- if (af_fmtstr_table[i].format == format)
- return true;
- }
- return false;
+ return format > 0 && format < AF_FORMAT_COUNT;
}
const char *af_fmt_to_str(int format)
{
- for (int i = 0; af_fmtstr_table[i].name; i++) {
- if (af_fmtstr_table[i].format == format)
- return af_fmtstr_table[i].name;
+ switch (format) {
+ case AF_FORMAT_U8: return "u8";
+ case AF_FORMAT_S16: return "s16";
+ case AF_FORMAT_S24: return "s24";
+ case AF_FORMAT_S32: return "s32";
+ case AF_FORMAT_FLOAT: return "float";
+ case AF_FORMAT_DOUBLE: return "double";
+ case AF_FORMAT_U8P: return "u8p";
+ case AF_FORMAT_S16P: return "s16p";
+ case AF_FORMAT_S32P: return "s32p";
+ case AF_FORMAT_FLOATP: return "floatp";
+ case AF_FORMAT_DOUBLEP: return "doublep";
+ case AF_FORMAT_S_AAC: return "spdif-aac";
+ case AF_FORMAT_S_AC3: return "spdif-ac3";
+ case AF_FORMAT_S_DTS: return "spdif-dts";
+ case AF_FORMAT_S_DTSHD: return "spdif-dtshd";
+ case AF_FORMAT_S_EAC3: return "spdif-eac3";
+ case AF_FORMAT_S_MP3: return "spdif-mp3";
+ case AF_FORMAT_S_TRUEHD: return "spdif-truehd";
}
-
return "??";
}
int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int samplerate)
{
- assert(!AF_FORMAT_IS_PLANAR(format));
- int bps = af_fmt2bps(format);
+ assert(!af_fmt_is_planar(format));
+ int bps = af_fmt_to_bytes(format);
int framelen = channels * bps;
int bytes = seconds * bps * samplerate;
if (bytes % framelen)
@@ -158,19 +166,9 @@ int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int sampler
return bytes;
}
-int af_str2fmt_short(bstr str)
-{
- for (int i = 0; af_fmtstr_table[i].name; i++) {
- if (!bstrcasecmp0(str, af_fmtstr_table[i].name))
- return af_fmtstr_table[i].format;
- }
- return 0;
-}
-
void af_fill_silence(void *dst, size_t bytes, int format)
{
- bool us = (format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_US;
- memset(dst, us ? 0x80 : 0, bytes);
+ memset(dst, af_fmt_is_unsigned(format) ? 0x80 : 0, bytes);
}
#define FMT_DIFF(type, a, b) (((a) & type) - ((b) & type))
@@ -186,33 +184,30 @@ int af_format_conversion_score(int dst_format, int src_format)
if (dst_format == src_format)
return 1024;
// Can't be normally converted
- if (AF_FORMAT_IS_SPECIAL(dst_format) || AF_FORMAT_IS_SPECIAL(src_format))
+ if (!af_fmt_is_pcm(dst_format) || !af_fmt_is_pcm(src_format))
return INT_MIN;
int score = 1024;
- if (FMT_DIFF(AF_FORMAT_INTERLEAVING_MASK, dst_format, src_format))
+ if (af_fmt_is_planar(dst_format) != af_fmt_is_planar(src_format))
score -= 1; // has to (de-)planarize
- if (FMT_DIFF(AF_FORMAT_SIGN_MASK, dst_format, src_format))
- score -= 4; // has to swap sign
- if (FMT_DIFF(AF_FORMAT_TYPE_MASK, dst_format, src_format)) {
- int dst_bits = dst_format & AF_FORMAT_BITS_MASK;
- if ((dst_format & AF_FORMAT_TYPE_MASK) == AF_FORMAT_F) {
+ if (af_fmt_is_float(dst_format) != af_fmt_is_float(src_format)) {
+ int dst_bytes = af_fmt_to_bytes(dst_format);
+ if (af_fmt_is_float(dst_format)) {
// For int->float, always prefer 32 bit float.
- score -= dst_bits == AF_FORMAT_32BIT ? 8 : 0;
+ score -= dst_bytes == 4 ? 1 : 0;
} else {
// For float->int, always prefer highest bit depth int
- score -= 8 * (AF_FORMAT_64BIT - dst_bits);
+ score -= 8 - dst_bytes;
}
+ // Has to convert float<->int - Consider this the worst case.
+ score -= 2048;
} else {
- int bits = FMT_DIFF(AF_FORMAT_BITS_MASK, dst_format, src_format);
- if (bits > 0) {
- score -= 8 * bits; // has to add padding
- } else if (bits < 0) {
- score -= 1024 - 8 * bits; // has to reduce bit depth
+ int bytes = af_fmt_to_bytes(dst_format) - af_fmt_to_bytes(src_format);
+ if (bytes > 0) {
+ score -= bytes; // has to add padding
+ } else if (bytes < 0) {
+ score -= 1024 - bytes; // has to reduce bit depth
}
}
- // Consider this the worst case.
- if (FMT_DIFF(AF_FORMAT_TYPE_MASK, dst_format, src_format))
- score -= 2048; // has to convert float<->int
return score;
}
diff --git a/audio/format.h b/audio/format.h
index c4e269f..ea17a44 100644
--- a/audio/format.h
+++ b/audio/format.h
@@ -22,96 +22,50 @@
#ifndef MPLAYER_AF_FORMAT_H
#define MPLAYER_AF_FORMAT_H
+#include <stddef.h>
#include <stdbool.h>
-#include "misc/bstr.h"
-
-// Signed/unsigned
-#define AF_FORMAT_SI (0<<0) // Signed
-#define AF_FORMAT_US (1<<0) // Unsigned
-#define AF_FORMAT_SIGN_MASK (1<<0)
-
-// Bits used
-// Some code assumes they're sorted by size.
-#define AF_FORMAT_8BIT (0<<1)
-#define AF_FORMAT_16BIT (1<<1)
-#define AF_FORMAT_24BIT (2<<1)
-#define AF_FORMAT_32BIT (3<<1)
-#define AF_FORMAT_64BIT (4<<1)
-#define AF_FORMAT_BITS_MASK (7<<1)
-
-// Fixed/floating point/special
-#define AF_FORMAT_I (1<<4) // Int
-#define AF_FORMAT_F (2<<4) // Foating point
-#define AF_FORMAT_S (4<<4) // special (IEC61937)
-#define AF_FORMAT_TYPE_MASK (7<<4)
-
-// Interleaving (planar formats have data for each channel in separate planes)
-#define AF_FORMAT_INTERLEAVED (0<<7) // must be 0
-#define AF_FORMAT_PLANAR (1<<7)
-#define AF_FORMAT_INTERLEAVING_MASK (1<<7)
-
-#define AF_FORMAT_S_CODEC(n) ((n)<<8)
-#define AF_FORMAT_S_CODEC_MASK (15 <<8) // 16 codecs max.
-
-#define AF_FORMAT_MASK ((1<<12)-1)
-
-#define AF_INTP (AF_FORMAT_I|AF_FORMAT_PLANAR)
-#define AF_FLTP (AF_FORMAT_F|AF_FORMAT_PLANAR)
-#define AF_FORMAT_S_(n) (AF_FORMAT_S_CODEC(n)|AF_FORMAT_S|AF_FORMAT_16BIT)
-
-// actual sample formats
enum af_format {
- AF_FORMAT_UNKNOWN = 0,
-
- AF_FORMAT_U8 = AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_8BIT,
- AF_FORMAT_S8 = AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_8BIT,
- AF_FORMAT_U16 = AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_16BIT,
- AF_FORMAT_S16 = AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_16BIT,
- AF_FORMAT_U24 = AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_24BIT,
- AF_FORMAT_S24 = AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_24BIT,
- AF_FORMAT_U32 = AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_32BIT,
- AF_FORMAT_S32 = AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_32BIT,
+ AF_FORMAT_UNKNOWN = 0,
- AF_FORMAT_FLOAT = AF_FORMAT_F|AF_FORMAT_32BIT,
- AF_FORMAT_DOUBLE = AF_FORMAT_F|AF_FORMAT_64BIT,
+ AF_FORMAT_U8,
+ AF_FORMAT_S16,
+ AF_FORMAT_S24,
+ AF_FORMAT_S32,
+ AF_FORMAT_FLOAT,
+ AF_FORMAT_DOUBLE,
// Planar variants
- AF_FORMAT_U8P = AF_INTP|AF_FORMAT_US|AF_FORMAT_8BIT,
- AF_FORMAT_S16P = AF_INTP|AF_FORMAT_SI|AF_FORMAT_16BIT,
- AF_FORMAT_S32P = AF_INTP|AF_FORMAT_SI|AF_FORMAT_32BIT,
- AF_FORMAT_FLOATP = AF_FLTP|AF_FORMAT_32BIT,
- AF_FORMAT_DOUBLEP = AF_FLTP|AF_FORMAT_64BIT,
+ AF_FORMAT_U8P,
+ AF_FORMAT_S16P,
+ AF_FORMAT_S32P,
+ AF_FORMAT_FLOATP,
+ AF_FORMAT_DOUBLEP,
// All of these use IEC61937 framing, and otherwise pretend to be like PCM.
- AF_FORMAT_S_AAC = AF_FORMAT_S_(0),
- AF_FORMAT_S_AC3 = AF_FORMAT_S_(1),
- AF_FORMAT_S_DTS = AF_FORMAT_S_(2),
- AF_FORMAT_S_DTSHD = AF_FORMAT_S_(3),
- AF_FORMAT_S_EAC3 = AF_FORMAT_S_(4),
- AF_FORMAT_S_MP3 = AF_FORMAT_S_(5),
- AF_FORMAT_S_TRUEHD = AF_FORMAT_S_(6),
+ AF_FORMAT_S_AAC,
+ AF_FORMAT_S_AC3,
+ AF_FORMAT_S_DTS,
+ AF_FORMAT_S_DTSHD,
+ AF_FORMAT_S_EAC3,
+ AF_FORMAT_S_MP3,
+ AF_FORMAT_S_TRUEHD,
+
+ AF_FORMAT_COUNT
};
-#define AF_FORMAT_IS_IEC61937(f) (((f) & AF_FORMAT_TYPE_MASK) == AF_FORMAT_S)
-#define AF_FORMAT_IS_SPECIAL(f) AF_FORMAT_IS_IEC61937(f)
-#define AF_FORMAT_IS_FLOAT(f) (!!((f) & AF_FORMAT_F))
-// false for interleaved and AF_FORMAT_UNKNOWN
-#define AF_FORMAT_IS_PLANAR(f) (!!((f) & AF_FORMAT_PLANAR))
-
-struct af_fmt_entry {
- const char *name;
- int format;
-};
-
-extern const struct af_fmt_entry af_fmtstr_table[];
-
-int af_str2fmt_short(bstr str);
const char *af_fmt_to_str(int format);
-int af_fmt2bps(int format);
-int af_fmt2bits(int format);
-int af_fmt_change_bits(int format, int bits);
+int af_fmt_to_bytes(int format);
+int af_fmt_change_bytes(int format, int bytes);
+
+bool af_fmt_is_valid(int format);
+bool af_fmt_is_unsigned(int format);
+bool af_fmt_is_float(int format);
+bool af_fmt_is_int(int format);
+bool af_fmt_is_planar(int format);
+bool af_fmt_is_spdif(int format);
+bool af_fmt_is_pcm(int format);
int af_fmt_to_planar(int format);
int af_fmt_from_planar(int format);
@@ -119,8 +73,6 @@ int af_fmt_from_planar(int format);
// Amount of bytes that contain audio of the given duration, aligned to frames.
int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int samplerate);
-bool af_fmt_is_valid(int format);
-
void af_fill_silence(void *dst, size_t bytes, int format);
int af_format_conversion_score(int dst_format, int src_format);
diff --git a/audio/mixer.c b/audio/mixer.c
index e56d5db..26f426c 100644
--- a/audio/mixer.c
+++ b/audio/mixer.c
@@ -69,10 +69,10 @@ bool mixer_audio_initialized(struct mixer *mixer)
return !!mixer->ao;
}
-float mixer_getneutralvolume(struct mixer *mixer)
+float mixer_getmaxvolume(struct mixer *mixer)
{
// gain == 1
- return mixer->softvol ? 1.0 / mixer->opts->softvol_max * 100.0 * 100.0 : 100;
+ return mixer->softvol ? mixer->opts->softvol_max : 100;
}
static void checkvolume(struct mixer *mixer)
@@ -85,8 +85,8 @@ static void checkvolume(struct mixer *mixer)
float gain;
if (!af_control_any_rev(mixer->af, AF_CONTROL_GET_VOLUME, &gain))
gain = 1.0;
- vol.left = (gain / (mixer->opts->softvol_max / 100.0)) * 100.0;
- vol.right = (gain / (mixer->opts->softvol_max / 100.0)) * 100.0;
+ vol.left = gain * 100.0;
+ vol.right = gain * 100.0;
} else {
MP_DBG(mixer, "Reading volume from AO.\n");
// Rely on the values not changing if the query is not supported
@@ -122,14 +122,14 @@ void mixer_getvolume(struct mixer *mixer, float *l, float *r)
static void setvolume_internal(struct mixer *mixer, float l, float r)
{
- struct ao_control_vol vol = {.left = l, .right = r};
if (!mixer->softvol) {
MP_DBG(mixer, "Setting volume on AO.\n");
+ struct ao_control_vol vol = {.left = l, .right = r};
if (ao_control(mixer->ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK)
MP_ERR(mixer, "Failed to change audio output volume.\n");
return;
}
- float gain = (l + r) / 2.0 / 100.0 * mixer->opts->softvol_max / 100.0;
+ float gain = (l + r) / 2.0 / 100.0;
if (!af_control_any_rev(mixer->af, AF_CONTROL_SET_VOLUME, &gain)) {
if (gain == 1.0)
return;
@@ -144,8 +144,9 @@ void mixer_setvolume(struct mixer *mixer, float l, float r)
{
checkvolume(mixer); // to check mute status
- mixer->vol_l = av_clipf(l, 0, 100);
- mixer->vol_r = av_clipf(r, 0, 100);
+ 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);
}
@@ -226,7 +227,7 @@ void mixer_setbalance(struct mixer *mixer, float val)
return;
}
- /* make all other channels pass thru since by default pan blocks all */
+ /* make all other channels pass through since by default pan blocks all */
for (int i = 2; i < AF_NCH; i++) {
float level[AF_NCH] = {0};
level[i] = 1.f;
@@ -242,9 +243,8 @@ char *mixer_get_volume_restore_data(struct mixer *mixer)
{
if (!mixer->driver[0])
return NULL;
- return talloc_asprintf(NULL, "%s:%f:%f:%d:%f", mixer->driver, mixer->vol_l,
- mixer->vol_r, mixer->muted_by_us,
- mixer->opts->softvol_max);
+ return talloc_asprintf(NULL, "%s:%f:%f:%d", mixer->driver, mixer->vol_l,
+ mixer->vol_r, mixer->muted_by_us);
}
static void probe_softvol(struct mixer *mixer)
@@ -315,11 +315,10 @@ static void restore_volume(struct mixer *mixer)
char *data = mixer->opts->mixer_restore_volume_data;
if (!mixer->persistent_volume && data && data[0]) {
char drv[40];
- float v_l, v_r, s;
+ float v_l, v_r;
int m;
- if (sscanf(data, "%39[^:]:%f:%f:%d:%f", drv, &v_l, &v_r, &m, &s) == 5) {
- float diff = fabs(mixer->opts->softvol_max - s);
- if (strcmp(mixer->driver, drv) == 0 && diff < 0.01) {
+ if (sscanf(data, "%39[^:]:%f:%f:%d", drv, &v_l, &v_r, &m) == 4) {
+ if (strcmp(mixer->driver, drv) == 0) {
force_vol_l = v_l;
force_vol_r = v_r;
force_mute = !!m;
diff --git a/audio/mixer.h b/audio/mixer.h
index 5aaf605..4e2ff35 100644
--- a/audio/mixer.h
+++ b/audio/mixer.h
@@ -44,7 +44,7 @@ void mixer_setmute(struct mixer *mixer, bool mute);
bool mixer_getmute(struct mixer *mixer);
void mixer_getbalance(struct mixer *mixer, float *bal);
void mixer_setbalance(struct mixer *mixer, float bal);
-float mixer_getneutralvolume(struct mixer *mixer);
+float mixer_getmaxvolume(struct mixer *mixer);
char *mixer_get_volume_restore_data(struct mixer *mixer);
#endif /* MPLAYER_MIXER_H */
diff --git a/audio/out/ao.c b/audio/out/ao.c
index f82637e..45dbbcf 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -191,14 +191,14 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
snprintf(rdevice, sizeof(rdevice), "%s", ao->device ? ao->device : "");
talloc_free(ao);
return ao_init(probing, global, input_ctx, encode_lavc_ctx,
- samplerate, format, channels, rdevice, redirect, args);
+ samplerate, format, channels, rdevice, redirect, NULL);
}
goto fail;
}
- ao->sstride = af_fmt2bps(ao->format);
+ ao->sstride = af_fmt_to_bytes(ao->format);
ao->num_planes = 1;
- if (AF_FORMAT_IS_PLANAR(ao->format)) {
+ if (af_fmt_is_planar(ao->format)) {
ao->num_planes = ao->channels.num;
} else {
ao->sstride *= ao->channels.num;
@@ -307,7 +307,8 @@ done:
// Uninitialize and destroy the AO. Remaining audio must be dropped.
void ao_uninit(struct ao *ao)
{
- ao->api->uninit(ao);
+ if (ao)
+ ao->api->uninit(ao);
talloc_free(ao);
}
@@ -413,18 +414,14 @@ bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
if (mp_msg_test(ao->log, MSGL_DEBUG)) {
for (int i = 0; i < s->num_chmaps; i++) {
struct mp_chmap c = s->chmaps[i];
- struct mp_chmap cr = c;
- mp_chmap_reorder_norm(&cr);
MP_DBG(ao, "chmap_sel #%d: %s (%s)\n", i, mp_chmap_to_str(&c),
- mp_chmap_to_str(&cr));
+ mp_chmap_to_str_hr(&c));
}
}
bool r = mp_chmap_sel_adjust(s, map);
if (r) {
- struct mp_chmap mapr = *map;
- mp_chmap_reorder_norm(&mapr);
MP_DBG(ao, "result: %s (%s)\n", mp_chmap_to_str(map),
- mp_chmap_to_str(&mapr));
+ mp_chmap_to_str_hr(map));
}
return r;
}
diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c
index fd6ea32..b32dceb 100644
--- a/audio/out/ao_alsa.c
+++ b/audio/out/ao_alsa.c
@@ -101,7 +101,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
long get_vol, set_vol;
float f_multi;
- if (AF_FORMAT_IS_SPECIAL(ao->format))
+ if (!af_fmt_is_pcm(ao->format))
return CONTROL_FALSE;
snd_mixer_selem_id_alloca(&sid);
@@ -204,14 +204,9 @@ alsa_error:
}
static const int mp_to_alsa_format[][2] = {
- {AF_FORMAT_S8, SND_PCM_FORMAT_S8},
{AF_FORMAT_U8, SND_PCM_FORMAT_U8},
- {AF_FORMAT_U16, SND_PCM_FORMAT_U16},
{AF_FORMAT_S16, SND_PCM_FORMAT_S16},
- {AF_FORMAT_U32, SND_PCM_FORMAT_U32},
{AF_FORMAT_S32, SND_PCM_FORMAT_S32},
- {AF_FORMAT_U24,
- MP_SELECT_LE_BE(SND_PCM_FORMAT_U24_3LE, SND_PCM_FORMAT_U24_3BE)},
{AF_FORMAT_S24,
MP_SELECT_LE_BE(SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S24_3BE)},
{AF_FORMAT_FLOAT, SND_PCM_FORMAT_FLOAT},
@@ -251,6 +246,8 @@ static const int alsa_to_mp_channels[][2] = {
{SND_CHMAP_TRL, MP_SP(TBL)},
{SND_CHMAP_TRR, MP_SP(TBR)},
{SND_CHMAP_TRC, MP_SP(TBC)},
+ {SND_CHMAP_RRC, MP_SP(SDR)},
+ {SND_CHMAP_RLC, MP_SP(SDL)},
{SND_CHMAP_MONO, MP_SP(FC)},
{SND_CHMAP_NA, MP_SPEAKER_ID_NA},
{SND_CHMAP_LAST, MP_SPEAKER_ID_COUNT}
@@ -304,6 +301,8 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
mp_chmap_from_alsa(&entry, &maps[i]->map);
if (mp_chmap_is_valid(&entry)) {
+ if (maps[i]->type == SND_CHMAP_TYPE_VAR)
+ mp_chmap_reorder_norm(&entry);
MP_VERBOSE(ao, "Got supported channel map: %s (type %s)\n",
mp_chmap_to_str(&entry),
snd_pcm_chmap_type_name(maps[i]->type));
@@ -375,8 +374,9 @@ static char *append_params(void *ta_parent, const char *device, const char *p)
static int try_open_device(struct ao *ao, const char *device)
{
struct priv *p = ao->priv;
+ int err;
- if (AF_FORMAT_IS_IEC61937(ao->format)) {
+ if (af_fmt_is_spdif(ao->format)) {
void *tmp = talloc_new(NULL);
char *params = talloc_asprintf(tmp,
"AES0=%d,AES1=%d,AES2=0,AES3=%d",
@@ -385,15 +385,29 @@ static int try_open_device(struct ao *ao, const char *device)
map_iec958_srate(ao->samplerate));
const char *ac3_device = append_params(tmp, device, params);
MP_VERBOSE(ao, "opening device '%s' => '%s'\n", device, ac3_device);
- int err = snd_pcm_open
- (&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, 0);
+ err = snd_pcm_open(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, 0);
+ if (err < 0) {
+ // Some spdif-capable devices do not accept the AES0 parameter,
+ // and instead require the iec958 pseudo-device (they will play
+ // noise otherwise). Unfortunately, ALSA gives us no way to map
+ // these devices, so try it for the default device only.
+ bstr dev;
+ bstr_split_tok(bstr0(device), ":", &dev, &(bstr){0});
+ if (bstr_equals0(dev, "default")) {
+ ac3_device = append_params(tmp, "iec958", params);
+ MP_VERBOSE(ao, "got error %d; opening iec fallback device '%s'\n",
+ err, ac3_device);
+ err = snd_pcm_open
+ (&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, 0);
+ }
+ }
talloc_free(tmp);
- if (!err)
- return 0;
+ } else {
+ MP_VERBOSE(ao, "opening device '%s'\n", device);
+ err = snd_pcm_open(&p->alsa, device, SND_PCM_STREAM_PLAYBACK, 0);
}
- MP_VERBOSE(ao, "opening device '%s'\n", device);
- return snd_pcm_open(&p->alsa, device, SND_PCM_STREAM_PLAYBACK, 0);
+ return err;
}
static void uninit(struct ao *ao)
@@ -419,8 +433,6 @@ static int init_device(struct ao *ao, bool second_try)
int err;
const char *device = "default";
- if (AF_FORMAT_IS_IEC61937(ao->format))
- device = "iec958";
if (ao->device)
device = ao->device;
if (p->cfg_device && p->cfg_device[0])
@@ -441,7 +453,7 @@ static int init_device(struct ao *ao, bool second_try)
err = snd_pcm_hw_params_any(p->alsa, alsa_hwparams);
CHECK_ALSA_ERROR("Unable to get initial parameters");
- if (AF_FORMAT_IS_IEC61937(ao->format)) {
+ if (af_fmt_is_spdif(ao->format)) {
if (ao->format == AF_FORMAT_S_MP3) {
p->alsa_fmt = SND_PCM_FORMAT_MPEG;
} else {
@@ -457,7 +469,7 @@ static int init_device(struct ao *ao, bool second_try)
err = snd_pcm_hw_params_test_format(p->alsa, alsa_hwparams, p->alsa_fmt);
if (err < 0) {
- if (AF_FORMAT_IS_IEC61937(ao->format))
+ if (af_fmt_is_spdif(ao->format))
CHECK_ALSA_ERROR("Unable to set IEC61937 format");
MP_INFO(ao, "Format %s is not supported by hardware, trying default.\n",
af_fmt_to_str(ao->format));
@@ -468,11 +480,11 @@ static int init_device(struct ao *ao, bool second_try)
err = snd_pcm_hw_params_set_format(p->alsa, alsa_hwparams, p->alsa_fmt);
CHECK_ALSA_ERROR("Unable to set format");
- snd_pcm_access_t access = AF_FORMAT_IS_PLANAR(ao->format)
+ snd_pcm_access_t access = af_fmt_is_planar(ao->format)
? SND_PCM_ACCESS_RW_NONINTERLEAVED
: SND_PCM_ACCESS_RW_INTERLEAVED;
err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access);
- if (err < 0 && AF_FORMAT_IS_PLANAR(ao->format)) {
+ if (err < 0 && af_fmt_is_planar(ao->format)) {
ao->format = af_fmt_from_planar(ao->format);
access = SND_PCM_ACCESS_RW_INTERLEAVED;
err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access);
@@ -480,8 +492,11 @@ static int init_device(struct ao *ao, bool second_try)
CHECK_ALSA_ERROR("Unable to set access type");
struct mp_chmap dev_chmap = ao->channels;
- if (AF_FORMAT_IS_IEC61937(ao->format) || p->cfg_ignore_chmap) {
+ if (af_fmt_is_spdif(ao->format) || p->cfg_ignore_chmap) {
dev_chmap.num = 0; // disable chmap API
+ } else if (dev_chmap.num == 1 && dev_chmap.speaker[0] == MP_SPEAKER_ID_FC) {
+ // As yet another ALSA API inconsistency, mono is not reported correctly.
+ dev_chmap.num = 0;
} else if (query_chmaps(ao, &dev_chmap)) {
ao->channels = dev_chmap;
} else {
@@ -505,8 +520,8 @@ static int init_device(struct ao *ao, bool second_try)
mp_chmap_from_channels_alsa(&ao->channels, num_channels);
if (!mp_chmap_is_valid(&ao->channels))
mp_chmap_from_channels(&ao->channels, 2);
- MP_ERR(ao, "Couldn't get requested number of channels (%d), fallback "
- "to %s.\n", req, mp_chmap_to_str(&ao->channels));
+ MP_ERR(ao, "Asked for %d channels, got %d - fallback to %s.\n", req,
+ num_channels, mp_chmap_to_str(&ao->channels));
}
// Some ALSA drivers have broken delay reporting, so disable the ALSA
@@ -556,10 +571,11 @@ static int init_device(struct ao *ao, bool second_try)
err = snd_pcm_set_chmap(p->alsa, alsa_chmap);
if (err == -ENXIO) {
- // I consider this an ALSA bug: the channel map was reported as
- // supported, but we still can't set it. It happens virtually
- // always with dmix, though.
- MP_VERBOSE(ao, "Device does not support requested channel map (%s)\n",
+ // A device my not be able to set any channel map, even channel maps
+ // that were reported as supported. This is either because the ALSA
+ // device is broken (dmix), or because the driver has only 1
+ // channel map per channel count, and setting the map is not needed.
+ MP_VERBOSE(ao, "device returned ENXIO when setting channel map %s\n",
mp_chmap_to_str(&dev_chmap));
} else {
CHECK_ALSA_WARN("Channel map setup failed");
@@ -581,7 +597,7 @@ static int init_device(struct ao *ao, bool second_try)
if (p->cfg_ignore_chmap) {
MP_VERBOSE(ao, "user set ignore-chmap; ignoring the channel map.\n");
- } else if (AF_FORMAT_IS_IEC61937(ao->format)) {
+ } else if (af_fmt_is_spdif(ao->format)) {
MP_VERBOSE(ao, "using spdif passthrough; ignoring the channel map.\n");
} else if (mp_chmap_is_valid(&chmap)) {
// Is it one that contains NA channels?
@@ -590,17 +606,18 @@ static int init_device(struct ao *ao, bool second_try)
if (mp_chmap_is_valid(&without_na) &&
!mp_chmap_equals(&without_na, &chmap) &&
+ !mp_chmap_equals(&chmap, &ao->channels) &&
!second_try)
{
// Sometimes, ALSA will advertise certain chmaps, but it's not
// possible to set them. This can happen with dmix: as of
// alsa 1.0.28, dmix can do stereo only, but advertises the
// surround chmaps of the underlying device. In this case,
- // requesting e.g. 5.1 will fail, but it will still allow
- // setting 6 channels. Then it will return something like
+ // e.g. setting 6 channels will succeed, but requesting 5.1
+ // afterwards will fail. Then it will return something like
// "FL FR NA NA NA NA" as channel map. This means we would
// have to pad stereo output to 6 channels with silence, which
- // is way too complicated in the general case. You can't change
+ // would require lots of extra processing. You can't change
// the number of channels to 2 either, because the hw params
// are already set! So just fuck it and reopen the device with
// the chmap "cleaned out" of NA entries.
@@ -625,8 +642,10 @@ static int init_device(struct ao *ao, bool second_try)
}
// mpv and ALSA use different conventions for mono
- if (ao->channels.num == 1)
+ if (ao->channels.num == 1) {
+ MP_VERBOSE(ao, "assuming we actually got MONO from ALSA.\n");
ao->channels.speaker[0] = MP_SP(FC);
+ }
free(alsa_chmap);
}
@@ -830,7 +849,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
return 0;
do {
- if (AF_FORMAT_IS_PLANAR(ao->format)) {
+ if (af_fmt_is_planar(ao->format)) {
res = snd_pcm_writen(p->alsa, data, samples);
} else {
res = snd_pcm_writei(p->alsa, data[0], samples);
@@ -904,15 +923,18 @@ 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)
- continue;
- char desc2[1024];
- snprintf(desc2, sizeof(desc2), "%s", desc ? desc : "");
- for (int i = 0; desc2[i]; i++) {
- if (desc2[i] == '\n')
- desc2[i] = '/';
+ if (io && strcmp(io, "Output") == 0) {
+ char desc2[1024];
+ snprintf(desc2, sizeof(desc2), "%s", desc ? desc : "");
+ for (int i = 0; desc2[i]; i++) {
+ if (desc2[i] == '\n')
+ desc2[i] = '/';
+ }
+ ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc2});
}
- ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc2});
+ free(name);
+ free(desc);
+ free(io);
}
snd_device_name_free_hint(hints);
diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c
index a66565d..c592c0e 100644
--- a/audio/out/ao_coreaudio.c
+++ b/audio/out/ao_coreaudio.c
@@ -25,18 +25,22 @@
#include "options/m_option.h"
#include "misc/ring.h"
#include "common/msg.h"
-#include "audio/out/ao_coreaudio_properties.h"
-#include "audio/out/ao_coreaudio_utils.h"
+#include "ao_coreaudio_chmap.h"
+#include "ao_coreaudio_properties.h"
+#include "ao_coreaudio_utils.h"
struct priv {
AudioDeviceID device;
AudioUnit audio_unit;
uint64_t hw_latency_us;
-};
-bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
- struct mp_chmap *chmap);
+ AudioStreamBasicDescription original_asbd;
+ AudioStreamID original_asbd_stream;
+
+ int change_physical_format;
+ int exclusive;
+};
static int64_t ca_get_hardware_latency(struct ao *ao) {
struct priv *p = ao->priv;
@@ -52,12 +56,8 @@ static int64_t ca_get_hardware_latency(struct ao *ao) {
&size);
CHECK_CA_ERROR("cannot get audio unit latency");
- uint32_t frames = 0;
- err = CA_GET_O(p->device, kAudioDevicePropertyLatency, &frames);
- CHECK_CA_ERROR("cannot get device latency");
-
uint64_t audiounit_latency_us = audiounit_latency_sec * 1e6;
- uint64_t device_latency_us = ca_frames_to_us(ao, frames);
+ uint64_t device_latency_us = ca_get_device_latency_us(ao, p->device);
MP_VERBOSE(ao, "audiounit latency [us]: %lld\n", audiounit_latency_us);
MP_VERBOSE(ao, "device latency [us]: %lld\n", device_latency_us);
@@ -74,11 +74,14 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
{
struct ao *ao = ctx;
struct priv *p = ao->priv;
- AudioBuffer buf = buffer_list->mBuffers[0];
+ void *planes[MP_NUM_CHANNELS] = {0};
+
+ for (int n = 0; n < ao->num_planes; n++)
+ planes[n] = buffer_list->mBuffers[n].mData;
int64_t end = mp_time_us();
end += p->hw_latency_us + ca_get_latency(ts) + ca_frames_to_us(ao, frames);
- ao_read_data(ao, &buf.mData, frames, end);
+ ao_read_data(ao, planes, frames, end);
return noErr;
}
@@ -121,8 +124,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
return CONTROL_UNKNOWN;
}
-static bool init_chmap(struct ao *ao);
static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd);
+static void init_physical_format(struct ao *ao);
static bool reinit_device(struct ao *ao) {
struct priv *p = ao->priv;
@@ -143,8 +146,10 @@ coreaudio_error:
static int init(struct ao *ao)
{
- if (AF_FORMAT_IS_IEC61937(ao->format)) {
- MP_WARN(ao, "detected IEC61937, redirecting to coreaudio_exclusive\n");
+ struct priv *p = ao->priv;
+
+ if (!af_fmt_is_pcm(ao->format) || p->exclusive) {
+ MP_VERBOSE(ao, "redirecting to coreaudio_exclusive\n");
ao->redirect = "coreaudio_exclusive";
return CONTROL_ERROR;
}
@@ -152,10 +157,11 @@ static int init(struct ao *ao)
if (!reinit_device(ao))
goto coreaudio_error;
- if (!init_chmap(ao))
- goto coreaudio_error;
+ if (p->change_physical_format)
+ init_physical_format(ao);
- ao->format = af_fmt_from_planar(ao->format);
+ if (!ca_init_chmap(ao, p->device))
+ goto coreaudio_error;
AudioStreamBasicDescription asbd;
ca_fill_asbd(ao, &asbd);
@@ -169,95 +175,71 @@ coreaudio_error:
return CONTROL_ERROR;
}
-static AudioChannelLayout* ca_query_layout(struct ao *ao, void *talloc_ctx)
+static void init_physical_format(struct ao *ao)
{
struct priv *p = ao->priv;
- OSStatus err;
- uint32_t psize;
- AudioChannelLayout *r = NULL;
+ OSErr err;
- AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
- .mSelector = kAudioDevicePropertyPreferredChannelLayout,
- .mScope = kAudioDevicePropertyScopeOutput,
- .mElement = kAudioObjectPropertyElementWildcard,
- };
+ AudioStreamBasicDescription asbd;
+ ca_fill_asbd(ao, &asbd);
- err = AudioObjectGetPropertyDataSize(p->device, &p_addr, 0, NULL, &psize);
- CHECK_CA_ERROR("could not get device preferred layout (size)");
+ AudioStreamID *streams;
+ size_t n_streams;
- r = talloc_size(talloc_ctx, psize);
+ err = CA_GET_ARY_O(p->device, kAudioDevicePropertyStreams,
+ &streams, &n_streams);
+ CHECK_CA_ERROR("could not get number of streams");
- err = AudioObjectGetPropertyData(p->device, &p_addr, 0, NULL, &psize, r);
- CHECK_CA_ERROR("could not get device preferred layout (get)");
+ MP_VERBOSE(ao, "Found %zd substream(s).\n", n_streams);
-coreaudio_error:
- return r;
-}
+ for (int i = 0; i < n_streams; i++) {
+ AudioStreamRangedDescription *formats;
+ size_t n_formats;
-static AudioChannelLayout* ca_query_stereo_layout(struct ao *ao, void *talloc_ctx)
-{
- struct priv *p = ao->priv;
- OSStatus err;
- const int nch = 2;
- uint32_t channels[nch];
- AudioChannelLayout *r = NULL;
-
- AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
- .mSelector = kAudioDevicePropertyPreferredChannelsForStereo,
- .mScope = kAudioDevicePropertyScopeOutput,
- .mElement = kAudioObjectPropertyElementWildcard,
- };
-
- uint32_t psize = sizeof(channels);
- err = AudioObjectGetPropertyData(p->device, &p_addr, 0, NULL, &psize, channels);
- CHECK_CA_ERROR("could not get device preferred stereo layout");
+ MP_VERBOSE(ao, "Looking at formats in substream %d...\n", i);
- psize = sizeof(AudioChannelLayout) + nch * sizeof(AudioChannelDescription);
- r = talloc_zero_size(talloc_ctx, psize);
- r->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
- r->mNumberChannelDescriptions = nch;
+ err = CA_GET_ARY(streams[i], kAudioStreamPropertyAvailablePhysicalFormats,
+ &formats, &n_formats);
- AudioChannelDescription desc = {0};
- desc.mChannelFlags = kAudioChannelFlags_AllOff;
+ if (!CHECK_CA_WARN("could not get number of stream formats"))
+ continue; // try next one
- for(int i = 0; i < nch; i++) {
- desc.mChannelLabel = channels[i];
- r->mChannelDescriptions[i] = desc;
- }
-coreaudio_error:
- return r;
-}
+ uint32_t direction;
+ err = CA_GET(streams[i], kAudioStreamPropertyDirection, &direction);
+ CHECK_CA_ERROR("could not get stream direction");
+ if (direction != 0) {
+ MP_VERBOSE(ao, "Not an output stream.\n");
+ continue;
+ }
-static bool init_chmap(struct ao *ao)
-{
- struct priv *p = ao->priv;
- void *ta_ctx = talloc_new(NULL);
+ AudioStreamBasicDescription best_asbd = {0};
- struct mp_chmap_sel chmap_sel = {.tmp = p};
- struct mp_chmap chmap = {0};
+ for (int j = 0; j < n_formats; j++) {
+ AudioStreamBasicDescription *stream_asbd = &formats[j].mFormat;
- AudioChannelLayout *ml = ca_query_layout(ao, ta_ctx);
- if (ml && ca_layout_to_mp_chmap(ao, ml, &chmap))
- mp_chmap_sel_add_map(&chmap_sel, &chmap);
+ ca_print_asbd(ao, "- ", stream_asbd);
- AudioChannelLayout *sl = ca_query_stereo_layout(ao, ta_ctx);
- if (sl && ca_layout_to_mp_chmap(ao, sl, &chmap))
- mp_chmap_sel_add_map(&chmap_sel, &chmap);
+ if (!best_asbd.mFormatID || ca_asbd_is_better(&asbd, &best_asbd,
+ stream_asbd))
+ best_asbd = *stream_asbd;
+ }
- talloc_free(ta_ctx);
+ if (best_asbd.mFormatID) {
+ p->original_asbd_stream = streams[i];
+ err = CA_GET(p->original_asbd_stream,
+ kAudioStreamPropertyPhysicalFormat,
+ &p->original_asbd);
+ CHECK_CA_WARN("could not get current physical stream format");
- if (!ao_chmap_sel_adjust(ao, &chmap_sel, &ao->channels)) {
- MP_ERR(ao, "could not select a suitable channel map among the "
- "hardware supported ones. Make sure to configure your "
- "output device correctly in 'Audio MIDI Setup.app'\n");
- goto coreaudio_error;
+ if (!ca_change_physical_format_sync(ao, streams[i], best_asbd))
+ p->original_asbd = (AudioStreamBasicDescription){0};
+ break;
+ }
}
- return true;
-
coreaudio_error:
- return false;
+ return;
}
static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
@@ -350,13 +332,23 @@ static void uninit(struct ao *ao)
AudioOutputUnitStop(p->audio_unit);
AudioUnitUninitialize(p->audio_unit);
AudioComponentInstanceDispose(p->audio_unit);
+
+ if (p->original_asbd.mFormatID) {
+ OSStatus err = CA_SET(p->original_asbd_stream,
+ kAudioStreamPropertyPhysicalFormat,
+ &p->original_asbd);
+ CHECK_CA_WARN("could not restore physical stream format");
+ }
}
static OSStatus hotplug_cb(AudioObjectID id, UInt32 naddr,
const AudioObjectPropertyAddress addr[],
- void *ctx) {
- reinit_device(ctx);
- ao_hotplug_event(ctx);
+ void *ctx)
+{
+ struct ao *ao = ctx;
+ MP_VERBOSE(ao, "Handling potential hotplug event...\n");
+ reinit_device(ao);
+ ao_hotplug_event(ao);
return noErr;
}
@@ -412,159 +404,6 @@ static void hotplug_uninit(struct ao *ao)
}
}
-
-// Channel Mapping functions
-static const int speaker_map[][2] = {
- { kAudioChannelLabel_Left, MP_SPEAKER_ID_FL },
- { kAudioChannelLabel_Right, MP_SPEAKER_ID_FR },
- { kAudioChannelLabel_Center, MP_SPEAKER_ID_FC },
- { kAudioChannelLabel_LFEScreen, MP_SPEAKER_ID_LFE },
- { kAudioChannelLabel_LeftSurround, MP_SPEAKER_ID_BL },
- { kAudioChannelLabel_RightSurround, MP_SPEAKER_ID_BR },
- { kAudioChannelLabel_LeftCenter, MP_SPEAKER_ID_FLC },
- { kAudioChannelLabel_RightCenter, MP_SPEAKER_ID_FRC },
- { kAudioChannelLabel_CenterSurround, MP_SPEAKER_ID_BC },
- { kAudioChannelLabel_LeftSurroundDirect, MP_SPEAKER_ID_SL },
- { kAudioChannelLabel_RightSurroundDirect, MP_SPEAKER_ID_SR },
- { kAudioChannelLabel_TopCenterSurround, MP_SPEAKER_ID_TC },
- { kAudioChannelLabel_VerticalHeightLeft, MP_SPEAKER_ID_TFL },
- { kAudioChannelLabel_VerticalHeightCenter, MP_SPEAKER_ID_TFC },
- { kAudioChannelLabel_VerticalHeightRight, MP_SPEAKER_ID_TFR },
- { kAudioChannelLabel_TopBackLeft, MP_SPEAKER_ID_TBL },
- { kAudioChannelLabel_TopBackCenter, MP_SPEAKER_ID_TBC },
- { kAudioChannelLabel_TopBackRight, MP_SPEAKER_ID_TBR },
-
- // unofficial extensions
- { kAudioChannelLabel_RearSurroundLeft, MP_SPEAKER_ID_SDL },
- { kAudioChannelLabel_RearSurroundRight, MP_SPEAKER_ID_SDR },
- { kAudioChannelLabel_LeftWide, MP_SPEAKER_ID_WL },
- { kAudioChannelLabel_RightWide, MP_SPEAKER_ID_WR },
- { kAudioChannelLabel_LFE2, MP_SPEAKER_ID_LFE2 },
-
- { kAudioChannelLabel_HeadphonesLeft, MP_SPEAKER_ID_DL },
- { kAudioChannelLabel_HeadphonesRight, MP_SPEAKER_ID_DR },
-
- { kAudioChannelLabel_Unknown, MP_SPEAKER_ID_NA },
-
- { 0, -1 },
-};
-
-static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
-{
- for (int i = 0; speaker_map[i][1] >= 0; i++)
- if (speaker_map[i][0] == label)
- return speaker_map[i][1];
- return -1;
-}
-
-static void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout)
-{
- if (!mp_msg_test(ao->log, l))
- return;
-
- AudioChannelDescription *descs = layout->mChannelDescriptions;
-
- mp_msg(ao->log, l, "layout: tag: <%u>, bitmap: <%u>, "
- "descriptions <%u>\n",
- (unsigned) layout->mChannelLayoutTag,
- (unsigned) layout->mChannelBitmap,
- (unsigned) layout->mNumberChannelDescriptions);
-
- for (int i = 0; i < layout->mNumberChannelDescriptions; i++) {
- AudioChannelDescription d = descs[i];
- mp_msg(ao->log, l, " - description %d: label <%u, %u>, "
- " flags: <%u>, coords: <%f, %f, %f>\n", i,
- (unsigned) d.mChannelLabel,
- (unsigned) ca_label_to_mp_speaker_id(d.mChannelLabel),
- (unsigned) d.mChannelFlags,
- d.mCoordinates[0],
- d.mCoordinates[1],
- d.mCoordinates[2]);
- }
-}
-
-static AudioChannelLayout *ca_layout_to_custom_layout(
- struct ao *ao, void *talloc_ctx, AudioChannelLayout *l)
-{
- AudioChannelLayoutTag tag = l->mChannelLayoutTag;
- AudioChannelLayout *r;
- OSStatus err;
-
- if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
- uint32_t psize;
- err = AudioFormatGetPropertyInfo(
- kAudioFormatProperty_ChannelLayoutForBitmap,
- sizeof(uint32_t), &l->mChannelBitmap, &psize);
- CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (info)");
- r = talloc_size(NULL, psize);
- err = AudioFormatGetProperty(
- kAudioFormatProperty_ChannelLayoutForBitmap,
- sizeof(uint32_t), &l->mChannelBitmap, &psize, r);
- CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (get)");
- } else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
- uint32_t psize;
- err = AudioFormatGetPropertyInfo(
- kAudioFormatProperty_ChannelLayoutForTag,
- sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize);
- r = talloc_size(NULL, psize);
- CHECK_CA_ERROR("failed to convert channel tag to descriptions (info)");
- err = AudioFormatGetProperty(
- kAudioFormatProperty_ChannelLayoutForTag,
- sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize, r);
- CHECK_CA_ERROR("failed to convert channel tag to descriptions (get)");
- } else {
- r = l;
- }
-
- return r;
-coreaudio_error:
- return NULL;
-}
-
-bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
- struct mp_chmap *chmap)
-{
- void *talloc_ctx = talloc_new(NULL);
-
- MP_DBG(ao, "input channel layout:\n");
- ca_log_layout(ao, MSGL_DEBUG, layout);
-
- AudioChannelLayout *l = ca_layout_to_custom_layout(ao, talloc_ctx, layout);
- if (!l)
- goto coreaudio_error;
-
- MP_VERBOSE(ao, "converted input channel layout:\n");
- ca_log_layout(ao, MSGL_V, l);
-
- if (l->mNumberChannelDescriptions > MP_NUM_CHANNELS) {
- MP_VERBOSE(ao, "layout has too many descriptions (%u, max: %d)\n",
- (unsigned) l->mNumberChannelDescriptions, MP_NUM_CHANNELS);
- return false;
- }
-
- int next_na = MP_SPEAKER_ID_NA;
- for (int n = 0; n < l->mNumberChannelDescriptions; n++) {
- AudioChannelLabel label = l->mChannelDescriptions[n].mChannelLabel;
- int speaker = ca_label_to_mp_speaker_id(label);
- if (speaker < 0) {
- MP_VERBOSE(ao, "channel label=%u unusable to build channel "
- "bitmap, skipping layout\n", (unsigned) label);
- goto coreaudio_error;
- } else {
- chmap->speaker[n] = speaker;
- chmap->num = n + 1;
- }
- }
-
- talloc_free(talloc_ctx);
- return chmap->num > 0;
-coreaudio_error:
- MP_VERBOSE(ao, "converted input channel layout (failed):\n");
- ca_log_layout(ao, MSGL_V, layout);
- talloc_free(talloc_ctx);
- return false;
-}
-
#define OPT_BASE_STRUCT struct priv
const struct ao_driver audio_out_coreaudio = {
@@ -579,4 +418,9 @@ const struct ao_driver audio_out_coreaudio = {
.hotplug_uninit = hotplug_uninit,
.list_devs = ca_get_device_list,
.priv_size = sizeof(struct priv),
+ .options = (const struct m_option[]){
+ OPT_FLAG("change-physical-format", change_physical_format, 0),
+ OPT_FLAG("exclusive", exclusive, 0),
+ {0}
+ },
};
diff --git a/audio/out/ao_coreaudio_chmap.c b/audio/out/ao_coreaudio_chmap.c
new file mode 100644
index 0000000..b1572da
--- /dev/null
+++ b/audio/out/ao_coreaudio_chmap.c
@@ -0,0 +1,267 @@
+/*
+ * 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 "common/common.h"
+
+#include "ao_coreaudio_utils.h"
+
+#include "ao_coreaudio_chmap.h"
+
+static const int speaker_map[][2] = {
+ { kAudioChannelLabel_Left, MP_SPEAKER_ID_FL },
+ { kAudioChannelLabel_Right, MP_SPEAKER_ID_FR },
+ { kAudioChannelLabel_Center, MP_SPEAKER_ID_FC },
+ { kAudioChannelLabel_LFEScreen, MP_SPEAKER_ID_LFE },
+ { kAudioChannelLabel_LeftSurround, MP_SPEAKER_ID_BL },
+ { kAudioChannelLabel_RightSurround, MP_SPEAKER_ID_BR },
+ { kAudioChannelLabel_LeftCenter, MP_SPEAKER_ID_FLC },
+ { kAudioChannelLabel_RightCenter, MP_SPEAKER_ID_FRC },
+ { kAudioChannelLabel_CenterSurround, MP_SPEAKER_ID_BC },
+ { kAudioChannelLabel_LeftSurroundDirect, MP_SPEAKER_ID_SL },
+ { kAudioChannelLabel_RightSurroundDirect, MP_SPEAKER_ID_SR },
+ { kAudioChannelLabel_TopCenterSurround, MP_SPEAKER_ID_TC },
+ { kAudioChannelLabel_VerticalHeightLeft, MP_SPEAKER_ID_TFL },
+ { kAudioChannelLabel_VerticalHeightCenter, MP_SPEAKER_ID_TFC },
+ { kAudioChannelLabel_VerticalHeightRight, MP_SPEAKER_ID_TFR },
+ { kAudioChannelLabel_TopBackLeft, MP_SPEAKER_ID_TBL },
+ { kAudioChannelLabel_TopBackCenter, MP_SPEAKER_ID_TBC },
+ { kAudioChannelLabel_TopBackRight, MP_SPEAKER_ID_TBR },
+
+ // unofficial extensions
+ { kAudioChannelLabel_RearSurroundLeft, MP_SPEAKER_ID_SDL },
+ { kAudioChannelLabel_RearSurroundRight, MP_SPEAKER_ID_SDR },
+ { kAudioChannelLabel_LeftWide, MP_SPEAKER_ID_WL },
+ { kAudioChannelLabel_RightWide, MP_SPEAKER_ID_WR },
+ { kAudioChannelLabel_LFE2, MP_SPEAKER_ID_LFE2 },
+
+ { kAudioChannelLabel_HeadphonesLeft, MP_SPEAKER_ID_DL },
+ { kAudioChannelLabel_HeadphonesRight, MP_SPEAKER_ID_DR },
+
+ { kAudioChannelLabel_Unknown, MP_SPEAKER_ID_NA },
+
+ { 0, -1 },
+};
+
+static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
+{
+ for (int i = 0; speaker_map[i][1] >= 0; i++)
+ if (speaker_map[i][0] == label)
+ return speaker_map[i][1];
+ return -1;
+}
+
+static void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout)
+{
+ if (!mp_msg_test(ao->log, l))
+ return;
+
+ AudioChannelDescription *descs = layout->mChannelDescriptions;
+
+ mp_msg(ao->log, l, "layout: tag: <%u>, bitmap: <%u>, "
+ "descriptions <%u>\n",
+ (unsigned) layout->mChannelLayoutTag,
+ (unsigned) layout->mChannelBitmap,
+ (unsigned) layout->mNumberChannelDescriptions);
+
+ for (int i = 0; i < layout->mNumberChannelDescriptions; i++) {
+ AudioChannelDescription d = descs[i];
+ mp_msg(ao->log, l, " - description %d: label <%u, %u>, "
+ " flags: <%u>, coords: <%f, %f, %f>\n", i,
+ (unsigned) d.mChannelLabel,
+ (unsigned) ca_label_to_mp_speaker_id(d.mChannelLabel),
+ (unsigned) d.mChannelFlags,
+ d.mCoordinates[0],
+ d.mCoordinates[1],
+ d.mCoordinates[2]);
+ }
+}
+
+static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
+ void *talloc_ctx,
+ AudioChannelLayout *l)
+{
+ AudioChannelLayoutTag tag = l->mChannelLayoutTag;
+ AudioChannelLayout *r;
+ OSStatus err;
+
+ if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
+ uint32_t psize;
+ err = AudioFormatGetPropertyInfo(
+ kAudioFormatProperty_ChannelLayoutForBitmap,
+ sizeof(uint32_t), &l->mChannelBitmap, &psize);
+ CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (info)");
+ r = talloc_size(NULL, psize);
+ err = AudioFormatGetProperty(
+ kAudioFormatProperty_ChannelLayoutForBitmap,
+ sizeof(uint32_t), &l->mChannelBitmap, &psize, r);
+ CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (get)");
+ } else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
+ uint32_t psize;
+ err = AudioFormatGetPropertyInfo(
+ kAudioFormatProperty_ChannelLayoutForTag,
+ sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize);
+ r = talloc_size(NULL, psize);
+ CHECK_CA_ERROR("failed to convert channel tag to descriptions (info)");
+ err = AudioFormatGetProperty(
+ kAudioFormatProperty_ChannelLayoutForTag,
+ sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize, r);
+ CHECK_CA_ERROR("failed to convert channel tag to descriptions (get)");
+ } else {
+ r = l;
+ }
+
+ return r;
+coreaudio_error:
+ return NULL;
+}
+
+static bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
+ struct mp_chmap *chmap)
+{
+ void *talloc_ctx = talloc_new(NULL);
+
+ MP_DBG(ao, "input channel layout:\n");
+ ca_log_layout(ao, MSGL_DEBUG, layout);
+
+ AudioChannelLayout *l = ca_layout_to_custom_layout(ao, talloc_ctx, layout);
+ if (!l)
+ goto coreaudio_error;
+
+ MP_VERBOSE(ao, "converted input channel layout:\n");
+ ca_log_layout(ao, MSGL_V, l);
+
+ if (l->mNumberChannelDescriptions > MP_NUM_CHANNELS) {
+ MP_VERBOSE(ao, "layout has too many descriptions (%u, max: %d)\n",
+ (unsigned) l->mNumberChannelDescriptions, MP_NUM_CHANNELS);
+ return false;
+ }
+
+ for (int n = 0; n < l->mNumberChannelDescriptions; n++) {
+ AudioChannelLabel label = l->mChannelDescriptions[n].mChannelLabel;
+ int speaker = ca_label_to_mp_speaker_id(label);
+ if (speaker < 0) {
+ MP_VERBOSE(ao, "channel label=%u unusable to build channel "
+ "bitmap, skipping layout\n", (unsigned) label);
+ goto coreaudio_error;
+ } else {
+ chmap->speaker[n] = speaker;
+ chmap->num = n + 1;
+ }
+ }
+
+ talloc_free(talloc_ctx);
+ return chmap->num > 0;
+coreaudio_error:
+ MP_VERBOSE(ao, "converted input channel layout (failed):\n");
+ ca_log_layout(ao, MSGL_V, layout);
+ talloc_free(talloc_ctx);
+ return false;
+}
+
+static AudioChannelLayout* ca_query_layout(struct ao *ao,
+ AudioDeviceID device,
+ void *talloc_ctx)
+{
+ OSStatus err;
+ uint32_t psize;
+ AudioChannelLayout *r = NULL;
+
+ AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
+ .mSelector = kAudioDevicePropertyPreferredChannelLayout,
+ .mScope = kAudioDevicePropertyScopeOutput,
+ .mElement = kAudioObjectPropertyElementWildcard,
+ };
+
+ err = AudioObjectGetPropertyDataSize(device, &p_addr, 0, NULL, &psize);
+ CHECK_CA_ERROR("could not get device preferred layout (size)");
+
+ r = talloc_size(talloc_ctx, psize);
+
+ err = AudioObjectGetPropertyData(device, &p_addr, 0, NULL, &psize, r);
+ CHECK_CA_ERROR("could not get device preferred layout (get)");
+
+coreaudio_error:
+ return r;
+}
+
+static AudioChannelLayout* ca_query_stereo_layout(struct ao *ao,
+ AudioDeviceID device,
+ void *talloc_ctx)
+{
+ OSStatus err;
+ const int nch = 2;
+ uint32_t channels[nch];
+ AudioChannelLayout *r = NULL;
+
+ AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
+ .mSelector = kAudioDevicePropertyPreferredChannelsForStereo,
+ .mScope = kAudioDevicePropertyScopeOutput,
+ .mElement = kAudioObjectPropertyElementWildcard,
+ };
+
+ uint32_t psize = sizeof(channels);
+ err = AudioObjectGetPropertyData(device, &p_addr, 0, NULL, &psize, channels);
+ CHECK_CA_ERROR("could not get device preferred stereo layout");
+
+ psize = sizeof(AudioChannelLayout) + nch * sizeof(AudioChannelDescription);
+ r = talloc_zero_size(talloc_ctx, psize);
+ r->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
+ r->mNumberChannelDescriptions = nch;
+
+ AudioChannelDescription desc = {0};
+ desc.mChannelFlags = kAudioChannelFlags_AllOff;
+
+ for(int i = 0; i < nch; i++) {
+ desc.mChannelLabel = channels[i];
+ r->mChannelDescriptions[i] = desc;
+ }
+
+coreaudio_error:
+ return r;
+}
+
+bool ca_init_chmap(struct ao *ao, AudioDeviceID device)
+{
+ void *ta_ctx = talloc_new(NULL);
+
+ struct mp_chmap_sel chmap_sel = {.tmp = ta_ctx};
+ struct mp_chmap chmap = {0};
+
+ mp_chmap_sel_add_map(&chmap_sel, &(struct mp_chmap)MP_CHMAP_INIT_MONO);
+
+ AudioChannelLayout *ml = ca_query_layout(ao, device, ta_ctx);
+ if (ml && ca_layout_to_mp_chmap(ao, ml, &chmap))
+ mp_chmap_sel_add_map(&chmap_sel, &chmap);
+
+ AudioChannelLayout *sl = ca_query_stereo_layout(ao, device, ta_ctx);
+ if (sl && ca_layout_to_mp_chmap(ao, sl, &chmap))
+ mp_chmap_sel_add_map(&chmap_sel, &chmap);
+
+ if (!ao_chmap_sel_adjust(ao, &chmap_sel, &ao->channels)) {
+ MP_ERR(ao, "could not select a suitable channel map among the "
+ "hardware supported ones. Make sure to configure your "
+ "output device correctly in 'Audio MIDI Setup.app'\n");
+ goto coreaudio_error;
+ }
+
+ talloc_free(ta_ctx);
+ return true;
+
+coreaudio_error:
+ talloc_free(ta_ctx);
+ return false;
+}
diff --git a/audio/out/ao_coreaudio_chmap.h b/audio/out/ao_coreaudio_chmap.h
new file mode 100644
index 0000000..ce31975
--- /dev/null
+++ b/audio/out/ao_coreaudio_chmap.h
@@ -0,0 +1,25 @@
+/*
+ * 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/>.
+ */
+
+#ifndef MPV_COREAUDIO_CHMAP_H
+#define MPV_COREAUDIO_CHMAP_H
+
+#include <AudioToolbox/AudioToolbox.h>
+
+bool ca_init_chmap(struct ao *ao, AudioDeviceID device);
+
+#endif
diff --git a/audio/out/ao_coreaudio_exclusive.c b/audio/out/ao_coreaudio_exclusive.c
index 7f7da67..f8aba87 100644
--- a/audio/out/ao_coreaudio_exclusive.c
+++ b/audio/out/ao_coreaudio_exclusive.c
@@ -44,329 +44,210 @@
#include "osdep/atomics.h"
#include "options/m_option.h"
#include "common/msg.h"
+#include "audio/out/ao_coreaudio_chmap.h"
#include "audio/out/ao_coreaudio_properties.h"
#include "audio/out/ao_coreaudio_utils.h"
-static bool ca_format_is_compressed(AudioStreamBasicDescription asbd)
-{
- switch (asbd.mFormatID)
- case 'IAC3':
- case 'iac3':
- case kAudioFormat60958AC3:
- case kAudioFormatAC3:
- return true;
- return false;
-}
-
-static bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream)
-{
- AudioStreamRangedDescription *formats = NULL;
- size_t n_formats;
-
- OSStatus err =
- CA_GET_ARY(stream, kAudioStreamPropertyAvailablePhysicalFormats,
- &formats, &n_formats);
+struct priv {
+ AudioDeviceID device; // selected device
- CHECK_CA_ERROR("Could not get number of stream formats.");
+ bool paused;
- for (int i = 0; i < n_formats; i++) {
- AudioStreamBasicDescription asbd = formats[i].mFormat;
- ca_print_asbd(ao, "supported format:", &(asbd));
- if (ca_format_is_compressed(asbd)) {
- talloc_free(formats);
- return true;
- }
- }
+ // audio render callback
+ AudioDeviceIOProcID render_cb;
- talloc_free(formats);
-coreaudio_error:
- return false;
-}
+ // pid set for hog mode, (-1) means that hog mode on the device was
+ // released. hog mode is exclusive access to a device
+ pid_t hog_pid;
-static bool ca_device_supports_compressed(struct ao *ao, AudioDeviceID device)
-{
- AudioStreamID *streams = NULL;
- size_t n_streams;
+ AudioStreamID stream;
- /* Retrieve all the output streams. */
- OSStatus err =
- CA_GET_ARY_O(device, kAudioDevicePropertyStreams, &streams, &n_streams);
+ // stream index in an AudioBufferList
+ int stream_idx;
- CHECK_CA_ERROR("could not get number of streams.");
+ // format we changed the stream to, and the original format to restore
+ AudioStreamBasicDescription stream_asbd;
+ AudioStreamBasicDescription original_asbd;
- for (int i = 0; i < n_streams; i++) {
- if (ca_stream_supports_compressed(ao, streams[i])) {
- talloc_free(streams);
- return true;
- }
- }
+ bool changed_mixing;
- talloc_free(streams);
+ atomic_bool reload_requested;
-coreaudio_error:
- return false;
-}
+ uint32_t hw_latency_us;
+};
-static OSStatus ca_property_listener(
- AudioObjectPropertySelector selector,
+static OSStatus property_listener_cb(
AudioObjectID object, uint32_t n_addresses,
const AudioObjectPropertyAddress addresses[],
void *data)
{
- for (int i = 0; i < n_addresses; i++) {
- if (addresses[i].mSelector == selector) {
- if (data)
- atomic_store((atomic_bool *)data, true);
- break;
+ struct ao *ao = data;
+ struct priv *p = ao->priv;
+
+ // Check whether we need to reset the compressed output stream.
+ AudioStreamBasicDescription f;
+ OSErr err = CA_GET(p->stream, kAudioStreamPropertyVirtualFormat, &f);
+ CHECK_CA_WARN("could not get stream format");
+ if (err != noErr || !ca_asbd_equals(&p->stream_asbd, &f)) {
+ if (atomic_compare_exchange_strong(&p->reload_requested,
+ &(bool){false}, true))
+ {
+ ao_request_reload(ao);
+ MP_INFO(ao, "Stream format changed! Reloading.\n");
}
}
- return noErr;
-}
-
-static OSStatus ca_stream_listener(
- AudioObjectID object, uint32_t n_addresses,
- const AudioObjectPropertyAddress addresses[],
- void *data)
-{
- return ca_property_listener(kAudioStreamPropertyPhysicalFormat,
- object, n_addresses, addresses, data);
-}
-
-static OSStatus ca_device_listener(
- AudioObjectID object, uint32_t n_addresses,
- const AudioObjectPropertyAddress addresses[],
- void *data)
-{
- return ca_property_listener(kAudioDevicePropertyDeviceHasChanged,
- object, n_addresses, addresses, data);
-}
-
-static OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid) {
- *pid = getpid();
- OSStatus err = CA_SET(device, kAudioDevicePropertyHogMode, pid);
- if (err != noErr)
- *pid = -1;
-
- return err;
-}
-static OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid) {
- if (*pid == getpid()) {
- *pid = -1;
- return CA_SET(device, kAudioDevicePropertyHogMode, &pid);
- }
return noErr;
}
-static OSStatus ca_change_mixing(struct ao *ao, AudioDeviceID device,
- uint32_t val, bool *changed) {
- *changed = false;
-
- AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
- .mSelector = kAudioDevicePropertySupportsMixing,
- .mScope = kAudioObjectPropertyScopeGlobal,
- .mElement = kAudioObjectPropertyElementMaster,
- };
-
- if (AudioObjectHasProperty(device, &p_addr)) {
- OSStatus err;
- Boolean writeable = 0;
- err = CA_SETTABLE(device, kAudioDevicePropertySupportsMixing,
- &writeable);
-
- if (!CHECK_CA_WARN("can't tell if mixing property is settable")) {
- return err;
- }
-
- if (!writeable)
- return noErr;
-
- err = CA_SET(device, kAudioDevicePropertySupportsMixing, &val);
- if (err != noErr)
- return err;
+static OSStatus enable_property_listener(struct ao *ao, bool enabled)
+{
+ struct priv *p = ao->priv;
- if (!CHECK_CA_WARN("can't set mix mode")) {
- return err;
+ uint32_t selectors[] = {kAudioDevicePropertyDeviceHasChanged,
+ kAudioHardwarePropertyDevices};
+ AudioDeviceID devs[] = {p->device,
+ kAudioObjectSystemObject};
+ assert(MP_ARRAY_SIZE(selectors) == MP_ARRAY_SIZE(devs));
+
+ OSStatus status = noErr;
+ for (int n = 0; n < MP_ARRAY_SIZE(devs); n++) {
+ AudioObjectPropertyAddress addr = {
+ .mScope = kAudioObjectPropertyScopeGlobal,
+ .mElement = kAudioObjectPropertyElementMaster,
+ .mSelector = selectors[n],
+ };
+ AudioDeviceID device = devs[n];
+
+ OSStatus status2;
+ if (enabled) {
+ status2 = AudioObjectAddPropertyListener(
+ device, &addr, property_listener_cb, ao);
+ } else {
+ status2 = AudioObjectRemovePropertyListener(
+ device, &addr, property_listener_cb, ao);
}
-
- *changed = true;
+ if (status == noErr)
+ status = status2;
}
- return noErr;
-}
-
-static OSStatus ca_disable_mixing(struct ao *ao,
- AudioDeviceID device, bool *changed) {
- return ca_change_mixing(ao, device, 0, changed);
+ return status;
}
-static OSStatus ca_enable_mixing(struct ao *ao,
- AudioDeviceID device, bool changed) {
- if (changed) {
- bool dont_care = false;
- return ca_change_mixing(ao, device, 1, &dont_care);
- }
+static OSStatus render_cb_compressed(
+ AudioDeviceID device, const AudioTimeStamp *ts,
+ const void *in_data, const AudioTimeStamp *in_ts,
+ AudioBufferList *out_data, const AudioTimeStamp *out_ts, void *ctx)
+{
+ struct ao *ao = ctx;
+ struct priv *p = ao->priv;
+ AudioBuffer buf = out_data->mBuffers[p->stream_idx];
+ int requested = buf.mDataByteSize;
- return noErr;
-}
+ int pseudo_frames = requested / ao->sstride;
-static OSStatus ca_change_device_listening(AudioDeviceID device,
- void *flag, bool enabled)
-{
- AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
- .mSelector = kAudioDevicePropertyDeviceHasChanged,
- .mScope = kAudioObjectPropertyScopeGlobal,
- .mElement = kAudioObjectPropertyElementMaster,
- };
-
- if (enabled) {
- return AudioObjectAddPropertyListener(
- device, &p_addr, ca_device_listener, flag);
- } else {
- return AudioObjectRemovePropertyListener(
- device, &p_addr, ca_device_listener, flag);
+ // we expect the callback to read full frames, which are aligned accordingly
+ if (pseudo_frames * ao->sstride != requested) {
+ MP_ERR(ao, "Unsupported unaligned read of %d bytes.\n", requested);
+ return kAudioHardwareUnspecifiedError;
}
-}
-static OSStatus ca_enable_device_listener(AudioDeviceID device, void *flag) {
- return ca_change_device_listening(device, flag, true);
-}
+ int64_t end = mp_time_us();
+ end += p->hw_latency_us + ca_get_latency(ts)
+ + ca_frames_to_us(ao, pseudo_frames);
-static OSStatus ca_disable_device_listener(AudioDeviceID device, void *flag) {
- return ca_change_device_listening(device, flag, false);
+ ao_read_data(ao, &buf.mData, pseudo_frames, end);
+
+ return noErr;
}
-static bool ca_change_format(struct ao *ao, AudioStreamID stream,
- AudioStreamBasicDescription change_format)
+// Apparently, audio devices can have multiple sub-streams. It's not clear to
+// me what devices with multiple streams actually do. So only select the first
+// one that fulfills some minimum requirements.
+// If this is not sufficient, we could duplicate the device list entries for
+// each sub-stream, and make it explicit.
+static int select_stream(struct ao *ao)
{
- OSStatus err = noErr;
- AudioObjectPropertyAddress p_addr;
- atomic_bool stream_format_changed = ATOMIC_VAR_INIT(false);
-
- ca_print_asbd(ao, "setting stream format:", &change_format);
-
- /* Install the callback. */
- p_addr = (AudioObjectPropertyAddress) {
- .mSelector = kAudioStreamPropertyPhysicalFormat,
- .mScope = kAudioObjectPropertyScopeGlobal,
- .mElement = kAudioObjectPropertyElementMaster,
- };
-
- err = AudioObjectAddPropertyListener(stream, &p_addr, ca_stream_listener,
- &stream_format_changed);
- if (!CHECK_CA_WARN("can't add property listener during format change")) {
- return false;
- }
+ struct priv *p = ao->priv;
- /* Change the format. */
- err = CA_SET(stream, kAudioStreamPropertyPhysicalFormat, &change_format);
- if (!CHECK_CA_WARN("error changing physical format")) {
- return false;
- }
+ AudioStreamID *streams;
+ size_t n_streams;
+ OSStatus err;
- /* The AudioStreamSetProperty is not only asynchronous,
- * it is also not Atomic, in its behaviour.
- * Therefore we check 5 times before we really give up. */
- bool format_set = false;
- AudioStreamBasicDescription actual_format = {0};
- for (int i = 0; i < 5; i++) {
- err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &actual_format);
- if (!CHECK_CA_WARN("could not retrieve physical format"))
- break;
+ /* Get a list of all the streams on this device. */
+ err = CA_GET_ARY_O(p->device, kAudioDevicePropertyStreams,
+ &streams, &n_streams);
+ CHECK_CA_ERROR("could not get number of streams");
+ for (int i = 0; i < n_streams; i++) {
+ uint32_t direction;
+ err = CA_GET(streams[i], kAudioStreamPropertyDirection, &direction);
+ CHECK_CA_WARN("could not get stream direction");
+ if (err == noErr && direction != 0) {
+ MP_VERBOSE(ao, "Substream %d is not an output stream.\n", i);
+ continue;
+ }
- format_set = ca_asbd_equals(&change_format, &actual_format);
- if (format_set)
+ if (af_fmt_is_pcm(ao->format) || ca_stream_supports_compressed(ao,
+ streams[i]))
+ {
+ MP_VERBOSE(ao, "Using substream %d/%zd.\n", i, n_streams);
+ p->stream = streams[i];
+ p->stream_idx = i;
break;
-
- for (int j = 0; !atomic_load(&stream_format_changed) && j < 50; j++)
- mp_sleep_us(10000);
-
- bool old = true;
- if (!atomic_compare_exchange_strong(&stream_format_changed, &old, false))
- MP_VERBOSE(ao, "reached timeout\n");
+ }
}
- ca_print_asbd(ao, "actual format in use:", &actual_format);
-
- err = AudioObjectRemovePropertyListener(stream, &p_addr, ca_stream_listener,
- &stream_format_changed);
+ talloc_free(streams);
- if (!CHECK_CA_WARN("can't remove property listener")) {
- return false;
+ if (p->stream_idx < 0) {
+ MP_ERR(ao, "No useable substream found.\n");
+ goto coreaudio_error;
}
- return format_set;
-}
-
+ return 0;
-struct priv {
- AudioDeviceID device; // selected device
-
- bool paused;
-
- // audio render callback
- AudioDeviceIOProcID render_cb;
-
- // pid set for hog mode, (-1) means that hog mode on the device was
- // released. hog mode is exclusive access to a device
- pid_t hog_pid;
+coreaudio_error:
+ return -1;
+}
- AudioStreamID stream;
+static int find_best_format(struct ao *ao, AudioStreamBasicDescription *out_fmt)
+{
+ struct priv *p = ao->priv;
- // stream index in an AudioBufferList
- int stream_idx;
+ // Build ASBD for the input format
+ AudioStreamBasicDescription asbd;
+ ca_fill_asbd(ao, &asbd);
+ ca_print_asbd(ao, "our format:", &asbd);
- // format we changed the stream to, and the original format to restore
- AudioStreamBasicDescription stream_asbd;
- AudioStreamBasicDescription original_asbd;
+ *out_fmt = (AudioStreamBasicDescription){0};
- bool changed_mixing;
- atomic_bool stream_asbd_changed;
- bool reload_requested;
+ AudioStreamRangedDescription *formats;
+ size_t n_formats;
+ OSStatus err;
- uint32_t hw_latency_us;
-};
+ err = CA_GET_ARY(p->stream, kAudioStreamPropertyAvailablePhysicalFormats,
+ &formats, &n_formats);
+ CHECK_CA_ERROR("could not get number of stream formats");
-static OSStatus render_cb_compressed(
- AudioDeviceID device, const AudioTimeStamp *ts,
- const void *in_data, const AudioTimeStamp *in_ts,
- AudioBufferList *out_data, const AudioTimeStamp *out_ts, void *ctx)
-{
- struct ao *ao = ctx;
- struct priv *p = ao->priv;
- AudioBuffer buf = out_data->mBuffers[p->stream_idx];
- int requested = buf.mDataByteSize;
+ for (int j = 0; j < n_formats; j++) {
+ AudioStreamBasicDescription *stream_asbd = &formats[j].mFormat;
- int pseudo_frames = requested / ao->sstride;
+ ca_print_asbd(ao, "- ", stream_asbd);
- // we expect the callback to read full frames, which are aligned accordingly
- if (pseudo_frames * ao->sstride != requested) {
- MP_ERR(ao, "Unsupported unaligned read of %d bytes.\n", requested);
- return kAudioHardwareUnspecifiedError;
+ if (!out_fmt->mFormatID || ca_asbd_is_better(&asbd, out_fmt, stream_asbd))
+ *out_fmt = *stream_asbd;
}
- int64_t end = mp_time_us();
- end += p->hw_latency_us + ca_get_latency(ts)
- + ca_frames_to_us(ao, pseudo_frames);
-
- ao_read_data(ao, &buf.mData, pseudo_frames, end);
-
- // Check whether we need to reset the compressed output stream.
- if (atomic_load(&p->stream_asbd_changed)) {
- AudioStreamBasicDescription f;
- OSErr err = CA_GET(p->stream, kAudioStreamPropertyPhysicalFormat, &f);
- CHECK_CA_WARN("could not get stream format");
- if (err == noErr && ca_asbd_equals(&p->stream_asbd, &f))
- atomic_store(&p->stream_asbd_changed, false);
- }
+ talloc_free(formats);
- if (atomic_load(&p->stream_asbd_changed) && !p->reload_requested) {
- p->reload_requested = true;
- ao_request_reload(ao);
- MP_INFO(ao, "Stream format changed! Reloading.\n");
+ if (!out_fmt->mFormatID) {
+ MP_ERR(ao, "no format found\n");
+ return -1;
}
- return noErr;
+ return 0;
+coreaudio_error:
+ return -1;
}
static int init(struct ao *ao)
@@ -378,20 +259,11 @@ static int init(struct ao *ao)
ao->format = af_fmt_from_planar(ao->format);
- if (!AF_FORMAT_IS_IEC61937(ao->format)) {
- MP_ERR(ao, "Only compressed formats are supported.\n");
- goto coreaudio_error_nounlock;
- }
-
- if (!ca_device_supports_compressed(ao, p->device)) {
- MP_ERR(ao, "selected device doesn't support compressed formats\n");
+ if (!af_fmt_is_pcm(ao->format) && !af_fmt_is_spdif(ao->format)) {
+ MP_ERR(ao, "Unsupported format.\n");
goto coreaudio_error_nounlock;
}
- // Build ASBD for the input format
- AudioStreamBasicDescription asbd;
- ca_fill_asbd(ao, &asbd);
-
uint32_t is_alive = 1;
err = CA_GET(p->device, kAudioDevicePropertyDeviceIsAlive, &is_alive);
CHECK_CA_WARN("could not check whether device is alive");
@@ -405,101 +277,55 @@ static int init(struct ao *ao)
err = ca_disable_mixing(ao, p->device, &p->changed_mixing);
CHECK_CA_WARN("failed to disable mixing");
- AudioStreamID *streams;
- size_t n_streams;
-
- /* Get a list of all the streams on this device. */
- err = CA_GET_ARY_O(p->device, kAudioDevicePropertyStreams,
- &streams, &n_streams);
-
- CHECK_CA_ERROR("could not get number of streams");
+ if (select_stream(ao) < 0)
+ goto coreaudio_error;
- for (int i = 0; i < n_streams && p->stream_idx < 0; i++) {
- bool compressed = ca_stream_supports_compressed(ao, streams[i]);
+ AudioStreamBasicDescription hwfmt;
+ if (find_best_format(ao, &hwfmt) < 0)
+ goto coreaudio_error;
- if (compressed) {
- AudioStreamRangedDescription *formats;
- size_t n_formats;
+ err = CA_GET(p->stream, kAudioStreamPropertyPhysicalFormat,
+ &p->original_asbd);
+ CHECK_CA_ERROR("could not get stream's original physical format");
- err = CA_GET_ARY(streams[i],
- kAudioStreamPropertyAvailablePhysicalFormats,
- &formats, &n_formats);
+ // Even if changing the physical format fails, we can try using the current
+ // virtual format.
+ ca_change_physical_format_sync(ao, p->stream, hwfmt);
- if (!CHECK_CA_WARN("could not get number of stream formats"))
- continue; // try next one
+ if (!ca_init_chmap(ao, p->device))
+ goto coreaudio_error;
- int req_rate_format = -1;
- int max_rate_format = -1;
+ err = CA_GET(p->stream, kAudioStreamPropertyVirtualFormat, &p->stream_asbd);
+ CHECK_CA_ERROR("could not get stream's virtual format");
- p->stream = streams[i];
- p->stream_idx = i;
+ ca_print_asbd(ao, "virtual format", &p->stream_asbd);
- for (int j = 0; j < n_formats; j++)
- if (ca_format_is_compressed(formats[j].mFormat)) {
- // select the compressed format that has exactly the same
- // samplerate. If an exact match cannot be found, select
- // the format with highest samplerate as backup.
- if (formats[j].mFormat.mSampleRate == asbd.mSampleRate) {
- req_rate_format = j;
- break;
- } else if (max_rate_format < 0 ||
- formats[j].mFormat.mSampleRate >
- formats[max_rate_format].mFormat.mSampleRate)
- max_rate_format = j;
- }
-
- if (req_rate_format >= 0)
- p->stream_asbd = formats[req_rate_format].mFormat;
- else
- p->stream_asbd = formats[max_rate_format].mFormat;
-
- talloc_free(formats);
- }
- }
+ int new_format = ca_asbd_to_mp_format(&p->stream_asbd);
- talloc_free(streams);
+ // If both old and new formats are spdif, avoid changing it due to the
+ // imperfect mapping between mp and CA formats.
+ if (!(af_fmt_is_spdif(ao->format) && af_fmt_is_spdif(new_format)))
+ ao->format = new_format;
- if (p->stream_idx < 0) {
- MP_WARN(ao , "can't find any compressed output stream format\n");
+ if (!ao->format || af_fmt_is_planar(ao->format)) {
+ MP_ERR(ao, "hardware format not supported\n");
goto coreaudio_error;
}
- err = CA_GET(p->stream, kAudioStreamPropertyPhysicalFormat,
- &p->original_asbd);
- CHECK_CA_ERROR("could not get stream's original physical format");
+ ao->samplerate = p->stream_asbd.mSampleRate;
- if (!ca_change_format(ao, p->stream, p->stream_asbd))
+ if (ao->channels.num != p->stream_asbd.mChannelsPerFrame) {
+ // We really expect that ca_init_chmap() fixes the layout to the HW's.
+ MP_ERR(ao, "number of channels changed, and unknown channel layout!\n");
goto coreaudio_error;
-
- void *changed = &p->stream_asbd_changed;
- err = ca_enable_device_listener(p->device, changed);
- CHECK_CA_ERROR("cannot install format change listener during init");
-
- if (p->stream_asbd.mFormatFlags & kAudioFormatFlagIsBigEndian)
- MP_WARN(ao, "stream has non-native byte order, output may fail\n");
-
- ao->samplerate = p->stream_asbd.mSampleRate;
- ao->bps = ao->samplerate *
- (p->stream_asbd.mBytesPerPacket /
- p->stream_asbd.mFramesPerPacket);
-
- uint32_t latency_frames = 0;
- uint32_t latency_properties[] = {
- kAudioDevicePropertyLatency,
- kAudioDevicePropertyBufferFrameSize,
- kAudioDevicePropertySafetyOffset,
- };
- for (int n = 0; n < MP_ARRAY_SIZE(latency_properties); n++) {
- uint32_t temp;
- err = CA_GET_O(p->device, latency_properties[n], &temp);
- CHECK_CA_WARN("cannot get device latency");
- if (err == noErr)
- latency_frames += temp;
}
- p->hw_latency_us = ca_frames_to_us(ao, latency_frames);
+ p->hw_latency_us = ca_get_device_latency_us(ao, p->device);
MP_VERBOSE(ao, "base latency: %d microseconds\n", (int)p->hw_latency_us);
+ err = enable_property_listener(ao, true);
+ CHECK_CA_ERROR("cannot install format change listener during init");
+
err = AudioDeviceCreateIOProcID(p->device,
(AudioDeviceIOProc)render_cb_compressed,
(void *)ao,
@@ -509,6 +335,8 @@ static int init(struct ao *ao)
return CONTROL_TRUE;
coreaudio_error:
+ err = enable_property_listener(ao, false);
+ CHECK_CA_WARN("can't remove format change listener");
err = ca_unlock_device(p->device, &p->hog_pid);
CHECK_CA_WARN("can't release hog mode");
coreaudio_error_nounlock:
@@ -520,8 +348,7 @@ static void uninit(struct ao *ao)
struct priv *p = ao->priv;
OSStatus err = noErr;
- void *changed = &p->stream_asbd_changed;
- err = ca_disable_device_listener(p->device, changed);
+ err = enable_property_listener(ao, false);
CHECK_CA_WARN("can't remove device listener, this may cause a crash");
err = AudioDeviceStop(p->device, p->render_cb);
@@ -530,7 +357,7 @@ static void uninit(struct ao *ao)
err = AudioDeviceDestroyIOProcID(p->device, p->render_cb);
CHECK_CA_WARN("failed to remove device render callback");
- if (!ca_change_format(ao, p->stream, p->original_asbd))
+ if (!ca_change_physical_format_sync(ao, p->stream, p->original_asbd))
MP_WARN(ao, "can't revert to original device format");
err = ca_enable_mixing(ao, p->device, p->changed_mixing);
@@ -568,7 +395,6 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
.list_devs = ca_get_device_list,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv){
- .stream_asbd_changed = ATOMIC_VAR_INIT(false),
.hog_pid = -1,
.stream = 0,
.stream_idx = -1,
diff --git a/audio/out/ao_coreaudio_utils.c b/audio/out/ao_coreaudio_utils.c
index 6c26dbb..f6463a3 100644
--- a/audio/out/ao_coreaudio_utils.c
+++ b/audio/out/ao_coreaudio_utils.c
@@ -28,6 +28,7 @@
#include "audio/out/ao_coreaudio_properties.h"
#include "osdep/timer.h"
#include "osdep/endian.h"
+#include "osdep/semaphore.h"
#include "audio/format.h"
CFStringRef cfstr_from_cstr(char *str)
@@ -167,16 +168,22 @@ static void ca_fill_asbd_raw(AudioStreamBasicDescription *asbd, int mp_format,
{
asbd->mSampleRate = samplerate;
// Set "AC3" for other spdif formats too - unknown if that works.
- asbd->mFormatID = AF_FORMAT_IS_IEC61937(mp_format) ?
+ asbd->mFormatID = af_fmt_is_spdif(mp_format) ?
kAudioFormat60958AC3 :
kAudioFormatLinearPCM;
asbd->mChannelsPerFrame = num_channels;
- asbd->mBitsPerChannel = af_fmt2bits(mp_format);
+ asbd->mBitsPerChannel = af_fmt_to_bytes(mp_format) * 8;
asbd->mFormatFlags = kAudioFormatFlagIsPacked;
- if ((mp_format & AF_FORMAT_TYPE_MASK) == AF_FORMAT_F) {
+ int channels_per_buffer = num_channels;
+ if (af_fmt_is_planar(mp_format)) {
+ asbd->mFormatFlags |= kAudioFormatFlagIsNonInterleaved;
+ channels_per_buffer = 1;
+ }
+
+ if (af_fmt_is_float(mp_format)) {
asbd->mFormatFlags |= kAudioFormatFlagIsFloat;
- } else if ((mp_format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_SI) {
+ } else if (!af_fmt_is_unsigned(mp_format)) {
asbd->mFormatFlags |= kAudioFormatFlagIsSignedInteger;
}
@@ -185,7 +192,7 @@ static void ca_fill_asbd_raw(AudioStreamBasicDescription *asbd, int mp_format,
asbd->mFramesPerPacket = 1;
asbd->mBytesPerPacket = asbd->mBytesPerFrame =
- asbd->mFramesPerPacket * asbd->mChannelsPerFrame *
+ asbd->mFramesPerPacket * channels_per_buffer *
(asbd->mBitsPerChannel / 8);
}
@@ -194,7 +201,7 @@ void ca_fill_asbd(struct ao *ao, AudioStreamBasicDescription *asbd)
ca_fill_asbd_raw(asbd, ao->format, ao->samplerate, ao->channels.num);
}
-static bool ca_formatid_is_digital(uint32_t formatid)
+bool ca_formatid_is_compressed(uint32_t formatid)
{
switch (formatid)
case 'IAC3':
@@ -208,31 +215,34 @@ static bool ca_formatid_is_digital(uint32_t formatid)
// This might be wrong, but for now it's sufficient for us.
static uint32_t ca_normalize_formatid(uint32_t formatID)
{
- return ca_formatid_is_digital(formatID) ? kAudioFormat60958AC3 : formatID;
+ return ca_formatid_is_compressed(formatID) ? kAudioFormat60958AC3 : formatID;
}
bool ca_asbd_equals(const AudioStreamBasicDescription *a,
const AudioStreamBasicDescription *b)
{
int flags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsFloat |
- kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsBigEndian;
+ kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsBigEndian;
+ bool spdif = ca_formatid_is_compressed(a->mFormatID) &&
+ ca_formatid_is_compressed(b->mFormatID);
return (a->mFormatFlags & flags) == (b->mFormatFlags & flags) &&
a->mBitsPerChannel == b->mBitsPerChannel &&
ca_normalize_formatid(a->mFormatID) ==
ca_normalize_formatid(b->mFormatID) &&
- a->mBytesPerPacket == b->mBytesPerPacket;
+ (spdif || a->mBytesPerPacket == b->mBytesPerPacket) &&
+ (spdif || a->mChannelsPerFrame == b->mChannelsPerFrame) &&
+ a->mSampleRate == b->mSampleRate;
}
// Return the AF_FORMAT_* (AF_FORMAT_S16 etc.) corresponding to the asbd.
int ca_asbd_to_mp_format(const AudioStreamBasicDescription *asbd)
{
- for (int n = 0; af_fmtstr_table[n].format; n++) {
- int mp_format = af_fmtstr_table[n].format;
+ for (int fmt = 1; fmt < AF_FORMAT_COUNT; fmt++) {
AudioStreamBasicDescription mp_asbd = {0};
- ca_fill_asbd_raw(&mp_asbd, mp_format, 0, asbd->mChannelsPerFrame);
+ ca_fill_asbd_raw(&mp_asbd, fmt, asbd->mSampleRate, asbd->mChannelsPerFrame);
if (ca_asbd_equals(&mp_asbd, asbd))
- return mp_format;
+ return af_fmt_is_spdif(fmt) ? AF_FORMAT_S_AC3 : fmt;
}
return 0;
}
@@ -317,3 +327,229 @@ int64_t ca_get_latency(const AudioTimeStamp *ts)
return (out - now) * 1e-3;
}
+
+bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream)
+{
+ AudioStreamRangedDescription *formats = NULL;
+ size_t n_formats;
+
+ OSStatus err =
+ CA_GET_ARY(stream, kAudioStreamPropertyAvailablePhysicalFormats,
+ &formats, &n_formats);
+
+ CHECK_CA_ERROR("Could not get number of stream formats.");
+
+ for (int i = 0; i < n_formats; i++) {
+ AudioStreamBasicDescription asbd = formats[i].mFormat;
+ if (ca_formatid_is_compressed(asbd.mFormatID)) {
+ talloc_free(formats);
+ return true;
+ }
+ }
+
+ talloc_free(formats);
+coreaudio_error:
+ return false;
+}
+
+bool ca_device_supports_compressed(struct ao *ao, AudioDeviceID device)
+{
+ AudioStreamID *streams = NULL;
+ size_t n_streams;
+
+ /* Retrieve all the output streams. */
+ OSStatus err =
+ CA_GET_ARY_O(device, kAudioDevicePropertyStreams, &streams, &n_streams);
+
+ CHECK_CA_ERROR("could not get number of streams.");
+
+ for (int i = 0; i < n_streams; i++) {
+ if (ca_stream_supports_compressed(ao, streams[i])) {
+ talloc_free(streams);
+ return true;
+ }
+ }
+
+ talloc_free(streams);
+
+coreaudio_error:
+ return false;
+}
+
+OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid)
+{
+ *pid = getpid();
+ OSStatus err = CA_SET(device, kAudioDevicePropertyHogMode, pid);
+ if (err != noErr)
+ *pid = -1;
+
+ return err;
+}
+
+OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid)
+{
+ if (*pid == getpid()) {
+ *pid = -1;
+ return CA_SET(device, kAudioDevicePropertyHogMode, &pid);
+ }
+ return noErr;
+}
+
+static OSStatus ca_change_mixing(struct ao *ao, AudioDeviceID device,
+ uint32_t val, bool *changed)
+{
+ *changed = false;
+
+ AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
+ .mSelector = kAudioDevicePropertySupportsMixing,
+ .mScope = kAudioObjectPropertyScopeGlobal,
+ .mElement = kAudioObjectPropertyElementMaster,
+ };
+
+ if (AudioObjectHasProperty(device, &p_addr)) {
+ OSStatus err;
+ Boolean writeable = 0;
+ err = CA_SETTABLE(device, kAudioDevicePropertySupportsMixing,
+ &writeable);
+
+ if (!CHECK_CA_WARN("can't tell if mixing property is settable")) {
+ return err;
+ }
+
+ if (!writeable)
+ return noErr;
+
+ err = CA_SET(device, kAudioDevicePropertySupportsMixing, &val);
+ if (err != noErr)
+ return err;
+
+ if (!CHECK_CA_WARN("can't set mix mode")) {
+ return err;
+ }
+
+ *changed = true;
+ }
+
+ return noErr;
+}
+
+OSStatus ca_disable_mixing(struct ao *ao, AudioDeviceID device, bool *changed)
+{
+ return ca_change_mixing(ao, device, 0, changed);
+}
+
+OSStatus ca_enable_mixing(struct ao *ao, AudioDeviceID device, bool changed)
+{
+ if (changed) {
+ bool dont_care = false;
+ return ca_change_mixing(ao, device, 1, &dont_care);
+ }
+
+ return noErr;
+}
+
+int64_t ca_get_device_latency_us(struct ao *ao, AudioDeviceID device)
+{
+ uint32_t latency_frames = 0;
+ uint32_t latency_properties[] = {
+ kAudioDevicePropertyLatency,
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioDevicePropertySafetyOffset,
+ };
+ for (int n = 0; n < MP_ARRAY_SIZE(latency_properties); n++) {
+ uint32_t temp;
+ OSStatus err = CA_GET_O(device, latency_properties[n], &temp);
+ CHECK_CA_WARN("cannot get device latency");
+ if (err == noErr) {
+ latency_frames += temp;
+ MP_VERBOSE(ao, "Latency property %s: %d frames\n",
+ fourcc_repr(latency_properties[n]), (int)temp);
+ }
+ }
+
+ return ca_frames_to_us(ao, latency_frames);
+}
+
+static OSStatus ca_change_format_listener(
+ AudioObjectID object, uint32_t n_addresses,
+ const AudioObjectPropertyAddress addresses[],
+ void *data)
+{
+ sem_t *sem = data;
+ sem_post(sem);
+ return noErr;
+}
+
+bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
+ AudioStreamBasicDescription change_format)
+{
+ OSStatus err = noErr;
+ bool format_set = false;
+
+ ca_print_asbd(ao, "setting stream physical format:", &change_format);
+
+ sem_t wakeup;
+ if (sem_init(&wakeup, 0, 0)) {
+ MP_WARN(ao, "OOM\n");
+ return false;
+ }
+
+ AudioStreamBasicDescription prev_format;
+ err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &prev_format);
+ CHECK_CA_ERROR("can't get current physical format");
+
+ /* Install the callback. */
+ AudioObjectPropertyAddress p_addr = {
+ .mSelector = kAudioStreamPropertyPhysicalFormat,
+ .mScope = kAudioObjectPropertyScopeGlobal,
+ .mElement = kAudioObjectPropertyElementMaster,
+ };
+
+ err = AudioObjectAddPropertyListener(stream, &p_addr,
+ ca_change_format_listener,
+ &wakeup);
+ CHECK_CA_ERROR("can't add property listener during format change");
+
+ /* Change the format. */
+ err = CA_SET(stream, kAudioStreamPropertyPhysicalFormat, &change_format);
+ CHECK_CA_WARN("error changing physical format");
+
+ /* The AudioStreamSetProperty is not only asynchronous,
+ * it is also not Atomic, in its behaviour. */
+ struct timespec timeout = mp_rel_time_to_timespec(0.5);
+ AudioStreamBasicDescription actual_format = {0};
+ while (1) {
+ err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &actual_format);
+ if (!CHECK_CA_WARN("could not retrieve physical format"))
+ break;
+
+ format_set = ca_asbd_equals(&change_format, &actual_format);
+ if (format_set)
+ break;
+
+ if (sem_timedwait(&wakeup, &timeout)) {
+ MP_VERBOSE(ao, "reached timeout\n");
+ break;
+ }
+ }
+
+ ca_print_asbd(ao, "actual format in use:", &actual_format);
+
+ if (!format_set) {
+ MP_WARN(ao, "changing physical format failed\n");
+ // Some drivers just fuck up and get into a broken state. Restore the
+ // old format in this case.
+ err = CA_SET(stream, kAudioStreamPropertyPhysicalFormat, &prev_format);
+ CHECK_CA_WARN("error restoring physical format");
+ }
+
+ err = AudioObjectRemovePropertyListener(stream, &p_addr,
+ ca_change_format_listener,
+ &wakeup);
+ CHECK_CA_ERROR("can't remove property listener");
+
+coreaudio_error:
+ sem_destroy(&wakeup);
+ return format_set;
+}
+
diff --git a/audio/out/ao_coreaudio_utils.h b/audio/out/ao_coreaudio_utils.h
index 50498a9..a0b5aa0 100644
--- a/audio/out/ao_coreaudio_utils.h
+++ b/audio/out/ao_coreaudio_utils.h
@@ -54,6 +54,7 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message);
void ca_get_device_list(struct ao *ao, struct ao_device_list *list);
OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device);
+bool ca_formatid_is_compressed(uint32_t formatid);
void ca_fill_asbd(struct ao *ao, AudioStreamBasicDescription *asbd);
void ca_print_asbd(struct ao *ao, const char *description,
const AudioStreamBasicDescription *asbd);
@@ -67,4 +68,14 @@ bool ca_asbd_is_better(AudioStreamBasicDescription *req,
int64_t ca_frames_to_us(struct ao *ao, uint32_t frames);
int64_t ca_get_latency(const AudioTimeStamp *ts);
+bool ca_device_supports_compressed(struct ao *ao, AudioDeviceID device);
+bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream);
+OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid);
+OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid);
+OSStatus ca_disable_mixing(struct ao *ao, AudioDeviceID device, bool *changed);
+OSStatus ca_enable_mixing(struct ao *ao, AudioDeviceID device, bool changed);
+int64_t ca_get_device_latency_us(struct ao *ao, AudioDeviceID device);
+bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
+ AudioStreamBasicDescription change_format);
+
#endif /* MPV_COREAUDIO_UTILS_H */
diff --git a/audio/out/ao_dsound.c b/audio/out/ao_dsound.c
index 9d216e6..c581bf5 100644
--- a/audio/out/ao_dsound.c
+++ b/audio/out/ao_dsound.c
@@ -288,7 +288,7 @@ static int InitDirectSound(struct ao *ao)
/* 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 hearable when it will have the focus.
+ * 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
@@ -444,7 +444,7 @@ static int init(struct ao *ao)
int format = af_fmt_from_planar(ao->format);
int rate = ao->samplerate;
- if (!AF_FORMAT_IS_IEC61937(format)) {
+ 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))
@@ -456,7 +456,7 @@ static int init(struct ao *ao)
case AF_FORMAT_U8:
break;
default:
- if (AF_FORMAT_IS_IEC61937(format))
+ 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));
@@ -465,7 +465,7 @@ static int init(struct ao *ao)
//set our audio parameters
ao->samplerate = rate;
ao->format = format;
- ao->bps = ao->channels.num * rate * af_fmt2bps(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));
@@ -478,7 +478,7 @@ static int init(struct ao *ao)
? sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) : 0;
wformat.Format.nChannels = ao->channels.num;
wformat.Format.nSamplesPerSec = rate;
- if (AF_FORMAT_IS_IEC61937(format)) {
+ 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;
@@ -486,7 +486,7 @@ static int init(struct ao *ao)
} else {
wformat.Format.wFormatTag = (ao->channels.num > 2)
? WAVE_FORMAT_EXTENSIBLE : WAVE_FORMAT_PCM;
- int bps = af_fmt2bps(format);
+ int bps = af_fmt_to_bytes(format);
wformat.Format.wBitsPerSample = bps * 8;
wformat.Format.nBlockAlign = wformat.Format.nChannels * bps;
}
@@ -616,9 +616,9 @@ static int check_free_buffer_size(struct ao *ao)
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 postion of the play cursor
+ // 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 postion where we actually write the data to
+ // 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
diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c
index b322982..eedc9c6 100644
--- a/audio/out/ao_lavc.c
+++ b/audio/out/ao_lavc.c
@@ -25,7 +25,6 @@
#include <limits.h>
#include <libavutil/common.h>
-#include <libavutil/audioconvert.h>
#include "config.h"
#include "options/options.h"
@@ -136,7 +135,7 @@ static int init(struct ao *ao)
select_format(ao, codec);
- ac->sample_size = af_fmt2bps(ao->format);
+ ac->sample_size = af_fmt_to_bytes(ao->format);
ac->stream->codec->sample_fmt = af_to_avformat(ao->format);
ac->stream->codec->bits_per_raw_sample = ac->sample_size * 8;
@@ -241,7 +240,7 @@ static int encode(struct ao *ao, double apts, void **data)
frame->format = af_to_avformat(ao->format);
frame->nb_samples = ac->aframesize;
- size_t num_planes = AF_FORMAT_IS_PLANAR(ao->format) ? ao->channels.num : 1;
+ size_t num_planes = af_fmt_is_planar(ao->format) ? ao->channels.num : 1;
assert(num_planes <= AV_NUM_DATA_POINTERS);
for (int n = 0; n < num_planes; n++)
frame->extended_data[n] = data[n];
@@ -350,7 +349,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
double pts = ectx->last_audio_in_pts;
pts += ectx->samples_since_last_pts / (double)ao->samplerate;
- size_t num_planes = AF_FORMAT_IS_PLANAR(ao->format) ? ao->channels.num : 1;
+ size_t num_planes = af_fmt_is_planar(ao->format) ? ao->channels.num : 1;
void *tempdata = NULL;
void *padded[MP_NUM_CHANNELS];
diff --git a/audio/out/ao_null.c b/audio/out/ao_null.c
index 0b5ff34..0af0a97 100644
--- a/audio/out/ao_null.c
+++ b/audio/out/ao_null.c
@@ -56,6 +56,8 @@ struct priv {
// called with sizes not aligned to this, a rounded size will be returned.
// (This is not needed by the AO API, but many AOs behave this way.)
int outburst; // samples
+
+ char **channel_layouts;
};
static void drain(struct ao *ao)
@@ -88,8 +90,19 @@ static int init(struct ao *ao)
ao->untimed = priv->untimed;
- struct mp_chmap_sel sel = {0};
- mp_chmap_sel_add_any(&sel);
+ struct mp_chmap_sel sel = {.tmp = ao};
+ if (priv->channel_layouts) {
+ for (int n = 0; priv->channel_layouts[n]; n++) {
+ struct mp_chmap map = {0};
+ if (!mp_chmap_from_str(&map, bstr0(priv->channel_layouts[n]))) {
+ MP_FATAL(ao, "Invalid channel map in option.\n");
+ return -1;
+ }
+ mp_chmap_sel_add_map(&sel, &map);
+ }
+ } else {
+ mp_chmap_sel_add_any(&sel);
+ }
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
mp_chmap_from_channels(&ao->channels, 2);
@@ -231,6 +244,7 @@ const struct ao_driver audio_out_null = {
OPT_FLOATRANGE("latency", latency_sec, 0, 0, 100),
OPT_FLAG("broken-eof", broken_eof, 0),
OPT_FLAG("broken-delay", broken_delay, 0),
+ OPT_STRINGLIST("channel-layouts", channel_layouts, 0),
{0}
},
};
diff --git a/audio/out/ao_oss.c b/audio/out/ao_oss.c
index bf320e9..3219087 100644
--- a/audio/out/ao_oss.c
+++ b/audio/out/ao_oss.c
@@ -94,40 +94,20 @@ static const struct mp_chmap oss_layouts[MP_NUM_CHANNELS + 1] = {
#define AFMT_S16_NE MP_SELECT_LE_BE(AFMT_S16_LE, AFMT_S16_BE)
#endif
-#if !defined(AFMT_U16_NE) && defined(AFMT_U16_LE) && defined(AFMT_U16_BE)
-#define AFMT_U16_NE MP_SELECT_LE_BE(AFMT_U16_LE, AFMT_U16_BE)
-#endif
-
-#if !defined(AFMT_U24_NE) && defined(AFMT_U24_LE) && defined(AFMT_U24_BE)
-#define AFMT_U24_NE MP_SELECT_LE_BE(AFMT_U24_LE, AFMT_U24_BE)
-#endif
-
#if !defined(AFMT_S24_NE) && defined(AFMT_S24_LE) && defined(AFMT_S24_BE)
#define AFMT_S24_NE MP_SELECT_LE_BE(AFMT_S24_LE, AFMT_S24_BE)
#endif
-#if !defined(AFMT_U32_NE) && defined(AFMT_U32_LE) && defined(AFMT_U32_BE)
-#define AFMT_U32_NE AFMT_U32MP_SELECT_LE_BE(AFMT_U32_LE, AFMT_U32_BE)
-#endif
-
#if !defined(AFMT_S32_NE) && defined(AFMT_S32_LE) && defined(AFMT_S32_BE)
#define AFMT_S32_NE AFMT_S32MP_SELECT_LE_BE(AFMT_S32_LE, AFMT_S32_BE)
#endif
static const int format_table[][2] = {
{AFMT_U8, AF_FORMAT_U8},
- {AFMT_S8, AF_FORMAT_S8},
- {AFMT_U16_NE, AF_FORMAT_U16},
{AFMT_S16_NE, AF_FORMAT_S16},
-#ifdef AFMT_U24_NE
- {AFMT_U24_NE, AF_FORMAT_U24},
-#endif
#ifdef AFMT_S24_NE
{AFMT_S24_NE, AF_FORMAT_S24},
#endif
-#ifdef AFMT_U32_NE
- {AFMT_U32_NE, AF_FORMAT_U32},
-#endif
#ifdef AFMT_S32_NE
{AFMT_S32_NE, AF_FORMAT_S32},
#endif
@@ -204,7 +184,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
return CONTROL_OK;
#endif
- if (AF_FORMAT_IS_SPECIAL(ao->format))
+ if (!af_fmt_is_pcm(ao->format))
return CONTROL_TRUE;
if ((fd = open(p->oss_mixer_device, O_RDONLY)) != -1) {
@@ -267,7 +247,7 @@ static bool try_format(struct ao *ao, int *format)
struct priv *p = ao->priv;
int oss_format = format2oss(*format);
- if (oss_format == -1 && AF_FORMAT_IS_IEC61937(*format))
+ if (oss_format == -1 && af_fmt_is_spdif(*format))
oss_format = AFMT_AC3;
if (oss_format == -1) {
@@ -323,7 +303,7 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
fcntl(p->audio_fd, F_SETFD, FD_CLOEXEC);
#endif
- if (AF_FORMAT_IS_IEC61937(format)) {
+ if (af_fmt_is_spdif(format)) {
if (ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &samplerate) == -1)
goto fail;
// Probably could be fixed by setting number of channels; needs testing.
@@ -347,7 +327,7 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
MP_VERBOSE(ao, "sample format: %s\n", af_fmt_to_str(format));
- if (!AF_FORMAT_IS_IEC61937(format)) {
+ if (!af_fmt_is_spdif(format)) {
struct mp_chmap_sel sel = {0};
for (int n = 0; n < MP_NUM_CHANNELS + 1; n++)
mp_chmap_sel_add_map(&sel, &oss_layouts[n]);
@@ -412,7 +392,7 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
}
}
- p->outburst -= p->outburst % (channels.num * af_fmt2bps(format)); // round down
+ p->outburst -= p->outburst % (channels.num * af_fmt_to_bytes(format)); // round down
return 0;
diff --git a/audio/out/ao_pcm.c b/audio/out/ao_pcm.c
index 5b9d61b..76f0315 100644
--- a/audio/out/ao_pcm.c
+++ b/audio/out/ao_pcm.c
@@ -73,7 +73,7 @@ static void fput32le(uint32_t val, FILE *fp)
static void write_wave_header(struct ao *ao, FILE *fp, uint64_t data_length)
{
uint16_t fmt = ao->format == AF_FORMAT_FLOAT ? WAV_ID_FLOAT_PCM : WAV_ID_PCM;
- int bits = af_fmt2bits(ao->format);
+ int bits = af_fmt_to_bytes(ao->format) * 8;
// Master RIFF chunk
fput32le(WAV_ID_RIFF, fp);
@@ -135,7 +135,7 @@ static int init(struct ao *ao)
case AF_FORMAT_FLOAT:
break;
default:
- if (!AF_FORMAT_IS_IEC61937(ao->format))
+ if (!af_fmt_is_spdif(ao->format))
ao->format = AF_FORMAT_S16;
break;
}
@@ -146,7 +146,7 @@ static int init(struct ao *ao)
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
return -1;
- ao->bps = ao->channels.num * ao->samplerate * af_fmt2bps(ao->format);
+ ao->bps = ao->channels.num * ao->samplerate * af_fmt_to_bytes(ao->format);
MP_INFO(ao, "File: %s (%s)\nPCM: Samplerate: %d Hz Channels: %d Format: %s\n",
priv->outputfilename,
diff --git a/audio/out/ao_pulse.c b/audio/out/ao_pulse.c
index f797802..78d8ac2 100644
--- a/audio/out/ao_pulse.c
+++ b/audio/out/ao_pulse.c
@@ -210,7 +210,7 @@ static pa_encoding_t map_digital_format(int format)
case AF_FORMAT_S_AAC: return PA_ENCODING_MPEG2_AAC_IEC61937;
#endif
default:
- if (AF_FORMAT_IS_IEC61937(format))
+ if (af_fmt_is_spdif(format))
return PA_ENCODING_ANY;
return PA_ENCODING_PCM;
}
diff --git a/audio/out/ao_rsound.c b/audio/out/ao_rsound.c
index ef1a001..0385b09 100644
--- a/audio/out/ao_rsound.c
+++ b/audio/out/ao_rsound.c
@@ -48,26 +48,9 @@ static int set_format(struct ao *ao)
case AF_FORMAT_U8:
rsd_format = RSD_U8;
break;
- case AF_FORMAT_S8:
- rsd_format = RSD_S8;
- break;
- case AF_FORMAT_S16:
- rsd_format = RSD_S16_NE;
- break;
- case AF_FORMAT_U16:
- rsd_format = RSD_U16_NE;
- break;
- case AF_FORMAT_S24:
- case AF_FORMAT_U24:
- rsd_format = RSD_S32_NE;
- ao->format = AF_FORMAT_S32;
- break;
case AF_FORMAT_S32:
rsd_format = RSD_S32_NE;
break;
- case AF_FORMAT_U32:
- rsd_format = RSD_U32_NE;
- break;
default:
rsd_format = RSD_S16_NE;
ao->format = AF_FORMAT_S16;
diff --git a/audio/out/ao_sdl.c b/audio/out/ao_sdl.c
index c1ecff8..bf65a4a 100644
--- a/audio/out/ao_sdl.c
+++ b/audio/out/ao_sdl.c
@@ -39,8 +39,6 @@ struct priv
static const int fmtmap[][2] = {
{AF_FORMAT_U8, AUDIO_U8},
- {AF_FORMAT_S8, AUDIO_S8},
- {AF_FORMAT_U16, AUDIO_U16SYS},
{AF_FORMAT_S16, AUDIO_S16SYS},
#ifdef AUDIO_S32SYS
{AF_FORMAT_S32, AUDIO_S32SYS},
diff --git a/audio/out/ao_sndio.c b/audio/out/ao_sndio.c
index abce0ef..e9df269 100644
--- a/audio/out/ao_sndio.c
+++ b/audio/out/ao_sndio.c
@@ -99,8 +99,7 @@ static const struct mp_chmap sndio_layouts[MP_NUM_CHANNELS + 1] = {
MP_CHMAP8(FL, FR, BL, BR, FC, LFE, SL, SR), // 7.1
/* above is the fixed channel assignment for sndio, since we need to fill
all channels and cannot insert silence, not all layouts are supported.
- NOTE: MP_SPEAKER_ID_NA could be used to add padding channels. Keep
- in mind that they don't actually contain silence. */
+ NOTE: MP_SPEAKER_ID_NA could be used to add padding channels. */
};
/*
@@ -116,12 +115,8 @@ static int init(struct ao *ao)
};
static const struct af_to_par af_to_par[] = {
{AF_FORMAT_U8, 8, 0},
- {AF_FORMAT_S8, 8, 1},
- {AF_FORMAT_U16, 16, 0},
{AF_FORMAT_S16, 16, 1},
- {AF_FORMAT_U24, 24, 0},
{AF_FORMAT_S24, 24, 1},
- {AF_FORMAT_U32, 32, 0},
{AF_FORMAT_S32, 32, 1},
};
const struct af_to_par *ap;
@@ -178,14 +173,14 @@ static int init(struct ao *ao)
MP_ERR(ao, "swapped endian output not supported\n");
goto error;
}
- if (p->par.bits == 8 && p->par.bps == 1) {
- ao->format = p->par.sig ? AF_FORMAT_S8 : AF_FORMAT_U8;
- } else if (p->par.bits == 16 && p->par.bps == 2) {
- ao->format = p->par.sig ? AF_FORMAT_S16 : AF_FORMAT_U16;
- } else if ((p->par.bits == 24 || p->par.msb) && p->par.bps == 3) {
- ao->format = p->par.sig ? AF_FORMAT_S24 : AF_FORMAT_U24;
- } else if ((p->par.bits == 32 || p->par.msb) && p->par.bps == 4) {
- ao->format = p->par.sig ? AF_FORMAT_S32 : AF_FORMAT_U32;
+ if (p->par.bits == 8 && p->par.bps == 1 && !p->par.sig) {
+ ao->format = AF_FORMAT_U8;
+ } else if (p->par.bits == 16 && p->par.bps == 2 && p->par.sig) {
+ ao->format = AF_FORMAT_S16;
+ } else if ((p->par.bits == 24 || p->par.msb) && p->par.bps == 3 && p->par.sig) {
+ ao->format = AF_FORMAT_S24;
+ } else if ((p->par.bits == 32 || p->par.msb) && p->par.bps == 4 && p->par.sig) {
+ ao->format = AF_FORMAT_S32;
} else {
MP_ERR(ao, "couldn't set format\n");
goto error;
diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c
index 3c0bab3..81b7535 100644
--- a/audio/out/ao_wasapi.c
+++ b/audio/out/ao_wasapi.c
@@ -390,6 +390,7 @@ static int hotplug_init(struct ao *ao)
{
MP_DBG(ao, "Hotplug init\n");
struct wasapi_state *state = ao->priv;
+ state->log = ao->log;
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
&IID_IMMDeviceEnumerator, (void **)&state->pEnumerator);
diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c
index b0b0c10..fd65cc8 100755
--- a/audio/out/ao_wasapi_utils.c
+++ b/audio/out/ao_wasapi_utils.c
@@ -85,13 +85,13 @@ const struct wasapi_fmt_mapping wasapi_fmt_table[] = {
static const GUID *format_to_subtype(int format)
{
- if (AF_FORMAT_IS_SPECIAL(format)) {
+ if (af_fmt_is_spdif(format)) {
for (int i = 0; wasapi_fmt_table[i].format; i++) {
if (wasapi_fmt_table[i].format == format)
return wasapi_fmt_table[i].subtype;
}
return &KSDATAFORMAT_SPECIFIER_NONE;
- } else if (AF_FORMAT_IS_FLOAT(format)) {
+ } else if (af_fmt_is_float(format)) {
return &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
}
return &KSDATAFORMAT_SUBTYPE_PCM;
@@ -229,7 +229,7 @@ static void set_waveformat(WAVEFORMATEXTENSIBLE *wformat,
wformat->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wformat->Format.nChannels = channels->num;
wformat->Format.nSamplesPerSec = samplerate;
- wformat->Format.wBitsPerSample = af_fmt2bits(format);
+ wformat->Format.wBitsPerSample = af_fmt_to_bytes(format) * 8;
wformat->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
wformat->SubFormat = *format_to_subtype(format);
@@ -306,9 +306,11 @@ static int format_from_waveformat(WAVEFORMATEX *wf)
// Since mpv doesn't have the notion of "valid bits", we just specify a
// format with the container size. The least significant, "invalid"
// bits will be excess precision ignored by wasapi.
- // The change_bits operations should be a no-op for properly
+ // The change_bytes operations should be a no-op for properly
// configured "special" formats, otherwise it will return 0.
- return af_fmt_change_bits(format, wf->wBitsPerSample);
+ if (wf->wBitsPerSample % 8)
+ return 0;
+ return af_fmt_change_bytes(format, wf->wBitsPerSample / 8);
}
static bool chmap_from_waveformat(struct mp_chmap *channels, const WAVEFORMATEX *wf)
@@ -365,7 +367,7 @@ static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf, AUDCLNT_SHAREMODE sha
}
// Do not touch the ao for passthrough, just assume that we set WAVEFORMATEX correctly.
- if (!AF_FORMAT_IS_SPECIAL(format)) {
+ if (af_fmt_is_pcm(format)) {
struct mp_chmap channels;
if (!chmap_from_waveformat(&channels, wf)) {
MP_ERR(ao, "Unable to construct channel map from WAVEFORMAT %s\n",
@@ -575,7 +577,7 @@ static bool find_formats(struct ao *ao)
// might be passthrough). If that fails, do a pcm format
// search.
return find_formats_exclusive(ao, true);
- } else if (AF_FORMAT_IS_SPECIAL(ao->format)) {
+ } else if (af_fmt_is_spdif(ao->format)) {
// If a passthrough format is requested, but exclusive mode
// was not explicitly set, try only the requested passthrough
// format in exclusive mode. Fall back on shared mode if that
diff --git a/audio/out/pull.c b/audio/out/pull.c
index 951034d..ed85c4e 100644
--- a/audio/out/pull.c
+++ b/audio/out/pull.c
@@ -155,7 +155,7 @@ end:
// pad with silence (underflow/paused/eof)
for (int n = 0; n < ao->num_planes; n++)
- af_fill_silence(data[n], full_bytes - bytes, ao->format);
+ af_fill_silence((char *)data[n] + bytes, full_bytes - bytes, ao->format);
return bytes / ao->sstride;
}
@@ -206,15 +206,6 @@ static void resume(struct ao *ao)
ao->driver->resume(ao);
}
-static void drain(struct ao *ao)
-{
- struct ao_pull_state *p = ao->api_priv;
- int state = atomic_load(&p->state);
- if (IS_PLAYING(state))
- mp_sleep_us(get_delay(ao) * 1000000);
- reset(ao);
-}
-
static bool get_eof(struct ao *ao)
{
struct ao_pull_state *p = ao->api_priv;
@@ -223,6 +214,24 @@ static bool get_eof(struct ao *ao)
return mp_ring_buffered(p->buffers[0]) == 0;
}
+static void drain(struct ao *ao)
+{
+ struct ao_pull_state *p = ao->api_priv;
+ int state = atomic_load(&p->state);
+ if (IS_PLAYING(state)) {
+ // Wait for lower bound.
+ mp_sleep_us(mp_ring_buffered(p->buffers[0]) / (double)ao->bps * 1e6);
+ // And then poll for actual end. (Unfortunately, this code considers
+ // audio APIs which do not want you to use mutexes in the audio
+ // callback, and an extra semaphore would require slightly more effort.)
+ // Limit to arbitrary ~250ms max. waiting for robustness.
+ int64_t max = mp_time_us() + 250000;
+ while (mp_time_us() < max && !get_eof(ao))
+ mp_sleep_us(1);
+ }
+ reset(ao);
+}
+
static void uninit(struct ao *ao)
{
ao->driver->uninit(ao);
diff --git a/audio/out/push.c b/audio/out/push.c
index beafd36..301004b 100644
--- a/audio/out/push.c
+++ b/audio/out/push.c
@@ -438,7 +438,7 @@ const struct ao_driver ao_api_push = {
int ao_play_silence(struct ao *ao, int samples)
{
assert(ao->api == &ao_api_push);
- if (samples <= 0 || AF_FORMAT_IS_SPECIAL(ao->format) || !ao->driver->play)
+ if (samples <= 0 || !af_fmt_is_pcm(ao->format) || !ao->driver->play)
return 0;
char *p = talloc_size(NULL, samples * ao->sstride);
af_fill_silence(p, samples * ao->sstride, ao->format);
diff --git a/bootstrap.py b/bootstrap.py
index 7ffd39a..e526ff2 100755
--- a/bootstrap.py
+++ b/bootstrap.py
@@ -5,13 +5,13 @@
from __future__ import print_function
import os, sys, stat, hashlib, subprocess
-WAFRELEASE = "waf-1.8.4"
-WAFURLS = ["http://ftp.waf.io/pub/release/" + WAFRELEASE,
+WAFRELEASE = "waf-1.8.12"
+WAFURLS = ["https://waf.io/" + WAFRELEASE,
"http://www.freehackers.org/~tnagy/release/" + WAFRELEASE]
-SHA256HASH = "f02035fa5d8814f33f19b2b20d43822ddef6bb39b955ca196c2a247a1f9ffaa8"
+SHA256HASH = "01bf2beab2106d1558800c8709bc2c8e496d3da4a2ca343fe091f22fca60c98b"
if os.path.exists("waf"):
- wafver = subprocess.check_output(['./waf', '--version']).decode()
+ wafver = subprocess.check_output([sys.executable, './waf', '--version']).decode()
if WAFRELEASE.split('-')[1] == wafver.split(' ')[1]:
print("Found 'waf', skipping download.")
sys.exit(0)
diff --git a/common/av_common.c b/common/av_common.c
index 05c6947..87616b9 100644
--- a/common/av_common.c
+++ b/common/av_common.c
@@ -59,15 +59,12 @@ void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st)
avctx->width = st->width;
avctx->height = st->height;
avctx->pix_fmt = st->pix_fmt;
- avctx->sample_aspect_ratio = st->sample_aspect_ratio;
avctx->chroma_sample_location = st->chroma_sample_location;
avctx->sample_rate = st->sample_rate;
avctx->channels = st->channels;
avctx->block_align = st->block_align;
avctx->channel_layout = st->channel_layout;
avctx->bits_per_coded_sample = st->bits_per_coded_sample;
- // Required in FFmpeg 2.5.x / Libav 11, deprecated afterwards.
- avctx->stream_codec_tag = st->stream_codec_tag;
}
// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it.
diff --git a/common/av_log.c b/common/av_log.c
index e5c414f..05542cd 100644
--- a/common/av_log.c
+++ b/common/av_log.c
@@ -134,8 +134,9 @@ static void mp_msg_av_log_callback(void *ptr, int level, const char *fmt,
struct mp_log *log = get_av_log(ptr);
if (mp_msg_test(log, mp_level)) {
- if (log_print_prefix)
- mp_msg(log, mp_level, "%s: ", avc ? avc->item_name(ptr) : "?");
+ const char *prefix = avc ? avc->item_name(ptr) : NULL;
+ if (log_print_prefix && prefix)
+ mp_msg(log, mp_level, "%s: ", prefix);
log_print_prefix = fmt[strlen(fmt) - 1] == '\n';
mp_msg_va(log, mp_level, fmt, vl);
@@ -173,6 +174,7 @@ void uninit_libav(struct mpv_global *global)
{
pthread_mutex_lock(&log_lock);
if (log_mpv_instance == global) {
+ av_log_set_callback(av_log_default_callback);
log_mpv_instance = NULL;
talloc_free(log_root);
}
@@ -207,36 +209,17 @@ void print_libav_versions(struct mp_log *log, int v)
mp_msg(log, v, "%s library versions:\n", LIB_PREFIX);
- bool mismatch = false;
- bool broken = false;
for (int n = 0; n < MP_ARRAY_SIZE(libs); n++) {
const struct lib *l = &libs[n];
mp_msg(log, v, " %-15s %d.%d.%d", l->name, V(l->buildv));
- if (l->buildv != l->runv) {
+ if (l->buildv != l->runv)
mp_msg(log, v, " (runtime %d.%d.%d)", V(l->runv));
- mismatch = true;
- broken |= ((l->buildv & 255) >= 100) != ((l->runv & 255) >= 100);
- }
mp_msg(log, v, "\n");
}
- // This just won't work. It's 100% broken.
- if (broken) {
- mp_fatal(log, "mpv was compiled and linked against a mixture of Libav "
- "and FFmpeg versions. This won't work and will most likely "
- "crash at some point. Aborting.\n");
- abort();
- }
- // We don't "really" support mismatched libraries, but if you like to
- // suffer, you're free to enjoy the terrible aspects of dynamic linking.
- // In particular, we don't use all these crazy accessors ffmpeg wants us
- // to use in order to be ABI compatible after Libav merges - because that
- // would make our code incompatible to Libav. It's madness.
- if (mismatch) {
- mp_warn(log, "Warning: mpv was compiled against a different version of "
- "%s than the shared\nlibrary it is linked against. This can "
- "expose subtle ABI compatibility issues\nand can lead to "
- "misbehavior and crashes.\n", LIB_PREFIX);
- }
+
+#if HAVE_AV_VERSION_INFO
+ mp_msg(log, v, "%s version: %s\n", LIB_PREFIX, av_version_info());
+#endif
}
#undef V
diff --git a/common/codecs.c b/common/codecs.c
index f89ef0c..35d2709 100644
--- a/common/codecs.c
+++ b/common/codecs.c
@@ -130,6 +130,28 @@ struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all,
return list;
}
+// selection is a ","-separated list of decoders, all in the given family.
+struct mp_decoder_list *mp_select_decoder_list(struct mp_decoder_list *all,
+ const char *codec,
+ const char *family,
+ const char *selection)
+{
+ struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
+ bstr sel = bstr0(selection);
+ while (sel.len) {
+ bstr decoder;
+ bstr_split_tok(sel, ",", &decoder, &sel);
+ add_new(list, find_decoder(all, bstr0(family), decoder), codec);
+ }
+ return list;
+}
+
+void mp_append_decoders(struct mp_decoder_list *list, struct mp_decoder_list *a)
+{
+ for (int n = 0; n < a->num_entries; n++)
+ add_new(list, &a->entries[n], NULL);
+}
+
void mp_print_decoders(struct mp_log *log, int msgl, const char *header,
struct mp_decoder_list *list)
{
diff --git a/common/codecs.h b/common/codecs.h
index 105aab5..a262ed6 100644
--- a/common/codecs.h
+++ b/common/codecs.h
@@ -37,6 +37,13 @@ struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all,
const char *codec,
const char *selection);
+struct mp_decoder_list *mp_select_decoder_list(struct mp_decoder_list *all,
+ const char *codec,
+ const char *family,
+ const char *selection);
+
+void mp_append_decoders(struct mp_decoder_list *list, struct mp_decoder_list *a);
+
struct mp_log;
void mp_print_decoders(struct mp_log *log, int msgl, const char *header,
struct mp_decoder_list *list);
diff --git a/common/msg.c b/common/msg.c
index 3a441d0..6a7f699 100644
--- a/common/msg.c
+++ b/common/msg.c
@@ -52,7 +52,7 @@ struct mp_log_root {
bool module;
bool show_time;
bool termosd; // use terminal control codes for status line
- int blank_lines; // number of lines useable by status
+ int blank_lines; // number of lines usable by status
int status_lines; // number of current status lines
bool color;
int verbose;
@@ -106,16 +106,12 @@ static void update_loglevel(struct mp_log *log)
{
struct mp_log_root *root = log->root;
pthread_mutex_lock(&mp_msg_lock);
- log->level = -1;
- log->terminal_level = -1;
- if (log->root->use_terminal) {
- log->level = MSGL_STATUS + log->root->verbose; // default log level
- for (int n = 0; root->msg_levels && root->msg_levels[n * 2 + 0]; n++) {
- if (match_mod(log->verbose_prefix, root->msg_levels[n * 2 + 0]))
- log->level = mp_msg_find_level(root->msg_levels[n * 2 + 1]);
- }
- log->terminal_level = log->root->use_terminal ? log->level : -1;
+ log->level = MSGL_STATUS + log->root->verbose; // default log level
+ for (int n = 0; root->msg_levels && root->msg_levels[n * 2 + 0]; n++) {
+ if (match_mod(log->verbose_prefix, root->msg_levels[n * 2 + 0]))
+ log->level = mp_msg_find_level(root->msg_levels[n * 2 + 1]);
}
+ log->terminal_level = log->level;
for (int n = 0; n < log->root->num_buffers; n++)
log->level = MPMAX(log->level, log->root->buffers[n]->level);
if (log->root->log_file)
@@ -239,7 +235,7 @@ static void pretty_print_module(FILE* stream, const char *prefix, bool use_color
static bool test_terminal_level(struct mp_log *log, int lev)
{
- return lev <= log->terminal_level &&
+ return lev <= log->terminal_level && log->root->use_terminal &&
!(lev == MSGL_STATUS && terminal_in_background());
}
@@ -297,7 +293,10 @@ static void write_msg_to_buffers(struct mp_log *log, int lev, char *text)
struct mp_log_root *root = log->root;
for (int n = 0; n < root->num_buffers; n++) {
struct mp_log_buffer *buffer = root->buffers[n];
- if (lev <= buffer->level && lev != MSGL_STATUS) {
+ int buffer_level = buffer->level;
+ if (buffer_level == MP_LOG_BUFFER_MSGL_TERM)
+ buffer_level = log->terminal_level;
+ if (lev <= buffer_level && lev != MSGL_STATUS) {
// Assuming a single writer (serialized by msg lock)
int avail = mp_ring_available(buffer->ring) / sizeof(void *);
if (avail < 1)
diff --git a/common/msg_control.h b/common/msg_control.h
index c26a557..d8d9b2e 100644
--- a/common/msg_control.h
+++ b/common/msg_control.h
@@ -18,6 +18,9 @@ struct mp_log_buffer_entry {
char *text;
};
+// Use --msg-level option for log level of this log buffer
+#define MP_LOG_BUFFER_MSGL_TERM (MSGL_MAX + 1)
+
struct mp_log_buffer;
struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global,
int size, int level,
diff --git a/common/playlist.c b/common/playlist.c
index bb849f1..9fd087b 100644
--- a/common/playlist.c
+++ b/common/playlist.c
@@ -199,7 +199,7 @@ void playlist_add_base_path(struct playlist *pl, bstr base_path)
return;
for (struct playlist_entry *e = pl->first; e; e = e->next) {
if (!mp_is_url(bstr0(e->filename))) {
- char *new_file = mp_path_join(e, base_path, bstr0(e->filename));
+ char *new_file = mp_path_join_bstr(e, base_path, bstr0(e->filename));
talloc_free(e->filename);
e->filename = new_file;
}
@@ -281,6 +281,10 @@ struct playlist *playlist_parse_file(const char *file, struct mpv_global *global
if (d && d->playlist) {
ret = talloc_zero(NULL, struct playlist);
playlist_transfer_entries(ret, d->playlist);
+ if (d->filetype && strcmp(d->filetype, "hls") == 0) {
+ mp_warn(log, "This might be a HLS stream. For correct operation, "
+ "pass it to the player\ndirectly. Don't use --playlist.\n");
+ }
}
free_demuxer_and_stream(d);
diff --git a/common/playlist.h b/common/playlist.h
index 0e571a0..be9fd99 100644
--- a/common/playlist.h
+++ b/common/playlist.h
@@ -34,6 +34,8 @@ struct playlist_entry {
struct playlist_param *params;
int num_params;
+ char *title;
+
// 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
diff --git a/demux/codec_tags.c b/demux/codec_tags.c
index fd67933..c7b48f6 100644
--- a/demux/codec_tags.c
+++ b/demux/codec_tags.c
@@ -22,53 +22,10 @@
#include "stheader.h"
#include "common/av_common.h"
-struct mp_codec_tag {
- uint32_t tag;
- const char *codec;
-};
-
-static const struct mp_codec_tag mp_codec_tags[] = {
- // Made-up tags used by demux_mkv.c to map codecs.
- // (This is a leftover from MPlayer's codecs.conf mechanism.)
- {MKTAG('p', 'r', '0', '0'), "prores"},
- {MKTAG('H', 'E', 'V', 'C'), "hevc"},
- {MKTAG('R', 'V', '2', '0'), "rv20"},
- {MKTAG('R', 'V', '3', '0'), "rv30"},
- {MKTAG('R', 'V', '4', '0'), "rv40"},
- {MKTAG('R', 'V', '1', '0'), "rv10"},
- {MKTAG('R', 'V', '1', '3'), "rv10"},
- {MKTAG('E', 'A', 'C', '3'), "eac3"},
- {MKTAG('M', 'P', '4', 'A'), "aac"}, // also the QT tag
- {MKTAG('v', 'r', 'b', 's'), "vorbis"},
- {MKTAG('O', 'p', 'u', 's'), "opus"},
- {MKTAG('W', 'V', 'P', 'K'), "wavpack"},
- {MKTAG('T', 'R', 'H', 'D'), "truehd"},
- {MKTAG('f', 'L', 'a', 'C'), "flac"},
- {MKTAG('a', 'L', 'a', 'C'), "alac"}, // also the QT tag
- {MKTAG('2', '8', '_', '8'), "ra_288"},
- {MKTAG('a', 't', 'r', 'c'), "atrac3"},
- {MKTAG('c', 'o', 'o', 'k'), "cook"},
- {MKTAG('d', 'n', 'e', 't'), "ac3"},
- {MKTAG('s', 'i', 'p', 'r'), "sipr"},
- {MKTAG('T', 'T', 'A', '1'), "tta"},
- // Fringe codecs, occur in the wild, but not mapped in FFmpeg.
- {MKTAG('B', 'I', 'K', 'b'), "binkvideo"},
- {MKTAG('B', 'I', 'K', 'f'), "binkvideo"},
- {MKTAG('B', 'I', 'K', 'g'), "binkvideo"},
- {MKTAG('B', 'I', 'K', 'h'), "binkvideo"},
- {MKTAG('B', 'I', 'K', 'i'), "binkvideo"},
- {0}
-};
-
#define HAVE_QT_TAGS (LIBAVFORMAT_VERSION_MICRO >= 100)
static const char *lookup_tag(int type, uint32_t tag)
{
- for (int n = 0; mp_codec_tags[n].codec; n++) {
- if (mp_codec_tags[n].tag == tag)
- return mp_codec_tags[n].codec;
- }
-
const struct AVCodecTag *av_tags[3] = {0};
switch (type) {
case STREAM_VIDEO: {
@@ -115,11 +72,11 @@ static const char *map_audio_pcm_tag(uint32_t tag, int bits)
void mp_set_codec_from_tag(struct sh_stream *sh)
{
- sh->codec = lookup_tag(sh->type, sh->format);
+ sh->codec = lookup_tag(sh->type, sh->codec_tag);
if (sh->audio && sh->audio->bits_per_coded_sample) {
const char *codec =
- map_audio_pcm_tag(sh->format, sh->audio->bits_per_coded_sample);
+ map_audio_pcm_tag(sh->codec_tag, sh->audio->bits_per_coded_sample);
if (codec)
sh->codec = codec;
}
@@ -151,7 +108,7 @@ const char *mp_map_mimetype_to_video_codec(const char *mimetype)
{
if (mimetype) {
for (int n = 0; mimetype_to_codec[n][0]; n++) {
- if (strcmp(mimetype_to_codec[n][0], mimetype) == 0)
+ if (strcasecmp(mimetype_to_codec[n][0], mimetype) == 0)
return mimetype_to_codec[n][1];
}
}
diff --git a/demux/cue.c b/demux/cue.c
new file mode 100644
index 0000000..7f2ded2
--- /dev/null
+++ b/demux/cue.c
@@ -0,0 +1,206 @@
+/*
+ * 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 <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "talloc.h"
+
+#include "misc/bstr.h"
+#include "common/common.h"
+
+#include "cue.h"
+
+#define SECS_PER_CUE_FRAME (1.0/75.0)
+
+enum cue_command {
+ CUE_ERROR = -1, // not a valid CUE command, or an unknown extension
+ CUE_EMPTY, // line with whitespace only
+ CUE_UNUSED, // valid CUE command, but ignored by this code
+ CUE_FILE,
+ CUE_TRACK,
+ CUE_INDEX,
+ CUE_TITLE,
+};
+
+static const struct {
+ enum cue_command command;
+ const char *text;
+} cue_command_strings[] = {
+ { CUE_FILE, "FILE" },
+ { CUE_TRACK, "TRACK" },
+ { CUE_INDEX, "INDEX" },
+ { CUE_TITLE, "TITLE" },
+ { CUE_UNUSED, "CATALOG" },
+ { CUE_UNUSED, "CDTEXTFILE" },
+ { CUE_UNUSED, "FLAGS" },
+ { CUE_UNUSED, "ISRC" },
+ { CUE_UNUSED, "PERFORMER" },
+ { CUE_UNUSED, "POSTGAP" },
+ { CUE_UNUSED, "PREGAP" },
+ { CUE_UNUSED, "REM" },
+ { CUE_UNUSED, "SONGWRITER" },
+ { CUE_UNUSED, "MESSAGE" },
+ { -1 },
+};
+
+static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params)
+{
+ struct bstr line = bstr_strip_linebreaks(bstr_getline(*data, data));
+ line = bstr_lstrip(line);
+ if (line.len == 0)
+ return CUE_EMPTY;
+ for (int n = 0; cue_command_strings[n].command != -1; n++) {
+ struct bstr name = bstr0(cue_command_strings[n].text);
+ if (bstr_startswith(line, name)) {
+ struct bstr rest = bstr_cut(line, name.len);
+ if (rest.len && !strchr(WHITESPACE, rest.start[0]))
+ continue;
+ if (out_params)
+ *out_params = rest;
+ return cue_command_strings[n].command;
+ }
+ }
+ return CUE_ERROR;
+}
+
+static bool eat_char(struct bstr *data, char ch)
+{
+ if (data->len && data->start[0] == ch) {
+ *data = bstr_cut(*data, 1);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static char *read_quoted(void *talloc_ctx, struct bstr *data)
+{
+ *data = bstr_lstrip(*data);
+ if (!eat_char(data, '"'))
+ return NULL;
+ int end = bstrchr(*data, '"');
+ if (end < 0)
+ return NULL;
+ struct bstr res = bstr_splice(*data, 0, end);
+ *data = bstr_cut(*data, end + 1);
+ return bstrto0(talloc_ctx, res);
+}
+
+// Read a 2 digit unsigned decimal integer.
+// Return -1 on failure.
+static int read_int_2(struct bstr *data)
+{
+ *data = bstr_lstrip(*data);
+ if (data->len && data->start[0] == '-')
+ return -1;
+ struct bstr s = *data;
+ int res = (int)bstrtoll(s, &s, 10);
+ if (data->len == s.len || data->len - s.len > 2)
+ return -1;
+ *data = s;
+ return res;
+}
+
+static double read_time(struct bstr *data)
+{
+ struct bstr s = *data;
+ bool ok = true;
+ double t1 = read_int_2(&s);
+ ok = eat_char(&s, ':') && ok;
+ double t2 = read_int_2(&s);
+ ok = eat_char(&s, ':') && ok;
+ double t3 = read_int_2(&s);
+ ok = ok && t1 >= 0 && t2 >= 0 && t3 >= 0;
+ return ok ? t1 * 60.0 + t2 + t3 * SECS_PER_CUE_FRAME : 0;
+}
+
+static struct bstr skip_utf8_bom(struct bstr data)
+{
+ return bstr_startswith0(data, "\xEF\xBB\xBF") ? bstr_cut(data, 3) : data;
+}
+
+// Check if the text in data is most likely CUE data. This is used by the
+// demuxer code to check the file type.
+// data is the start of the probed file, possibly cut off at a random point.
+bool mp_probe_cue(struct bstr data)
+{
+ bool valid = false;
+ data = skip_utf8_bom(data);
+ for (;;) {
+ enum cue_command cmd = read_cmd(&data, NULL);
+ // End reached. Since the line was most likely cut off, don't use the
+ // result of the last parsing call.
+ if (data.len == 0)
+ break;
+ if (cmd == CUE_ERROR)
+ return false;
+ if (cmd != CUE_EMPTY)
+ valid = true;
+ }
+ return valid;
+}
+
+struct cue_file *mp_parse_cue(struct bstr data)
+{
+ struct cue_file *f = talloc_zero(NULL, struct cue_file);
+
+ data = skip_utf8_bom(data);
+
+ char *filename = NULL;
+ // Global metadata, and copied into new tracks.
+ struct cue_track proto_track = {0};
+ struct cue_track *cur_track = &proto_track;
+
+ while (data.len) {
+ struct bstr param;
+ switch (read_cmd(&data, &param)) {
+ case CUE_ERROR:
+ talloc_free(f);
+ return NULL;
+ case CUE_TRACK: {
+ MP_TARRAY_GROW(f, f->tracks, f->num_tracks);
+ f->num_tracks += 1;
+ cur_track = &f->tracks[f->num_tracks - 1];
+ *cur_track = proto_track;
+ break;
+ }
+ case CUE_TITLE:
+ cur_track->title = read_quoted(f, &param);
+ break;
+ case CUE_INDEX: {
+ int type = read_int_2(&param);
+ double time = read_time(&param);
+ if (type == 1) {
+ cur_track->start = time;
+ cur_track->filename = filename;
+ } else if (type == 0) {
+ cur_track->pregap_start = time;
+ }
+ break;
+ }
+ case CUE_FILE:
+ // NOTE: FILE comes before TRACK, so don't use cur_track->filename
+ filename = read_quoted(f, &param);
+ break;
+ }
+ }
+
+ return f;
+}
diff --git a/demux/cue.h b/demux/cue.h
new file mode 100644
index 0000000..5645dd0
--- /dev/null
+++ b/demux/cue.h
@@ -0,0 +1,41 @@
+/*
+ * 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/>.
+ */
+
+#ifndef MP_CUE_H_
+#define MP_CUE_H_
+
+#include <stdbool.h>
+
+#include "misc/bstr.h"
+
+struct cue_file {
+ struct cue_track *tracks;
+ int num_tracks;
+};
+
+struct cue_track {
+ double pregap_start; // corresponds to INDEX 00
+ double start; // corresponds to INDEX 01
+ char *filename;
+ int source;
+ char *title;
+};
+
+bool mp_probe_cue(struct bstr data);
+struct cue_file *mp_parse_cue(struct bstr data);
+
+#endif
diff --git a/demux/demux.c b/demux/demux.c
index bb36705..d0e44ab 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -37,8 +37,7 @@
#include "stream/stream.h"
#include "demux.h"
#include "stheader.h"
-
-#include "audio/format.h"
+#include "cue.h"
// Demuxer list
extern const struct demuxer_desc demuxer_desc_edl;
@@ -54,6 +53,7 @@ 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;
+extern const demuxer_desc_t demuxer_desc_libarchive;
/* Please do not add any new demuxers here. If you want to implement a new
* demuxer, add it to libavformat, except for wrappers around external
@@ -72,6 +72,9 @@ const demuxer_desc_t *const demuxer_list[] = {
&demuxer_desc_libass,
#endif
&demuxer_desc_matroska,
+#if HAVE_LIBARCHIVE
+ &demuxer_desc_libarchive,
+#endif
&demuxer_desc_rar,
&demuxer_desc_lavf,
&demuxer_desc_mf,
@@ -113,8 +116,8 @@ struct demux_internal {
bool idle;
bool autoselect;
double min_secs;
- int min_packs;
- int min_bytes;
+ int max_packs;
+ int max_bytes;
bool tracks_switched; // thread needs to inform demuxer of this
@@ -128,7 +131,6 @@ struct demux_internal {
// Cached state.
bool force_cache_update;
double time_length;
- struct mp_nav_event *nav_event;
struct mp_tags *stream_metadata;
int64_t stream_size;
int64_t stream_cache_size;
@@ -244,7 +246,6 @@ void free_demuxer(demuxer_t *demuxer)
ds_flush(demuxer->streams[n]->ds);
pthread_mutex_destroy(&in->lock);
pthread_cond_destroy(&in->wakeup);
- talloc_free(in->nav_event);
talloc_free(demuxer);
}
@@ -390,12 +391,13 @@ static bool read_packet(struct demux_internal *in)
read_more |= ds->active && !ds->head;
packs += ds->packs;
bytes += ds->bytes;
- if (ds->active && ds->last_ts != MP_NOPTS_VALUE && in->min_secs > 0)
+ if (ds->active && ds->last_ts != MP_NOPTS_VALUE && in->min_secs > 0 &&
+ ds->last_ts >= ds->base_ts)
read_more |= ds->last_ts - ds->base_ts < in->min_secs;
}
MP_DBG(in, "packets=%zd, bytes=%zd, active=%d, more=%d\n",
packs, bytes, active, read_more);
- if (packs >= MAX_PACKS || bytes >= MAX_PACK_BYTES) {
+ if (packs >= in->max_packs || bytes >= in->max_bytes) {
if (!in->warned_queue_overflow) {
in->warned_queue_overflow = true;
MP_ERR(in, "Too many packets in the demuxer packet queues:\n");
@@ -414,8 +416,6 @@ static bool read_packet(struct demux_internal *in)
pthread_cond_signal(&in->wakeup);
return false;
}
- if (packs < in->min_packs && bytes < in->min_bytes)
- read_more |= active;
if (!read_more)
return false;
@@ -527,9 +527,13 @@ static void execute_seek(struct demux_internal *in)
pthread_mutex_unlock(&in->lock);
+ MP_VERBOSE(in, "execute seek (to %f flags %d)\n", pts, flags);
+
if (in->d_thread->desc->seek)
in->d_thread->desc->seek(in->d_thread, pts, flags);
+ MP_VERBOSE(in, "seek done\n");
+
pthread_mutex_lock(&in->lock);
}
@@ -785,27 +789,37 @@ static int decode_peak(demuxer_t *demuxer, const char *tag, float *out)
return 0;
}
+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) {
+ 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));
+ }
+ }
+}
+
static void demux_export_replaygain(demuxer_t *demuxer)
{
- float tg, tp, ag, ap;
+ struct replaygain_data rg = {0};
- if (!decode_gain(demuxer, "REPLAYGAIN_TRACK_GAIN", &tg) &&
- !decode_peak(demuxer, "REPLAYGAIN_TRACK_PEAK", &tp) &&
- !decode_gain(demuxer, "REPLAYGAIN_ALBUM_GAIN", &ag) &&
- !decode_peak(demuxer, "REPLAYGAIN_ALBUM_PEAK", &ap))
+ if (!decode_gain(demuxer, "REPLAYGAIN_TRACK_GAIN", &rg.track_gain) &&
+ !decode_peak(demuxer, "REPLAYGAIN_TRACK_PEAK", &rg.track_peak) &&
+ !decode_gain(demuxer, "REPLAYGAIN_ALBUM_GAIN", &rg.album_gain) &&
+ !decode_peak(demuxer, "REPLAYGAIN_ALBUM_PEAK", &rg.album_peak))
{
- struct replaygain_data *rgain = talloc_ptrtype(demuxer, rgain);
-
- rgain->track_gain = tg;
- rgain->track_peak = tp;
- rgain->album_gain = ag;
- rgain->album_peak = ap;
+ apply_replaygain(demuxer, &rg);
+ }
- for (int n = 0; n < demuxer->num_streams; n++) {
- struct sh_stream *sh = demuxer->streams[n];
- if (sh->audio && !sh->audio->replaygain_data)
- sh->audio->replaygain_data = rgain;
- }
+ if (!decode_gain(demuxer, "REPLAYGAIN_GAIN", &rg.track_gain) &&
+ !decode_peak(demuxer, "REPLAYGAIN_PEAK", &rg.track_peak))
+ {
+ rg.album_gain = rg.track_gain;
+ rg.album_peak = rg.track_peak;
+ apply_replaygain(demuxer, &rg);
}
}
@@ -902,6 +916,21 @@ static void demux_init_cache(struct demuxer *demuxer)
in->stream_base_filename = talloc_steal(demuxer, base);
}
+static void demux_init_cuesheet(struct demuxer *demuxer)
+{
+ char *cue = mp_tags_get_str(demuxer->metadata, "cuesheet");
+ 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];
+ demuxer_add_chapter(demuxer, t->title, t->start, -1);
+ }
+ }
+ talloc_free(f);
+ }
+}
+
static struct demuxer *open_given_type(struct mpv_global *global,
struct mp_log *log,
const struct demuxer_desc *desc,
@@ -934,8 +963,8 @@ static struct demuxer *open_given_type(struct mpv_global *global,
.d_buffer = talloc(demuxer, struct demuxer),
.d_user = demuxer,
.min_secs = demuxer->opts->demuxer_min_secs,
- .min_packs = demuxer->opts->demuxer_min_packs,
- .min_bytes = demuxer->opts->demuxer_min_bytes,
+ .max_packs = demuxer->opts->demuxer_max_packs,
+ .max_bytes = demuxer->opts->demuxer_max_bytes,
};
pthread_mutex_init(&in->lock, NULL);
pthread_cond_init(&in->wakeup, NULL);
@@ -971,12 +1000,12 @@ static struct demuxer *open_given_type(struct mpv_global *global,
mp_verbose(log, "Detected file format: %s\n", desc->desc);
if (!in->d_thread->seekable)
mp_verbose(log, "Stream is not seekable.\n");
- // Pretend we can seek if we can't seek, but there's a cache.
- if (!in->d_thread->seekable && stream->uncached_stream) {
- mp_verbose(log, "Enabling seeking because stream cache is active.\n");
+ if (!in->d_thread->seekable && demuxer->opts->force_seekable) {
+ mp_warn(log, "Not seekable, but enabling seeking on user request.\n");
in->d_thread->seekable = true;
in->d_thread->partially_seekable = true;
}
+ demux_init_cuesheet(in->d_thread);
demux_init_cache(demuxer);
demux_changed(in->d_thread, DEMUX_EVENT_ALL);
demux_update(demuxer);
@@ -1050,14 +1079,22 @@ struct demuxer *demux_open_url(const char *url,
struct mpv_global *global)
{
struct MPOpts *opts = global->opts;
- struct stream *s = stream_create(url, STREAM_READ, cancel, global);
+ struct demuxer_params dummy = {0};
+ if (!params)
+ params = &dummy;
+ struct stream *s = stream_create(url, STREAM_READ | params->stream_flags,
+ cancel, global);
if (!s)
return NULL;
- if (!(params && params->disable_cache))
+ if (params->allow_capture)
+ stream_set_capture_file(s, opts->stream_capture);
+ if (!params->disable_cache)
stream_enable_cache(&s, &opts->stream_cache);
struct demuxer *d = demux_open(s, params, global);
- if (!d)
+ if (!d) {
+ params->demuxer_failed = true;
free_stream(s);
+ }
return d;
}
@@ -1108,6 +1145,9 @@ int demux_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
pthread_mutex_lock(&in->lock);
+ MP_VERBOSE(in, "queuing seek to %f%s\n", rel_seek_secs,
+ in->seeking ? " (cascade)" : "");
+
flush_locked(demuxer);
in->seeking = true;
in->seek_flags = flags;
@@ -1194,21 +1234,19 @@ bool demux_stream_is_selected(struct sh_stream *stream)
return r;
}
-int demuxer_add_attachment(demuxer_t *demuxer, struct bstr name,
- struct bstr type, struct bstr data)
+int demuxer_add_attachment(demuxer_t *demuxer, char *name, char *type,
+ void *data, size_t data_size)
{
if (!(demuxer->num_attachments % 32))
demuxer->attachments = talloc_realloc(demuxer, demuxer->attachments,
struct demux_attachment,
demuxer->num_attachments + 32);
- struct demux_attachment *att =
- demuxer->attachments + demuxer->num_attachments;
- att->name = talloc_strndup(demuxer->attachments, name.start, name.len);
- att->type = talloc_strndup(demuxer->attachments, type.start, type.len);
- att->data = talloc_size(demuxer->attachments, data.len);
- memcpy(att->data, data.start, data.len);
- att->data_size = data.len;
+ struct demux_attachment *att = &demuxer->attachments[demuxer->num_attachments];
+ att->name = talloc_strdup(demuxer->attachments, name);
+ att->type = talloc_strdup(demuxer->attachments, type);
+ att->data = talloc_memdup(demuxer->attachments, data, data_size);
+ att->data_size = data_size;
return demuxer->num_attachments++;
}
@@ -1231,17 +1269,16 @@ static void demuxer_sort_chapters(demuxer_t *demuxer)
sizeof(struct demux_chapter), chapter_compare);
}
-int demuxer_add_chapter(demuxer_t *demuxer, struct bstr name,
+int demuxer_add_chapter(demuxer_t *demuxer, char *name,
double pts, uint64_t demuxer_id)
{
struct demux_chapter new = {
.original_index = demuxer->num_chapters,
.pts = pts,
- .name = name.len ? bstrdup0(demuxer, name) : NULL,
.metadata = talloc_zero(demuxer, struct mp_tags),
.demuxer_id = demuxer_id,
};
- mp_tags_set_bstr(new.metadata, bstr0("TITLE"), name);
+ mp_tags_set_str(new.metadata, "TITLE", name);
MP_TARRAY_APPEND(demuxer, demuxer->chapters, demuxer->num_chapters, new);
return demuxer->num_chapters - 1;
}
@@ -1263,25 +1300,17 @@ static void update_cache(struct demux_internal *in)
// Don't lock while querying the stream.
double time_length = -1;
struct mp_tags *stream_metadata = NULL;
- int64_t stream_size = -1;
int64_t stream_cache_size = -1;
int64_t stream_cache_fill = -1;
int stream_cache_idle = -1;
- struct mp_nav_event *nav_event = NULL;
-
- pthread_mutex_lock(&in->lock);
- bool need_nav_event = !in->nav_event;;
- pthread_mutex_unlock(&in->lock);
if (demuxer->desc->control) {
demuxer->desc->control(demuxer, DEMUXER_CTRL_GET_TIME_LENGTH,
&time_length);
- if (need_nav_event)
- demuxer->desc->control(demuxer, DEMUXER_CTRL_GET_NAV_EVENT, &nav_event);
}
+ int64_t stream_size = stream_get_size(stream);
stream_control(stream, STREAM_CTRL_GET_METADATA, &stream_metadata);
- stream_control(stream, STREAM_CTRL_GET_SIZE, &stream_size);
stream_control(stream, STREAM_CTRL_GET_CACHE_SIZE, &stream_cache_size);
stream_control(stream, STREAM_CTRL_GET_CACHE_FILL, &stream_cache_fill);
stream_control(stream, STREAM_CTRL_GET_CACHE_IDLE, &stream_cache_idle);
@@ -1297,7 +1326,6 @@ static void update_cache(struct demux_internal *in)
in->stream_metadata = talloc_steal(in, stream_metadata);
in->d_buffer->events |= DEMUX_EVENT_METADATA;
}
- in->nav_event = nav_event ? nav_event : in->nav_event;
pthread_mutex_unlock(&in->lock);
}
@@ -1396,13 +1424,6 @@ static int cached_demux_control(struct demux_internal *in, int cmd, void *arg)
r->ts_duration = 0;
return DEMUXER_CTRL_OK;
}
- case DEMUXER_CTRL_GET_NAV_EVENT:
- if (!in->nav_event)
- return DEMUXER_CTRL_NOTIMPL;
- *(struct mp_nav_event **)arg = in->nav_event;
- in->nav_event = NULL;
- return DEMUXER_CTRL_OK;
-
}
return DEMUXER_CTRL_DONTKNOW;
}
@@ -1492,9 +1513,7 @@ struct demux_chapter *demux_copy_chapter_data(struct demux_chapter *c, int num)
struct demux_chapter *new = talloc_array(NULL, struct demux_chapter, num);
for (int n = 0; n < num; n++) {
new[n] = c[n];
- new[n].name = talloc_strdup(new, new[n].name);
- if (new[n].metadata)
- new[n].metadata = mp_tags_dup(new, new[n].metadata);
+ new[n].metadata = mp_tags_dup(new, new[n].metadata);
}
return new;
}
diff --git a/demux/demux.h b/demux/demux.h
index f3ed97c..7e4dcea 100644
--- a/demux/demux.h
+++ b/demux/demux.h
@@ -30,11 +30,6 @@
#include "packet.h"
#include "stheader.h"
-// Maximum total size of packets queued - if larger, no new packets are read,
-// and the demuxer pretends EOF was reached.
-#define MAX_PACKS 16000
-#define MAX_PACK_BYTES (400 * 1024 * 1024)
-
// DEMUXER control commands/answers
#define DEMUXER_CTRL_NOTIMPL -1
#define DEMUXER_CTRL_DONTKNOW 0
@@ -47,7 +42,6 @@ enum demux_ctrl {
DEMUXER_CTRL_IDENTIFY_PROGRAM,
DEMUXER_CTRL_STREAM_CTRL,
DEMUXER_CTRL_GET_READER_STATE,
- DEMUXER_CTRL_GET_NAV_EVENT,
DEMUXER_CTRL_GET_BITRATE_STATS, // double[STREAM_TYPE_COUNT]
};
@@ -119,7 +113,6 @@ typedef struct demux_chapter
{
int original_index;
double pts;
- char *name;
struct mp_tags *metadata;
uint64_t demuxer_id; // for mapping to internal demuxer data structures
} demux_chapter_t;
@@ -170,7 +163,12 @@ struct demuxer_params {
int matroska_wanted_segment;
bool *matroska_was_valid;
bool expect_subtitle;
- bool disable_cache; // demux_open_url() only
+ // -- demux_open_url() only
+ int stream_flags;
+ bool allow_capture;
+ bool disable_cache;
+ // result
+ bool demuxer_failed;
};
typedef struct demuxer {
@@ -284,9 +282,9 @@ void demux_set_stream_autoselect(struct demuxer *demuxer, bool autoselect);
void demuxer_help(struct mp_log *log);
-int demuxer_add_attachment(struct demuxer *demuxer, struct bstr name,
- struct bstr type, struct bstr data);
-int demuxer_add_chapter(demuxer_t *demuxer, struct bstr name,
+int demuxer_add_attachment(struct demuxer *demuxer, char *name,
+ char *type, void *data, size_t data_size);
+int demuxer_add_chapter(demuxer_t *demuxer, char *name,
double pts, uint64_t demuxer_id);
double demuxer_get_time_length(struct demuxer *demuxer);
diff --git a/demux/demux_cue.c b/demux/demux_cue.c
index 93518c9..ed3d3d8 100644
--- a/demux/demux_cue.c
+++ b/demux/demux_cue.c
@@ -33,149 +33,14 @@
#include "stream/stream.h"
#include "timeline.h"
-#define PROBE_SIZE 512
-#define SECS_PER_CUE_FRAME (1.0/75.0)
-
-enum cue_command {
- CUE_ERROR = -1, // not a valid CUE command, or an unknown extension
- CUE_EMPTY, // line with whitespace only
- CUE_UNUSED, // valid CUE command, but ignored by this code
- CUE_FILE,
- CUE_TRACK,
- CUE_INDEX,
- CUE_TITLE,
-};
-
-static const struct {
- enum cue_command command;
- const char *text;
-} cue_command_strings[] = {
- { CUE_FILE, "FILE" },
- { CUE_TRACK, "TRACK" },
- { CUE_INDEX, "INDEX" },
- { CUE_TITLE, "TITLE" },
- { CUE_UNUSED, "CATALOG" },
- { CUE_UNUSED, "CDTEXTFILE" },
- { CUE_UNUSED, "FLAGS" },
- { CUE_UNUSED, "ISRC" },
- { CUE_UNUSED, "PERFORMER" },
- { CUE_UNUSED, "POSTGAP" },
- { CUE_UNUSED, "PREGAP" },
- { CUE_UNUSED, "REM" },
- { CUE_UNUSED, "SONGWRITER" },
- { CUE_UNUSED, "MESSAGE" },
- { -1 },
-};
+#include "cue.h"
-struct cue_track {
- double pregap_start; // corresponds to INDEX 00
- double start; // corresponds to INDEX 01
- struct bstr filename;
- int source;
- struct bstr title;
-};
+#define PROBE_SIZE 512
struct priv {
bstr data;
};
-static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params)
-{
- struct bstr line = bstr_strip_linebreaks(bstr_getline(*data, data));
- line = bstr_lstrip(line);
- if (line.len == 0)
- return CUE_EMPTY;
- for (int n = 0; cue_command_strings[n].command != -1; n++) {
- struct bstr name = bstr0(cue_command_strings[n].text);
- if (bstr_startswith(line, name)) {
- struct bstr rest = bstr_cut(line, name.len);
- if (rest.len && !strchr(WHITESPACE, rest.start[0]))
- continue;
- if (out_params)
- *out_params = rest;
- return cue_command_strings[n].command;
- }
- }
- return CUE_ERROR;
-}
-
-static bool eat_char(struct bstr *data, char ch)
-{
- if (data->len && data->start[0] == ch) {
- *data = bstr_cut(*data, 1);
- return true;
- } else {
- return false;
- }
-}
-
-static struct bstr read_quoted(struct bstr *data)
-{
- *data = bstr_lstrip(*data);
- if (!eat_char(data, '"'))
- return (struct bstr) {0};
- int end = bstrchr(*data, '"');
- if (end < 0)
- return (struct bstr) {0};
- struct bstr res = bstr_splice(*data, 0, end);
- *data = bstr_cut(*data, end + 1);
- return res;
-}
-
-// Read a 2 digit unsigned decimal integer.
-// Return -1 on failure.
-static int read_int_2(struct bstr *data)
-{
- *data = bstr_lstrip(*data);
- if (data->len && data->start[0] == '-')
- return -1;
- struct bstr s = *data;
- int res = (int)bstrtoll(s, &s, 10);
- if (data->len == s.len || data->len - s.len > 2)
- return -1;
- *data = s;
- return res;
-}
-
-static double read_time(struct bstr *data)
-{
- struct bstr s = *data;
- bool ok = true;
- double t1 = read_int_2(&s);
- ok = eat_char(&s, ':') && ok;
- double t2 = read_int_2(&s);
- ok = eat_char(&s, ':') && ok;
- double t3 = read_int_2(&s);
- ok = ok && t1 >= 0 && t2 >= 0 && t3 >= 0;
- return ok ? t1 * 60.0 + t2 + t3 * SECS_PER_CUE_FRAME : 0;
-}
-
-static struct bstr skip_utf8_bom(struct bstr data)
-{
- return bstr_startswith0(data, "\xEF\xBB\xBF") ? bstr_cut(data, 3) : data;
-}
-
-// Check if the text in data is most likely CUE data. This is used by the
-// demuxer code to check the file type.
-// data is the start of the probed file, possibly cut off at a random point.
-static bool mp_probe_cue(struct bstr data)
-{
- bool valid = false;
- data = skip_utf8_bom(data);
- for (;;) {
- enum cue_command cmd = read_cmd(&data, NULL);
- // End reached. Since the line was most likely cut off, don't use the
- // result of the last parsing call.
- if (data.len == 0)
- break;
- if (cmd == CUE_ERROR)
- return false;
- if (cmd != CUE_EMPTY)
- valid = true;
- }
- return valid;
-}
-
static void add_source(struct timeline *tl, struct demuxer *d)
{
MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d);
@@ -191,10 +56,7 @@ static bool try_open(struct timeline *tl, char *filename)
|| bstrcasecmp(bstr0(tl->demuxer->filename), bfilename) == 0)
return false;
- struct stream *s = stream_create(filename, STREAM_READ, tl->cancel, tl->global);
- if (!s)
- return false;
- struct demuxer *d = demux_open(s, NULL, tl->global);
+ struct demuxer *d = demux_open_url(filename, NULL, tl->cancel, tl->global);
// Since .bin files are raw PCM data with no headers, we have to explicitly
// open them. Also, try to avoid to open files that are most likely not .bin
// files, as that would only play noise. Checking the file extension is
@@ -204,29 +66,28 @@ static bool try_open(struct timeline *tl, char *filename)
if (!d && bstr_case_endswith(bfilename, bstr0(".bin"))) {
MP_WARN(tl, "CUE: Opening as BIN file!\n");
struct demuxer_params p = {.force_format = "rawaudio"};
- d = demux_open(s, &p, tl->global);
+ d = demux_open_url(filename, &p, tl->cancel, tl->global);
}
if (d) {
add_source(tl, d);
return true;
}
MP_ERR(tl, "Could not open source '%s'!\n", filename);
- free_stream(s);
return false;
}
-static bool open_source(struct timeline *tl, struct bstr filename)
+static bool open_source(struct timeline *tl, char *filename)
{
void *ctx = talloc_new(NULL);
bool res = false;
struct bstr dirname = mp_dirname(tl->demuxer->filename);
- struct bstr base_filename = bstr0(mp_basename(bstrdup0(ctx, filename)));
+ struct bstr base_filename = bstr0(mp_basename(filename));
if (!base_filename.len) {
MP_WARN(tl, "CUE: Invalid audio filename in .cue file!\n");
} else {
- char *fullname = mp_path_join(ctx, dirname, base_filename);
+ char *fullname = mp_path_join_bstr(ctx, dirname, base_filename);
if (try_open(tl, fullname)) {
res = true;
goto out;
@@ -252,7 +113,7 @@ static bool open_source(struct timeline *tl, struct bstr filename)
MP_WARN(tl, "CUE: No useful audio filename "
"in .cue file found, trying with '%s' instead!\n",
dename0);
- if (try_open(tl, mp_path_join(ctx, dirname, dename))) {
+ if (try_open(tl, mp_path_join_bstr(ctx, dirname, dename))) {
res = true;
break;
}
@@ -289,50 +150,15 @@ static void build_timeline(struct timeline *tl)
add_source(tl, tl->demuxer);
- struct bstr data = p->data;
- data = skip_utf8_bom(data);
-
- struct cue_track *tracks = NULL;
- size_t track_count = 0;
-
- struct bstr filename = {0};
- // Global metadata, and copied into new tracks.
- struct cue_track proto_track = {0};
- struct cue_track *cur_track = &proto_track;
-
- while (data.len) {
- struct bstr param;
- switch (read_cmd(&data, &param)) {
- case CUE_ERROR:
- MP_ERR(tl, "CUE: error parsing input file!\n");
- goto out;
- case CUE_TRACK: {
- track_count++;
- tracks = talloc_realloc(ctx, tracks, struct cue_track, track_count);
- cur_track = &tracks[track_count - 1];
- *cur_track = proto_track;
- break;
- }
- case CUE_TITLE:
- cur_track->title = read_quoted(&param);
- break;
- case CUE_INDEX: {
- int type = read_int_2(&param);
- double time = read_time(&param);
- if (type == 1) {
- cur_track->start = time;
- cur_track->filename = filename;
- } else if (type == 0) {
- cur_track->pregap_start = time;
- }
- break;
- }
- case CUE_FILE:
- // NOTE: FILE comes before TRACK, so don't use cur_track->filename
- filename = read_quoted(&param);
- break;
- }
+ struct cue_file *f = mp_parse_cue(p->data);
+ if (!f) {
+ MP_ERR(tl, "CUE: error parsing input file!\n");
+ goto out;
}
+ talloc_steal(ctx, f);
+
+ struct cue_track *tracks = f->tracks;
+ size_t track_count = f->num_tracks;
if (track_count == 0) {
MP_ERR(tl, "CUE: no tracks found!\n");
@@ -343,21 +169,21 @@ static void build_timeline(struct timeline *tl)
// CUE files usually use either separate files for every single track, or
// only one file for all tracks.
- struct bstr *files = 0;
+ char **files = 0;
size_t file_count = 0;
for (size_t n = 0; n < track_count; n++) {
struct cue_track *track = &tracks[n];
track->source = -1;
for (size_t file = 0; file < file_count; file++) {
- if (bstrcmp(files[file], track->filename) == 0) {
+ if (strcmp(files[file], track->filename) == 0) {
track->source = file;
break;
}
}
if (track->source == -1) {
file_count++;
- files = talloc_realloc(ctx, files, struct bstr, file_count);
+ files = talloc_realloc(ctx, files, char *, file_count);
files[file_count - 1] = track->filename;
track->source = file_count - 1;
}
@@ -398,9 +224,10 @@ static void build_timeline(struct timeline *tl)
};
chapters[i] = (struct demux_chapter) {
.pts = timeline[i].start,
- // might want to include other metadata here
- .name = bstrdup0(chapters, tracks[i].title),
+ .metadata = talloc_zero(tl, struct mp_tags),
};
+ // might want to include other metadata here
+ mp_tags_set_str(chapters[i].metadata, "title", tracks[i].title);
starttime += duration;
}
diff --git a/demux/demux_disc.c b/demux/demux_disc.c
index 3cbd01a..a816a74 100644
--- a/demux/demux_disc.c
+++ b/demux/demux_disc.c
@@ -112,8 +112,8 @@ static void add_dvd_streams(demuxer_t *demuxer)
}
s = talloc_asprintf_append(s, "\n");
- sh->sub->extradata = s;
- sh->sub->extradata_len = strlen(s);
+ sh->extradata = s;
+ sh->extradata_size = strlen(s);
}
}
}
@@ -141,7 +141,7 @@ static void add_streams(demuxer_t *demuxer)
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->format = src->format;
+ sh->codec_tag = src->codec_tag;
sh->lav_headers = src->lav_headers;
sh->demuxer_id = src->demuxer_id;
if (src->video) {
@@ -282,7 +282,7 @@ static void add_stream_chapters(struct demuxer *demuxer)
double p = n;
if (stream_control(demuxer->stream, STREAM_CTRL_GET_CHAPTER_TIME, &p) < 1)
continue;
- demuxer_add_chapter(demuxer, bstr0(""), p, 0);
+ demuxer_add_chapter(demuxer, "", p, 0);
}
}
@@ -301,7 +301,7 @@ static int d_open(demuxer_t *demuxer, enum demux_check check)
char *t = NULL;
stream_control(demuxer->stream, STREAM_CTRL_GET_DISC_NAME, &t);
if (t) {
- mp_tags_set_bstr(demuxer->metadata, bstr0("TITLE"), bstr0(t));
+ mp_tags_set_str(demuxer->metadata, "TITLE", t);
talloc_free(t);
}
@@ -361,9 +361,6 @@ static int d_control(demuxer_t *demuxer, int cmd, void *arg)
case DEMUXER_CTRL_SWITCHED_TRACKS:
reselect_streams(demuxer);
return DEMUXER_CTRL_OK;
- case DEMUXER_CTRL_GET_NAV_EVENT:
- return stream_control(demuxer->stream, STREAM_CTRL_GET_NAV_EVENT, arg)
- == STREAM_OK ? DEMUXER_CTRL_OK : DEMUXER_CTRL_DONTKNOW;
}
return demux_control(p->slave, cmd, arg);
}
diff --git a/demux/demux_edl.c b/demux/demux_edl.c
index 3bda4f4..9ba0307 100644
--- a/demux/demux_edl.c
+++ b/demux/demux_edl.c
@@ -25,10 +25,10 @@
#include "talloc.h"
-#include "player/core.h"
+#include "demux.h"
+#include "timeline.h"
#include "common/msg.h"
#include "common/global.h"
-#include "demux/demux.h"
#include "options/path.h"
#include "misc/bstr.h"
#include "common/common.h"
@@ -167,7 +167,7 @@ static void copy_chapters(struct demux_chapter **chapters, int *num_chapters,
if (time >= start && time <= start + len) {
struct demux_chapter ch = {
.pts = dest_offset + time - start,
- .name = talloc_strdup(*chapters, src->chapters[n].name),
+ .metadata = mp_tags_dup(*chapters, src->chapters[n].metadata),
};
MP_TARRAY_APPEND(NULL, *chapters, *num_chapters, ch);
}
@@ -238,8 +238,9 @@ static void build_timeline(struct timeline *tl, struct tl_parts *parts)
// Add a chapter between each file.
struct demux_chapter ch = {
.pts = starttime,
- .name = talloc_strdup(tl, part->filename),
+ .metadata = talloc_zero(tl, struct mp_tags),
};
+ mp_tags_set_str(ch.metadata, "title", part->filename);
MP_TARRAY_APPEND(tl, tl->chapters, tl->num_chapters, ch);
// Also copy the source file's chapters for the relevant parts
@@ -272,7 +273,7 @@ static void fix_filenames(struct tl_parts *parts, char *source_path)
for (int n = 0; n < parts->num_parts; n++) {
struct tl_part *part = &parts->parts[n];
char *filename = mp_basename(part->filename); // plain filename only
- part->filename = mp_path_join(parts, dirname, bstr0(filename));
+ part->filename = mp_path_join_bstr(parts, dirname, bstr0(filename));
}
}
diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c
index c148695..8978ebf 100644
--- a/demux/demux_lavf.c
+++ b/demux/demux_lavf.c
@@ -45,6 +45,7 @@
#include "demux.h"
#include "stheader.h"
#include "options/m_option.h"
+#include "options/path.h"
#define INITIAL_PROBE_SIZE STREAM_BUFFER_SIZE
@@ -203,8 +204,8 @@ static int64_t mp_seek(void *opaque, int64_t pos, int whence)
int64_t current_pos;
MP_TRACE(demuxer, "mp_seek(%p, %"PRId64", %d)\n", stream, pos, whence);
if (whence == SEEK_END || whence == AVSEEK_SIZE) {
- int64_t end;
- if (stream_control(stream, STREAM_CTRL_GET_SIZE, &end) != STREAM_OK)
+ int64_t end = stream_get_size(stream);
+ if (end < 0)
return -1;
if (whence == AVSEEK_SIZE)
return end;
@@ -276,13 +277,7 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
demuxer->priv = talloc_zero(NULL, lavf_priv_t);
priv = demuxer->priv;
- priv->filename = s->url;
- if (!priv->filename) {
- priv->filename = "mp:unknown";
- MP_WARN(demuxer, "Stream url is not set!\n");
- }
-
- priv->filename = remove_prefix(priv->filename, prefixes);
+ priv->filename = remove_prefix(s->url, prefixes);
char *avdevice_format = NULL;
if (s->uncached_type == STREAMTYPE_AVDEVICE) {
@@ -420,6 +415,43 @@ static void parse_cryptokey(AVFormatContext *avfc, const char *str)
*key++ = (char2int(str[0]) << 4) | char2int(str[1]);
}
+static char *replace_idx_ext(void *ta_ctx, bstr f)
+{
+ if (f.len < 4 || f.start[f.len - 4] != '.')
+ return NULL;
+ char *ext = bstr_endswith0(f, "IDX") ? "SUB" : "sub"; // match case
+ return talloc_asprintf(ta_ctx, "%.*s.%s", BSTR_P(bstr_splice(f, 0, -4)), ext);
+}
+
+static void guess_and_set_vobsub_name(struct demuxer *demuxer, AVDictionary **d)
+{
+ lavf_priv_t *priv = demuxer->priv;
+ if (!matches_avinputformat_name(priv, "vobsub"))
+ return;
+
+ void *tmp = talloc_new(NULL);
+ bstr bfilename = bstr0(priv->filename);
+ char *subname = NULL;
+ if (mp_is_url(bfilename)) {
+ // It might be a http URL, which has additional parameters after the
+ // end of the actual file path.
+ bstr start, end;
+ if (bstr_split_tok(bfilename, "?", &start, &end)) {
+ subname = replace_idx_ext(tmp, start);
+ if (subname)
+ subname = talloc_asprintf(tmp, "%s?%.*s", subname, BSTR_P(end));
+ }
+ }
+ if (!subname)
+ subname = replace_idx_ext(tmp, bfilename);
+ if (!subname)
+ subname = talloc_asprintf(tmp, "%.*s.sub", BSTR_P(bfilename));
+
+ MP_VERBOSE(demuxer, "Assuming associated .sub file: %s\n", subname);
+ av_dict_set(d, "sub_name", subname, 0);
+ talloc_free(tmp);
+}
+
static void select_tracks(struct demuxer *demuxer, int start)
{
lavf_priv_t *priv = demuxer->priv;
@@ -489,8 +521,6 @@ static void handle_stream(demuxer_t *demuxer, int i)
break;
sh_audio_t *sh_audio = sh->audio;
- sh->format = codec->codec_tag;
-
// probably unneeded
mp_chmap_set_unknown(&sh_audio->channels, codec->channels);
if (codec->channel_layout)
@@ -518,7 +548,6 @@ static void handle_stream(demuxer_t *demuxer, int i)
}
}
- sh->format = codec->codec_tag;
sh_video->disp_w = codec->width;
sh_video->disp_h = codec->height;
/* Try to make up some frame rate value, even if it's not reliable.
@@ -546,9 +575,11 @@ static void handle_stream(demuxer_t *demuxer, int i)
/ (float)(codec->height * codec->sample_aspect_ratio.den);
uint8_t *sd = av_stream_get_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, NULL);
- if (sd)
- sh_video->rotate = -av_display_rotation_get((uint32_t *)sd);
- sh_video->rotate = ((sh_video->rotate % 360) + 360) % 360;
+ if (sd) {
+ double r = av_display_rotation_get((uint32_t *)sd);
+ if (!isnan(r))
+ sh_video->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");
@@ -563,9 +594,9 @@ static void handle_stream(demuxer_t *demuxer, int i)
sh_sub = sh->sub;
if (codec->extradata_size) {
- sh_sub->extradata = talloc_size(sh, codec->extradata_size);
- memcpy(sh_sub->extradata, codec->extradata, codec->extradata_size);
- sh_sub->extradata_len = 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;
}
if (matches_avinputformat_name(priv, "microdvd")) {
@@ -589,9 +620,8 @@ static void handle_stream(demuxer_t *demuxer, int i)
AVDictionaryEntry *mt = av_dict_get(st->metadata, "mimetype", NULL, 0);
char *mimetype = mt ? mt->value : NULL;
if (mimetype) {
- demuxer_add_attachment(demuxer, bstr0(filename), bstr0(mimetype),
- (struct bstr){codec->extradata,
- codec->extradata_size});
+ demuxer_add_attachment(demuxer, filename, mimetype,
+ codec->extradata, codec->extradata_size);
}
break;
}
@@ -604,10 +634,13 @@ 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 = codec;
if (st->disposition & AV_DISPOSITION_DEFAULT)
- sh->default_track = 1;
+ sh->default_track = true;
+ if (st->disposition & AV_DISPOSITION_FORCED)
+ sh->forced_track = true;
if (priv->format_hack.use_stream_ids)
sh->demuxer_id = st->id;
AVDictionaryEntry *title = av_dict_get(st->metadata, "title", NULL, 0);
@@ -748,6 +781,8 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
av_dict_set(&dopts, "rtsp_transport", transport, 0);
}
+ guess_and_set_vobsub_name(demuxer, &dopts);
+
avfc->interrupt_callback = (AVIOInterruptCB){
.callback = interrupt_cb,
.opaque = demuxer,
@@ -776,7 +811,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
for (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 ? bstr0(t->value) : bstr0(""),
+ int index = demuxer_add_chapter(demuxer, t ? t->value : "",
c->start * av_q2d(c->time_base), i);
mp_tags_copy_from_av_dictionary(demuxer->chapters[index].metadata, c->metadata);
}
@@ -882,8 +917,7 @@ static void demux_seek_lavf(demuxer_t *demuxer, double rel_seek_secs, int flags)
if (flags & SEEK_FACTOR) {
struct stream *s = demuxer->stream;
- int64_t end = 0;
- stream_control(s, STREAM_CTRL_GET_SIZE, &end);
+ int64_t end = stream_get_size(s);
if (end > 0 && demuxer->ts_resets_possible &&
!(priv->avif_flags & AVFMT_NO_BYTE_SEEK))
{
diff --git a/demux/demux_libarchive.c b/demux/demux_libarchive.c
new file mode 100644
index 0000000..1ce8b32
--- /dev/null
+++ b/demux/demux_libarchive.c
@@ -0,0 +1,106 @@
+/*
+ * 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 <archive.h>
+#include <archive_entry.h>
+
+#include "common/common.h"
+#include "common/playlist.h"
+#include "stream/stream.h"
+#include "demux.h"
+
+#include "stream/stream_libarchive.h"
+
+static int cmp_filename(const void *a, const void *b)
+{
+ return strcmp(*(char **)a, *(char **)b);
+}
+
+static int open_file(struct demuxer *demuxer, enum demux_check check)
+{
+ int flags = 0;
+ int probe_size = STREAM_BUFFER_SIZE;
+ if (check <= DEMUX_CHECK_REQUEST) {
+ flags |= MP_ARCHIVE_FLAG_UNSAFE;
+ probe_size *= 100;
+ }
+
+ bstr probe = stream_peek(demuxer->stream, probe_size);
+ if (probe.len == 0)
+ return -1;
+ struct stream *probe_stream = open_memory_stream(probe.start, probe.len);
+ struct mp_archive *mpa = mp_archive_new(mp_null_log, probe_stream, flags);
+ bool ok = !!mpa;
+ free_stream(probe_stream);
+ mp_archive_free(mpa);
+ if (!ok)
+ return -1;
+
+ mpa = mp_archive_new(demuxer->log, demuxer->stream, flags);
+ if (!mpa)
+ return -1;
+
+ struct playlist *pl = talloc_zero(demuxer, struct playlist);
+ demuxer->playlist = pl;
+
+ // make it load archive://
+ pl->disable_safety = true;
+
+ char *prefix = mp_url_escape(mpa, demuxer->stream->url, "~|");
+
+ char **files = NULL;
+ int num_files = 0;
+
+ for (;;) {
+ struct archive_entry *entry;
+ int r = archive_read_next_header(mpa->arch, &entry);
+ if (r == ARCHIVE_EOF)
+ break;
+ if (r < ARCHIVE_OK)
+ MP_ERR(demuxer, "libarchive: %s\n", archive_error_string(mpa->arch));
+ if (r < ARCHIVE_WARN)
+ break;
+ if (archive_entry_filetype(entry) != AE_IFREG)
+ continue;
+ const char *fn = archive_entry_pathname(entry);
+ // Some archives may have no filenames.
+ if (!fn)
+ fn = talloc_asprintf(mpa, "mpv_unknown#%d\n", num_files);
+ // stream_libarchive.c does the real work
+ char *f = talloc_asprintf(mpa, "archive://%s|%s", prefix, fn);
+ MP_TARRAY_APPEND(mpa, files, num_files, f);
+ }
+
+ if (files)
+ qsort(files, num_files, sizeof(files[0]), cmp_filename);
+
+ for (int n = 0; n < num_files; n++)
+ playlist_add_file(pl, files[n]);
+
+ demuxer->filetype = "archive";
+ demuxer->fully_read = true;
+
+ mp_archive_free(mpa);
+
+ return 0;
+}
+
+const struct demuxer_desc demuxer_desc_libarchive = {
+ .name = "libarchive",
+ .desc = "libarchive wrapper",
+ .open = open_file,
+};
diff --git a/demux/demux_libass.c b/demux/demux_libass.c
index b83408b..ef09f04 100644
--- a/demux/demux_libass.c
+++ b/demux/demux_libass.c
@@ -95,8 +95,8 @@ static int d_check_file(struct demuxer *demuxer, enum demux_check check)
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_SUB);
sh->codec = "ass";
- sh->sub->extradata = cbuf.start;
- sh->sub->extradata_len = cbuf.len;
+ sh->extradata = cbuf.start;
+ sh->extradata_size = cbuf.len;
demuxer->seekable = true;
demuxer->fully_read = true;
diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c
index b4ae042..e2743d5 100644
--- a/demux/demux_mkv.c
+++ b/demux/demux_mkv.c
@@ -96,7 +96,6 @@ typedef struct mkv_track {
struct sh_stream *stream;
char *codec_id;
- int ms_compat;
char *language;
int type;
@@ -107,7 +106,6 @@ typedef struct mkv_track {
uint32_t colorspace;
int stereo_mode;
- uint32_t a_formattag;
uint32_t a_channels, a_bps;
float a_sfreq;
float a_osfreq;
@@ -116,6 +114,7 @@ typedef struct mkv_track {
double codec_delay;
int default_track;
+ int forced_track;
unsigned char *private_data;
unsigned int private_size;
@@ -199,14 +198,15 @@ const struct m_sub_options demux_mkv_conf = {
OPT_FLAG("subtitle-preroll", subtitle_preroll, 0),
OPT_DOUBLE("subtitle-preroll-secs", subtitle_preroll_secs,
M_OPT_MIN, .min = 0),
- OPT_FLAG("probe-video-duration", probe_duration, 0),
+ OPT_CHOICE("probe-video-duration", probe_duration, 0,
+ ({"no", 0}, {"yes", 1}, {"full", 2})),
OPT_FLAG("fix-timestamps", fix_timestamps, 0),
{0}
},
.size = sizeof(struct demux_mkv_opts),
.defaults = &(const struct demux_mkv_opts){
.subtitle_preroll_secs = 1.0,
- .fix_timestamps = 1,
+ .fix_timestamps = 0,
},
};
@@ -375,8 +375,8 @@ static int demux_mkv_read_info(demuxer_t *demuxer)
MP_VERBOSE(demuxer, "| + duration: %.3fs\n",
mkv_d->duration);
}
- if (info.n_title) {
- mp_tags_set_bstr(demuxer->metadata, bstr0("TITLE"), info.title);
+ if (info.title) {
+ mp_tags_set_str(demuxer->metadata, "TITLE", info.title);
}
if (info.n_segment_uid) {
int len = info.segment_uid.len;
@@ -509,10 +509,7 @@ static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track,
struct ebml_video *video)
{
if (video->n_frame_rate) {
- track->v_frate = video->frame_rate;
- MP_VERBOSE(demuxer, "| + Frame rate: %f\n", track->v_frate);
- if (track->v_frate > 0)
- track->default_duration = 1 / track->v_frate;
+ MP_VERBOSE(demuxer, "| + Frame rate: %f (ignored)\n", video->frame_rate);
}
if (video->n_display_width) {
track->v_dwidth = video->display_width;
@@ -574,9 +571,8 @@ static void parse_trackentry(struct demuxer *demuxer,
MP_ERR(demuxer, "Missing track number!\n");
}
- if (entry->n_name) {
- track->name = talloc_strndup(track, entry->name.start,
- entry->name.len);
+ if (entry->name) {
+ track->name = talloc_strdup(track, entry->name);
MP_VERBOSE(demuxer, "| + Name: %s\n", track->name);
}
@@ -607,12 +603,8 @@ static void parse_trackentry(struct demuxer *demuxer,
parse_trackvideo(demuxer, track, &entry->video);
}
- if (entry->n_codec_id) {
- track->codec_id = talloc_strndup(track, entry->codec_id.start,
- entry->codec_id.len);
- if (!strcmp(track->codec_id, MKV_V_MSCOMP)
- || !strcmp(track->codec_id, MKV_A_ACM))
- track->ms_compat = 1;
+ if (entry->codec_id) {
+ track->codec_id = talloc_strdup(track, entry->codec_id);
MP_VERBOSE(demuxer, "| + Codec ID: %s\n", track->codec_id);
} else {
MP_ERR(demuxer, "Missing codec ID!\n");
@@ -627,9 +619,8 @@ static void parse_trackentry(struct demuxer *demuxer,
MP_VERBOSE(demuxer, "| + CodecPrivate, length %u\n", track->private_size);
}
- if (entry->n_language) {
- track->language = talloc_strndup(track, entry->language.start,
- entry->language.len);
+ if (entry->language) {
+ track->language = talloc_strdup(track, entry->language);
MP_VERBOSE(demuxer, "| + Language: %s\n", track->language);
} else {
track->language = talloc_strdup(track, "eng");
@@ -642,13 +633,17 @@ static void parse_trackentry(struct demuxer *demuxer,
track->default_track = 1;
}
+ if (entry->n_flag_forced) {
+ track->forced_track = entry->flag_forced;
+ MP_VERBOSE(demuxer, "| + Forced flag: %u\n", track->forced_track);
+ }
+
if (entry->n_default_duration) {
track->default_duration = entry->default_duration / 1e9;
if (entry->default_duration == 0) {
MP_VERBOSE(demuxer, "| + Default duration: 0");
} else {
- if (!track->v_frate)
- track->v_frate = 1e9 / entry->default_duration;
+ track->v_frate = 1e9 / entry->default_duration;
MP_VERBOSE(demuxer, "| + Default duration: %.3fms ( = %.3f fps)\n",
entry->default_duration / 1000000.0, track->v_frate);
}
@@ -729,26 +724,36 @@ static int demux_mkv_read_cues(demuxer_t *demuxer)
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
stream_t *s = demuxer->stream;
- if (opts->index_mode != 1) {
+ if (opts->index_mode != 1 || mkv_d->index_complete) {
ebml_read_skip(demuxer->log, -1, s);
return 0;
}
- MP_VERBOSE(demuxer, "/---- [ parsing cues ] -----------\n");
+ MP_VERBOSE(demuxer, "Parsing cues...\n");
struct ebml_cues cues = {0};
struct ebml_parse_ctx parse_ctx = {demuxer->log};
if (ebml_read_element(s, &parse_ctx, &cues, &ebml_cues_desc) < 0)
return -1;
- mkv_d->num_indexes = 0;
- mkv_d->index_has_durations = false;
-
for (int i = 0; i < cues.n_cue_point; i++) {
struct ebml_cue_point *cuepoint = &cues.cue_point[i];
if (cuepoint->n_cue_time != 1 || !cuepoint->n_cue_track_positions) {
MP_WARN(demuxer, "Malformed CuePoint element\n");
- continue;
+ goto done;
}
+ if (cuepoint->cue_time / 1e9 > mkv_d->duration / mkv_d->tc_scale * 10 &&
+ mkv_d->duration != 0)
+ goto done;
+ }
+ if (cues.n_cue_point <= 3) // probably too sparse and will just break seeking
+ goto done;
+
+ // Discard incremental index.
+ mkv_d->num_indexes = 0;
+ mkv_d->index_has_durations = false;
+
+ for (int i = 0; i < cues.n_cue_point; i++) {
+ struct ebml_cue_point *cuepoint = &cues.cue_point[i];
uint64_t time = cuepoint->cue_time;
for (int c = 0; c < cuepoint->n_cue_track_positions; c++) {
struct ebml_cue_track_positions *trackpos =
@@ -768,7 +773,9 @@ static int demux_mkv_read_cues(demuxer_t *demuxer)
// Do not attempt to create index on the fly.
mkv_d->index_complete = true;
- MP_VERBOSE(demuxer, "\\---- [ parsing cues ] -----------\n");
+done:
+ if (!mkv_d->index_complete)
+ MP_WARN(demuxer, "Discarding potentially broken or useless index.\n");
talloc_free(parse_ctx.talloc_ctx);
return 0;
}
@@ -785,7 +792,7 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
if (wanted_edition_uid)
wanted_edition = -1;
- MP_VERBOSE(demuxer, "/---- [ parsing chapters ] ---------\n");
+ MP_VERBOSE(demuxer, "Parsing chapters...\n");
struct ebml_chapters file_chapters = {0};
struct ebml_parse_ctx parse_ctx = {demuxer->log};
if (ebml_read_element(s, &parse_ctx, &file_chapters,
@@ -851,18 +858,27 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
for (int i = 0; i < chapter_count; i++) {
struct ebml_chapter_atom *ca = editions[idx].chapter_atom + i;
struct matroska_chapter chapter = {0};
- struct bstr name = { "(unnamed)", 9 };
+ char *name = "(unnamed)";
- if (!ca->n_chapter_time_start)
- MP_MSG(demuxer, warn_level, "Chapter lacks start time\n");
chapter.start = ca->chapter_time_start;
chapter.end = ca->chapter_time_end;
+ if (!ca->n_chapter_time_start)
+ MP_MSG(demuxer, warn_level, "Chapter lacks start time\n");
+ if (!ca->n_chapter_time_start || !ca->n_chapter_time_end) {
+ if (demuxer->matroska_data.ordered_chapters) {
+ MP_MSG(demuxer, warn_level, "Chapter lacks start or end "
+ "time, disabling ordered chapters.\n");
+ demuxer->matroska_data.ordered_chapters = NULL;
+ demuxer->matroska_data.num_ordered_chapters = 0;
+ }
+ }
+
if (ca->n_chapter_display) {
if (ca->n_chapter_display > 1)
MP_MSG(demuxer, warn_level, "Multiple chapter "
"names not supported, picking first\n");
- if (!ca->chapter_display[0].n_chap_string)
+ if (!ca->chapter_display[0].chap_string)
MP_MSG(demuxer, warn_level, "Malformed chapter name entry\n");
else
name = ca->chapter_display[0].chap_string;
@@ -890,7 +906,7 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
}
MP_VERBOSE(demuxer, "Chapter %u from %02d:%02d:%02d.%03d "
- "to %02d:%02d:%02d.%03d, %.*s\n", i,
+ "to %02d:%02d:%02d.%03d, %s\n", i,
(int) (chapter.start / 60 / 60 / 1000000000),
(int) ((chapter.start / 60 / 1000000000) % 60),
(int) ((chapter.start / 1000000000) % 60),
@@ -899,14 +915,14 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
(int) ((chapter.end / 60 / 1000000000) % 60),
(int) ((chapter.end / 1000000000) % 60),
(int) (chapter.end % 1000000000),
- BSTR_P(name));
+ name);
if (idx == selected_edition) {
demuxer_add_chapter(demuxer, name, chapter.start / 1e9,
ca->chapter_uid);
}
if (m_chapters) {
- chapter.name = talloc_strndup(m_chapters, name.start, name.len);
+ chapter.name = talloc_strdup(m_chapters, name);
m_chapters[i] = chapter;
}
}
@@ -916,7 +932,6 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
demuxer->edition = selected_edition;
talloc_free(parse_ctx.talloc_ctx);
- MP_VERBOSE(demuxer, "\\---- [ parsing chapters ] ---------\n");
return 0;
}
@@ -974,8 +989,10 @@ static void process_tags(demuxer_t *demuxer)
if (dst) {
for (int j = 0; j < tag.n_simple_tag; j++) {
- mp_tags_set_bstr(dst, tag.simple_tag[j].tag_name,
- tag.simple_tag[j].tag_string);
+ if (tag.simple_tag[j].tag_name && tag.simple_tag[j].tag_string) {
+ mp_tags_set_str(dst, tag.simple_tag[j].tag_name,
+ tag.simple_tag[j].tag_string);
+ }
}
}
}
@@ -985,7 +1002,7 @@ static int demux_mkv_read_attachments(demuxer_t *demuxer)
{
stream_t *s = demuxer->stream;
- MP_VERBOSE(demuxer, "/---- [ parsing attachments ] ---------\n");
+ MP_VERBOSE(demuxer, "Parsing attachments...\n");
struct ebml_attachments attachments = {0};
struct ebml_parse_ctx parse_ctx = {demuxer->log};
@@ -995,20 +1012,20 @@ static int demux_mkv_read_attachments(demuxer_t *demuxer)
for (int i = 0; i < attachments.n_attached_file; i++) {
struct ebml_attached_file *attachment = &attachments.attached_file[i];
- if (!attachment->n_file_name || !attachment->n_file_mime_type
+ if (!attachment->file_name || !attachment->file_mime_type
|| !attachment->n_file_data) {
MP_WARN(demuxer, "Malformed attachment\n");
continue;
}
- struct bstr name = attachment->file_name;
- struct bstr mime = attachment->file_mime_type;
- demuxer_add_attachment(demuxer, name, mime, attachment->file_data);
- MP_VERBOSE(demuxer, "Attachment: %.*s, %.*s, %zu bytes\n",
- BSTR_P(name), BSTR_P(mime), attachment->file_data.len);
+ char *name = attachment->file_name;
+ char *mime = attachment->file_mime_type;
+ demuxer_add_attachment(demuxer, name, mime, attachment->file_data.start,
+ attachment->file_data.len);
+ MP_VERBOSE(demuxer, "Attachment: %s, %s, %zu bytes\n",
+ name, mime, attachment->file_data.len);
}
talloc_free(parse_ctx.talloc_ctx);
- MP_VERBOSE(demuxer, "\\---- [ parsing attachments ] ---------\n");
return 0;
}
@@ -1060,7 +1077,7 @@ static int demux_mkv_read_seekhead(demuxer_t *demuxer)
struct ebml_seek_head seekhead = {0};
struct ebml_parse_ctx parse_ctx = {demuxer->log};
- MP_VERBOSE(demuxer, "/---- [ parsing seek head ] ---------\n");
+ MP_VERBOSE(demuxer, "Parsing seek head...\n");
if (ebml_read_element(s, &parse_ctx, &seekhead, &ebml_seek_head_desc) < 0) {
res = -1;
goto out;
@@ -1077,7 +1094,6 @@ static int demux_mkv_read_seekhead(demuxer_t *demuxer)
get_header_element(demuxer, seek->seek_id, pos);
}
out:
- MP_VERBOSE(demuxer, "\\---- [ parsing seek head ] ---------\n");
talloc_free(parse_ctx.talloc_ctx);
return res;
}
@@ -1173,6 +1189,20 @@ static void add_coverart(struct demuxer *demuxer)
}
}
+static void init_track(demuxer_t *demuxer, mkv_track_t *track,
+ struct sh_stream *sh)
+{
+ track->stream = sh;
+
+ if (track->language && (strcmp(track->language, "und") != 0))
+ sh->lang = track->language;
+
+ sh->demuxer_id = track->tnum;
+ sh->title = track->name;
+ sh->default_track = track->default_track;
+ sh->forced_track = track->forced_track;
+}
+
static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track);
static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track);
static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track);
@@ -1196,26 +1226,20 @@ static void display_create_tracks(demuxer_t *demuxer)
}
}
-typedef struct {
- char *id;
- int fourcc;
- int extradata;
-} videocodec_info_t;
-
-static const videocodec_info_t vinfo[] = {
- {MKV_V_MJPEG, MP_FOURCC('m', 'j', 'p', 'g'), 1},
- {MKV_V_MPEG1, MP_FOURCC('m', 'p', 'g', '1'), 0},
- {MKV_V_MPEG2, MP_FOURCC('m', 'p', 'g', '2'), 0},
- {MKV_V_MPEG4_SP, MP_FOURCC('m', 'p', '4', 'v'), 1},
- {MKV_V_MPEG4_ASP, MP_FOURCC('m', 'p', '4', 'v'), 1},
- {MKV_V_MPEG4_AP, MP_FOURCC('m', 'p', '4', 'v'), 1},
- {MKV_V_MPEG4_AVC, MP_FOURCC('a', 'v', 'c', '1'), 1},
- {MKV_V_THEORA, MP_FOURCC('t', 'h', 'e', 'o'), 1},
- {MKV_V_VP8, MP_FOURCC('V', 'P', '8', '0'), 0},
- {MKV_V_VP9, MP_FOURCC('V', 'P', '9', '0'), 0},
- {MKV_V_DIRAC, MP_FOURCC('d', 'r', 'a', 'c'), 0},
- {MKV_V_PRORES, MP_FOURCC('p', 'r', '0', '0'), 0},
- {MKV_V_HEVC, MP_FOURCC('H', 'E', 'V', 'C'), 1},
+static const char *const mkv_video_tags[][2] = {
+ {"V_MJPEG", "mjpeg"},
+ {"V_MPEG1", "mpeg1video"},
+ {"V_MPEG2", "mpeg2video"},
+ {"V_MPEG4/ISO/SP", "mpeg4"},
+ {"V_MPEG4/ISO/ASP", "mpeg4"},
+ {"V_MPEG4/ISO/AP", "mpeg4"},
+ {"V_MPEG4/ISO/AVC", "h264"},
+ {"V_THEORA", "theora"},
+ {"V_VP8", "vp8"},
+ {"V_VP9", "vp9"},
+ {"V_DIRAC", "dirac"},
+ {"V_PRORES", "prores"},
+ {"V_MPEGH/ISO/HEVC", "hevc"},
{0}
};
@@ -1226,12 +1250,12 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_VIDEO);
if (!sh)
return 1;
- track->stream = sh;
+ init_track(demuxer, track, sh);
sh_video_t *sh_v = sh->video;
- sh->demuxer_id = track->tnum;
- sh->title = talloc_strdup(sh_v, track->name);
- if (track->ms_compat) { /* MS compatibility mode */
+ 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;
@@ -1242,73 +1266,72 @@ 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->format = AV_RL32(h + 16); // biCompression
+ sh->codec_tag = AV_RL32(h + 16); // biCompression
extradata = track->private_data + 40;
extradata_size = track->private_size - 40;
mp_set_codec_from_tag(sh);
+ sh_v->avi_dts = true;
+ } else if (track->private_size >= RVPROPERTIES_SIZE
+ && (!strcmp(track->codec_id, "V_REAL/RV10")
+ || !strcmp(track->codec_id, "V_REAL/RV20")
+ || !strcmp(track->codec_id, "V_REAL/RV30")
+ || !strcmp(track->codec_id, "V_REAL/RV40")))
+ {
+ unsigned char *src;
+ unsigned int cnt;
+
+ src = (uint8_t *) track->private_data + RVPROPERTIES_SIZE;
+
+ 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;
+ }
+ // copy type1 and type2 info from rv properties
+ extradata_size = cnt + 8;
+ extradata = src - 8;
+ track->parse = true;
+ 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";
+ } else if (strcmp(track->codec_id, "V_QUICKTIME") == 0) {
+ uint32_t fourcc1 = 0, fourcc2 = 0;
+ if (track->private_size >= 8) {
+ fourcc1 = AV_RL32(track->private_data + 0);
+ fourcc2 = AV_RL32(track->private_data + 4);
+ }
+ if (fourcc1 == MP_FOURCC('S', 'V', 'Q', '3') ||
+ fourcc2 == MP_FOURCC('S', 'V', 'Q', '3'))
+ {
+ sh->codec = "svq3";
+ extradata = track->private_data;
+ extradata_size = track->private_size;
+ }
} else {
- sh_v->bits_per_coded_sample = 24;
-
- if (track->private_size >= RVPROPERTIES_SIZE
- && (!strcmp(track->codec_id, MKV_V_REALV10)
- || !strcmp(track->codec_id, MKV_V_REALV20)
- || !strcmp(track->codec_id, MKV_V_REALV30)
- || !strcmp(track->codec_id, MKV_V_REALV40))) {
- unsigned char *src;
- uint32_t type2;
- unsigned int cnt;
-
- src = (uint8_t *) track->private_data + RVPROPERTIES_SIZE;
-
- cnt = track->private_size - RVPROPERTIES_SIZE;
- type2 = AV_RB32(src - 4);
- if (type2 == 0x10003000 || type2 == 0x10003001)
- sh->format = MP_FOURCC('R', 'V', '1', '3');
- else
- sh->format = MP_FOURCC('R', 'V', track->codec_id[9], '0');
- // copy type1 and type2 info from rv properties
- extradata_size = cnt + 8;
- extradata = src - 8;
- track->parse = true;
- track->parse_timebase = 1e3;
- mp_set_codec_from_tag(sh);
- } else if (strcmp(track->codec_id, MKV_V_UNCOMPRESSED) == 0) {
- // raw video, "like AVI" - this is a FourCC
- sh->format = track->colorspace;
- sh->codec = "rawvideo";
- } else if (strcmp(track->codec_id, MKV_V_QUICKTIME) == 0) {
- uint32_t fourcc1 = 0, fourcc2 = 0;
- if (track->private_size >= 8) {
- fourcc1 = AV_RL32(track->private_data + 0);
- fourcc2 = AV_RL32(track->private_data + 4);
- }
- if (fourcc1 == MP_FOURCC('S', 'V', 'Q', '3') ||
- fourcc2 == MP_FOURCC('S', 'V', 'Q', '3'))
- {
- sh->codec = "svq3";
- extradata = track->private_data;
- extradata_size = track->private_size;
- }
- } else {
- const videocodec_info_t *vi = vinfo;
- while (vi->id && strcmp(vi->id, track->codec_id))
- vi++;
- if (vi->id) {
- sh->format = vi->fourcc;
- mp_set_codec_from_tag(sh);
- }
- if (vi->extradata && track->private_data && track->private_size > 0)
- {
- extradata = track->private_data;
- extradata_size = track->private_size;
+ 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];
+ break;
}
}
+ if (track->private_data && track->private_size > 0) {
+ extradata = track->private_data;
+ extradata_size = track->private_size;
+ }
}
- if (sh->format == MP_FOURCC('V', 'P', '9', '0')) {
+ const char *codec = sh->codec ? sh->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');
}
if (extradata_size > 0x1000000) {
@@ -1316,15 +1339,13 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
return 1;
}
- sh_v->extradata = talloc_memdup(sh_v, extradata, extradata_size);
- sh_v->extradata_len = extradata_size;
+ sh->extradata = talloc_memdup(sh_v, extradata, extradata_size);
+ sh->extradata_size = extradata_size;
if (!sh->codec) {
MP_WARN(demuxer, "Unknown/unsupported CodecID (%s) or missing/bad "
"CodecPrivate data (track %u).\n",
track->codec_id, track->tnum);
}
- if (track->v_frate == 0.0)
- track->v_frate = 25.0;
sh_v->fps = track->v_frate;
sh_v->disp_w = track->v_width;
sh_v->disp_h = track->v_height;
@@ -1332,46 +1353,94 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
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);
- sh_v->avi_dts = track->ms_compat;
sh_v->stereo_mode = track->stereo_mode;
return 0;
}
-static const struct mkv_audio_tag {
- char *id; bool prefix; uint32_t formattag;
-} mkv_audio_tags[] = {
- { MKV_A_MP2, 0, 0x0055 },
- { MKV_A_MP3, 0, 0x0055 },
- { MKV_A_AC3, 1, 0x2000 },
- { MKV_A_EAC3, 1, MP_FOURCC('E', 'A', 'C', '3') },
- { MKV_A_DTS, 0, 0x2001 },
- { MKV_A_PCM, 0, 0x0001 },
- { MKV_A_PCM_BE, 0, 0x0001 },
- { MKV_A_PCM_FLT, 0, 0x0003 },
- { MKV_A_AAC_2MAIN, 0, MP_FOURCC('M', 'P', '4', 'A') },
- { MKV_A_AAC_2LC, 1, MP_FOURCC('M', 'P', '4', 'A') },
- { MKV_A_AAC_2SSR, 0, MP_FOURCC('M', 'P', '4', 'A') },
- { MKV_A_AAC_4MAIN, 0, MP_FOURCC('M', 'P', '4', 'A') },
- { MKV_A_AAC_4LC, 1, MP_FOURCC('M', 'P', '4', 'A') },
- { MKV_A_AAC_4SSR, 0, MP_FOURCC('M', 'P', '4', 'A') },
- { MKV_A_AAC_4LTP, 0, MP_FOURCC('M', 'P', '4', 'A') },
- { MKV_A_AAC, 0, MP_FOURCC('M', 'P', '4', 'A') },
- { MKV_A_VORBIS, 0, MP_FOURCC('v', 'r', 'b', 's') },
- { MKV_A_OPUS, 0, MP_FOURCC('O', 'p', 'u', 's') },
- { MKV_A_OPUS_EXP, 0, MP_FOURCC('O', 'p', 'u', 's') },
- { MKV_A_QDMC, 0, MP_FOURCC('Q', 'D', 'M', 'C') },
- { MKV_A_QDMC2, 0, MP_FOURCC('Q', 'D', 'M', '2') },
- { MKV_A_WAVPACK, 0, MP_FOURCC('W', 'V', 'P', 'K') },
- { MKV_A_TRUEHD, 0, MP_FOURCC('T', 'R', 'H', 'D') },
- { MKV_A_FLAC, 0, MP_FOURCC('f', 'L', 'a', 'C') },
- { MKV_A_ALAC, 0, MP_FOURCC('a', 'L', 'a', 'C') },
- { MKV_A_REAL28, 0, MP_FOURCC('2', '8', '_', '8') },
- { MKV_A_REALATRC, 0, MP_FOURCC('a', 't', 'r', 'c') },
- { MKV_A_REALCOOK, 0, MP_FOURCC('c', 'o', 'o', 'k') },
- { MKV_A_REALDNET, 0, MP_FOURCC('d', 'n', 'e', 't') },
- { MKV_A_REALSIPR, 0, MP_FOURCC('s', 'i', 'p', 'r') },
- { MKV_A_TTA1, 0, MP_FOURCC('T', 'T', 'A', '1') },
+// Parse VorbisComment and look for WAVEFORMATEXTENSIBLE_CHANNEL_MASK.
+// Do not change *channels if nothing found or an error happens.
+static void parse_vorbis_chmap(struct mp_chmap *channels, unsigned char *data,
+ int size)
+{
+ // Skip the useless vendor string.
+ if (size < 4)
+ return;
+ uint32_t vendor_length = AV_RL32(data);
+ if (vendor_length + 4 > size) // also check for the next AV_RB32 below
+ return;
+ size -= vendor_length + 4;
+ data += vendor_length + 4;
+ uint32_t num_headers = AV_RL32(data);
+ size -= 4;
+ data += 4;
+ for (int n = 0; n < num_headers; n++) {
+ if (size < 4)
+ return;
+ uint32_t len = AV_RL32(data);
+ size -= 4;
+ data += 4;
+ if (len > size)
+ return;
+ if (len > 34 && !memcmp(data, "WAVEFORMATEXTENSIBLE_CHANNEL_MASK=", 34)) {
+ char smask[80];
+ snprintf(smask, sizeof(smask), "%.*s", (int)(len - 34), data + 34);
+ char *end = NULL;
+ uint32_t mask = strtol(smask, &end, 0);
+ if (!end || end[0])
+ mask = 0;
+ struct mp_chmap chmask = {0};
+ mp_chmap_from_waveext(&chmask, mask);
+ if (mp_chmap_is_valid(&chmask))
+ *channels = chmask;
+ }
+ size -= len;
+ data += len;
+ }
+}
+
+// Parse VorbisComment-in-FLAC and look for WAVEFORMATEXTENSIBLE_CHANNEL_MASK.
+// Do not change *channels if nothing found or an error happens.
+static void parse_flac_chmap(struct mp_chmap *channels, unsigned char *data,
+ int size)
+{
+ // Skip FLAC header.
+ if (size < 4)
+ return;
+ data += 4;
+ size -= 4;
+ // Parse FLAC blocks...
+ while (size >= 4) {
+ unsigned btype = data[0] & 0x7F;
+ unsigned bsize = AV_RB24(data + 1);
+ data += 4;
+ size -= 4;
+ if (bsize > size)
+ return;
+ if (btype == 4) // VORBIS_COMMENT
+ parse_vorbis_chmap(channels, data, bsize);
+ data += bsize;
+ size -= bsize;
+ }
+}
+
+static const char *const mkv_audio_tags[][2] = {
+ { "A_MPEG/L2", "mp3" },
+ { "A_MPEG/L3", "mp3" },
+ { "A_AC3", "ac3" },
+ { "A_EAC3", "eac3" },
+ { "A_DTS", "dts" },
+ { "A_AAC", "aac" },
+ { "A_VORBIS", "vorbis" },
+ { "A_OPUS", "opus" },
+ { "A_OPUS/EXPERIMENTAL", "opus" },
+ { "A_QUICKTIME/QDMC", "qdmc" },
+ { "A_QUICKTIME/QDM2", "qdm2" },
+ { "A_WAVPACK4", "wavpack" },
+ { "A_TRUEHD", "truehd" },
+ { "A_FLAC", "flac" },
+ { "A_ALAC", "alac" },
+ { "A_TTA1", "tta" },
{ NULL },
};
@@ -1380,7 +1449,7 @@ 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;
- track->stream = sh;
+ init_track(demuxer, track, sh);
sh_audio_t *sh_a = sh->audio;
if (track->private_size > 0x1000000)
@@ -1389,132 +1458,65 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
unsigned char *extradata = track->private_data;
unsigned int extradata_len = track->private_size;
- if (track->language && (strcmp(track->language, "und") != 0))
- sh->lang = talloc_strdup(sh_a, track->language);
- sh->demuxer_id = track->tnum;
- sh->title = talloc_strdup(sh_a, track->name);
- sh->default_track = track->default_track;
if (!track->a_osfreq)
track->a_osfreq = track->a_sfreq;
+ sh_a->bits_per_coded_sample = track->a_bps ? track->a_bps : 16;
+ sh_a->samplerate = (uint32_t) track->a_osfreq;
+
+ 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];
+ break;
+ }
+ }
- if (track->ms_compat) {
+ if (!strcmp(track->codec_id, "A_MS/ACM")) { /* AVI compatibility mode */
// The private_data contains a WAVEFORMATEX struct
if (track->private_size < 18)
goto error;
MP_VERBOSE(demuxer, "track with MS compat audio.\n");
unsigned char *h = track->private_data;
- track->a_formattag = AV_RL16(h + 0); // wFormatTag
+ sh->codec_tag = AV_RL16(h + 0); // wFormatTag
if (track->a_channels == 0)
track->a_channels = AV_RL16(h + 2); // nChannels
- if (track->a_osfreq == 0.0)
- track->a_osfreq = AV_RL32(h + 4); // nSamplesPerSec
+ if (sh_a->samplerate == 0)
+ sh_a->samplerate = AV_RL32(h + 4); // nSamplesPerSec
sh_a->bitrate = AV_RL32(h + 8) * 8; // nAvgBytesPerSec
sh_a->block_align = AV_RL16(h + 12); // nBlockAlign
if (track->a_bps == 0)
track->a_bps = AV_RL16(h + 14); // wBitsPerSample
extradata = track->private_data + 18;
extradata_len = track->private_size - 18;
- } else {
- for (int i = 0; ; i++) {
- const struct mkv_audio_tag *t = mkv_audio_tags + i;
- if (t->id == NULL)
- goto error;
- if (t->prefix) {
- if (!bstr_startswith0(bstr0(track->codec_id), t->id))
- continue;
- } else {
- if (strcmp(track->codec_id, t->id))
- continue;
- }
- track->a_formattag = t->formattag;
- break;
- }
- }
-
- sh->format = track->a_formattag;
- mp_chmap_set_unknown(&sh_a->channels, track->a_channels);
- sh_a->samplerate = (uint32_t) track->a_osfreq;
- sh_a->bits_per_coded_sample = track->a_bps ? track->a_bps : 16;
- if (track->a_formattag == 0x0055) { /* MP3 || MP2 */
- sh_a->bitrate = 16000 * 8;
- sh_a->block_align = 1152;
- track->parse = true;
- } else if ((track->a_formattag == 0x2000) /* AC3 */
- || track->a_formattag == MP_FOURCC('E', 'A', 'C', '3')
- || (track->a_formattag == 0x2001)) { /* DTS */
- sh_a->bits_per_coded_sample = 0;
- sh_a->bitrate = 0;
- sh_a->block_align = 0;
- } else if (track->a_formattag == 0x0001) { /* PCM || PCM_BE */
- if (!strcmp(track->codec_id, MKV_A_PCM_BE))
- sh->format = MP_FOURCC('t', 'w', 'o', 's');
- } else if (track->a_formattag == 0x0003) { /* PCM_FLT */
- /* ok */
- } else if (!strcmp(track->codec_id, MKV_A_QDMC)
- || !strcmp(track->codec_id, MKV_A_QDMC2)) {
- sh_a->bitrate = 16000 * 8;
- sh_a->block_align = 1486;
- } else if (track->a_formattag == MP_FOURCC('M', 'P', '4', 'A')) {
- sh_a->bitrate = 16000 * 8;
- sh_a->block_align = 1024;
-
- if (strcmp(track->codec_id, MKV_A_AAC)) {
- /* Recreate the 'private data' */
- int srate_idx = aac_get_sample_rate_index(track->a_sfreq);
- const char *tail = "";
- if (strlen(track->codec_id) >= 12)
- tail = &track->codec_id[12];
- int profile = 3;
- if (!strncmp(tail, "MAIN", 4))
- profile = 0;
- else if (!strncmp(tail, "LC", 2))
- profile = 1;
- else if (!strncmp(tail, "SSR", 3))
- profile = 2;
- sh_a->codecdata = talloc_size(sh_a, 5);
- sh_a->codecdata[0] = ((profile + 1) << 3) | ((srate_idx & 0xE) >> 1);
- sh_a->codecdata[1] =
- ((srate_idx & 0x1) << 7) | (track->a_channels << 3);
-
- if (strstr(track->codec_id, "SBR") != NULL) {
- /* HE-AAC (aka SBR AAC) */
- sh_a->codecdata_len = 5;
-
- srate_idx = aac_get_sample_rate_index(sh_a->samplerate);
- sh_a->codecdata[2] = AAC_SYNC_EXTENSION_TYPE >> 3;
- sh_a->codecdata[3] = ((AAC_SYNC_EXTENSION_TYPE & 0x07) << 5) | 5;
- sh_a->codecdata[4] = (1 << 7) | (srate_idx << 3);
- track->default_duration = 1024.0 / (sh_a->samplerate / 2);
- } else {
- sh_a->codecdata_len = 2;
- track->default_duration = 1024.0 / sh_a->samplerate;
- }
- }
- } else if (track->a_formattag == MP_FOURCC('v', 'r', 'b', 's')) {
- /* VORBIS */
- } else if (!strcmp(track->codec_id, MKV_A_OPUS)
- || !strcmp(track->codec_id, MKV_A_OPUS_EXP)) {
- sh->format = MP_FOURCC('O', 'p', 'u', 's');
- } else if (!strncmp(track->codec_id, MKV_A_REALATRC, 7)) {
+ sh_a->bits_per_coded_sample = track->a_bps;
+ mp_set_codec_from_tag(sh);
+ } 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);
+ } 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);
+ } else if (!strcmp(track->codec_id, "A_PCM/FLOAT/IEEE")) {
+ sh->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;
/* Common initialization for all RealAudio codecs */
unsigned char *src = track->private_data;
- sh_a->bitrate = 0; /* FIXME !? */
-
int version = AV_RB16(src + 4);
unsigned int flavor = AV_RB16(src + 22);
track->coded_framesize = AV_RB32(src + 24);
track->sub_packet_h = AV_RB16(src + 40);
sh_a->block_align = track->audiopk_size = AV_RB16(src + 42);
track->sub_packet_size = AV_RB16(src + 44);
+ int offset = 0;
if (version == 4) {
- src += RAPROPERTIES4_SIZE;
- src += src[0] + 1;
- src += src[0] + 1;
+ offset += RAPROPERTIES4_SIZE;
+ if (offset + 1 > track->private_size)
+ goto error;
+ offset += (src[offset] + 1) * 2 + 3;
} else {
- src += RAPROPERTIES5_SIZE;
+ offset += RAPROPERTIES5_SIZE + 3 + (version == 5 ? 1 : 0);
}
if (track->audiopk_size == 0 || track->sub_packet_size == 0 ||
@@ -1523,86 +1525,120 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
if (track->coded_framesize > 0x40000000)
goto error;
- src += 3;
- if (version == 5)
- src++;
- uint32_t codecdata_length = AV_RB32(src);
- if (codecdata_length > 0x1000000)
+ if (offset + 4 > track->private_size)
+ goto error;
+ uint32_t codecdata_length = AV_RB32(src + offset);
+ offset += 4;
+ if (offset > track->private_size ||
+ codecdata_length > track->private_size - offset)
goto error;
- src += 4;
extradata_len = codecdata_length;
- extradata = src;
+ extradata = src + offset;
- switch (track->a_formattag) {
- case MP_FOURCC('a', 't', 'r', 'c'):
+ if (!strcmp(track->codec_id, "A_REAL/ATRC")) {
+ sh->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;
- break;
- case MP_FOURCC('c', 'o', 'o', 'k'):
+ } else if (!strcmp(track->codec_id, "A_REAL/COOK")) {
+ sh->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;
- break;
- case MP_FOURCC('s', 'i', 'p', 'r'):
+ } else if (!strcmp(track->codec_id, "A_REAL/SIPR")) {
+ sh->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;
- break;
- case MP_FOURCC('2', '8', '_', '8'):
+ } else if (!strcmp(track->codec_id, "A_REAL/28_8")) {
+ sh->codec = "ra_288";
sh_a->bitrate = 3600 * 8;
sh_a->block_align = track->coded_framesize;
- break;
+ } else if (!strcmp(track->codec_id, "A_REAL/DNET")) {
+ sh->codec = "ac3";
+ } else {
+ goto error;
}
track->audio_buf =
talloc_array_size(track, track->sub_packet_h, track->audiopk_size);
track->audio_timestamp =
talloc_array(track, double, track->sub_packet_h);
+ } else if (!strncmp(track->codec_id, "A_AAC/", 6)) {
+ sh->codec = "aac";
+
+ /* Recreate the 'private data' (not needed for plain A_AAC) */
+ int srate_idx = aac_get_sample_rate_index(track->a_sfreq);
+ const char *tail = "";
+ if (strlen(track->codec_id) >= 12)
+ tail = &track->codec_id[12];
+ int profile = 3;
+ if (!strncmp(tail, "MAIN", 4))
+ profile = 0;
+ else if (!strncmp(tail, "LC", 2))
+ profile = 1;
+ else if (!strncmp(tail, "SSR", 3))
+ profile = 2;
+ extradata = talloc_size(sh_a, 5);
+ extradata[0] = ((profile + 1) << 3) | ((srate_idx & 0xE) >> 1);
+ extradata[1] = ((srate_idx & 0x1) << 7) | (track->a_channels << 3);
+
+ if (strstr(track->codec_id, "SBR") != NULL) {
+ /* HE-AAC (aka SBR AAC) */
+ extradata_len = 5;
+
+ srate_idx = aac_get_sample_rate_index(sh_a->samplerate);
+ extradata[2] = AAC_SYNC_EXTENSION_TYPE >> 3;
+ extradata[3] = ((AAC_SYNC_EXTENSION_TYPE & 0x07) << 5) | 5;
+ extradata[4] = (1 << 7) | (srate_idx << 3);
+ track->default_duration = 1024.0 / (sh_a->samplerate / 2);
+ } else {
+ extradata_len = 2;
+ track->default_duration = 1024.0 / sh_a->samplerate;
+ }
+ } else if (!strncmp(track->codec_id, "A_AC3/", 6)) {
+ sh->codec = "ac3";
+ } else if (!strncmp(track->codec_id, "A_EAC3/", 7)) {
+ sh->codec = "eac3";
+ }
+
+ if (!sh->codec)
+ goto error;
- } else if (!strcmp(track->codec_id, MKV_A_FLAC)
- || (track->a_formattag == 0xf1ac)) {
- sh_a->bits_per_coded_sample = 0;
- sh_a->bitrate = 0;
- sh_a->block_align = 0;
+ mp_chmap_set_unknown(&sh_a->channels, track->a_channels);
- if (track->ms_compat)
- sh->format = MP_FOURCC('f', 'L', 'a', 'C');
+ const char *codec = sh->codec;
+ if (!strcmp(codec, "mp3") || !strcmp(codec, "truehd")) {
+ track->parse = true;
+ } else if (!strcmp(codec, "flac")) {
unsigned char *ptr = extradata;
unsigned int size = extradata_len;
if (size < 4 || ptr[0] != 'f' || ptr[1] != 'L' || ptr[2] != 'a'
|| ptr[3] != 'C') {
- sh_a->codecdata = talloc_size(sh_a, 4);
- sh_a->codecdata_len = 4;
- memcpy(sh_a->codecdata, "fLaC", 4);
- } else {
- sh_a->codecdata = talloc_size(sh_a, size);
- sh_a->codecdata_len = size;
- memcpy(sh_a->codecdata, ptr, size);
+ extradata = talloc_size(sh_a, 4);
+ extradata_len = 4;
+ memcpy(extradata, "fLaC", 4);
}
- } else if (!strcmp(track->codec_id, MKV_A_ALAC)) {
+ parse_flac_chmap(&sh_a->channels, extradata, extradata_len);
+ } else if (!strcmp(codec, "alac")) {
if (track->private_size) {
- sh_a->codecdata_len = track->private_size + 12;
- sh_a->codecdata = talloc_size(sh_a, sh_a->codecdata_len);
- char *data = sh_a->codecdata;
- AV_WB32(data + 0, sh_a->codecdata_len);
+ extradata_len = track->private_size + 12;
+ extradata = talloc_size(sh_a, extradata_len);
+ char *data = extradata;
+ AV_WB32(data + 0, extradata_len);
memcpy(data + 4, "alac", 4);
AV_WB32(data + 8, 0);
memcpy(data + 12, track->private_data, track->private_size);
}
- } else if (track->a_formattag == MP_FOURCC('W', 'V', 'P', 'K')) {
- /* ok */
- } else if (track->a_formattag == MP_FOURCC('T', 'R', 'H', 'D')) {
- track->parse = true;
- } else if (track->a_formattag == MP_FOURCC('T', 'T', 'A', '1')) {
- sh_a->codecdata_len = 30;
- sh_a->codecdata = talloc_zero_size(sh_a, sh_a->codecdata_len);
- if (!sh_a->codecdata)
+ } else if (!strcmp(codec, "tta")) {
+ extradata_len = 30;
+ extradata = talloc_zero_size(sh_a, extradata_len);
+ if (!extradata)
goto error;
- char *data = sh_a->codecdata;
+ char *data = extradata;
memcpy(data + 0, "TTA1", 4);
AV_WL16(data + 4, 1);
AV_WL16(data + 6, sh_a->channels.num);
@@ -1610,22 +1646,16 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
AV_WL32(data + 10, track->a_osfreq);
// Bogus: last frame won't be played.
AV_WL32(data + 14, 0);
- } else if (!track->ms_compat) {
- goto error;
}
// Some files have broken default DefaultDuration set, which will lead to
// audio packets with incorrect timestamps. This follows FFmpeg commit
// 6158a3b, sample see FFmpeg ticket 2508.
- if (sh_a->samplerate == 8000 && strcmp(track->codec_id, MKV_A_AC3) == 0)
+ if (sh_a->samplerate == 8000 && strcmp(codec, "ac3") == 0)
track->default_duration = 0;
- mp_set_codec_from_tag(sh);
-
- if (!sh_a->codecdata && extradata_len) {
- sh_a->codecdata = talloc_memdup(sh_a, extradata, extradata_len);
- sh_a->codecdata_len = extradata_len;
- }
+ sh->extradata = extradata;
+ sh->extradata_size = extradata_len;
return 0;
@@ -1637,17 +1667,17 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
}
static const char *const mkv_sub_tag[][2] = {
- { MKV_S_VOBSUB, "dvd_subtitle" },
- { MKV_S_TEXTSSA, "ass"},
- { MKV_S_TEXTASS, "ass"},
- { MKV_S_SSA, "ass"},
- { MKV_S_ASS, "ass"},
- { MKV_S_TEXTASCII, "subrip"},
- { MKV_S_TEXTUTF8, "subrip"},
- { MKV_S_PGS, "hdmv_pgs_subtitle"},
- { MKV_S_WEBVTT_S, "webvtt-webm"},
- { MKV_S_WEBVTT_C, "webvtt-webm"},
- { MKV_S_DVB, "dvb_subtitle"},
+ { "S_VOBSUB", "dvd_subtitle" },
+ { "S_TEXT/SSA", "ass"},
+ { "S_TEXT/ASS", "ass"},
+ { "S_SSA", "ass"},
+ { "S_ASS", "ass"},
+ { "S_TEXT/ASCII", "subrip"},
+ { "S_TEXT/UTF8", "subrip"},
+ { "S_HDMV/PGS", "hdmv_pgs_subtitle"},
+ { "D_WEBVTT/SUBTITLES", "webvtt-webm"},
+ { "D_WEBVTT/CAPTIONS", "webvtt-webm"},
+ { "S_DVBSUB", "dvb_subtitle"},
{0}
};
@@ -1667,9 +1697,8 @@ static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track)
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_SUB);
if (!sh)
return 1;
- track->stream = sh;
- sh_sub_t *sh_s = sh->sub;
- sh->demuxer_id = track->tnum;
+ init_track(demuxer, track, sh);
+
sh->codec = subtitle_type;
bstr in = (bstr){track->private_data, track->private_size};
bstr buffer = demux_mkv_decode(demuxer->log, track, in, 2);
@@ -1679,13 +1708,8 @@ static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track)
track->private_data = buffer.start;
track->private_size = buffer.len;
}
- sh_s->extradata = talloc_size(sh, track->private_size);
- memcpy(sh_s->extradata, track->private_data, track->private_size);
- sh_s->extradata_len = track->private_size;
- if (track->language && (strcmp(track->language, "und") != 0))
- sh->lang = talloc_strdup(sh, track->language);
- sh->title = talloc_strdup(sh, track->name);
- sh->default_track = track->default_track;
+ sh->extradata = track->private_data;
+ sh->extradata_size = track->private_size;
if (!subtitle_type)
MP_ERR(demuxer, "Subtitle type '%s' is not supported.\n", track->codec_id);
@@ -1699,15 +1723,15 @@ static int read_ebml_header(demuxer_t *demuxer)
if (ebml_read_id(s) != EBML_ID_EBML)
return 0;
- struct ebml_ebml ebml_master = {{0}};
+ struct ebml_ebml ebml_master = {0};
struct ebml_parse_ctx parse_ctx = { demuxer->log, .no_error_messages = true };
if (ebml_read_element(s, &parse_ctx, &ebml_master, &ebml_ebml_desc) < 0)
return 0;
- if (ebml_master.doc_type.start == NULL) {
+ if (!ebml_master.doc_type) {
MP_VERBOSE(demuxer, "File has EBML header but no doctype."
" Assuming \"matroska\".\n");
- } else if (bstrcmp(ebml_master.doc_type, bstr0("matroska")) != 0
- && bstrcmp(ebml_master.doc_type, bstr0("webm")) != 0) {
+ } else if (strcmp(ebml_master.doc_type, "matroska") != 0
+ && strcmp(ebml_master.doc_type, "webm") != 0) {
MP_DBG(demuxer, "no head found\n");
talloc_free(parse_ctx.talloc_ctx);
return 0;
@@ -1757,9 +1781,7 @@ static int read_mkv_segment_header(demuxer_t *demuxer, int64_t *segment_end)
MP_VERBOSE(demuxer, " (skipping)\n");
if (*segment_end <= 0)
break;
- int64_t end = 0;
- stream_control(s, STREAM_CTRL_GET_SIZE, &end);
- if (*segment_end >= end)
+ if (*segment_end >= stream_get_size(s))
return 0;
if (!stream_seek(s, *segment_end)) {
MP_WARN(demuxer, "Failed to seek in file\n");
@@ -1823,8 +1845,7 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
return -1;
}
- int64_t end = 0;
- stream_control(s, STREAM_CTRL_GET_SIZE, &end);
+ int64_t end = stream_get_size(s);
// Read headers that come after the first cluster (i.e. require seeking).
// Note: reading might increase ->num_headers.
@@ -1972,11 +1993,11 @@ static bool handle_realaudio(demuxer_t *demuxer, mkv_track_t *track,
// track->audio_buf allocation size
size_t audiobuf_size = sph * w;
- if (!track->audio_buf || !track->audio_timestamp)
+ if (!track->audio_buf || !track->audio_timestamp || !track->stream)
return false;
- switch (track->a_formattag) {
- case MP_FOURCC('2', '8', '_', '8'):
+ const char *codec = track->stream->codec ? track->stream->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;
if (dst_offset + cfs > audiobuf_size)
@@ -1986,9 +2007,7 @@ static bool handle_realaudio(demuxer_t *demuxer, mkv_track_t *track,
goto error;
memcpy(track->audio_buf + dst_offset, buffer + src_offset, cfs);
}
- break;
- case MP_FOURCC('c', 'o', 'o', 'k'):
- case MP_FOURCC('a', 't', 'r', 'c'):
+ } else if (!strcmp(codec, "cook") || !strcmp(codec, "atrac3")) {
for (int x = 0; x < w / sps; x++) {
uint32_t dst_offset =
sps * (sph * x + ((sph + 1) / 2) * (spc & 1) + (spc >> 1));
@@ -1999,8 +2018,7 @@ static bool handle_realaudio(demuxer_t *demuxer, mkv_track_t *track,
goto error;
memcpy(track->audio_buf + dst_offset, buffer + src_offset, sps);
}
- break;
- case MP_FOURCC('s', 'i', 'p', 'r'):
+ } else if (!strcmp(codec, "sipr")) {
if (spc * w + w > audiobuf_size || w > size)
goto error;
memcpy(track->audio_buf + spc * w, buffer, w);
@@ -2026,8 +2044,7 @@ static bool handle_realaudio(demuxer_t *demuxer, mkv_track_t *track,
}
}
}
- break;
- default:
+ } else {
// Not a codec that requires reordering
return false;
}
@@ -2172,7 +2189,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 (track->a_formattag == MP_FOURCC('W', 'V', 'P', 'K')) {
+ if (stream->codec && strcmp(stream->codec, "wavpack") == 0) {
int size = dp->len;
uint8_t *parsed;
if (libav_parse_wavpack(track, dp->buffer, &parsed, &size) >= 0) {
@@ -2186,7 +2203,7 @@ static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
}
}
- if (track->codec_id && strcmp(track->codec_id, MKV_V_PRORES) == 0) {
+ if (stream->codec && strcmp(stream->codec, "prores") == 0) {
size_t newlen = dp->len + 8;
struct demux_packet *new = new_demux_packet(newlen);
if (new) {
@@ -2416,7 +2433,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 (track->ms_compat)
+ if (stream->video && stream->video->avi_dts)
MPSWAP(double, dp->pts, dp->dts);
if (i == 0)
dp->duration = block_duration / 1e9;
@@ -2793,9 +2810,7 @@ static void demux_mkv_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
read_deferred_cues(demuxer);
- int64_t size = 0;
- stream_control(s, STREAM_CTRL_GET_SIZE, &size);
-
+ int64_t size = stream_get_size(s);
int64_t target_filepos = size * MPCLAMP(rel_seek_secs, 0, 1);
mkv_index_t *index = NULL;
@@ -2817,7 +2832,7 @@ static void demux_mkv_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
stream_seek(s, index->filepos);
mkv_d->skip_to_timecode = index->timecode * mkv_d->tc_scale;
} else {
- stream_seek(s, target_filepos);
+ stream_seek(s, MPMAX(target_filepos, 0));
if (ebml_resync_cluster(mp_null_log, s) < 0) {
// Assume EOF
mkv_d->cluster_end = size;
@@ -2851,23 +2866,31 @@ static void probe_last_timestamp(struct demuxer *demuxer)
if (v_tnum < 0)
return;
- read_deferred_cues(demuxer);
-
- if (!mkv_d->index_complete)
- return;
+ // In full mode, we start reading data from the current file position,
+ // which works because this function is called after headers are parsed.
+ if (demuxer->opts->demux_mkv->probe_duration != 2) {
+ read_deferred_cues(demuxer);
+ if (mkv_d->index_complete) {
+ // Find last cluster that still has video packets
+ int64_t target = 0;
+ for (size_t i = 0; i < mkv_d->num_indexes; i++) {
+ struct mkv_index *cur = &mkv_d->indexes[i];
+ if (cur->tnum == v_tnum)
+ target = MPMAX(target, cur->filepos);
+ }
+ if (!target)
+ return;
- // Find last cluster that still has video packets
- int64_t target = 0;
- for (size_t i = 0; i < mkv_d->num_indexes; i++) {
- struct mkv_index *cur = &mkv_d->indexes[i];
- if (cur->tnum == v_tnum)
- target = MPMAX(target, cur->filepos);
+ if (!stream_seek(demuxer->stream, target))
+ return;
+ } else {
+ // No index -> just try to find a random cluster towards file end.
+ int64_t size = stream_get_size(demuxer->stream);
+ stream_seek(demuxer->stream, MPMAX(size - 10 * 1024 * 1024, 0));
+ if (ebml_resync_cluster(mp_null_log, demuxer->stream) < 0)
+ stream_seek(demuxer->stream, old_pos); // full scan otherwise
+ }
}
- if (!target)
- return;
-
- if (!stream_seek(demuxer->stream, target))
- return;
int64_t last_ts[STREAM_TYPE_COUNT] = {0};
while (1) {
@@ -2886,6 +2909,9 @@ static void probe_last_timestamp(struct demuxer *demuxer)
}
}
+ if (!last_ts[STREAM_VIDEO])
+ last_ts[STREAM_VIDEO] = mkv_d->cluster_tc;
+
if (last_ts[STREAM_VIDEO])
mkv_d->duration = last_ts[STREAM_VIDEO] / 1e9;
diff --git a/demux/demux_mkv_timeline.c b/demux/demux_mkv_timeline.c
index c7310a6..a874f9c 100644
--- a/demux/demux_mkv_timeline.c
+++ b/demux/demux_mkv_timeline.c
@@ -118,7 +118,7 @@ static char **find_files(const char *original_file)
if (!strcmp(ep->d_name, basename))
continue;
- char *name = mp_path_join(results, directory, bstr0(ep->d_name));
+ char *name = mp_path_join_bstr(results, directory, bstr0(ep->d_name));
char *s1 = ep->d_name;
char *s2 = basename;
int matchlen = 0;
@@ -372,7 +372,8 @@ static void build_timeline_loop(struct tl_ctx *ctx,
break; // malformed files can cause this to happen.
chapters[i].pts = ctx->start_time / 1e9;
- chapters[i].name = talloc_strdup(chapters, c->name);
+ chapters[i].metadata = talloc_zero(chapters, struct mp_tags);
+ mp_tags_set_str(chapters[i].metadata, "title", c->name);
}
/* If we're the source or it's a non-ordered edition reference,
@@ -556,9 +557,6 @@ void build_ordered_chapter_timeline(struct timeline *tl)
struct demux_chapter *chapters =
talloc_zero_array(tl, struct demux_chapter, m->num_ordered_chapters);
- // Stupid hack, because fuck everything.
- for (int n = 0; n < m->num_ordered_chapters; n++)
- chapters[n].pts = -1;
ctx->timeline = talloc_array_ptrtype(tl, ctx->timeline, 0);
ctx->num_chapters = m->num_ordered_chapters;
@@ -569,9 +567,9 @@ void build_ordered_chapter_timeline(struct timeline *tl)
};
build_timeline_loop(ctx, chapters, &info, 0);
- // Fuck everything (2): filter out all "unset" chapters.
+ // Fuck everything: filter out all "unset" chapters.
for (int n = m->num_ordered_chapters - 1; n >= 0; n--) {
- if (chapters[n].pts == -1)
+ if (!chapters[n].metadata)
MP_TARRAY_REMOVE_AT(chapters, m->num_ordered_chapters, n);
}
diff --git a/demux/demux_playlist.c b/demux/demux_playlist.c
index 6f73bea..25ad22b 100644
--- a/demux/demux_playlist.c
+++ b/demux/demux_playlist.c
@@ -35,7 +35,7 @@ static bool check_mimetype(struct stream *s, const char *const *list)
{
if (s->mime_type) {
for (int n = 0; list && list[n]; n++) {
- if (strcmp(s->mime_type, list[n]) == 0)
+ if (strcasecmp(s->mime_type, list[n]) == 0)
return true;
}
}
@@ -54,6 +54,7 @@ struct pl_parser {
bool add_base;
enum demux_check check_level;
struct stream *real_stream;
+ char *format;
};
static char *pl_get_line0(struct pl_parser *p)
@@ -112,11 +113,27 @@ static int parse_m3u(struct pl_parser *p)
ok:
if (p->probing)
return 0;
+ char *title = NULL;
while (line.len || !pl_eof(p)) {
- if (line.len > 0 && !bstr_startswith0(line, "#"))
- pl_add(p, line);
+ if (bstr_eatstart0(&line, "#EXTINF:")) {
+ bstr duration, btitle;
+ if (bstr_split_tok(line, ",", &duration, &btitle) && btitle.len) {
+ talloc_free(title);
+ title = bstrto0(NULL, btitle);
+ }
+ } else if (bstr_startswith0(line, "#EXT-X-")) {
+ p->format = "hls";
+ } else if (line.len > 0 && !bstr_startswith0(line, "#")) {
+ char *fn = bstrto0(NULL, line);
+ struct playlist_entry *e = playlist_entry_new(fn);
+ talloc_free(fn);
+ e->title = talloc_steal(e, title);
+ title = NULL;
+ playlist_add(p->pl, e);
+ }
line = bstr_strip(pl_get_line(p));
}
+ talloc_free(title);
return 0;
}
@@ -236,7 +253,7 @@ static int parse_dir(struct pl_parser *p)
struct dirent *ep;
while ((ep = readdir(dp))) {
- if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0)
+ if (ep->d_name[0] == '.')
continue;
MP_TARRAY_APPEND(p, files, num_files, talloc_strdup(p, ep->d_name));
}
@@ -245,7 +262,7 @@ static int parse_dir(struct pl_parser *p)
qsort(files, num_files, sizeof(files[0]), cmp_filename);
for (int n = 0; n < num_files; n++)
- playlist_add_file(p->pl, mp_path_join(p, bstr0(path), bstr0(files[n])));
+ playlist_add_file(p->pl, mp_path_join(p, path, files[n]));
closedir(dp);
@@ -324,7 +341,7 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
if (p->add_base)
playlist_add_base_path(p->pl, mp_dirname(demuxer->filename));
demuxer->playlist = talloc_steal(demuxer, p->pl);
- demuxer->filetype = fmt->name;
+ demuxer->filetype = p->format ? p->format : fmt->name;
demuxer->fully_read = true;
talloc_free(p);
return ok ? 0 : -1;
diff --git a/demux/demux_raw.c b/demux/demux_raw.c
index 65d6b75..a672877 100644
--- a/demux/demux_raw.c
+++ b/demux/demux_raw.c
@@ -222,7 +222,7 @@ static int demux_rawvideo_open(demuxer_t *demuxer, enum demux_check check)
sh = new_sh_stream(demuxer, STREAM_VIDEO);
sh_video = sh->video;
sh->codec = decoder;
- sh->format = imgfmt;
+ sh->codec_tag = imgfmt;
sh_video->fps = opts->fps;
sh_video->disp_w = width;
sh_video->disp_h = height;
diff --git a/demux/demux_subreader.c b/demux/demux_subreader.c
index 55a8542..87a5e9a 100644
--- a/demux/demux_subreader.c
+++ b/demux/demux_subreader.c
@@ -292,7 +292,7 @@ static subtitle *sub_read_line_subviewer(stream_t *st, subtitle *current,
current->start = a1 * 360000 + a2 * 6000 + a3 * 100 + a4 / 10;
current->end = b1 * 360000 + b2 * 6000 + b3 * 100 + b4 / 10;
- /* Concat lines */
+ /* Concatenate lines */
full_line[0] = 0;
for (i = 0; i < SUB_MAX_TEXT; i++) {
int blank = 1, len = 0;
diff --git a/demux/demux_tv.c b/demux/demux_tv.c
index 198ca92..ab58487 100644
--- a/demux/demux_tv.c
+++ b/demux/demux_tv.c
@@ -60,7 +60,7 @@ static int demux_open_tv(demuxer_t *demuxer, enum demux_check check)
sh_v->codec = "mjpeg";
} else {
sh_v->codec = "rawvideo";
- sh_v->format = fourcc;
+ sh_v->codec_tag = fourcc;
}
/* set FPS and FRAMETIME */
diff --git a/demux/ebml.c b/demux/ebml.c
index d75b61e..dbdc5e2 100644
--- a/demux/ebml.c
+++ b/demux/ebml.c
@@ -158,7 +158,7 @@ uint64_t ebml_read_uint(stream_t *s)
uint64_t len, value = 0;
len = ebml_read_length(s);
- if (len == EBML_UINT_INVALID || len < 1 || len > 8)
+ if (len == EBML_UINT_INVALID || len > 8)
return EBML_UINT_INVALID;
while (len--)
@@ -177,8 +177,10 @@ int64_t ebml_read_int(stream_t *s)
int l;
len = ebml_read_length(s);
- if (len == EBML_UINT_INVALID || len < 1 || len > 8)
+ if (len == EBML_UINT_INVALID || len > 8)
return EBML_INT_INVALID;
+ if (!len)
+ return 0;
len--;
l = stream_read_char(s);
@@ -318,7 +320,7 @@ static uint64_t ebml_parse_length(uint8_t *data, size_t data_len, int *length)
static uint64_t ebml_parse_uint(uint8_t *data, int length)
{
- assert(length >= 1 && length <= 8);
+ assert(length >= 0 && length <= 8);
uint64_t r = 0;
while (length--)
r = (r << 8) + *data++;
@@ -327,7 +329,9 @@ static uint64_t ebml_parse_uint(uint8_t *data, int length)
static int64_t ebml_parse_sint(uint8_t *data, int length)
{
- assert(length >=1 && length <= 8);
+ assert(length >= 0 && length <= 8);
+ if (!length)
+ return 0;
uint64_t r = 0;
if (*data & 0x80)
r = -1;
@@ -338,7 +342,7 @@ static int64_t ebml_parse_sint(uint8_t *data, int length)
static double ebml_parse_float(uint8_t *data, int length)
{
- assert(length == 4 || length == 8);
+ assert(length == 0 || length == 4 || length == 8);
uint64_t i = ebml_parse_uint(data, length);
if (length == 4)
return av_int2float(i);
@@ -415,7 +419,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
break;
}
- for (int i = 0; i < type->field_count; i++)
+ for (int i = 0; i < type->field_count; i++) {
if (num_elems[i] && type->fields[i].multiple) {
char *ptr = s + type->fields[i].offset;
switch (type->fields[i].desc->type) {
@@ -442,6 +446,9 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
double, num_elems[i]);
break;
case EBML_TYPE_STR:
+ *(char ***) ptr = talloc_zero_array(ctx->talloc_ctx,
+ char *, num_elems[i]);
+ break;
case EBML_TYPE_BINARY:
*(struct bstr **) ptr = talloc_zero_array(ctx->talloc_ctx,
struct bstr,
@@ -455,6 +462,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
abort();
}
}
+ }
while (data < end) {
int len;
@@ -550,7 +558,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
case EBML_TYPE_SINT:;
int64_t *sintptr;
GETPTR(sintptr, int64_t);
- if (length < 1 || length > 8) {
+ if (length > 8) {
MP_DBG(ctx, "sint invalid length %"PRIu64"\n", length);
goto error;
}
@@ -561,7 +569,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
case EBML_TYPE_FLOAT:;
double *floatptr;
GETPTR(floatptr, double);
- if (length != 4 && length != 8) {
+ if (length != 0 && length != 4 && length != 8) {
MP_DBG(ctx, "float invalid length %"PRIu64"\n", length);
goto error;
}
@@ -570,19 +578,26 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
break;
case EBML_TYPE_STR:
+ if (length > 1024 * 1024) {
+ MP_ERR(ctx, "Not reading overly long string element.\n");
+ break;
+ }
+ char **strptr;
+ GETPTR(strptr, char *);
+ *strptr = talloc_strndup(ctx->talloc_ctx, data, length);
+ MP_DBG(ctx, "string \"%s\"\n", *strptr);
+ break;
+
case EBML_TYPE_BINARY:;
if (length > 0x80000000) {
MP_ERR(ctx, "Not reading overly long EBML element.\n");
break;
}
- struct bstr *strptr;
- GETPTR(strptr, struct bstr);
- strptr->start = data;
- strptr->len = length;
- if (ed->type == EBML_TYPE_STR)
- MP_DBG(ctx, "string \"%.*s\"\n", BSTR_P(*strptr));
- else
- MP_DBG(ctx, "binary %zd bytes\n", strptr->len);
+ struct bstr *binptr;
+ GETPTR(binptr, struct bstr);
+ binptr->start = data;
+ binptr->len = length;
+ MP_DBG(ctx, "binary %zd bytes\n", binptr->len);
break;
case EBML_TYPE_EBML_ID:;
@@ -616,6 +631,10 @@ int ebml_read_element(struct stream *s, struct ebml_parse_ctx *ctx,
"- partial or corrupt file?\n");
return -1;
}
+ if (length == EBML_UINT_INVALID) {
+ MP_MSG(ctx, msglevel, "EBML element with unknown length - unsupported\n");
+ return -1;
+ }
if (length > 1000000000) {
MP_MSG(ctx, msglevel, "Refusing to read element over 100 MB in size\n");
return -1;
diff --git a/demux/matroska.h b/demux/matroska.h
index fce4e6a..169ee7f 100644
--- a/demux/matroska.h
+++ b/demux/matroska.h
@@ -1,8 +1,4 @@
/*
- * CodecID definitions for Matroska files
- *
- * see http://www.matroska.org/technical/specs/codecid/index.html
- *
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or modify
@@ -25,76 +21,4 @@
struct timeline;
void build_ordered_chapter_timeline(struct timeline *tl);
-#define MKV_A_AAC_2MAIN "A_AAC/MPEG2/MAIN"
-#define MKV_A_AAC_2LC "A_AAC/MPEG2/LC"
-#define MKV_A_AAC_2SBR "A_AAC/MPEG2/LC/SBR"
-#define MKV_A_AAC_2SSR "A_AAC/MPEG2/SSR"
-#define MKV_A_AAC_4MAIN "A_AAC/MPEG4/MAIN"
-#define MKV_A_AAC_4LC "A_AAC/MPEG4/LC"
-#define MKV_A_AAC_4SBR "A_AAC/MPEG4/LC/SBR"
-#define MKV_A_AAC_4SSR "A_AAC/MPEG4/SSR"
-#define MKV_A_AAC_4LTP "A_AAC/MPEG4/LTP"
-#define MKV_A_AAC "A_AAC"
-#define MKV_A_AC3 "A_AC3"
-#define MKV_A_DTS "A_DTS"
-#define MKV_A_EAC3 "A_EAC3"
-#define MKV_A_MP2 "A_MPEG/L2"
-#define MKV_A_MP3 "A_MPEG/L3"
-#define MKV_A_PCM "A_PCM/INT/LIT"
-#define MKV_A_PCM_BE "A_PCM/INT/BIG"
-#define MKV_A_PCM_FLT "A_PCM/FLOAT/IEEE"
-#define MKV_A_VORBIS "A_VORBIS"
-#define MKV_A_OPUS "A_OPUS"
-#define MKV_A_OPUS_EXP "A_OPUS/EXPERIMENTAL"
-#define MKV_A_ACM "A_MS/ACM"
-#define MKV_A_REAL28 "A_REAL/28_8"
-#define MKV_A_REALATRC "A_REAL/ATRC"
-#define MKV_A_REALCOOK "A_REAL/COOK"
-#define MKV_A_REALDNET "A_REAL/DNET"
-#define MKV_A_REALSIPR "A_REAL/SIPR"
-#define MKV_A_QDMC "A_QUICKTIME/QDMC"
-#define MKV_A_QDMC2 "A_QUICKTIME/QDM2"
-#define MKV_A_FLAC "A_FLAC"
-#define MKV_A_ALAC "A_ALAC"
-#define MKV_A_TTA1 "A_TTA1"
-#define MKV_A_WAVPACK "A_WAVPACK4"
-#define MKV_A_TRUEHD "A_TRUEHD"
-
-#define MKV_V_MSCOMP "V_MS/VFW/FOURCC"
-#define MKV_V_REALV10 "V_REAL/RV10"
-#define MKV_V_REALV20 "V_REAL/RV20"
-#define MKV_V_REALV30 "V_REAL/RV30"
-#define MKV_V_REALV40 "V_REAL/RV40"
-#define MKV_V_SORENSONV1 "V_SORENSON/V1"
-#define MKV_V_SORENSONV2 "V_SORENSON/V2"
-#define MKV_V_SORENSONV3 "V_SORENSON/V3"
-#define MKV_V_CINEPAK "V_CINEPAK"
-#define MKV_V_QUICKTIME "V_QUICKTIME"
-#define MKV_V_MPEG1 "V_MPEG1"
-#define MKV_V_MPEG2 "V_MPEG2"
-#define MKV_V_MPEG4_SP "V_MPEG4/ISO/SP"
-#define MKV_V_MPEG4_ASP "V_MPEG4/ISO/ASP"
-#define MKV_V_MPEG4_AP "V_MPEG4/ISO/AP"
-#define MKV_V_MPEG4_AVC "V_MPEG4/ISO/AVC"
-#define MKV_V_THEORA "V_THEORA"
-#define MKV_V_VP8 "V_VP8"
-#define MKV_V_VP9 "V_VP9"
-#define MKV_V_MJPEG "V_MJPEG"
-#define MKV_V_UNCOMPRESSED "V_UNCOMPRESSED"
-#define MKV_V_DIRAC "V_DIRAC"
-#define MKV_V_PRORES "V_PRORES"
-#define MKV_V_HEVC "V_MPEGH/ISO/HEVC"
-
-#define MKV_S_TEXTASCII "S_TEXT/ASCII"
-#define MKV_S_TEXTUTF8 "S_TEXT/UTF8"
-#define MKV_S_TEXTSSA "S_TEXT/SSA"
-#define MKV_S_TEXTASS "S_TEXT/ASS"
-#define MKV_S_VOBSUB "S_VOBSUB"
-#define MKV_S_PGS "S_HDMV/PGS"
-#define MKV_S_SSA "S_SSA" // Deprecated
-#define MKV_S_ASS "S_ASS" // Deprecated
-#define MKV_S_WEBVTT_S "D_WEBVTT/SUBTITLES"
-#define MKV_S_WEBVTT_C "D_WEBVTT/CAPTIONS"
-#define MKV_S_DVB "S_DVBSUB"
-
#endif /* MPLAYER_MATROSKA_H */
diff --git a/demux/stheader.h b/demux/stheader.h
index 444f2f3..a615867 100644
--- a/demux/stheader.h
+++ b/demux/stheader.h
@@ -47,7 +47,10 @@ struct sh_stream {
const char *codec;
// Usually a FourCC, exact meaning depends on codec.
- unsigned int format;
+ 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;
@@ -55,6 +58,7 @@ struct sh_stream {
char *title;
char *lang; // language code
bool default_track; // container default track flag
+ bool forced_track; // container forced track flag
int hls_bitrate;
bool missing_timestamps;
@@ -73,8 +77,6 @@ typedef struct sh_audio {
int bitrate; // compressed bits/sec
int block_align;
int bits_per_coded_sample;
- unsigned char *codecdata;
- int codecdata_len;
struct replaygain_data *replaygain_data;
} sh_audio_t;
@@ -83,16 +85,12 @@ typedef struct sh_video {
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;
- unsigned char *extradata;
- int extradata_len;
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 {
- unsigned char *extradata; // extra header data passed from demuxer
- int extradata_len;
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
diff --git a/etc/example.conf b/etc/example.conf
index e335c8e..4c545d9 100644
--- a/etc/example.conf
+++ b/etc/example.conf
@@ -61,8 +61,9 @@
# Output 5.1 audio natively, and upmix/downmix audio with a different format.
#audio-channels=5.1
# Disable any automatic remix, _if_ the audio output accepts the audio format.
-# of the currently played file.
-#audio-channels=empty
+# of the currently played file. See caveats mentioned in the manpage.
+# (This is the default.)
+#audio-channels=auto
##################
# other settings #
diff --git a/etc/input.conf b/etc/input.conf
index 73be521..d9c9958 100644
--- a/etc/input.conf
+++ b/etc/input.conf
@@ -12,7 +12,7 @@
# Use 'ignore' to unbind a key fully (e.g. 'ctrl+a ignore').
#
# Strings need to be quoted and escaped:
-# KEY show_text "This is a single backslash: \\ and a quote: \" !"
+# KEY show-text "This is a single backslash: \\ and a quote: \" !"
#
# You can use modifier-key combinations like Shift+Left or Ctrl+Alt+x with
# the modifiers Shift, Ctrl, Alt and Meta (may not work on the terminal).
@@ -33,8 +33,8 @@
#MOUSE_BTN2 cycle pause # toggle pause on/off
#MOUSE_BTN3 seek 10
#MOUSE_BTN4 seek -10
-#MOUSE_BTN5 add volume 2
-#MOUSE_BTN6 add volume -2
+#MOUSE_BTN5 add volume -2
+#MOUSE_BTN6 add volume 2
# Mouse wheels, touchpad or other input devices that have axes
# if the input devices supports precise scrolling it will also scale the
@@ -56,8 +56,8 @@
#Shift+UP no-osd seek 5 exact
#Shift+DOWN no-osd seek -5 exact
# Skip to previous/next subtitle (subject to some restrictions; see manpage)
-#Ctrl+LEFT no-osd sub_seek -1
-#Ctrl+RIGHT no-osd sub_seek 1
+#Ctrl+LEFT no-osd sub-seek -1
+#Ctrl+RIGHT no-osd sub-seek 1
#PGUP add chapter 1 # skip to next chapter
#PGDWN add chapter -1 # skip to previous chapter
#Shift+PGUP seek 600
@@ -68,21 +68,21 @@
#} multiply speed 2.0
#BS set speed 1.0 # reset speed to normal
#q quit
-#Q quit_watch_later
-#q {encode} quit
+#Q quit-watch-later
+#q {encode} quit 4
#ESC set fullscreen no
-#ESC {encode} quit
+#ESC {encode} quit 4
#p cycle pause # toggle pause/playback mode
-#. frame_step # advance one frame and pause
-#, frame_back_step # go back by one frame and pause
+#. frame-step # advance one frame and pause
+#, frame-back-step # go back by one frame and pause
#SPACE cycle pause
-#> playlist_next # skip to next file
-#ENTER playlist_next # skip to next file
-#< playlist_prev # skip to previous file
-#O osd # cycle through OSD mode
-#o show_progress
-#P show_progress
-#I show_text "${filename}" # display filename in osd
+#> playlist-next # skip to next file
+#ENTER playlist-next # skip to next file
+#< playlist-prev # skip to previous file
+#O no-osd cycle_values osd-level 3 1 # cycle through OSD mode
+#o show-progress
+#P show-progress
+#I show-text "${filename}" # display filename in osd
#z add sub-delay -0.1 # subtract 100 ms delay from subs
#x add sub-delay +0.1 # add
#ctrl++ add audio-delay 0.100 # this changes audio/video sync
@@ -100,9 +100,8 @@
#6 add gamma 1
#7 add saturation -1
#8 add saturation 1
-#d cycle framedrop # cycle through framedrop modes
# toggle deinterlacer (automatically inserts or removes required filter)
-#D cycle deinterlace
+#d cycle deinterlace
#r add sub-pos -1 # move subtitles up
#t add sub-pos +1 # down
#v cycle sub-visibility
@@ -110,7 +109,7 @@
#V cycle ass-vsfilter-aspect-compat
# switch between applying no style overrides to SSA/ASS subtitles, and
# overriding them almost completely with the normal subtitle style
-#u cycle_values ass-style-override "force" "no"
+#u cycle-values ass-style-override "force" "no"
#j cycle sub # cycle through subtitles
#J cycle sub down # ...backwards
#SHARP cycle audio # switch audio streams
@@ -119,11 +118,12 @@
#f cycle fullscreen # toggle fullscreen
#s screenshot # take a screenshot
#S screenshot video # ...without subtitles
+#Ctrl+s screenshot window # ...with subtitles and OSD, and scaled
#Alt+s screenshot each-frame # automatically screenshot every frame
#w add panscan -0.1 # zoom out with -panscan 0 -fs
#e add panscan +0.1 # in
# cycle video aspect ratios; "-1" is the container aspect
-#A cycle_values video-aspect "16:9" "4:3" "2.35:1" "-1"
+#A cycle-values video-aspect "16:9" "4:3" "2.35:1" "-1"
#POWER quit
#PLAY cycle pause
#PAUSE cycle pause
@@ -131,16 +131,17 @@
#STOP quit
#FORWARD seek 60
#REWIND seek -60
-#NEXT playlist_next
-#PREV playlist_prev
+#NEXT playlist-next
+#PREV playlist-prev
#VOLUME_UP add volume 2
#VOLUME_DOWN add volume -2
#MUTE cycle mute
#CLOSE_WIN quit
-#CLOSE_WIN {encode} quit
+#CLOSE_WIN {encode} quit 4
#E cycle edition # next edition
#l ab_loop # Set/clear A-B loop points
-#ctrl+c quit
+#L cycle-values loop "inf" "no" # toggle infinite looping
+#ctrl+c quit 4
# Apple Remote section
#AR_PLAY cycle pause
@@ -151,27 +152,13 @@
#AR_NEXT_HOLD seek 120
#AR_PREV seek -10
#AR_PREV_HOLD seek -120
-#AR_MENU show_progress
+#AR_MENU show-progress
#AR_MENU_HOLD cycle mute
#AR_VUP add volume 2
#AR_VUP_HOLD add chapter 1
#AR_VDOWN add volume -2
#AR_VDOWN_HOLD add chapter -1
-# For dvdnav:// and bdnav://
-
-# navigation controls during playback
-#ENTER {discnav} discnav menu # DISCNAV MENU
-# BS {discnav} discnav prev # DISCNAV PREVIOUS menu (in the order chapter->title->root)
-# navigation controls when showing menu (additionally to the controls above)
-#UP {discnav-menu} discnav up # DISCNAV UP
-#DOWN {discnav-menu} discnav down # DISCNAV DOWN
-#LEFT {discnav-menu} discnav left # DISCNAV LEFT
-#RIGHT {discnav-menu} discnav right # DISCNAV RIGHT
-#ENTER {discnav-menu} discnav select # DISCNAV SELECT (ok)
-#MOUSE_BTN0 {discnav-menu} discnav mouse
-#MOUSE_MOVE {discnav-menu} discnav mouse_move
-
# For tv://
#h cycle tv-channel -1 # previous channel
#k cycle tv-channel +1 # next channel
@@ -189,9 +176,9 @@
# ? add sub-scale +0.1 # increase subtitle font size
# ? 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
+# ? 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
diff --git a/etc/mplayer-input.conf b/etc/mplayer-input.conf
index de60fdc..97eafae 100644
--- a/etc/mplayer-input.conf
+++ b/etc/mplayer-input.conf
@@ -93,31 +93,6 @@ AR_MENU_HOLD cycle mute
AR_VUP add volume 1
AR_VDOWN add volume -1
-##
-## DVD and Bluray menus
-##
-## Unlike MPlayer, input doesn't require blocking normal keys when no menu
-## is active. The "discnav-menu" input section is active only when a menu
-## is shown, while "discnav" is always active when a DVD/Bluray is played.
-##
-## The bindings were adjusted according to this.
-##
-
-UP {discnav-menu} discnav up # DVDNav UP
-DOWN {discnav-menu} discnav down # DVDNav DOWN
-LEFT {discnav-menu} discnav left # DVDNav LEFT
-RIGHT {discnav-menu} discnav right # DVDNav RIGHT
-ESC {discnav} discnav menu # DVDNav MENU
-ENTER {discnav-menu} discnav select # DVDNav SELECT (ok)
-BS {discnav-menu} discnav prev # DVDNav PREVIOUS menu (in the order chapter->title->root)
-
-AR_VUP {discnav-menu} discnav up # DVDNav UP
-AR_VDOWN {discnav-menu} discnav down # DVDNav DOWN
-AR_PREV {discnav-menu} discnav left # DVDNav LEFT
-AR_NEXT {discnav-menu} discnav right # DVDNav RIGHT
-AR_MENU {discnav} discnav menu # DVDNav MENU
-AR_PLAY {discnav-menu} discnav select # DVDNav SELECT (ok)
-
#? add chapter -1 # skip to previous dvd chapter
#? add chapter +1 # next
diff --git a/etc/mpv.desktop b/etc/mpv.desktop
index 9fe33a6..5504d65 100644
--- a/etc/mpv.desktop
+++ b/etc/mpv.desktop
@@ -3,7 +3,7 @@ Type=Application
Name=mpv Media Player
Name[ca]=Reproductor multimèdia mpv
Name[cs]=mpv přehrávač
-Name[pl]=mpv odtwarzacz multimedialny
+Name[pl]=Odtwarzacz mpv
Name[ru]=Проигрыватель mpv
Name[zh-CN]=mpv 媒体播放器
Name[zh-TW]=mpv 媒體播放器
diff --git a/etc/restore-old-bindings.conf b/etc/restore-old-bindings.conf
index ea0c664..e8499b5 100644
--- a/etc/restore-old-bindings.conf
+++ b/etc/restore-old-bindings.conf
@@ -9,6 +9,12 @@
#
# Older installations use ~/.mpv/input.conf instead.
+# changed in mpv 0.10.0
+
+O osd
+D cycle deinterlace
+d cycle framedrop
+
# changed in mpv 0.7.0
ENTER playlist_next force
diff --git a/input/cmd_list.c b/input/cmd_list.c
index acd8ae7..3640cce 100644
--- a/input/cmd_list.c
+++ b/input/cmd_list.c
@@ -63,6 +63,7 @@ const struct mp_cmd_def mp_cmds[] = {
OARG_FLAGS(4|0, ({"relative", 4|0}, {"-", 4|0},
{"absolute-percent", 4|1},
{"absolute", 4|2},
+ {"relative-percent", 4|3},
{"keyframes", 32|8},
{"exact", 32|16})),
// backwards compatibility only
@@ -72,37 +73,38 @@ const struct mp_cmd_def mp_cmds[] = {
},
.allow_auto_repeat = true,
},
- { MP_CMD_REVERT_SEEK, "revert_seek", {
+ { MP_CMD_REVERT_SEEK, "revert-seek", {
OARG_FLAGS(0, ({"mark", 1})),
}},
{ MP_CMD_QUIT, "quit", { OARG_INT(0) } },
- { MP_CMD_QUIT_WATCH_LATER, "quit_watch_later", { OARG_INT(0) } },
+ { MP_CMD_QUIT_WATCH_LATER, "quit-watch-later", { OARG_INT(0) } },
{ MP_CMD_STOP, "stop", },
- { MP_CMD_FRAME_STEP, "frame_step", .allow_auto_repeat = true,
+ { MP_CMD_FRAME_STEP, "frame-step", .allow_auto_repeat = true,
.on_updown = true },
- { MP_CMD_FRAME_BACK_STEP, "frame_back_step", .allow_auto_repeat = true },
- { MP_CMD_PLAYLIST_NEXT, "playlist_next", {
+ { MP_CMD_FRAME_BACK_STEP, "frame-back-step", .allow_auto_repeat = true },
+ { MP_CMD_PLAYLIST_NEXT, "playlist-next", {
OARG_CHOICE(0, ({"weak", 0},
{"force", 1})),
}},
- { MP_CMD_PLAYLIST_PREV, "playlist_prev", {
+ { MP_CMD_PLAYLIST_PREV, "playlist-prev", {
OARG_CHOICE(0, ({"weak", 0},
{"force", 1})),
}},
- { MP_CMD_SUB_STEP, "sub_step", { ARG_INT }, .allow_auto_repeat = true },
- { MP_CMD_SUB_SEEK, "sub_seek", { ARG_INT }, .allow_auto_repeat = true },
+ { MP_CMD_PLAYLIST_SHUFFLE, "playlist-shuffle", },
+ { MP_CMD_SUB_STEP, "sub-step", { ARG_INT }, .allow_auto_repeat = true },
+ { MP_CMD_SUB_SEEK, "sub-seek", { ARG_INT }, .allow_auto_repeat = true },
{ MP_CMD_OSD, "osd", { OARG_INT(-1) } },
- { MP_CMD_PRINT_TEXT, "print_text", { ARG_STRING }, .allow_auto_repeat = true },
- { MP_CMD_SHOW_TEXT, "show_text", { ARG_STRING, OARG_INT(-1), OARG_INT(0) },
+ { MP_CMD_PRINT_TEXT, "print-text", { ARG_STRING }, .allow_auto_repeat = true },
+ { MP_CMD_SHOW_TEXT, "show-text", { ARG_STRING, OARG_INT(-1), OARG_INT(0) },
.allow_auto_repeat = true},
- { MP_CMD_SHOW_PROGRESS, "show_progress", .allow_auto_repeat = true},
- { MP_CMD_SUB_ADD, "sub_add", { ARG_STRING,
+ { MP_CMD_SHOW_PROGRESS, "show-progress", .allow_auto_repeat = true},
+ { MP_CMD_SUB_ADD, "sub-add", { ARG_STRING,
OARG_CHOICE(0, ({"select", 0}, {"auto", 1}, {"cached", 2})),
OARG_STRING(""), OARG_STRING("") } },
- { MP_CMD_SUB_REMOVE, "sub_remove", { OARG_INT(-1) } },
- { MP_CMD_SUB_RELOAD, "sub_reload", { OARG_INT(-1) } },
+ { MP_CMD_SUB_REMOVE, "sub-remove", { OARG_INT(-1) } },
+ { MP_CMD_SUB_RELOAD, "sub-reload", { OARG_INT(-1) } },
- { MP_CMD_TV_LAST_CHANNEL, "tv_last_channel", },
+ { MP_CMD_TV_LAST_CHANNEL, "tv-last-channel", },
{ MP_CMD_SCREENSHOT, "screenshot", {
OARG_FLAGS(4|2, ({"video", 4|0}, {"-", 4|0},
@@ -113,13 +115,13 @@ const struct mp_cmd_def mp_cmds[] = {
OARG_CHOICE(0, ({"unused", 0}, {"single", 0},
{"each-frame", 8})),
}},
- { MP_CMD_SCREENSHOT_TO_FILE, "screenshot_to_file", {
+ { MP_CMD_SCREENSHOT_TO_FILE, "screenshot-to-file", {
ARG_STRING,
OARG_CHOICE(2, ({"video", 0},
{"window", 1},
{"subtitles", 2})),
}},
- { MP_CMD_SCREENSHOT_RAW, "screenshot_raw", {
+ { MP_CMD_SCREENSHOT_RAW, "screenshot-raw", {
OARG_CHOICE(2, ({"video", 0},
{"window", 1},
{"subtitles", 2})),
@@ -136,15 +138,14 @@ const struct mp_cmd_def mp_cmds[] = {
OARG_CHOICE(0, ({"replace", 0},
{"append", 1})),
}},
- { MP_CMD_PLAYLIST_CLEAR, "playlist_clear", },
- { MP_CMD_PLAYLIST_REMOVE, "playlist_remove", {
+ { MP_CMD_PLAYLIST_CLEAR, "playlist-clear", },
+ { MP_CMD_PLAYLIST_REMOVE, "playlist-remove", {
ARG_CHOICE_OR_INT(0, INT_MAX, ({"current", -1})),
}},
- { MP_CMD_PLAYLIST_MOVE, "playlist_move", { ARG_INT, ARG_INT } },
+ { MP_CMD_PLAYLIST_MOVE, "playlist-move", { ARG_INT, ARG_INT } },
{ MP_CMD_RUN, "run", { ARG_STRING, ARG_STRING }, .vararg = true },
{ MP_CMD_SET, "set", { ARG_STRING, ARG_STRING } },
- { MP_CMD_GET_PROPERTY, "get_property", { ARG_STRING } },
{ MP_CMD_ADD, "add", { ARG_STRING, OARG_DOUBLE(0) },
.allow_auto_repeat = true},
{ MP_CMD_CYCLE, "cycle", {
@@ -156,45 +157,51 @@ const struct mp_cmd_def mp_cmds[] = {
{ MP_CMD_MULTIPLY, "multiply", { ARG_STRING, ARG_DOUBLE },
.allow_auto_repeat = true},
- { MP_CMD_CYCLE_VALUES, "cycle_values", { ARG_STRING, ARG_STRING, ARG_STRING },
+ { MP_CMD_CYCLE_VALUES, "cycle-values", { ARG_STRING, ARG_STRING, ARG_STRING },
.vararg = true},
- { MP_CMD_ENABLE_INPUT_SECTION, "enable_section", {
+ { MP_CMD_ENABLE_INPUT_SECTION, "enable-section", {
ARG_STRING,
- OARG_CHOICE(0, ({"default", 0},
- {"exclusive", 1})),
+ OARG_FLAGS(0, ({"default", 0},
+ {"exclusive", MP_INPUT_EXCLUSIVE},
+ {"allow-hide-cursor", MP_INPUT_ALLOW_HIDE_CURSOR},
+ {"allow-vo-dragging", MP_INPUT_ALLOW_VO_DRAGGING})),
+ }},
+ { MP_CMD_DISABLE_INPUT_SECTION, "disable-section", { ARG_STRING } },
+ { MP_CMD_DEFINE_INPUT_SECTION, "define-section", {
+ ARG_STRING,
+ ARG_STRING,
+ OARG_CHOICE(1, ({"default", 1},
+ {"force", 0})),
}},
- { MP_CMD_DISABLE_INPUT_SECTION, "disable_section", { ARG_STRING } },
-
- { MP_CMD_DISCNAV, "discnav", { ARG_STRING } },
- { MP_CMD_AB_LOOP, "ab_loop", },
+ { MP_CMD_AB_LOOP, "ab-loop", },
- { MP_CMD_DROP_BUFFERS, "drop_buffers", },
+ { MP_CMD_DROP_BUFFERS, "drop-buffers", },
{ MP_CMD_AF, "af", { ARG_STRING, ARG_STRING } },
- { MP_CMD_AO_RELOAD, "ao_reload", },
+ { MP_CMD_AO_RELOAD, "ao-reload", },
{ MP_CMD_VF, "vf", { ARG_STRING, ARG_STRING } },
- { MP_CMD_VO_CMDLINE, "vo_cmdline", { ARG_STRING } },
+ { MP_CMD_VO_CMDLINE, "vo-cmdline", { ARG_STRING } },
- { MP_CMD_SCRIPT_BINDING, "script_binding", { ARG_STRING },
+ { MP_CMD_SCRIPT_BINDING, "script-binding", { ARG_STRING },
.allow_auto_repeat = true, .on_updown = true},
- { MP_CMD_SCRIPT_MESSAGE, "script_message", { ARG_STRING }, .vararg = true },
- { MP_CMD_SCRIPT_MESSAGE_TO, "script_message_to", { ARG_STRING, ARG_STRING },
+ { MP_CMD_SCRIPT_MESSAGE, "script-message", { ARG_STRING }, .vararg = true },
+ { MP_CMD_SCRIPT_MESSAGE_TO, "script-message-to", { ARG_STRING, ARG_STRING },
.vararg = true },
- { MP_CMD_OVERLAY_ADD, "overlay_add",
+ { MP_CMD_OVERLAY_ADD, "overlay-add",
{ ARG_INT, ARG_INT, ARG_INT, ARG_STRING, ARG_INT, ARG_STRING, ARG_INT,
ARG_INT, ARG_INT }},
- { MP_CMD_OVERLAY_REMOVE, "overlay_remove", { ARG_INT } },
+ { MP_CMD_OVERLAY_REMOVE, "overlay-remove", { ARG_INT } },
- { MP_CMD_WRITE_WATCH_LATER_CONFIG, "write_watch_later_config", },
+ { MP_CMD_WRITE_WATCH_LATER_CONFIG, "write-watch-later-config", },
- { MP_CMD_HOOK_ADD, "hook_add", { ARG_STRING, ARG_INT, ARG_INT } },
- { MP_CMD_HOOK_ACK, "hook_ack", { ARG_STRING } },
+ { MP_CMD_HOOK_ADD, "hook-add", { ARG_STRING, ARG_INT, ARG_INT } },
+ { MP_CMD_HOOK_ACK, "hook-ack", { ARG_STRING } },
{ MP_CMD_MOUSE, "mouse", {
ARG_INT, ARG_INT, // coordinate (x, y)
@@ -202,15 +209,18 @@ const struct mp_cmd_def mp_cmds[] = {
OARG_CHOICE(0, ({"single", 0},
{"double", 1})),
}},
+ { MP_CMD_KEYPRESS, "keypress", { ARG_STRING } },
+ { MP_CMD_KEYDOWN, "keydown", { ARG_STRING } },
+ { MP_CMD_KEYUP, "keyup", { OARG_STRING("") } },
- { MP_CMD_AUDIO_ADD, "audio_add", { ARG_STRING,
+ { MP_CMD_AUDIO_ADD, "audio-add", { ARG_STRING,
OARG_CHOICE(0, ({"select", 0}, {"auto", 1}, {"cached", 2})),
OARG_STRING(""), OARG_STRING("") } },
- { MP_CMD_AUDIO_REMOVE, "audio_remove", { OARG_INT(-1) } },
- { MP_CMD_AUDIO_RELOAD, "audio_reload", { OARG_INT(-1) } },
+ { MP_CMD_AUDIO_REMOVE, "audio-remove", { OARG_INT(-1) } },
+ { MP_CMD_AUDIO_RELOAD, "audio-reload", { OARG_INT(-1) } },
- { MP_CMD_RESCAN_EXTERNAL_FILES, "rescan_external_files", {
- OARG_CHOICE(0, ({"keep-selection", 0},
+ { MP_CMD_RESCAN_EXTERNAL_FILES, "rescan-external-files", {
+ OARG_CHOICE(1, ({"keep-selection", 0},
{"reselect", 1})),
}},
@@ -262,20 +272,19 @@ static const struct legacy_cmd legacy_cmds[] = {
{"set_property", "no-osd set"},
{"set_property_osd", "set"},
{"speed_set", "set speed"},
- {"osd_show_text", "show_text"},
- {"osd_show_property_text", "show_text"},
- {"osd_show_progression", "show_progress"},
- {"show_chapters_osd", "show_text ${chapter-list}"},
- {"show_chapters", "show_text ${chapter-list}"},
- {"show_tracks_osd", "show_text ${track-list}"},
- {"show_tracks", "show_text ${track-list}"},
- {"show_playlist", "show_text ${playlist}"},
+ {"osd_show_text", "show-text"},
+ {"osd_show_property_text", "show-text"},
+ {"osd_show_progression", "show-progress"},
+ {"show_chapters_osd", "show-text ${chapter-list}"},
+ {"show_chapters", "show-text ${chapter-list}"},
+ {"show_tracks_osd", "show-text ${track-list}"},
+ {"show_tracks", "show-text ${track-list}"},
+ {"show_playlist", "show-text ${playlist}"},
{"speed_mult", "multiply speed"},
- {"dvdnav", "discnav"},
// Approximate (can fail if user added additional whitespace)
- {"pt_step 1", "playlist_next"},
- {"pt_step -1", "playlist_prev"},
+ {"pt_step 1", "playlist-next"},
+ {"pt_step -1", "playlist-prev"},
// Switch_ratio without argument resets aspect ratio
{"switch_ratio ", "set aspect "},
{"switch_ratio", "set aspect 0"},
diff --git a/input/cmd_list.h b/input/cmd_list.h
index e9418d4..c8c0dc3 100644
--- a/input/cmd_list.h
+++ b/input/cmd_list.h
@@ -54,6 +54,7 @@ enum mp_command_type {
MP_CMD_PLAYLIST_CLEAR,
MP_CMD_PLAYLIST_REMOVE,
MP_CMD_PLAYLIST_MOVE,
+ MP_CMD_PLAYLIST_SHUFFLE,
MP_CMD_SUB_STEP,
MP_CMD_SUB_SEEK,
MP_CMD_TV_LAST_CHANNEL,
@@ -64,7 +65,6 @@ enum mp_command_type {
MP_CMD_SUB_REMOVE,
MP_CMD_SUB_RELOAD,
MP_CMD_SET,
- MP_CMD_GET_PROPERTY,
MP_CMD_PRINT_TEXT,
MP_CMD_SHOW_TEXT,
MP_CMD_SHOW_PROGRESS,
@@ -79,14 +79,16 @@ enum mp_command_type {
MP_CMD_ENABLE_INPUT_SECTION,
MP_CMD_DISABLE_INPUT_SECTION,
-
- MP_CMD_DISCNAV,
+ MP_CMD_DEFINE_INPUT_SECTION,
MP_CMD_AB_LOOP,
MP_CMD_DROP_BUFFERS,
MP_CMD_MOUSE,
+ MP_CMD_KEYPRESS,
+ MP_CMD_KEYDOWN,
+ MP_CMD_KEYUP,
/// Audio Filter commands
MP_CMD_AF,
diff --git a/input/cmd_parse.c b/input/cmd_parse.c
index f588bb7..206bd41 100644
--- a/input/cmd_parse.c
+++ b/input/cmd_parse.c
@@ -69,8 +69,16 @@ static bool find_cmd(struct mp_log *log, struct mp_cmd *cmd, bstr name)
mp_err(log, "Command name missing.\n");
return false;
}
+
+ char nname[80];
+ snprintf(nname, sizeof(nname), "%.*s", BSTR_P(name));
+ for (int n = 0; nname[n]; n++) {
+ if (nname[n] == '_')
+ nname[n] = '-';
+ }
+
for (int n = 0; mp_cmds[n].name; n++) {
- if (bstr_equals0(name, mp_cmds[n].name)) {
+ if (strcmp(nname, mp_cmds[n].name) == 0) {
cmd->def = &mp_cmds[n];
cmd->name = (char *)cmd->def->name;
cmd->id = cmd->def->id;
diff --git a/input/event.c b/input/event.c
index add77f0..d47f17d 100644
--- a/input/event.c
+++ b/input/event.c
@@ -20,7 +20,8 @@
#include "common/msg.h"
#include "sub/find_subfiles.h"
-void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files)
+void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files,
+ enum mp_dnd_action action)
{
bool all_sub = true;
for (int i = 0; i < num_files; i++)
@@ -42,8 +43,9 @@ void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files)
"osd-auto",
"loadfile",
files[i],
- /* Start playing the dropped files right away */
- (i == 0) ? "replace" : "append",
+ /* Either start playing the dropped files right away
+ or add them to the end of the current playlist */
+ (i == 0 && action == DND_REPLACE) ? "replace" : "append-play",
NULL
};
mp_input_run_cmd(ictx, cmd);
@@ -52,7 +54,7 @@ void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files)
}
int mp_event_drop_mime_data(struct input_ctx *ictx, const char *mime_type,
- bstr data)
+ bstr data, enum mp_dnd_action action)
{
// X11 and Wayland file list format.
if (strcmp(mime_type, "text/uri-list") == 0) {
@@ -67,7 +69,7 @@ int mp_event_drop_mime_data(struct input_ctx *ictx, const char *mime_type,
char *s = bstrto0(tmp, line);
MP_TARRAY_APPEND(tmp, files, num_files, s);
}
- mp_event_drop_files(ictx, num_files, files);
+ mp_event_drop_files(ictx, num_files, files, action);
talloc_free(tmp);
return num_files > 0;
} else {
diff --git a/input/event.h b/input/event.h
index a1cb542..e2ce36b 100644
--- a/input/event.h
+++ b/input/event.h
@@ -19,10 +19,16 @@
struct input_ctx;
+enum mp_dnd_action {
+ DND_REPLACE,
+ DND_APPEND,
+};
+
// Enqueue files for playback after drag and drop
-void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files);
+void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files,
+ enum mp_dnd_action append);
// Drop data in a specific format (identified by the mimetype).
// Returns <0 on error, ==0 if data was ok but empty, >0 on success.
int mp_event_drop_mime_data(struct input_ctx *ictx, const char *mime_type,
- bstr data);
+ bstr data, enum mp_dnd_action append);
diff --git a/input/input.c b/input/input.c
index 1a6931a..c22ece8 100644
--- a/input/input.c
+++ b/input/input.c
@@ -319,7 +319,7 @@ static mp_cmd_t *handle_test(struct input_ctx *ictx, int code)
msg = talloc_asprintf_append(msg, "(nothing)");
MP_INFO(ictx, "%s\n", msg);
- const char *args[] = {"show_text", msg, NULL};
+ const char *args[] = {"show-text", msg, NULL};
mp_cmd_t *res = mp_input_parse_cmd_strv(ictx->log, args);
talloc_free(msg);
return res;
@@ -453,7 +453,7 @@ static mp_cmd_t *get_cmd_from_keys(struct input_ctx *ictx, char *force_section,
if (MP_KEY_IS_MOUSE_MOVE(code))
msgl = MSGL_DEBUG;
char *key_buf = mp_input_get_key_combo_name(&code, 1);
- MP_MSG(ictx, msgl, "No bind found for key '%s'.\n", key_buf);
+ MP_MSG(ictx, msgl, "No key binding found for key '%s'.\n", key_buf);
talloc_free(key_buf);
return NULL;
}
@@ -469,7 +469,7 @@ static mp_cmd_t *get_cmd_from_keys(struct input_ctx *ictx, char *force_section,
ret->is_mouse_button = code & MP_KEY_EMIT_ON_UP;
} else {
char *key_buf = mp_input_get_key_combo_name(&code, 1);
- MP_ERR(ictx, "Invalid command for bound key '%s': '%s'\n",
+ MP_ERR(ictx, "Invalid command for key binding '%s': '%s'\n",
key_buf, cmd->cmd);
talloc_free(key_buf);
}
@@ -1016,7 +1016,7 @@ void mp_input_define_section(struct input_ctx *ictx, char *name, char *location,
// Delete:
struct cmd_bind_section *bs = get_bind_section(ictx, bstr0(name));
remove_binds(bs, builtin);
- if (contents) {
+ if (contents && contents[0]) {
// Redefine:
parse_config(ictx, builtin, bstr0(contents), location, name);
} else {
@@ -1331,7 +1331,6 @@ void mp_input_run_cmd(struct input_ctx *ictx, const char **cmd)
struct mp_input_src_internal {
pthread_t thread;
bool thread_running;
- int wakeup[2];
bool init_done;
char *cmd_buffer;
@@ -1339,7 +1338,7 @@ struct mp_input_src_internal {
bool drop;
};
-struct mp_input_src *mp_input_add_src(struct input_ctx *ictx)
+static struct mp_input_src *mp_input_add_src(struct input_ctx *ictx)
{
input_lock(ictx);
if (ictx->num_sources == MP_MAX_SOURCES) {
@@ -1354,10 +1353,7 @@ struct mp_input_src *mp_input_add_src(struct input_ctx *ictx)
.global = ictx->global,
.log = mp_log_new(src, ictx->log, name),
.input_ctx = ictx,
- .in = talloc(src, struct mp_input_src_internal),
- };
- *src->in = (struct mp_input_src_internal){
- .wakeup = {-1, -1},
+ .in = talloc_zero(src, struct mp_input_src_internal),
};
ictx->sources[ictx->num_sources++] = src;
@@ -1366,6 +1362,8 @@ struct mp_input_src *mp_input_add_src(struct input_ctx *ictx)
return src;
}
+static void mp_input_src_kill(struct mp_input_src *src);
+
static void close_input_sources(struct input_ctx *ictx)
{
// To avoid lock-order issues, we first remove each source from the context,
@@ -1380,7 +1378,7 @@ static void close_input_sources(struct input_ctx *ictx)
}
}
-void mp_input_src_kill(struct mp_input_src *src)
+static void mp_input_src_kill(struct mp_input_src *src)
{
if (!src)
return;
@@ -1390,7 +1388,6 @@ void mp_input_src_kill(struct mp_input_src *src)
if (ictx->sources[n] == src) {
MP_TARRAY_REMOVE_AT(ictx->sources, ictx->num_sources, n);
input_unlock(ictx);
- write(src->in->wakeup[1], &(char){0}, 1);
if (src->cancel)
src->cancel(src);
if (src->in->thread_running)
@@ -1439,14 +1436,6 @@ int mp_input_add_thread_src(struct input_ctx *ictx, void *ctx,
if (!src)
return -1;
-#ifndef __MINGW32__
- // Always create for convenience.
- if (mp_make_wakeup_pipe(src->in->wakeup) < 0) {
- mp_input_src_kill(src);
- return -1;
- }
-#endif
-
void *args[] = {src, loop_fn, ctx};
if (pthread_create(&src->in->thread, NULL, input_src_thread, args)) {
mp_input_src_kill(src);
@@ -1459,11 +1448,6 @@ int mp_input_add_thread_src(struct input_ctx *ictx, void *ctx,
return 0;
}
-int mp_input_src_get_wakeup_fd(struct mp_input_src *src)
-{
- return src->in->wakeup[0];
-}
-
#define CMD_BUFFER (4 * 4096)
void mp_input_src_feed_cmd_text(struct mp_input_src *src, char *buf, size_t len)
diff --git a/input/input.h b/input/input.h
index d51492d..2b2299d 100644
--- a/input/input.h
+++ b/input/input.h
@@ -106,11 +106,6 @@ struct mp_input_src {
void *priv;
};
-// Add a new input source. The input code can create a new thread, which feeds
-// keys or commands to input_ctx. mp_input_src.uninit must be set.
-// mp_input_src_kill() must not be called by anything after init.
-struct mp_input_src *mp_input_add_src(struct input_ctx *ictx);
-
// Add an input source that runs on a thread. The source is automatically
// removed if the thread loop exits.
// ctx: this is passed to loop_fn.
@@ -129,14 +124,6 @@ int mp_input_add_thread_src(struct input_ctx *ictx, void *ctx,
// Set src->cancel and src->uninit (if needed) before calling this.
void mp_input_src_init_done(struct mp_input_src *src);
-// Currently only with mp_input_add_thread_src().
-int mp_input_src_get_wakeup_fd(struct mp_input_src *src);
-
-// Remove and free the source. You can call this only while the input_ctx
-// exists; otherwise there would be a race condition when another thread
-// destroys input_ctx.
-void mp_input_src_kill(struct mp_input_src *src);
-
// Feed text data, which will be split into lines of commands.
void mp_input_src_feed_cmd_text(struct mp_input_src *src, char *buf, size_t len);
diff --git a/input/ipc.c b/input/ipc.c
index f1caeee..5ed057e 100644
--- a/input/ipc.c
+++ b/input/ipc.c
@@ -43,6 +43,10 @@
#include "options/path.h"
#include "player/client.h"
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+
struct mp_ipc_ctx {
struct mp_log *log;
struct mp_client_api *client_api;
@@ -240,6 +244,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
mpv_node msg_node;
mpv_node reply_node = {.format = MPV_FORMAT_NODE_MAP, .u.list = NULL};
+ mpv_node *reqid_node = NULL;
rc = json_parse(ta_parent, &msg_node, &src, 3);
if (rc < 0) {
@@ -253,6 +258,8 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
goto error;
}
+ reqid_node = mpv_node_map_get(&msg_node, "request_id");
+
mpv_node *cmd_node = mpv_node_map_get(&msg_node, "command");
if (!cmd_node ||
(cmd_node->format != MPV_FORMAT_NODE_ARRAY) ||
@@ -466,6 +473,14 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent,
}
error:
+ /* If the request contains a "request_id", copy it back into the response.
+ * This makes it easier on the requester to match up the IPC results with
+ * the original requests.
+ */
+ if (reqid_node) {
+ mpv_node_map_add(ta_parent, &reply_node, "request_id", reqid_node);
+ }
+
mpv_node_map_add_string(ta_parent, &reply_node, "error", mpv_error_string(rc));
char *output = talloc_strdup(ta_parent, "");
@@ -486,7 +501,7 @@ static int ipc_write_str(struct client_arg *client, const char *buf)
{
size_t count = strlen(buf);
while (count > 0) {
- ssize_t rc = write(client->client_fd, buf, count);
+ ssize_t rc = send(client->client_fd, buf, count, MSG_NOSIGNAL);
if (rc <= 0) {
if (rc == 0)
return -1;
@@ -529,7 +544,7 @@ static void *client_thread(void *p)
goto done;
}
- MP_INFO(arg, "Client connected\n");
+ MP_VERBOSE(arg, "Client connected\n");
struct pollfd fds[2] = {
{.events = POLLIN, .fd = pipe_fd},
@@ -597,7 +612,7 @@ static void *client_thread(void *p)
}
if (bytes == 0) {
- MP_INFO(arg, "Client disconnected\n");
+ MP_VERBOSE(arg, "Client disconnected\n");
goto done;
}
@@ -731,7 +746,7 @@ static void *ipc_thread(void *p)
mpthread_set_name("ipc socket listener");
- MP_INFO(arg, "Starting IPC master\n");
+ MP_VERBOSE(arg, "Starting IPC master\n");
ipc_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (ipc_fd < 0) {
diff --git a/libmpv/client.h b/libmpv/client.h
index a800043..54ac421 100644
--- a/libmpv/client.h
+++ b/libmpv/client.h
@@ -118,8 +118,10 @@ extern "C" {
* (used through libass), ALSA, FFmpeg, and possibly more.
* - The FPU precision must be set at least to double precision.
* - On Windows, mpv will call timeBeginPeriod(1).
- * - SIGPIPE should be blocked. Some parts rely on this signal not crashing the
- * process (such as ffmpeg OpenSSL support, or the mpv IPC code).
+ * - On UNIX, every mpv_initialize() call will block SIGPIPE. This is done
+ * because FFmpeg makes unsafe use of OpenSSL and GnuTLS, which can raise
+ * this signal under certain circumstances. Once these libraries (or FFmpeg)
+ * are fixed, libmpv will not block the signal anymore.
* - On memory exhaustion, mpv will kill the process.
*
* Encoding of filenames
@@ -196,7 +198,7 @@ extern "C" {
* relational operators (<, >, <=, >=).
*/
#define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL)
-#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 16)
+#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 19)
/**
* Return the MPV_CLIENT_API_VERSION the mpv source has been compiled with.
@@ -369,6 +371,8 @@ const char *mpv_client_name(mpv_handle *ctx);
* if there are no more files to play on the internal playlist, instead of
* exiting. This is equivalent to the --idle option.
* - Disable parts of input handling.
+ * - Most of the different settings can be viewed with the command line player
+ * by running "mpv --show-profile=libmpv".
*
* All this assumes that API users want a mpv instance that is strictly
* isolated from the command line player's configuration, user settings, and
@@ -1303,6 +1307,15 @@ typedef enum mpv_end_file_reason {
* mpv_event_end_file.error will be set.
*/
MPV_END_FILE_REASON_ERROR = 4,
+ /**
+ * The file was a playlist or similar. When the playlist is read, its
+ * entries will be appended to the playlist after the entry of the current
+ * file, the entry of the current file is removed, and a MPV_EVENT_END_FILE
+ * event is sent with reason set to MPV_END_FILE_REASON_REDIRECT. Then
+ * playback continues with the playlist contents.
+ * Since API version 1.18.
+ */
+ MPV_END_FILE_REASON_REDIRECT = 5,
} mpv_end_file_reason;
typedef struct mpv_event_end_file {
@@ -1405,6 +1418,9 @@ int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable);
* @param min_level Minimal log level as string. Valid log levels:
* no fatal error warn info status v debug trace
* The value "no" disables all messages. This is the default.
+ * An exception is the value "terminal-default", which uses the
+ * log level as set by the "--msg-level" option. This works
+ * even if the terminal is disabled. (Since API version 1.19.)
* Also see mpv_log_level.
*/
int mpv_request_log_messages(mpv_handle *ctx, const char *min_level);
diff --git a/libmpv/opengl_cb.h b/libmpv/opengl_cb.h
index fd1409a..2e031cd 100644
--- a/libmpv/opengl_cb.h
+++ b/libmpv/opengl_cb.h
@@ -90,7 +90,7 @@ extern "C" {
* The mpv_opengl_cb_* functions can be called from any thread, under the
* following conditions:
* - only one of the mpv_opengl_cb_* functions can be called at the same time
- * (unless they belong to different mpv_handles)
+ * (unless they belong to different mpv cores created by mpv_create())
* - for functions which need an OpenGL context (see above) the OpenGL context
* must be "current" in the current thread, and it must be the same context
* as used with mpv_opengl_cb_init_gl()
@@ -107,6 +107,24 @@ extern "C" {
* When the mpv core is destroyed (e.g. via mpv_terminate_destroy()), the OpenGL
* context must have been uninitialized. If this doesn't happen, undefined
* behavior will result.
+ *
+ * Special D3D interop considerations
+ * ----------------------------------
+ *
+ * If OpenGL switches to fullscreen, most players give it access GPU access,
+ * which means DXVA2 hardware decoding in mpv won't work. This can be worked
+ * around by giving mpv access to Direct3D device, which it will then use to
+ * create a decoder. The device can be either the real device used for display,
+ * or a "blank" device created before switching to fullscreen.
+ *
+ * You can do this by adding "GL_MP_D3D_interfaces" to the additional extension
+ * string when calling mpv_opengl_cb_init_gl(). The get_proc_address callback
+ * should resolve a function named "glMPGetD3DInterface", which has the
+ * signature: "void* __stdcall glMPGetD3DInterface(const char* name)". If
+ * name is "IDirect3DDevice9", it should return a IDirect3DDevice9 pointer
+ * (or NULL if not available). libmpv will release this interface when it is
+ * done with it (usually when mpv_opengl_cb_uninit_gl() is called). New
+ * interface names can be added in the future.
*/
/**
diff --git a/misc/charset_conv.c b/misc/charset_conv.c
index 31f53cc..bceb52a 100644
--- a/misc/charset_conv.c
+++ b/misc/charset_conv.c
@@ -36,6 +36,10 @@
#include <libguess.h>
#endif
+#if HAVE_UCHARDET
+#include <uchardet.h>
+#endif
+
#if HAVE_ICONV
#include <iconv.h>
#endif
@@ -81,6 +85,7 @@ bool mp_charset_requires_guess(const char *user_cp)
// Note that "utf8" is the UTF-8 codepage, while "utf8:..." specifies UTF-8
// by default, plus a codepage that is used if the input is not UTF-8.
return bstrcasecmp0(res[0], "enca") == 0 ||
+ bstrcasecmp0(res[0], "uchardet") == 0 ||
bstrcasecmp0(res[0], "auto") == 0 ||
bstrcasecmp0(res[0], "guess") == 0 ||
(r > 1 && bstrcasecmp0(res[0], "utf-8") == 0) ||
@@ -102,6 +107,11 @@ static const char *ms_bom_guess(bstr buf)
#if HAVE_ENCA
static const char *enca_guess(struct mp_log *log, bstr buf, const char *language)
{
+ // Do our own UTF-8 detection, because ENCA seems to get it wrong sometimes
+ // (suggested by divVerent). Explicitly allow cut-off UTF-8.
+ if (bstr_validate_utf8(buf) > -8)
+ return "UTF-8";
+
if (!language || !language[0])
language = "__"; // neutral language
@@ -145,32 +155,58 @@ static const char *libguess_guess(struct mp_log *log, bstr buf,
}
#endif
+#if HAVE_UCHARDET
+static const char *mp_uchardet(void *talloc_ctx, struct mp_log *log, bstr buf)
+{
+ uchardet_t det = uchardet_new();
+ if (!det)
+ return NULL;
+ if (uchardet_handle_data(det, buf.start, buf.len) != 0) {
+ uchardet_delete(det);
+ return NULL;
+ }
+ uchardet_data_end(det);
+ char *res = talloc_strdup(talloc_ctx, uchardet_get_charset(det));
+ if (res && !res[0])
+ res = NULL;
+ if (res) {
+ iconv_t icdsc = iconv_open("UTF-8", res);
+ if (icdsc == (iconv_t)(-1)) {
+ mp_warn(log, "Charset detected as %s, but not supported by iconv.\n",
+ res);
+ res = NULL;
+ } else {
+ iconv_close(icdsc);
+ }
+ }
+ uchardet_delete(det);
+ return res;
+}
+#endif
+
// Runs charset auto-detection on the input buffer, and returns the result.
// If auto-detection fails, NULL is returned.
// If user_cp doesn't refer to any known auto-detection (for example because
// it's a real iconv codepage), user_cp is returned without even looking at
// the buf data.
-const char *mp_charset_guess(struct mp_log *log, bstr buf, const char *user_cp,
- int flags)
+// The return value may (but doesn't have to) be allocated under talloc_ctx.
+const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf,
+ const char *user_cp, int flags)
{
if (!mp_charset_requires_guess(user_cp))
return user_cp;
bool use_auto = strcasecmp(user_cp, "auto") == 0;
if (use_auto) {
-#if HAVE_ENCA
+#if HAVE_UCHARDET
+ user_cp = "uchardet";
+#elif HAVE_ENCA
user_cp = "enca";
#else
user_cp = "UTF-8:UTF-8-BROKEN";
#endif
}
- // Do our own UTF-8 detection, because at least ENCA seems to get it
- // wrong sometimes (suggested by divVerent).
- int r = bstr_validate_utf8(buf);
- if (r >= 0 || (r > -8 && (flags & MP_ICONV_ALLOW_CUTOFF)))
- return "UTF-8";
-
bstr params[3] = {{0}};
split_colon(user_cp, 3, params);
@@ -195,9 +231,17 @@ const char *mp_charset_guess(struct mp_log *log, bstr buf, const char *user_cp,
if (bstrcasecmp0(type, "guess") == 0)
res = libguess_guess(log, buf, lang);
#endif
+#if HAVE_UCHARDET
+ if (bstrcasecmp0(type, "uchardet") == 0)
+ res = mp_uchardet(talloc_ctx, log, buf);
+#endif
+
if (bstrcasecmp0(type, "utf8") == 0 || bstrcasecmp0(type, "utf-8") == 0) {
if (!fallback)
fallback = params[1].start; // must be already 0-terminated
+ int r = bstr_validate_utf8(buf);
+ if (r >= 0 || (r > -8 && (flags & MP_ICONV_ALLOW_CUTOFF)))
+ res = "utf-8";
}
if (res) {
@@ -211,6 +255,7 @@ const char *mp_charset_guess(struct mp_log *log, bstr buf, const char *user_cp,
if (!res && !(flags & MP_STRICT_UTF8))
res = "UTF-8-BROKEN";
+ mp_verbose(log, "Using charset '%s'.\n", res);
return res;
}
@@ -225,8 +270,11 @@ const char *mp_charset_guess(struct mp_log *log, bstr buf, const char *user_cp,
bstr mp_charset_guess_and_conv_to_utf8(struct mp_log *log, bstr buf,
const char *user_cp, int flags)
{
- return mp_iconv_to_utf8(log, buf, mp_charset_guess(log, buf, user_cp, flags),
- 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.
diff --git a/misc/charset_conv.h b/misc/charset_conv.h
index 93bd91c..bd76ae0 100644
--- a/misc/charset_conv.h
+++ b/misc/charset_conv.h
@@ -14,8 +14,8 @@ enum {
bool mp_charset_is_utf8(const char *user_cp);
bool mp_charset_requires_guess(const char *user_cp);
-const char *mp_charset_guess(struct mp_log *log, bstr buf, const char *user_cp,
- int flags);
+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);
diff --git a/options/m_config.c b/options/m_config.c
index e513019..030c144 100644
--- a/options/m_config.c
+++ b/options/m_config.c
@@ -272,6 +272,7 @@ static void ensure_backup(struct m_config *config, struct m_config_option *co)
m_option_copy(co->opt, bc->backup, co->data);
bc->next = config->backup_opts;
config->backup_opts = bc;
+ co->is_set_locally = true;
}
void m_config_restore_backups(struct m_config *config)
@@ -282,6 +283,7 @@ void m_config_restore_backups(struct m_config *config)
m_option_copy(bc->co->opt, bc->co->data, bc->backup);
m_option_free(bc->co->opt, bc->backup);
+ bc->co->is_set_locally = false;
talloc_free(bc);
}
}
diff --git a/options/m_config.h b/options/m_config.h
index 4b29c9c..89f620b 100644
--- a/options/m_config.h
+++ b/options/m_config.h
@@ -39,6 +39,7 @@ struct mp_log;
struct m_config_option {
bool is_generated : 1; // Automatically added ("no-" options)
bool is_set_from_cmdline : 1; // Set by user from command line
+ bool is_set_locally : 1; // Has a backup entry
bool warning_was_printed : 1;
const char *name; // Full name (ie option-subopt)
const struct m_option *opt; // Option description
diff --git a/options/m_option.c b/options/m_option.c
index bd8e335..020bd37 100644
--- a/options/m_option.c
+++ b/options/m_option.c
@@ -743,13 +743,18 @@ static int apply_flag(const struct m_option *opt, int *val, bstr flag)
static const char *find_next_flag(const struct m_option *opt, int *val)
{
+ struct m_opt_choice_alternatives *best = NULL;
struct m_opt_choice_alternatives *alt;
for (alt = opt->priv; alt->name; alt++) {
if (alt->value && (alt->value & (*val)) == alt->value) {
- *val = *val & ~(unsigned)alt->value;
- return alt->name;
+ if (!best || av_popcount64(alt->value) > av_popcount64(best->value))
+ best = alt;
}
}
+ if (best) {
+ *val = *val & ~(unsigned)best->value;
+ return best->name;
+ }
*val = 0; // if there are still flags left, there's not much we can do
return NULL;
}
@@ -1515,7 +1520,7 @@ const m_option_type_t m_option_type_string_append_list = {
.set = str_list_set,
};
-static int read_subparam(struct mp_log *log, bstr optname,
+static int read_subparam(struct mp_log *log, bstr optname, char *termset,
bstr *str, bstr *out_subparam);
static int parse_keyvalue_list(struct mp_log *log, const m_option_t *opt,
@@ -1527,7 +1532,7 @@ static int parse_keyvalue_list(struct mp_log *log, const m_option_t *opt,
while (param.len) {
bstr key, val;
- r = read_subparam(log, name, &param, &key);
+ r = read_subparam(log, name, "=", &param, &key);
if (r < 0)
break;
if (!bstr_eatstart0(&param, "=")) {
@@ -1535,7 +1540,7 @@ static int parse_keyvalue_list(struct mp_log *log, const m_option_t *opt,
r = M_OPT_INVALID;
break;
}
- r = read_subparam(log, name, &param, &val);
+ r = read_subparam(log, name, ",:", &param, &val);
if (r < 0)
break;
if (dst) {
@@ -1717,9 +1722,10 @@ const m_option_type_t m_option_type_print_fn = {
#define VAL(x) (*(char ***)(x))
// Read s sub-option name, or a positional sub-opt value.
+// termset is a string containing the set of chars that terminate an option.
// Return 0 on succes, M_OPT_ error code otherwise.
// optname is for error reporting.
-static int read_subparam(struct mp_log *log, bstr optname,
+static int read_subparam(struct mp_log *log, bstr optname, char *termset,
bstr *str, bstr *out_subparam)
{
bstr p = *str;
@@ -1764,7 +1770,7 @@ static int read_subparam(struct mp_log *log, bstr optname,
} else {
// Skip until the next character that could possibly be a meta
// character in option parsing.
- int optlen = bstrcspn(p, ":=,\\%\"'[]");
+ int optlen = bstrcspn(p, termset);
subparam = bstr_splice(p, 0, optlen);
p = bstr_cut(p, optlen);
}
@@ -1784,11 +1790,11 @@ static int split_subconf(struct mp_log *log, bstr optname, bstr *str,
bstr p = *str;
bstr subparam = {0};
bstr subopt;
- int r = read_subparam(log, optname, &p, &subopt);
+ int r = read_subparam(log, optname, ":=,\\%\"'[]", &p, &subopt);
if (r < 0)
return r;
if (bstr_eatstart0(&p, "=")) {
- r = read_subparam(log, subopt, &p, &subparam);
+ r = read_subparam(log, subopt, ":=,\\%\"'[]", &p, &subparam);
if (r < 0)
return r;
}
@@ -2216,13 +2222,17 @@ static int parse_afmt(struct mp_log *log, const m_option_t *opt,
if (!bstrcmp0(param, "help")) {
mp_info(log, "Available formats:");
- for (int i = 0; af_fmtstr_table[i].name; i++)
- mp_info(log, " %s", af_fmtstr_table[i].name);
+ for (int i = 1; i < AF_FORMAT_COUNT; i++)
+ mp_info(log, " %s", af_fmt_to_str(i));
mp_info(log, "\n");
return M_OPT_EXIT - 1;
}
- int fmt = af_str2fmt_short(param);
+ int fmt = 0;
+ for (int i = 1; i < AF_FORMAT_COUNT; i++) {
+ if (bstr_equals0(param, af_fmt_to_str(i)))
+ fmt = i;
+ }
if (!fmt) {
mp_err(log, "Option %.*s: unknown format name: '%.*s'\n",
BSTR_P(name), BSTR_P(param));
@@ -2742,8 +2752,8 @@ print_help: ;
desc->print_help(log);
m_config_print_option_list(config);
} else {
- mp_warn(log, "Option %.*s doesn't exist.\n",
- BSTR_P(opt_name));
+ mp_warn(log, "Option %.*s: item %.*s doesn't exist.\n",
+ BSTR_P(opt_name), BSTR_P(name));
}
r = M_OPT_EXIT - 1;
diff --git a/options/m_property.c b/options/m_property.c
index 7b1505c..9af3c91 100644
--- a/options/m_property.c
+++ b/options/m_property.c
@@ -36,49 +36,6 @@
#include "common/msg.h"
#include "common/common.h"
-struct legacy_prop {
- const char *old, *new;
-};
-static const struct legacy_prop legacy_props[] = {
- {"switch_video", "video"},
- {"switch_audio", "audio"},
- {"switch_program", "program"},
- {"framedropping", "framedrop"},
- {"osdlevel", "osd-level"},
- {0}
-};
-
-static bool translate_legacy_property(struct mp_log *log, const char *name,
- char *buffer, size_t buffer_size)
-{
- if (strlen(name) + 1 > buffer_size)
- return false;
-
- const char *old_name = name;
-
- for (int n = 0; legacy_props[n].new; n++) {
- if (strcmp(name, legacy_props[n].old) == 0) {
- name = legacy_props[n].new;
- break;
- }
- }
-
- snprintf(buffer, buffer_size, "%s", name);
-
- // Old names used "_" instead of "-"
- for (int n = 0; buffer[n]; n++) {
- if (buffer[n] == '_')
- buffer[n] = '-';
- }
-
- if (log && strcmp(old_name, buffer) != 0) {
- mp_warn(log, "Warning: property '%s' is deprecated, replaced with '%s'."
- " Fix your input.conf!\n", old_name, buffer);
- }
-
- return true;
-}
-
static struct m_property *m_property_list_find(const struct m_property *list,
const char *name)
{
@@ -117,15 +74,11 @@ static int do_action(const struct m_property *prop_list, const char *name,
// (as a hack, log can be NULL on read-only paths)
int m_property_do(struct mp_log *log, const struct m_property *prop_list,
- const char *in_name, int action, void *arg, void *ctx)
+ const char *name, int action, void *arg, void *ctx)
{
union m_option_value val = {0};
int r;
- char name[64];
- if (!translate_legacy_property(log, in_name, name, sizeof(name)))
- return M_PROPERTY_UNKNOWN;
-
struct m_option opt = {0};
r = do_action(prop_list, name, M_PROPERTY_GET_TYPE, &opt, ctx);
if (r <= 0)
diff --git a/options/m_property.h b/options/m_property.h
index 7c5f924..93a4a73 100644
--- a/options/m_property.h
+++ b/options/m_property.h
@@ -54,7 +54,7 @@ enum mp_property_action {
// arg: struct m_property_switch_arg*
M_PROPERTY_SWITCH,
- // Get a string containing a parsable representation.
+ // Get a string containing a parseable representation.
// Can't be overridden by property implementations.
// arg: char**
M_PROPERTY_GET_STRING,
diff --git a/options/options.c b/options/options.c
index f59f135..71aea33 100644
--- a/options/options.c
+++ b/options/options.c
@@ -38,6 +38,7 @@
#include "common/common.h"
#include "stream/stream.h"
#include "video/csputils.h"
+#include "video/hwdec.h"
#include "sub/osd.h"
#include "audio/mixer.h"
#include "audio/filter/af.h"
@@ -79,6 +80,19 @@ extern const struct m_obj_list af_obj_list;
extern const struct m_obj_list vo_obj_list;
extern const struct m_obj_list ao_obj_list;
+const struct m_opt_choice_alternatives mp_hwdec_names[] = {
+ {"no", HWDEC_NONE},
+ {"auto", HWDEC_AUTO},
+ {"vdpau", HWDEC_VDPAU},
+ {"vda", HWDEC_VDA},
+ {"videotoolbox",HWDEC_VIDEOTOOLBOX},
+ {"vaapi", HWDEC_VAAPI},
+ {"vaapi-copy", HWDEC_VAAPI_COPY},
+ {"dxva2-copy", HWDEC_DXVA2_COPY},
+ {"rpi", HWDEC_RPI},
+ {0}
+};
+
#define OPT_BASE_STRUCT struct MPOpts
const m_option_t mp_opts[] = {
@@ -95,10 +109,6 @@ const m_option_t mp_opts[] = {
{ "show-profile", CONF_TYPE_STRING, CONF_NOCFG | M_OPT_FIXED, .offset = -1},
{ "list-options", CONF_TYPE_STORE, CONF_NOCFG | M_OPT_FIXED, .offset = -1},
- // handled in main.c (looks at the raw argv[])
- { "leak-report", CONF_TYPE_STORE, CONF_GLOBAL | CONF_NOCFG | M_OPT_FIXED,
- .offset = -1 },
-
OPT_FLAG("shuffle", shuffle, 0),
// ------------------------- common options --------------------
@@ -147,6 +157,7 @@ const m_option_t mp_opts[] = {
({"no", 0})),
OPT_INTRANGE("cache-initial", stream_cache.initial, 0, 0, 0x7fffffff),
OPT_INTRANGE("cache-seek-min", stream_cache.seek_min, 0, 0, 0x7fffffff),
+ OPT_INTRANGE("cache-backbuffer", stream_cache.back_buffer, 0, 0, 0x7fffffff),
OPT_STRING("cache-file", stream_cache.file, M_OPT_FILE),
OPT_INTRANGE("cache-file-size", stream_cache.file_max, 0, 0, 0x7fffffff),
@@ -189,6 +200,8 @@ const m_option_t mp_opts[] = {
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),
+ OPT_CHOICE_OR_INT("playlist-pos", playlist_pos, 0, 0, INT_MAX, ({"no", -1})),
+
OPT_FLAG("pause", pause, M_OPT_FIXED),
OPT_CHOICE("keep-open", keep_open, 0,
({"no", 0},
@@ -198,24 +211,24 @@ const m_option_t mp_opts[] = {
OPT_CHOICE("index", index_mode, 0, ({"default", 1}, {"recreate", 0})),
// select audio/video/subtitle stream
- OPT_TRACKCHOICE("aid", audio_id),
- OPT_TRACKCHOICE("vid", video_id),
- OPT_TRACKCHOICE("sid", sub_id),
- OPT_TRACKCHOICE("secondary-sid", sub2_id),
- OPT_TRACKCHOICE("ff-aid", audio_id_ff),
- OPT_TRACKCHOICE("ff-vid", video_id_ff),
- OPT_TRACKCHOICE("ff-sid", sub_id_ff),
- OPT_FLAG_STORE("no-sub", sub_id, 0, -2),
- OPT_FLAG_STORE("no-video", video_id, 0, -2),
- OPT_FLAG_STORE("no-audio", audio_id, 0, -2),
- OPT_STRINGLIST("alang", audio_lang, 0),
- OPT_STRINGLIST("slang", sub_lang, 0),
+ OPT_TRACKCHOICE("aid", stream_id[0][STREAM_AUDIO]),
+ OPT_TRACKCHOICE("vid", stream_id[0][STREAM_VIDEO]),
+ OPT_TRACKCHOICE("sid", stream_id[0][STREAM_SUB]),
+ OPT_TRACKCHOICE("secondary-sid", stream_id[1][STREAM_SUB]),
+ OPT_TRACKCHOICE("ff-aid", stream_id_ff[STREAM_AUDIO]),
+ OPT_TRACKCHOICE("ff-vid", stream_id_ff[STREAM_VIDEO]),
+ OPT_TRACKCHOICE("ff-sid", stream_id_ff[STREAM_SUB]),
+ OPT_FLAG_STORE("no-sub", stream_id[0][STREAM_SUB], 0, -2),
+ OPT_FLAG_STORE("no-video", stream_id[0][STREAM_VIDEO], 0, -2),
+ OPT_FLAG_STORE("no-audio", stream_id[0][STREAM_AUDIO], 0, -2),
+ OPT_STRINGLIST("alang", stream_lang[STREAM_AUDIO], 0),
+ OPT_STRINGLIST("slang", stream_lang[STREAM_SUB], 0),
OPT_CHOICE("audio-display", audio_display, 0,
({"no", 0}, {"attachment", 1})),
- OPT_CHOICE("hls-bitrate", hls_bitrate, 0,
- ({"no", 0}, {"min", 1}, {"max", 2})),
+ OPT_CHOICE_OR_INT("hls-bitrate", hls_bitrate, 0, 0, INT_MAX,
+ ({"no", -1}, {"min", 0}, {"max", INT_MAX})),
OPT_STRINGLIST("display-tags*", display_tags, 0),
@@ -231,8 +244,10 @@ const m_option_t mp_opts[] = {
OPT_STRING("sub-demuxer", sub_demuxer_name, 0),
OPT_FLAG("demuxer-thread", demuxer_thread, 0),
OPT_DOUBLE("demuxer-readahead-secs", demuxer_min_secs, M_OPT_MIN, .min = 0),
- OPT_INTRANGE("demuxer-readahead-packets", demuxer_min_packs, 0, 0, MAX_PACKS),
- OPT_INTRANGE("demuxer-readahead-bytes", demuxer_min_bytes, 0, 0, MAX_PACK_BYTES),
+ OPT_INTRANGE("demuxer-max-packets", demuxer_max_packs, 0, 0, INT_MAX),
+ OPT_INTRANGE("demuxer-max-bytes", demuxer_max_bytes, 0, 0, INT_MAX),
+
+ OPT_FLAG("force-seekable", force_seekable, 0),
OPT_DOUBLE("cache-secs", demuxer_min_secs_cache, M_OPT_MIN, .min = 0),
OPT_FLAG("cache-pause", cache_pausing, 0),
@@ -283,17 +298,12 @@ const m_option_t mp_opts[] = {
OPT_STRING("ad", audio_decoders, 0),
OPT_STRING("vd", video_decoders, 0),
+ OPT_STRING("audio-spdif", audio_spdif, 0),
+
OPT_FLAG("ad-spdif-dtshd", dtshd, 0),
- OPT_CHOICE("hwdec", hwdec_api, 0,
- ({"no", 0},
- {"auto", -1},
- {"vdpau", 1},
- {"vda", 2},
- {"vaapi", 4},
- {"vaapi-copy", 5},
- {"dxva2-copy", 6},
- {"rpi", 7})),
+ 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),
OPT_SUBSTRUCT("sws", vo.sws_opts, sws_conf, 0),
@@ -302,6 +312,8 @@ const m_option_t mp_opts[] = {
// 0 means square pixels
OPT_FLOATRANGE("video-aspect", movie_aspect, 0, -1.0, 10.0),
OPT_FLOAT_STORE("no-video-aspect", movie_aspect, 0, 0.0),
+ OPT_CHOICE("video-aspect-method", aspect_method, 0,
+ ({"hybrid", 0}, {"bitstream", 1}, {"container", 2})),
OPT_CHOICE("field-dominance", field_dominance, 0,
({"auto", -1}, {"top", 0}, {"bottom", 1})),
@@ -325,6 +337,7 @@ const m_option_t mp_opts[] = {
OPT_FLAG("sub-visibility", sub_visibility, 0),
OPT_FLAG("sub-forced-only", forced_subs_only, 0),
OPT_FLAG("stretch-dvd-subs", stretch_dvd_subs, 0),
+ OPT_FLAG("stretch-image-subs-to-screen", stretch_image_subs, 0),
OPT_FLAG("sub-fix-timing", sub_fix_timing, 0),
OPT_CHOICE("sub-auto", sub_auto, 0,
({"no", -1}, {"exact", 0}, {"fuzzy", 1}, {"all", 2})),
@@ -371,7 +384,8 @@ const m_option_t mp_opts[] = {
OPT_SETTINGSLIST("ao-defaults", ao_defs, 0, &ao_obj_list),
OPT_STRING("audio-device", audio_device, 0),
OPT_STRING("audio-client-name", audio_client_name, 0),
- OPT_FLAG("force-window", force_vo, 0),
+ OPT_CHOICE("force-window", force_vo, 0,
+ ({"no", 0}, {"yes", 1}, {"immediate", 2})),
OPT_FLAG("ontop", vo.ontop, M_OPT_FIXED),
OPT_FLAG("border", vo.border, M_OPT_FIXED),
OPT_FLAG("on-all-workspaces", vo.all_workspaces, M_OPT_FIXED),
@@ -382,8 +396,8 @@ const m_option_t mp_opts[] = {
({"no", SOFTVOL_NO},
{"yes", SOFTVOL_YES},
{"auto", SOFTVOL_AUTO})),
- OPT_FLOATRANGE("softvol-max", softvol_max, 0, 10, 10000),
- OPT_FLOATRANGE("volume", mixer_init_volume, 0, -1, 100),
+ OPT_FLOATRANGE("softvol-max", softvol_max, 0, 100, 1000),
+ OPT_FLOATRANGE("volume", mixer_init_volume, 0, -1, 1000),
OPT_CHOICE("mute", mixer_init_mute, 0,
({"auto", -1},
{"no", 0},
@@ -405,7 +419,7 @@ const m_option_t mp_opts[] = {
// vo name (X classname) and window title strings
OPT_STRING("x11-name", vo.winname, 0),
OPT_STRING("title", wintitle, 0),
- OPT_STRING("media-title", media_title, 0),
+ OPT_STRING("force-media-title", media_title, 0),
// set aspect ratio of monitor - useful for 16:9 TV-out
OPT_FLOATRANGE("monitoraspect", vo.force_monitor_aspect, 0, 0.0, 9.0),
OPT_FLOATRANGE("monitorpixelaspect", vo.monitor_pixel_aspect, 0, 0.2, 9.0),
@@ -480,7 +494,7 @@ const m_option_t mp_opts[] = {
OPT_CHOICE_OR_INT("loop", loop_times, 0, 1, 10000,
({"no", 1},
- {"inf", -1},
+ {"inf", -1}, {"yes", -1},
{"force", -2})),
OPT_CHOICE_OR_INT("loop-file", loop_file, 0, 0, 10000,
({"no", 0},
@@ -508,6 +522,18 @@ const m_option_t mp_opts[] = {
OPT_CHOICE("pts-association-mode", user_pts_assoc_mode, 0,
({"auto", 0}, {"decoder", 1}, {"sort", 2})),
OPT_FLAG("initial-audio-sync", initial_audio_sync, 0),
+ OPT_CHOICE("video-sync", video_sync, 0,
+ ({"audio", VS_DEFAULT},
+ {"display-resample", VS_DISP_RESAMPLE},
+ {"display-resample-vdrop", VS_DISP_RESAMPLE_VDROP},
+ {"display-resample-desync", VS_DISP_RESAMPLE_NONE},
+ {"display-vdrop", VS_DISP_VDROP},
+ {"display-desync", VS_DISP_NONE},
+ {"desync", VS_NONE})),
+ OPT_DOUBLE("video-sync-max-video-change", sync_max_video_change,
+ M_OPT_MIN, .min = 0),
+ OPT_DOUBLE("video-sync-max-audio-change", sync_max_audio_change,
+ M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 1),
OPT_CHOICE("hr-seek", hr_seek, 0,
({"no", -1}, {"absolute", 0}, {"yes", 1}, {"always", 1})),
OPT_FLOAT("hr-seek-demuxer-offset", hr_seek_demuxer_offset, 0),
@@ -543,6 +569,7 @@ const m_option_t mp_opts[] = {
OPT_SUBSTRUCT("screenshot", screenshot_image_opts, image_writer_conf, 0),
OPT_STRING("screenshot-template", screenshot_template, 0),
+ OPT_STRING("screenshot-directory", screenshot_directory, 0),
OPT_SUBSTRUCT("input", input_opts, input_config, 0),
@@ -557,8 +584,6 @@ const m_option_t mp_opts[] = {
OPT_SUBSTRUCT("", encode_opts, encode_config, 0),
#endif
- OPT_FLAG("slave-broken", slave_mode, CONF_GLOBAL),
-
OPT_REMOVED("a52drc", "use --ad-lavc-ac3drc=level"),
OPT_REMOVED("afm", "use --ad=..."),
OPT_REPLACED("aspect", "video-aspect"),
@@ -608,7 +633,7 @@ const m_option_t mp_opts[] = {
OPT_REPLACED("sub", "sub-file"),
OPT_REPLACED("subcp", "sub-codepage"),
OPT_REPLACED("subdelay", "sub-delay"),
- OPT_REPLACED("subfile", "sub"),
+ OPT_REPLACED("subfile", "sub-file"),
OPT_REPLACED("subfont-text-scale", "sub-scale"),
OPT_REPLACED("subfont", "sub-text-font"),
OPT_REPLACED("subfps", "sub-fps"),
@@ -634,6 +659,7 @@ const m_option_t mp_opts[] = {
OPT_REPLACED("mkv-subtitle-preroll", "demuxer-mkv-subtitle-preroll"),
OPT_REPLACED("dtshd", "ad-spdif-dtshd"),
OPT_REPLACED("ass-use-margins", "sub-use-margins"),
+ OPT_REPLACED("media-title", "force-media-title"),
{0}
};
@@ -646,7 +672,7 @@ const struct MPOpts mp_default_opts = {
.video_decoders = NULL,
.deinterlace = -1,
.softvol = SOFTVOL_AUTO,
- .softvol_max = 200,
+ .softvol_max = 130,
.mixer_init_volume = -1,
.mixer_init_mute = -1,
.gapless_audio = -1,
@@ -666,7 +692,7 @@ const struct MPOpts mp_default_opts = {
.window_scale = 1.0,
},
.allow_win_drag = 1,
- .wintitle = "mpv - ${?media-title:${media-title}}${!media-title:No file.}",
+ .wintitle = "${?media-title:${media-title}}${!media-title:No file} - mpv",
.heartbeat_interval = 30.0,
.stop_screensaver = 1,
.cursor_autohide_delay = 1000,
@@ -700,22 +726,25 @@ const struct MPOpts mp_default_opts = {
.chapter_merge_threshold = 100,
.chapter_seek_threshold = 5.0,
.hr_seek_framedrop = 1,
+ .sync_max_video_change = 1,
+ .sync_max_audio_change = 0.125,
.load_config = 1,
.position_resume = 1,
.stream_cache = {
.size = -1,
- .def_size = 150000,
+ .def_size = 75000,
.initial = 0,
.seek_min = 500,
+ .back_buffer = 75000,
.file_max = 1024 * 1024,
},
+ .demuxer_max_packs = 16000,
+ .demuxer_max_bytes = 400 * 1024 * 1024,
.demuxer_thread = 1,
- .demuxer_min_packs = 0,
- .demuxer_min_bytes = 0,
.demuxer_min_secs = 1.0,
.network_rtsp_transport = 2,
.network_timeout = 0.0,
- .hls_bitrate = 2,
+ .hls_bitrate = INT_MAX,
.demuxer_min_secs_cache = 10.0,
.cache_pausing = 1,
.chapterrange = {-1, -1},
@@ -729,15 +758,18 @@ const struct MPOpts mp_default_opts = {
.term_osd = 2,
.term_osd_bar_chars = "[-+-]",
.consolecontrols = 1,
+ .playlist_pos = -1,
.play_frames = -1,
.keep_open = 0,
- .audio_id = -1,
- .video_id = -1,
- .sub_id = -1,
- .audio_id_ff = -1,
- .video_id_ff = -1,
- .sub_id_ff = -1,
- .sub2_id = -2,
+ .stream_id = { { [STREAM_AUDIO] = -1,
+ [STREAM_VIDEO] = -1,
+ [STREAM_SUB] = -1, },
+ { [STREAM_AUDIO] = -2,
+ [STREAM_VIDEO] = -2,
+ [STREAM_SUB] = -2, }, },
+ .stream_id_ff = { [STREAM_AUDIO] = -1,
+ [STREAM_VIDEO] = -1,
+ [STREAM_SUB] = -1, },
.audio_display = 1,
.sub_visibility = 1,
.sub_pos = 100,
@@ -763,9 +795,9 @@ const struct MPOpts mp_default_opts = {
.use_embedded_fonts = 1,
.sub_fix_timing = 1,
.sub_cp = "auto",
- .screenshot_template = "shot%n",
+ .screenshot_template = "mpv-shot%n",
- .hwdec_codecs = "h264,vc1,wmv3",
+ .hwdec_codecs = "h264,vc1,wmv3,hevc",
.index_mode = 1,
diff --git a/options/options.h b/options/options.h
index ced5184..5cf144a 100644
--- a/options/options.h
+++ b/options/options.h
@@ -4,6 +4,7 @@
#include <stdbool.h>
#include <stdint.h>
#include "m_option.h"
+#include "common/common.h"
typedef struct mp_vo_opts {
struct m_obj_settings *video_driver_list, *vo_defs;
@@ -41,7 +42,10 @@ typedef struct mp_vo_opts {
float monitor_pixel_aspect;
int force_window_position;
+ // vo_wayland, vo_drm
struct sws_opts *sws_opts;
+ // vo_opengl, vo_opengl_cb
+ int hwdec_preload_api;
} mp_vo_opts;
struct mp_cache_opts {
@@ -49,6 +53,7 @@ struct mp_cache_opts {
int def_size;
int initial;
int seek_min;
+ int back_buffer;
char *file;
int file_max;
};
@@ -108,6 +113,7 @@ typedef struct MPOpts {
char *audio_decoders;
char *video_decoders;
+ char *audio_spdif;
int osd_level;
int osd_duration;
@@ -138,6 +144,9 @@ typedef struct MPOpts {
int correct_pts;
int user_pts_assoc_mode;
int initial_audio_sync;
+ int video_sync;
+ double sync_max_video_change;
+ double sync_max_audio_change;
int hr_seek;
float hr_seek_demuxer_offset;
int hr_seek_framedrop;
@@ -158,6 +167,7 @@ typedef struct MPOpts {
float heartbeat_interval;
int player_idle_mode;
int consolecontrols;
+ int playlist_pos;
struct m_rel_time play_start;
struct m_rel_time play_end;
struct m_rel_time play_length;
@@ -170,15 +180,9 @@ typedef struct MPOpts {
int ignore_path_in_watch_later_config;
int pause;
int keep_open;
- int audio_id;
- int video_id;
- int sub_id;
- int sub2_id;
- int audio_id_ff;
- int video_id_ff;
- int sub_id_ff;
- char **audio_lang;
- char **sub_lang;
+ int stream_id[2][STREAM_TYPE_COUNT];
+ int stream_id_ff[STREAM_TYPE_COUNT];
+ char **stream_lang[STREAM_TYPE_COUNT];
int audio_display;
char **display_tags;
int sub_visibility;
@@ -188,24 +192,27 @@ typedef struct MPOpts {
float sub_speed;
int forced_subs_only;
int stretch_dvd_subs;
+ int stretch_image_subs;
int sub_fix_timing;
char *sub_cp;
char **audio_files;
char *demuxer_name;
+ int demuxer_max_packs;
+ int demuxer_max_bytes;
int demuxer_thread;
- int demuxer_min_packs;
- int demuxer_min_bytes;
double demuxer_min_secs;
char *audio_demuxer_name;
char *sub_demuxer_name;
+ int force_seekable;
double demuxer_min_secs_cache;
int cache_pausing;
struct image_writer_opts *screenshot_image_opts;
char *screenshot_template;
+ char *screenshot_directory;
double force_fps;
int index_mode;
@@ -220,6 +227,7 @@ typedef struct MPOpts {
struct m_obj_settings *af_settings, *af_defs;
int deinterlace;
float movie_aspect;
+ int aspect_method;
int field_dominance;
char **sub_name;
char **sub_paths;
@@ -261,8 +269,6 @@ typedef struct MPOpts {
int w32_priority;
- int slave_mode;
-
int network_cookies_enabled;
char *network_cookies_file;
char *network_useragent;
diff --git a/options/path.c b/options/path.c
index d19b360..08d16fe 100644
--- a/options/path.c
+++ b/options/path.c
@@ -43,16 +43,8 @@
#include "osdep/io.h"
#include "osdep/path.h"
-#define MAX_CONFIG_PATHS 32
-
-static const char *mp_get_forced_home(void *talloc_ctx, const char *type)
-{
- return strcmp(type, "home") == 0 ? getenv("MPV_HOME") : NULL;
-}
-
-// In order of increasing priority: the first hiz has highest priority.
+// In order of decreasing priority: the first has highest priority.
static const mp_get_platform_path_cb path_resolvers[] = {
- mp_get_forced_home,
#if HAVE_COCOA
mp_get_platform_path_osx,
#endif
@@ -64,8 +56,33 @@ static const mp_get_platform_path_cb path_resolvers[] = {
#endif
};
-static const char *mp_get_platform_path(void *talloc_ctx, const char *type)
+// from highest (most preferred) to lowest priority
+static const char *const config_dirs[] = {
+ "home",
+ "old_home",
+ "osxbundle",
+ "global",
+};
+
+// Return a platform specific path using a path type as defined in osdep/path.h.
+// Keep in mind that the only way to free the return value is freeing talloc_ctx
+// (or its children), as this function can return a statically allocated string.
+static const char *mp_get_platform_path(void *talloc_ctx,
+ struct mpv_global *global,
+ const char *type)
{
+ assert(talloc_ctx);
+
+ const char *force_configdir = getenv("MPV_HOME");
+ if (global->opts->force_configdir && global->opts->force_configdir[0])
+ force_configdir = global->opts->force_configdir;
+ if (force_configdir) {
+ for (int n = 0; n < MP_ARRAY_SIZE(config_dirs); n++) {
+ if (strcmp(config_dirs[n], type) == 0)
+ return n == 0 ? force_configdir : NULL;
+ }
+ }
+
for (int n = 0; n < MP_ARRAY_SIZE(path_resolvers); n++) {
const char *path = path_resolvers[n](talloc_ctx, type);
if (path && path[0])
@@ -74,96 +91,66 @@ static const char *mp_get_platform_path(void *talloc_ctx, const char *type)
return NULL;
}
-// Return NULL-terminated array of config directories, from highest to lowest
-// priority
-static char **mp_config_dirs(void *talloc_ctx, struct mpv_global *global)
+char *mp_find_user_config_file(void *talloc_ctx, struct mpv_global *global,
+ const char *filename)
{
- struct MPOpts *opts = global->opts;
-
- char **ret = talloc_zero_array(talloc_ctx, char*, MAX_CONFIG_PATHS + 1);
- int num_ret = 0;
-
- if (!opts->load_config)
- return ret;
-
- if (opts->force_configdir && opts->force_configdir[0]) {
- ret[0] = talloc_strdup(ret, opts->force_configdir);
- return ret;
- }
-
- // from highest (most preferred) to lowest priority
- static const char *const configdirs[] = {
- "home",
- "old_home",
- "osxbundle",
- "global",
- };
-
- for (int n = 0; n < MP_ARRAY_SIZE(configdirs); n++) {
- const char *path = mp_get_platform_path(ret, configdirs[n]);
- if (path && path[0] && num_ret < MAX_CONFIG_PATHS)
- ret[num_ret++] = (char *)path;
- }
-
- MP_VERBOSE(global, "search dirs:");
- for (int n = 0; n < num_ret; n++)
- MP_VERBOSE(global, " %s", ret[n]);
- MP_VERBOSE(global, "\n");
-
- return ret;
-}
-
-char *mp_find_config_file(void *talloc_ctx, struct mpv_global *global,
- const char *filename)
-{
- char *res = NULL;
- char **dirs = mp_config_dirs(NULL, global);
- for (int i = 0; dirs && dirs[i]; i++) {
- char *file = talloc_asprintf(talloc_ctx, "%s/%s", dirs[i], filename);
-
- if (mp_path_exists(file)) {
- res = file;
- break;
- }
-
- talloc_free(file);
- }
- talloc_free(dirs);
-
- MP_VERBOSE(global, "config path: '%s' -> '%s'\n", filename,
- res ? res : "(NULL)");
+ void *tmp = talloc_new(NULL);
+ char *res = (char *)mp_get_platform_path(tmp, global, config_dirs[0]);
+ if (res)
+ res = mp_path_join(talloc_ctx, res, filename);
+ talloc_free(tmp);
+ MP_VERBOSE(global, "config path: '%s' -> '%s'\n", filename, res ? res : "-");
return res;
}
-char **mp_find_all_config_files(void *talloc_ctx, struct mpv_global *global,
- const char *filename)
+static char **mp_find_all_config_files_limited(void *talloc_ctx,
+ struct mpv_global *global,
+ int max_files,
+ const char *filename)
{
- char **ret = talloc_zero_array(talloc_ctx, char*, MAX_CONFIG_PATHS + 1);
+ char **ret = talloc_array(talloc_ctx, char*, 2); // 2 preallocated
int num_ret = 0;
- char **dirs = mp_config_dirs(NULL, global);
- for (int i = 0; dirs && dirs[i]; i++) {
+ for (int i = 0; i < MP_ARRAY_SIZE(config_dirs); i++) {
+ const char *dir = mp_get_platform_path(ret, global, config_dirs[i]);
bstr s = bstr0(filename);
- while (s.len) {
+ while (dir && num_ret < max_files && s.len) {
bstr fn;
bstr_split_tok(s, "|", &fn, &s);
- char *file = talloc_asprintf(ret, "%s/%.*s", dirs[i], BSTR_P(fn));
- if (mp_path_exists(file) && num_ret < MAX_CONFIG_PATHS)
- ret[num_ret++] = file;
+ char *file = mp_path_join_bstr(ret, bstr0(dir), fn);
+ if (mp_path_exists(file)) {
+ MP_VERBOSE(global, "config path: '%.*s' -> '%s'\n",
+ BSTR_P(fn), file);
+ MP_TARRAY_APPEND(NULL, ret, num_ret, file);
+ } else {
+ MP_VERBOSE(global, "config path: '%.*s' -/-> '%s'\n",
+ BSTR_P(fn), file);
+ }
}
}
- talloc_free(dirs);
+
+ MP_TARRAY_GROW(NULL, ret, num_ret);
+ ret[num_ret] = NULL;
for (int n = 0; n < num_ret / 2; n++)
MPSWAP(char*, ret[n], ret[num_ret - n - 1]);
+ return ret;
+}
- MP_VERBOSE(global, "config file: '%s'\n", filename);
-
- for (char** c = ret; *c; c++)
- MP_VERBOSE(global, " -> '%s'\n", *c);
+char **mp_find_all_config_files(void *talloc_ctx, struct mpv_global *global,
+ const char *filename)
+{
+ return mp_find_all_config_files_limited(talloc_ctx, global, 64, filename);
+}
- return ret;
+char *mp_find_config_file(void *talloc_ctx, struct mpv_global *global,
+ const char *filename)
+{
+ char **l = mp_find_all_config_files_limited(talloc_ctx, global, 1, filename);
+ char *r = l && l[0] ? talloc_steal(talloc_ctx, l[0]) : NULL;
+ talloc_free(l);
+ return r;
}
char *mp_get_user_path(void *talloc_ctx, struct mpv_global *global,
@@ -181,7 +168,14 @@ char *mp_get_user_path(void *talloc_ctx, struct mpv_global *global,
if (bstr_equals0(prefix, "~")) {
res = mp_find_config_file(talloc_ctx, global, rest0);
} else if (bstr_equals0(prefix, "")) {
- res = mp_path_join(talloc_ctx, bstr0(getenv("HOME")), rest);
+ res = mp_path_join_bstr(talloc_ctx, bstr0(getenv("HOME")), rest);
+ } else if (bstr_eatstart0(&prefix, "~")) {
+ void *tmp = talloc_new(NULL);
+ char type[80];
+ snprintf(type, sizeof(type), "%.*s", BSTR_P(prefix));
+ const char *p = mp_get_platform_path(tmp, global, type);
+ res = mp_path_join_bstr(talloc_ctx, bstr0(p), rest);
+ talloc_free(tmp);
}
}
}
@@ -228,7 +222,7 @@ char *mp_splitext(const char *path, bstr *root)
return (char *)split + 1;
}
-char *mp_path_join(void *talloc_ctx, struct bstr p1, struct bstr p2)
+char *mp_path_join_bstr(void *talloc_ctx, struct bstr p1, struct bstr p2)
{
if (p1.len == 0)
return bstrdup0(talloc_ctx, p2);
@@ -256,6 +250,11 @@ char *mp_path_join(void *talloc_ctx, struct bstr p1, struct bstr p2)
have_separator ? "" : "/", BSTR_P(p2));
}
+char *mp_path_join(void *talloc_ctx, const char *p1, const char *p2)
+{
+ return mp_path_join_bstr(talloc_ctx, bstr0(p1), bstr0(p2));
+}
+
char *mp_getcwd(void *talloc_ctx)
{
char *wd = talloc_array(talloc_ctx, char, 20);
@@ -329,13 +328,8 @@ void mp_mkdirp(const char *dir)
void mp_mk_config_dir(struct mpv_global *global, char *subdir)
{
- void *tmp = talloc_new(NULL);
- char *dir = mp_config_dirs(tmp, global)[0];
-
- if (dir) {
- dir = talloc_asprintf(tmp, "%s/%s", dir, subdir);
+ char *dir = mp_find_user_config_file(NULL, global, subdir);
+ if (dir)
mp_mkdirp(dir);
- }
-
- talloc_free(tmp);
+ talloc_free(dir);
}
diff --git a/options/path.h b/options/path.h
index 7f9c0cb..763a8dd 100644
--- a/options/path.h
+++ b/options/path.h
@@ -31,6 +31,12 @@ struct mpv_global;
char *mp_find_config_file(void *talloc_ctx, struct mpv_global *global,
const char *filename);
+// Like mp_find_config_file(), but search only the local writable user config
+// dir. Also, this returns a result even if the file does not exist. Calling
+// it with filename="" is equivalent to retrieving the user config dir.
+char *mp_find_user_config_file(void *talloc_ctx, struct mpv_global *global,
+ const char *filename);
+
// Find all instances of the given config file. Paths are returned in order
// from lowest to highest priority. filename can contain multiple names
// separated with '|', with the first having highest priority.
@@ -63,7 +69,8 @@ struct bstr mp_dirname(const char *path);
* for the result. '/' is inserted between the components if needed.
* If p2 is an absolute path then the value of p1 is ignored.
*/
-char *mp_path_join(void *talloc_ctx, struct bstr p1, struct bstr p2);
+char *mp_path_join(void *talloc_ctx, const char *p1, const char *p2);
+char *mp_path_join_bstr(void *talloc_ctx, struct bstr p1, struct bstr p2);
char *mp_getcwd(void *talloc_ctx);
diff --git a/osdep/atomics.h b/osdep/atomics.h
index e0bef8a..1cac2d5 100644
--- a/osdep/atomics.h
+++ b/osdep/atomics.h
@@ -22,8 +22,6 @@
#include <inttypes.h>
#include "config.h"
-#define HAVE_ATOMICS 1
-
#if HAVE_STDATOMIC
#include <stdatomic.h>
#else
@@ -97,9 +95,6 @@ typedef struct { volatile unsigned long long v, t; } atomic_ullong;
#define atomic_compare_exchange_strong(p, old, new) \
((p)->v == *(old) ? ((p)->v = (new), 1) : (*(old) = (p)->v, 0))
-#undef HAVE_ATOMICS
-#define HAVE_ATOMICS 0
-
#endif /* no atomics */
#endif /* else HAVE_STDATOMIC */
diff --git a/osdep/macosx_application.m b/osdep/macosx_application.m
index 595c47e..6e91809 100644
--- a/osdep/macosx_application.m
+++ b/osdep/macosx_application.m
@@ -77,8 +77,7 @@ static void terminate_cocoa_application(void)
{
[super sendEvent:event];
- if (_eventsResponder.inputContext)
- mp_input_wakeup(_eventsResponder.inputContext);
+ [_eventsResponder wakeup];
}
- (id)init
@@ -167,16 +166,10 @@ static void terminate_cocoa_application(void)
- (void)stopMPV:(char *)cmd
{
- struct input_ctx *inputContext = _eventsResponder.inputContext;
- if (inputContext) {
- mp_cmd_t *cmdt = mp_input_parse_cmd(inputContext, bstr0(cmd), "");
- mp_input_queue_cmd(inputContext, cmdt);
- } else {
+ if (![_eventsResponder queueCommand:cmd])
terminate_cocoa_application();
- }
}
-
- (void)registerMenuItem:(NSMenuItem*)menuItem forKey:(MPMenuKey)key
{
[self.menuItems setObject:menuItem forKey:[NSNumber numberWithInt:key]];
diff --git a/osdep/macosx_events.m b/osdep/macosx_events.m
index 23a3e02..ae909d8 100644
--- a/osdep/macosx_events.m
+++ b/osdep/macosx_events.m
@@ -40,7 +40,7 @@
@interface EventsResponder ()
{
struct input_ctx *_inputContext;
- NSCondition *_input_ready;
+ NSCondition *_input_lock;
CFMachPortRef _mk_tap_port;
#if HAVE_APPLE_REMOTE
HIDRemote *_remote;
@@ -188,9 +188,7 @@ void cocoa_uninit_media_keys(void) {
void cocoa_put_key(int keycode)
{
- struct input_ctx *inputContext = [EventsResponder sharedInstance].inputContext;
- if (inputContext)
- mp_input_put_key(inputContext, keycode);
+ [[EventsResponder sharedInstance] putKey:keycode];
}
void cocoa_put_key_event(void *event)
@@ -206,7 +204,7 @@ void cocoa_put_key_with_modifiers(int keycode, int modifiers)
void cocoa_set_input_context(struct input_ctx *input_context)
{
- [EventsResponder sharedInstance].inputContext = input_context;
+ [[EventsResponder sharedInstance] setInputContext:input_context];
}
@implementation EventsResponder
@@ -225,38 +223,64 @@ void cocoa_set_input_context(struct input_ctx *input_context)
{
self = [super init];
if (self) {
- _input_ready = [NSCondition new];
+ _input_lock = [NSCondition new];
}
return self;
}
- (void)waitForInputContext
{
- [_input_ready lock];
- while (!self.inputContext)
- [_input_ready wait];
- [_input_ready unlock];
+ [_input_lock lock];
+ while (!_inputContext)
+ [_input_lock wait];
+ [_input_lock unlock];
}
- (void)setInputContext:(struct input_ctx *)ctx;
{
- [_input_ready lock];
+ [_input_lock lock];
_inputContext = ctx;
- [_input_ready signal];
- [_input_ready unlock];
+ [_input_lock signal];
+ [_input_lock unlock];
}
-- (struct input_ctx *)inputContext
+- (void)wakeup
{
- return _inputContext;
+ [_input_lock lock];
+ if (_inputContext)
+ mp_input_wakeup(_inputContext);
+ [_input_lock unlock];
+}
+
+- (bool)queueCommand:(char *)cmd
+{
+ bool r = false;
+ [_input_lock lock];
+ if (_inputContext) {
+ mp_cmd_t *cmdt = mp_input_parse_cmd(_inputContext, bstr0(cmd), "");
+ mp_input_queue_cmd(_inputContext, cmdt);
+ r = true;
+ }
+ [_input_lock unlock];
+ return r;
+}
+
+- (void)putKey:(int)keycode
+{
+ [_input_lock lock];
+ if (_inputContext)
+ mp_input_put_key(_inputContext, keycode);
+ [_input_lock unlock];
}
- (BOOL)useAltGr
{
- if (self.inputContext)
- return mp_input_use_alt_gr(self.inputContext);
- else
- return YES;
+ BOOL r = YES;
+ [_input_lock lock];
+ if (_inputContext)
+ r = mp_input_use_alt_gr(_inputContext);
+ [_input_lock unlock];
+ return r;
}
- (void)startEventMonitor
@@ -446,7 +470,10 @@ void cocoa_set_input_context(struct input_ctx *input_context)
size_t bytes = [p lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
files_utf8[i] = talloc_memdup(files_utf8, filename, bytes + 1);
}];
- mp_event_drop_files(_inputContext, num_files, files_utf8);
+ [_input_lock lock];
+ if (_inputContext)
+ mp_event_drop_files(_inputContext, num_files, files_utf8, DND_REPLACE);
+ [_input_lock unlock];
talloc_free(files_utf8);
}
diff --git a/osdep/macosx_events_objc.h b/osdep/macosx_events_objc.h
index e9b14ed..70a058e 100644
--- a/osdep/macosx_events_objc.h
+++ b/osdep/macosx_events_objc.h
@@ -27,11 +27,17 @@ struct input_ctx;
+ (EventsResponder *)sharedInstance;
+- (void)setInputContext:(struct input_ctx *)ctx;
+
/// Blocks until inputContext is present.
- (void)waitForInputContext;
-- (void)handleFilesArray:(NSArray *)files;
+- (void)wakeup;
+
+- (bool)queueCommand:(char *)cmd;
-@property(nonatomic, assign) struct input_ctx *inputContext;
+- (void)putKey:(int)keycode;
+
+- (void)handleFilesArray:(NSArray *)files;
@end
diff --git a/osdep/path-macosx.m b/osdep/path-macosx.m
index 618f203..f011289 100644
--- a/osdep/path-macosx.m
+++ b/osdep/path-macosx.m
@@ -28,5 +28,7 @@ const char *mp_get_platform_path_osx(void *talloc_ctx, const char *type)
[pool release];
return res;
}
+ if (strcmp(type, "desktop") == 0 && getenv("HOME"))
+ return mp_path_join(talloc_ctx, getenv("HOME"), "Desktop");
return NULL;
}
diff --git a/osdep/path-unix.c b/osdep/path-unix.c
index c3b70d7..cea4235 100644
--- a/osdep/path-unix.c
+++ b/osdep/path-unix.c
@@ -60,5 +60,7 @@ const char *mp_get_platform_path_unix(void *talloc_ctx, const char *type)
return old_home;
if (strcmp(type, "global") == 0)
return MPV_CONFDIR;
+ if (strcmp(type, "desktop") == 0)
+ return getenv("HOME");
return NULL;
}
diff --git a/osdep/path-win.c b/osdep/path-win.c
index f22b8a3..a735fad 100644
--- a/osdep/path-win.c
+++ b/osdep/path-win.c
@@ -17,6 +17,7 @@
#include <windows.h>
#include <shlobj.h>
+#include <pthread.h>
#include "osdep/path.h"
#include "osdep/io.h"
@@ -24,6 +25,10 @@
// Warning: do not use PATH_MAX. Cygwin messed it up.
+static pthread_once_t path_init_once = PTHREAD_ONCE_INIT;
+
+static char *portable_path;
+
static char *mp_get_win_exe_dir(void *talloc_ctx)
{
wchar_t w_exedir[MAX_PATH + 1] = {0};
@@ -42,12 +47,12 @@ static char *mp_get_win_exe_dir(void *talloc_ctx)
return mp_to_utf8(talloc_ctx, w_exedir);
}
-static char *mp_get_win_exe_subdir(void *talloc_ctx)
+static char *mp_get_win_exe_subdir(void *ta_ctx, const char *name)
{
- return talloc_asprintf(talloc_ctx, "%s/mpv", mp_get_win_exe_dir(talloc_ctx));
+ return talloc_asprintf(ta_ctx, "%s/%s", mp_get_win_exe_dir(ta_ctx), name);
}
-static char *mp_get_win_app_dir(void *talloc_ctx)
+static char *mp_get_win_shell_dir(void *talloc_ctx, int folder)
{
wchar_t w_appdir[MAX_PATH + 1] = {0};
@@ -55,17 +60,41 @@ static char *mp_get_win_app_dir(void *talloc_ctx)
SHGFP_TYPE_CURRENT, w_appdir) != S_OK)
return NULL;
- return talloc_asprintf(talloc_ctx, "%s/mpv", mp_to_utf8(talloc_ctx, w_appdir));
+ return mp_to_utf8(talloc_ctx, w_appdir);
+}
+
+static char *mp_get_win_app_dir(void *talloc_ctx)
+{
+ char *path = mp_get_win_shell_dir(talloc_ctx, CSIDL_APPDATA);
+ return path ? mp_path_join(talloc_ctx, path, "mpv") : NULL;
+}
+
+
+static void path_init(void)
+{
+ void *tmp = talloc_new(NULL);
+ char *path = mp_get_win_exe_subdir(tmp, "portable_config");
+ if (path && mp_path_exists(path))
+ portable_path = talloc_strdup(NULL, path);
+ talloc_free(tmp);
}
const char *mp_get_platform_path_win(void *talloc_ctx, const char *type)
{
- if (strcmp(type, "home") == 0)
- return mp_get_win_app_dir(talloc_ctx);
- if (strcmp(type, "old_home") == 0)
- return mp_get_win_exe_dir(talloc_ctx);
- // Not really true, but serves as a way to return a lowest-priority dir.
- if (strcmp(type, "global") == 0)
- return mp_get_win_exe_subdir(talloc_ctx);
+ pthread_once(&path_init_once, path_init);
+ if (portable_path) {
+ if (strcmp(type, "home") == 0)
+ return portable_path;
+ } else {
+ if (strcmp(type, "home") == 0)
+ return mp_get_win_app_dir(talloc_ctx);
+ if (strcmp(type, "old_home") == 0)
+ return mp_get_win_exe_dir(talloc_ctx);
+ // Not really true, but serves as a way to return a lowest-priority dir.
+ if (strcmp(type, "global") == 0)
+ return mp_get_win_exe_subdir(talloc_ctx, "mpv");
+ }
+ if (strcmp(type, "desktop") == 0)
+ return mp_get_win_shell_dir(talloc_ctx, CSIDL_DESKTOPDIRECTORY);
return NULL;
}
diff --git a/osdep/path.h b/osdep/path.h
index 59a3ba7..f38074b 100644
--- a/osdep/path.h
+++ b/osdep/path.h
@@ -7,8 +7,9 @@
// The following type values are defined:
// "home" the native mpv-specific user config dir
// "old_home" same as "home", but lesser priority (compatibility)
-// "osxbundle" OSX bundle path
+// "osxbundle" OSX bundle resource path
// "global" the least priority, global config file location
+// "desktop" path to desktop contents
//
// It is allowed to return a static string, so the caller must set talloc_ctx
// to something other than NULL to avoid memory leaks.
diff --git a/osdep/subprocess-posix.c b/osdep/subprocess-posix.c
index 9d565f4..16f9735 100644
--- a/osdep/subprocess-posix.c
+++ b/osdep/subprocess-posix.c
@@ -30,8 +30,6 @@
#include "common/common.h"
#include "stream/stream.h"
-// Normally, this must be declared manually, but glibc is retarded
-// resulting in a warning.
extern char **environ;
// A silly helper: automatically skips entries with negative FDs
@@ -66,6 +64,8 @@ int mp_subprocess(char **args, struct mp_cancel *cancel, void *ctx,
int p_stderr[2] = {-1, -1};
int devnull = -1;
pid_t pid = -1;
+ bool spawned = false;
+ bool killed_by_us = false;
if (on_stdout && mp_make_cloexec_pipe(p_stdout) < 0)
goto done;
@@ -91,6 +91,7 @@ int mp_subprocess(char **args, struct mp_cancel *cancel, void *ctx,
pid = -1;
goto done;
}
+ spawned = true;
close(p_stdout[1]);
p_stdout[1] = -1;
@@ -126,6 +127,7 @@ int mp_subprocess(char **args, struct mp_cancel *cancel, void *ctx,
}
if (fds[2].revents) {
kill(pid, SIGKILL);
+ killed_by_us = true;
break;
}
}
@@ -145,12 +147,15 @@ done:
close(p_stderr[1]);
close(devnull);
- if (WIFEXITED(status) && WEXITSTATUS(status) != 127) {
+ if (!spawned || (WIFEXITED(status) && WEXITSTATUS(status) == 127)) {
+ *error = "init";
+ status = -1;
+ } else if (WIFEXITED(status)) {
*error = NULL;
status = WEXITSTATUS(status);
} else {
- *error = WEXITSTATUS(status) == 127 ? "init" : "killed";
- status = -1;
+ *error = "killed";
+ status = killed_by_us ? MP_SUBPROCESS_EKILLED_BY_US : -1;
}
return status;
diff --git a/osdep/subprocess-win.c b/osdep/subprocess-win.c
index a5be9e5..3f03309 100644
--- a/osdep/subprocess-win.c
+++ b/osdep/subprocess-win.c
@@ -357,6 +357,7 @@ int mp_subprocess(char **args, struct mp_cancel *cancel, void *ctx,
if (pi.hProcess) {
TerminateProcess(pi.hProcess, 1);
*error = "killed";
+ status = MP_SUBPROCESS_EKILLED_BY_US;
goto done;
}
break;
diff --git a/osdep/subprocess.h b/osdep/subprocess.h
index 1bd5afe..33c4013 100644
--- a/osdep/subprocess.h
+++ b/osdep/subprocess.h
@@ -28,6 +28,8 @@ typedef void (*subprocess_read_cb)(void *ctx, char *data, size_t size);
int mp_subprocess(char **args, struct mp_cancel *cancel, void *ctx,
subprocess_read_cb on_stdout, subprocess_read_cb on_stderr,
char **error);
+// mp_subprocess return values. -1 is a generic error code.
+#define MP_SUBPROCESS_EKILLED_BY_US -2
struct mp_log;
void mp_subprocess_detached(struct mp_log *log, char **args);
diff --git a/osdep/terminal-unix.c b/osdep/terminal-unix.c
index f43376b..3c2784b 100644
--- a/osdep/terminal-unix.c
+++ b/osdep/terminal-unix.c
@@ -386,20 +386,19 @@ static void *terminal_thread(void *ptr)
mpthread_set_name("terminal");
bool stdin_ok = read_terminal; // if false, we still wait for SIGTERM
while (1) {
+ getch2_poll();
struct pollfd fds[2] = {
{.events = POLLIN, .fd = death_pipe[0]},
{.events = POLLIN, .fd = STDIN_FILENO},
};
- // Wait with some timeout, so we can call getch2_poll() frequently.
- poll(fds, stdin_ok ? 2 : 1, 1000);
+ poll(fds, stdin_ok ? 2 : 1, -1);
if (fds[0].revents)
break;
if (fds[1].revents)
stdin_ok = getch2(input_ctx);
- getch2_poll();
}
// Important if we received SIGTERM, rather than regular quit.
- struct mp_cmd *cmd = mp_input_parse_cmd(input_ctx, bstr0("quit"), "");
+ struct mp_cmd *cmd = mp_input_parse_cmd(input_ctx, bstr0("quit 4"), "");
if (cmd)
mp_input_queue_cmd(input_ctx, cmd);
return NULL;
@@ -488,9 +487,6 @@ int terminal_init(void)
setsigaction(SIGTTIN, SIG_IGN, 0, true);
setsigaction(SIGTTOU, SIG_IGN, 0, true);
- // get sane behavior, instead of hysteric UNIX-nonsense
- setsigaction(SIGPIPE, SIG_IGN, 0, true);
-
getch2_poll();
return 0;
diff --git a/osdep/terminal-win.c b/osdep/terminal-win.c
index e6b17fd..8dd2258 100644
--- a/osdep/terminal-win.c
+++ b/osdep/terminal-win.c
@@ -92,7 +92,7 @@ static void read_input(void)
case KEY_EVENT: {
KEY_EVENT_RECORD *record = &eventbuffer[i].Event.KeyEvent;
- /*only a pressed key is interresting for us*/
+ /*only a pressed key is interesting for us*/
if (record->bKeyDown) {
UINT vkey = record->wVirtualKeyCode;
bool ext = record->dwControlKeyState & ENHANCED_KEY;
diff --git a/osdep/timer-win2.c b/osdep/timer-win2.c
index b87456f..b198395 100644
--- a/osdep/timer-win2.c
+++ b/osdep/timer-win2.c
@@ -23,6 +23,8 @@
#include <stdlib.h>
#include "timer.h"
+static LARGE_INTEGER perf_freq;
+
void mp_sleep_us(int64_t us)
{
if (us < 0)
@@ -35,32 +37,20 @@ void mp_sleep_us(int64_t us)
Sleep(us / 1000);
}
-#if defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0 && defined(CLOCK_MONOTONIC)
-uint64_t mp_raw_time_us(void)
-{
- struct timespec ts;
- if (clock_gettime(CLOCK_MONOTONIC, &ts))
- abort();
- return ts.tv_sec * 1000000LL + ts.tv_nsec / 1000;
-}
-#else
uint64_t mp_raw_time_us(void)
{
- struct timeval tv;
- gettimeofday(&tv,NULL);
- return tv.tv_sec * 1000000LL + tv.tv_usec;
-}
-#endif
+ LARGE_INTEGER perf_count;
+ QueryPerformanceCounter(&perf_count);
-static void restore_timer(void)
-{
- // The MSDN documents that begin/end "must" be matched. This satisfies
- // this requirement.
- timeEndPeriod(1);
+ // Convert QPC units (1/perf_freq seconds) to microseconds. This will work
+ // without overflow because the QPC value is guaranteed not to roll-over
+ // within 100 years, so perf_freq must be less than 2.9*10^9.
+ return perf_count.QuadPart / perf_freq.QuadPart * 1000000 +
+ perf_count.QuadPart % perf_freq.QuadPart * 1000000 / perf_freq.QuadPart;
}
void mp_raw_time_init(void)
{
+ QueryPerformanceFrequency(&perf_freq);
timeBeginPeriod(1); // request 1ms timer resolution
- atexit(restore_timer);
}
diff --git a/osdep/timer.c b/osdep/timer.c
index 053d12d..32f0172 100644
--- a/osdep/timer.c
+++ b/osdep/timer.c
@@ -48,7 +48,10 @@ void mp_time_init(void)
int64_t mp_time_us(void)
{
- return mp_raw_time_us() - raw_time_offset;
+ int64_t r = mp_raw_time_us() - raw_time_offset;
+ if (r < MP_START_TIME)
+ r = MP_START_TIME;
+ return r;
}
double mp_time_sec(void)
diff --git a/osdep/win32/include/pthread.h b/osdep/win32/include/pthread.h
index 2e9436d..7b82eb2 100644
--- a/osdep/win32/include/pthread.h
+++ b/osdep/win32/include/pthread.h
@@ -25,11 +25,12 @@
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
typedef struct {
- volatile LONG requires_init;
+ char static_mutex;
+ INIT_ONCE static_init;
CRITICAL_SECTION cs;
} pthread_mutex_t;
-#define PTHREAD_MUTEX_INITIALIZER {1}
+#define PTHREAD_MUTEX_INITIALIZER {1, INIT_ONCE_STATIC_INIT}
#define pthread_mutexattr_t int
#define pthread_mutexattr_destroy(attr) (void)0
diff --git a/osdep/win32/pthread.c b/osdep/win32/pthread.c
index ee4b5a1..4838df4 100644
--- a/osdep/win32/pthread.c
+++ b/osdep/win32/pthread.c
@@ -18,27 +18,6 @@
#include <errno.h>
#include <sys/time.h>
-// We keep this around to avoid active waiting while handling static
-// initializers.
-static pthread_once_t init_cs_once = PTHREAD_ONCE_INIT;
-static CRITICAL_SECTION init_cs;
-
-static void init_init_cs(void)
-{
- InitializeCriticalSection(&init_cs);
-}
-
-static void init_lock(void)
-{
- pthread_once(&init_cs_once, init_init_cs);
- EnterCriticalSection(&init_cs);
-}
-
-static void init_unlock(void)
-{
- LeaveCriticalSection(&init_cs);
-}
-
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
{
BOOL pending;
@@ -66,12 +45,14 @@ int pthread_mutex_init(pthread_mutex_t *restrict mutex,
int pthread_mutex_lock(pthread_mutex_t *mutex)
{
- if (mutex->requires_init) {
- init_lock();
- if (mutex->requires_init)
+ if (mutex->static_mutex) {
+ BOOL pending;
+ if (!InitOnceBeginInitialize(&mutex->static_init, 0, &pending, NULL))
+ abort();
+ if (pending) {
InitializeCriticalSection(&mutex->cs);
- _InterlockedAnd(&mutex->requires_init, 0);
- init_unlock();
+ InitOnceComplete(&mutex->static_init, 0, NULL);
+ }
}
EnterCriticalSection(&mutex->cs);
return 0;
diff --git a/player/audio.c b/player/audio.c
index ea729ce..92c4bba 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -45,9 +45,14 @@
static int update_playback_speed_filters(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
- double speed = opts->playback_speed;
+ double speed = mpctx->audio_speed;
struct af_stream *afs = mpctx->d_audio->afilter;
+ // Use pitch correction only for speed adjustments by the user, not minor
+ // sync correction ones.
+ bool use_pitch_correction = opts->pitch_correction &&
+ opts->playback_speed != 1.0;
+
// Make sure only exactly one filter changes speed; resetting them all
// and setting 1 filter is the easiest way to achieve this.
af_control_all(afs, AF_CONTROL_SET_PLAYBACK_SPEED, &(double){1});
@@ -63,7 +68,7 @@ static int update_playback_speed_filters(struct MPContext *mpctx)
return 0;
int method = AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE;
- if (opts->pitch_correction)
+ if (use_pitch_correction)
method = AF_CONTROL_SET_PLAYBACK_SPEED;
if (!af_control_any_rev(afs, method, &speed)) {
@@ -88,6 +93,8 @@ static int recreate_audio_filters(struct MPContext *mpctx)
if (update_playback_speed_filters(mpctx) < 0) {
mpctx->opts->playback_speed = 1.0;
+ mpctx->speed_factor_a = 1.0;
+ mpctx->audio_speed = 1.0;
mp_notify(mpctx, MP_EVENT_CHANGE_ALL, NULL);
}
@@ -117,14 +124,20 @@ int reinit_audio_filters(struct MPContext *mpctx)
return 1;
}
-void set_playback_speed(struct MPContext *mpctx, double new_speed)
+// Call this if opts->playback_speed or mpctx->speed_correction changes.
+void update_playback_speed(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
- // Adjust time until next frame flip for nosound mode
- mpctx->time_frame *= opts->playback_speed / new_speed;
+ double old_speed_factor_a = mpctx->speed_factor_a;
+ double old_audio_speed = mpctx->audio_speed;
+
+ mpctx->audio_speed = opts->playback_speed * mpctx->speed_factor_a;
+ mpctx->video_speed = opts->playback_speed * mpctx->speed_factor_v;
- opts->playback_speed = new_speed;
+ if (mpctx->speed_factor_a == old_speed_factor_a &&
+ mpctx->audio_speed == old_audio_speed)
+ return;
if (!mpctx->d_audio || mpctx->d_audio->afilter->initialized < 1)
return;
@@ -150,6 +163,8 @@ void uninit_audio_out(struct MPContext *mpctx)
ao_drain(mpctx->ao);
mixer_uninit_audio(mpctx->mixer);
ao_uninit(mpctx->ao);
+
+ mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
}
mpctx->ao = NULL;
talloc_free(mpctx->ao_decoder_fmt);
@@ -166,6 +181,8 @@ void uninit_audio_chain(struct MPContext *mpctx)
mpctx->ao_buffer = NULL;
mpctx->audio_status = STATUS_EOF;
reselect_demux_streams(mpctx);
+
+ mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
}
}
@@ -190,8 +207,9 @@ void reinit_audio_chain(struct MPContext *mpctx)
mpctx->d_audio->pool = mp_audio_pool_create(mpctx->d_audio);
mpctx->d_audio->afilter = af_new(mpctx->global);
mpctx->d_audio->afilter->replaygain_data = sh->audio->replaygain_data;
+ mpctx->d_audio->spdif_passthrough = true;
mpctx->ao_buffer = mp_audio_buffer_create(NULL);
- if (!audio_init_best_codec(mpctx->d_audio, opts->audio_decoders))
+ if (!audio_init_best_codec(mpctx->d_audio))
goto init_error;
reset_audio_state(mpctx);
@@ -224,7 +242,7 @@ void reinit_audio_chain(struct MPContext *mpctx)
afs->output = (struct mp_audio){0};
if (mpctx->ao) {
ao_get_format(mpctx->ao, &afs->output);
- } else if (!AF_FORMAT_IS_SPECIAL(in_format.format)) {
+ } else if (af_fmt_is_pcm(in_format.format)) {
afs->output.rate = opts->force_srate;
mp_audio_set_format(&afs->output, opts->audio_output_format);
mp_audio_set_channels(&afs->output, &opts->audio_output_channels);
@@ -249,16 +267,38 @@ void reinit_audio_chain(struct MPContext *mpctx)
mpctx->ao = ao_init_best(mpctx->global, mpctx->input,
mpctx->encode_lavc_ctx, afs->output.rate,
afs->output.format, afs->output.channels);
- struct ao *ao = mpctx->ao;
- if (!ao) {
+
+ struct mp_audio fmt = {0};
+ if (mpctx->ao)
+ ao_get_format(mpctx->ao, &fmt);
+
+ // Verify passthrough format was not changed.
+ if (mpctx->ao && af_fmt_is_spdif(afs->output.format)) {
+ if (!mp_audio_config_equals(&afs->output, &fmt)) {
+ MP_ERR(mpctx, "Passthrough format unsupported.\n");
+ ao_uninit(mpctx->ao);
+ mpctx->ao = NULL;
+ }
+ }
+
+ if (!mpctx->ao) {
+ // If spdif was used, try to fallback to PCM.
+ if (af_fmt_is_spdif(afs->output.format) &&
+ mpctx->d_audio->spdif_passthrough)
+ {
+ mpctx->d_audio->spdif_passthrough = false;
+ if (!audio_init_best_codec(mpctx->d_audio))
+ goto init_error;
+ reset_audio_state(mpctx);
+ reinit_audio_chain(mpctx);
+ return;
+ }
+
MP_ERR(mpctx, "Could not open/initialize audio device -> no sound.\n");
mpctx->error_playing = MPV_ERROR_AO_INIT_FAILED;
goto init_error;
}
- struct mp_audio fmt;
- ao_get_format(ao, &fmt);
-
mp_audio_buffer_reinit(mpctx->ao_buffer, &fmt);
afs->output = fmt;
if (!mp_audio_config_equals(&afs->output, &afs->filter_output))
@@ -267,16 +307,16 @@ void reinit_audio_chain(struct MPContext *mpctx)
mpctx->ao_decoder_fmt = talloc(NULL, struct mp_audio);
*mpctx->ao_decoder_fmt = in_format;
- MP_INFO(mpctx, "AO: [%s] %s\n", ao_get_name(ao),
+ MP_INFO(mpctx, "AO: [%s] %s\n", ao_get_name(mpctx->ao),
mp_audio_config_to_str(&fmt));
- MP_VERBOSE(mpctx, "AO: Description: %s\n", ao_get_description(ao));
+ MP_VERBOSE(mpctx, "AO: Description: %s\n", ao_get_description(mpctx->ao));
update_window_title(mpctx, true);
}
if (recreate_audio_filters(mpctx) < 0)
goto init_error;
- set_playback_speed(mpctx, opts->playback_speed);
+ update_playback_speed(mpctx);
return;
@@ -325,9 +365,9 @@ double written_audio_pts(struct MPContext *mpctx)
// accept everything to internal buffers yet
buffered_output += mp_audio_buffer_seconds(mpctx->ao_buffer);
- // Filters divide audio length by playback_speed, so multiply by it
+ // Filters divide audio length by audio_speed, so multiply by it
// to get the length in original units without speedup or slowdown
- a_pts -= buffered_output * mpctx->opts->playback_speed;
+ a_pts -= buffered_output * mpctx->audio_speed;
return a_pts +
get_track_video_offset(mpctx, mpctx->current_track[0][STREAM_AUDIO]);
@@ -339,11 +379,10 @@ double playing_audio_pts(struct MPContext *mpctx)
double pts = written_audio_pts(mpctx);
if (pts == MP_NOPTS_VALUE || !mpctx->ao)
return pts;
- return pts - mpctx->opts->playback_speed * ao_get_delay(mpctx->ao);
+ return pts - mpctx->audio_speed * ao_get_delay(mpctx->ao);
}
-static int write_to_ao(struct MPContext *mpctx, struct mp_audio *data, int flags,
- double pts)
+static int write_to_ao(struct MPContext *mpctx, struct mp_audio *data, int flags)
{
if (mpctx->paused)
return 0;
@@ -355,7 +394,7 @@ static int write_to_ao(struct MPContext *mpctx, struct mp_audio *data, int flags
#endif
if (data->samples == 0)
return 0;
- double real_samplerate = out_format.rate / mpctx->opts->playback_speed;
+ double real_samplerate = out_format.rate / mpctx->audio_speed;
int played = ao_play(mpctx->ao, data->planes, data->samples, flags);
assert(played <= data->samples);
if (played > 0) {
@@ -383,7 +422,7 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
struct mp_audio out_format = {0};
ao_get_format(mpctx->ao, &out_format);
- double play_samplerate = out_format.rate / opts->playback_speed;
+ double play_samplerate = out_format.rate / mpctx->audio_speed;
if (!opts->initial_audio_sync) {
mpctx->audio_status = STATUS_FILLING;
@@ -424,7 +463,7 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
return true;
}
-static void do_fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
+void fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
{
struct MPOpts *opts = mpctx->opts;
struct dec_audio *d_audio = mpctx->d_audio;
@@ -460,7 +499,7 @@ static void do_fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
struct mp_audio out_format = {0};
ao_get_format(mpctx->ao, &out_format);
- double play_samplerate = out_format.rate / opts->playback_speed;
+ double play_samplerate = out_format.rate / mpctx->audio_speed;
// If audio is infinitely fast, somehow try keeping approximate A/V sync.
if (mpctx->audio_status == STATUS_PLAYING && ao_untimed(mpctx->ao) &&
@@ -478,6 +517,7 @@ static void do_fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
}
int status = AD_OK;
+ bool working = false;
if (playsize > mp_audio_buffer_samples(mpctx->ao_buffer)) {
status = audio_decode(d_audio, mpctx->ao_buffer, playsize);
if (status == AD_WAIT)
@@ -495,6 +535,7 @@ static void do_fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
}
if (status == AD_ERR)
mpctx->sleeptime = 0;
+ working = true;
}
// If EOF was reached before, but now something can be decoded, try to
@@ -527,7 +568,8 @@ static void do_fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
mpctx->audio_status = STATUS_FILLING;
if (status != AD_OK && !mp_audio_buffer_samples(mpctx->ao_buffer))
mpctx->audio_status = STATUS_EOF;
- mpctx->sleeptime = 0;
+ if (working || end_sync)
+ mpctx->sleeptime = 0;
return; // continue on next iteration
}
@@ -568,18 +610,15 @@ static void do_fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
if (audio_eof && !opts->gapless_audio)
playflags |= AOPLAY_FINAL_CHUNK;
- if (mpctx->paused)
- playsize = 0;
-
struct mp_audio data;
mp_audio_buffer_peek(mpctx->ao_buffer, &data);
- data.samples = MPMIN(data.samples, playsize);
- int played = write_to_ao(mpctx, &data, playflags, written_audio_pts(mpctx));
+ data.samples = MPMIN(data.samples, mpctx->paused ? 0 : playsize);
+ int played = write_to_ao(mpctx, &data, playflags);
assert(played >= 0 && played <= data.samples);
mp_audio_buffer_skip(mpctx->ao_buffer, played);
mpctx->audio_status = STATUS_PLAYING;
- if (audio_eof) {
+ if (audio_eof && !playsize) {
mpctx->audio_status = STATUS_DRAINING;
// Wait until the AO has played all queued data. In the gapless case,
// we trigger EOF immediately, and let it play asynchronously.
@@ -588,15 +627,6 @@ static void do_fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
}
}
-void fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
-{
- do_fill_audio_out_buffers(mpctx, endpts);
- // Run audio playback state machine again to display the actual audio PTS
- // as current time on OSD in audio-only mode in most situations.
- if (mpctx->audio_status == STATUS_SYNCING)
- do_fill_audio_out_buffers(mpctx, endpts);
-}
-
// Drop data queued for output, or which the AO is currently outputting.
void clear_audio_output_buffers(struct MPContext *mpctx)
{
diff --git a/player/client.c b/player/client.c
index b6c3ade..46c4add 100644
--- a/player/client.c
+++ b/player/client.c
@@ -468,20 +468,7 @@ mpv_handle *mpv_create(void)
if (ctx) {
ctx->owner = true;
ctx->fuzzy_initialized = true;
- // Set some defaults.
- mpv_set_option_string(ctx, "config", "no");
- mpv_set_option_string(ctx, "idle", "yes");
- mpv_set_option_string(ctx, "terminal", "no");
- mpv_set_option_string(ctx, "input-terminal", "no");
- mpv_set_option_string(ctx, "osc", "no");
- mpv_set_option_string(ctx, "ytdl", "no");
- mpv_set_option_string(ctx, "input-default-bindings", "no");
- mpv_set_option_string(ctx, "input-vo-keyboard", "no");
- mpv_set_option_string(ctx, "input-lirc", "no");
- mpv_set_option_string(ctx, "input-appleremote", "no");
- mpv_set_option_string(ctx, "input-media-keys", "no");
- mpv_set_option_string(ctx, "input-app-events", "no");
- mpv_set_option_string(ctx, "stop-playback-on-init-failure", "yes");
+ m_config_set_profile(mpctx->mconfig, "libmpv", 0);
} else {
mp_destroy(mpctx);
}
@@ -1206,7 +1193,7 @@ static void getproperty_fn(void *arg)
char *s = NULL;
err = mp_property_do(req->name, M_PROPERTY_GET_STRING, &s, req->mpctx);
if (err == M_PROPERTY_OK)
- *(char **)req->data = s;
+ *(char **)data = s;
break;
}
case MPV_FORMAT_NODE:
@@ -1536,6 +1523,9 @@ int mpv_request_log_messages(mpv_handle *ctx, const char *min_level)
break;
}
}
+ if (strcmp(min_level, "terminal-default") == 0)
+ level = MP_LOG_BUFFER_MSGL_TERM;
+
if (level < 0 && strcmp(min_level, "no") != 0)
return MPV_ERROR_INVALID_PARAMETER;
@@ -1560,6 +1550,7 @@ static bool gen_log_message_event(struct mpv_handle *ctx)
if (msg) {
struct mpv_event_log_message *cmsg =
talloc_ptrtype(ctx->cur_event, cmsg);
+ talloc_steal(cmsg, msg);
*cmsg = (struct mpv_event_log_message){
.prefix = msg->prefix,
.level = mp_log_levels[msg->level],
@@ -1610,7 +1601,7 @@ static const char *const err_table[] = {
[-MPV_ERROR_LOADING_FAILED] = "loading failed",
[-MPV_ERROR_AO_INIT_FAILED] = "audio output initialization failed",
[-MPV_ERROR_VO_INIT_FAILED] = "audio output initialization failed",
- [-MPV_ERROR_NOTHING_TO_PLAY] = "the file has no audio or video data",
+ [-MPV_ERROR_NOTHING_TO_PLAY] = "no audio or video data found",
[-MPV_ERROR_UNKNOWN_FORMAT] = "unrecognized file format",
[-MPV_ERROR_UNSUPPORTED] = "not supported",
[-MPV_ERROR_NOT_IMPLEMENTED] = "operation not implemented",
@@ -1677,7 +1668,7 @@ void kill_video(struct mp_client_api *client_api)
{
struct MPContext *mpctx = client_api->mpctx;
mp_dispatch_lock(mpctx->dispatch);
- mp_switch_track(mpctx, STREAM_VIDEO, NULL);
+ mp_switch_track(mpctx, STREAM_VIDEO, NULL, 0);
uninit_video_out(mpctx);
mp_dispatch_unlock(mpctx->dispatch);
}
diff --git a/player/command.c b/player/command.c
index 4b1979b..8514be9 100644
--- a/player/command.c
+++ b/player/command.c
@@ -75,6 +75,9 @@ struct command_ctx {
double prev_pts;
+ char **warned_deprecated;
+ int num_warned_deprecated;
+
struct cycle_counter *cycle_counters;
int num_cycle_counters;
@@ -107,7 +110,8 @@ struct hook_handler {
bool active; // hook is currently in progress (only 1 at a time for now)
};
-static int edit_filters(struct MPContext *mpctx, enum stream_type mediatype,
+static int edit_filters(struct MPContext *mpctx, struct mp_log *log,
+ enum stream_type mediatype,
const char *cmd, const char *arg);
static int set_filters(struct MPContext *mpctx, enum stream_type mediatype,
struct m_obj_settings *new_chain);
@@ -275,9 +279,8 @@ static int mp_property_playback_speed(void *ctx, struct m_property *prop,
double speed = mpctx->opts->playback_speed;
switch (action) {
case M_PROPERTY_SET: {
- double new_speed = *(double *)arg;
- if (speed != new_speed)
- set_playback_speed(mpctx, new_speed);
+ mpctx->opts->playback_speed = *(double *)arg;
+ update_playback_speed(mpctx);
return M_PROPERTY_OK;
}
case M_PROPERTY_PRINT:
@@ -287,6 +290,33 @@ static int mp_property_playback_speed(void *ctx, struct m_property *prop,
return mp_property_generic_option(mpctx, prop, action, arg);
}
+static int mp_property_av_speed_correction(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ char *type = prop->priv;
+ double val = 0;
+ switch (type[0]) {
+ case 'a': val = mpctx->speed_factor_a; break;
+ case 'v': val = mpctx->speed_factor_v; break;
+ default: abort();
+ }
+
+ if (action == M_PROPERTY_PRINT) {
+ *(char **)arg = talloc_asprintf(NULL, "%+.05f%%", (val - 1) * 100);
+ return M_PROPERTY_OK;
+ }
+
+ return m_property_double_ro(action, arg, val);
+}
+
+static int mp_property_display_sync_active(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ return m_property_flag_ro(action, arg, mpctx->display_sync_active);
+}
+
/// filename with path (RO)
static int mp_property_path(void *ctx, struct m_property *prop,
int action, void *arg)
@@ -360,8 +390,6 @@ static int mp_property_media_title(void *ctx, struct m_property *prop,
name = mpctx->opts->media_title;
if (name && name[0])
return m_property_strdup_ro(action, arg, name);
- if (name && name[0])
- return m_property_strdup_ro(action, arg, name);
if (mpctx->master_demuxer) {
name = mp_tags_get_str(mpctx->master_demuxer->metadata, "title");
if (name && name[0])
@@ -370,6 +398,8 @@ static int mp_property_media_title(void *ctx, struct m_property *prop,
if (name && name[0])
return m_property_strdup_ro(action, arg, name);
}
+ if (mpctx->playing && mpctx->playing->title)
+ return m_property_strdup_ro(action, arg, mpctx->playing->title);
return mp_property_filename(ctx, prop, action, arg);
}
@@ -484,9 +514,8 @@ static int property_time(int action, void *arg, double time)
return M_PROPERTY_NOT_IMPLEMENTED;
}
-/// Media length in seconds (RO)
-static int mp_property_length(void *ctx, struct m_property *prop,
- int action, void *arg)
+static int mp_property_duration(void *ctx, struct m_property *prop,
+ int action, void *arg)
{
MPContext *mpctx = ctx;
double len = get_time_length(mpctx);
@@ -503,8 +532,6 @@ static int mp_property_avsync(void *ctx, struct m_property *prop,
MPContext *mpctx = ctx;
if (!mpctx->d_audio || !mpctx->d_video)
return M_PROPERTY_UNAVAILABLE;
- if (mpctx->last_av_difference == MP_NOPTS_VALUE)
- return M_PROPERTY_UNAVAILABLE;
if (action == M_PROPERTY_PRINT) {
*(char **)arg = talloc_asprintf(NULL, "%7.3f", mpctx->last_av_difference);
return M_PROPERTY_OK;
@@ -545,12 +572,22 @@ static int mp_property_vo_drop_frame_count(void *ctx, struct m_property *prop,
return m_property_int_ro(action, arg, vo_get_drop_count(mpctx->video_out));
}
+static int mp_property_vo_missed_frame_count(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ if (!mpctx->d_video)
+ return M_PROPERTY_UNAVAILABLE;
+
+ return m_property_int_ro(action, arg, vo_get_missed_count(mpctx->video_out));
+}
+
/// Current position in percent (RW)
static int mp_property_percent_pos(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- if (!mpctx->num_sources)
+ if (!mpctx->playback_initialized)
return M_PROPERTY_UNAVAILABLE;
switch (action) {
@@ -600,7 +637,7 @@ static int mp_property_time_pos(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- if (!mpctx->num_sources)
+ if (!mpctx->playback_initialized)
return M_PROPERTY_UNAVAILABLE;
if (action == M_PROPERTY_SET) {
@@ -638,7 +675,7 @@ static int mp_property_playtime_remaining(void *ctx, struct m_property *prop,
if (!time_remaining(mpctx, &remaining))
return M_PROPERTY_UNAVAILABLE;
- double speed = mpctx->opts->playback_speed;
+ double speed = mpctx->video_speed;
return property_time(action, arg, remaining / speed);
}
@@ -646,9 +683,14 @@ static int mp_property_playback_time(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- if (!mpctx->num_sources)
+ if (!mpctx->playback_initialized)
return M_PROPERTY_UNAVAILABLE;
+ if (action == M_PROPERTY_SET) {
+ double target = get_start_time(mpctx) + *(double *)arg;
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, target, MPSEEK_DEFAULT, true);
+ return M_PROPERTY_OK;
+ }
return property_time(action, arg, get_playback_time(mpctx));
}
@@ -678,30 +720,13 @@ static int mp_property_disc_title(void *ctx, struct m_property *prop,
title = *(int*)arg;
if (demux_stream_control(d, STREAM_CTRL_SET_CURRENT_TITLE, &title) < 0)
return M_PROPERTY_NOT_IMPLEMENTED;
- mpctx->stop_play = PT_RELOAD_DEMUXER;
+ if (!mpctx->stop_play)
+ mpctx->stop_play = PT_RELOAD_FILE;
return M_PROPERTY_OK;
}
return M_PROPERTY_NOT_IMPLEMENTED;
}
-static int mp_property_disc_menu(void *ctx, struct m_property *prop,
- int action, void *arg)
-{
- MPContext *mpctx = ctx;
- int state = mp_nav_in_menu(mpctx);
- if (state < 0)
- return M_PROPERTY_UNAVAILABLE;
- return m_property_flag_ro(action, arg, !!state);
-}
-
-static int mp_property_mouse_on_button(void *ctx, struct m_property *prop,
- int action, void *arg)
-{
- MPContext *mpctx = ctx;
- bool on = mp_nav_mouse_on_button(mpctx);
- return m_property_flag_ro(action, arg, on);
-}
-
/// Current chapter (RW)
static int mp_property_chapter(void *ctx, struct m_property *prop,
int action, void *arg)
@@ -757,10 +782,16 @@ static int mp_property_chapter(void *ctx, struct m_property *prop,
if (mpctx->opts->keep_open) {
seek_to_last_frame(mpctx);
} else {
- mpctx->stop_play = PT_NEXT_ENTRY;
+ if (!mpctx->stop_play)
+ mpctx->stop_play = PT_NEXT_ENTRY;
}
} else {
- mp_seek_chapter(mpctx, chapter);
+ double pts = chapter_start_time(mpctx, chapter);
+ if (pts != MP_NOPTS_VALUE) {
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, pts, MPSEEK_DEFAULT, true);
+ mpctx->last_chapter_seek = chapter;
+ mpctx->last_chapter_pts = pts;
+ }
}
return M_PROPERTY_OK;
}
@@ -779,7 +810,6 @@ static int get_chapter_entry(int item, int action, void *arg, void *ctx)
};
int r = m_property_read_sub(props, action, arg);
- talloc_free(name);
return r;
}
@@ -789,7 +819,7 @@ static int mp_property_list_chapters(void *ctx, struct m_property *prop,
MPContext *mpctx = ctx;
int count = get_chapter_count(mpctx);
if (action == M_PROPERTY_PRINT) {
- int cur = mpctx->num_sources ? get_current_chapter(mpctx) : -1;
+ int cur = mpctx->playback_initialized ? get_current_chapter(mpctx) : -1;
char *res = NULL;
int n;
@@ -837,7 +867,8 @@ static int mp_property_edition(void *ctx, struct m_property *prop,
edition = *(int *)arg;
if (edition != demuxer->edition) {
opts->edition_id = edition;
- mpctx->stop_play = PT_RELOAD_DEMUXER;
+ if (!mpctx->stop_play)
+ mpctx->stop_play = PT_RELOAD_FILE;
}
return M_PROPERTY_OK;
}
@@ -965,7 +996,7 @@ static int mp_property_chapters(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- if (!mpctx->num_sources)
+ if (!mpctx->playback_initialized)
return M_PROPERTY_UNAVAILABLE;
int count = get_chapter_count(mpctx);
return m_property_int_ro(action, arg, count);
@@ -1151,11 +1182,8 @@ static int mp_property_chapter_metadata(void *ctx, struct m_property *prop,
{
MPContext *mpctx = ctx;
int chapter = get_current_chapter(mpctx);
- if (chapter < 0 || chapter >= mpctx->num_chapters)
+ if (chapter < 0)
return M_PROPERTY_UNAVAILABLE;
- if (!mpctx->chapters[chapter].metadata)
- return M_PROPERTY_UNAVAILABLE;
-
return tag_property(action, arg, mpctx->chapters[chapter].metadata);
}
@@ -1229,7 +1257,7 @@ static int mp_property_eof_reached(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- if (!mpctx->num_sources)
+ if (!mpctx->playback_initialized)
return M_PROPERTY_UNAVAILABLE;
bool eof = mpctx->video_status == STATUS_EOF &&
mpctx->audio_status == STATUS_EOF;
@@ -1240,7 +1268,7 @@ static int mp_property_seeking(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- if (!mpctx->num_sources)
+ if (!mpctx->playback_initialized)
return M_PROPERTY_UNAVAILABLE;
return m_property_flag_ro(action, arg, !mpctx->restart_complete);
}
@@ -1419,7 +1447,7 @@ static int mp_property_paused_for_cache(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- if (!mpctx->num_sources)
+ if (!mpctx->playback_initialized)
return M_PROPERTY_UNAVAILABLE;
return m_property_flag_ro(action, arg, mpctx->paused_for_cache);
}
@@ -1480,11 +1508,11 @@ static int mp_property_volume(void *ctx, struct m_property *prop,
.type = CONF_TYPE_FLOAT,
.flags = M_OPT_RANGE,
.min = 0,
- .max = 100,
+ .max = mixer_getmaxvolume(mpctx->mixer),
};
return M_PROPERTY_OK;
case M_PROPERTY_GET_NEUTRAL:
- *(float *)arg = mixer_getneutralvolume(mpctx->mixer);
+ *(float *)arg = 100;
return M_PROPERTY_OK;
case M_PROPERTY_PRINT: {
float val;
@@ -1610,8 +1638,9 @@ static int mp_property_ao_detected_device(void *ctx,struct m_property *prop,
{
struct MPContext *mpctx = ctx;
struct command_ctx *cmd = mpctx->command_ctx;
- if (!mpctx->ao)
- return M_PROPERTY_UNAVAILABLE;
+ if (!cmd->hotplug)
+ cmd->hotplug = ao_hotplug_create(mpctx->global, mpctx->input);
+
const char *d = ao_hotplug_get_detected_device(cmd->hotplug);
return m_property_strdup_ro(action, arg, d);
}
@@ -1637,8 +1666,8 @@ static int mp_property_audio_delay(void *ctx, struct m_property *prop,
}
/// Audio codec tag (RO)
-static int mp_property_audio_format(void *ctx, struct m_property *prop,
- int action, void *arg)
+static int mp_property_audio_codec_name(void *ctx, struct m_property *prop,
+ int action, void *arg)
{
MPContext *mpctx = ctx;
const char *c = mpctx->d_audio ? mpctx->d_audio->header->codec : NULL;
@@ -1654,45 +1683,41 @@ static int mp_property_audio_codec(void *ctx, struct m_property *prop,
return m_property_strdup_ro(action, arg, c);
}
-/// Samplerate (RO)
-static int mp_property_samplerate(void *ctx, struct m_property *prop,
- int action, void *arg)
+static int property_audiofmt(struct mp_audio a, int action, void *arg)
+{
+ if (!mp_audio_config_valid(&a))
+ return M_PROPERTY_UNAVAILABLE;
+
+ struct m_sub_property props[] = {
+ {"samplerate", SUB_PROP_INT(a.rate)},
+ {"channel-count", SUB_PROP_INT(a.channels.num)},
+ {"channels", SUB_PROP_STR(mp_chmap_to_str(&a.channels))},
+ {"hr-channels", SUB_PROP_STR(mp_chmap_to_str_hr(&a.channels))},
+ {"format", SUB_PROP_STR(af_fmt_to_str(a.format))},
+ {0}
+ };
+
+ return m_property_read_sub(props, action, arg);
+}
+
+static int mp_property_audio_params(void *ctx, struct m_property *prop,
+ int action, void *arg)
{
MPContext *mpctx = ctx;
struct mp_audio fmt = {0};
if (mpctx->d_audio)
fmt = mpctx->d_audio->decode_format;
- if (!fmt.rate)
- return M_PROPERTY_UNAVAILABLE;
- if (action == M_PROPERTY_PRINT) {
- *(char **)arg = talloc_asprintf(NULL, "%d kHz", fmt.rate / 1000);
- return M_PROPERTY_OK;
- }
- return m_property_int_ro(action, arg, fmt.rate);
+ return property_audiofmt(fmt, action, arg);
}
-/// Number of channels (RO)
-static int mp_property_channels(void *ctx, struct m_property *prop,
- int action, void *arg)
+static int mp_property_audio_out_params(void *ctx, struct m_property *prop,
+ int action, void *arg)
{
MPContext *mpctx = ctx;
struct mp_audio fmt = {0};
- if (mpctx->d_audio)
- fmt = mpctx->d_audio->decode_format;
- if (!fmt.channels.num)
- return M_PROPERTY_UNAVAILABLE;
- switch (action) {
- case M_PROPERTY_PRINT:
- *(char **) arg = talloc_strdup(NULL, mp_chmap_to_str(&fmt.channels));
- return M_PROPERTY_OK;
- case M_PROPERTY_GET:
- *(int *)arg = fmt.channels.num;
- return M_PROPERTY_OK;
- case M_PROPERTY_GET_TYPE:
- *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_INT};
- return M_PROPERTY_OK;
- }
- return M_PROPERTY_NOT_IMPLEMENTED;
+ if (mpctx->ao)
+ ao_get_format(mpctx->ao, &fmt);
+ return property_audiofmt(fmt, action, arg);
}
/// Balance (RW)
@@ -1774,13 +1799,15 @@ static int property_switch_track(struct m_property *prop, int action, void *arg,
MPContext *mpctx, int order,
enum stream_type type)
{
- if (!mpctx->num_sources)
- return M_PROPERTY_UNAVAILABLE;
struct track *track = mpctx->current_track[order][type];
switch (action) {
case M_PROPERTY_GET:
- *(int *) arg = track ? track->user_tid : -2;
+ if (mpctx->playback_initialized) {
+ *(int *)arg = track ? track->user_tid : -2;
+ } else {
+ *(int *)arg = mpctx->opts->stream_id[order][type];
+ }
return M_PROPERTY_OK;
case M_PROPERTY_PRINT:
if (!track)
@@ -1800,16 +1827,23 @@ static int property_switch_track(struct m_property *prop, int action, void *arg,
return M_PROPERTY_OK;
case M_PROPERTY_SWITCH: {
+ if (!mpctx->playback_initialized)
+ return M_PROPERTY_ERROR;
struct m_property_switch_arg *sarg = arg;
mp_switch_track_n(mpctx, order, type,
- track_next(mpctx, order, type, sarg->inc >= 0 ? +1 : -1, track));
- mp_mark_user_track_selection(mpctx, order, type);
+ track_next(mpctx, order, type, sarg->inc >= 0 ? +1 : -1, track),
+ FLAG_MARK_SELECTION);
+ print_track_list(mpctx, "Track switched:");
return M_PROPERTY_OK;
}
case M_PROPERTY_SET:
- track = mp_track_by_tid(mpctx, type, *(int *)arg);
- mp_switch_track_n(mpctx, order, type, track);
- mp_mark_user_track_selection(mpctx, order, type);
+ if (mpctx->playback_initialized) {
+ track = mp_track_by_tid(mpctx, type, *(int *)arg);
+ mp_switch_track_n(mpctx, order, type, track, FLAG_MARK_SELECTION);
+ print_track_list(mpctx, "Track switched:");
+ } else {
+ mpctx->opts->stream_id[order][type] = *(int *)arg;
+ }
return M_PROPERTY_OK;
}
return mp_property_generic_option(mpctx, prop, action, arg);
@@ -1821,8 +1855,6 @@ static int property_switch_track_ff(void *ctx, struct m_property *prop,
{
MPContext *mpctx = ctx;
enum stream_type type = (intptr_t)prop->priv;
- if (!mpctx->num_sources)
- return M_PROPERTY_UNAVAILABLE;
struct track *track = mpctx->current_track[0][type];
switch (action) {
@@ -1831,23 +1863,34 @@ static int property_switch_track_ff(void *ctx, struct m_property *prop,
return M_PROPERTY_OK;
case M_PROPERTY_SET: {
int id = *(int *)arg;
- track = NULL;
- for (int n = 0; n < mpctx->num_tracks; n++) {
- struct track *cur = mpctx->tracks[n];
- if (cur->type == type && cur->ff_index == id) {
- track = cur;
- break;
+ if (mpctx->playback_initialized) {
+ track = NULL;
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *cur = mpctx->tracks[n];
+ if (cur->type == type && cur->ff_index == id) {
+ track = cur;
+ break;
+ }
}
+ if (!track && id >= 0)
+ return M_PROPERTY_ERROR;
+ mp_switch_track_n(mpctx, 0, type, track, 0);
+ print_track_list(mpctx, "Track switched:");
+ } else {
+ mpctx->opts->stream_id_ff[type] = *(int *)arg;
}
- if (!track && id >= 0)
- return M_PROPERTY_ERROR;
- mp_switch_track_n(mpctx, 0, type, track);
return M_PROPERTY_OK;
}
}
return mp_property_generic_option(mpctx, prop, action, arg);
}
+static int track_channels(struct track *track)
+{
+ return track->stream && track->stream->audio
+ ? track->stream->audio->channels.num : 0;
+}
+
static int get_track_entry(int item, int action, void *arg, void *ctx)
{
struct MPContext *mpctx = ctx;
@@ -1865,8 +1908,11 @@ static int get_track_entry(int item, int action, void *arg, void *ctx)
.unavailable = !track->title},
{"lang", SUB_PROP_STR(track->lang),
.unavailable = !track->lang},
+ {"audio-channels", SUB_PROP_INT(track_channels(track)),
+ .unavailable = track_channels(track) <= 0},
{"albumart", SUB_PROP_FLAG(track->attached_picture)},
{"default", SUB_PROP_FLAG(track->default_track)},
+ {"forced", SUB_PROP_FLAG(track->forced_track)},
{"external", SUB_PROP_FLAG(track->is_external)},
{"selected", SUB_PROP_FLAG(track->selected)},
{"external-filename", SUB_PROP_STR(track->external_filename),
@@ -1987,11 +2033,12 @@ static int mp_property_program(void *ctx, struct m_property *prop,
return M_PROPERTY_ERROR;
}
mp_switch_track(mpctx, STREAM_VIDEO,
- find_track_by_demuxer_id(mpctx, STREAM_VIDEO, prog.vid));
+ find_track_by_demuxer_id(mpctx, STREAM_VIDEO, prog.vid), 0);
mp_switch_track(mpctx, STREAM_AUDIO,
- find_track_by_demuxer_id(mpctx, STREAM_AUDIO, prog.aid));
+ find_track_by_demuxer_id(mpctx, STREAM_AUDIO, prog.aid), 0);
mp_switch_track(mpctx, STREAM_SUB,
- find_track_by_demuxer_id(mpctx, STREAM_VIDEO, prog.sid));
+ find_track_by_demuxer_id(mpctx, STREAM_VIDEO, prog.sid), 0);
+ print_track_list(mpctx, "Program switched:");
return M_PROPERTY_OK;
case M_PROPERTY_GET_TYPE:
*(struct m_option *)arg = (struct m_option){
@@ -2011,41 +2058,51 @@ static int mp_property_hwdec(void *ctx, struct m_property *prop,
MPContext *mpctx = ctx;
struct MPOpts *opts = mpctx->opts;
struct dec_video *vd = mpctx->d_video;
- if (!vd)
- return M_PROPERTY_UNAVAILABLE;
-
- int current = 0;
- video_vd_control(vd, VDCTRL_GET_HWDEC, &current);
- switch (action) {
- case M_PROPERTY_GET:
- *(int *)arg = current;
- return M_PROPERTY_OK;
- case M_PROPERTY_SET: {
+ if (action == M_PROPERTY_SET) {
int new = *(int *)arg;
- if (current == new)
+
+ if (opts->hwdec_api == new)
return M_PROPERTY_OK;
- if (!mpctx->d_video)
- return M_PROPERTY_ERROR;
- double last_pts = mpctx->last_vo_pts;
- uninit_video_chain(mpctx);
+
opts->hwdec_api = new;
- reinit_video_chain(mpctx);
- if (last_pts != MP_NOPTS_VALUE)
- queue_seek(mpctx, MPSEEK_ABSOLUTE, last_pts, MPSEEK_EXACT, true);
+
+ if (!vd)
+ return M_PROPERTY_OK;
+
+ int current = -2;
+ video_vd_control(vd, VDCTRL_GET_HWDEC, &current);
+ if (current != opts->hwdec_api) {
+ double last_pts = mpctx->last_vo_pts;
+ uninit_video_chain(mpctx);
+ reinit_video_chain(mpctx);
+ if (last_pts != MP_NOPTS_VALUE)
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, last_pts, MPSEEK_EXACT, true);
+ }
return M_PROPERTY_OK;
}
- }
return mp_property_generic_option(mpctx, prop, action, arg);
}
+static int mp_property_hwdec_active(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ struct dec_video *vd = mpctx->d_video;
+ bool active = false;
+ if (vd) {
+ int current = 0;
+ video_vd_control(vd, VDCTRL_GET_HWDEC, &current);
+ active = current > 0;
+ }
+ return m_property_flag_ro(action, arg, active);
+}
+
static int mp_property_detected_hwdec(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
struct dec_video *vd = mpctx->d_video;
- if (!vd || !vd->hwdec_info)
- return M_PROPERTY_UNAVAILABLE;
switch (action) {
case M_PROPERTY_GET_TYPE: {
@@ -2054,14 +2111,16 @@ static int mp_property_detected_hwdec(void *ctx, struct m_property *prop,
return mp_property_generic_option(mpctx, &dummy, action, arg);
}
case M_PROPERTY_GET: {
- int d = vd->hwdec_info->hwctx ? vd->hwdec_info->hwctx->type : HWDEC_NONE;
- if (d) {
- *(int *)arg = d;
- } else {
- // Maybe one of the "-copy" ones. These are "detected" every time
- // the decoder is opened, so we don't know much about them otherwise.
- return mp_property_hwdec(ctx, prop, action, arg);
- }
+ int current = 0;
+ if (vd)
+ video_vd_control(vd, VDCTRL_GET_HWDEC, &current);
+
+ if (current <= 0 && vd && vd->hwdec_info && vd->hwdec_info->hwctx)
+ current = vd->hwdec_info->hwctx->type;
+
+ // In case of the "-copy" ones, which are "detected" every time the
+ // decoder is opened, return "no" if no decoding is active.
+ *(int *)arg = current > 0 ? current : 0;
return M_PROPERTY_OK;
}
}
@@ -2075,7 +2134,7 @@ static bool probe_deint_filter(struct MPContext *mpctx, const char *filt)
char filter[80];
// add a label so that removing the filter is easier
snprintf(filter, sizeof(filter), "@%s:%s", VF_DEINTERLACE_LABEL, filt);
- return edit_filters(mpctx, STREAM_VIDEO, "pre", filter) >= 0;
+ return edit_filters(mpctx, mp_null_log, STREAM_VIDEO, "pre", filter) >= 0;
}
static bool check_output_format(struct MPContext *mpctx, int imgfmt)
@@ -2106,7 +2165,7 @@ static int probe_deint_filters(struct MPContext *mpctx)
if (check_output_format(mpctx, IMGFMT_VAAPI) &&
probe_deint_filter(mpctx, "vavpp"))
return 0;
- if (probe_deint_filter(mpctx, "yadif"))
+ if (probe_deint_filter(mpctx, "yadif:mode=field:interlaced-only=yes"))
return 0;
return -1;
}
@@ -2125,12 +2184,17 @@ static int get_deinterlacing(struct MPContext *mpctx)
return enabled;
}
-static void set_deinterlacing(struct MPContext *mpctx, bool enable)
+void remove_deint_filter(struct MPContext *mpctx)
+{
+ edit_filters(mpctx, mp_null_log, STREAM_VIDEO, "del", "@" VF_DEINTERLACE_LABEL);
+}
+
+void set_deinterlacing(struct MPContext *mpctx, bool enable)
{
struct dec_video *vd = mpctx->d_video;
if (vf_find_by_label(vd->vfilter, VF_DEINTERLACE_LABEL)) {
if (!enable)
- edit_filters(mpctx, STREAM_VIDEO, "del", "@" VF_DEINTERLACE_LABEL);
+ remove_deint_filter(mpctx);
} else {
if ((get_deinterlacing(mpctx) > 0) != enable) {
int arg = enable;
@@ -2146,7 +2210,7 @@ static int mp_property_deinterlace(void *ctx, struct m_property *prop,
{
MPContext *mpctx = ctx;
if (!mpctx->d_video || !mpctx->d_video->vfilter)
- return M_PROPERTY_UNAVAILABLE;
+ return mp_property_generic_option(mpctx, prop, action, arg);
switch (action) {
case M_PROPERTY_GET:
*(int *)arg = get_deinterlacing(mpctx) > 0;
@@ -2211,8 +2275,12 @@ static int mp_property_fullscreen(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- return mp_property_vo_flag(prop, action, arg, VOCTRL_FULLSCREEN,
- &mpctx->opts->vo.fullscreen, mpctx);
+ int oldval = mpctx->opts->vo.fullscreen;
+ int r = mp_property_vo_flag(prop, action, arg, VOCTRL_FULLSCREEN,
+ &mpctx->opts->vo.fullscreen, mpctx);
+ if (oldval && oldval != mpctx->opts->vo.fullscreen)
+ mpctx->mouse_event_ts--; // Show mouse cursor
+ return r;
}
/// Window always on top (RW)
@@ -2370,6 +2438,10 @@ static int property_imgparams(struct mp_image_params p, int action, void *arg)
SUB_PROP_STR(m_opt_choice_str(mp_csp_trc_names, p.gamma))},
{"chroma-location",
SUB_PROP_STR(m_opt_choice_str(mp_chroma_names, p.chroma_location))},
+ {"stereo-in",
+ SUB_PROP_STR(m_opt_choice_str(mp_stereo3d_names, p.stereo_in))},
+ {"stereo-out",
+ SUB_PROP_STR(m_opt_choice_str(mp_stereo3d_names, p.stereo_out))},
{"rotate", SUB_PROP_INT(p.rotate)},
{0}
};
@@ -2590,25 +2662,10 @@ static int mp_property_vf_fps(void *ctx, struct m_property *prop,
MPContext *mpctx = ctx;
if (!mpctx->d_video)
return M_PROPERTY_UNAVAILABLE;
- double next_pts = mpctx->vo_pts_history_pts[0];
- if (mpctx->vo_pts_history_seek[0] != mpctx->vo_pts_history_seek_ts)
- return M_PROPERTY_UNAVAILABLE;
- if (next_pts == MP_NOPTS_VALUE)
+ double res = stabilize_frame_duration(mpctx, false);
+ if (res <= 0)
return M_PROPERTY_UNAVAILABLE;
- int num_samples = 10;
- assert(num_samples + 1 <= MAX_NUM_VO_PTS);
- double duration = 0;
- for (int n = 1; n < 1 + num_samples; n++) {
- double frame_pts = mpctx->vo_pts_history_pts[n];
- // Discontinuity -> refuse to return a value.
- if (mpctx->vo_pts_history_seek[n] != mpctx->vo_pts_history_seek_ts)
- return M_PROPERTY_UNAVAILABLE;
- if (frame_pts == MP_NOPTS_VALUE)
- return M_PROPERTY_UNAVAILABLE;
- duration += next_pts - frame_pts;
- next_pts = frame_pts;
- }
- return m_property_double_ro(action, arg, num_samples / duration);
+ return m_property_double_ro(action, arg, 1 / res);
}
/// Video aspect (RO)
@@ -2616,27 +2673,27 @@ static int mp_property_aspect(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- if (!mpctx->d_video)
- return M_PROPERTY_UNAVAILABLE;
- struct dec_video *d_video = mpctx->d_video;
- struct sh_video *sh_video = d_video->header->video;
switch (action) {
case M_PROPERTY_SET: {
mpctx->opts->movie_aspect = *(float *)arg;
- reinit_video_filters(mpctx);
- mp_force_video_refresh(mpctx);
+ if (mpctx->d_video) {
+ reinit_video_filters(mpctx);
+ mp_force_video_refresh(mpctx);
+ }
return M_PROPERTY_OK;
}
case M_PROPERTY_GET: {
- float aspect = -1;
- struct mp_image_params *params = &d_video->vfilter->override_params;
- if (params && params->d_w && params->d_h) {
- aspect = (float)params->d_w / params->d_h;
- } else if (sh_video->disp_w && sh_video->disp_h) {
- aspect = (float)sh_video->disp_w / sh_video->disp_h;
+ float aspect = mpctx->opts->movie_aspect;
+ if (mpctx->d_video && aspect <= 0) {
+ struct dec_video *d_video = mpctx->d_video;
+ struct sh_video *sh_video = d_video->header->video;
+ struct mp_image_params *params = &d_video->vfilter->override_params;
+ if (params && params->d_w && params->d_h) {
+ aspect = (float)params->d_w / params->d_h;
+ } else if (sh_video->disp_w && sh_video->disp_h) {
+ aspect = (float)sh_video->disp_w / sh_video->disp_h;
+ }
}
- if (aspect <= 0)
- return M_PROPERTY_UNAVAILABLE;
*(float *)arg = aspect;
return M_PROPERTY_OK;
}
@@ -2830,16 +2887,16 @@ static int mp_property_dvb_channel(void *ctx, struct m_property *prop,
case M_PROPERTY_SET:
mpctx->last_dvb_step = 1;
r = prop_stream_ctrl(mpctx, STREAM_CTRL_DVB_SET_CHANNEL, arg);
- if (r == M_PROPERTY_OK)
- mpctx->stop_play = PT_RELOAD_DEMUXER;
+ if (r == M_PROPERTY_OK && !mpctx->stop_play)
+ mpctx->stop_play = PT_RELOAD_FILE;
return r;
case M_PROPERTY_SWITCH: {
struct m_property_switch_arg *sa = arg;
int dir = sa->inc >= 0 ? 1 : -1;
mpctx->last_dvb_step = dir;
r = prop_stream_ctrl(mpctx, STREAM_CTRL_DVB_STEP_CHANNEL, &dir);
- if (r == M_PROPERTY_OK)
- mpctx->stop_play = PT_RELOAD_DEMUXER;
+ if (r == M_PROPERTY_OK && !mpctx->stop_play)
+ mpctx->stop_play = PT_RELOAD_FILE;
return r;
}
case M_PROPERTY_GET_TYPE:
@@ -2899,6 +2956,7 @@ static int get_playlist_entry(int item, int action, void *arg, void *ctx)
{"filename", SUB_PROP_STR(e->filename)},
{"current", SUB_PROP_FLAG(1), .unavailable = !current},
{"playing", SUB_PROP_FLAG(1), .unavailable = !playing},
+ {"title", SUB_PROP_STR(e->title), .unavailable = !e->title},
{0}
};
@@ -3058,6 +3116,20 @@ static int mp_property_cwd(void *ctx, struct m_property *prop,
return M_PROPERTY_NOT_IMPLEMENTED;
}
+static int mp_property_protocols(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(char ***)arg = stream_get_proto_list();
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET_TYPE:
+ *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_STRING_LIST};
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
static int mp_property_version(void *ctx, struct m_property *prop,
int action, void *arg)
{
@@ -3077,6 +3149,29 @@ static int mp_property_alias(void *ctx, struct m_property *prop,
return mp_property_do(real_property, action, arg, ctx);
}
+static int mp_property_deprecated_alias(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ struct command_ctx *cmd = mpctx->command_ctx;
+ const char *real_property = prop->priv;
+ if (action == M_PROPERTY_SET || action == M_PROPERTY_GET ||
+ action == M_PROPERTY_PRINT)
+ {
+ for (int n = 0; n < cmd->num_warned_deprecated; n++) {
+ if (strcmp(cmd->warned_deprecated[n], prop->name) == 0)
+ goto done;
+ }
+ MP_WARN(mpctx, "Warning: property '%s' was replaced with '%s' and "
+ "might be removed in the future.\n", prop->name, real_property);
+ MP_TARRAY_APPEND(cmd, cmd->warned_deprecated, cmd->num_warned_deprecated,
+ (char *)prop->name);
+
+ done:;
+ }
+ return mp_property_do(real_property, action, arg, ctx);
+}
+
static int access_options(struct m_property_action_arg *ka, bool local,
MPContext *mpctx)
{
@@ -3185,6 +3280,7 @@ static int mp_property_option_info(void *ctx, struct m_property *prop,
{"name", SUB_PROP_STR(co->name)},
{"type", SUB_PROP_STR(opt->type->name)},
{"set-from-commandline", SUB_PROP_FLAG(co->is_set_from_cmdline)},
+ {"set-locally", SUB_PROP_FLAG(co->is_set_locally)},
{"default-value", *opt, def},
{"min", SUB_PROP_DOUBLE(opt->min),
.unavailable = !(has_minmax && (opt->flags & M_OPT_MIN))},
@@ -3233,6 +3329,9 @@ static int mp_property_list(void *ctx, struct m_property *prop,
#define M_PROPERTY_ALIAS(name, real_property) \
{(name), mp_property_alias, .priv = (real_property)}
+#define M_PROPERTY_DEPRECATED_ALIAS(name, real_property) \
+ {(name), mp_property_deprecated_alias, .priv = (real_property)}
+
/// All properties available in MPlayer.
/** \ingroup Properties
*/
@@ -3243,6 +3342,9 @@ static const struct m_property mp_properties[] = {
{"loop", mp_property_generic_option},
{"loop-file", mp_property_generic_option},
{"speed", mp_property_playback_speed},
+ {"audio-speed-correction", mp_property_av_speed_correction, .priv = "a"},
+ {"video-speed-correction", mp_property_av_speed_correction, .priv = "v"},
+ {"display-sync-active", mp_property_display_sync_active},
{"filename", mp_property_filename},
{"stream-open-filename", mp_property_stream_open_filename},
{"file-size", mp_property_file_size},
@@ -3254,11 +3356,13 @@ static const struct m_property mp_properties[] = {
{"file-format", mp_property_file_format},
{"stream-pos", mp_property_stream_pos},
{"stream-end", mp_property_stream_end},
- {"length", mp_property_length},
+ {"duration", mp_property_duration},
+ M_PROPERTY_DEPRECATED_ALIAS("length", "duration"),
{"avsync", mp_property_avsync},
{"total-avsync-change", mp_property_total_avsync_change},
{"drop-frame-count", mp_property_drop_frame_cnt},
{"vo-drop-frame-count", mp_property_vo_drop_frame_count},
+ {"vo-missed-frame-count", mp_property_vo_missed_frame_count},
{"percent-pos", mp_property_percent_pos},
{"time-start", mp_property_time_start},
{"time-pos", mp_property_time_pos},
@@ -3266,8 +3370,6 @@ static const struct m_property mp_properties[] = {
{"playtime-remaining", mp_property_playtime_remaining},
{"playback-time", mp_property_playback_time},
{"disc-title", mp_property_disc_title},
- {"disc-menu-active", mp_property_disc_menu},
- {"disc-mouse-on-button", mp_property_mouse_on_button},
{"chapter", mp_property_chapter},
{"edition", mp_property_edition},
{"disc-titles", mp_property_disc_titles},
@@ -3313,10 +3415,12 @@ static const struct m_property mp_properties[] = {
{"volume", mp_property_volume},
{"mute", mp_property_mute},
{"audio-delay", mp_property_audio_delay},
- {"audio-format", mp_property_audio_format},
+ {"audio-codec-name", mp_property_audio_codec_name},
{"audio-codec", mp_property_audio_codec},
- {"audio-samplerate", mp_property_samplerate},
- {"audio-channels", mp_property_channels},
+ {"audio-params", mp_property_audio_params},
+ {"audio-out-params", mp_property_audio_out_params},
+ M_PROPERTY_DEPRECATED_ALIAS("audio-samplerate", "audio-params/samplerate"),
+ M_PROPERTY_DEPRECATED_ALIAS("audio-channels", "audio-params/channel-count"),
{"aid", mp_property_audio},
{"balance", mp_property_balance},
{"volume-restore-data", mp_property_volrestore},
@@ -3362,7 +3466,8 @@ static const struct m_property mp_properties[] = {
{"vid", mp_property_video},
{"program", mp_property_program},
{"hwdec", mp_property_hwdec},
- {"detected-hwdec", mp_property_detected_hwdec},
+ {"hwdec-active", mp_property_hwdec_active},
+ {"hwdec-detected", mp_property_detected_hwdec},
{"estimated-frame-count", mp_property_frame_count},
{"estimated-frame-number", mp_property_frame_number},
@@ -3431,6 +3536,8 @@ static const struct m_property mp_properties[] = {
{"working-directory", mp_property_cwd},
+ {"protocol-list", mp_property_protocols},
+
{"mpv-version", mp_property_version},
{"mpv-configuration", mp_property_configuration},
@@ -3449,6 +3556,8 @@ static const struct m_property mp_properties[] = {
M_PROPERTY_ALIAS("colormatrix-primaries", "video-params/primaries"),
M_PROPERTY_ALIAS("colormatrix-gamma", "video-params/gamma"),
+ M_PROPERTY_DEPRECATED_ALIAS("audio-format", "audio-codec-name"),
+
{0},
};
@@ -3468,15 +3577,17 @@ static const char *const *const mp_event_property_change[] = {
E(MPV_EVENT_TICK, "time-pos", "stream-pos", "stream-time-pos", "avsync",
"percent-pos", "time-remaining", "playtime-remaining", "playback-time",
"estimated-vf-fps", "drop-frame-count", "vo-drop-frame-count",
- "total-avsync-change"),
+ "total-avsync-change", "audio-speed-correction", "video-speed-correction",
+ "vo-missed-frame-count"),
E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params",
"video-format", "video-codec", "video-bitrate", "dwidth", "dheight",
"width", "height", "fps", "aspect", "vo-configured", "current-vo",
"detected-hwdec", "colormatrix", "colormatrix-input-range",
- "colormatrix-output-range", "colormatrix-primaries"),
+ "colormatrix-output-range", "colormatrix-primaries", "video-aspect"),
E(MPV_EVENT_AUDIO_RECONFIG, "audio-format", "audio-codec", "audio-bitrate",
"samplerate", "channels", "audio", "volume", "mute", "balance",
- "volume-restore-data", "current-ao"),
+ "volume-restore-data", "current-ao", "audio-codec-name", "audio-params",
+ "audio-out-params"),
E(MPV_EVENT_SEEK, "seeking", "core-idle"),
E(MPV_EVENT_PLAYBACK_RESTART, "seeking", "core-idle"),
E(MPV_EVENT_METADATA_UPDATE, "metadata", "filtered-metadata", "media-title"),
@@ -3485,16 +3596,15 @@ static const char *const *const mp_event_property_change[] = {
"demuxer-cache-duration", "demuxer-cache-idle", "paused-for-cache",
"demuxer-cache-time"),
E(MP_EVENT_WIN_RESIZE, "window-scale"),
- E(MP_EVENT_WIN_STATE, "window-minimized", "display-names", "display-fps"),
- E(MP_EVENT_AUDIO_DEVICES, "audio-device-list"),
- E(MP_EVENT_DETECTED_AUDIO_DEVICE, "audio-out-detected-device"),
+ E(MP_EVENT_WIN_STATE, "window-minimized", "display-names", "display-fps", "fullscreen"),
};
#undef E
+// If there is no prefix, return length+1 (avoids matching full name as prefix).
static int prefix_len(const char *p)
{
const char *end = strchr(p, '/');
- return end ? end - p : strlen(p);
+ return end ? end - p : strlen(p) + 1;
}
static bool match_property(const char *a, const char *b)
@@ -3664,8 +3774,8 @@ static const struct property_osd_display {
{ "tv-hue", "Hue", .osd_progbar = OSD_HUE},
{ "tv-saturation", "Saturation", .osd_progbar = OSD_SATURATION },
{ "tv-contrast", "Contrast", .osd_progbar = OSD_CONTRAST },
- { "ab-loop-a", "A-B loop point A"},
- { "ab-loop-b", "A-B loop point B"},
+ { "ab-loop-a", "A-B loop start"},
+ { "ab-loop-b", .msg = "A-B loop: ${ab-loop-a} - ${ab-loop-b}"},
{ "audio-device", "Audio device"},
// By default, don't display the following properties on OSD
{ "pause", NULL },
@@ -3743,21 +3853,6 @@ static void show_property_osd(MPContext *mpctx, const char *name, int osd_mode)
}
}
-static const char *property_error_string(int error_value)
-{
- switch (error_value) {
- case M_PROPERTY_ERROR:
- return "ERROR";
- case M_PROPERTY_UNAVAILABLE:
- return "PROPERTY_UNAVAILABLE";
- case M_PROPERTY_NOT_IMPLEMENTED:
- return "NOT_IMPLEMENTED";
- case M_PROPERTY_UNKNOWN:
- return "PROPERTY_UNKNOWN";
- }
- return "UNKNOWN";
-}
-
static bool reinit_filters(MPContext *mpctx, enum stream_type mediatype)
{
switch (mediatype) {
@@ -3804,7 +3899,8 @@ static int set_filters(struct MPContext *mpctx, enum stream_type mediatype,
return success ? 0 : -1;
}
-static int edit_filters(struct MPContext *mpctx, enum stream_type mediatype,
+static int edit_filters(struct MPContext *mpctx, struct mp_log *log,
+ enum stream_type mediatype,
const char *cmd, const char *arg)
{
bstr option = bstr0(filter_opt[mediatype]);
@@ -3819,8 +3915,7 @@ static int edit_filters(struct MPContext *mpctx, enum stream_type mediatype,
struct m_obj_settings *new_chain = NULL;
m_option_copy(co->opt, &new_chain, co->data);
- int r = m_option_parse(mpctx->log, co->opt, bstr0(optname), bstr0(arg),
- &new_chain);
+ int r = m_option_parse(log, co->opt, bstr0(optname), bstr0(arg), &new_chain);
if (r >= 0)
r = set_filters(mpctx, mediatype, new_chain);
@@ -3832,7 +3927,7 @@ static int edit_filters(struct MPContext *mpctx, enum stream_type mediatype,
static int edit_filters_osd(struct MPContext *mpctx, enum stream_type mediatype,
const char *cmd, const char *arg, bool on_osd)
{
- int r = edit_filters(mpctx, mediatype, cmd, arg);
+ int r = edit_filters(mpctx, mpctx->log, mediatype, cmd, arg);
if (on_osd) {
if (r >= 0) {
const char *prop = filter_opt[mediatype];
@@ -4111,20 +4206,35 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
case 1: precision = MPSEEK_KEYFRAME; break;
case 2: precision = MPSEEK_EXACT; break;
}
- if (!mpctx->num_sources)
+ if (!mpctx->playback_initialized)
return -1;
mark_seek(mpctx);
- if (abs == 2) { // Absolute seek to a timestamp in seconds
+ switch (abs) {
+ case 0: { // Relative seek
+ queue_seek(mpctx, MPSEEK_RELATIVE, v, precision, false);
+ set_osd_function(mpctx, (v > 0) ? OSD_FFW : OSD_REW);
+ break;
+ }
+ case 1: { // Absolute seek by percentage
+ double ratio = v / 100.0;
+ double cur_pos = get_current_pos_ratio(mpctx, false);
+ queue_seek(mpctx, MPSEEK_FACTOR, ratio, precision, false);
+ set_osd_function(mpctx, cur_pos < ratio ? OSD_FFW : OSD_REW);
+ break;
+ }
+ case 2: { // Absolute seek to a timestamp in seconds
queue_seek(mpctx, MPSEEK_ABSOLUTE, v, precision, false);
set_osd_function(mpctx,
v > get_current_time(mpctx) ? OSD_FFW : OSD_REW);
- } else if (abs) { /* Absolute seek by percentage */
- queue_seek(mpctx, MPSEEK_FACTOR, v / 100.0, precision, false);
- set_osd_function(mpctx, OSD_FFW); // Direction isn't set correctly
- } else {
- queue_seek(mpctx, MPSEEK_RELATIVE, v, precision, false);
- set_osd_function(mpctx, (v > 0) ? OSD_FFW : OSD_REW);
+ break;
}
+ case 3: { // Relative seek by percentage
+ queue_seek(mpctx, MPSEEK_FACTOR,
+ get_current_pos_ratio(mpctx, false) + v / 100.0,
+ precision, false);
+ set_osd_function(mpctx, v > 0 ? OSD_FFW : OSD_REW);
+ break;
+ }}
if (bar_osd)
mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR;
if (msg_or_nobar_osd)
@@ -4133,7 +4243,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
}
case MP_CMD_REVERT_SEEK: {
- if (!mpctx->num_sources)
+ if (!mpctx->playback_initialized)
return -1;
double oldpts = cmdctx->last_seek_pts;
if (cmdctx->marked_pts != MP_NOPTS_VALUE)
@@ -4259,27 +4369,8 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
break;
}
- case MP_CMD_GET_PROPERTY: {
- char *tmp;
- int r = mp_property_do(cmd->args[0].v.s, M_PROPERTY_GET_STRING,
- &tmp, mpctx);
- if (r <= 0) {
- MP_WARN(mpctx, "Failed to get value of property '%s'.\n",
- cmd->args[0].v.s);
- MP_INFO(mpctx, "ANS_ERROR=%s\n", property_error_string(r));
- return -1;
- }
- MP_INFO(mpctx, "ANS_%s=%s\n", cmd->args[0].v.s, tmp);
- talloc_free(tmp);
- MP_WARN(mpctx, "The get_property command is deprecated and "
- "will be removed in the next release.\n"
- "Use libmpv or the JSON IPC. "
- "(Or print_text, if you must.)");
- break;
- }
-
case MP_CMD_FRAME_STEP:
- if (!mpctx->num_sources)
+ if (!mpctx->playback_initialized)
return -1;
if (cmd->is_up_down) {
if (cmd->is_up) {
@@ -4298,7 +4389,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
break;
case MP_CMD_FRAME_BACK_STEP:
- if (!mpctx->num_sources)
+ if (!mpctx->playback_initialized)
return -1;
add_step_frame(mpctx, -1);
break;
@@ -4329,7 +4420,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
case MP_CMD_SUB_STEP:
case MP_CMD_SUB_SEEK: {
- if (!mpctx->num_sources)
+ if (!mpctx->playback_initialized)
return -1;
struct osd_sub_state state;
update_osd_sub_state(mpctx, 0, &state);
@@ -4464,7 +4555,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
if (!e)
return -1;
// Can't play a removed entry
- if (mpctx->playlist->current == e)
+ if (mpctx->playlist->current == e && !mpctx->stop_play)
mpctx->stop_play = PT_CURRENT_ENTRY;
playlist_remove(mpctx->playlist, e);
mp_notify_property(mpctx, "playlist");
@@ -4483,9 +4574,15 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
break;
}
+ case MP_CMD_PLAYLIST_SHUFFLE: {
+ playlist_shuffle(mpctx->playlist);
+ break;
+ }
+
case MP_CMD_STOP:
playlist_clear(mpctx->playlist);
- mpctx->stop_play = PT_STOP;
+ if (!mpctx->stop_play)
+ mpctx->stop_play = PT_STOP;
break;
case MP_CMD_SHOW_PROGRESS:
@@ -4507,11 +4604,9 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
return -1;
int type = cmd->id == MP_CMD_SUB_ADD ? STREAM_SUB : STREAM_AUDIO;
if (cmd->args[1].v.i == 2) {
- struct track *t = find_track_with_url(mpctx, type,
- cmd->args[0].v.s);
+ struct track *t = find_track_with_url(mpctx, type, cmd->args[0].v.s);
if (t) {
- mp_switch_track(mpctx, t->type, t);
- mp_mark_user_track_selection(mpctx, 0, t->type);
+ mp_switch_track(mpctx, t->type, t, FLAG_MARK_SELECTION);
return 0;
}
}
@@ -4521,8 +4616,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
if (cmd->args[1].v.i == 1) {
t->no_default = true;
} else {
- mp_switch_track(mpctx, t->type, t);
- mp_mark_user_track_selection(mpctx, 0, t->type);
+ mp_switch_track(mpctx, t->type, t, FLAG_MARK_SELECTION);
}
char *title = cmd->args[2].v.s;
if (title && title[0])
@@ -4530,7 +4624,8 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
char *lang = cmd->args[3].v.s;
if (lang && lang[0])
t->lang = talloc_strdup(t, lang);
- print_track_list(mpctx);
+ if (mpctx->playback_initialized)
+ print_track_list(mpctx, "Track added:");
break;
}
@@ -4541,7 +4636,8 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
if (!t)
return -1;
mp_remove_track(mpctx, t);
- print_track_list(mpctx);
+ if (mpctx->playback_initialized)
+ print_track_list(mpctx, "Track removed:");
break;
}
@@ -4549,15 +4645,17 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
case MP_CMD_AUDIO_RELOAD: {
int type = cmd->id == MP_CMD_SUB_RELOAD ? STREAM_SUB : STREAM_AUDIO;
struct track *t = mp_track_by_tid(mpctx, type, cmd->args[0].v.i);
+ struct track *nt = NULL;
if (t && t->is_external && t->external_filename) {
- struct track *nt = mp_add_external_file(mpctx, t->external_filename,
- type);
- if (nt) {
- mp_remove_track(mpctx, t);
- mp_switch_track(mpctx, nt->type, nt);
- print_track_list(mpctx);
- return 0;
- }
+ char *filename = talloc_strdup(NULL, t->external_filename);
+ mp_remove_track(mpctx, t);
+ nt = mp_add_external_file(mpctx, filename, type);
+ talloc_free(filename);
+ }
+ if (nt) {
+ mp_switch_track(mpctx, nt->type, nt, 0);
+ print_track_list(mpctx, "Reloaded:");
+ return 0;
}
return -1;
}
@@ -4568,16 +4666,15 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
autoload_external_files(mpctx);
if (cmd->args[0].v.i) {
// somewhat fuzzy and not ideal
- struct track *a = select_track(mpctx, STREAM_AUDIO, opts->audio_id,
- opts->audio_id_ff, opts->audio_lang);
+ struct track *a = select_default_track(mpctx, 0, STREAM_AUDIO);
if (a && a->is_external)
- mp_switch_track(mpctx, STREAM_AUDIO, a);
- struct track *s = select_track(mpctx, STREAM_SUB, opts->sub_id,
- opts->sub_id_ff, opts->sub_lang);
+ mp_switch_track(mpctx, STREAM_AUDIO, a, 0);
+ struct track *s = select_default_track(mpctx, 0, STREAM_SUB);
if (s && s->is_external)
- mp_switch_track(mpctx, STREAM_SUB, s);
+ mp_switch_track(mpctx, STREAM_SUB, s, 0);
- print_track_list(mpctx);
+ if (mpctx->playback_initialized)
+ print_track_list(mpctx, "Track list:\n");
}
break;
}
@@ -4625,16 +4722,16 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
}
case MP_CMD_ENABLE_INPUT_SECTION:
- mp_input_enable_section(mpctx->input, cmd->args[0].v.s,
- cmd->args[1].v.i == 1 ? MP_INPUT_EXCLUSIVE : 0);
+ mp_input_enable_section(mpctx->input, cmd->args[0].v.s, cmd->args[1].v.i);
break;
case MP_CMD_DISABLE_INPUT_SECTION:
mp_input_disable_section(mpctx->input, cmd->args[0].v.s);
break;
- case MP_CMD_DISCNAV:
- mp_nav_user_input(mpctx, cmd->args[0].v.s);
+ case MP_CMD_DEFINE_INPUT_SECTION:
+ mp_input_define_section(mpctx->input, cmd->args[0].v.s, "<api>",
+ cmd->args[1].v.s, !!cmd->args[2].v.i);
break;
case MP_CMD_AB_LOOP: {
@@ -4802,6 +4899,36 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
break;
}
+ case MP_CMD_KEYPRESS:
+ case MP_CMD_KEYDOWN: {
+ const char *key_name = cmd->args[0].v.s;
+ int code = mp_input_get_key_from_name(key_name);
+ if (code < 0) {
+ MP_ERR(mpctx, "%s is not a valid input name.\n", key_name);
+ return -1;
+ }
+ if (cmd->id == MP_CMD_KEYDOWN)
+ code |= MP_KEY_STATE_DOWN;
+
+ mp_input_put_key(mpctx->input, code);
+ break;
+ }
+
+ case MP_CMD_KEYUP: {
+ const char *key_name = cmd->args[0].v.s;
+ if (key_name[0] == '\0') {
+ mp_input_put_key(mpctx->input, MP_INPUT_RELEASE_ALL);
+ } else {
+ int code = mp_input_get_key_from_name(key_name);
+ if (code < 0) {
+ MP_ERR(mpctx, "%s is not a valid input name.\n", key_name);
+ return -1;
+ }
+ mp_input_put_key(mpctx->input, code | MP_KEY_STATE_UP);
+ }
+ break;
+ }
+
default:
MP_VERBOSE(mpctx, "Received unknown cmd %s\n", cmd->name);
return -1;
@@ -4829,30 +4956,12 @@ void command_init(struct MPContext *mpctx)
static void command_event(struct MPContext *mpctx, int event, void *arg)
{
struct command_ctx *ctx = mpctx->command_ctx;
- struct MPOpts *opts = mpctx->opts;
if (event == MPV_EVENT_START_FILE) {
ctx->last_seek_pts = MP_NOPTS_VALUE;
ctx->marked_pts = MP_NOPTS_VALUE;
}
- if (event == MPV_EVENT_TICK) {
- double now =
- mpctx->restart_complete ? mpctx->playback_pts : MP_NOPTS_VALUE;
- if (now != MP_NOPTS_VALUE && opts->ab_loop[0] != MP_NOPTS_VALUE &&
- opts->ab_loop[1] != MP_NOPTS_VALUE)
- {
- if (ctx->prev_pts >= opts->ab_loop[0] &&
- ctx->prev_pts < opts->ab_loop[1] &&
- now >= opts->ab_loop[1])
- {
- mark_seek(mpctx);
- queue_seek(mpctx, MPSEEK_ABSOLUTE, opts->ab_loop[0],
- MPSEEK_EXACT, false);
- }
- }
- ctx->prev_pts = now;
- }
if (event == MPV_EVENT_SEEK)
ctx->prev_pts = MP_NOPTS_VALUE;
if (event == MPV_EVENT_IDLE)
@@ -4865,6 +4974,27 @@ static void command_event(struct MPContext *mpctx, int event, void *arg)
}
}
+void handle_ab_loop(struct MPContext *mpctx)
+{
+ struct command_ctx *ctx = mpctx->command_ctx;
+ struct MPOpts *opts = mpctx->opts;
+
+ double now = mpctx->restart_complete ? mpctx->playback_pts : MP_NOPTS_VALUE;
+ if (now != MP_NOPTS_VALUE && opts->ab_loop[0] != MP_NOPTS_VALUE &&
+ opts->ab_loop[1] != MP_NOPTS_VALUE)
+ {
+ if (ctx->prev_pts >= opts->ab_loop[0] &&
+ ctx->prev_pts < opts->ab_loop[1] &&
+ (now >= opts->ab_loop[1] || mpctx->stop_play == AT_END_OF_FILE))
+ {
+ mark_seek(mpctx);
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, opts->ab_loop[0],
+ MPSEEK_EXACT, false);
+ }
+ }
+ ctx->prev_pts = now;
+}
+
void handle_command_updates(struct MPContext *mpctx)
{
struct command_ctx *ctx = mpctx->command_ctx;
diff --git a/player/command.h b/player/command.h
index 447e01c..e65ffa0 100644
--- a/player/command.h
+++ b/player/command.h
@@ -18,6 +18,8 @@
#ifndef MPLAYER_COMMAND_H
#define MPLAYER_COMMAND_H
+#include <stdbool.h>
+
struct MPContext;
struct mp_cmd;
struct mp_log;
@@ -49,11 +51,14 @@ enum {
MP_EVENT_CACHE_UPDATE,
MP_EVENT_WIN_RESIZE,
MP_EVENT_WIN_STATE,
- MP_EVENT_AUDIO_DEVICES,
- MP_EVENT_DETECTED_AUDIO_DEVICE,
};
bool mp_hook_test_completion(struct MPContext *mpctx, char *type);
void mp_hook_run(struct MPContext *mpctx, char *client, char *type);
+void handle_ab_loop(struct MPContext *mpctx);
+
+void remove_deint_filter(struct MPContext *mpctx);
+void set_deinterlacing(struct MPContext *mpctx, bool enable);
+
#endif /* MPLAYER_COMMAND_H */
diff --git a/player/configfiles.c b/player/configfiles.c
index a48223d..44b811f 100644
--- a/player/configfiles.c
+++ b/player/configfiles.c
@@ -74,11 +74,7 @@ void mp_parse_cfgfiles(struct MPContext *mpctx)
// encoding profile.
if (encoding) {
section = "playback-default";
-
- char *cf = mp_find_config_file(NULL, mpctx->global, "encoding-profiles.conf");
- if (cf)
- m_config_parse_config_file(mpctx->mconfig, cf, SECT_ENCODE, 0);
- talloc_free(cf);
+ load_all_cfgfiles(mpctx, SECT_ENCODE, "encoding-profiles.conf");
}
load_all_cfgfiles(mpctx, section, "mpv.conf|config");
@@ -117,7 +113,7 @@ static void mp_load_per_file_config(struct MPContext *mpctx)
char *name = mp_basename(cfg);
bstr dir = mp_dirname(cfg);
- char *dircfg = mp_path_join(NULL, dir, bstr0("mpv.conf"));
+ char *dircfg = mp_path_join_bstr(NULL, dir, bstr0("mpv.conf"));
try_load_config(mpctx, dircfg, FILE_LOCAL_FLAGS);
talloc_free(dircfg);
@@ -166,10 +162,10 @@ void mp_load_auto_profiles(struct MPContext *mpctx)
#define MP_WATCH_LATER_CONF "watch_later"
-static char *mp_get_playback_resume_config_filename(struct mpv_global *global,
+static char *mp_get_playback_resume_config_filename(struct MPContext *mpctx,
const char *fname)
{
- struct MPOpts *opts = global->opts;
+ struct MPOpts *opts = mpctx->opts;
char *res = NULL;
void *tmp = talloc_new(NULL);
const char *realpath = fname;
@@ -181,7 +177,7 @@ static char *mp_get_playback_resume_config_filename(struct mpv_global *global,
char *cwd = mp_getcwd(tmp);
if (!cwd)
goto exit;
- realpath = mp_path_join(tmp, bstr0(cwd), bstr0(fname));
+ realpath = mp_path_join(tmp, cwd, fname);
}
}
if (bstr_startswith0(bfname, "dvd://") && opts->dvd_device)
@@ -195,15 +191,14 @@ static char *mp_get_playback_resume_config_filename(struct mpv_global *global,
for (int i = 0; i < 16; i++)
conf = talloc_asprintf_append(conf, "%02X", md5[i]);
- res = talloc_asprintf(tmp, MP_WATCH_LATER_CONF "/%s", conf);
- res = mp_find_config_file(NULL, global, res);
-
- if (!res) {
- res = mp_find_config_file(tmp, global, MP_WATCH_LATER_CONF);
- if (res)
- res = talloc_asprintf(NULL, "%s/%s", res, conf);
+ if (!mpctx->cached_watch_later_configdir) {
+ mpctx->cached_watch_later_configdir =
+ mp_find_user_config_file(mpctx, mpctx->global, MP_WATCH_LATER_CONF);
}
+ if (mpctx->cached_watch_later_configdir)
+ res = mp_path_join(NULL, mpctx->cached_watch_later_configdir, conf);
+
exit:
talloc_free(tmp);
return res;
@@ -246,6 +241,7 @@ static const char *const backup_properties[] = {
"options/ass-style-override",
"options/ab-loop-a",
"options/ab-loop-b",
+ "options/video-aspect",
0
};
@@ -298,7 +294,7 @@ void mp_write_watch_later_conf(struct MPContext *mpctx)
mp_mk_config_dir(mpctx->global, MP_WATCH_LATER_CONF);
- conffile = mp_get_playback_resume_config_filename(mpctx->global, filename);
+ conffile = mp_get_playback_resume_config_filename(mpctx, filename);
if (!conffile)
goto exit;
@@ -342,7 +338,9 @@ exit:
void mp_load_playback_resume(struct MPContext *mpctx, const char *file)
{
- char *fname = mp_get_playback_resume_config_filename(mpctx->global, file);
+ if (!mpctx->opts->position_resume)
+ return;
+ char *fname = mp_get_playback_resume_config_filename(mpctx, file);
if (fname && mp_path_exists(fname)) {
// Never apply the saved start position to following files
m_config_backup_opt(mpctx->mconfig, "start");
@@ -365,8 +363,7 @@ struct playlist_entry *mp_check_playlist_resume(struct MPContext *mpctx,
if (!mpctx->opts->position_resume)
return NULL;
for (struct playlist_entry *e = playlist->first; e; e = e->next) {
- char *conf = mp_get_playback_resume_config_filename(mpctx->global,
- e->filename);
+ char *conf = mp_get_playback_resume_config_filename(mpctx, e->filename);
bool exists = conf && mp_path_exists(conf);
talloc_free(conf);
if (exists)
diff --git a/player/core.h b/player/core.h
index ea7694d..6ac1466 100644
--- a/player/core.h
+++ b/player/core.h
@@ -19,6 +19,7 @@
#define MPLAYER_MP_CORE_H
#include <stdbool.h>
+#include <pthread.h>
#include "libmpv/client.h"
@@ -26,6 +27,7 @@
#include "options/options.h"
#include "sub/osd.h"
#include "demux/timeline.h"
+#include "video/out/vo.h"
// definitions used internally by the core player code
@@ -36,7 +38,7 @@ enum stop_play_reason {
PT_NEXT_ENTRY, // prepare to play next entry in playlist
PT_CURRENT_ENTRY, // prepare to play mpctx->playlist->current
PT_STOP, // stop playback, clear playlist
- PT_RELOAD_DEMUXER, // restart playback, but keep stream open
+ PT_RELOAD_FILE, // restart playback
PT_QUIT, // stop playback, quit player
PT_ERROR, // play next playlist entry (due to an error)
};
@@ -74,6 +76,25 @@ enum seek_precision {
MPSEEK_VERY_EXACT,
};
+// Comes from the assumption that some formats round timestamps to ms.
+#define FRAME_DURATION_TOLERANCE 0.0011
+
+enum video_sync {
+ VS_DEFAULT = 0,
+ VS_DISP_RESAMPLE,
+ VS_DISP_RESAMPLE_VDROP,
+ VS_DISP_RESAMPLE_NONE,
+ VS_DISP_VDROP,
+ VS_DISP_NONE,
+ VS_NONE,
+};
+
+#define VS_IS_DISP(x) ((x) == VS_DISP_RESAMPLE || \
+ (x) == VS_DISP_RESAMPLE_VDROP || \
+ (x) == VS_DISP_RESAMPLE_NONE || \
+ (x) == VS_DISP_VDROP || \
+ (x) == VS_DISP_NONE)
+
struct track {
enum stream_type type;
@@ -89,7 +110,7 @@ struct track {
int ff_index; // same as stream->ff_index, or 0.
char *title;
- bool default_track;
+ bool default_track, forced_track;
bool attached_picture;
char *lang;
@@ -227,11 +248,25 @@ typedef struct MPContext {
struct vo *video_out;
// next_frame[0] is the next frame, next_frame[1] the one after that.
- struct mp_image *next_frame[2];
+ struct mp_image *next_frames[VO_MAX_REQ_FRAMES];
+ int num_next_frames;
struct mp_image *saved_frame; // for hrseek_lastframe
enum playback_status video_status, audio_status;
bool restart_complete;
+ // Factors to multiply with opts->playback_speed to get the total audio or
+ // video speed (usually 1.0, but can be set to by the sync code).
+ double speed_factor_v, speed_factor_a;
+ // Redundant values set from opts->playback_speed and speed_factor_*.
+ // update_playback_speed() updates them from the other fields.
+ double audio_speed, video_speed;
+ bool display_sync_active;
+ bool broken_fps_header;
+ double display_sync_frameduration;
+ int display_sync_drift_dir;
+ // Timing error (in seconds) due to rounding on vsync boundaries
+ double display_sync_error;
+ int display_sync_disable_counter;
/* Set if audio should be timed to start with video frame after seeking,
* not set when e.g. playing cover art */
bool sync_audio_to_video;
@@ -315,6 +350,7 @@ typedef struct MPContext {
* loaded across ordered chapters, instead of reloading and rescanning
* them on each transition. (Both of these objects contain this state.)
*/
+ pthread_mutex_t ass_lock;
struct ass_renderer *ass_renderer;
struct ass_library *ass_library;
struct mp_log *ass_log;
@@ -335,10 +371,11 @@ typedef struct MPContext {
// playback rate. Used to avoid showing it multiple times.
bool drop_message_shown;
+ char *cached_watch_later_configdir;
+
struct screenshot_ctx *screenshot_ctx;
struct command_ctx *command_ctx;
struct encode_lavc_context *encode_lavc_ctx;
- struct mp_nav_state *nav_state;
struct mp_ipc_ctx *ipc_ctx;
@@ -353,7 +390,7 @@ double playing_audio_pts(struct MPContext *mpctx);
void fill_audio_out_buffers(struct MPContext *mpctx, double endpts);
double written_audio_pts(struct MPContext *mpctx);
void clear_audio_output_buffers(struct MPContext *mpctx);
-void set_playback_speed(struct MPContext *mpctx, double new_speed);
+void update_playback_speed(struct MPContext *mpctx);
void uninit_audio_out(struct MPContext *mpctx);
void uninit_audio_chain(struct MPContext *mpctx);
@@ -366,26 +403,16 @@ void mp_write_watch_later_conf(struct MPContext *mpctx);
struct playlist_entry *mp_check_playlist_resume(struct MPContext *mpctx,
struct playlist *playlist);
-// discnav.c
-void mp_nav_init(struct MPContext *mpctx);
-void mp_nav_reset(struct MPContext *mpctx);
-void mp_nav_destroy(struct MPContext *mpctx);
-void mp_nav_user_input(struct MPContext *mpctx, char *command);
-void mp_handle_nav(struct MPContext *mpctx);
-int mp_nav_in_menu(struct MPContext *mpctx);
-bool mp_nav_mouse_on_button(struct MPContext *mpctx);
-
// loadfile.c
void uninit_player(struct MPContext *mpctx, unsigned int mask);
struct track *mp_add_external_file(struct MPContext *mpctx, char *filename,
enum stream_type filter);
+#define FLAG_MARK_SELECTION 1
void mp_switch_track(struct MPContext *mpctx, enum stream_type type,
- struct track *track);
+ struct track *track, int flags);
void mp_switch_track_n(struct MPContext *mpctx, int order,
- enum stream_type type, struct track *track);
+ enum stream_type type, struct track *track, int flags);
void mp_deselect_track(struct MPContext *mpctx, struct track *track);
-void mp_mark_user_track_selection(struct MPContext *mpctx, int order,
- enum stream_type type);
struct track *mp_track_by_tid(struct MPContext *mpctx, enum stream_type type,
int tid);
double timeline_set_from_time(struct MPContext *mpctx, double pts, bool *need_reset);
@@ -396,12 +423,12 @@ struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction,
void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e);
void mp_play_files(struct MPContext *mpctx);
void update_demuxer_properties(struct MPContext *mpctx);
-void print_track_list(struct MPContext *mpctx);
+void print_track_list(struct MPContext *mpctx, const char *msg);
void reselect_demux_streams(struct MPContext *mpctx);
void prepare_playlist(struct MPContext *mpctx, struct playlist *pl);
void autoload_external_files(struct MPContext *mpctx);
-struct track *select_track(struct MPContext *mpctx, enum stream_type type,
- int tid, int ffid, char **langs);
+struct track *select_default_track(struct MPContext *mpctx, int order,
+ enum stream_type type);
// main.c
int mp_initialize(struct MPContext *mpctx, char **argv);
@@ -422,7 +449,7 @@ float mp_get_cache_percent(struct MPContext *mpctx);
bool mp_get_cache_idle(struct MPContext *mpctx);
void update_window_title(struct MPContext *mpctx, bool force);
void error_on_track(struct MPContext *mpctx, struct track *track);
-void stream_dump(struct MPContext *mpctx);
+int stream_dump(struct MPContext *mpctx, const char *source_filename);
int mpctx_run_reentrant(struct MPContext *mpctx, void (*thread_fn)(void *arg),
void *thread_arg);
struct mpv_global *create_sub_global(struct MPContext *mpctx);
@@ -446,7 +473,6 @@ void unpause_player(struct MPContext *mpctx);
void add_step_frame(struct MPContext *mpctx, int dir);
void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount,
enum seek_precision exact, bool immediate);
-bool mp_seek_chapter(struct MPContext *mpctx, int chapter);
double get_time_length(struct MPContext *mpctx);
double get_current_time(struct MPContext *mpctx);
double get_playback_time(struct MPContext *mpctx);
@@ -464,6 +490,7 @@ void mp_idle(struct MPContext *mpctx);
void idle_loop(struct MPContext *mpctx);
void handle_force_window(struct MPContext *mpctx, bool reconfig);
void add_frame_pts(struct MPContext *mpctx, double pts);
+int get_past_frame_durations(struct MPContext *mpctx, double *fd, int num);
void seek_to_last_frame(struct MPContext *mpctx);
// scripting.c
@@ -493,5 +520,6 @@ void write_video(struct MPContext *mpctx, double endpts);
void mp_force_video_refresh(struct MPContext *mpctx);
void uninit_video_out(struct MPContext *mpctx);
void uninit_video_chain(struct MPContext *mpctx);
+double stabilize_frame_duration(struct MPContext *mpctx, bool require_exact);
#endif /* MPLAYER_MP_CORE_H */
diff --git a/player/discnav.c b/player/discnav.c
deleted file mode 100644
index 719e88a..0000000
--- a/player/discnav.c
+++ /dev/null
@@ -1,376 +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/>.
- */
-
-#include <limits.h>
-#include <pthread.h>
-#include <assert.h>
-
-#include "core.h"
-#include "command.h"
-
-#include "common/msg.h"
-#include "common/common.h"
-#include "input/input.h"
-
-#include "demux/demux.h"
-#include "stream/discnav.h"
-
-#include "sub/dec_sub.h"
-#include "sub/osd.h"
-
-#include "video/mp_image.h"
-#include "video/decode/dec_video.h"
-
-struct mp_nav_state {
- struct mp_log *log;
-
- int nav_still_frame;
- bool nav_eof;
- bool nav_menu;
- bool nav_draining;
- bool nav_mouse_on_button;
-
- // Accessed by OSD (possibly separate thread)
- // Protected by the given lock
- pthread_mutex_t osd_lock;
- int hi_visible;
- int highlight[4]; // x0 y0 x1 y1
- int vidsize[2];
- int subsize[2];
- struct sub_bitmap *hi_elem;
- struct sub_bitmap *overlays[2];
- struct sub_bitmap outputs[3];
-};
-
-static inline bool is_valid_size(int size[2])
-{
- return size[0] >= 1 && size[1] >= 1;
-}
-
-static void update_resolution(struct MPContext *mpctx)
-{
- struct mp_nav_state *nav = mpctx->nav_state;
- int size[2] = {0};
- if (mpctx->d_sub[0])
- sub_control(mpctx->d_sub[0], SD_CTRL_GET_RESOLUTION, size);
- if (!is_valid_size(size)) {
- struct mp_image_params vid = {0};
- if (mpctx->d_video)
- vid = mpctx->d_video->decoder_output;
- size[0] = vid.w;
- size[1] = vid.h;
- }
- pthread_mutex_lock(&nav->osd_lock);
- nav->vidsize[0] = size[0];
- nav->vidsize[1] = size[1];
- pthread_mutex_unlock(&nav->osd_lock);
-}
-
-// Send update events and such.
-static void update_state(struct MPContext *mpctx)
-{
- mp_notify_property(mpctx, "disc-menu-active");
-}
-
-// Return 1 if in menu, 0 if in video, or <0 if no navigation possible.
-int mp_nav_in_menu(struct MPContext *mpctx)
-{
- return mpctx->nav_state ? mpctx->nav_state->nav_menu : -1;
-}
-
-static void update_mouse_on_button(struct MPContext *mpctx)
-{
- mp_notify_property(mpctx, "disc-mouse-on-button");
-}
-
-static void set_mouse_on_button(struct MPContext *mpctx, bool in)
-{
- struct mp_nav_state *nav = mpctx->nav_state;
- if (nav->nav_mouse_on_button != in) {
- nav->nav_mouse_on_button = in;
- update_mouse_on_button(mpctx);
- }
-}
-
-bool mp_nav_mouse_on_button(struct MPContext *mpctx)
-{
- return mpctx->nav_state ? mpctx->nav_state->nav_mouse_on_button : false;
-}
-
-// If a demuxer is accessing the stream, we have to use demux_stream_control()
-// to avoid synchronization issues; otherwise access it directly.
-static int run_stream_control(struct MPContext *mpctx, int cmd, void *arg)
-{
- if (mpctx->demuxer) {
- return demux_stream_control(mpctx->demuxer, cmd, arg);
- } else if (mpctx->stream) {
- return stream_control(mpctx->stream, cmd, arg);
- }
- return STREAM_ERROR;
-}
-
-// Allocate state and enable navigation features. Must happen before
-// initializing cache, because the cache would read data. Since stream_dvdnav is
-// in a mode which skips all transitions on reading data (before enabling
-// navigation), this would skip some menu screens.
-void mp_nav_init(struct MPContext *mpctx)
-{
- assert(!mpctx->nav_state);
-
- // dvdnav is interactive
- if (mpctx->encode_lavc_ctx)
- return;
-
- struct mp_nav_cmd inp = {MP_NAV_CMD_ENABLE};
- if (run_stream_control(mpctx, STREAM_CTRL_NAV_CMD, &inp) < 1)
- return;
-
- mpctx->nav_state = talloc_zero(NULL, struct mp_nav_state);
- mpctx->nav_state->log = mp_log_new(mpctx->nav_state, mpctx->log, "discnav");
- pthread_mutex_init(&mpctx->nav_state->osd_lock, NULL);
-
- MP_VERBOSE(mpctx->nav_state, "enabling\n");
-
- mp_input_enable_section(mpctx->input, "discnav",
- MP_INPUT_ALLOW_VO_DRAGGING | MP_INPUT_ALLOW_HIDE_CURSOR);
-
- update_state(mpctx);
- update_mouse_on_button(mpctx);
-}
-
-void mp_nav_reset(struct MPContext *mpctx)
-{
- struct mp_nav_state *nav = mpctx->nav_state;
- if (!nav)
- return;
- struct mp_nav_cmd inp = {MP_NAV_CMD_RESUME};
- run_stream_control(mpctx, STREAM_CTRL_NAV_CMD, &inp);
- osd_set_nav_highlight(mpctx->osd, NULL);
- nav->hi_visible = 0;
- nav->nav_menu = false;
- nav->nav_draining = false;
- nav->nav_still_frame = 0;
- mp_input_disable_section(mpctx->input, "discnav-menu");
- run_stream_control(mpctx, STREAM_CTRL_RESUME_CACHE, NULL);
- update_state(mpctx);
-}
-
-void mp_nav_destroy(struct MPContext *mpctx)
-{
- osd_set_nav_highlight(mpctx->osd, NULL);
- if (!mpctx->nav_state)
- return;
- mp_input_disable_section(mpctx->input, "discnav");
- mp_input_disable_section(mpctx->input, "discnav-menu");
- pthread_mutex_destroy(&mpctx->nav_state->osd_lock);
- talloc_free(mpctx->nav_state);
- mpctx->nav_state = NULL;
- update_state(mpctx);
- update_mouse_on_button(mpctx);
-}
-
-void mp_nav_user_input(struct MPContext *mpctx, char *command)
-{
- struct mp_nav_state *nav = mpctx->nav_state;
- if (!nav)
- return;
- // In the short time while the demuxer is opened (running in a different
- // thread) we can't access the stream directly. Once the demuxer is opened,
- // we can access the stream via demux_stream_control() though.
- if (!mpctx->demuxer)
- return;
- if (strcmp(command, "mouse_move") == 0) {
- struct mp_image_params vid = {0};
- if (mpctx->d_video)
- vid = mpctx->d_video->decoder_output;
- struct mp_nav_cmd inp = {MP_NAV_CMD_MOUSE_POS};
- int x, y;
- mp_input_get_mouse_pos(mpctx->input, &x, &y);
- osd_coords_to_video(mpctx->osd, vid.w, vid.h, &x, &y);
- inp.u.mouse_pos.x = x;
- inp.u.mouse_pos.y = y;
- run_stream_control(mpctx, STREAM_CTRL_NAV_CMD, &inp);
- set_mouse_on_button(mpctx, inp.mouse_on_button);
- } else {
- struct mp_nav_cmd inp = {MP_NAV_CMD_MENU};
- inp.u.menu.action = command;
- run_stream_control(mpctx, STREAM_CTRL_NAV_CMD, &inp);
- }
-}
-
-void mp_handle_nav(struct MPContext *mpctx)
-{
- struct mp_nav_state *nav = mpctx->nav_state;
- if (!nav)
- return;
- mpctx->sleeptime = MPMIN(mpctx->sleeptime, 0.5);
- while (1) {
- if (!mpctx->demuxer)
- break;
- struct mp_nav_event *ev = NULL;
- demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_NAV_EVENT, &ev);
- if (!ev)
- break;
- switch (ev->event) {
- case MP_NAV_EVENT_DRAIN: {
- nav->nav_draining = true;
- MP_VERBOSE(nav, "drain requested\n");
- break;
- }
- case MP_NAV_EVENT_RESET_ALL: {
- mpctx->stop_play = PT_RELOAD_DEMUXER;
- MP_VERBOSE(nav, "reload\n");
- // return immediately.
- // other events should be handled after reloaded.
- talloc_free(ev);
- return;
- }
- case MP_NAV_EVENT_RESET: {
- nav->nav_still_frame = 0;
- break;
- }
- case MP_NAV_EVENT_EOF:
- nav->nav_eof = true;
- break;
- case MP_NAV_EVENT_STILL_FRAME: {
- int len = ev->u.still_frame.seconds;
- MP_VERBOSE(nav, "wait for %d seconds\n", len);
- if (len > 0 && nav->nav_still_frame == 0)
- nav->nav_still_frame = len;
- break;
- }
- case MP_NAV_EVENT_MENU_MODE:
- nav->nav_menu = ev->u.menu_mode.enable;
- if (nav->nav_menu) {
- mp_input_enable_section(mpctx->input, "discnav-menu",
- MP_INPUT_ON_TOP);
- } else {
- mp_input_disable_section(mpctx->input, "discnav-menu");
- }
- update_state(mpctx);
- break;
- case MP_NAV_EVENT_HIGHLIGHT: {
- pthread_mutex_lock(&nav->osd_lock);
- MP_VERBOSE(nav, "highlight: %d %d %d - %d %d\n",
- ev->u.highlight.display,
- ev->u.highlight.sx, ev->u.highlight.sy,
- ev->u.highlight.ex, ev->u.highlight.ey);
- nav->highlight[0] = ev->u.highlight.sx;
- nav->highlight[1] = ev->u.highlight.sy;
- nav->highlight[2] = ev->u.highlight.ex;
- nav->highlight[3] = ev->u.highlight.ey;
- nav->hi_visible = ev->u.highlight.display;
- pthread_mutex_unlock(&nav->osd_lock);
- update_resolution(mpctx);
- osd_set_nav_highlight(mpctx->osd, mpctx);
- break;
- }
- case MP_NAV_EVENT_OVERLAY: {
- pthread_mutex_lock(&nav->osd_lock);
- for (int i = 0; i < 2; i++) {
- if (nav->overlays[i])
- talloc_free(nav->overlays[i]);
- nav->overlays[i] = talloc_steal(nav, ev->u.overlay.images[i]);
- }
- pthread_mutex_unlock(&nav->osd_lock);
- update_resolution(mpctx);
- osd_set_nav_highlight(mpctx->osd, mpctx);
- break;
- }
- default: ; // ignore
- }
- talloc_free(ev);
- }
- update_resolution(mpctx);
- if (mpctx->stop_play == AT_END_OF_FILE) {
- if (nav->nav_still_frame > 0) {
- // gross hack
- mpctx->time_frame += nav->nav_still_frame;
- nav->nav_still_frame = -2;
- } else if (nav->nav_still_frame == -2) {
- struct mp_nav_cmd inp = {MP_NAV_CMD_SKIP_STILL};
- run_stream_control(mpctx, STREAM_CTRL_NAV_CMD, &inp);
- }
- }
- if (nav->nav_draining && mpctx->stop_play == AT_END_OF_FILE) {
- MP_VERBOSE(nav, "execute drain\n");
- struct mp_nav_cmd inp = {MP_NAV_CMD_DRAIN_OK};
- run_stream_control(mpctx, STREAM_CTRL_NAV_CMD, &inp);
- nav->nav_draining = false;
- run_stream_control(mpctx, STREAM_CTRL_RESUME_CACHE, NULL);
- }
- // E.g. keep displaying still frames
- if (mpctx->stop_play == AT_END_OF_FILE && !nav->nav_eof)
- mpctx->stop_play = KEEP_PLAYING;
-}
-
-// Render "fake" highlights, because using actual dvd sub highlight elements
-// is too hard, and would require changes to libavcodec's dvdsub decoder.
-// Note: a proper solution would introduce something like
-// SD_CTRL_APPLY_DVDNAV, which would crop the vobsub frame,
-// and apply the current CLUT.
-void mp_nav_get_highlight(void *priv, struct mp_osd_res res,
- struct sub_bitmaps *out_imgs)
-{
- struct MPContext *mpctx = priv;
- struct mp_nav_state *nav = mpctx->nav_state;
-
- pthread_mutex_lock(&nav->osd_lock);
-
- struct sub_bitmap *sub = nav->hi_elem;
- if (!sub)
- sub = talloc_zero(nav, struct sub_bitmap);
-
- nav->hi_elem = sub;
- if (!is_valid_size(nav->vidsize)) {
- pthread_mutex_unlock(&nav->osd_lock);
- return;
- }
- int sizes[2] = {nav->vidsize[0], nav->vidsize[1]};
- if (sizes[0] != nav->subsize[0] || sizes[1] != nav->subsize[1]) {
- talloc_free(sub->bitmap);
- sub->bitmap = talloc_array(sub, uint32_t, sizes[0] * sizes[1]);
- memset(sub->bitmap, 0x80, talloc_get_size(sub->bitmap));
- nav->subsize[0] = sizes[0];
- nav->subsize[1] = sizes[1];
- }
-
- out_imgs->num_parts = 0;
-
- if (nav->hi_visible) {
- sub->x = nav->highlight[0];
- sub->y = nav->highlight[1];
- sub->w = MPCLAMP(nav->highlight[2] - sub->x, 0, sizes[0]);
- sub->h = MPCLAMP(nav->highlight[3] - sub->y, 0, sizes[1]);
- sub->stride = sub->w * 4;
- if (sub->w > 0 && sub->h > 0)
- nav->outputs[out_imgs->num_parts++] = *sub;
- }
-
- if (nav->overlays[0])
- nav->outputs[out_imgs->num_parts++] = *nav->overlays[0];
- if (nav->overlays[1])
- nav->outputs[out_imgs->num_parts++] = *nav->overlays[1];
-
- if (out_imgs->num_parts) {
- out_imgs->parts = nav->outputs;
- out_imgs->format = SUBBITMAP_RGBA;
- osd_rescale_bitmaps(out_imgs, sizes[0], sizes[1], res, 0);
- }
-
- pthread_mutex_unlock(&nav->osd_lock);
-}
diff --git a/player/loadfile.c b/player/loadfile.c
index 40ed71d..c6f0a17 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -90,7 +90,7 @@ static void uninit_demuxer(struct MPContext *mpctx)
timeline_destroy(mpctx->tl);
mpctx->tl = NULL;
- free_demuxer(mpctx->master_demuxer);
+ free_demuxer_and_stream(mpctx->master_demuxer);
mpctx->master_demuxer = NULL;
talloc_free(mpctx->sources);
@@ -132,6 +132,8 @@ static void print_stream(struct MPContext *mpctx, struct track *t)
APPEND(b, " --%s=%s", langopt, t->lang);
if (t->default_track)
APPEND(b, " (*)");
+ if (t->forced_track)
+ APPEND(b, " (f)");
if (t->attached_picture)
APPEND(b, " [P]");
if (t->title)
@@ -143,8 +145,10 @@ static void print_stream(struct MPContext *mpctx, struct track *t)
MP_INFO(mpctx, "%s\n", b);
}
-void print_track_list(struct MPContext *mpctx)
+void print_track_list(struct MPContext *mpctx, const char *msg)
{
+ if (msg)
+ MP_INFO(mpctx, "%s\n", msg);
for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
for (int n = 0; n < mpctx->num_tracks; n++)
if (mpctx->tracks[n]->type == t)
@@ -176,7 +180,7 @@ void update_demuxer_properties(struct MPContext *mpctx)
struct demuxer *tracks = mpctx->track_layout;
if (tracks->events & DEMUX_EVENT_STREAMS) {
add_demuxer_tracks(mpctx, tracks);
- print_track_list(mpctx);
+ print_track_list(mpctx, NULL);
tracks->events &= ~DEMUX_EVENT_STREAMS;
}
if (events & DEMUX_EVENT_METADATA) {
@@ -381,6 +385,7 @@ static struct track *add_stream_track(struct MPContext *mpctx,
.ff_index = stream->ff_index,
.title = stream->title,
.default_track = stream->default_track,
+ .forced_track = stream->forced_track,
.attached_picture = stream->attached_picture != NULL,
.lang = stream->lang,
.under_timeline = under_timeline,
@@ -422,7 +427,8 @@ static int match_lang(char **langs, char *lang)
* 1) track is external (no_default cancels this)
* 1b) track was passed explicitly (is not an auto-loaded subtitle)
* 2) earlier match in lang list
- * 3) track is marked default
+ * 3a) track is marked forced
+ * 3b) track is marked default
* 4) attached picture, HLS bitrate
* 5) lower track number
* If select_fallback is not set, 5) is only used to determine whether a
@@ -442,20 +448,32 @@ static bool compare_track(struct track *t1, struct track *t2, char **langs,
int l1 = match_lang(langs, t1->lang), l2 = match_lang(langs, t2->lang);
if (l1 != l2)
return l1 > l2;
+ if (t1->forced_track != t2->forced_track)
+ return t1->forced_track;
if (t1->default_track != t2->default_track)
return t1->default_track;
if (t1->attached_picture != t2->attached_picture)
return !t1->attached_picture;
- if (t1->stream && t2->stream && opts->hls_bitrate) {
- int d = t1->stream->hls_bitrate - t2->stream->hls_bitrate;
- if (d)
- return opts->hls_bitrate == 1 ? d < 0 : d > 0;
+ if (t1->stream && t2->stream && opts->hls_bitrate >= 0 &&
+ t1->stream->hls_bitrate != t2->stream->hls_bitrate)
+ {
+ bool t1_ok = t1->stream->hls_bitrate <= opts->hls_bitrate;
+ bool t2_ok = t2->stream->hls_bitrate <= opts->hls_bitrate;
+ if (t1_ok != t2_ok)
+ return t1_ok;
+ if (t1_ok && t2_ok)
+ return t1->stream->hls_bitrate > t2->stream->hls_bitrate;
+ return t1->stream->hls_bitrate < t2->stream->hls_bitrate;
}
return t1->user_tid <= t2->user_tid;
}
-struct track *select_track(struct MPContext *mpctx, enum stream_type type,
- int tid, int ffid, char **langs)
+struct track *select_default_track(struct MPContext *mpctx, int order,
+ enum stream_type type)
{
+ struct MPOpts *opts = mpctx->opts;
+ int tid = opts->stream_id[order][type];
+ int ffid = order == 0 ? opts->stream_id_ff[type] : -1;
+ char **langs = order == 0 ? opts->stream_lang[type] : NULL;
if (ffid != -1)
tid = -1; // prefer selecting ffid
if (tid == -2 || ffid == -2)
@@ -474,7 +492,8 @@ struct track *select_track(struct MPContext *mpctx, enum stream_type type,
pick = track;
}
if (pick && !select_fallback && !(pick->is_external && !pick->no_default)
- && !match_lang(langs, pick->lang) && !pick->default_track)
+ && !match_lang(langs, pick->lang) && !pick->default_track
+ && !pick->forced_track)
pick = NULL;
if (pick && pick->attached_picture && !mpctx->opts->audio_display)
pick = NULL;
@@ -508,15 +527,14 @@ static void check_previous_track_selection(struct MPContext *mpctx)
char *h = track_layout_hash(mpctx);
if (strcmp(h, mpctx->track_layout_hash) != 0) {
- // Reset selection, but only if they're not "auto" or "off".
- if (opts->video_id >= 0)
- mpctx->opts->video_id = -1;
- if (opts->audio_id >= 0)
- mpctx->opts->audio_id = -1;
- if (opts->sub_id >= 0)
- mpctx->opts->sub_id = -1;
- if (opts->sub2_id >= 0)
- mpctx->opts->sub2_id = -2;
+ // Reset selection, but only if they're not "auto" or "off". The
+ // defaults are -1 (default selection), or -2 (off) for secondary tracks.
+ for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
+ for (int i = 0; i < NUM_PTRACKS; i++) {
+ if (opts->stream_id[i][t] >= 0)
+ opts->stream_id[i][t] = i == 0 ? -1 : -2;
+ }
+ }
talloc_free(mpctx->track_layout_hash);
mpctx->track_layout_hash = NULL;
}
@@ -524,11 +542,20 @@ static void check_previous_track_selection(struct MPContext *mpctx)
}
void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type,
- struct track *track)
+ struct track *track, int flags)
{
assert(!track || track->type == type);
assert(order >= 0 && order < NUM_PTRACKS);
+ // Mark the current track selection as explicitly user-requested. (This is
+ // different from auto-selection or disabling a track due to errors.)
+ if (flags & FLAG_MARK_SELECTION)
+ mpctx->opts->stream_id[order][type] = track ? track->user_tid : -2;
+
+ // No decoder should be initialized yet.
+ if (!mpctx->demuxer)
+ return;
+
struct track *current = mpctx->current_track[order][type];
if (track == current)
return;
@@ -589,34 +616,16 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type
}
void mp_switch_track(struct MPContext *mpctx, enum stream_type type,
- struct track *track)
+ struct track *track, int flags)
{
- mp_switch_track_n(mpctx, 0, type, track);
+ mp_switch_track_n(mpctx, 0, type, track, flags);
}
void mp_deselect_track(struct MPContext *mpctx, struct track *track)
{
if (track && track->selected) {
for (int t = 0; t < NUM_PTRACKS; t++)
- mp_switch_track_n(mpctx, t, track->type, NULL);
- }
-}
-
-// Mark the current track selection as explicitly user-requested. (This is
-// different from auto-selection or disabling a track due to errors.)
-void mp_mark_user_track_selection(struct MPContext *mpctx, int order,
- enum stream_type type)
-{
- struct track *track = mpctx->current_track[order][type];
- int user_tid = track ? track->user_tid : -2;
- if (type == STREAM_VIDEO && order == 0) {
- mpctx->opts->video_id = user_tid;
- } else if (type == STREAM_AUDIO && order == 0) {
- mpctx->opts->audio_id = user_tid;
- } else if (type == STREAM_SUB && order == 0) {
- mpctx->opts->sub_id = user_tid;
- } else if (type == STREAM_SUB && order == 1) {
- mpctx->opts->sub2_id = user_tid;
+ mp_switch_track_n(mpctx, t, track->type, NULL, 0);
}
}
@@ -795,13 +804,20 @@ void prepare_playlist(struct MPContext *mpctx, struct playlist *pl)
{
struct MPOpts *opts = mpctx->opts;
+ pl->current = NULL;
+
+ if (opts->playlist_pos >= 0)
+ pl->current = playlist_entry_from_index(pl, opts->playlist_pos);
+
if (opts->shuffle)
playlist_shuffle(pl);
if (opts->merge_files)
merge_playlist_files(pl);
- pl->current = mp_check_playlist_resume(mpctx, pl);
+ if (!pl->current)
+ pl->current = mp_check_playlist_resume(mpctx, pl);
+
if (!pl->current)
pl->current = pl->first;
}
@@ -879,14 +895,11 @@ static void load_chapters(struct MPContext *mpctx)
bool free_src = false;
char *chapter_file = mpctx->opts->chapter_file;
if (chapter_file && chapter_file[0]) {
- struct stream *stream = stream_create(chapter_file, STREAM_READ,
+ struct demuxer *demux = demux_open_url(chapter_file, NULL,
mpctx->playback_abort, mpctx->global);
- if (stream) {
- struct demuxer *demux = demux_open(stream, NULL, mpctx->global);
- if (demux) {
- src = demux;
- free_src = true;
- }
+ if (demux) {
+ src = demux;
+ free_src = true;
}
talloc_free(mpctx->chapters);
mpctx->chapters = NULL;
@@ -896,11 +909,8 @@ static void load_chapters(struct MPContext *mpctx)
mpctx->num_chapters = src->num_chapters;
mpctx->chapters = demux_copy_chapter_data(src->chapters, src->num_chapters);
}
- if (free_src) {
- struct stream *s = src->stream;
- free_demuxer(src);
- free_stream(s);
- }
+ if (free_src)
+ free_demuxer_and_stream(src);
}
static void load_per_file_options(m_config_t *conf,
@@ -913,55 +923,35 @@ static void load_per_file_options(m_config_t *conf,
}
}
-struct stream_open_args {
- struct mp_cancel *cancel;
- struct mpv_global *global; // contains copy of global options
- char *filename;
- int stream_flags;
- struct stream *stream; // result
-};
-
-static void open_stream_thread(void *pctx)
-{
- struct stream_open_args *args = pctx;
- args->stream = stream_create(args->filename, args->stream_flags,
- args->cancel, args->global);
-}
-
-static struct stream *open_stream_reentrant(struct MPContext *mpctx,
- char *filename, int stream_flags)
-{
- struct stream_open_args args = {
- .cancel = mpctx->playback_abort,
- .global = create_sub_global(mpctx),
- .filename = filename,
- .stream_flags = stream_flags,
- };
- mpctx_run_reentrant(mpctx, open_stream_thread, &args);
- if (args.stream) {
- talloc_steal(args.stream, args.global);
- } else {
- talloc_free(args.global);
- }
- return args.stream;
-}
-
struct demux_open_args {
- struct stream *stream;
+ int stream_flags;
+ char *url;
struct mpv_global *global;
+ struct mp_cancel *cancel;
struct mp_log *log;
// results
struct demuxer *demux;
struct timeline *tl;
+ int err;
};
static void open_demux_thread(void *pctx)
{
struct demux_open_args *args = pctx;
- struct stream *s = args->stream;
struct mpv_global *global = args->global;
- struct demuxer_params p = {.force_format = global->opts->demuxer_name};
- args->demux = demux_open(s, &p, global);
+ struct demuxer_params p = {
+ .force_format = global->opts->demuxer_name,
+ .allow_capture = true,
+ .stream_flags = args->stream_flags,
+ };
+ args->demux = demux_open_url(args->url, &p, args->cancel, global);
+ if (!args->demux) {
+ if (p.demuxer_failed) {
+ args->err = MPV_ERROR_UNKNOWN_FORMAT;
+ } else {
+ args->err = MPV_ERROR_LOADING_FAILED;
+ }
+ }
if (args->demux)
args->tl = timeline_load(global, args->log, args->demux);
}
@@ -970,17 +960,23 @@ static void open_demux_reentrant(struct MPContext *mpctx)
{
struct demux_open_args args = {
.global = create_sub_global(mpctx),
- .stream = mpctx->stream,
+ .cancel = mpctx->playback_abort,
.log = mpctx->log,
+ .stream_flags = mpctx->playing->stream_flags,
+ .url = talloc_strdup(NULL, mpctx->stream_open_filename),
};
+ if (mpctx->opts->load_unsafe_playlists)
+ args.stream_flags = 0;
mpctx_run_reentrant(mpctx, open_demux_thread, &args);
if (args.demux) {
talloc_steal(args.demux, args.global);
mpctx->master_demuxer = args.demux;
mpctx->tl = args.tl;
} else {
+ mpctx->error_playing = args.err;
talloc_free(args.global);
}
+ talloc_free(args.url);
}
static void load_timeline(struct MPContext *mpctx)
@@ -1034,6 +1030,12 @@ static void play_current_file(struct MPContext *mpctx)
mpctx->playing_msg_shown = false;
mpctx->backstep_active = false;
mpctx->max_frames = -1;
+ mpctx->video_speed = mpctx->audio_speed = opts->playback_speed;
+ mpctx->speed_factor_a = mpctx->speed_factor_v = 1.0;
+ mpctx->display_sync_frameduration = 0.0;
+ mpctx->display_sync_error = 0.0;
+ mpctx->broken_fps_header = false;
+ mpctx->display_sync_active = false;
mpctx->seek = (struct seek_params){ 0 };
reset_playback_state(mpctx);
@@ -1063,8 +1065,7 @@ static void play_current_file(struct MPContext *mpctx)
mp_load_auto_profiles(mpctx);
- if (opts->position_resume)
- mp_load_playback_resume(mpctx, mpctx->filename);
+ mp_load_playback_resume(mpctx, mpctx->filename);
load_per_file_options(mpctx->mconfig, mpctx->playing->params,
mpctx->playing->num_params);
@@ -1073,6 +1074,8 @@ static void play_current_file(struct MPContext *mpctx)
MP_INFO(mpctx, "Playing: %s\n", mpctx->filename);
+reopen_file:
+
assert(mpctx->stream == NULL);
assert(mpctx->demuxer == NULL);
assert(mpctx->d_audio == NULL);
@@ -1083,42 +1086,15 @@ static void play_current_file(struct MPContext *mpctx)
if (process_open_hooks(mpctx) < 0)
goto terminate_playback;
- int stream_flags = STREAM_READ;
- if (!opts->load_unsafe_playlists)
- stream_flags |= mpctx->playing->stream_flags;
- mpctx->stream = open_stream_reentrant(mpctx, mpctx->stream_open_filename,
- stream_flags);
- if (!mpctx->stream)
- goto terminate_playback;
-
if (opts->stream_dump && opts->stream_dump[0]) {
- stream_dump(mpctx);
- mpctx->error_playing = 1;
+ if (stream_dump(mpctx, mpctx->stream_open_filename) < 0)
+ mpctx->error_playing = 1;
goto terminate_playback;
}
- // Must be called before enabling cache.
- mp_nav_init(mpctx);
-
- stream_enable_cache(&mpctx->stream, &opts->stream_cache);
-
- mp_notify(mpctx, MP_EVENT_CHANGE_ALL, NULL);
- mp_process_input(mpctx);
- if (mpctx->stop_play)
- goto terminate_playback;
-
- stream_set_capture_file(mpctx->stream, opts->stream_capture);
-
-goto_reopen_demuxer: ;
-
- mp_nav_reset(mpctx);
-
open_demux_reentrant(mpctx);
- if (!mpctx->master_demuxer) {
- MP_ERR(mpctx, "Failed to recognize file format.\n");
- mpctx->error_playing = MPV_ERROR_UNKNOWN_FORMAT;
+ if (!mpctx->master_demuxer)
goto terminate_playback;
- }
mpctx->demuxer = mpctx->master_demuxer;
load_timeline(mpctx);
@@ -1135,7 +1111,7 @@ goto_reopen_demuxer: ;
e->stream_flags |= entry_stream_flags;
transfer_playlist(mpctx, pl);
mp_notify_property(mpctx, "playlist");
- mpctx->error_playing = 1;
+ mpctx->error_playing = 2;
goto terminate_playback;
}
@@ -1152,17 +1128,11 @@ goto_reopen_demuxer: ;
check_previous_track_selection(mpctx);
- mpctx->current_track[0][STREAM_VIDEO] =
- select_track(mpctx, STREAM_VIDEO, opts->video_id, opts->video_id_ff,
- NULL);
- mpctx->current_track[0][STREAM_AUDIO] =
- select_track(mpctx, STREAM_AUDIO, opts->audio_id, opts->audio_id_ff,
- opts->audio_lang);
- mpctx->current_track[0][STREAM_SUB] =
- select_track(mpctx, STREAM_SUB, opts->sub_id, opts->sub_id_ff,
- opts->sub_lang);
- mpctx->current_track[1][STREAM_SUB] =
- select_track(mpctx, STREAM_SUB, opts->sub2_id, -1, NULL);
+ assert(NUM_PTRACKS == 2); // opts->stream_id is hardcoded to 2
+ for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
+ for (int i = 0; i < NUM_PTRACKS; i++)
+ mpctx->current_track[i][t] = select_default_track(mpctx, i, t);
+ }
for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
for (int i = 0; i < NUM_PTRACKS; i++) {
struct track *track = mpctx->current_track[i][t];
@@ -1205,12 +1175,6 @@ goto_reopen_demuxer: ;
!mpctx->current_track[0][STREAM_AUDIO])
{
MP_FATAL(mpctx, "No video or audio streams selected.\n");
- struct demuxer *d = mpctx->demuxer;
- if (d->stream->uncached_type == STREAMTYPE_DVB) {
- int dir = mpctx->last_dvb_step;
- if (demux_stream_control(d, STREAM_CTRL_DVB_STEP_CHANNEL, &dir) > 0)
- mpctx->stop_play = PT_RELOAD_DEMUXER;
- }
mpctx->error_playing = MPV_ERROR_NOTHING_TO_PLAY;
goto terminate_playback;
}
@@ -1223,7 +1187,8 @@ goto_reopen_demuxer: ;
MP_VERBOSE(mpctx, "Starting playback...\n");
if (mpctx->max_frames == 0) {
- mpctx->stop_play = PT_NEXT_ENTRY;
+ if (!mpctx->stop_play)
+ mpctx->stop_play = PT_NEXT_ENTRY;
mpctx->error_playing = 0;
goto terminate_playback;
}
@@ -1258,20 +1223,8 @@ goto_reopen_demuxer: ;
terminate_playback:
- if (mpctx->stop_play == PT_RELOAD_DEMUXER) {
- mpctx->stop_play = KEEP_PLAYING;
- mpctx->playback_initialized = false;
- uninit_audio_chain(mpctx);
- uninit_video_chain(mpctx);
- uninit_sub_all(mpctx);
- uninit_demuxer(mpctx);
- goto goto_reopen_demuxer;
- }
-
process_unload_hooks(mpctx);
- mp_nav_destroy(mpctx);
-
if (mpctx->stop_play == KEEP_PLAYING)
mpctx->stop_play = AT_END_OF_FILE;
@@ -1283,8 +1236,6 @@ terminate_playback:
mp_cancel_trigger(mpctx->playback_abort);
- MP_INFO(mpctx, "\n");
-
// time to uninit all, except global stuff:
uninit_audio_chain(mpctx);
uninit_video_chain(mpctx);
@@ -1295,13 +1246,18 @@ terminate_playback:
if (!opts->gapless_audio && !mpctx->encode_lavc_ctx)
uninit_audio_out(mpctx);
+ mpctx->playback_initialized = false;
+
+ if (mpctx->stop_play == PT_RELOAD_FILE) {
+ mpctx->stop_play = KEEP_PLAYING;
+ goto reopen_file;
+ }
+
m_config_restore_backups(mpctx->mconfig);
talloc_free(mpctx->filtered_tags);
mpctx->filtered_tags = NULL;
- mpctx->playback_initialized = false;
-
mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL);
bool nothing_played = !mpctx->shown_aframes && !mpctx->shown_vframes &&
@@ -1311,11 +1267,13 @@ terminate_playback:
case PT_ERROR:
case AT_END_OF_FILE:
{
- if (mpctx->error_playing >= 0 && nothing_played)
+ if (mpctx->error_playing == 0 && nothing_played)
mpctx->error_playing = MPV_ERROR_NOTHING_TO_PLAY;
- end_event.error = mpctx->error_playing;
- if (end_event.error < 0) {
+ if (mpctx->error_playing < 0) {
+ end_event.error = mpctx->error_playing;
end_event.reason = MPV_END_FILE_REASON_ERROR;
+ } else if (mpctx->error_playing == 2) {
+ end_event.reason = MPV_END_FILE_REASON_REDIRECT;
} else {
end_event.reason = MPV_END_FILE_REASON_EOF;
}
@@ -1335,6 +1293,12 @@ terminate_playback:
};
mp_notify(mpctx, MPV_EVENT_END_FILE, &end_event);
+ MP_VERBOSE(mpctx, "finished playback, %s (reason %d)\n",
+ mpv_error_string(end_event.error), end_event.reason);
+ if (mpctx->error_playing == MPV_ERROR_UNKNOWN_FORMAT)
+ MP_ERR(mpctx, "Failed to recognize file format.\n");
+ MP_INFO(mpctx, "\n");
+
if (mpctx->playing)
playlist_entry_unref(mpctx->playing);
mpctx->playing = NULL;
@@ -1434,5 +1398,6 @@ void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e)
assert(!e || playlist_entry_to_index(mpctx->playlist, e) >= 0);
mpctx->playlist->current = e;
mpctx->playlist->current_was_replaced = false;
- mpctx->stop_play = PT_CURRENT_ENTRY;
+ if (!mpctx->stop_play)
+ mpctx->stop_play = PT_CURRENT_ENTRY;
}
diff --git a/player/lua.c b/player/lua.c
index 4ef772f..cf0c5ca 100644
--- a/player/lua.c
+++ b/player/lua.c
@@ -277,16 +277,10 @@ static void set_path(lua_State *L)
const char *path = lua_tostring(L, -1);
char *newpath = talloc_strdup(tmp, path ? path : "");
- char *dirs[] = {"scripts", "lua", NULL};
- for (int s = 0; dirs[s]; s++) {
- char **luadir = mp_find_all_config_files(tmp, get_mpctx(L)->global,
- dirs[s]);
- for (int i = 0; luadir && luadir[i]; i++) {
- // No need to display a warning for lua files in the deprecated
- // 'lua' dirs since scripting.c already warned on them
- newpath = talloc_asprintf_append(newpath, ";%s",
- mp_path_join(tmp, bstr0(luadir[i]), bstr0("?.lua")));
- }
+ char **luadir = mp_find_all_config_files(tmp, get_mpctx(L)->global, "scripts");
+ for (int i = 0; luadir && luadir[i]; i++) {
+ newpath = talloc_asprintf_append(newpath, ";%s",
+ mp_path_join(tmp, luadir[i], "?.lua"));
}
lua_pushstring(L, newpath); // package path newpath
@@ -518,6 +512,27 @@ static int script_wait_event(lua_State *L)
lua_setfield(L, -2, "args"); // event
break;
}
+ case MPV_EVENT_END_FILE: {
+ mpv_event_end_file *eef = event->data;
+ const char *reason;
+ switch (eef->reason) {
+ case MPV_END_FILE_REASON_EOF: reason = "eof"; break;
+ case MPV_END_FILE_REASON_STOP: reason = "stop"; break;
+ case MPV_END_FILE_REASON_QUIT: reason = "quit"; break;
+ case MPV_END_FILE_REASON_ERROR: reason = "error"; break;
+ case MPV_END_FILE_REASON_REDIRECT: reason = "redirect"; break;
+ default:
+ reason = "unknown";
+ }
+ lua_pushstring(L, reason); // event reason
+ lua_setfield(L, -2, "reason"); // event
+
+ if (eef->reason == MPV_END_FILE_REASON_ERROR) {
+ lua_pushstring(L, mpv_error_string(eef->error)); // event error
+ lua_setfield(L, -2, "error"); // event
+ }
+ break;
+ }
case MPV_EVENT_PROPERTY_CHANGE: {
mpv_event_property *prop = event->data;
lua_pushstring(L, prop->name);
@@ -1009,58 +1024,6 @@ static int script_get_time(lua_State *L)
return 1;
}
-static int script_input_define_section(lua_State *L)
-{
- struct MPContext *mpctx = get_mpctx(L);
- char *section = (char *)luaL_checkstring(L, 1);
- char *contents = (char *)luaL_checkstring(L, 2);
- char *flags = (char *)luaL_optstring(L, 3, "");
- bool builtin = true;
- if (strcmp(flags, "default") == 0) {
- builtin = true;
- } else if (strcmp(flags, "force") == 0) {
- builtin = false;
- } else if (strcmp(flags, "") == 0) {
- //pass
- } else {
- luaL_error(L, "invalid flags: '%s'", flags);
- }
- mp_input_define_section(mpctx->input, section, "<script>", contents, builtin);
- return 0;
-}
-
-static int script_input_enable_section(lua_State *L)
-{
- struct MPContext *mpctx = get_mpctx(L);
- char *section = (char *)luaL_checkstring(L, 1);
- char *sflags = (char *)luaL_optstring(L, 2, "");
- bstr bflags = bstr0(sflags);
- int flags = 0;
- while (bflags.len) {
- bstr val;
- bstr_split_tok(bflags, "|", &val, &bflags);
- if (bstr_equals0(val, "allow-hide-cursor")) {
- flags |= MP_INPUT_ALLOW_HIDE_CURSOR;
- } else if (bstr_equals0(val, "allow-vo-dragging")) {
- flags |= MP_INPUT_ALLOW_VO_DRAGGING;
- } else if (bstr_equals0(val, "exclusive")) {
- flags |= MP_INPUT_EXCLUSIVE;
- } else {
- luaL_error(L, "invalid flag");
- }
- }
- mp_input_enable_section(mpctx->input, section, flags);
- return 0;
-}
-
-static int script_input_disable_section(lua_State *L)
-{
- struct MPContext *mpctx = get_mpctx(L);
- char *section = (char *)luaL_checkstring(L, 1);
- mp_input_disable_section(mpctx->input, section);
- return 0;
-}
-
static int script_input_set_section_mouse_area(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
@@ -1148,7 +1111,7 @@ static int script_join_path(lua_State *L)
{
const char *p1 = luaL_checkstring(L, 1);
const char *p2 = luaL_checkstring(L, 2);
- char *r = mp_path_join(NULL, bstr0(p1), bstr0(p2));
+ char *r = mp_path_join(NULL, p1, p2);
lua_pushstring(L, r);
talloc_free(r);
return 1;
@@ -1228,6 +1191,8 @@ static int script_subprocess(lua_State *L)
lua_setfield(L, -2, "status"); // res
lua_pushlstring(L, cb_ctx.output.start, cb_ctx.output.len); // res d
lua_setfield(L, -2, "stdout"); // res
+ lua_pushboolean(L, status == MP_SUBPROCESS_EKILLED_BY_US); // res b
+ lua_setfield(L, -2, "killed_by_us"); // res
return 1;
}
@@ -1304,9 +1269,6 @@ static const struct fn_entry main_fns[] = {
FN_ENTRY(get_screen_margins),
FN_ENTRY(get_mouse_pos),
FN_ENTRY(get_time),
- FN_ENTRY(input_define_section),
- FN_ENTRY(input_enable_section),
- FN_ENTRY(input_disable_section),
FN_ENTRY(input_set_section_mouse_area),
FN_ENTRY(format_time),
FN_ENTRY(enable_messages),
diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua
index 265463a..d0ef57c 100644
--- a/player/lua/defaults.lua
+++ b/player/lua/defaults.lua
@@ -24,6 +24,24 @@ function mp.get_opt(key, def)
return val
end
+function mp.input_define_section(section, contents, flags)
+ if flags == nil or flags == "" then
+ flags = "default"
+ end
+ mp.commandv("define-section", section, contents, flags)
+end
+
+function mp.input_enable_section(section, flags)
+ if flags == nil then
+ flags = ""
+ end
+ mp.commandv("enable-section", section, flags)
+end
+
+function mp.input_disable_section(section)
+ mp.commandv("disable-section", section)
+end
+
-- For dispatching script_binding. This is sent as:
-- script_message_to $script_name $binding_name $keystate
-- The array is indexed by $binding_name, and has functions like this as value:
@@ -89,7 +107,7 @@ function mp.set_key_bindings(list, section, flags)
cb()
end
end
- cfg = cfg .. key .. " script_binding " ..
+ cfg = cfg .. key .. " script-binding " ..
mp.script_name .. "/" .. mangle .. "\n"
else
cfg = cfg .. key .. " " .. cb .. "\n"
@@ -133,7 +151,7 @@ local function update_key_bindings()
end
mp.input_define_section(section, cfg, flags)
-- TODO: remove the section if the script is stopped
- mp.input_enable_section(section, "allow-hide-cursor|allow-vo-dragging")
+ mp.input_enable_section(section, "allow-hide-cursor+allow-vo-dragging")
end
end
@@ -187,7 +205,7 @@ local function add_binding(attrs, key, name, fn, rp)
end
msg_cb = fn
end
- attrs.bind = bind .. " script_binding " .. mp.script_name .. "/" .. name
+ attrs.bind = bind .. " script-binding " .. mp.script_name .. "/" .. name
attrs.name = name
key_bindings[name] = attrs
update_key_bindings()
@@ -464,7 +482,7 @@ function mp.osd_message(text, duration)
else
duration = tostring(math.floor(duration * 1000))
end
- mp.commandv("show_text", text, duration)
+ mp.commandv("show-text", text, duration)
end
local hook_table = {}
@@ -475,7 +493,7 @@ local function hook_run(id, cont)
if fn then
fn()
end
- mp.commandv("hook_ack", cont)
+ mp.commandv("hook-ack", cont)
end
function mp.add_hook(name, pri, cb)
@@ -485,7 +503,7 @@ function mp.add_hook(name, pri, cb)
end
local id = #hook_table + 1
hook_table[id] = cb
- mp.commandv("hook_add", name, id, pri)
+ mp.commandv("hook-add", name, id, pri)
end
local mp_utils = package.loaded["mp.utils"]
diff --git a/player/lua/options.lua b/player/lua/options.lua
index 087e31b..dd76f7a 100644
--- a/player/lua/options.lua
+++ b/player/lua/options.lua
@@ -30,7 +30,7 @@ local function typeconv(desttypeval, val)
end
-function read_options(options, identifier)
+local function read_options(options, identifier)
if identifier == nil then
identifier = mp.get_script_name()
end
@@ -101,4 +101,9 @@ function read_options(options, identifier)
end
+-- backwards compatibility with broken read_options export
+_G.read_options = read_options
+return {
+ read_options = read_options,
+}
diff --git a/player/lua/osc.lua b/player/lua/osc.lua
index 4aade7c..993c103 100644
--- a/player/lua/osc.lua
+++ b/player/lua/osc.lua
@@ -30,10 +30,12 @@ local user_opts = {
-- functions that depend on it)
layout = "box",
seekbarstyle = "slider", -- slider (diamond marker) or bar (fill)
+ timetotal = false, -- display total time instead of remaining time?
+ timems = false, -- display timecodes with milliseconds?
}
-- read options from config and command-line
-read_options(user_opts, "osc")
+opt.read_options(user_opts, "osc")
local osc_param = { -- calculated by osc_init()
playresy = 0, -- canvas size Y
@@ -65,8 +67,8 @@ local state = {
mouse_down_counter = 0, -- used for softrepeat
active_element = nil, -- nil = none, 0 = background, 1+ = see elements[]
active_event_source = nil, -- the "button" that issued the current event
- rightTC_trem = true, -- if the right timcode should display total or remaining time
- tc_ms = false, -- Should the timecodes display their time with milliseconds
+ rightTC_trem = not user_opts.timetotal, -- if the right timecode should display total or remaining time
+ tc_ms = user_opts.timems, -- Should the timecodes display their time with milliseconds
mp_screen_sizeX, mp_screen_sizeY, -- last screen-resolution, to detect resolution changes to issue reINITs
initREQ = false, -- is a re-init request pending?
last_mouseX, last_mouseY, -- last mouse position, to detect siginificant mouse movement
@@ -77,6 +79,8 @@ local state = {
cache_idle = false,
idle = false,
enabled = true,
+ input_enabled = true,
+ showhide_enabled = false,
}
@@ -1482,7 +1486,7 @@ function osc_init()
ne.enabled = not (mp.get_property("percent-pos") == nil)
ne.slider.markerF = function ()
- local duration = mp.get_property_number("length", nil)
+ local duration = mp.get_property_number("duration", nil)
if not (duration == nil) then
local chapters = mp.get_property_native("chapter-list", {})
local markers = {}
@@ -1497,7 +1501,7 @@ function osc_init()
ne.slider.posF =
function () return mp.get_property_number("percent-pos", nil) end
ne.slider.tooltipF = function (pos)
- local duration = mp.get_property_number("length", nil)
+ local duration = mp.get_property_number("duration", nil)
if not ((duration == nil) or (pos == nil)) then
possec = duration * (pos / 100)
return mp.format_time(possec)
@@ -1542,8 +1546,8 @@ function osc_init()
-- tc_right (total/remaining time)
ne = new_element("tc_right", "button")
- ne.visible = (not (mp.get_property("length") == nil))
- and (mp.get_property_number("length") > 0)
+ ne.visible = (not (mp.get_property("duration") == nil))
+ and (mp.get_property_number("duration") > 0)
ne.content = function ()
if (state.rightTC_trem) then
if state.tc_ms then
@@ -1555,7 +1559,7 @@ function osc_init()
if state.tc_ms then
return (mp.get_property_osd("length/full"))
else
- return (mp.get_property_osd("length"))
+ return (mp.get_property_osd("duration"))
end
end
end
@@ -1769,9 +1773,14 @@ function render()
for _,cords in ipairs(osc_param.areas["input"]) do
if state.osc_visible then -- activate only when OSC is actually visible
mp.set_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "input")
- mp.enable_key_bindings("input")
- else
- mp.disable_key_bindings("input")
+ end
+ if state.osc_visible ~= state.input_enabled then
+ if state.osc_visible then
+ mp.enable_key_bindings("input")
+ else
+ mp.disable_key_bindings("input")
+ end
+ state.input_enabled = state.osc_visible
end
if (mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2)) then
@@ -1886,6 +1895,8 @@ end
-- called by mpv on every frame
function tick()
+ if (not state.enabled) then return end
+
if (state.idle) then
-- render idle message
@@ -1917,7 +1928,10 @@ function tick()
ass:append("Drop files to play here.")
mp.set_osd_ass(640, 360, ass.text)
- mp.disable_key_bindings("showhide")
+ if state.showhide_enabled then
+ mp.disable_key_bindings("showhide")
+ state.showhide_enabled = false
+ end
elseif (state.fullscreen and user_opts.showfullscreen)
@@ -1933,7 +1947,10 @@ end
function do_enable_keybindings()
if state.enabled then
- mp.enable_key_bindings("showhide", "allow-vo-dragging|allow-hide-cursor")
+ if not state.showhide_enabled then
+ mp.enable_key_bindings("showhide", "allow-vo-dragging+allow-hide-cursor")
+ end
+ state.showhide_enabled = true
end
end
@@ -1944,15 +1961,18 @@ function enable_osc(enable)
show_osc()
else
hide_osc()
- mp.disable_key_bindings("showhide")
+ if state.showhide_enabled then
+ mp.disable_key_bindings("showhide")
+ end
+ state.showhide_enabled = false
end
end
validate_user_opts()
-mp.register_event("tick", tick)
mp.register_event("start-file", request_init)
mp.register_event("tracks-changed", request_init)
+mp.observe_property("playlist", nil, request_init)
mp.register_script_message("enable-osc", function() enable_osc(true) end)
mp.register_script_message("disable-osc", function() enable_osc(false) end)
@@ -1972,13 +1992,11 @@ mp.observe_property("idle", "bool",
)
mp.observe_property("pause", "bool", pause_state)
mp.observe_property("cache-idle", "bool", cache_state)
-
-mp.observe_property("disc-menu-active", "bool", function(name, val)
- if val == true then
- hide_osc()
- mp.disable_key_bindings("showhide")
+mp.observe_property("vo-configured", "bool", function(name, val)
+ if val then
+ mp.register_event("tick", tick)
else
- do_enable_keybindings()
+ mp.unregister_event(tick)
end
end)
diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua
index 5e87bc3..df08c3d 100644
--- a/player/lua/ytdl_hook.lua
+++ b/player/lua/ytdl_hook.lua
@@ -9,7 +9,7 @@ local ytdl = {
local function exec(args)
local ret = utils.subprocess({args = args})
- return ret.status, ret.stdout
+ return ret.status, ret.stdout, ret
end
-- return if it was explicitly set on the command line
@@ -96,12 +96,9 @@ mp.add_hook("on_load", 10, function ()
local format = mp.get_property("options/ytdl-format")
local raw_options = mp.get_property_native("options/ytdl-raw-options")
- -- subformat workaround
- local subformat = "ass/srt/best"
-
local command = {
ytdl.path, "--no-warnings", "-J", "--flat-playlist", "--all-subs",
- "--sub-format", subformat, "--no-playlist"
+ "--sub-format", "ass/srt/best", "--no-playlist"
}
-- Checks if video option is "no", change options accordingly
@@ -123,10 +120,13 @@ mp.add_hook("on_load", 10, function ()
end
table.insert(command, "--")
table.insert(command, url)
- local es, json = exec(command)
+ msg.debug("Running: " .. table.concat(command,' '))
+ local es, json, result = exec(command)
if (es < 0) or (json == nil) or (json == "") then
- msg.warn("youtube-dl failed, trying to play URL directly ...")
+ if not result.killed_by_us then
+ msg.warn("youtube-dl failed, trying to play URL directly ...")
+ end
return
end
@@ -144,7 +144,8 @@ mp.add_hook("on_load", 10, function ()
-- direct URL, nothing to do
msg.verbose("Got direct URL")
return
- elseif not (json["_type"] == nil) and (json["_type"] == "playlist") then
+ elseif not (json["_type"] == nil)
+ and ((json["_type"] == "playlist") or (json["_type"] == "multi_video")) then
-- a playlist
if (#json.entries == 0) then
@@ -176,7 +177,7 @@ mp.add_hook("on_load", 10, function ()
mp.set_property("stream-open-filename", playlist)
if not (json.title == nil) then
- mp.set_property("file-local-options/media-title", json.title)
+ mp.set_property("file-local-options/force-media-title", json.title)
end
else
@@ -234,28 +235,36 @@ mp.add_hook("on_load", 10, function ()
mp.set_property("stream-open-filename", streamurl)
- mp.set_property("file-local-options/media-title", json.title)
+ mp.set_property("file-local-options/force-media-title", json.title)
-- add subtitles
if not (json.requested_subtitles == nil) then
for lang, sub_info in pairs(json.requested_subtitles) do
msg.verbose("adding subtitle ["..lang.."]")
- local slang = lang
- if (lang:len() > 3) then
- slang = lang:sub(1,2)
- end
+ local sub = nil
if not (sub_info.data == nil) then
sub = "memory://"..sub_info.data
- else
+ elseif not (sub_info.url == nil) then
sub = sub_info.url
end
- mp.commandv("sub_add", sub,
- "auto", lang.." "..sub_info.ext, slang)
+
+ if not (sub == nil) then
+ mp.commandv("sub_add", sub,
+ "auto", sub_info.ext, lang)
+ else
+ msg.verbose("No subtitle data/url for ["..lang.."]")
+ end
end
end
+ -- set start and end time
+ if not (json.start_time == nil) then
+ msg.debug("setting start to: " .. json.start_time .. " secs")
+ mp.set_property("file-local-options/start",json.start_time)
+ end
+
-- for rtmp
if not (json.play_path == nil) then
local rtmp_prop = append_rtmp_prop(nil, "rtmp_tcurl", streamurl)
diff --git a/player/main.c b/player/main.c
index ca87cb1..f2e1333 100644
--- a/player/main.c
+++ b/player/main.c
@@ -22,6 +22,7 @@
#include <assert.h>
#include <string.h>
#include <pthread.h>
+#include <signal.h>
#include "config.h"
#include "talloc.h"
@@ -99,7 +100,37 @@ static const char def_config[] =
"[pseudo-gui]\n"
"terminal=no\n"
"force-window=yes\n"
- "idle=once\n";
+ "idle=once\n"
+ "screenshot-directory=~~desktop/\n"
+ "\n"
+ "[libmpv]\n"
+ "config=no\n"
+ "idle=yes\n"
+ "terminal=no\n"
+ "input-terminal=no\n"
+ "osc=no\n"
+ "ytdl=no\n"
+ "input-default-bindings=no\n"
+ "input-vo-keyboard=no\n"
+ "input-lirc=no\n"
+ "input-appleremote=no\n"
+ "input-media-keys=no\n"
+ "input-app-events=no\n"
+ "stop-playback-on-init-failure=yes\n"
+#if HAVE_ENCODING
+ "\n"
+ "[encoding]\n"
+ "vo=lavc\n"
+ "ao=lavc\n"
+ "keep-open=no\n"
+ "force-window=no\n"
+ "gapless-audio=yes\n"
+ "resume-playback=no\n"
+ "load-scripts=no\n"
+ "osc=no\n"
+ "framedrop=no\n"
+#endif
+;
static pthread_mutex_t terminal_owner_lock = PTHREAD_MUTEX_INITIALIZER;
static struct MPContext *terminal_owner;
@@ -191,6 +222,7 @@ void mp_destroy(struct MPContext *mpctx)
pthread_detach(pthread_self());
mp_msg_uninit(mpctx->global);
+ pthread_mutex_destroy(&mpctx->ass_lock);
talloc_free(mpctx);
}
@@ -247,6 +279,10 @@ static bool handle_help_options(struct MPContext *mpctx)
talloc_free(list);
opt_exit = 1;
}
+ if (opts->audio_spdif && strcmp(opts->audio_spdif, "help") == 0) {
+ MP_INFO(mpctx, "Choices: ac3,dts-hd,dts (and possibly more)\n");
+ opt_exit = 1;
+ }
if (opts->video_decoders && strcmp(opts->video_decoders, "help") == 0) {
struct mp_decoder_list *list = video_decoder_list();
mp_print_decoders(log, MSGL_INFO, "Video decoders:", list);
@@ -271,16 +307,6 @@ static bool handle_help_options(struct MPContext *mpctx)
return opt_exit;
}
-static void osdep_preinit(int argc, char **argv)
-{
- char *enable_talloc = getenv("MPV_LEAK_REPORT");
- if (argc > 1 && (strcmp(argv[1], "-leak-report") == 0 ||
- strcmp(argv[1], "--leak-report") == 0))
- enable_talloc = "1";
- if (enable_talloc && strcmp(enable_talloc, "1") == 0)
- talloc_enable_leak_report();
-}
-
static int cfg_include(void *ctx, char *filename, int flags)
{
struct MPContext *mpctx = ctx;
@@ -304,6 +330,8 @@ struct MPContext *mp_create(void)
.playback_abort = mp_cancel_new(mpctx),
};
+ pthread_mutex_init(&mpctx->ass_lock, NULL);
+
mpctx->global = talloc_zero(mpctx, struct mpv_global);
// Nothing must call mp_msg*() and related before this
@@ -334,6 +362,10 @@ struct MPContext *mp_create(void)
init_libav(mpctx->global);
mp_clients_init(mpctx);
+#if HAVE_COCOA
+ cocoa_set_input_context(mpctx->input);
+#endif
+
return mpctx;
}
@@ -389,12 +421,6 @@ int mp_initialize(struct MPContext *mpctx, char **options)
}
MP_STATS(mpctx, "start init");
- if (opts->slave_mode) {
- MP_WARN(mpctx, "--slave-broken is deprecated (see manpage).\n");
- opts->consolecontrols = 0;
- m_config_set_option0(mpctx->mconfig, "input-file", "/dev/stdin");
- }
-
if (!mpctx->playlist->first && !opts->player_idle_mode)
return -3;
@@ -408,18 +434,10 @@ int mp_initialize(struct MPContext *mpctx, char **options)
mpctx->encode_lavc_ctx = encode_lavc_init(opts->encode_opts,
mpctx->global);
if(!mpctx->encode_lavc_ctx) {
- MP_INFO(mpctx, "Encoding initialization failed.");
+ MP_INFO(mpctx, "Encoding initialization failed.\n");
return -1;
}
- m_config_set_option0(mpctx->mconfig, "vo", "lavc");
- m_config_set_option0(mpctx->mconfig, "ao", "lavc");
- m_config_set_option0(mpctx->mconfig, "keep-open", "no");
- m_config_set_option0(mpctx->mconfig, "force-window", "no");
- m_config_set_option0(mpctx->mconfig, "gapless-audio", "yes");
- m_config_set_option0(mpctx->mconfig, "resume-playback", "no");
- m_config_set_option0(mpctx->mconfig, "load-scripts", "no");
- m_config_set_option0(mpctx->mconfig, "osc", "no");
- m_config_set_option0(mpctx->mconfig, "framedrop", "no");
+ m_config_set_profile(mpctx->mconfig, "encoding", 0);
// never use auto
if (!opts->audio_output_channels.num) {
m_config_set_option_ext(mpctx->mconfig, bstr0("audio-channels"),
@@ -441,13 +459,13 @@ int mp_initialize(struct MPContext *mpctx, char **options)
mp_get_resume_defaults(mpctx);
+ // Lua user scripts (etc.) can call arbitrary functions. Load them at a point
+ // where this is safe.
+ mp_load_scripts(mpctx);
+
if (opts->consolecontrols && cas_terminal_owner(mpctx, mpctx))
terminal_setup_getch(mpctx->input);
-#if HAVE_COCOA
- cocoa_set_input_context(mpctx->input);
-#endif
-
if (opts->force_vo) {
struct vo_extra ex = {
.input_ctx = mpctx->input,
@@ -460,13 +478,11 @@ int mp_initialize(struct MPContext *mpctx, char **options)
"the selected video_out (-vo) device.\n");
return -1;
}
+ if (opts->force_vo == 2)
+ handle_force_window(mpctx, false);
mpctx->mouse_cursor_visible = true;
}
- // Lua user scripts (etc.) can call arbitrary functions. Load them at a point
- // where this is safe.
- mp_load_scripts(mpctx);
-
#if !defined(__MINGW32__)
mpctx->ipc_ctx = mp_init_ipc(mpctx->clients, mpctx->global);
#endif
@@ -475,6 +491,12 @@ int mp_initialize(struct MPContext *mpctx, char **options)
if (opts->w32_priority > 0)
SetPriorityClass(GetCurrentProcess(), opts->w32_priority);
#endif
+#ifndef _WIN32
+ // Deal with OpenSSL and GnuTLS not using MSG_NOSIGNAL.
+ struct sigaction sa = { .sa_handler = SIG_IGN, .sa_flags = SA_RESTART };
+ sigfillset(&sa.sa_mask);
+ sigaction(SIGPIPE, &sa, NULL);
+#endif
prepare_playlist(mpctx, mpctx->playlist);
@@ -485,7 +507,9 @@ int mp_initialize(struct MPContext *mpctx, char **options)
int mpv_main(int argc, char *argv[])
{
- osdep_preinit(argc, argv);
+ char *enable_talloc = getenv("MPV_LEAK_REPORT");
+ if (enable_talloc && strcmp(enable_talloc, "1") == 0)
+ talloc_enable_leak_report();
struct MPContext *mpctx = mp_create();
struct MPOpts *opts = mpctx->opts;
diff --git a/player/misc.c b/player/misc.c
index f3d9214..add73e5 100644
--- a/player/misc.c
+++ b/player/misc.c
@@ -192,7 +192,8 @@ void error_on_track(struct MPContext *mpctx, struct track *track)
(!mpctx->current_track[0][STREAM_AUDIO] &&
!mpctx->current_track[0][STREAM_VIDEO]))
{
- mpctx->stop_play = PT_ERROR;
+ if (!mpctx->stop_play)
+ mpctx->stop_play = PT_ERROR;
if (mpctx->error_playing >= 0)
mpctx->error_playing = MPV_ERROR_NOTHING_TO_PLAY;
}
@@ -200,17 +201,16 @@ void error_on_track(struct MPContext *mpctx, struct track *track)
}
}
-void stream_dump(struct MPContext *mpctx)
+int stream_dump(struct MPContext *mpctx, const char *source_filename)
{
struct MPOpts *opts = mpctx->opts;
- char *filename = opts->stream_dump;
- stream_t *stream = mpctx->stream;
- assert(stream && filename);
+ stream_t *stream = stream_open(source_filename, mpctx->global);
+ if (!stream)
+ return -1;
- int64_t size = 0;
- stream_control(stream, STREAM_CTRL_GET_SIZE, &size);
+ int64_t size = stream_get_size(stream);
- stream_set_capture_file(stream, filename);
+ stream_set_capture_file(stream, opts->stream_dump);
while (mpctx->stop_play == KEEP_PLAYING && !stream->eof) {
if (!opts->quiet && ((stream->pos / (1024 * 1024)) % 2) == 1) {
@@ -221,6 +221,9 @@ void stream_dump(struct MPContext *mpctx)
stream_fill_buffer(stream);
mp_process_input(mpctx);
}
+
+ free_stream(stream);
+ return 0;
}
void merge_playlist_files(struct playlist *pl)
diff --git a/player/osd.c b/player/osd.c
index 1f482e2..8a00086 100644
--- a/player/osd.c
+++ b/player/osd.c
@@ -207,10 +207,7 @@ static void print_status(struct MPContext *mpctx)
// A-V sync
if (mpctx->d_audio && mpctx->d_video && mpctx->sync_audio_to_video) {
- if (mpctx->last_av_difference != MP_NOPTS_VALUE)
- saddf(&line, " A-V:%7.3f", mpctx->last_av_difference);
- else
- saddf(&line, " A-V: ???");
+ saddf(&line, " A-V:%7.3f", mpctx->last_av_difference);
if (fabs(mpctx->total_avsync_change) > 0.05)
saddf(&line, " ct:%7.3f", mpctx->total_avsync_change);
}
@@ -228,6 +225,16 @@ static void print_status(struct MPContext *mpctx)
{
// VO stats
if (mpctx->d_video) {
+ if (mpctx->display_sync_active) {
+ char *f =
+ mp_property_expand_string(mpctx, "${audio-speed-correction}");
+ if (f)
+ saddf(&line, " DS: %s", f);
+ talloc_free(f);
+ int64_t m = vo_get_missed_count(mpctx->video_out);
+ if (m > 0)
+ saddf(&line, " Missed: %"PRId64, m);
+ }
int64_t c = vo_get_drop_count(mpctx->video_out);
if (c > 0 || mpctx->dropped_frames_total > 0) {
saddf(&line, " Dropped: %"PRId64, c);
@@ -251,7 +258,11 @@ static void print_status(struct MPContext *mpctx)
} else {
saddf(&line, "%2ds", (int)s.ts_duration);
}
- saddf(&line, "+%lldKB", (long long)(fill / 1024));
+ if (fill >= 1024 * 1024) {
+ saddf(&line, "+%lldMB", (long long)(fill / 1024 / 1024));
+ } else {
+ saddf(&line, "+%lldKB", (long long)(fill / 1024));
+ }
}
}
diff --git a/player/playloop.c b/player/playloop.c
index c9d74c2..2ebfa9d 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -190,25 +190,40 @@ static int mp_seek(MPContext *mpctx, struct seek_params seek,
if (hr_seek_very_exact)
hr_seek_offset = MPMAX(hr_seek_offset, 0.5); // arbitrary
+ double target_time = MP_NOPTS_VALUE;
+ int direction = 0;
+
+ switch (seek.type) {
+ case MPSEEK_ABSOLUTE:
+ target_time = seek.amount;
+ break;
+ case MPSEEK_RELATIVE:
+ direction = seek.amount > 0 ? 1 : -1;
+ target_time = seek.amount + get_current_time(mpctx);
+ break;
+ case MPSEEK_FACTOR: ;
+ double len = get_time_length(mpctx);
+ if (len >= 0)
+ target_time = seek.amount * len + get_start_time(mpctx);
+ break;
+ }
+
bool hr_seek = opts->correct_pts && seek.exact != MPSEEK_KEYFRAME;
hr_seek &= (opts->hr_seek == 0 && seek.type == MPSEEK_ABSOLUTE) ||
opts->hr_seek > 0 || seek.exact >= MPSEEK_EXACT;
if (seek.type == MPSEEK_FACTOR || seek.amount < 0 ||
(seek.type == MPSEEK_ABSOLUTE && seek.amount < mpctx->last_chapter_pts))
mpctx->last_chapter_seek = -2;
- if (seek.type == MPSEEK_FACTOR && !mpctx->demuxer->ts_resets_possible) {
- double len = get_time_length(mpctx);
- if (len >= 0) {
- seek.amount = seek.amount * len + get_start_time(mpctx);
- seek.type = MPSEEK_ABSOLUTE;
- }
- }
- int direction = 0;
- if (seek.type == MPSEEK_RELATIVE && (!mpctx->demuxer->rel_seeks || hr_seek)) {
+
+ // Prefer doing absolute seeks, unless not possible.
+ if ((seek.type == MPSEEK_FACTOR && !mpctx->demuxer->ts_resets_possible &&
+ target_time != MP_NOPTS_VALUE) ||
+ (seek.type == MPSEEK_RELATIVE && (!mpctx->demuxer->rel_seeks || hr_seek)))
+ {
seek.type = MPSEEK_ABSOLUTE;
- direction = seek.amount > 0 ? 1 : -1;
- seek.amount += get_current_time(mpctx);
+ seek.amount = target_time;
}
+
hr_seek &= seek.type == MPSEEK_ABSOLUTE; // otherwise, no target PTS known
double demuxer_amount = seek.amount;
@@ -273,8 +288,7 @@ static int mp_seek(MPContext *mpctx, struct seek_params seek,
/* Use the target time as "current position" for further relative
* seeks etc until a new video frame has been decoded */
- if (seek.type == MPSEEK_ABSOLUTE)
- mpctx->last_seek_pts = seek.amount;
+ mpctx->last_seek_pts = target_time;
// The hr_seek==false case is for skipping frames with PTS before the
// current timeline chapter start. It's not really known where the demuxer
@@ -287,6 +301,9 @@ static int mp_seek(MPContext *mpctx, struct seek_params seek,
mpctx->hrseek_framedrop = !hr_seek_very_exact;
mpctx->hrseek_pts = hr_seek ? seek.amount
: mpctx->timeline[mpctx->timeline_part].start;
+
+ MP_VERBOSE(mpctx, "hr-seek, skipping to %f%s\n", mpctx->hrseek_pts,
+ mpctx->hrseek_framedrop ? "" : " (no framedrop)");
}
mpctx->start_timestamp = mp_time_sec();
@@ -303,6 +320,10 @@ void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount,
enum seek_precision exact, bool immediate)
{
struct seek_params *seek = &mpctx->seek;
+
+ if (mpctx->stop_play == AT_END_OF_FILE)
+ mpctx->stop_play = KEEP_PLAYING;
+
switch (type) {
case MPSEEK_RELATIVE:
seek->immediate |= immediate;
@@ -467,7 +488,6 @@ char *chapter_display_name(struct MPContext *mpctx, int chapter)
dname = talloc_asprintf(NULL, "(%d) of %d", chapter + 1,
chapter_count);
}
- talloc_free(name);
return dname;
}
@@ -476,10 +496,10 @@ char *chapter_name(struct MPContext *mpctx, int chapter)
{
if (chapter < 0 || chapter >= mpctx->num_chapters)
return NULL;
- return talloc_strdup(NULL, mpctx->chapters[chapter].name);
+ return mp_tags_get_str(mpctx->chapters[chapter].metadata, "title");
}
-// returns the start of the chapter in seconds (-1 if unavailable)
+// returns the start of the chapter in seconds (NOPTS if unavailable)
double chapter_start_time(struct MPContext *mpctx, int chapter)
{
if (chapter == -1)
@@ -494,27 +514,6 @@ int get_chapter_count(struct MPContext *mpctx)
return mpctx->num_chapters;
}
-// Seek to a given chapter. Queues the seek.
-bool mp_seek_chapter(struct MPContext *mpctx, int chapter)
-{
- int num = get_chapter_count(mpctx);
- if (num == 0)
- return false;
- if (chapter < -1 || chapter >= num)
- return false;
-
- mpctx->last_chapter_seek = -2;
-
- double pts = chapter_start_time(mpctx, chapter);
- if (pts == MP_NOPTS_VALUE)
- return false;
-
- queue_seek(mpctx, MPSEEK_ABSOLUTE, pts, MPSEEK_DEFAULT, true);
- mpctx->last_chapter_seek = chapter;
- mpctx->last_chapter_pts = pts;
- return true;
-}
-
static void handle_osd_redraw(struct MPContext *mpctx)
{
if (!mpctx->video_out || !mpctx->video_out->config_ok)
@@ -691,6 +690,27 @@ void add_frame_pts(struct MPContext *mpctx, double pts)
mpctx->vo_pts_history_pts[0] = pts;
}
+// Return the last (at most num) frame duration in fd[]. Return the number of
+// entries written to fd[] (range [0, num]). fd[0] is the most recent frame.
+int get_past_frame_durations(struct MPContext *mpctx, double *fd, int num)
+{
+ double next_pts = mpctx->vo_pts_history_pts[0];
+ if (mpctx->vo_pts_history_seek[0] != mpctx->vo_pts_history_seek_ts ||
+ next_pts == MP_NOPTS_VALUE)
+ return 0;
+ int num_ret = 0;
+ for (int n = 1; n < MAX_NUM_VO_PTS && num_ret < num; n++) {
+ double frame_pts = mpctx->vo_pts_history_pts[n];
+ // Discontinuity -> refuse to return a value.
+ if (mpctx->vo_pts_history_seek[n] != mpctx->vo_pts_history_seek_ts ||
+ next_pts <= frame_pts || frame_pts == MP_NOPTS_VALUE)
+ break;
+ fd[num_ret++] = next_pts - frame_pts;
+ next_pts = frame_pts;
+ }
+ return num_ret;
+}
+
static double find_previous_pts(struct MPContext *mpctx, double pts)
{
for (int n = 0; n < MAX_NUM_VO_PTS - 1; n++) {
@@ -770,7 +790,7 @@ static void handle_sstep(struct MPContext *mpctx)
}
if (mpctx->video_status >= STATUS_EOF) {
- if (mpctx->max_frames >= 0)
+ if (mpctx->max_frames >= 0 && !mpctx->stop_play)
mpctx->stop_play = AT_END_OF_FILE; // force EOF even if audio left
if (mpctx->step_frames > 0 && !mpctx->paused)
pause_player(mpctx);
@@ -890,7 +910,7 @@ void handle_force_window(struct MPContext *mpctx, bool reconfig)
static void handle_dummy_ticks(struct MPContext *mpctx)
{
if (mpctx->video_status == STATUS_EOF || mpctx->paused) {
- if (mp_time_sec() - mpctx->last_idle_tick > 0.5) {
+ if (mp_time_sec() - mpctx->last_idle_tick > 0.050) {
mpctx->last_idle_tick = mp_time_sec();
mp_notify(mpctx, MPV_EVENT_TICK, NULL);
}
@@ -924,7 +944,9 @@ static void handle_playback_restart(struct MPContext *mpctx, double endpts)
if (opts->playing_msg && opts->playing_msg[0]) {
char *msg =
mp_property_expand_escaped_string(mpctx, opts->playing_msg);
- MP_INFO(mpctx, "%s\n", msg);
+ struct mp_log *log = mp_log_new(NULL, mpctx->log, "!term-msg");
+ mp_info(log, "%s\n", msg);
+ talloc_free(log);
talloc_free(msg);
}
if (opts->osd_playing_msg && opts->osd_playing_msg[0]) {
@@ -935,6 +957,7 @@ static void handle_playback_restart(struct MPContext *mpctx, double endpts)
}
}
mpctx->playing_msg_shown = true;
+ MP_VERBOSE(mpctx, "playback restart complete\n");
}
}
@@ -962,7 +985,8 @@ static void handle_segment_switch(struct MPContext *mpctx, bool end_is_new_segme
.amount = mpctx->timeline[new_part].start
}, true);
} else {
- mpctx->stop_play = AT_END_OF_FILE;
+ if (!mpctx->stop_play)
+ mpctx->stop_play = AT_END_OF_FILE;
}
}
}
@@ -1014,10 +1038,10 @@ void run_playloop(struct MPContext *mpctx)
handle_segment_switch(mpctx, end_is_new_segment);
- mp_handle_nav(mpctx);
-
handle_loop_file(mpctx);
+ handle_ab_loop(mpctx);
+
handle_keep_open(mpctx);
handle_sstep(mpctx);
@@ -1028,7 +1052,7 @@ void run_playloop(struct MPContext *mpctx)
handle_osd_redraw(mpctx);
mp_wait_events(mpctx, mpctx->sleeptime);
- mpctx->sleeptime = 100.0; // infinite for all practical purposes
+ mpctx->sleeptime = 1e9; // infinite for all practical purposes
handle_pause_on_low_cache(mpctx);
@@ -1065,10 +1089,10 @@ void idle_loop(struct MPContext *mpctx)
&& mpctx->stop_play != PT_QUIT)
{
if (need_reinit) {
- mp_notify(mpctx, MPV_EVENT_IDLE, NULL);
uninit_audio_out(mpctx);
handle_force_window(mpctx, true);
mpctx->sleeptime = 0;
+ mp_notify(mpctx, MPV_EVENT_IDLE, NULL);
need_reinit = false;
}
mp_idle(mpctx);
diff --git a/player/screenshot.c b/player/screenshot.c
index a47de29..9c4f5cc 100644
--- a/player/screenshot.c
+++ b/player/screenshot.c
@@ -165,15 +165,17 @@ static char *create_fname(struct MPContext *mpctx, char *template,
}
case 'f':
case 'F': {
- if (!mpctx->filename)
- goto error_exit;
- char *video_file = mp_basename(mpctx->filename);
- if (video_file) {
- char *name = video_file;
- if (fmt == 'F')
- name = stripext(res, video_file);
- append_filename(&res, name);
- }
+ char *video_file = NULL;
+ if (mpctx->filename)
+ video_file = mp_basename(mpctx->filename);
+
+ if (!video_file)
+ video_file = "NO_FILE";
+
+ char *name = video_file;
+ if (fmt == 'F')
+ name = stripext(res, video_file);
+ append_filename(&res, name);
break;
}
case 'x':
@@ -280,6 +282,16 @@ static char *gen_fname(screenshot_ctx *ctx, const char *file_ext)
return NULL;
}
+ char *dir = ctx->mpctx->opts->screenshot_directory;
+ if (dir && dir[0]) {
+ void *t = fname;
+ dir = mp_get_user_path(t, ctx->mpctx->global, dir);
+ fname = mp_path_join(NULL, dir, fname);
+ talloc_free(t);
+
+ mp_mkdirp(dir);
+ }
+
if (!mp_path_exists(fname))
return fname;
diff --git a/player/scripting.c b/player/scripting.c
index 3747f50..3ae9719 100644
--- a/player/scripting.c
+++ b/player/scripting.c
@@ -163,7 +163,7 @@ static char **list_script_files(void *talloc_ctx, char *path)
return NULL;
struct dirent *ep;
while ((ep = readdir(dp))) {
- char *fname = mp_path_join(talloc_ctx, bstr0(path), bstr0(ep->d_name));
+ char *fname = mp_path_join(talloc_ctx, path, ep->d_name);
struct stat s;
if (!stat(fname, &s) && S_ISREG(s.st_mode))
MP_TARRAY_APPEND(talloc_ctx, files, count, fname);
@@ -192,25 +192,11 @@ void mp_load_scripts(struct MPContext *mpctx)
// Load all scripts
void *tmp = talloc_new(NULL);
- const char *dirs[] = {"scripts", "lua", NULL}; // 'lua' is deprecated
- int warning_displayed = 0;
- for (int s = 0; dirs[s]; s++) {
- char **scriptsdir = mp_find_all_config_files(tmp, mpctx->global, dirs[s]);
- for (int i = 0; scriptsdir && scriptsdir[i]; i++) {
- files = list_script_files(tmp, scriptsdir[i]);
- for (int n = 0; files && files[n]; n++) {
- if (s && !warning_displayed) {
- warning_displayed = 1;
- char *cfg = mp_find_config_file(tmp, mpctx->global, "");
- if (cfg)
- cfg = mp_path_join(tmp, bstr0(cfg), bstr0("scripts"));
- MP_WARN(mpctx, "Warning: '%s' - lua subdir is deprecated.\n"
- "Please move scripts to '%s'.\n",
- files[n], cfg ? cfg : "/scripts");
- }
- mp_load_script(mpctx, files[n]);
- }
- }
+ char **scriptsdir = mp_find_all_config_files(tmp, mpctx->global, "scripts");
+ for (int i = 0; scriptsdir && scriptsdir[i]; i++) {
+ files = list_script_files(tmp, scriptsdir[i]);
+ for (int n = 0; files && files[n]; n++)
+ mp_load_script(mpctx, files[n]);
}
talloc_free(tmp);
}
diff --git a/player/sub.c b/player/sub.c
index 89da9f7..384339c 100644
--- a/player/sub.c
+++ b/player/sub.c
@@ -109,6 +109,9 @@ static void init_sub_renderer(struct MPContext *mpctx)
ass_set_style_overrides(mpctx->ass_library, opts->ass_force_style_list);
mpctx->ass_renderer = ass_renderer_init(mpctx->ass_library);
+
+ mp_ass_configure_fonts(mpctx->ass_renderer, opts->sub_text_style,
+ mpctx->global, mpctx->ass_log);
}
void uninit_sub_renderer(struct MPContext *mpctx)
@@ -126,9 +129,6 @@ void uninit_sub_renderer(struct MPContext *mpctx)
static void init_sub_renderer(struct MPContext *mpctx) {}
void uninit_sub_renderer(struct MPContext *mpctx) {}
-static void mp_ass_configure_fonts(struct ass_renderer *a, struct osd_style_opts *b,
- struct mpv_global *c, struct mp_log *d) {}
-
#endif
static void reset_subtitles(struct MPContext *mpctx, int order)
@@ -306,14 +306,10 @@ static void reinit_subdec(struct MPContext *mpctx, struct track *track,
sub_set_video_res(dec_sub, w, h);
sub_set_video_fps(dec_sub, fps);
- sub_set_ass_renderer(dec_sub, mpctx->ass_library, mpctx->ass_renderer);
+ sub_set_ass_renderer(dec_sub, mpctx->ass_library, mpctx->ass_renderer,
+ &mpctx->ass_lock);
sub_init_from_sh(dec_sub, track->stream);
- if (mpctx->ass_renderer) {
- mp_ass_configure_fonts(mpctx->ass_renderer, opts->sub_text_style,
- mpctx->global, mpctx->ass_log);
- }
-
// Don't do this if the file has video/audio streams. Don't do it even
// if it has only sub streams, because reading packets will change the
// demuxer position.
diff --git a/player/video.c b/player/video.c
index d35a9de..de7e601 100644
--- a/player/video.c
+++ b/player/video.c
@@ -42,6 +42,8 @@
#include "video/decode/dec_video.h"
#include "video/decode/vd.h"
#include "video/out/vo.h"
+#include "audio/filter/af.h"
+#include "audio/decode/dec_audio.h"
#include "core.h"
#include "command.h"
@@ -58,23 +60,13 @@ enum {
};
static const char av_desync_help_text[] =
-"\n\n"
-" *************************************************\n"
-" **** Audio/Video desynchronisation detected! ****\n"
-" *************************************************\n\n"
-"This means either the audio or the video is played too slowly.\n"
-"Possible reasons, problems, workarounds:\n"
-"- Your system is simply too slow for this file.\n"
-" Transcode it to a lower bitrate file with e.g. mpv encoding support.\n"
-"- Slow video output.\n"
-" Try a different --vo driver (--vo=help for a list). Make sure framedrop\n"
-" is not disabled, or experiment with different values for --framedrop.\n"
-" Make sure you have proper drivers for your GPU installed. If mpv\n"
-" autoselects 'VO: [x11]', it's a sure sign your drivers are messed up.\n"
-"- Playing from a slow network source. Download the file instead.\n"
-"- Try to find out whether audio/video/subs are causing this by experimenting\n"
-" with --no-video, --no-audio, or --no-sub.\n"
-"If none of this helps you, file a bug report.\n\n";
+"\n"
+"Audio/Video desynchronisation detected! Possible reasons include too slow\n"
+"hardware, temporary CPU spikes, broken drivers, and broken files. Audio\n"
+"position will not match to the video (see A-V status field).\n"
+"\n";
+
+static bool decode_coverart(struct dec_video *d_video);
static void set_allowed_vo_formats(struct vf_chain *c, struct vo *vo)
{
@@ -176,9 +168,8 @@ static void recreate_video_filters(struct MPContext *mpctx)
vf_append_filter_list(d_video->vfilter, opts->vf_settings);
// for vf_sub
- vf_control_any(d_video->vfilter, VFCTRL_SET_OSD_OBJ, mpctx->osd);
osd_set_render_subs_in_filter(mpctx->osd,
- vf_control_any(d_video->vfilter, VFCTRL_INIT_OSD, NULL) == CONTROL_OK);
+ vf_control_any(d_video->vfilter, VFCTRL_INIT_OSD, mpctx->osd) > 0);
set_allowed_vo_formats(d_video->vfilter, mpctx->video_out);
}
@@ -206,8 +197,9 @@ void reset_video_state(struct MPContext *mpctx)
if (mpctx->video_out)
vo_seek_reset(mpctx->video_out);
- mp_image_unrefp(&mpctx->next_frame[0]);
- mp_image_unrefp(&mpctx->next_frame[1]);
+ for (int n = 0; n < mpctx->num_next_frames; n++)
+ mp_image_unrefp(&mpctx->next_frames[n]);
+ mpctx->num_next_frames = 0;
mp_image_unrefp(&mpctx->saved_frame);
mpctx->delay = 0;
@@ -215,9 +207,12 @@ void reset_video_state(struct MPContext *mpctx)
mpctx->video_pts = MP_NOPTS_VALUE;
mpctx->video_next_pts = MP_NOPTS_VALUE;
mpctx->total_avsync_change = 0;
+ mpctx->last_av_difference = 0;
+ mpctx->display_sync_disable_counter = 0;
mpctx->dropped_frames_total = 0;
mpctx->dropped_frames = 0;
mpctx->drop_message_shown = 0;
+ mpctx->display_sync_drift_dir = 0;
mpctx->video_status = mpctx->d_video ? STATUS_SYNCING : STATUS_EOF;
}
@@ -240,6 +235,7 @@ void uninit_video_chain(struct MPContext *mpctx)
mpctx->video_status = STATUS_EOF;
mpctx->sync_audio_to_video = false;
reselect_demux_streams(mpctx);
+ remove_deint_filter(mpctx);
}
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
}
@@ -253,12 +249,6 @@ int reinit_video_chain(struct MPContext *mpctx)
if (!sh)
goto no_video;
- MP_VERBOSE(mpctx, "[V] fourcc:0x%X size:%dx%d fps:%5.3f\n",
- sh->format,
- sh->video->disp_w, sh->video->disp_h,
- sh->video->fps);
-
- //================== Init VIDEO (codec & libvo) ==========================
if (!mpctx->video_out) {
struct vo_extra ex = {
.input_ctx = mpctx->input,
@@ -307,6 +297,9 @@ int reinit_video_chain(struct MPContext *mpctx)
if (!video_init_best_codec(d_video, opts->video_decoders))
goto err_out;
+ if (d_video->header->attached_picture && !decode_coverart(d_video))
+ goto err_out;
+
bool saver_state = opts->pause || !opts->stop_screensaver;
vo_control(mpctx->video_out, saver_state ? VOCTRL_RESTORE_SCREENSAVER
: VOCTRL_KILL_SCREENSAVER, NULL);
@@ -345,7 +338,9 @@ void mp_force_video_refresh(struct MPContext *mpctx)
return;
// If not paused, the next frame should come soon enough.
- if (opts->pause && mpctx->last_vo_pts != MP_NOPTS_VALUE) {
+ if (opts->pause && mpctx->video_status == STATUS_PLAYING &&
+ mpctx->last_vo_pts != MP_NOPTS_VALUE)
+ {
queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->last_vo_pts,
MPSEEK_VERY_EXACT, true);
}
@@ -368,6 +363,17 @@ static int check_framedrop(struct MPContext *mpctx)
return 0;
}
+static bool decode_coverart(struct dec_video *d_video)
+{
+ d_video->cover_art_mpi =
+ video_decode(d_video, d_video->header->attached_picture, 0);
+ // Might need flush.
+ if (!d_video->cover_art_mpi)
+ d_video->cover_art_mpi = video_decode(d_video, NULL, 0);
+
+ return !!d_video->cover_art_mpi;
+}
+
// Read a packet, store decoded image into d_video->waiting_decoded_mpi
// returns VD_* code
static int decode_image(struct MPContext *mpctx)
@@ -375,9 +381,8 @@ static int decode_image(struct MPContext *mpctx)
struct dec_video *d_video = mpctx->d_video;
if (d_video->header->attached_picture) {
- d_video->waiting_decoded_mpi =
- video_decode(d_video, d_video->header->attached_picture, 0);
- return d_video->waiting_decoded_mpi ? VD_EOF : VD_PROGRESS;
+ d_video->waiting_decoded_mpi = mp_image_new_ref(d_video->cover_art_mpi);
+ return VD_EOF;
}
struct demux_packet *pkt;
@@ -423,8 +428,10 @@ static void init_filter_params(struct MPContext *mpctx)
// recreate the chain a second time, which is not very elegant, but allows
// us to test whether enabling deinterlacing works with the current video
// format and other filters.
- if (opts->deinterlace >= 0)
- mp_property_do("deinterlace", M_PROPERTY_SET, &opts->deinterlace, mpctx);
+ if (opts->deinterlace >= 0) {
+ remove_deint_filter(mpctx);
+ set_deinterlacing(mpctx, opts->deinterlace != 0);
+ }
}
// Feed newly decoded frames to the filter, take care of format changes.
@@ -544,65 +551,93 @@ static void adjust_sync(struct MPContext *mpctx, double v_pts, double frame_time
mpctx->total_avsync_change += change;
}
-// Move the frame in next_frame[1] to next_frame[0]. This makes the frame
-// "known" to the playback logic. A frame in next_frame[0] is either "known" or
-// NULL, so the moving must always be done by this function.
-static void shift_new_frame(struct MPContext *mpctx)
+// Make the frame at position 0 "known" to the playback logic. This must happen
+// only once for each frame, so this function has to be called carefully.
+// Generally, if position 0 gets a new frame, this must be called.
+static void handle_new_frame(struct MPContext *mpctx)
{
- if (mpctx->next_frame[0] || !mpctx->next_frame[1])
- return;
-
- mpctx->next_frame[0] = mpctx->next_frame[1];
- mpctx->next_frame[1] = NULL;
+ assert(mpctx->num_next_frames >= 1);
double frame_time = 0;
- double pts = mpctx->next_frame[0]->pts;
+ double pts = mpctx->next_frames[0]->pts;
if (mpctx->video_pts != MP_NOPTS_VALUE) {
frame_time = pts - mpctx->video_pts;
- if (frame_time <= 0 || frame_time >= 60) {
- // Assume a PTS difference >= 60 seconds is a discontinuity.
+ double tolerance = 15;
+ if (mpctx->demuxer->ts_resets_possible) {
+ // Fortunately no real framerate is likely to go below this. It
+ // still could be that the file is VFR, but the demuxer reports a
+ // higher rate, so account for the case of e.g. 60hz demuxer fps
+ // but 23hz actual fps.
+ double fps = 23.976;
+ if (mpctx->d_video->fps > 0 && mpctx->d_video->fps < fps)
+ fps = mpctx->d_video->fps;
+ tolerance = 3 * 1.0 / fps;
+ }
+ if (frame_time <= 0 || frame_time >= tolerance) {
+ // Assume a discontinuity.
MP_WARN(mpctx, "Invalid video timestamp: %f -> %f\n",
mpctx->video_pts, pts);
frame_time = 0;
+ if (mpctx->d_audio)
+ mpctx->audio_status = STATUS_SYNCING;
}
}
mpctx->video_next_pts = pts;
mpctx->delay -= frame_time;
if (mpctx->video_status >= STATUS_PLAYING) {
- mpctx->time_frame += frame_time / mpctx->opts->playback_speed;
+ mpctx->time_frame += frame_time / mpctx->video_speed;
adjust_sync(mpctx, pts, frame_time);
}
mpctx->dropped_frames = 0;
MP_TRACE(mpctx, "frametime=%5.3f\n", frame_time);
}
+// Remove the first frame in mpctx->next_frames
+static void shift_frames(struct MPContext *mpctx)
+{
+ if (mpctx->num_next_frames < 1)
+ return;
+ talloc_free(mpctx->next_frames[0]);
+ for (int n = 0; n < mpctx->num_next_frames - 1; n++)
+ mpctx->next_frames[n] = mpctx->next_frames[n + 1];
+ mpctx->num_next_frames -= 1;
+}
+
+static int get_req_frames(struct MPContext *mpctx, bool eof)
+{
+ // On EOF, drain all frames.
+ // On the first frame, output a new frame as quickly as possible.
+ if (eof || mpctx->video_pts == MP_NOPTS_VALUE)
+ return 1;
+
+ int req = vo_get_num_req_frames(mpctx->video_out);
+ return MPCLAMP(req, 2, MP_ARRAY_SIZE(mpctx->next_frames));
+}
+
// Whether it's fine to call add_new_frame() now.
static bool needs_new_frame(struct MPContext *mpctx)
{
- return !mpctx->next_frame[1];
+ return mpctx->num_next_frames < get_req_frames(mpctx, false);
}
-// Queue a frame to mpctx->next_frame[]. Call only if needs_new_frame() signals ok.
+// Queue a frame to mpctx->next_frames[]. Call only if needs_new_frame() signals ok.
static void add_new_frame(struct MPContext *mpctx, struct mp_image *frame)
{
assert(needs_new_frame(mpctx));
assert(frame);
- mpctx->next_frame[1] = frame;
- shift_new_frame(mpctx);
+ mpctx->next_frames[mpctx->num_next_frames++] = frame;
+ if (mpctx->num_next_frames == 1)
+ handle_new_frame(mpctx);
}
// Enough video filtered already to push one frame to the VO?
// Set eof to true if no new frames are to be expected.
static bool have_new_frame(struct MPContext *mpctx, bool eof)
{
- bool need_2nd = !!(mpctx->opts->frame_dropping & 1) // we need the duration
- && mpctx->video_pts != MP_NOPTS_VALUE // ...except for the 1st frame
- && !eof; // on EOF, drain the remaining frames
-
- return mpctx->next_frame[0] && (!need_2nd || mpctx->next_frame[1]);
+ return mpctx->num_next_frames >= get_req_frames(mpctx, eof);
}
-// Fill mpctx->next_frame[] with a newly filtered or decoded image.
+// Fill mpctx->next_frames[] with a newly filtered or decoded image.
// returns VD_* code
static int video_output_image(struct MPContext *mpctx, double endpts)
{
@@ -611,13 +646,15 @@ static int video_output_image(struct MPContext *mpctx, double endpts)
if (mpctx->d_video->header->attached_picture) {
if (vo_has_frame(mpctx->video_out))
return VD_EOF;
- if (mpctx->next_frame[0])
+ if (mpctx->num_next_frames >= 1)
return VD_NEW_FRAME;
int r = video_decode_and_filter(mpctx);
video_filter(mpctx, true); // force EOF filtering (avoid decoding more)
- mpctx->next_frame[0] = vf_read_output_frame(mpctx->d_video->vfilter);
- if (mpctx->next_frame[0])
- mpctx->next_frame[0]->pts = MP_NOPTS_VALUE;
+ mpctx->next_frames[0] = vf_read_output_frame(mpctx->d_video->vfilter);
+ if (mpctx->next_frames[0]) {
+ mpctx->next_frames[0]->pts = MP_NOPTS_VALUE;
+ mpctx->num_next_frames = 1;
+ }
return r <= 0 ? VD_EOF : VD_PROGRESS;
}
@@ -672,13 +709,15 @@ static void update_avsync_before_frame(struct MPContext *mpctx)
if (!mpctx->sync_audio_to_video || mpctx->video_status < STATUS_READY) {
mpctx->time_frame = 0;
+ } else if (mpctx->display_sync_active || opts->video_sync == VS_NONE) {
+ // don't touch the timing
} else if (mpctx->audio_status == STATUS_PLAYING &&
mpctx->video_status == STATUS_PLAYING &&
!ao_untimed(mpctx->ao))
{
double buffered_audio = ao_get_delay(mpctx->ao);
- double predicted = mpctx->delay / opts->playback_speed +
+ double predicted = mpctx->delay / mpctx->video_speed +
mpctx->time_frame;
double difference = buffered_audio - predicted;
MP_STATS(mpctx, "value %f audio-diff", difference);
@@ -694,7 +733,7 @@ static void update_avsync_before_frame(struct MPContext *mpctx)
buffered_audio = predicted + difference / opts->autosync;
}
- mpctx->time_frame = buffered_audio - mpctx->delay / opts->playback_speed;
+ mpctx->time_frame = buffered_audio - mpctx->delay / mpctx->video_speed;
} else {
/* If we're more than 200 ms behind the right playback
* position, don't try to speed up display of following
@@ -723,9 +762,9 @@ static void update_avsync_after_frame(struct MPContext *mpctx)
mpctx->last_av_difference = a_pos - mpctx->video_pts + opts->audio_delay;
if (mpctx->time_frame > 0)
- mpctx->last_av_difference += mpctx->time_frame * opts->playback_speed;
+ mpctx->last_av_difference += mpctx->time_frame * mpctx->video_speed;
if (a_pos == MP_NOPTS_VALUE || mpctx->video_pts == MP_NOPTS_VALUE) {
- mpctx->last_av_difference = MP_NOPTS_VALUE;
+ mpctx->last_av_difference = 0;
} else if (fabs(mpctx->last_av_difference) > 0.5 && !mpctx->drop_message_shown) {
MP_WARN(mpctx, "%s", av_desync_help_text);
mpctx->drop_message_shown = true;
@@ -751,6 +790,290 @@ static void init_vo(struct MPContext *mpctx)
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
}
+// Attempt to stabilize frame duration from jittery timestamps. This is mostly
+// needed with semi-broken file formats which round timestamps to ms, or files
+// created from them.
+// We do this to make a stable decision how much to change video playback speed.
+// Otherwise calc_best_speed() could make a different decision every frame, and
+// also audio speed would have to be readjusted all the time.
+// Return -1 if the frame duration seems to be unstable.
+// If require_exact is false, just return the average frame duration on failure.
+double stabilize_frame_duration(struct MPContext *mpctx, bool require_exact)
+{
+ if (require_exact && mpctx->broken_fps_header)
+ return -1;
+
+ // Note: the past frame durations are raw and unadjusted.
+ double fd[10];
+ int num = get_past_frame_durations(mpctx, fd, MP_ARRAY_SIZE(fd));
+ if (num < MP_ARRAY_SIZE(fd))
+ return -1;
+
+ bool ok = true;
+ double min = fd[0];
+ double max = fd[0];
+ double total_duration = 0;
+ for (int n = 0; n < num; n++) {
+ double cur = fd[n];
+ if (fabs(cur - fd[num - 1]) > FRAME_DURATION_TOLERANCE)
+ ok = false;
+ min = MPMIN(min, cur);
+ max = MPMAX(max, cur);
+ total_duration += cur;
+ }
+
+ if (max - min > FRAME_DURATION_TOLERANCE || !ok)
+ goto fail;
+
+ // It's not really possible to compute the actual, correct FPS, unless we
+ // e.g. consider a list of potentially correct values, detect cycles, or
+ // use similar guessing methods.
+ // Naively using the average between min and max should give a stable, but
+ // still relatively close value.
+ double modified_duration = (min + max) / 2;
+
+ // Except for the demuxer reported FPS, which might be the correct one.
+ // VFR files could contain segments that don't match.
+ if (mpctx->d_video->fps > 0) {
+ double demux_duration = 1.0 / mpctx->d_video->fps;
+ if (fabs(modified_duration - demux_duration) <= FRAME_DURATION_TOLERANCE)
+ modified_duration = demux_duration;
+ }
+
+ // Verify the estimated stabilized frame duration with the actual time
+ // passed in these frames. If it's wrong (wrong FPS in the header), then
+ // this will deviate a bit.
+ if (fabs(total_duration - modified_duration * num) > FRAME_DURATION_TOLERANCE)
+ {
+ if (require_exact && !mpctx->broken_fps_header) {
+ // The error message is slightly misleading: a framerate header
+ // field is not really needed, as long as the file has an exact
+ // timebase.
+ MP_WARN(mpctx, "File has broken or missing framerate header\n"
+ "field, or is VFR with broken timestamps.\n");
+ mpctx->broken_fps_header = true;
+ }
+ goto fail;
+ }
+
+ return modified_duration;
+
+fail:
+ return require_exact ? -1 : total_duration / num;
+}
+
+static bool using_spdif_passthrough(struct MPContext *mpctx)
+{
+ if (mpctx->d_audio && mpctx->d_audio->afilter)
+ return !af_fmt_is_pcm(mpctx->d_audio->afilter->output.format);
+ return false;
+}
+
+// Find a speed factor such that the display FPS is an integer multiple of the
+// effective video FPS. If this is not possible, try to do it for multiples,
+// which still leads to an improved end result.
+// Both parameters are durations in seconds.
+static double calc_best_speed(struct MPContext *mpctx, double vsync, double frame)
+{
+ struct MPOpts *opts = mpctx->opts;
+
+ double ratio = frame / vsync;
+ for (int factor = 1; factor <= 5; factor++) {
+ double scale = ratio * factor / floor(ratio * factor + 0.5);
+ if (fabs(scale - 1) > opts->sync_max_video_change / 100)
+ continue; // large deviation, skip
+ return scale; // decent match found
+ }
+ return -1;
+}
+
+// Manipulate frame timing for display sync, or do nothing for normal timing.
+static void handle_display_sync_frame(struct MPContext *mpctx,
+ struct vo_frame *frame)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct vo *vo = mpctx->video_out;
+ bool old_display_sync = mpctx->display_sync_active;
+ int mode = opts->video_sync;
+
+ if (!mpctx->display_sync_active) {
+ mpctx->display_sync_error = 0.0;
+ mpctx->display_sync_drift_dir = 0;
+ }
+
+ mpctx->display_sync_active = false;
+ mpctx->speed_factor_a = 1.0;
+ mpctx->speed_factor_v = 1.0;
+
+ if (!VS_IS_DISP(mode))
+ goto done;
+ bool resample = mode == VS_DISP_RESAMPLE || mode == VS_DISP_RESAMPLE_VDROP ||
+ mode == VS_DISP_RESAMPLE_NONE;
+ bool drop = mode == VS_DISP_VDROP || mode == VS_DISP_RESAMPLE ||
+ mode == VS_DISP_RESAMPLE_VDROP;
+ drop &= (opts->frame_dropping & 1);
+
+ if (resample && using_spdif_passthrough(mpctx))
+ goto done;
+
+ double vsync = vo_get_vsync_interval(vo) / 1e6;
+ if (vsync <= 0)
+ goto done;
+
+ double adjusted_duration = stabilize_frame_duration(mpctx, true);
+ if (adjusted_duration >= 0)
+ adjusted_duration /= opts->playback_speed;
+ if (adjusted_duration <= 0.002 || adjusted_duration > 0.05)
+ goto done;
+
+ double prev_duration = mpctx->display_sync_frameduration;
+ mpctx->display_sync_frameduration = adjusted_duration;
+ if (adjusted_duration != prev_duration) {
+ mpctx->display_sync_disable_counter = 50;
+ goto done;
+ }
+
+ double video_speed_correction = calc_best_speed(mpctx, vsync, adjusted_duration);
+ if (video_speed_correction <= 0)
+ goto done;
+
+ double av_diff = mpctx->last_av_difference;
+ if (fabs(av_diff) > 0.5)
+ goto done;
+
+ // At this point, we decided that we could use display sync for this frame.
+ // But if we switch too often between these modes, keep it disabled. In
+ // fact, we disable it if it just wants to switch between enable/disable
+ // more than once in the last N frames.
+ if (!old_display_sync) {
+ if (mpctx->display_sync_disable_counter > 0)
+ goto done; // keep disabled
+ mpctx->display_sync_disable_counter = 50;
+ }
+
+ MP_STATS(mpctx, "value %f avdiff", av_diff);
+
+ // Intended number of additional display frames to drop (<0) or repeat (>0)
+ int drop_repeat = 0;
+
+ // If we are too far ahead/behind, attempt to drop/repeat frames. In
+ // particular, don't attempt to change speed for them.
+ if (drop) {
+ drop_repeat = -av_diff / vsync; // round towards 0
+ av_diff -= drop_repeat * vsync;
+ }
+
+ if (resample) {
+ double audio_factor = 1.0;
+ if (mode == VS_DISP_RESAMPLE && mpctx->audio_status == STATUS_PLAYING) {
+ // Try to smooth out audio timing drifts. This can happen if either
+ // video isn't playing at expected speed, or audio is not playing at
+ // the requested speed. Both are unavoidable.
+ // The audio desync is made up of 2 parts: 1. drift due to rounding
+ // errors and imperfect information, and 2. an offset, due to
+ // unaligned audio/video start, or disruptive events halting audio
+ // or video for a small time.
+ // Instead of trying to be clever, just apply an awfully dumb drift
+ // compensation with a constant factor, which does what we want. In
+ // theory we could calculate the exact drift compensation needed,
+ // but it likely would be wrong anyway, and we'd run into the same
+ // issues again, except with more complex code.
+ // 1 means drifts to positive, -1 means drifts to negative
+ double max_drift = vsync / 2;
+ int new = mpctx->display_sync_drift_dir;
+ if (av_diff * -mpctx->display_sync_drift_dir >= 0)
+ new = 0;
+ if (fabs(av_diff) > max_drift)
+ new = copysign(1, av_diff);
+ if (mpctx->display_sync_drift_dir != new) {
+ MP_VERBOSE(mpctx, "Change display sync audio drift: %d\n", new);
+ mpctx->display_sync_drift_dir = new;
+ }
+ double max_correct = opts->sync_max_audio_change / 100;
+ audio_factor = 1 + max_correct * -mpctx->display_sync_drift_dir;
+ }
+
+ mpctx->speed_factor_a = audio_factor * video_speed_correction;
+
+ MP_STATS(mpctx, "value %f aspeed", mpctx->speed_factor_a - 1);
+ }
+
+ // Determine for how many vsyncs a frame should be displayed. This can be
+ // e.g. 2 for 30hz on a 60hz display. It can also be 0 if the video
+ // framerate is higher than the display framerate.
+ // We use the speed-adjusted (i.e. real) frame duration for this.
+ double frame_duration = adjusted_duration / video_speed_correction;
+ double ratio = (frame_duration + mpctx->display_sync_error) / vsync;
+ int num_vsyncs = MPMAX(floor(ratio + 0.5), 0);
+ mpctx->display_sync_error += frame_duration - num_vsyncs * vsync;
+ frame->vsync_offset = mpctx->display_sync_error * 1e6;
+
+ MP_DBG(mpctx, "s=%f vsyncs=%d dur=%f ratio=%f err=%.20f (%f)\n",
+ video_speed_correction, num_vsyncs, adjusted_duration, ratio,
+ mpctx->display_sync_error, mpctx->display_sync_error / vsync);
+
+ // We can only drop all frames at most. We can repeat much more frames,
+ // but we still limit it to 10 times the original frames to avoid that
+ // corner cases or exceptional situations cause too much havoc.
+ drop_repeat = MPCLAMP(drop_repeat, -num_vsyncs, num_vsyncs * 10);
+ num_vsyncs += drop_repeat;
+ if (drop_repeat < 0)
+ vo_increment_drop_count(vo, 1);
+
+ // Estimate the video position, so we can calculate a good A/V difference
+ // value with update_avsync_after_frame() later. This is used to estimate
+ // A/V drift.
+ mpctx->time_frame = 0;
+ double time_left = (vo_get_next_frame_start_time(vo) - mp_time_us()) / 1e6;
+ if (time_left >= 0)
+ mpctx->time_frame += time_left;
+ // We also know that the timing is (necessarily) off, because we have to
+ // align frame timings on the vsync boundaries. This is unavoidable, and
+ // for the sake of the video sync calculations we pretend it's perfect.
+ mpctx->time_frame -= mpctx->display_sync_error;
+
+ mpctx->speed_factor_v = video_speed_correction;
+
+ frame->num_vsyncs = num_vsyncs;
+ frame->display_synced = true;
+
+ mpctx->display_sync_active = true;
+
+done:
+
+ update_playback_speed(mpctx);
+
+ if (old_display_sync != mpctx->display_sync_active) {
+ MP_VERBOSE(mpctx, "Video sync mode %s.\n",
+ mpctx->display_sync_active ? "enabled" : "disabled");
+ }
+
+ mpctx->display_sync_disable_counter =
+ MPMAX(0, mpctx->display_sync_disable_counter - 1);
+}
+
+// Return the next frame duration as stored in the file.
+// frame=0 means the current frame, 1 the frame after that etc.
+// Can return -1, though usually will return a fallback if frame unavailable.
+static double get_frame_duration(struct MPContext *mpctx, int frame)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct vo *vo = mpctx->video_out;
+
+ double diff = -1;
+ if (frame + 2 <= mpctx->num_next_frames) {
+ double vpts0 = mpctx->next_frames[frame]->pts;
+ double vpts1 = mpctx->next_frames[frame + 1]->pts;
+ if (vpts0 != MP_NOPTS_VALUE && vpts1 != MP_NOPTS_VALUE)
+ diff = vpts1 - vpts0;
+ }
+ if (diff < 0 && mpctx->d_video->fps > 0)
+ diff = 1.0 / mpctx->d_video->fps; // fallback to demuxer-reported fps
+ if (opts->untimed || vo->driver->untimed)
+ diff = -1; // disable frame dropping and aspects of frame timing
+ return diff;
+}
+
void write_video(struct MPContext *mpctx, double endpts)
{
struct MPOpts *opts = mpctx->opts;
@@ -793,7 +1116,7 @@ void write_video(struct MPContext *mpctx, double endpts)
}
// Filter output is different from VO input?
- struct mp_image_params p = mpctx->next_frame[0]->params;
+ struct mp_image_params p = mpctx->next_frames[0]->params;
if (!vo->params || !mp_image_params_equal(&p, vo->params)) {
// Changing config deletes the current frame; wait until it's finished.
if (vo_still_displaying(vo))
@@ -822,31 +1145,38 @@ void write_video(struct MPContext *mpctx, double endpts)
int64_t pts = mp_time_us() + (int64_t)(time_frame * 1e6);
// wait until VO wakes us up to get more frames
- if (!vo_is_ready_for_frame(vo, pts)) {
+ // (NB: in theory, the 1st frame after display sync mode change uses the
+ // wrong waiting mode)
+ if (!vo_is_ready_for_frame(vo, mpctx->display_sync_active ? -1 : pts)) {
if (video_feed_async_filter(mpctx) < 0)
goto error;
return;
}
- int64_t duration = -1;
- double diff = -1;
- double vpts0 = mpctx->next_frame[0] ? mpctx->next_frame[0]->pts : MP_NOPTS_VALUE;
- double vpts1 = mpctx->next_frame[1] ? mpctx->next_frame[1]->pts : MP_NOPTS_VALUE;
- if (vpts0 != MP_NOPTS_VALUE && vpts1 != MP_NOPTS_VALUE)
- diff = vpts1 - vpts0;
- if (diff < 0 && mpctx->d_video->fps > 0)
- diff = 1.0 / mpctx->d_video->fps; // fallback to demuxer-reported fps
- if (opts->untimed || vo->driver->untimed)
- diff = -1; // disable frame dropping and aspects of frame timing
+ assert(mpctx->num_next_frames >= 1);
+ struct vo_frame dummy = {
+ .pts = pts,
+ .duration = -1,
+ .still = mpctx->step_frames > 0,
+ .num_frames = mpctx->num_next_frames,
+ .num_vsyncs = 1,
+ };
+ for (int n = 0; n < dummy.num_frames; n++)
+ dummy.frames[n] = mpctx->next_frames[n];
+ struct vo_frame *frame = vo_frame_ref(&dummy);
+
+ double diff = get_frame_duration(mpctx, 0);
if (diff >= 0) {
// expected A/V sync correction is ignored
- diff /= opts->playback_speed;
+ diff /= mpctx->video_speed;
if (mpctx->time_frame < 0)
diff += mpctx->time_frame;
- duration = MPCLAMP(diff, 0, 10) * 1e6;
+ frame->duration = MPCLAMP(diff, 0, 10) * 1e6;
}
- mpctx->video_pts = mpctx->next_frame[0]->pts;
+ handle_display_sync_frame(mpctx, frame);
+
+ mpctx->video_pts = mpctx->next_frames[0]->pts;
mpctx->last_vo_pts = mpctx->video_pts;
mpctx->playback_pts = mpctx->video_pts;
@@ -856,16 +1186,20 @@ void write_video(struct MPContext *mpctx, double endpts)
update_osd_msg(mpctx);
update_subtitles(mpctx);
- vo_queue_frame(vo, mpctx->next_frame[0], pts, duration);
- mpctx->next_frame[0] = NULL;
+ vo_queue_frame(vo, frame);
+
+ shift_frames(mpctx);
- shift_new_frame(mpctx);
+ // The frames were shifted down; "initialize" the new first entry.
+ if (mpctx->num_next_frames >= 1)
+ handle_new_frame(mpctx);
mpctx->shown_vframes++;
if (mpctx->video_status < STATUS_PLAYING) {
mpctx->video_status = STATUS_READY;
// After a seek, make sure to wait until the first frame is visible.
vo_wait_frame(vo);
+ MP_VERBOSE(mpctx, "first video frame after restart shown\n");
}
screenshot_flip(mpctx);
@@ -880,7 +1214,7 @@ void write_video(struct MPContext *mpctx, double endpts)
if (!mpctx->step_frames && !opts->pause)
pause_player(mpctx);
}
- if (mpctx->max_frames == 0)
+ if (mpctx->max_frames == 0 && !mpctx->stop_play)
mpctx->stop_play = AT_END_OF_FILE;
if (mpctx->max_frames > 0)
mpctx->max_frames--;
diff --git a/stream/cache.c b/stream/cache.c
index ede1e1f..43b7eba 100644
--- a/stream/cache.c
+++ b/stream/cache.c
@@ -215,6 +215,13 @@ static bool cache_fill(struct priv *s)
// number of buffer bytes which should be preserved in backwards direction
int64_t back = MPCLAMP(read - s->min_filepos, 0, s->back_size);
+ // limit maximum readahead so that the backbuffer space is reserved, even
+ // if the backbuffer is not used. limit it to ensure that we don't stall the
+ // network when starting a file, or we wouldn't download new data until we
+ // get new free space again. (unless everything fits in the cache.)
+ if (s->stream_size > s->buffer_size)
+ back = MPMAX(back, s->back_size);
+
// number of buffer bytes that are valid and can be read
int64_t newb = FFMAX(s->max_filepos - read, 0);
@@ -276,11 +283,15 @@ done:
}
// This is called both during init and at runtime.
+// The size argument is the readahead half only; s->back_size is the backbuffer.
static int resize_cache(struct priv *s, int64_t size)
{
- int64_t min_size = FILL_LIMIT * 4;
- int64_t max_size = ((size_t)-1) / 4;
+ int64_t min_size = FILL_LIMIT * 2;
+ int64_t max_size = ((size_t)-1) / 8;
+
int64_t buffer_size = MPCLAMP(size, min_size, max_size);
+ s->back_size = MPCLAMP(s->back_size, min_size, max_size);
+ buffer_size += s->back_size;
unsigned char *buffer = malloc(buffer_size);
if (!buffer) {
@@ -317,7 +328,6 @@ static int resize_cache(struct priv *s, int64_t size)
free(s->buffer);
s->buffer_size = buffer_size;
- s->back_size = buffer_size / 2;
s->buffer = buffer;
s->idle = false;
s->eof = false;
@@ -327,6 +337,8 @@ static int resize_cache(struct priv *s, int64_t size)
if (s->seek_limit > s->buffer_size - FILL_LIMIT)
s->seek_limit = s->buffer_size - FILL_LIMIT;
+ assert(s->back_size < s->buffer_size);
+
return STREAM_OK;
}
@@ -343,7 +355,8 @@ static void update_cached_controls(struct priv *s)
s->stream_metadata = talloc_steal(s, tags);
}
s->stream_size = s->eof_pos;
- if (stream_control(s->stream, STREAM_CTRL_GET_SIZE, &i64) == STREAM_OK)
+ i64 = stream_get_size(s->stream);
+ if (i64 >= 0)
s->stream_size = i64;
s->has_avseek = stream_control(s->stream, STREAM_CTRL_HAS_AVSEEK, NULL) > 0;
}
@@ -609,11 +622,11 @@ int stream_cache_init(stream_t *cache, stream_t *stream,
cache_drop_contents(s);
s->seek_limit = opts->seek_min * 1024ULL;
+ s->back_size = opts->back_buffer * 1024ULL;
int64_t cache_size = opts->size * 1024ULL;
- int64_t file_size = -1;
- stream_control(stream, STREAM_CTRL_GET_SIZE, &file_size);
+ int64_t file_size = stream_get_size(stream);
if (file_size >= 0)
cache_size = MPMIN(cache_size, file_size);
@@ -623,8 +636,9 @@ int stream_cache_init(stream_t *cache, stream_t *stream,
return -1;
}
- MP_VERBOSE(cache, "Cache size set to %" PRId64 " KiB\n",
- s->buffer_size / 1024);
+ MP_VERBOSE(cache, "Cache size set to %lld KiB (%lld KiB backbuffer)\n",
+ (long long)(s->buffer_size / 1024),
+ (long long)(s->back_size / 1024));
pthread_mutex_init(&s->mutex, NULL);
pthread_cond_init(&s->wakeup, NULL);
diff --git a/stream/cache_file.c b/stream/cache_file.c
index 94b3f4b..901b3f6 100644
--- a/stream/cache_file.c
+++ b/stream/cache_file.c
@@ -66,8 +66,7 @@ static int fill_buffer(stream_t *s, char *buffer, int max_len)
}
// Size of file changes -> invalidate last block
if (s->pos >= p->size - BLOCK_SIZE) {
- int64_t new_size = -1;
- stream_control(s, STREAM_CTRL_GET_SIZE, &new_size);
+ int64_t new_size = stream_get_size(s);
if (p->size >= 0 && new_size != p->size)
set_bit(p, BLOCK_ALIGN(p->size), 0);
p->size = MPMIN(p->max_size, new_size);
diff --git a/stream/discnav.h b/stream/discnav.h
deleted file mode 100644
index b40998d..0000000
--- a/stream/discnav.h
+++ /dev/null
@@ -1,86 +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/>.
- */
-
-#ifndef MPLAYER_STREAM_DVDNAV_H
-#define MPLAYER_STREAM_DVDNAV_H
-
-#include <inttypes.h>
-#include <stdbool.h>
-#include "stream.h"
-
-// Sent from stream to player.
-// Note: order matters somewhat (stream_dvdnav sends them in numeric order)
-enum mp_nav_event_type {
- MP_NAV_EVENT_NONE,
- MP_NAV_EVENT_MENU_MODE, // menu mode on/off
- MP_NAV_EVENT_HIGHLIGHT, // highlight changed
- MP_NAV_EVENT_RESET, // reinitialize some things
- MP_NAV_EVENT_RESET_CLUT, // reinitialize sub palette
- MP_NAV_EVENT_RESET_ALL, // reinitialize all things
- MP_NAV_EVENT_DRAIN, // reply with MP_NAV_CMD_DRAIN_OK
- MP_NAV_EVENT_STILL_FRAME, // keep displaying current frame
- MP_NAV_EVENT_EOF, // it's over
- MP_NAV_EVENT_OVERLAY, // overlay changed
-};
-
-struct sub_bitmap;
-
-struct mp_nav_event {
- enum mp_nav_event_type event;
- union {
- struct {
- int seconds; // -1: infinite
- } still_frame;
- struct {
- int display;
- int sx, sy, ex, ey;
- uint32_t palette;
- } highlight;
- struct {
- bool enable;
- } menu_mode;
- struct {
- struct sub_bitmap *images[2];
- } overlay;
- } u;
-};
-
-// Sent from player to stream.
-enum mp_nav_cmd_type {
- MP_NAV_CMD_NONE,
- MP_NAV_CMD_ENABLE, // enable interactive navigation
- MP_NAV_CMD_DRAIN_OK, // acknowledge EVENT_DRAIN
- MP_NAV_CMD_RESUME,
- MP_NAV_CMD_SKIP_STILL, // after showing the frame in EVENT_STILL_FRAME
- MP_NAV_CMD_MENU,
- MP_NAV_CMD_MOUSE_POS,
-};
-
-struct mp_nav_cmd {
- enum mp_nav_cmd_type event;
- union {
- struct {
- const char *action;
- } menu;
- struct {
- int x, y;
- } mouse_pos;
- } u;
- bool mouse_on_button;
-};
-
-#endif /* MPLAYER_STREAM_DVDNAV_H */
diff --git a/stream/stream.c b/stream/stream.c
index cfdf67b..4f8ee11 100644
--- a/stream/stream.c
+++ b/stream/stream.c
@@ -75,6 +75,7 @@ extern const stream_info_t stream_info_bluray;
extern const stream_info_t stream_info_bdnav;
extern const stream_info_t stream_info_rar;
extern const stream_info_t stream_info_edl;
+extern const stream_info_t stream_info_libarchive;
static const stream_info_t *const stream_list[] = {
#if HAVE_CDDA
@@ -108,6 +109,9 @@ static const stream_info_t *const stream_list[] = {
&stream_info_bluray,
&stream_info_bdnav,
#endif
+#if HAVE_LIBARCHIVE
+ &stream_info_libarchive,
+#endif
&stream_info_memory,
&stream_info_null,
@@ -464,8 +468,7 @@ static int stream_read_unbuffered(stream_t *s, void *buf, int len)
// just in case this is an error e.g. due to network
// timeout reset and retry
// do not retry if this looks like proper eof
- int64_t size = -1;
- stream_control(s, STREAM_CTRL_GET_SIZE, &size);
+ int64_t size = stream_get_size(s);
if (!s->eof && s->pos != size && stream_reconnect(s)) {
s->eof = 1; // make sure EOF is set to ensure no endless recursion
return stream_read_unbuffered(s, buf, orig_len);
@@ -698,6 +701,15 @@ int stream_control(stream_t *s, int cmd, void *arg)
return s->control ? s->control(s, cmd, arg) : STREAM_UNSUPPORTED;
}
+// Return the current size of the stream, or a negative value if unknown.
+int64_t stream_get_size(stream_t *s)
+{
+ int64_t size = -1;
+ if (stream_control(s, STREAM_CTRL_GET_SIZE, &size) != STREAM_OK)
+ size = -1;
+ return size;
+}
+
void free_stream(stream_t *s)
{
if (!s)
@@ -904,8 +916,7 @@ struct bstr stream_read_complete(struct stream *s, void *talloc_ctx,
int total_read = 0;
int padding = 1;
char *buf = NULL;
- int64_t size = 0;
- stream_control(s, STREAM_CTRL_GET_SIZE, &size);
+ int64_t size = stream_get_size(s);
if (size > max_size)
return (struct bstr){NULL, 0};
if (size > 0)
@@ -929,6 +940,20 @@ struct bstr stream_read_complete(struct stream *s, void *talloc_ctx,
return (struct bstr){buf, total_read};
}
+struct bstr stream_read_file(const char *filename, void *talloc_ctx,
+ struct mpv_global *global, int max_size)
+{
+ struct bstr res = {0};
+ char *fname = mp_get_user_path(NULL, global, filename);
+ stream_t *s = stream_open(fname, global);
+ if (s) {
+ res = stream_read_complete(s, talloc_ctx, max_size);
+ free_stream(s);
+ }
+ talloc_free(fname);
+ return res;
+}
+
struct mp_cancel {
atomic_bool triggered;
#ifdef __MINGW32__
@@ -1024,11 +1049,10 @@ int mp_cancel_get_fd(struct mp_cancel *c)
return c->wakeup_pipe[0];
}
-void stream_print_proto_list(struct mp_log *log)
+char **stream_get_proto_list(void)
{
- int count = 0;
-
- mp_info(log, "Protocols:\n\n");
+ char **list = NULL;
+ int num = 0;
for (int i = 0; stream_list[i]; i++) {
const stream_info_t *stream_info = stream_list[i];
@@ -1039,9 +1063,25 @@ void stream_print_proto_list(struct mp_log *log)
if (*stream_info->protocols[j] == '\0')
continue;
- mp_info(log, " %s://\n", stream_info->protocols[j]);
- count++;
+ MP_TARRAY_APPEND(NULL, list, num,
+ talloc_strdup(NULL, stream_info->protocols[j]));
}
}
+ MP_TARRAY_APPEND(NULL, list, num, NULL);
+ return list;
+}
+
+void stream_print_proto_list(struct mp_log *log)
+{
+ int count = 0;
+
+ mp_info(log, "Protocols:\n\n");
+ char **list = stream_get_proto_list();
+ for (int i = 0; list[i]; i++) {
+ mp_info(log, " %s://\n", list[i]);
+ count++;
+ talloc_free(list[i]);
+ }
+ talloc_free(list);
mp_info(log, "\nTotal: %d protocols\n", count);
}
diff --git a/stream/stream.h b/stream/stream.h
index 7b47514..28a6ba6 100644
--- a/stream/stream.h
+++ b/stream/stream.h
@@ -103,8 +103,6 @@ enum stream_ctrl {
// Optical discs
STREAM_CTRL_GET_TIME_LENGTH,
STREAM_CTRL_GET_DVD_INFO,
- STREAM_CTRL_GET_NAV_EVENT, // struct mp_nav_event**
- STREAM_CTRL_NAV_CMD, // struct mp_nav_cmd*
STREAM_CTRL_GET_DISC_NAME,
STREAM_CTRL_GET_NUM_CHAPTERS,
STREAM_CTRL_GET_CURRENT_TIME,
@@ -253,11 +251,14 @@ int stream_read(stream_t *s, char *mem, int total);
int stream_read_partial(stream_t *s, char *buf, int buf_size);
struct bstr stream_peek(stream_t *s, int len);
void stream_drop_buffers(stream_t *s);
+int64_t stream_get_size(stream_t *s);
struct mpv_global;
struct bstr stream_read_complete(struct stream *s, void *talloc_ctx,
int max_size);
+struct bstr stream_read_file(const char *filename, void *talloc_ctx,
+ struct mpv_global *global, int max_size);
int stream_control(stream_t *s, int cmd, void *arg);
void free_stream(stream_t *s);
struct stream *stream_create(const char *url, int flags,
@@ -289,5 +290,6 @@ void mp_setup_av_network_options(struct AVDictionary **dict,
struct MPOpts *opts);
void stream_print_proto_list(struct mp_log *log);
+char **stream_get_proto_list(void);
#endif /* MPLAYER_STREAM_H */
diff --git a/stream/stream_bluray.c b/stream/stream_bluray.c
index dc06a6b..b40c327 100644
--- a/stream/stream_bluray.c
+++ b/stream/stream_bluray.c
@@ -46,11 +46,9 @@
#include "options/path.h"
#include "stream.h"
#include "osdep/timer.h"
-#include "discnav.h"
#include "sub/osd.h"
#include "sub/img_convert.h"
#include "video/mp_image.h"
-#include "video/mp_image_pool.h"
#define BLURAY_SECTOR_SIZE 6144
@@ -74,12 +72,6 @@
#define AACS_ERROR_MMC_FAILURE -7 /* MMC failed */
#define AACS_ERROR_NO_DK -8 /* no matching device key */
-struct bluray_overlay {
- struct sub_bitmap *image;
- bool clean, hidden;
- int x, y, w, h;
-};
-
struct bluray_priv_s {
BLURAY *bd;
BLURAY_TITLE_INFO *title_info;
@@ -91,15 +83,7 @@ struct bluray_priv_s {
int cfg_title;
char *cfg_device;
- // overlay stuffs
- struct bluray_overlay overlays[2], ol_flushed[2];
- struct mp_image_pool *pool;
-
- // navigation stuffs
- uint64_t next_event;
- uint32_t still_length;
- int mousex, mousey;
- bool in_menu, use_nav, nav_enabled, popup_enabled;
+ bool use_nav;
};
static const struct bluray_priv_s bluray_stream_priv_dflts = {
@@ -132,191 +116,11 @@ static void destruct(struct bluray_priv_s *priv)
if (priv->title_info)
bd_free_title_info(priv->title_info);
bd_close(priv->bd);
- talloc_free(priv->pool);
}
inline static int play_title(struct bluray_priv_s *priv, int title)
{
- if (priv->use_nav) {
- if (title == priv->num_titles - 1)
- title = BLURAY_TITLE_FIRST_PLAY;
- return bd_play_title(priv->bd, title);
- } else
- return bd_select_title(priv->bd, title);
-}
-
-static void overlay_release(struct bluray_overlay *overlay)
-{
- if (overlay->image)
- talloc_free(overlay->image);
- *overlay = (struct bluray_overlay) { .clean = true };
-}
-
-static void overlay_alloc(struct bluray_priv_s *priv,
- struct bluray_overlay *overlay,
- int x, int y, int w, int h)
-{
- assert(overlay->image == NULL);
- struct sub_bitmap *image = talloc_zero(NULL, struct sub_bitmap);
- overlay->w = image->w = image->dw = w;
- overlay->h = image->h = image->dh = h;
- overlay->x = image->x = x;
- overlay->y = image->y = y;
- struct mp_image *mpi = mp_image_pool_get(priv->pool, IMGFMT_RGBA, w, h);
- mpi = talloc_steal(image, mpi);
- assert(image->w > 0 && image->h > 0 && mpi != NULL);
- image->stride = mpi->stride[0];
- image->bitmap = mpi->planes[0];
- overlay->image = image;
- overlay->clean = true;
- overlay->hidden = false;
-}
-
-static void overlay_close_all(struct bluray_priv_s *priv)
-{
- for (int i = 0; i < 2; i++)
- overlay_release(&priv->overlays[i]);
-}
-
-static void overlay_close(struct bluray_priv_s *priv,
- const BD_OVERLAY *const bo)
-{
- overlay_release(&priv->overlays[bo->plane]);
-}
-
-static inline uint32_t conv_rgba(const BD_PG_PALETTE_ENTRY *p)
-{
- uint32_t rgba;
- uint8_t *out = (uint8_t*)&rgba;
- const int y = p->Y, cb = (int)p->Cb - 128, cr = (int)p->Cr - 128;
- // CAUTION: inaccurate but fast, broken in big endian
-#define CONV(a) (MPCLAMP((a), 0, 255)*p->T >> 8)
- out[0] = CONV(y + cb + (cb >> 1) + (cb >> 2) + (cb >> 6));
- out[1] = CONV(y - ((cb >> 2) + (cb >> 4) + (cb >> 5))
- - ((cr >> 3) + (cr >> 4) + (cr >> 5)));
- out[2] = CONV(y + cr + (cr >> 2) + (cr >> 3) + (cr >> 5));
- out[3] = p->T;
-#undef CONV
- return rgba;
-}
-
-static void overlay_process(void *data, const BD_OVERLAY *const bo)
-{
- stream_t *s = data;
- struct bluray_priv_s *priv = s->priv;
- if (!bo) {
- overlay_close_all(priv);
- return;
- }
- struct bluray_overlay *overlay = &priv->overlays[bo->plane];
- switch (bo->cmd) {
- case BD_OVERLAY_INIT:
- overlay_alloc(priv, overlay, bo->x, bo->y, bo->w, bo->h);
- break;
- case BD_OVERLAY_CLOSE:
- overlay_close(priv, bo);
- break;
- case BD_OVERLAY_CLEAR:
- if (!overlay->clean) {
- memset(overlay->image->bitmap, 0,
- overlay->image->stride*overlay->h);
- overlay->clean = true;
- }
- break;
- case BD_OVERLAY_DRAW: {
- if (!bo->img)
- break;
- overlay->hidden = false;
- overlay->clean = false;
- struct sub_bitmap *img = overlay->image;
- uint32_t *const origin = img->bitmap;
- const BD_PG_RLE_ELEM *in = bo->img;
- for (int y = 0; y < bo->h; y++) {
- uint32_t *out = origin + (img->stride/4) * (y + bo->y) + bo->x;
- for (int x = 0; x < bo->w; ) {
- uint32_t c = 0;
- if (bo->palette[in->color].T) {
- c = conv_rgba(&bo->palette[in->color]);
- for (int i = 0; i < in->len; i++)
- *out++ = c;
- } else {
- memset(out, 0, in->len*4);
- out += in->len;
- }
- x += in->len;
- ++in;
- }
- }
- break;
- }
- case BD_OVERLAY_WIPE: {
- uint32_t *const origin = overlay->image->bitmap;
- for (int y = 0; y < bo->h; y++)
- memset(origin + overlay->w * (y + bo->y) + bo->x, 0, 4 * bo->w);
- break;
- }
- case BD_OVERLAY_HIDE:
- priv->overlays[bo->plane].hidden = true;
- break;
- case BD_OVERLAY_FLUSH: {
- struct bluray_overlay *in = overlay;
- struct bluray_overlay *out = &priv->ol_flushed[bo->plane];
- if (out->image && (out->image->stride != in->image->stride ||
- out->image->h != in->image->h))
- overlay_release(out);
- if (!out->image)
- overlay_alloc(priv, out, in->x, in->y, in->w, in->h);
- const int len = in->image->stride*in->image->h;
- memcpy(out->image->bitmap, in->image->bitmap, len);
- out->clean = in->clean;
- out->hidden = in->hidden;
- priv->next_event |= 1 << MP_NAV_EVENT_OVERLAY;
- break;
- } default:
- break;
- }
-}
-
-static inline bool set_event_type(struct bluray_priv_s *priv, int type,
- struct mp_nav_event *event)
-{
- if (!(priv->next_event & (1 << type)))
- return false;
- priv->next_event &= ~(1 << type);
- event->event = type;
- return true;
-}
-
-static void fill_next_event(stream_t *s, struct mp_nav_event **ret)
-{
- struct bluray_priv_s *priv = s->priv;
- struct mp_nav_event e = {0};
- // this should be checked before any other events
- if (!set_event_type(priv, MP_NAV_EVENT_RESET_ALL, &e))
- for (int n = 0; n < 30 && !set_event_type(priv, n, &e); n++) ;
- switch (e.event) {
- case MP_NAV_EVENT_NONE:
- return;
- case MP_NAV_EVENT_OVERLAY: {
- for (int i = 0; i < 2; i++) {
- struct bluray_overlay *o = &priv->ol_flushed[i];
- e.u.overlay.images[i] = NULL;
- if (!o->clean && !o->hidden) {
- e.u.overlay.images[i] = o->image;
- o->image = NULL;
- }
- }
- break;
- }
- case MP_NAV_EVENT_MENU_MODE:
- e.u.menu_mode.enable = priv->in_menu;
- break;
- case MP_NAV_EVENT_STILL_FRAME:
- e.u.still_frame.seconds = priv->still_length;
- break;
- }
- *ret = talloc(NULL, struct mp_nav_event);
- **ret = e;
+ return bd_select_title(priv->bd, title);
}
static void bluray_stream_close(stream_t *s)
@@ -326,46 +130,28 @@ static void bluray_stream_close(stream_t *s)
static void handle_event(stream_t *s, const BD_EVENT *ev)
{
- static const int reset_flags = (1 << MP_NAV_EVENT_RESET_ALL)
- | (1 << MP_NAV_EVENT_RESET);
struct bluray_priv_s *b = s->priv;
switch (ev->event) {
case BD_EVENT_MENU:
- b->in_menu = ev->param;
- b->next_event |= 1 << MP_NAV_EVENT_MENU_MODE;
break;
case BD_EVENT_STILL:
- b->still_length = ev->param ? -1 : 0;
- if (b->nav_enabled)
- b->next_event |= 1 << MP_NAV_EVENT_STILL_FRAME;
break;
case BD_EVENT_STILL_TIME:
- b->still_length = ev->param ? -1 : ev->param*1000;
- if (b->nav_enabled)
- b->next_event |= 1 << MP_NAV_EVENT_STILL_FRAME;
- else
- bd_read_skip_still(b->bd);
+ bd_read_skip_still(b->bd);
break;
case BD_EVENT_END_OF_TITLE:
- overlay_close_all(b);
break;
case BD_EVENT_PLAYLIST:
- b->next_event = reset_flags;
b->current_playlist = ev->param;
- if (!b->use_nav)
- b->current_title = bd_get_current_title(b->bd);
+ b->current_title = bd_get_current_title(b->bd);
if (b->title_info)
bd_free_title_info(b->title_info);
b->title_info = bd_get_playlist_info(b->bd, b->current_playlist,
b->current_angle);
break;
case BD_EVENT_TITLE:
- b->next_event = reset_flags;
if (ev->param == BLURAY_TITLE_FIRST_PLAY) {
- if (b->use_nav)
- b->current_title = b->num_titles - 1;
- else
- b->current_title = bd_get_current_title(b->bd);
+ b->current_title = bd_get_current_title(b->bd);
} else
b->current_title = ev->param;
if (b->title_info) {
@@ -382,11 +168,9 @@ static void handle_event(stream_t *s, const BD_EVENT *ev)
}
break;
case BD_EVENT_POPUP:
- b->popup_enabled = ev->param;
break;
#if BLURAY_VERSION >= BLURAY_VERSION_CODE(0, 5, 0)
case BD_EVENT_DISCONTINUITY:
- b->next_event = reset_flags;
break;
#endif
default:
@@ -398,87 +182,12 @@ static void handle_event(stream_t *s, const BD_EVENT *ev)
static int bluray_stream_fill_buffer(stream_t *s, char *buf, int len)
{
struct bluray_priv_s *b = s->priv;
- assert(!b->use_nav);
BD_EVENT event;
while (bd_get_event(b->bd, &event))
handle_event(s, &event);
return bd_read(b->bd, buf, len);
}
-static int bdnav_stream_fill_buffer(stream_t *s, char *buf, int len)
-{
- struct bluray_priv_s *b = s->priv;
- assert(b->use_nav);
- BD_EVENT event;
- int read = -1;
- for (;;) {
- read = bd_read_ext(b->bd, buf, len, &event);
- if (read < 0)
- return read;
- if (read == 0) {
- if (event.event == BD_EVENT_NONE)
- return 0; // end of stream
- handle_event(s, &event);
- } else
- break;
- }
- return read;
-}
-
-static bd_vk_key_e translate_nav_menu_action(const char *cmd)
-{
- if (strcmp(cmd, "mouse") == 0)
- return BD_VK_MOUSE_ACTIVATE;
- if (strcmp(cmd, "up") == 0)
- return BD_VK_UP;
- if (strcmp(cmd, "down") == 0)
- return BD_VK_DOWN;
- if (strcmp(cmd, "left") == 0)
- return BD_VK_LEFT;
- if (strcmp(cmd, "right") == 0)
- return BD_VK_RIGHT;
- if (strcmp(cmd, "select") == 0)
- return BD_VK_ENTER;
- return BD_VK_NONE;
-}
-
-static void handle_nav_command(stream_t *s, struct mp_nav_cmd *ev)
-{
- struct bluray_priv_s *priv = s->priv;
- switch (ev->event) {
- case MP_NAV_CMD_ENABLE:
- priv->nav_enabled = true;
- break;
- case MP_NAV_CMD_MENU: {
- const int64_t pts = mp_time_us();
- const char *action = ev->u.menu.action;
- bd_vk_key_e key = translate_nav_menu_action(action);
- if (key != BD_VK_NONE) {
- if (key == BD_VK_MOUSE_ACTIVATE)
- ev->mouse_on_button = bd_mouse_select(priv->bd, pts,
- priv->mousex,
- priv->mousey);
- bd_user_input(priv->bd, pts, key);
- } else if (strcmp(action, "menu") == 0) {
- if (priv->popup_enabled)
- bd_user_input(priv->bd, pts, BD_VK_POPUP);
- else
- bd_menu_call(priv->bd, pts);
- }
- break;
- } case MP_NAV_CMD_MOUSE_POS:
- priv->mousex = ev->u.mouse_pos.x;
- priv->mousey = ev->u.mouse_pos.y;
- ev->mouse_on_button = bd_mouse_select(priv->bd, mp_time_us(),
- priv->mousex,
- priv->mousey);
- break;
- case MP_NAV_CMD_SKIP_STILL:
- bd_read_skip_still(priv->bd);
- break;
- }
-}
-
static int bluray_stream_control(stream_t *s, int cmd, void *arg)
{
struct bluray_priv_s *b = s->priv;
@@ -592,17 +301,6 @@ static int bluray_stream_control(stream_t *s, int cmd, void *arg)
*(char**)arg = talloc_strdup(NULL, meta->di_name);
return STREAM_OK;
}
- case STREAM_CTRL_NAV_CMD:
- if (!b->use_nav)
- return STREAM_UNSUPPORTED;
- handle_nav_command(s, arg);
- return STREAM_OK;
- case STREAM_CTRL_GET_NAV_EVENT: {
- struct mp_nav_event **ev = arg;
- if (ev)
- fill_next_event(s, ev);
- return STREAM_OK;
- }
case STREAM_CTRL_GET_SIZE:
*(int64_t *)arg = bd_get_title_size(b->bd);
return STREAM_OK;
@@ -670,19 +368,10 @@ static void select_initial_title(stream_t *s, int title_guess) {
struct bluray_priv_s *b = s->priv;
int title = -1;
- if (b->use_nav) {
- if (b->cfg_title == BLURAY_MENU_TITLE)
- title = 0; // BLURAY_TITLE_TOP_MENU
- else if (b->cfg_title == BLURAY_DEFAULT_TITLE)
- title = b->num_titles - 1;
- else
- title = b->cfg_title;
- } else {
- if (b->cfg_title != BLURAY_DEFAULT_TITLE )
- title = b->cfg_title;
- else
- title = title_guess;
- }
+ if (b->cfg_title != BLURAY_DEFAULT_TITLE )
+ title = b->cfg_title;
+ else
+ title = title_guess;
if (title < 0)
return;
@@ -690,29 +379,10 @@ static void select_initial_title(stream_t *s, int title_guess) {
b->current_title = title;
else {
MP_WARN(s, "Couldn't start title '%d'.\n", title);
- if (!b->use_nav) // cannot query title info in nav
- b->current_title = bd_get_current_title(b->bd);
+ b->current_title = bd_get_current_title(b->bd);
}
}
-static void select_initial_angle(stream_t *s) {
- struct bluray_priv_s *b = s->priv;
- if (!b->use_nav) // no way to figure out current title info
- return;
- BLURAY_TITLE_INFO *info = bd_get_title_info(b->bd, b->current_title, 0);
- if (!info)
- return;
- /* Select angle */
- unsigned int angle = s->opts->bluray_angle;
- if (!angle)
- angle = BLURAY_DEFAULT_ANGLE;
- angle = FFMIN(angle, info->angle_count);
- if (angle)
- bd_select_angle(b->bd, angle);
- b->current_angle = bd_get_current_angle(b->bd);
- bd_free_title_info(info);
-}
-
static int bluray_stream_open(stream_t *s)
{
struct bluray_priv_s *b = s->priv;
@@ -744,10 +414,8 @@ static int bluray_stream_open(stream_t *s)
int title_guess = BLURAY_DEFAULT_TITLE;
if (b->use_nav) {
- const BLURAY_DISC_INFO *disc_info = bd_get_disc_info(b->bd);
- b->num_titles = disc_info->num_hdmv_titles + disc_info->num_bdj_titles;
- ++b->num_titles; // for BLURAY_TITLE_TOP_MENU
- ++b->num_titles; // for BLURAY_TITLE_FIRST_PLAY
+ MP_FATAL(s, "BluRay menu support has been removed.\n");
+ return STREAM_ERROR;
} else {
/* check for available titles on disc */
b->num_titles = bd_get_titles(bd, TITLES_RELEVANT, 0);
@@ -775,28 +443,15 @@ static int bluray_stream_open(stream_t *s)
}
// these should be set before any callback
- b->pool = mp_image_pool_new(6);
b->current_angle = -1;
b->current_title = -1;
// initialize libbluray event queue
bd_get_event(bd, NULL);
- if (b->use_nav) {
- if (!bd_play(bd)) {
- destruct(b);
- return STREAM_ERROR;
- }
- bd_register_overlay_proc(bd, s, overlay_process);
- }
-
select_initial_title(s, title_guess);
- select_initial_angle(s);
- if (b->use_nav)
- s->fill_buffer = bdnav_stream_fill_buffer;
- else
- s->fill_buffer = bluray_stream_fill_buffer;
+ s->fill_buffer = bluray_stream_fill_buffer;
s->close = bluray_stream_close;
s->control = bluray_stream_control;
s->type = STREAMTYPE_BLURAY;
@@ -891,9 +546,9 @@ static int bdmv_dir_stream_open(stream_t *stream)
// directory containing MovieObject.bdmv, or that file itself.
if (!check_bdmv(path)) {
// On UNIX, just assume the filename has always this case.
- char *npath = mp_path_join(priv, bstr0(path), bstr0("MovieObject.bdmv"));
+ char *npath = mp_path_join(priv, path, "MovieObject.bdmv");
if (!check_bdmv(npath)) {
- npath = mp_path_join(priv, bstr0(path), bstr0("BDMV/MovieObject.bdmv"));
+ npath = mp_path_join(priv, path, "BDMV/MovieObject.bdmv");
if (!check_bdmv(npath))
goto unsupported;
}
diff --git a/stream/stream_dvdnav.c b/stream/stream_dvdnav.c
index 095ba98..e2562db 100644
--- a/stream/stream_dvdnav.c
+++ b/stream/stream_dvdnav.c
@@ -39,7 +39,6 @@
#include "osdep/timer.h"
#include "stream.h"
#include "demux/demux.h"
-#include "discnav.h"
#include "video/out/vo.h"
#include "stream_dvd_common.h"
@@ -54,11 +53,6 @@ struct priv {
int title;
uint32_t spu_clut[16];
bool spu_clut_valid;
- dvdnav_highlight_event_t hlev;
- int still_length; // still frame duration
- unsigned long next_event; // bitmask of events to return to player
- bool suspended_read;
- bool nav_enabled;
bool had_initial_vts;
int dvd_speed;
@@ -97,126 +91,9 @@ static const char *const mp_dvdnav_events[] = {
DNE(DVDNAV_WAIT),
};
-static const char *const mp_nav_cmd_types[] = {
- DNE(MP_NAV_CMD_NONE),
- DNE(MP_NAV_CMD_ENABLE),
- DNE(MP_NAV_CMD_DRAIN_OK),
- DNE(MP_NAV_CMD_RESUME),
- DNE(MP_NAV_CMD_SKIP_STILL),
- DNE(MP_NAV_CMD_MENU),
- DNE(MP_NAV_CMD_MOUSE_POS),
-};
-
-static const char *const mp_nav_event_types[] = {
- DNE(MP_NAV_EVENT_NONE),
- DNE(MP_NAV_EVENT_RESET),
- DNE(MP_NAV_EVENT_RESET_CLUT),
- DNE(MP_NAV_EVENT_RESET_ALL),
- DNE(MP_NAV_EVENT_DRAIN),
- DNE(MP_NAV_EVENT_STILL_FRAME),
- DNE(MP_NAV_EVENT_HIGHLIGHT),
- DNE(MP_NAV_EVENT_MENU_MODE),
- DNE(MP_NAV_EVENT_EOF),
-};
-
#define LOOKUP_NAME(array, i) \
(((i) >= 0 && (i) < MP_ARRAY_SIZE(array)) ? array[(i)] : "?")
-static void dvdnav_get_highlight(struct priv *priv, int display_mode)
-{
- pci_t *pnavpci = NULL;
- dvdnav_highlight_event_t *hlev = &(priv->hlev);
- int btnum;
-
- if (!priv || !priv->dvdnav)
- return;
-
- pnavpci = dvdnav_get_current_nav_pci(priv->dvdnav);
- if (!pnavpci) {
- hlev->display = 0;
- return;
- }
-
- dvdnav_get_current_highlight(priv->dvdnav, &(hlev->buttonN));
- hlev->display = display_mode; /* show */
-
- if (hlev->buttonN > 0 && pnavpci->hli.hl_gi.btn_ns > 0 && hlev->display) {
- for (btnum = 0; btnum < pnavpci->hli.hl_gi.btn_ns; btnum++) {
- btni_t *btni = &(pnavpci->hli.btnit[btnum]);
-
- if (hlev->buttonN == btnum + 1) {
- hlev->sx = FFMIN(btni->x_start, btni->x_end);
- hlev->ex = FFMAX(btni->x_start, btni->x_end);
- hlev->sy = FFMIN(btni->y_start, btni->y_end);
- hlev->ey = FFMAX(btni->y_start, btni->y_end);
-
- hlev->palette = (btni->btn_coln == 0) ?
- 0 : pnavpci->hli.btn_colit.btn_coli[btni->btn_coln - 1][0];
- break;
- }
- }
- } else { /* hide button or no button */
- hlev->sx = hlev->ex = 0;
- hlev->sy = hlev->ey = 0;
- hlev->palette = hlev->buttonN = 0;
- }
-}
-
-static void handle_menu_input(stream_t *stream, const char *cmd)
-{
- struct priv *priv = stream->priv;
- dvdnav_t *nav = priv->dvdnav;
- dvdnav_status_t status = DVDNAV_STATUS_ERR;
- pci_t *pci = dvdnav_get_current_nav_pci(nav);
-
- MP_VERBOSE(stream, "DVDNAV: input '%s'\n", cmd);
-
- if (!pci)
- return;
-
- if (strcmp(cmd, "up") == 0) {
- status = dvdnav_upper_button_select(nav, pci);
- } else if (strcmp(cmd, "down") == 0) {
- status = dvdnav_lower_button_select(nav, pci);
- } else if (strcmp(cmd, "left") == 0) {
- status = dvdnav_left_button_select(nav, pci);
- } else if (strcmp(cmd, "right") == 0) {
- status = dvdnav_right_button_select(nav, pci);
- } else if (strcmp(cmd, "menu") == 0) {
- status = dvdnav_menu_call(nav, DVD_MENU_Root);
- } else if (strcmp(cmd, "prev") == 0) {
- int title = 0, part = 0;
- dvdnav_current_title_info(nav, &title, &part);
- if (title)
- status = dvdnav_menu_call(nav, DVD_MENU_Part);
- if (status != DVDNAV_STATUS_OK)
- status = dvdnav_menu_call(nav, DVD_MENU_Title);
- if (status != DVDNAV_STATUS_OK)
- status = dvdnav_menu_call(nav, DVD_MENU_Root);
- } else if (strcmp(cmd, "select") == 0) {
- status = dvdnav_button_activate(nav, pci);
- } else if (strcmp(cmd, "mouse") == 0) {
- status = dvdnav_mouse_activate(nav, pci, priv->mousex, priv->mousey);
- } else {
- MP_VERBOSE(stream, "Unknown DVDNAV command: '%s'\n", cmd);
- }
-}
-
-static dvdnav_status_t handle_mouse_pos(stream_t *stream, int x, int y)
-{
- struct priv *priv = stream->priv;
- dvdnav_t *nav = priv->dvdnav;
- pci_t *pci = dvdnav_get_current_nav_pci(nav);
-
- if (!pci)
- return DVDNAV_STATUS_ERR;
-
- dvdnav_status_t status = dvdnav_mouse_select(nav, pci, x, y);
- priv->mousex = x;
- priv->mousey = y;
- return status;
-}
-
/**
* \brief mp_dvdnav_lang_from_aid() returns the language corresponding to audio id 'aid'
* \param stream: - stream pointer
@@ -284,77 +161,6 @@ static int mp_dvdnav_number_of_subs(stream_t *stream)
return n;
}
-static void handle_cmd(stream_t *s, struct mp_nav_cmd *ev)
-{
- struct priv *priv = s->priv;
- MP_VERBOSE(s, "DVDNAV: input '%s'\n",
- LOOKUP_NAME(mp_nav_cmd_types, ev->event));
- switch (ev->event) {
- case MP_NAV_CMD_ENABLE:
- priv->nav_enabled = true;
- break;
- case MP_NAV_CMD_DRAIN_OK:
- dvdnav_wait_skip(priv->dvdnav);
- break;
- case MP_NAV_CMD_RESUME:
- priv->suspended_read = false;
- break;
- case MP_NAV_CMD_SKIP_STILL:
- dvdnav_still_skip(priv->dvdnav);
- break;
- case MP_NAV_CMD_MENU:
- handle_menu_input(s, ev->u.menu.action);
- break;
- case MP_NAV_CMD_MOUSE_POS:
- ev->mouse_on_button = handle_mouse_pos(s, ev->u.mouse_pos.x, ev->u.mouse_pos.y);
- break;
- }
-
-}
-
-static inline bool set_event_type(struct priv *priv, int type,
- struct mp_nav_event *event)
-{
- if (!(priv->next_event & (1 << type)))
- return false;
- priv->next_event &= ~(1 << type);
- event->event = type;
- return true;
-}
-
-static void fill_next_event(stream_t *s, struct mp_nav_event **ret)
-{
- struct priv *priv = s->priv;
- struct mp_nav_event e = {0};
- if (!set_event_type(priv, MP_NAV_EVENT_RESET_ALL, &e))
- for (int n = 0; n < 30 && !set_event_type(priv, n, &e); n++) ;
- switch (e.event) {
- case MP_NAV_EVENT_NONE:
- return;
- case MP_NAV_EVENT_HIGHLIGHT: {
- dvdnav_highlight_event_t hlev = priv->hlev;
- e.u.highlight.display = hlev.display;
- e.u.highlight.sx = hlev.sx;
- e.u.highlight.sy = hlev.sy;
- e.u.highlight.ex = hlev.ex;
- e.u.highlight.ey = hlev.ey;
- e.u.highlight.palette = hlev.palette;
- break;
- }
- case MP_NAV_EVENT_MENU_MODE:
- e.u.menu_mode.enable = !dvdnav_is_domain_vts(priv->dvdnav);
- break;
- case MP_NAV_EVENT_STILL_FRAME:
- e.u.still_frame.seconds = priv->still_length;
- break;
- }
- *ret = talloc(NULL, struct mp_nav_event);
- **ret = e;
-
- MP_VERBOSE(s, "DVDNAV: player event '%s'\n",
- LOOKUP_NAME(mp_nav_event_types, e.event));
-}
-
static int fill_buffer(stream_t *s, char *buf, int max_len)
{
struct priv *priv = s->priv;
@@ -364,9 +170,6 @@ static int fill_buffer(stream_t *s, char *buf, int max_len)
return -1;
while (1) {
- if (priv->suspended_read)
- return -1;
-
int len = -1;
int event = DVDNAV_NOP;
if (dvdnav_get_next_block(dvdnav, buf, &event, &len) != DVDNAV_STATUS_OK)
@@ -378,52 +181,26 @@ static int fill_buffer(stream_t *s, char *buf, int max_len)
if (event != DVDNAV_BLOCK_OK) {
const char *name = LOOKUP_NAME(mp_dvdnav_events, event);
MP_VERBOSE(s, "DVDNAV: event %s (%d).\n", name, event);
- dvdnav_get_highlight(priv, 1);
}
switch (event) {
case DVDNAV_BLOCK_OK:
return len;
- case DVDNAV_STOP: {
- priv->next_event |= 1 << MP_NAV_EVENT_EOF;
+ case DVDNAV_STOP:
return 0;
- }
case DVDNAV_NAV_PACKET: {
pci_t *pnavpci = dvdnav_get_current_nav_pci(dvdnav);
uint32_t start_pts = pnavpci->pci_gi.vobu_s_ptm;
MP_TRACE(s, "start pts = %"PRIu32"\n", start_pts);
break;
}
- case DVDNAV_STILL_FRAME: {
- dvdnav_still_event_t *still_event = (dvdnav_still_event_t *) buf;
- priv->still_length = still_event->length;
- if (priv->still_length == 255)
- priv->still_length = -1;
- MP_VERBOSE(s, "len=%d\n", priv->still_length);
- /* set still frame duration */
- if (priv->still_length <= 1) {
- pci_t *pnavpci = dvdnav_get_current_nav_pci(dvdnav);
- priv->duration = mp_dvdtimetomsec(&pnavpci->pci_gi.e_eltm);
- }
- if (priv->nav_enabled) {
- priv->next_event |= 1 << MP_NAV_EVENT_STILL_FRAME;
- } else {
- dvdnav_still_skip(dvdnav);
- }
+ case DVDNAV_STILL_FRAME:
+ dvdnav_still_skip(dvdnav);
return 0;
- }
- case DVDNAV_WAIT: {
- if (priv->nav_enabled) {
- priv->next_event |= 1 << MP_NAV_EVENT_DRAIN;
- } else {
- dvdnav_wait_skip(dvdnav);
- }
+ case DVDNAV_WAIT:
+ dvdnav_wait_skip(dvdnav);
return 0;
- }
- case DVDNAV_HIGHLIGHT: {
- dvdnav_get_highlight(priv, 1);
- priv->next_event |= 1 << MP_NAV_EVENT_HIGHLIGHT;
+ case DVDNAV_HIGHLIGHT:
break;
- }
case DVDNAV_VTS_CHANGE: {
int tit = 0, part = 0;
dvdnav_vts_change_event_t *vts_event =
@@ -437,38 +214,25 @@ static int fill_buffer(stream_t *s, char *buf, int max_len)
priv->had_initial_vts = true;
break;
}
- // clear all previous events
- priv->next_event = 0;
- priv->next_event |= 1 << MP_NAV_EVENT_RESET;
- priv->next_event |= 1 << MP_NAV_EVENT_RESET_ALL;
if (dvdnav_current_title_info(dvdnav, &tit, &part) == DVDNAV_STATUS_OK)
{
MP_VERBOSE(s, "DVDNAV, NEW TITLE %d\n", tit);
- dvdnav_get_highlight(priv, 0);
- if (priv->title > 0 && tit != priv->title) {
- priv->next_event |= 1 << MP_NAV_EVENT_EOF;;
+ if (priv->title > 0 && tit != priv->title)
MP_WARN(s, "Requested title not found\n");
- }
}
- if (priv->nav_enabled)
- priv->suspended_read = true;
break;
}
case DVDNAV_CELL_CHANGE: {
dvdnav_cell_change_event_t *ev = (dvdnav_cell_change_event_t *)buf;
- priv->next_event |= 1 << MP_NAV_EVENT_RESET;
- priv->next_event |= 1 << MP_NAV_EVENT_MENU_MODE;
if (ev->pgc_length)
priv->duration = ev->pgc_length / 90;
- dvdnav_get_highlight(priv, 1);
break;
}
case DVDNAV_SPU_CLUT_CHANGE: {
memcpy(priv->spu_clut, buf, 16 * sizeof(uint32_t));
priv->spu_clut_valid = true;
- priv->next_event |= 1 << MP_NAV_EVENT_RESET_CLUT;
break;
}
}
@@ -666,16 +430,6 @@ static int control(stream_t *stream, int cmd, void *arg)
memcpy(req->palette, priv->spu_clut, sizeof(req->palette));
return STREAM_OK;
}
- case STREAM_CTRL_GET_NAV_EVENT: {
- struct mp_nav_event **ev = arg;
- if (ev)
- fill_next_event(stream, ev);
- return STREAM_OK;
- }
- case STREAM_CTRL_NAV_CMD: {
- handle_cmd(stream, (struct mp_nav_cmd *)arg);
- return STREAM_OK;
- }
case STREAM_CTRL_GET_DISC_NAME: {
const char *volume = NULL;
if (dvdnav_get_title_string(dvdnav, &volume) != DVDNAV_STATUS_OK)
@@ -781,8 +535,8 @@ static int open_s(stream_t *stream)
return STREAM_UNSUPPORTED;
}
} else {
- if (dvdnav_menu_call(priv->dvdnav, DVD_MENU_Root) != DVDNAV_STATUS_OK)
- dvdnav_menu_call(priv->dvdnav, DVD_MENU_Title);
+ MP_FATAL(stream, "DVD menu support has been removed.\n");
+ return STREAM_ERROR;
}
if (stream->opts->dvd_angle > 1)
dvdnav_angle_change(priv->dvdnav, stream->opts->dvd_angle);
@@ -835,9 +589,9 @@ static int ifo_dvdnav_stream_open(stream_t *stream)
// directory containing VIDEO_TS.IFO, or that file itself.
if (!check_ifo(path)) {
// On UNIX, just assume the filename is always uppercase.
- char *npath = mp_path_join(priv, bstr0(path), bstr0("VIDEO_TS.IFO"));
+ char *npath = mp_path_join(priv, path, "VIDEO_TS.IFO");
if (!check_ifo(npath)) {
- npath = mp_path_join(priv, bstr0(path), bstr0("VIDEO_TS/VIDEO_TS.IFO"));
+ npath = mp_path_join(priv, path, "VIDEO_TS/VIDEO_TS.IFO");
if (!check_ifo(npath))
goto unsupported;
}
diff --git a/stream/stream_file.c b/stream/stream_file.c
index d0da862..527261e 100644
--- a/stream/stream_file.c
+++ b/stream/stream_file.c
@@ -229,12 +229,11 @@ static bool check_stream_network(int fd)
static int open_f(stream_t *stream)
{
- int fd;
- struct priv *priv = talloc_ptrtype(stream, priv);
- *priv = (struct priv) {
+ struct priv *p = talloc_ptrtype(stream, p);
+ *p = (struct priv) {
.fd = -1
};
- stream->priv = priv;
+ stream->priv = p;
stream->type = STREAMTYPE_FILE;
bool write = stream->mode == STREAM_WRITE;
@@ -247,19 +246,26 @@ static int open_f(stream_t *stream)
filename = stream->path;
}
- if (!strcmp(filename, "-")) {
+ if (strncmp(stream->url, "fd://", 5) == 0) {
+ char *end = NULL;
+ p->fd = strtol(stream->url + 5, &end, 0);
+ if (!end || end == stream->url + 5 || end[0]) {
+ MP_ERR(stream, "Invalid FD: %s\n", stream->url);
+ return STREAM_ERROR;
+ }
+ p->close = false;
+ } else if (!strcmp(filename, "-")) {
if (!write) {
MP_INFO(stream, "Reading from stdin...\n");
- fd = 0;
+ p->fd = 0;
} else {
MP_INFO(stream, "Writing to stdout...\n");
- fd = 1;
+ p->fd = 1;
}
#ifdef __MINGW32__
- setmode(fd, O_BINARY);
+ setmode(p->fd, O_BINARY);
#endif
- priv->fd = fd;
- priv->close = false;
+ p->close = false;
} else {
mode_t openmode = S_IRUSR | S_IWUSR;
#ifndef __MINGW32__
@@ -267,14 +273,14 @@ static int open_f(stream_t *stream)
if (!write)
m |= O_NONBLOCK;
#endif
- fd = open(filename, m | O_BINARY, openmode);
- if (fd < 0) {
+ p->fd = open(filename, m | O_BINARY, openmode);
+ if (p->fd < 0) {
MP_ERR(stream, "Cannot open file '%s': %s\n",
- filename, mp_strerror(errno));
+ filename, mp_strerror(errno));
return STREAM_ERROR;
}
struct stat st;
- if (fstat(fd, &st) == 0) {
+ if (fstat(p->fd, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
stream->type = STREAMTYPE_DIR;
stream->allow_caching = false;
@@ -282,19 +288,18 @@ static int open_f(stream_t *stream)
}
#ifndef __MINGW32__
if (S_ISREG(st.st_mode)) {
- priv->regular = true;
+ p->regular = true;
// O_NONBLOCK has weird semantics on file locks; remove it.
- int val = fcntl(fd, F_GETFL) & ~(unsigned)O_NONBLOCK;
- fcntl(fd, F_SETFL, val);
+ int val = fcntl(p->fd, F_GETFL) & ~(unsigned)O_NONBLOCK;
+ fcntl(p->fd, F_SETFL, val);
}
#endif
}
- priv->fd = fd;
- priv->close = true;
+ p->close = true;
}
- off_t len = lseek(fd, 0, SEEK_END);
- lseek(fd, 0, SEEK_SET);
+ off_t len = lseek(p->fd, 0, SEEK_END);
+ lseek(p->fd, 0, SEEK_SET);
if (len != (off_t)-1) {
stream->seek = seek;
stream->seekable = true;
@@ -307,7 +312,7 @@ static int open_f(stream_t *stream)
stream->read_chunk = 64 * 1024;
stream->close = s_close;
- if (check_stream_network(fd))
+ if (check_stream_network(p->fd))
stream->streaming = true;
return STREAM_OK;
@@ -316,7 +321,7 @@ static int open_f(stream_t *stream)
const stream_info_t stream_info_file = {
.name = "file",
.open = open_f,
- .protocols = (const char*const[]){ "file", "", NULL },
+ .protocols = (const char*const[]){ "file", "", "fd", NULL },
.can_write = true,
.is_safe = true,
};
diff --git a/stream/stream_libarchive.c b/stream/stream_libarchive.c
new file mode 100644
index 0000000..116488d
--- /dev/null
+++ b/stream/stream_libarchive.c
@@ -0,0 +1,271 @@
+/*
+ * 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 <archive.h>
+#include <archive_entry.h>
+
+#include "common/common.h"
+#include "stream.h"
+
+#include "stream_libarchive.h"
+
+static ssize_t read_cb(struct archive *arch, void *priv, const void **buffer)
+{
+ struct mp_archive *mpa = priv;
+ int res = stream_read_partial(mpa->src, mpa->buffer, sizeof(mpa->buffer));
+ *buffer = mpa->buffer;
+ return MPMAX(res, 0);
+}
+
+static int64_t seek_cb(struct archive *arch, void *priv,
+ int64_t offset, int whence)
+{
+ struct mp_archive *mpa = priv;
+ switch (whence) {
+ case SEEK_SET:
+ break;
+ case SEEK_CUR:
+ offset += mpa->src->pos;
+ break;
+ case SEEK_END: ;
+ int64_t size = stream_get_size(mpa->src);
+ if (size < 0)
+ return -1;
+ offset += size;
+ break;
+ default:
+ return -1;
+ }
+ return stream_seek(mpa->src, offset) ? offset : -1;
+}
+
+static int64_t skip_cb(struct archive *arch, void *priv, int64_t request)
+{
+ struct mp_archive *mpa = priv;
+ int64_t old = stream_tell(mpa->src);
+ stream_skip(mpa->src, request);
+ return stream_tell(mpa->src) - old;
+}
+
+void mp_archive_free(struct mp_archive *mpa)
+{
+ if (mpa && mpa->arch) {
+ archive_read_close(mpa->arch);
+ archive_read_free(mpa->arch);
+ }
+ talloc_free(mpa);
+}
+
+struct mp_archive *mp_archive_new(struct mp_log *log, struct stream *src,
+ int flags)
+{
+ struct mp_archive *mpa = talloc_zero(NULL, struct mp_archive);
+ mpa->src = src;
+ stream_seek(mpa->src, 0);
+ mpa->arch = archive_read_new();
+ if (!mpa->arch)
+ goto err;
+
+ archive_read_support_format_7zip(mpa->arch);
+ archive_read_support_format_iso9660(mpa->arch);
+ archive_read_support_format_rar(mpa->arch);
+ archive_read_support_format_zip(mpa->arch);
+ archive_read_support_filter_bzip2(mpa->arch);
+ archive_read_support_filter_gzip(mpa->arch);
+ archive_read_support_filter_xz(mpa->arch);
+ if (flags & MP_ARCHIVE_FLAG_UNSAFE) {
+ archive_read_support_format_gnutar(mpa->arch);
+ archive_read_support_format_tar(mpa->arch);
+ }
+
+ archive_read_set_callback_data(mpa->arch, mpa);
+ archive_read_set_read_callback(mpa->arch, read_cb);
+ archive_read_set_skip_callback(mpa->arch, skip_cb);
+ if (mpa->src->seekable)
+ archive_read_set_seek_callback(mpa->arch, seek_cb);
+ if (archive_read_open1(mpa->arch) < ARCHIVE_OK)
+ goto err;
+ return mpa;
+
+err:
+ mp_archive_free(mpa);
+ return NULL;
+}
+
+struct priv {
+ struct mp_archive *mpa;
+ struct stream *src;
+ int64_t entry_size;
+ char *entry_name;
+};
+
+static int reopen_archive(stream_t *s)
+{
+ struct priv *p = s->priv;
+ mp_archive_free(p->mpa);
+ p->mpa = mp_archive_new(s->log, p->src, MP_ARCHIVE_FLAG_UNSAFE);
+ if (!p->mpa)
+ return STREAM_ERROR;
+
+ // Follows the same logic as demux_libarchive.c.
+ struct mp_archive *mpa = p->mpa;
+ int num_files = 0;
+ for (;;) {
+ struct archive_entry *entry;
+ int r = archive_read_next_header(mpa->arch, &entry);
+ if (r == ARCHIVE_EOF) {
+ MP_ERR(s, "archive entry not found. '%s'\n", p->entry_name);
+ goto error;
+ }
+ if (r < ARCHIVE_OK)
+ MP_ERR(s, "libarchive: %s\n", archive_error_string(mpa->arch));
+ if (r < ARCHIVE_WARN)
+ goto error;
+ if (archive_entry_filetype(entry) != AE_IFREG)
+ continue;
+ const char *fn = archive_entry_pathname(entry);
+ char buf[64];
+ if (!fn) {
+ snprintf(buf, sizeof(buf), "mpv_unknown#%d\n", num_files);
+ fn = buf;
+ }
+ if (strcmp(p->entry_name, fn) == 0) {
+ p->entry_size = -1;
+ if (archive_entry_size_is_set(entry))
+ p->entry_size = archive_entry_size(entry);
+ return STREAM_OK;
+ }
+ num_files++;
+ }
+
+error:
+ mp_archive_free(p->mpa);
+ p->mpa = NULL;
+ MP_ERR(s, "could not open archive\n");
+ return STREAM_ERROR;
+}
+
+static int archive_entry_fill_buffer(stream_t *s, char *buffer, int max_len)
+{
+ struct priv *p = s->priv;
+ if (!p->mpa)
+ return 0;
+ int r = archive_read_data(p->mpa->arch, buffer, max_len);
+ if (r < 0)
+ MP_ERR(s, "libarchive: %s\n", archive_error_string(p->mpa->arch));
+ return r;
+}
+
+static int archive_entry_seek(stream_t *s, int64_t newpos)
+{
+ struct priv *p = s->priv;
+ if (!p->mpa)
+ return -1;
+ if (archive_seek_data(p->mpa->arch, newpos, SEEK_SET) >= 0)
+ return 1;
+ // libarchive can't seek in most formats.
+ if (newpos < s->pos) {
+ // Hack seeking backwards into working by reopening the archive and
+ // starting over.
+ MP_VERBOSE(s, "trying to reopen archive for performing seek\n");
+ if (reopen_archive(s) < STREAM_OK)
+ return -1;
+ s->pos = 0;
+ }
+ if (newpos > s->pos) {
+ // For seeking forwards, just keep reading data (there's no libarchive
+ // skip function either).
+ char buffer[4096];
+ while (newpos > s->pos) {
+ int size = MPMIN(newpos - s->pos, sizeof(buffer));
+ int r = archive_read_data(p->mpa->arch, buffer, size);
+ if (r < 0) {
+ MP_ERR(s, "libarchive: %s\n", archive_error_string(p->mpa->arch));
+ return -1;
+ }
+ s->pos += r;
+ }
+ }
+ return 1;
+}
+
+static void archive_entry_close(stream_t *s)
+{
+ struct priv *p = s->priv;
+ mp_archive_free(p->mpa);
+ free_stream(p->src);
+}
+
+static int archive_entry_control(stream_t *s, int cmd, void *arg)
+{
+ struct priv *p = s->priv;
+ switch (cmd) {
+ case STREAM_CTRL_GET_BASE_FILENAME:
+ *(char **)arg = talloc_strdup(NULL, p->src->url);
+ return STREAM_OK;
+ case STREAM_CTRL_GET_SIZE:
+ if (p->entry_size < 0)
+ break;
+ *(int64_t *)arg = p->entry_size;
+ return STREAM_OK;
+ }
+ return STREAM_UNSUPPORTED;
+}
+
+static int archive_entry_open(stream_t *stream)
+{
+ struct priv *p = talloc_zero(stream, struct priv);
+ stream->priv = p;
+
+ if (!strchr(stream->path, '|'))
+ return STREAM_ERROR;
+
+ char *base = talloc_strdup(p, stream->path);
+ char *name = strchr(base, '|');
+ *name++ = '\0';
+ p->entry_name = name;
+ mp_url_unescape_inplace(base);
+
+ p->src = stream_create(base, STREAM_READ | STREAM_SAFE_ONLY,
+ stream->cancel, stream->global);
+ if (!p->src) {
+ archive_entry_close(stream);
+ return STREAM_ERROR;
+ }
+
+ int r = reopen_archive(stream);
+ if (r < STREAM_OK) {
+ archive_entry_close(stream);
+ return r;
+ }
+
+ stream->fill_buffer = archive_entry_fill_buffer;
+ if (p->src->seekable) {
+ stream->seek = archive_entry_seek;
+ stream->seekable = true;
+ }
+ stream->close = archive_entry_close;
+ stream->control = archive_entry_control;
+
+ return STREAM_OK;
+}
+
+const stream_info_t stream_info_libarchive = {
+ .name = "libarchive",
+ .open = archive_entry_open,
+ .protocols = (const char*const[]){ "archive", NULL },
+};
diff --git a/stream/stream_libarchive.h b/stream/stream_libarchive.h
new file mode 100644
index 0000000..f69faad
--- /dev/null
+++ b/stream/stream_libarchive.h
@@ -0,0 +1,13 @@
+struct mp_log;
+
+struct mp_archive {
+ struct archive *arch;
+ struct stream *src;
+ char buffer[4096];
+};
+
+void mp_archive_free(struct mp_archive *mpa);
+
+#define MP_ARCHIVE_FLAG_UNSAFE 1
+struct mp_archive *mp_archive_new(struct mp_log *log, struct stream *src,
+ int flags);
diff --git a/sub/dec_sub.c b/sub/dec_sub.c
index bfb2c04..20fa9ef 100644
--- a/sub/dec_sub.c
+++ b/sub/dec_sub.c
@@ -156,11 +156,13 @@ void sub_set_extradata(struct dec_sub *sub, void *data, int data_len)
}
void sub_set_ass_renderer(struct dec_sub *sub, struct ass_library *ass_library,
- struct ass_renderer *ass_renderer)
+ struct ass_renderer *ass_renderer,
+ pthread_mutex_t *ass_lock)
{
pthread_mutex_lock(&sub->lock);
sub->init_sd.ass_library = ass_library;
sub->init_sd.ass_renderer = ass_renderer;
+ sub->init_sd.ass_lock = ass_lock;
pthread_mutex_unlock(&sub->lock);
}
@@ -202,8 +204,8 @@ void sub_init_from_sh(struct dec_sub *sub, struct sh_stream *sh)
pthread_mutex_lock(&sub->lock);
- if (sh->sub->extradata && !sub->init_sd.extradata)
- sub_set_extradata(sub, sh->sub->extradata, sh->sub->extradata_len);
+ if (sh->extradata && !sub->init_sd.extradata)
+ sub_set_extradata(sub, sh->extradata, sh->extradata_size);
struct sd init_sd = sub->init_sd;
init_sd.codec = sh->codec;
@@ -230,6 +232,7 @@ void sub_init_from_sh(struct dec_sub *sub, struct sh_stream *sh)
.extradata_len = sd->output_extradata_len,
.ass_library = sub->init_sd.ass_library,
.ass_renderer = sub->init_sd.ass_renderer,
+ .ass_lock = sub->init_sd.ass_lock,
};
}
@@ -300,8 +303,8 @@ void sub_decode(struct dec_sub *sub, struct demux_packet *packet)
pthread_mutex_unlock(&sub->lock);
}
-static const char *guess_sub_cp(struct mp_log *log, struct packet_list *subs,
- const char *usercp)
+static const char *guess_sub_cp(struct mp_log *log, void *talloc_ctx,
+ struct packet_list *subs, const char *usercp)
{
if (!mp_charset_requires_guess(usercp))
return usercp;
@@ -327,7 +330,7 @@ static const char *guess_sub_cp(struct mp_log *log, struct packet_list *subs,
memcpy(text.start + text.len + pkt->len, sep, sep_len);
text.len += pkt->len + sep_len;
}
- const char *guess = mp_charset_guess(log, text, usercp, 0);
+ const char *guess = mp_charset_guess(talloc_ctx, log, text, usercp, 0);
talloc_free(text.start);
return guess;
}
@@ -452,7 +455,7 @@ bool sub_read_all_packets(struct dec_sub *sub, struct sh_stream *sh)
}
if (opts->sub_cp && !sh->sub->is_utf8)
- sub->charset = guess_sub_cp(sub->log, subs, opts->sub_cp);
+ sub->charset = guess_sub_cp(sub->log, sub, subs, opts->sub_cp);
if (sub->charset && sub->charset[0] && !mp_charset_is_utf8(sub->charset))
MP_INFO(sub, "Using subtitle charset: %s\n", sub->charset);
diff --git a/sub/dec_sub.h b/sub/dec_sub.h
index 495ac46..ddec46e 100644
--- a/sub/dec_sub.h
+++ b/sub/dec_sub.h
@@ -3,6 +3,7 @@
#include <stdbool.h>
#include <stdint.h>
+#include <pthread.h>
#include "osd.h"
@@ -31,7 +32,8 @@ void sub_set_video_res(struct dec_sub *sub, int w, int h);
void sub_set_video_fps(struct dec_sub *sub, double fps);
void sub_set_extradata(struct dec_sub *sub, void *data, int data_len);
void sub_set_ass_renderer(struct dec_sub *sub, struct ass_library *ass_library,
- struct ass_renderer *ass_renderer);
+ struct ass_renderer *ass_renderer,
+ pthread_mutex_t *ass_lock);
void sub_init_from_sh(struct dec_sub *sub, struct sh_stream *sh);
bool sub_is_initialized(struct dec_sub *sub);
diff --git a/sub/find_subfiles.c b/sub/find_subfiles.c
index 2bd1c7d..aa0a181 100644
--- a/sub/find_subfiles.c
+++ b/sub/find_subfiles.c
@@ -142,11 +142,11 @@ static void append_dir_subtitles(struct mpv_global *global,
int fuzz = -1;
switch (type) {
case STREAM_SUB:
- langs = opts->sub_lang;
+ langs = opts->stream_lang[type];
fuzz = opts->sub_auto;
break;
case STREAM_AUDIO:
- langs = opts->audio_lang;
+ langs = opts->stream_lang[type];
fuzz = opts->audiofile_auto;
break;
}
@@ -188,7 +188,7 @@ static void append_dir_subtitles(struct mpv_global *global,
if (prio) {
prio += prio;
- char *subpath = mp_path_join(*slist, path, dename);
+ char *subpath = mp_path_join_bstr(*slist, path, dename);
if (mp_path_exists(subpath)) {
MP_GROW_ARRAY(*slist, *nsub);
struct subfn *sub = *slist + (*nsub)++;
@@ -256,8 +256,8 @@ struct subfn *find_external_files(struct mpv_global *global, const char *fname)
// Load subtitles in dirs specified by sub-paths option
if (opts->sub_paths) {
for (int i = 0; opts->sub_paths[i]; i++) {
- char *path = mp_path_join(slist, mp_dirname(fname),
- bstr0(opts->sub_paths[i]));
+ char *path = mp_path_join_bstr(slist, mp_dirname(fname),
+ bstr0(opts->sub_paths[i]));
append_dir_subtitles(global, &slist, &n, bstr0(path), fname, 0);
}
}
diff --git a/sub/osd.c b/sub/osd.c
index 992806e..fcfca2a 100644
--- a/sub/osd.c
+++ b/sub/osd.c
@@ -224,14 +224,6 @@ void osd_set_external2(struct osd_state *osd, struct sub_bitmaps *imgs)
pthread_mutex_unlock(&osd->lock);
}
-void osd_set_nav_highlight(struct osd_state *osd, void *priv)
-{
- pthread_mutex_lock(&osd->lock);
- osd->objs[OSDTYPE_NAV_HIGHLIGHT]->highlight_priv = priv;
- osd_changed_unlocked(osd, OSDTYPE_NAV_HIGHLIGHT);
- pthread_mutex_unlock(&osd->lock);
-}
-
static void render_object(struct osd_state *osd, struct osd_object *obj,
struct mp_osd_res res, double video_pts,
const bool sub_formats[SUBBITMAP_COUNT],
@@ -265,9 +257,6 @@ static void render_object(struct osd_state *osd, struct osd_object *obj,
*out_imgs = *obj->external2;
obj->external2->change_id = 0;
}
- } else if (obj->type == OSDTYPE_NAV_HIGHLIGHT) {
- if (obj->highlight_priv)
- mp_nav_get_highlight(obj->highlight_priv, obj->vo_res, out_imgs);
} else {
osd_object_get_bitmaps(osd, obj, out_imgs);
}
diff --git a/sub/osd.h b/sub/osd.h
index 3f23e69..4a341e9 100644
--- a/sub/osd.h
+++ b/sub/osd.h
@@ -82,8 +82,6 @@ enum mp_osdtype {
OSDTYPE_SUB,
OSDTYPE_SUB2,
- OSDTYPE_NAV_HIGHLIGHT, // dvdnav fake highlights
-
OSDTYPE_PROGBAR,
OSDTYPE_OSD,
@@ -175,8 +173,6 @@ void osd_set_external(struct osd_state *osd, int res_x, int res_y, char *text);
void osd_set_external2(struct osd_state *osd, struct sub_bitmaps *imgs);
-void osd_set_nav_highlight(struct osd_state *osd, void *priv);
-
enum mp_osd_draw_flags {
OSD_DRAW_SUB_FILTER = (1 << 0),
OSD_DRAW_SUB_ONLY = (1 << 1),
@@ -228,8 +224,4 @@ extern const char *const osd_ass_1;
void osd_object_get_resolution(struct osd_state *osd, int obj,
int *out_w, int *out_h);
-// defined in player
-void mp_nav_get_highlight(void *priv, struct mp_osd_res res,
- struct sub_bitmaps *out_imgs);
-
#endif /* MPLAYER_SUB_H */
diff --git a/sub/osd_state.h b/sub/osd_state.h
index 73bfcdd..6fa03ab 100644
--- a/sub/osd_state.h
+++ b/sub/osd_state.h
@@ -28,9 +28,6 @@ struct osd_object {
// OSDTYPE_EXTERNAL2
struct sub_bitmaps *external2;
- // OSDTYPE_NAV_HIGHLIGHT
- void *highlight_priv;
-
// caches for OSD conversion (internal to render_object())
struct osd_conv_cache *cache[OSD_CONV_CACHE_MAX];
struct sub_bitmaps cached;
diff --git a/sub/sd.h b/sub/sd.h
index 7b23990..a77028a 100644
--- a/sub/sd.h
+++ b/sub/sd.h
@@ -28,6 +28,7 @@ struct sd {
// Shared renderer for ASS - done to avoid reloading embedded fonts.
struct ass_library *ass_library;
struct ass_renderer *ass_renderer;
+ pthread_mutex_t *ass_lock;
// If false, try to remove multiple subtitles.
// (Only for decoders which have accept_packets_in_advance set.)
diff --git a/sub/sd_ass.c b/sub/sd_ass.c
index 6f3e24a..baec35f 100644
--- a/sub/sd_ass.c
+++ b/sub/sd_ass.c
@@ -19,6 +19,7 @@
#include <assert.h>
#include <string.h>
#include <math.h>
+#include <limits.h>
#include <libavutil/common.h>
#include <ass/ass.h>
@@ -39,6 +40,7 @@ struct sd_ass_priv {
bool is_converted;
struct sub_bitmap *parts;
bool flush_on_seek;
+ int extend_event;
char last_text[500];
struct mp_image_params video_params;
struct mp_image_params last_params;
@@ -59,14 +61,17 @@ static bool supports_format(const char *format)
static int init(struct sd *sd)
{
struct MPOpts *opts = sd->opts;
- if (!sd->ass_library || !sd->ass_renderer || !sd->codec)
+ if (!sd->ass_library || !sd->ass_renderer || !sd->ass_lock || !sd->codec)
return -1;
struct sd_ass_priv *ctx = talloc_zero(NULL, struct sd_ass_priv);
sd->priv = ctx;
+ ctx->extend_event = -1;
ctx->is_converted = sd->converted_from != NULL;
+ pthread_mutex_lock(sd->ass_lock);
+
ctx->ass_track = ass_new_track(sd->ass_library);
if (!ctx->is_converted)
ctx->ass_track->track_type = TRACK_TYPE_ASS;
@@ -78,6 +83,8 @@ static int init(struct sd *sd)
mp_ass_add_default_styles(ctx->ass_track, opts);
+ pthread_mutex_unlock(sd->ass_lock);
+
return 0;
}
@@ -96,16 +103,19 @@ static void decode(struct sd *sd, struct demux_packet *packet)
ass_process_data(track, packet->buffer, packet->len);
return;
}
+
// plaintext subs
if (packet->pts == MP_NOPTS_VALUE) {
MP_WARN(sd, "Subtitle without pts, ignored\n");
return;
}
- if (packet->duration <= 0) {
- MP_WARN(sd, "Subtitle without duration or "
- "duration set to 0 at pts %f, ignored\n", packet->pts);
- return;
+ if (ctx->extend_event >= 0 && ctx->extend_event < track->n_events) {
+ ASS_Event *event = &track->events[ctx->extend_event];
+ if (event->Start <= ipts)
+ event->Duration = ipts - event->Start;
+ ctx->extend_event = -1;
}
+
unsigned char *text = packet->buffer;
if (!sd->no_remove_duplicates) {
for (int i = 0; i < track->n_events; i++) {
@@ -117,6 +127,20 @@ static void decode(struct sd *sd, struct demux_packet *packet)
}
int eid = ass_alloc_event(track);
ASS_Event *event = track->events + eid;
+
+ if (packet->duration == 0) {
+ MP_WARN(sd, "Subtitle without duration or "
+ "duration set to 0 at pts %f.\n", packet->pts);
+ }
+ if (packet->duration < 0) {
+ // Assume unknown duration. The FFmpeg API is very unclear about this.
+ MP_WARN(sd, "Assuming subtitle without duration at pts %f\n", packet->pts);
+ // _If_ there's a next subtitle, the duration will be adjusted again.
+ // If not, show it forever.
+ iduration = INT_MAX;
+ ctx->extend_event = eid;
+ }
+
event->Start = ipts;
event->Duration = iduration;
event->Style = track->default_style;
@@ -197,6 +221,8 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, double pts,
if (pts == MP_NOPTS_VALUE || !sd->ass_renderer)
return;
+ pthread_mutex_lock(sd->ass_lock);
+
ASS_Renderer *renderer = sd->ass_renderer;
double scale = dim.display_par;
if (!ctx->is_converted && (!opts->ass_style_override ||
@@ -224,6 +250,8 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, double pts,
if (!ctx->is_converted)
mangle_colors(sd, res);
+
+ pthread_mutex_unlock(sd->ass_lock);
}
struct buf {
@@ -341,8 +369,10 @@ static void fix_events(struct sd *sd)
static void reset(struct sd *sd)
{
struct sd_ass_priv *ctx = sd->priv;
- if (ctx->flush_on_seek || sd->opts->sub_clear_on_seek)
+ if (ctx->flush_on_seek || sd->opts->sub_clear_on_seek) {
ass_flush_events(ctx->ass_track);
+ ctx->extend_event = -1;
+ }
ctx->flush_on_seek = false;
}
diff --git a/sub/sd_lavc.c b/sub/sd_lavc.c
index 80a8409..2deac14 100644
--- a/sub/sd_lavc.c
+++ b/sub/sd_lavc.c
@@ -272,6 +272,8 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res d, double pts,
}
if (priv->avctx->codec_id == AV_CODEC_ID_HDMV_PGS_SUBTITLE)
video_par = -1;
+ if (opts->stretch_image_subs)
+ d.ml = d.mr = d.mt = d.mb = 0;
int insize[2];
get_resolution(sd, insize);
osd_rescale_bitmaps(res, insize[0], insize[1], d, video_par);
diff --git a/sub/sd_lavc_conv.c b/sub/sd_lavc_conv.c
index 09e960c..9da6799 100644
--- a/sub/sd_lavc_conv.c
+++ b/sub/sd_lavc_conv.c
@@ -48,19 +48,9 @@ static bool supports_format(const char *format)
{
format = get_lavc_format(format);
enum AVCodecID cid = mp_codec_to_av_codec_id(format);
+ AVCodec *codec = avcodec_find_decoder(cid);
const AVCodecDescriptor *desc = avcodec_descriptor_get(cid);
- if (!desc)
- return false;
- // These are known to support AVSubtitleRect->ass.
- const char *whitelist[] =
- {"text", "ass", "ssa", "srt", "subrip", "microdvd", "mpl2",
- "jacosub", "pjs", "sami", "realtext", "subviewer", "subviewer1",
- "vplayer", "webvtt", 0};
- for (int n = 0; whitelist[n]; n++) {
- if (strcmp(format, whitelist[n]) == 0)
- return true;
- }
- return false;
+ return codec && desc && desc->type == AVMEDIA_TYPE_SUBTITLE;
}
// Disable style definitions generated by the libavcodec converter.
@@ -249,6 +239,8 @@ static void decode(struct sd *sd, struct demux_packet *packet)
MP_ERR(sd, "Error decoding subtitle\n");
} else if (got_sub) {
for (int i = 0; i < sub.num_rects; i++) {
+ if (sub.rects[i]->w > 0 && sub.rects[i]->h > 0)
+ MP_WARN(sd, "Ignoring bitmap subtitle.\n");
char *ass_line = sub.rects[i]->ass;
if (!ass_line)
break;
diff --git a/ta/ta_talloc.h b/ta/ta_talloc.h
index 1cf9923..2331b4e 100644
--- a/ta/ta_talloc.h
+++ b/ta/ta_talloc.h
@@ -99,6 +99,17 @@ char *ta_talloc_asprintf_append_buffer(char *s, const char *fmt, ...) TA_PRF(2,
(idxvar)++; \
} while (0)
+#define MP_TARRAY_INSERT_AT(ctx, p, idxvar, at, ...)\
+ do { \
+ size_t at_ = (at); \
+ assert(at_ <= (idxvar)); \
+ MP_TARRAY_GROW(ctx, p, idxvar); \
+ memmove((p) + at_ + 1, (p) + at_, \
+ ((idxvar) - at_) * sizeof((p)[0])); \
+ (idxvar)++; \
+ (p)[at_] = (TA_EXPAND_ARGS(__VA_ARGS__)); \
+ } while (0)
+
// Doesn't actually free any memory, or do any other talloc calls.
#define MP_TARRAY_REMOVE_AT(p, idxvar, at) \
do { \
diff --git a/test/chmap.c b/test/chmap.c
index 365c15b..395b716 100644
--- a/test/chmap.c
+++ b/test/chmap.c
@@ -16,8 +16,8 @@ static void test_mp_chmap_diff(void **state) {
}
int main(void) {
- const UnitTest tests[] = {
- unit_test(test_mp_chmap_diff),
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_mp_chmap_diff),
};
- return run_tests(tests);
+ return cmocka_run_group_tests(tests, NULL, NULL);
}
diff --git a/test/chmap_sel.c b/test/chmap_sel.c
index 914e003..0301045 100644
--- a/test/chmap_sel.c
+++ b/test/chmap_sel.c
@@ -87,20 +87,45 @@ static void test_mp_chmap_sel_fallback_reject_unknown(void **state) {
assert_string_equal(mp_chmap_to_str(&b), "5.1");
}
+static void test_mp_chmap_sel_fallback_more_replacements(void **state) {
+ test_sel("quad", "quad(side)", LAYOUTS("quad(side)", "stereo"));
+ test_sel("quad", "7.0", LAYOUTS("quad(side)", "7.0"));
+ test_sel("quad", "7.0", LAYOUTS("7.0", "quad(side)"));
+ test_sel("quad", "7.1(wide-side)", LAYOUTS("7.1(wide-side)", "stereo"));
+ test_sel("quad", "7.1(wide-side)", LAYOUTS("stereo", "7.1(wide-side)"));
+ test_sel("quad", "fl-fr-fc-bl-br",
+ LAYOUTS("fl-fr-fc-bl-br", "fl-fr-sl-sr"));
+ test_sel("quad", "fl-fr-bl-br-na-na-na-na",
+ LAYOUTS("fl-fr-bl-br-na-na-na-na", "quad(side)", "stereo"));
+ test_sel("quad", "fl-fr-bl-br-na-na-na-na",
+ LAYOUTS("stereo", "quad(side)", "fl-fr-bl-br-na-na-na-na"));
+ test_sel("fl-fr-fc-lfe-sl-sr", "fl-fr-lfe-fc-bl-br-na-na",
+ LAYOUTS("fl-fr-lfe-fc-bl-br-na-na", "fl-fr-lfe-fc-bl-br-sdl-sdr"));
+ test_sel("fl-fr-fc-lfe-sl-sr", "fl-fr-lfe-fc-bl-br-na-na",
+ LAYOUTS("fl-fr-lfe-fc-bl-br-sdl-sdr", "fl-fr-lfe-fc-bl-br-na-na"));
+}
+
+static void test_mp_chmap_sel_fallback_na_channels(void **state) {
+ test_sel("na-fl-fr", "na-fl-fr", LAYOUTS("na-fl-fr-na", "fl-na-fr", "na-fl-fr",
+ "fl-fr-na-na", "na-na-fl-fr"));
+}
+
int main(void) {
- const UnitTest tests[] = {
- unit_test(test_mp_chmap_sel_fallback_upmix),
- unit_test(test_mp_chmap_sel_fallback_downmix),
- unit_test(test_mp_chmap_sel_fallback_incompatible),
- unit_test(test_mp_chmap_sel_fallback_prefer_compatible),
- unit_test(test_mp_chmap_sel_fallback_prefer_closest_upmix),
- unit_test(test_mp_chmap_sel_fallback_use_replacements),
- unit_test(test_mp_chmap_sel_fallback_works_on_alsa_chmaps),
- unit_test(test_mp_chmap_sel_fallback_mono_to_stereo),
- unit_test(test_mp_chmap_sel_fallback_stereo_to_stereo),
- unit_test(test_mp_chmap_sel_fallback_no_downmix),
- unit_test(test_mp_chmap_sel_fallback_minimal_downmix),
- unit_test(test_mp_chmap_sel_fallback_reject_unknown),
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_mp_chmap_sel_fallback_upmix),
+ cmocka_unit_test(test_mp_chmap_sel_fallback_downmix),
+ cmocka_unit_test(test_mp_chmap_sel_fallback_incompatible),
+ cmocka_unit_test(test_mp_chmap_sel_fallback_prefer_compatible),
+ cmocka_unit_test(test_mp_chmap_sel_fallback_prefer_closest_upmix),
+ cmocka_unit_test(test_mp_chmap_sel_fallback_use_replacements),
+ cmocka_unit_test(test_mp_chmap_sel_fallback_works_on_alsa_chmaps),
+ cmocka_unit_test(test_mp_chmap_sel_fallback_mono_to_stereo),
+ cmocka_unit_test(test_mp_chmap_sel_fallback_stereo_to_stereo),
+ cmocka_unit_test(test_mp_chmap_sel_fallback_no_downmix),
+ cmocka_unit_test(test_mp_chmap_sel_fallback_minimal_downmix),
+ cmocka_unit_test(test_mp_chmap_sel_fallback_reject_unknown),
+ cmocka_unit_test(test_mp_chmap_sel_fallback_more_replacements),
+ cmocka_unit_test(test_mp_chmap_sel_fallback_na_channels),
};
- return run_tests(tests);
+ return cmocka_run_group_tests(tests, NULL, NULL);
}
diff --git a/test/gl_video.c b/test/gl_video.c
index 543ff2e..253ab35 100644
--- a/test/gl_video.c
+++ b/test/gl_video.c
@@ -31,12 +31,12 @@ static void test_scale_ambient_lux_log10_midpoint(void **state) {
}
int main(void) {
- const UnitTest tests[] = {
- unit_test(test_scale_ambient_lux_limits),
- unit_test(test_scale_ambient_lux_sign),
- unit_test(test_scale_ambient_lux_clamping),
- unit_test(test_scale_ambient_lux_log10_midpoint),
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_scale_ambient_lux_limits),
+ cmocka_unit_test(test_scale_ambient_lux_sign),
+ cmocka_unit_test(test_scale_ambient_lux_clamping),
+ cmocka_unit_test(test_scale_ambient_lux_log10_midpoint),
};
- return run_tests(tests);
+ return cmocka_run_group_tests(tests, NULL, NULL);
}
diff --git a/version.sh b/version.sh
index 3eac7ef..674c6a1 100755
--- a/version.sh
+++ b/version.sh
@@ -2,22 +2,34 @@
export LC_ALL=C
+version_h="version.h"
+print=yes
+
for ac_option do
+ ac_arg=$(echo $ac_option | cut -d '=' -f 2-)
case "$ac_option" in
--extra=*)
- extra="-$option"
+ extra="-$ac_arg"
+ ;;
+ --versionh=*)
+ version_h="$(pwd)/$ac_arg"
+ print=no
;;
- --print)
- print=yes
+ --cwd=*)
+ cwd="$ac_arg"
;;
*)
- echo "Unknown parameter: $option" >&2
+ echo "Unknown parameter: $ac_option" >&2
exit 1
;;
esac
done
+if test "$cwd" ; then
+ cd "$cwd"
+fi
+
# Extract revision number from file used by daily tarball snapshots
# or from "git describe" output
git_revision=$(cat snapshot_version 2> /dev/null)
@@ -42,12 +54,12 @@ if test "$print" = yes ; then
fi
NEW_REVISION="#define VERSION \"${VERSION}\""
-OLD_REVISION=$(head -n 1 version.h 2> /dev/null)
+OLD_REVISION=$(head -n 1 "$version_h" 2> /dev/null)
BUILDDATE="#define BUILDDATE \"$(date)\""
# Update version.h only on revision changes to avoid spurious rebuilds
if test "$NEW_REVISION" != "$OLD_REVISION"; then
- cat <<EOF > version.h
+ cat <<EOF > "$version_h"
$NEW_REVISION
$BUILDDATE
EOF
diff --git a/video/csputils.c b/video/csputils.c
index ca092fc..ede6cd1 100644
--- a/video/csputils.c
+++ b/video/csputils.c
@@ -104,6 +104,7 @@ const struct m_opt_choice_alternatives mp_chroma_names[] = {
// The numeric index matches the Matroska StereoMode value. If you add entries
// that don't match Matroska, make sure demux_mkv.c rejects them properly.
const struct m_opt_choice_alternatives mp_stereo3d_names[] = {
+ {"no", -1}, // disable/invalid
{"mono", 0},
{"sbs2l", 1}, // "side_by_side_left"
{"ab2r", 2}, // "top_bottom_right"
@@ -689,14 +690,6 @@ void mp_get_yuv2rgb_coeffs(struct mp_csp_params *params, struct mp_cmat *m)
+ params->brightness;
}
- // Brightness adds a constant to output R,G,B.
- // Contrast scales Y around 1/2 (not 0 in this implementation).
- for (int i = 0; i < 3; i++) {
- m->c[i] += params->brightness;
- m->m[i][0] *= params->contrast;
- m->c[i] += (rgblev.max-rgblev.min) * (1 - params->contrast)/2;
- }
-
int in_bits = FFMAX(params->int_bits_in, 1);
int out_bits = FFMAX(params->int_bits_out, 1);
double in_scale = (1 << in_bits) - 1.0;
diff --git a/video/d3d.h b/video/d3d.h
new file mode 100644
index 0000000..30bee49
--- /dev/null
+++ b/video/d3d.h
@@ -0,0 +1,13 @@
+#ifndef MP_D3D_H_
+#define MP_D3D_H_
+
+#include <d3d9.h>
+
+#include "hwdec.h"
+
+struct mp_d3d_ctx {
+ struct mp_hwdec_ctx hwctx;
+ IDirect3DDevice9 *d3d9_device;
+};
+
+#endif
diff --git a/video/decode/dec_video.c b/video/decode/dec_video.c
index 0c733b0..34b437a 100644
--- a/video/decode/dec_video.c
+++ b/video/decode/dec_video.c
@@ -117,6 +117,7 @@ int video_get_colors(struct dec_video *d_video, const char *item, int *value)
void video_uninit(struct dec_video *d_video)
{
mp_image_unrefp(&d_video->waiting_decoded_mpi);
+ mp_image_unrefp(&d_video->cover_art_mpi);
if (d_video->vd_driver) {
MP_VERBOSE(d_video, "Uninit video.\n");
d_video->vd_driver->uninit(d_video);
@@ -385,24 +386,45 @@ int video_reconfig_filters(struct dec_video *d_video,
struct mp_image_params p = *params;
struct sh_video *sh = d_video->header->video;
- float decoder_aspect = p.d_w / (float)p.d_h;
+ // While mp_image_params normally always have to have d_w/d_h set, the
+ // decoder signals unknown bitstream aspect ratio with both set to 0.
+ float dec_aspect = p.d_w > 0 && p.d_h > 0 ? p.d_w / (float)p.d_h : 0;
if (d_video->initial_decoder_aspect == 0)
- d_video->initial_decoder_aspect = decoder_aspect;
+ d_video->initial_decoder_aspect = dec_aspect;
+
+ bool use_container = true;
+ switch (opts->aspect_method) {
+ case 0:
+ // We normally prefer the container aspect, unless the decoder aspect
+ // changes at least once.
+ if (dec_aspect > 0 && d_video->initial_decoder_aspect != dec_aspect) {
+ MP_VERBOSE(d_video, "Using bitstream aspect ratio.\n");
+ // Even if the aspect switches back, don't use container aspect again.
+ d_video->initial_decoder_aspect = -1;
+ use_container = false;
+ }
+ break;
+ case 1:
+ use_container = false;
+ break;
+ }
- // We normally prefer the container aspect, unless the decoder aspect
- // changes at least once.
- if (d_video->initial_decoder_aspect == decoder_aspect) {
- if (sh->aspect > 0)
- vf_set_dar(&p.d_w, &p.d_h, p.w, p.h, sh->aspect);
- } else {
- MP_VERBOSE(d_video, "Using bitstream aspect ratio.\n");
- // Even if the aspect switches back, don't use container aspect again.
- d_video->initial_decoder_aspect = -1;
+ if (use_container && sh->aspect > 0) {
+ MP_VERBOSE(d_video, "Using container aspect ratio.\n");
+ vf_set_dar(&p.d_w, &p.d_h, p.w, p.h, sh->aspect);
}
float force_aspect = opts->movie_aspect;
- if (force_aspect >= 0.0)
+ if (force_aspect >= 0.0) {
+ MP_VERBOSE(d_video, "Forcing user-set aspect ratio.\n");
vf_set_dar(&p.d_w, &p.d_h, p.w, p.h, force_aspect);
+ }
+
+ // Assume square pixels if no aspect ratio is set at all.
+ if (p.d_w <= 0 || p.d_h <= 0) {
+ p.d_w = p.w;
+ p.d_h = p.h;
+ }
// Detect colorspace from resolution.
mp_image_params_guess_csp(&p);
diff --git a/video/decode/dec_video.h b/video/decode/dec_video.h
index 5ab7213..69e2a44 100644
--- a/video/decode/dec_video.h
+++ b/video/decode/dec_video.h
@@ -43,6 +43,8 @@ struct dec_video {
struct mp_image *waiting_decoded_mpi;
struct mp_image_params decoder_output; // last output of the decoder
+ struct mp_image *cover_art_mpi;
+
void *priv; // for free use by vd_driver
// Last PTS from decoder (set with each vd_driver->decode() call)
diff --git a/video/decode/dxva2.c b/video/decode/dxva2.c
index ef77561..5e06f50 100644
--- a/video/decode/dxva2.c
+++ b/video/decode/dxva2.c
@@ -36,6 +36,7 @@
#include "video/fmt-conversion.h"
#include "video/mp_image_pool.h"
#include "video/hwdec.h"
+#include "video/d3d.h"
#include "gpu_memcpy_sse4.h"
// A minor evil.
@@ -55,6 +56,7 @@ DEFINE_GUID(DXVA2_ModeH264_F, 0x1b81be69, 0xa0c7,0x11d3,0xb9,0x84,0x00,0
DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68, 0x4951,0x4C54,0x88,0xFE,0xAB,0xD2,0x5C,0x15,0xB3,0xD6);
DEFINE_GUID(DXVA2_ModeVC1_D, 0x1b81beA3, 0xa0c7,0x11d3,0xb9,0x84,0x00,0xc0,0x4f,0x2e,0x73,0xc5);
DEFINE_GUID(DXVA2_ModeVC1_D2010, 0x1b81beA4, 0xa0c7,0x11d3,0xb9,0x84,0x00,0xc0,0x4f,0x2e,0x73,0xc5);
+DEFINE_GUID(DXVA2_ModeHEVC_VLD_Main, 0x5b11d51b, 0x2f4c,0x4452,0xbc,0xc3,0x09,0xf2,0xa1,0x16,0x0c,0xc0);
DEFINE_GUID(DXVA2_NoEncrypt, 0x1b81beD0, 0xa0c7,0x11d3,0xb9,0x84,0x00,0xc0,0x4f,0x2e,0x73,0xc5);
DEFINE_GUID(GUID_NULL, 0x00000000, 0x0000,0x0000,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00);
@@ -83,6 +85,8 @@ static const dxva2_mode dxva2_modes[] = {
{ &DXVA2_ModeVC1_D, AV_CODEC_ID_VC1 },
{ &DXVA2_ModeVC1_D, AV_CODEC_ID_WMV3 },
+ { &DXVA2_ModeHEVC_VLD_Main, AV_CODEC_ID_HEVC },
+
{ NULL, 0 },
};
@@ -201,14 +205,11 @@ static void dxva2_release_img(void *ptr)
av_free(w);
}
-static struct mp_image *dxva2_allocate_image(struct lavc_ctx *s, int fmt,
+static struct mp_image *dxva2_allocate_image(struct lavc_ctx *s,
int img_w, int img_h)
{
DXVA2Context *ctx = s->hwdec_priv;
- if (fmt != IMGFMT_DXVA2)
- return NULL;
-
int i, old_unused = -1;
for (i = 0; i < ctx->num_surfaces; i++) {
surface_info *info = &ctx->surface_infos[i];
@@ -307,8 +308,11 @@ static struct mp_image *dxva2_retrieve_image(struct lavc_ctx *s,
IDirect3DSurface9_GetDesc(surface, &surfaceDesc);
+ if (surfaceDesc.Width < img->w || surfaceDesc.Height < img->h)
+ return img;
+
struct mp_image *sw_img =
- mp_image_pool_get(ctx->sw_pool, IMGFMT_NV12, img->w, img->h);
+ mp_image_pool_get(ctx->sw_pool, IMGFMT_NV12, surfaceDesc.Width, surfaceDesc.Height);
if (!sw_img)
return img;
@@ -321,6 +325,7 @@ static struct mp_image *dxva2_retrieve_image(struct lavc_ctx *s,
}
ctx->copy_nv12(sw_img, LockedRect.pBits, LockedRect.Pitch, surfaceDesc.Height);
+ mp_image_set_size(sw_img, img->w, img->h);
mp_image_copy_attributes(sw_img, img);
IDirect3DSurface9_UnlockRect(surface);
@@ -329,58 +334,35 @@ static struct mp_image *dxva2_retrieve_image(struct lavc_ctx *s,
return sw_img;
}
-static int dxva2_init(struct lavc_ctx *s)
+static int create_device(struct lavc_ctx *s)
{
- DXVA2Context *ctx;
+ DXVA2Context *ctx = s->hwdec_priv;
pDirect3DCreate9 *createD3D = NULL;
- pCreateDeviceManager9 *createDeviceManager = NULL;
HRESULT hr;
D3DPRESENT_PARAMETERS d3dpp = {0};
D3DDISPLAYMODE d3ddm;
- unsigned resetToken = 0;
UINT adapter = D3DADAPTER_DEFAULT;
- ctx = talloc_zero(NULL, DXVA2Context);
- if (!ctx)
- return -1;
- s->hwdec_priv = ctx;
-
- ctx->log = mp_log_new(s, s->log, "dxva2");
- ctx->sw_pool = talloc_steal(ctx, mp_image_pool_new(17));
-
- if (av_get_cpu_flags() & AV_CPU_FLAG_SSE4) {
- // Use a memcpy implementation optimised for copying from GPU memory
- MP_DBG(ctx, "Using SSE4 memcpy\n");
- ctx->copy_nv12 = copy_nv12_gpu_sse4;
- } else {
- // Use the CRT memcpy. This can be slower than software decoding.
- MP_WARN(ctx, "Using fallback memcpy (slow)\n");
- ctx->copy_nv12 = copy_nv12_fallback;
+ if (s->hwdec_info && s->hwdec_info->hwctx && s->hwdec_info->hwctx->d3d_ctx) {
+ ctx->d3d9device = s->hwdec_info->hwctx->d3d_ctx->d3d9_device;
+ if (ctx->d3d9device) {
+ IDirect3D9_AddRef(ctx->d3d9device);
+ MP_VERBOSE(ctx, "Using VO-supplied device %p.\n", ctx->d3d9device);
+ return 0;
+ }
}
- ctx->deviceHandle = INVALID_HANDLE_VALUE;
-
ctx->d3dlib = LoadLibrary(L"d3d9.dll");
if (!ctx->d3dlib) {
MP_ERR(ctx, "Failed to load D3D9 library\n");
goto fail;
}
- ctx->dxva2lib = LoadLibrary(L"dxva2.dll");
- if (!ctx->dxva2lib) {
- MP_ERR(ctx, "Failed to load DXVA2 library\n");
- goto fail;
- }
createD3D = (pDirect3DCreate9 *)GetProcAddress(ctx->d3dlib, "Direct3DCreate9");
if (!createD3D) {
MP_ERR(ctx, "Failed to locate Direct3DCreate9\n");
goto fail;
}
- createDeviceManager = (pCreateDeviceManager9 *)GetProcAddress(ctx->dxva2lib, "DXVA2CreateDirect3DDeviceManager9");
- if (!createDeviceManager) {
- MP_ERR(ctx, "Failed to locate DXVA2CreateDirect3DDeviceManager9\n");
- goto fail;
- }
ctx->d3d9 = createD3D(D3D_SDK_VERSION);
if (!ctx->d3d9) {
@@ -405,6 +387,54 @@ static int dxva2_init(struct lavc_ctx *s)
goto fail;
}
+ return 0;
+
+fail:
+ return -1;
+}
+
+static int dxva2_init(struct lavc_ctx *s)
+{
+ DXVA2Context *ctx;
+ pCreateDeviceManager9 *createDeviceManager = NULL;
+ HRESULT hr;
+ unsigned resetToken = 0;
+
+ ctx = talloc_zero(NULL, DXVA2Context);
+ if (!ctx)
+ return -1;
+ s->hwdec_priv = ctx;
+
+ ctx->log = mp_log_new(s, s->log, "dxva2");
+ ctx->sw_pool = talloc_steal(ctx, mp_image_pool_new(17));
+
+ if (av_get_cpu_flags() & AV_CPU_FLAG_SSE4) {
+ // Use a memcpy implementation optimised for copying from GPU memory
+ MP_DBG(ctx, "Using SSE4 memcpy\n");
+ ctx->copy_nv12 = copy_nv12_gpu_sse4;
+ } else {
+ // Use the CRT memcpy. This can be slower than software decoding.
+ MP_WARN(ctx, "Using fallback memcpy (slow)\n");
+ ctx->copy_nv12 = copy_nv12_fallback;
+ }
+
+ ctx->deviceHandle = INVALID_HANDLE_VALUE;
+
+ ctx->dxva2lib = LoadLibrary(L"dxva2.dll");
+ if (!ctx->dxva2lib) {
+ MP_ERR(ctx, "Failed to load DXVA2 library\n");
+ goto fail;
+ }
+
+ if (create_device(s) < 0)
+ goto fail;
+
+ createDeviceManager = (pCreateDeviceManager9 *)GetProcAddress(ctx->dxva2lib, "DXVA2CreateDirect3DDeviceManager9");
+ if (!createDeviceManager) {
+ MP_ERR(ctx, "Failed to locate DXVA2CreateDirect3DDeviceManager9\n");
+ goto fail;
+ }
+
hr = createDeviceManager(&resetToken, &ctx->d3d9devmgr);
if (FAILED(hr)) {
MP_ERR(ctx, "Failed to create Direct3D device manager\n");
@@ -558,6 +588,10 @@ static int dxva2_create_decoder(struct lavc_ctx *s, int w, int h,
but it causes issues for H.264 on certain AMD GPUs..... */
if (codec_id == AV_CODEC_ID_MPEG2VIDEO)
surface_alignment = 32;
+ /* the HEVC DXVA2 spec asks for 128 pixel aligned surfaces to ensure
+ all coding features have enough room to work with */
+ else if (codec_id == AV_CODEC_ID_HEVC)
+ surface_alignment = 128;
else
surface_alignment = 16;
@@ -565,7 +599,7 @@ static int dxva2_create_decoder(struct lavc_ctx *s, int w, int h,
ctx->num_surfaces = 4;
/* add surfaces based on number of possible refs */
- if (codec_id == AV_CODEC_ID_H264)
+ if (codec_id == AV_CODEC_ID_H264 || codec_id == AV_CODEC_ID_HEVC)
ctx->num_surfaces += 16;
else
ctx->num_surfaces += 2;
@@ -615,7 +649,7 @@ fail:
return -1;
}
-static int dxva2_init_decoder(struct lavc_ctx *s, int fmt, int w, int h)
+static int dxva2_init_decoder(struct lavc_ctx *s, int w, int h)
{
DXVA2Context *ctx = s->hwdec_priv;
@@ -642,6 +676,7 @@ static int dxva2_init_decoder(struct lavc_ctx *s, int fmt, int w, int h)
static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info,
const char *decoder)
{
+ hwdec_request_api(info, "dxva2"); // we can do without too
for (int i = 0; dxva2_modes[i].guid; i++) {
const dxva2_mode *mode = &dxva2_modes[i];
if (mp_codec_to_av_codec_id(decoder) == mode->codec)
diff --git a/video/decode/lavc.h b/video/decode/lavc.h
index eaddd4a..90b2d6a 100644
--- a/video/decode/lavc.h
+++ b/video/decode/lavc.h
@@ -15,11 +15,12 @@ typedef struct lavc_ctx {
AVCodecContext *avctx;
AVFrame *pic;
struct vd_lavc_hwdec *hwdec;
- int selected_hwdec;
enum AVPixelFormat pix_fmt;
int best_csp;
enum AVDiscard skip_frame;
const char *software_fallback_decoder;
+ bool hwdec_failed;
+ bool hwdec_notified;
// From VO
struct mp_hwdec_info *hwdec_info;
@@ -31,6 +32,8 @@ typedef struct lavc_ctx {
int hwdec_w;
int hwdec_h;
int hwdec_profile;
+
+ bool hwdec_request_reinit;
} vd_ffmpeg_ctx;
struct vd_lavc_hwdec {
@@ -41,12 +44,11 @@ struct vd_lavc_hwdec {
int (*probe)(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info,
const char *decoder);
int (*init)(struct lavc_ctx *ctx);
- int (*init_decoder)(struct lavc_ctx *ctx, int fmt, int w, int h);
+ int (*init_decoder)(struct lavc_ctx *ctx, int w, int h);
void (*uninit)(struct lavc_ctx *ctx);
// Note: if init_decoder is set, this will always use the values from the
// last successful init_decoder call. Otherwise, it's up to you.
- struct mp_image *(*allocate_image)(struct lavc_ctx *ctx, int fmt,
- int w, int h);
+ struct mp_image *(*allocate_image)(struct lavc_ctx *ctx, int w, int h);
// Process the image returned by the libavcodec decoder.
struct mp_image *(*process_image)(struct lavc_ctx *ctx, struct mp_image *img);
// For horrible Intel shit-drivers only
diff --git a/video/decode/rpi.c b/video/decode/rpi.c
index 44a550f..819369d 100644
--- a/video/decode/rpi.c
+++ b/video/decode/rpi.c
@@ -18,7 +18,7 @@
#include "lavc.h"
#include "common/common.h"
-static int init_decoder(struct lavc_ctx *ctx, int fmt, int w, int h)
+static int init_decoder(struct lavc_ctx *ctx, int w, int h)
{
return 0;
}
diff --git a/video/decode/vaapi.c b/video/decode/vaapi.c
index f78a01d..f88e446 100644
--- a/video/decode/vaapi.c
+++ b/video/decode/vaapi.c
@@ -40,20 +40,14 @@
* The VAAPI decoder can work only with surfaces passed to the decoder at
* creation time. This means all surfaces have to be created in advance.
* So, additionally to the maximum number of reference frames, we need
- * surfaces for:
- * - 1 decode frame
- * - decoding 2 frames ahead (done by generic playback code)
- * - keeping the reference to the previous frame (done by vo_vaapi.c)
- * - keeping the reference to a dropped frame (done by vo.c)
+ * surfaces for all kinds of buffering between decoder and VO.
* Note that redundant additional surfaces also might allow for some
* buffering (i.e. not trying to reuse a surface while it's busy).
*/
-#define ADDTIONAL_SURFACES 5
+#define ADDTIONAL_SURFACES 6
-// Magic number taken from original MPlayer vaapi patch.
-#define MAX_DECODER_SURFACES 21
-
-#define MAX_SURFACES (MAX_DECODER_SURFACES + ADDTIONAL_SURFACES)
+// Some upper bound.
+#define MAX_SURFACES 25
struct priv {
struct mp_log *log;
@@ -71,6 +65,8 @@ struct priv {
struct mp_image_pool *sw_pool;
};
+#define HAS_HEVC VA_CHECK_VERSION(0, 38, 0)
+
#define PE(av_codec_id, ff_profile, vdp_profile) \
{AV_CODEC_ID_ ## av_codec_id, FF_PROFILE_ ## ff_profile, \
VAProfile ## vdp_profile}
@@ -90,6 +86,10 @@ static const struct hwdec_profile_entry profiles[] = {
PE(WMV3, VC1_ADVANCED, VC1Advanced),
PE(WMV3, VC1_MAIN, VC1Main),
PE(WMV3, VC1_SIMPLE, VC1Simple),
+#if HAS_HEVC
+ PE(HEVC, HEVC_MAIN, HEVCMain),
+ PE(HEVC, HEVC_MAIN_10, HEVCMain10),
+#endif
{0}
};
@@ -109,6 +109,10 @@ static const char *str_va_profile(VAProfile profile)
PROFILE(VC1Simple);
PROFILE(VC1Main);
PROFILE(VC1Advanced);
+#if HAS_HEVC
+ PROFILE(HEVCMain);
+ PROFILE(HEVCMain10);
+#endif
#undef PROFILE
}
return "<unknown>";
@@ -127,35 +131,6 @@ static int find_entrypoint(int format, VAEntrypoint *ep, int num_ep)
return -1;
}
-static int is_direct_mapping(VADisplay display)
-{
- VADisplayAttribute attr = {0};
- VAStatus status;
-
-#if VA_CHECK_VERSION(0,34,0)
- attr.type = VADisplayAttribRenderMode;
- attr.flags = VA_DISPLAY_ATTRIB_GETTABLE;
-
- status = vaGetDisplayAttributes(display, &attr, 1);
- if (status == VA_STATUS_SUCCESS)
- return !(attr.value & (VA_RENDER_MODE_LOCAL_OVERLAY|
- VA_RENDER_MODE_EXTERNAL_OVERLAY));
-#else
- /* If the driver doesn't make a copy of the VA surface for
- display, then we have to retain it until it's no longer the
- visible surface. In other words, if the driver is using
- DirectSurface mode, we don't want to decode the new surface
- into the previous one that was used for display. */
- attr.type = VADisplayAttribDirectSurface;
- attr.flags = VA_DISPLAY_ATTRIB_GETTABLE;
-
- status = vaGetDisplayAttributes(display, &attr, 1);
- if (status == VA_STATUS_SUCCESS)
- return !attr.value;
-#endif
- return 0;
-}
-
// We must allocate only surfaces that were passed to the decoder on creation.
// We achieve this by reserving surfaces in the pool as needed.
// Releasing surfaces is necessary after filling the surface id list so
@@ -164,10 +139,12 @@ static bool preallocate_surfaces(struct lavc_ctx *ctx, int num, int w, int h,
VASurfaceID out_surfaces[MAX_SURFACES])
{
struct priv *p = ctx->hwdec_priv;
- assert(num <= MAX_SURFACES);
struct mp_image *reserve[MAX_SURFACES] = {0};
bool res = true;
+ if (num > MAX_SURFACES)
+ return false;
+
for (int n = 0; n < num; n++) {
reserve[n] = mp_image_pool_get(p->pool, IMGFMT_VAAPI, w, h);
out_surfaces[n] = va_surface_id(reserve[n]);
@@ -212,7 +189,7 @@ static bool has_profile(VAProfile *va_profiles, int num_profiles, VAProfile p)
return false;
}
-static int init_decoder(struct lavc_ctx *ctx, int fmt, int w, int h)
+static int init_decoder(struct lavc_ctx *ctx, int w, int h)
{
void *tmp = talloc_new(NULL);
@@ -248,14 +225,7 @@ static int init_decoder(struct lavc_ctx *ctx, int fmt, int w, int h)
MP_VERBOSE(p, "Using profile '%s'.\n", str_va_profile(va_profile));
- int num_surfaces = hwdec_get_max_refs(ctx);
- if (!is_direct_mapping(p->display)) {
- MP_VERBOSE(p, "No direct mapping.\n");
- // Note: not sure why it has to be *=2 rather than +=1.
- num_surfaces *= 2;
- }
- num_surfaces = MPMIN(num_surfaces, MAX_DECODER_SURFACES) + ADDTIONAL_SURFACES;
-
+ int num_surfaces = hwdec_get_max_refs(ctx) + ADDTIONAL_SURFACES;
if (num_surfaces > MAX_SURFACES) {
MP_ERR(p, "Internal error: too many surfaces.\n");
goto error;
@@ -310,15 +280,13 @@ error:
return res;
}
-static struct mp_image *allocate_image(struct lavc_ctx *ctx, int format,
- int w, int h)
+static struct mp_image *allocate_image(struct lavc_ctx *ctx, int w, int h)
{
struct priv *p = ctx->hwdec_priv;
- struct mp_image *img =
- mp_image_pool_get_no_alloc(p->pool, IMGFMT_VAAPI, w, h);
+ struct mp_image *img = mp_image_pool_get(p->pool, IMGFMT_VAAPI, w, h);
if (!img)
- MP_ERR(p, "Insufficient number of surfaces.\n");
+ MP_ERR(p, "Failed to allocate additional VAAPI surface.\n");
return img;
}
@@ -343,7 +311,7 @@ static bool create_va_dummy_ctx(struct priv *p)
VADisplay *display = vaGetDisplay(p->x11_display);
if (!display)
goto destroy_ctx;
- p->ctx = va_initialize(display, p->log);
+ p->ctx = va_initialize(display, p->log, true);
if (!p->ctx) {
vaTerminate(display);
goto destroy_ctx;
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c
index ca9ab9e..d9974e2 100644
--- a/video/decode/vd_lavc.c
+++ b/video/decode/vd_lavc.c
@@ -98,7 +98,7 @@ const struct m_sub_options vd_lavc_conf = {
OPT_DISCARD("skipidct", skip_idct, 0),
OPT_DISCARD("skipframe", skip_frame, 0),
OPT_DISCARD("framedrop", framedrop, 0),
- OPT_INTRANGE("threads", threads, 0, 0, 16),
+ OPT_INT("threads", threads, M_OPT_MIN, .min = 0),
OPT_FLAG("bitexact", bitexact, 0),
OPT_FLAG("check-hw-profile", check_hw_profile, 0),
OPT_KEYVALUELIST("o", avopts, 0),
@@ -117,6 +117,7 @@ const struct m_sub_options vd_lavc_conf = {
const struct vd_lavc_hwdec mp_vd_lavc_vdpau;
const struct vd_lavc_hwdec mp_vd_lavc_vda;
+const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox;
const struct vd_lavc_hwdec mp_vd_lavc_vaapi;
const struct vd_lavc_hwdec mp_vd_lavc_vaapi_copy;
const struct vd_lavc_hwdec mp_vd_lavc_dxva2_copy;
@@ -129,12 +130,15 @@ static const struct vd_lavc_hwdec *const hwdec_list[] = {
#if HAVE_VDPAU_HWACCEL
&mp_vd_lavc_vdpau,
#endif
+#if HAVE_VIDEOTOOLBOX_HWACCEL
+ &mp_vd_lavc_videotoolbox,
+#endif
#if HAVE_VDA_HWACCEL
&mp_vd_lavc_vda,
#endif
#if HAVE_VAAPI_HWACCEL
- &mp_vd_lavc_vaapi_copy,
&mp_vd_lavc_vaapi,
+ &mp_vd_lavc_vaapi_copy,
#endif
#if HAVE_DXVA2_HWACCEL
&mp_vd_lavc_dxva2_copy,
@@ -214,7 +218,10 @@ bool hwdec_check_codec_support(const char *decoder,
int hwdec_get_max_refs(struct lavc_ctx *ctx)
{
- return ctx->avctx->codec_id == AV_CODEC_ID_H264 ? 16 : 2;
+ if (ctx->avctx->codec_id == AV_CODEC_ID_H264 ||
+ ctx->avctx->codec_id == AV_CODEC_ID_HEVC)
+ return 16;
+ return 2;
}
void hwdec_request_api(struct mp_hwdec_info *info, const char *api_name)
@@ -236,6 +243,7 @@ static struct vd_lavc_hwdec *probe_hwdec(struct dec_video *vd, bool autoprobe,
enum hwdec_type api,
const char *decoder)
{
+ MP_VERBOSE(vd, "Probing '%s'...\n", m_opt_choice_str(mp_hwdec_names, api));
struct vd_lavc_hwdec *hwdec = find_hwcodec(api);
if (!hwdec) {
MP_VERBOSE(vd, "Requested hardware decoder not compiled.\n");
@@ -252,10 +260,11 @@ static struct vd_lavc_hwdec *probe_hwdec(struct dec_video *vd, bool autoprobe,
if (r >= 0) {
return hwdec;
} else if (r == HWDEC_ERR_NO_CODEC) {
- MP_VERBOSE(vd, "Hardware decoder '%s' not found in "
- "libavcodec.\n", decoder);
+ MP_VERBOSE(vd, "Hardware decoder '%s' not found in libavcodec.\n",
+ decoder);
} else if (r == HWDEC_ERR_NO_CTX && !autoprobe) {
- MP_WARN(vd, "VO does not support requested hardware decoder.\n");
+ MP_WARN(vd, "VO does not support requested hardware decoder, or "
+ "loading it failed.\n");
}
return NULL;
}
@@ -266,6 +275,21 @@ static void uninit(struct dec_video *vd)
talloc_free(vd->priv);
}
+static bool force_fallback(struct dec_video *vd)
+{
+ vd_ffmpeg_ctx *ctx = vd->priv;
+ if (!ctx->software_fallback_decoder)
+ return false;
+
+ uninit_avctx(vd);
+ int lev = ctx->hwdec_notified ? MSGL_WARN : MSGL_V;
+ mp_msg(vd->log, lev, "Falling back to software decoding.\n");
+ const char *decoder = ctx->software_fallback_decoder;
+ ctx->software_fallback_decoder = NULL;
+ init_avctx(vd, decoder, NULL);
+ return true;
+}
+
static int init(struct dec_video *vd, const char *decoder)
{
vd_ffmpeg_ctx *ctx;
@@ -273,8 +297,6 @@ static int init(struct dec_video *vd, const char *decoder)
ctx->log = vd->log;
ctx->opts = vd->opts;
- ctx->selected_hwdec = vd->opts->hwdec_api;
-
if (bstr_endswith0(bstr0(decoder), "_vdpau")) {
MP_WARN(vd, "VDPAU decoder '%s' was requested. "
"This way of enabling hardware\ndecoding is not supported "
@@ -308,20 +330,14 @@ static int init(struct dec_video *vd, const char *decoder)
ctx->software_fallback_decoder = talloc_strdup(ctx, decoder);
if (hwdec->get_codec)
decoder = hwdec->get_codec(ctx);
- MP_INFO(vd, "Using hardware decoding.\n");
- } else if (vd->opts->hwdec_api != HWDEC_NONE) {
- MP_INFO(vd, "Using software decoding.\n");
+ MP_VERBOSE(vd, "Trying hardware decoding.\n");
+ } else {
+ MP_VERBOSE(vd, "Using software decoding.\n");
}
init_avctx(vd, decoder, hwdec);
if (!ctx->avctx) {
- if (ctx->software_fallback_decoder) {
- MP_ERR(vd, "Error initializing hardware decoding, "
- "falling back to software decoding.\n");
- decoder = ctx->software_fallback_decoder;
- ctx->software_fallback_decoder = NULL;
- init_avctx(vd, decoder, NULL);
- }
+ force_fallback(vd);
if (!ctx->avctx) {
uninit(vd);
return 0;
@@ -400,19 +416,19 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
// Do this after the above avopt handling in case it changes values
ctx->skip_frame = avctx->skip_frame;
- avctx->codec_tag = sh->format;
+ avctx->codec_tag = sh->codec_tag;
avctx->coded_width = sh->video->disp_w;
avctx->coded_height = sh->video->disp_h;
avctx->bits_per_coded_sample = sh->video->bits_per_coded_sample;
- mp_lavc_set_extradata(avctx, sh->video->extradata, sh->video->extradata_len);
+ mp_lavc_set_extradata(avctx, sh->extradata, sh->extradata_size);
if (mp_rawvideo) {
- avctx->pix_fmt = imgfmt2pixfmt(sh->format);
+ avctx->pix_fmt = imgfmt2pixfmt(sh->codec_tag);
avctx->codec_tag = 0;
- if (avctx->pix_fmt == AV_PIX_FMT_NONE && sh->format)
+ if (avctx->pix_fmt == AV_PIX_FMT_NONE && sh->codec_tag)
MP_ERR(vd, "Image format %s not supported by lavc.\n",
- mp_imgfmt_to_name(sh->format));
+ mp_imgfmt_to_name(sh->codec_tag));
}
if (sh->lav_headers)
@@ -448,6 +464,8 @@ static void uninit_avctx(struct dec_video *vd)
av_freep(&ctx->avctx);
av_frame_free(&ctx->pic);
+
+ ctx->hwdec_failed = false;
}
static void update_image_params(struct dec_video *vd, AVFrame *frame,
@@ -468,15 +486,12 @@ static void update_image_params(struct dec_video *vd, AVFrame *frame,
av_get_pix_fmt_name(pix_fmt));
}
- int d_w, d_h;
- vf_set_dar(&d_w, &d_h, width, height, aspect);
-
*out_params = (struct mp_image_params) {
.imgfmt = ctx->best_csp,
.w = width,
.h = height,
- .d_w = d_w,
- .d_h = d_h,
+ .d_w = 0,
+ .d_h = 0,
.colorspace = avcol_spc_to_mp_csp(ctx->avctx->colorspace),
.colorlevels = avcol_range_to_mp_csp_levels(ctx->avctx->color_range),
.primaries = avcol_pri_to_mp_csp_prim(ctx->avctx->color_primaries),
@@ -487,6 +502,9 @@ static void update_image_params(struct dec_video *vd, AVFrame *frame,
.stereo_in = vd->header->video->stereo_mode,
};
+ if (aspect > 0)
+ vf_set_dar(&out_params->d_w, &out_params->d_h, width, height, aspect);
+
if (opts->video_rotate < 0) {
out_params->rotate = 0;
} else {
@@ -514,17 +532,18 @@ static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx,
// There could be more reasons for a change, and it's possible
// that we miss some. (Might also depend on the hwaccel type.)
bool change =
- ctx->hwdec_w != avctx->width ||
- ctx->hwdec_h != avctx->height ||
+ ctx->hwdec_w != avctx->coded_width ||
+ ctx->hwdec_h != avctx->coded_height ||
ctx->hwdec_fmt != ctx->hwdec->image_format ||
- ctx->hwdec_profile != avctx->profile;
- ctx->hwdec_w = avctx->width;
- ctx->hwdec_h = avctx->height;
+ ctx->hwdec_profile != avctx->profile ||
+ ctx->hwdec_request_reinit;
+ ctx->hwdec_w = avctx->coded_width;
+ ctx->hwdec_h = avctx->coded_height;
ctx->hwdec_fmt = ctx->hwdec->image_format;
ctx->hwdec_profile = avctx->profile;
- if (ctx->hwdec->init_decoder && change) {
- if (ctx->hwdec->init_decoder(ctx, ctx->hwdec_fmt,
- ctx->hwdec_w, ctx->hwdec_h) < 0)
+ ctx->hwdec_request_reinit = false;
+ if (change) {
+ if (ctx->hwdec->init_decoder(ctx, ctx->hwdec_w, ctx->hwdec_h) < 0)
{
ctx->hwdec_fmt = 0;
break;
@@ -535,13 +554,23 @@ static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx,
}
}
+ ctx->hwdec_failed = true;
+ for (int i = 0; fmt[i] != AV_PIX_FMT_NONE; i++) {
+ const AVPixFmtDescriptor *d = av_pix_fmt_desc_get(fmt[i]);
+ if (d && !(d->flags & AV_PIX_FMT_FLAG_HWACCEL))
+ return fmt[i];
+ }
return AV_PIX_FMT_NONE;
}
-static struct mp_image *get_surface_hwdec(struct dec_video *vd, AVFrame *pic)
+static int get_buffer2_hwdec(AVCodecContext *avctx, AVFrame *pic, int flags)
{
+ struct dec_video *vd = avctx->opaque;
vd_ffmpeg_ctx *ctx = vd->priv;
+ if (ctx->hwdec_failed)
+ return avcodec_default_get_buffer2(avctx, pic, flags);
+
/* Decoders using ffmpeg's hwaccel architecture (everything except vdpau)
* can fall back to software decoding automatically. However, we don't
* want that: multithreading was already disabled. ffmpeg's fallback
@@ -555,45 +584,27 @@ static struct mp_image *get_surface_hwdec(struct dec_video *vd, AVFrame *pic)
*/
int imgfmt = pixfmt2imgfmt(pic->format);
if (!IMGFMT_IS_HWACCEL(imgfmt) || !ctx->hwdec)
- return NULL;
-
- // Using frame->width/height is bad. For non-mod 16 video (which would
- // require alignment of frame sizes) we want the decoded size, not the
- // aligned size. At least vdpau needs this: the video mixer is created
- // with decoded size, and the video surfaces must have matching size.
- int w = ctx->avctx->width;
- int h = ctx->avctx->height;
-
- if (ctx->hwdec->init_decoder) {
- if (imgfmt != ctx->hwdec_fmt && w != ctx->hwdec_w && h != ctx->hwdec_h)
- return NULL;
- }
-
- struct mp_image *mpi = ctx->hwdec->allocate_image(ctx, imgfmt, w, h);
+ return -1;
- if (mpi) {
- for (int i = 0; i < 4; i++)
- pic->data[i] = mpi->planes[i];
- }
+ // We expect it to use the exact size used to create the hw decoder in
+ // get_format_hwdec(). For cropped video, this is expected to be the
+ // uncropped size (usually coded_width/coded_height).
+ int w = pic->width;
+ int h = pic->height;
- return mpi;
-}
-
-static void free_mpi(void *opaque, uint8_t *data)
-{
- struct mp_image *mpi = opaque;
- talloc_free(mpi);
-}
-
-static int get_buffer2_hwdec(AVCodecContext *avctx, AVFrame *pic, int flags)
-{
- struct dec_video *vd = avctx->opaque;
+ if (imgfmt != ctx->hwdec_fmt && w != ctx->hwdec_w && h != ctx->hwdec_h)
+ return -1;
- struct mp_image *mpi = get_surface_hwdec(vd, pic);
+ struct mp_image *mpi = ctx->hwdec->allocate_image(ctx, w, h);
if (!mpi)
return -1;
- pic->buf[0] = av_buffer_create(NULL, 0, free_mpi, mpi, 0);
+ for (int i = 0; i < 4; i++) {
+ pic->data[i] = mpi->planes[i];
+ pic->buf[i] = mpi->bufs[i];
+ mpi->bufs[i] = NULL;
+ }
+ talloc_free(mpi);
return 0;
}
@@ -621,11 +632,16 @@ static int decode(struct dec_video *vd, struct demux_packet *packet,
hwdec_lock(ctx);
ret = avcodec_decode_video2(avctx, ctx->pic, &got_picture, &pkt);
hwdec_unlock(ctx);
- if (ret < 0) {
- MP_WARN(vd, "Error while decoding frame!\n");
+
+ if (ctx->hwdec_failed || ret < 0) {
+ if (ret < 0)
+ MP_WARN(vd, "Error while decoding frame!\n");
return -1;
}
+ if (ctx->hwdec_request_reinit)
+ avcodec_flush_buffers(avctx);
+
// Skipped frame, or delayed output due to multithreaded decoding.
if (!got_picture)
return 0;
@@ -649,21 +665,6 @@ static int decode(struct dec_video *vd, struct demux_packet *packet,
return 1;
}
-static int force_fallback(struct dec_video *vd)
-{
- vd_ffmpeg_ctx *ctx = vd->priv;
- if (ctx->software_fallback_decoder) {
- uninit_avctx(vd);
- MP_ERR(vd, "Error using hardware "
- "decoding, falling back to software decoding.\n");
- const char *decoder = ctx->software_fallback_decoder;
- ctx->software_fallback_decoder = NULL;
- init_avctx(vd, decoder, NULL);
- return ctx->avctx ? CONTROL_OK : CONTROL_ERROR;
- }
- return CONTROL_FALSE;
-}
-
static struct mp_image *decode_with_fallback(struct dec_video *vd,
struct demux_packet *packet, int flags)
{
@@ -675,10 +676,20 @@ static struct mp_image *decode_with_fallback(struct dec_video *vd,
int res = decode(vd, packet, flags, &mpi);
if (res < 0) {
// Failed hardware decoding? Try again in software.
- if (force_fallback(vd) == CONTROL_OK)
+ if (force_fallback(vd) && ctx->avctx)
decode(vd, packet, flags, &mpi);
}
+ if (mpi && !ctx->hwdec_notified && vd->opts->hwdec_api != HWDEC_NONE) {
+ if (ctx->hwdec) {
+ MP_INFO(vd, "Using hardware decoding (%s).\n",
+ m_opt_choice_str(mp_hwdec_names, ctx->hwdec->type));
+ } else {
+ MP_INFO(vd, "Using software decoding.\n");
+ }
+ ctx->hwdec_notified = true;
+ }
+
return mpi;
}
@@ -698,14 +709,16 @@ static int control(struct dec_video *vd, int cmd, void *arg)
*(int *)arg = delay;
return CONTROL_TRUE;
case VDCTRL_GET_HWDEC: {
- int hwdec = ctx->selected_hwdec;
+ int hwdec = ctx->hwdec ? ctx->hwdec->type : 0;
if (!ctx->software_fallback_decoder)
hwdec = 0;
*(int *)arg = hwdec;
return CONTROL_TRUE;
}
case VDCTRL_FORCE_HWDEC_FALLBACK:
- return force_fallback(vd);
+ if (force_fallback(vd))
+ return ctx->avctx ? CONTROL_OK : CONTROL_ERROR;
+ return CONTROL_FALSE;
}
return CONTROL_UNKNOWN;
}
diff --git a/video/decode/vda.c b/video/decode/vda.c
index 6e54479..538d21b 100644
--- a/video/decode/vda.c
+++ b/video/decode/vda.c
@@ -26,12 +26,12 @@
#include "video/decode/lavc.h"
#include "config.h"
-
static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info,
const char *decoder)
{
- hwdec_request_api(info, "vda");
-
+ hwdec_request_api(info, "videotoolbox");
+ if (!info || !info->hwctx)
+ return HWDEC_ERR_NO_CTX;
if (mp_codec_to_av_codec_id(decoder) != AV_CODEC_ID_H264)
return HWDEC_ERR_NO_CODEC;
return 0;
@@ -73,10 +73,17 @@ static void print_vda_error(struct mp_log *log, int lev, char *message,
mp_msg(log, lev, "%s: %d\n", message, error_code);
}
-static int init_decoder(struct lavc_ctx *ctx, int fmt, int w, int h)
+static int init_decoder(struct lavc_ctx *ctx, int w, int h)
{
av_vda_default_free(ctx->avctx);
+#if HAVE_VDA_DEFAULT_INIT2
+ AVVDAContext *vdactx = av_vda_alloc_context();
+ vdactx->cv_pix_fmt_type = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
+ int err = av_vda_default_init2(ctx->avctx, vdactx);
+#else
int err = av_vda_default_init(ctx->avctx);
+#endif
+
if (err < 0) {
print_vda_error(ctx->log, MSGL_ERR, "failed to init VDA decoder", err);
return -1;
@@ -90,6 +97,16 @@ static void uninit(struct lavc_ctx *ctx)
av_vda_default_free(ctx->avctx);
}
+static struct mp_image *process_image(struct lavc_ctx *ctx, struct mp_image *img)
+{
+ // Same representation. IMGFMT_VDA is only needed to select the libavcodec
+ // hwaccel driver.
+ if (img->imgfmt == IMGFMT_VDA)
+ mp_image_setfmt(img, IMGFMT_VIDEOTOOLBOX);
+
+ return img;
+}
+
const struct vd_lavc_hwdec mp_vd_lavc_vda = {
.type = HWDEC_VDA,
.image_format = IMGFMT_VDA,
@@ -97,4 +114,5 @@ const struct vd_lavc_hwdec mp_vd_lavc_vda = {
.init = init,
.uninit = uninit,
.init_decoder = init_decoder,
+ .process_image = process_image,
};
diff --git a/video/decode/vdpau.c b/video/decode/vdpau.c
index 83dfa0b..9a3e7e2 100644
--- a/video/decode/vdpau.c
+++ b/video/decode/vdpau.c
@@ -15,158 +15,61 @@
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <stddef.h>
-#include <assert.h>
-
#include <libavcodec/avcodec.h>
#include <libavcodec/vdpau.h>
#include <libavutil/common.h>
-#include "config.h"
#include "lavc.h"
#include "common/common.h"
-#include "common/av_common.h"
-#include "video/fmt-conversion.h"
#include "video/vdpau.h"
#include "video/hwdec.h"
-#include "video/decode/dec_video.h"
struct priv {
struct mp_log *log;
struct mp_vdpau_ctx *mpvdp;
- struct vdp_functions *vdp;
uint64_t preemption_counter;
- int fmt, w, h;
-
- AVVDPAUContext *context;
};
-#define PE(av_codec_id, ff_profile, vdp_profile) \
- {AV_CODEC_ID_ ## av_codec_id, FF_PROFILE_ ## ff_profile, \
- VDP_DECODER_PROFILE_ ## vdp_profile}
-
-static const struct hwdec_profile_entry profiles[] = {
- PE(MPEG1VIDEO, UNKNOWN, MPEG1),
- PE(MPEG2VIDEO, MPEG2_MAIN, MPEG2_MAIN),
- PE(MPEG2VIDEO, MPEG2_SIMPLE, MPEG2_SIMPLE),
- PE(MPEG4, MPEG4_ADVANCED_SIMPLE, MPEG4_PART2_ASP),
- PE(MPEG4, MPEG4_SIMPLE, MPEG4_PART2_SP),
- PE(H264, H264_HIGH, H264_HIGH),
- PE(H264, H264_MAIN, H264_MAIN),
- PE(H264, H264_BASELINE, H264_BASELINE),
- PE(VC1, VC1_ADVANCED, VC1_ADVANCED),
- PE(VC1, VC1_MAIN, VC1_MAIN),
- PE(VC1, VC1_SIMPLE, VC1_SIMPLE),
- PE(WMV3, VC1_ADVANCED, VC1_ADVANCED),
- PE(WMV3, VC1_MAIN, VC1_MAIN),
- PE(WMV3, VC1_SIMPLE, VC1_SIMPLE),
- {0}
-};
-
-static int init_decoder(struct lavc_ctx *ctx, int fmt, int w, int h)
+static int init_decoder(struct lavc_ctx *ctx, int w, int h)
{
struct priv *p = ctx->hwdec_priv;
- struct vdp_functions *vdp = &p->mpvdp->vdp;
- VdpDevice vdp_device = p->mpvdp->vdp_device;
- VdpStatus vdp_st;
-
- p->fmt = fmt;
- p->w = w;
- p->h = h;
// During preemption, pretend everything is ok.
if (mp_vdpau_handle_preemption(p->mpvdp, &p->preemption_counter) < 0)
return 0;
- if (p->context->decoder != VDP_INVALID_HANDLE)
- vdp->decoder_destroy(p->context->decoder);
-
- const struct hwdec_profile_entry *pe = hwdec_find_profile(ctx, profiles);
- if (!pe) {
- MP_ERR(p, "Unsupported codec or profile.\n");
- goto fail;
- }
-
- VdpBool supported;
- uint32_t maxl, maxm, maxw, maxh;
- vdp_st = vdp->decoder_query_capabilities(vdp_device, pe->hw_profile,
- &supported, &maxl, &maxm,
- &maxw, &maxh);
- CHECK_VDP_WARNING(p, "Querying VDPAU decoder capabilities");
- if (!supported) {
- MP_ERR(p, "Codec or profile not supported by hardware.\n");
- goto fail;
- }
- if (w > maxw || h > maxh) {
- MP_ERR(p, "Video resolution(%dx%d) is larger than the maximum size(%dx%d) supported.\n",
- w, h, maxw, maxh);
- goto fail;
- }
-
- int maxrefs = hwdec_get_max_refs(ctx);
-
- vdp_st = vdp->decoder_create(vdp_device, pe->hw_profile, w, h, maxrefs,
- &p->context->decoder);
- CHECK_VDP_WARNING(p, "Failed creating VDPAU decoder");
- if (vdp_st != VDP_STATUS_OK)
- goto fail;
- return 0;
-
-fail:
- p->context->decoder = VDP_INVALID_HANDLE;
- return -1;
+ return av_vdpau_bind_context(ctx->avctx, p->mpvdp->vdp_device,
+ p->mpvdp->get_proc_address,
+ AV_HWACCEL_FLAG_IGNORE_LEVEL |
+ AV_HWACCEL_FLAG_ALLOW_HIGH_DEPTH);
}
-static struct mp_image *allocate_image(struct lavc_ctx *ctx, int fmt,
- int w, int h)
+static struct mp_image *allocate_image(struct lavc_ctx *ctx, int w, int h)
{
struct priv *p = ctx->hwdec_priv;
- if (mp_vdpau_handle_preemption(p->mpvdp, &p->preemption_counter) == 0) {
- if (init_decoder(ctx, p->fmt, p->w, p->h) < 0)
- return NULL;
- }
+ // In case of preemption, reinit the decoder. Setting hwdec_request_reinit
+ // will cause init_decoder() to be called again.
+ if (mp_vdpau_handle_preemption(p->mpvdp, &p->preemption_counter) == 0)
+ ctx->hwdec_request_reinit = true;
- VdpChromaType chroma;
- mp_vdpau_get_format(IMGFMT_VDPAU, &chroma, NULL);
+ VdpChromaType chroma = 0;
+ uint32_t s_w = w, s_h = h;
+ if (av_vdpau_get_surface_parameters(ctx->avctx, &chroma, &s_w, &s_h) < 0)
+ return NULL;
- return mp_vdpau_get_video_surface(p->mpvdp, chroma, w, h);
+ return mp_vdpau_get_video_surface(p->mpvdp, chroma, s_w, s_h);
}
static void uninit(struct lavc_ctx *ctx)
{
struct priv *p = ctx->hwdec_priv;
- if (!p)
- return;
-
- if (p->context && p->context->decoder != VDP_INVALID_HANDLE)
- p->vdp->decoder_destroy(p->context->decoder);
-
- av_free(p->context);
talloc_free(p);
- ctx->hwdec_priv = NULL;
+ av_freep(&ctx->avctx->hwaccel_context);
}
-#if LIBAVCODEC_VERSION_MICRO >= 100
-static int render2(struct AVCodecContext *avctx, struct AVFrame *frame,
- const VdpPictureInfo *pic_info, uint32_t buffers_used,
- const VdpBitstreamBuffer *buffers)
-{
- struct dec_video *vd = avctx->opaque;
- struct lavc_ctx *ctx = vd->priv;
- struct priv *p = ctx->hwdec_priv;
- VdpVideoSurface surf = (uintptr_t)frame->data[3];
- VdpStatus status;
-
- status = p->vdp->decoder_render(p->context->decoder, surf, pic_info,
- buffers_used, buffers);
-
- return status;
-}
-#endif
-
static int init(struct lavc_ctx *ctx)
{
struct priv *p = talloc_ptrtype(NULL, p);
@@ -176,23 +79,9 @@ static int init(struct lavc_ctx *ctx)
};
ctx->hwdec_priv = p;
- p->context = av_vdpau_alloc_context();
- if (!p->context)
- goto error;
-
- p->vdp = &p->mpvdp->vdp;
-#if LIBAVCODEC_VERSION_MICRO >= 100
- p->context->render2 = render2;
-#else
- p->context->render = p->vdp->decoder_render;
-#endif
- p->context->decoder = VDP_INVALID_HANDLE;
-
if (mp_vdpau_handle_preemption(p->mpvdp, &p->preemption_counter) < 1)
goto error;
- ctx->avctx->hwaccel_context = p->context;
-
return 0;
error:
@@ -206,8 +95,6 @@ static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info,
hwdec_request_api(info, "vdpau");
if (!info || !info->hwctx || !info->hwctx->vdpau_ctx)
return HWDEC_ERR_NO_CTX;
- if (!hwdec_check_codec_support(decoder, profiles))
- return HWDEC_ERR_NO_CODEC;
if (mp_vdpau_guess_if_emulated(info->hwctx->vdpau_ctx))
return HWDEC_ERR_EMULATED;
return 0;
diff --git a/video/decode/videotoolbox.c b/video/decode/videotoolbox.c
new file mode 100644
index 0000000..470f6b6
--- /dev/null
+++ b/video/decode/videotoolbox.c
@@ -0,0 +1,115 @@
+/*
+ * This file is part of mpv.
+ *
+ * Copyright (c) 2015 Sebastien Zwickert
+ *
+ * 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 <libavcodec/version.h>
+#include <libavcodec/videotoolbox.h>
+
+#include "common/av_common.h"
+#include "common/msg.h"
+#include "video/mp_image.h"
+#include "video/decode/lavc.h"
+#include "config.h"
+
+
+static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info,
+ const char *decoder)
+{
+ hwdec_request_api(info, "videotoolbox");
+ if (!info || !info->hwctx)
+ return HWDEC_ERR_NO_CTX;
+ switch (mp_codec_to_av_codec_id(decoder)) {
+ case AV_CODEC_ID_H264:
+ case AV_CODEC_ID_H263:
+ case AV_CODEC_ID_MPEG1VIDEO:
+ case AV_CODEC_ID_MPEG2VIDEO:
+ case AV_CODEC_ID_MPEG4:
+ break;
+ default:
+ return HWDEC_ERR_NO_CODEC;
+ }
+ return 0;
+}
+
+static int init(struct lavc_ctx *ctx)
+{
+ return 0;
+}
+
+struct videotoolbox_error {
+ int code;
+ char *reason;
+};
+
+static const struct videotoolbox_error videotoolbox_errors[] = {
+ { AVERROR(ENOSYS),
+ "Hardware doesn't support accelerated decoding for this stream"
+ " or Videotoolbox decoder is not available at the moment (another"
+ " application is using it)."
+ },
+ { AVERROR(EINVAL),
+ "Invalid configuration provided to VTDecompressionSessionCreate" },
+ { AVERROR_INVALIDDATA,
+ "Generic error returned by the decoder layer. The cause can be Videotoolbox"
+ " found errors in the bitstream." },
+ { 0, NULL },
+};
+
+static void print_videotoolbox_error(struct mp_log *log, int lev, char *message,
+ int error_code)
+{
+ for (int n = 0; videotoolbox_errors[n].code < 0; n++)
+ if (videotoolbox_errors[n].code == error_code) {
+ mp_msg(log, lev, "%s: %s (%d)\n",
+ message, videotoolbox_errors[n].reason, error_code);
+ return;
+ }
+
+ mp_msg(log, lev, "%s: %d\n", message, error_code);
+}
+
+static int init_decoder(struct lavc_ctx *ctx, int w, int h)
+{
+ av_videotoolbox_default_free(ctx->avctx);
+
+ AVVideotoolboxContext *vtctx = av_videotoolbox_alloc_context();
+ vtctx->cv_pix_fmt_type = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
+ int err = av_videotoolbox_default_init2(ctx->avctx, vtctx);
+
+ if (err < 0) {
+ print_videotoolbox_error(ctx->log, MSGL_ERR, "failed to init videotoolbox decoder", err);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void uninit(struct lavc_ctx *ctx)
+{
+ if (ctx->avctx)
+ av_videotoolbox_default_free(ctx->avctx);
+}
+
+const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox = {
+ .type = HWDEC_VIDEOTOOLBOX,
+ .image_format = IMGFMT_VIDEOTOOLBOX,
+ .probe = probe,
+ .init = init,
+ .uninit = uninit,
+ .init_decoder = init_decoder,
+};
diff --git a/video/filter/vf.c b/video/filter/vf.c
index 4f9f43f..4139492 100644
--- a/video/filter/vf.c
+++ b/video/filter/vf.c
@@ -63,6 +63,7 @@ extern const vf_info_t vf_info_vaapi;
extern const vf_info_t vf_info_vapoursynth;
extern const vf_info_t vf_info_vapoursynth_lazy;
extern const vf_info_t vf_info_vdpaupp;
+extern const vf_info_t vf_info_vdpaurb;
extern const vf_info_t vf_info_buffer;
// list of available filters:
@@ -101,11 +102,12 @@ static const vf_info_t *const filter_list[] = {
#if HAVE_VAPOURSYNTH_CORE && HAVE_VAPOURSYNTH_LAZY
&vf_info_vapoursynth_lazy,
#endif
-#if HAVE_VAAPI_VPP
+#if HAVE_VAAPI
&vf_info_vaapi,
#endif
#if HAVE_VDPAU
&vf_info_vdpaupp,
+ &vf_info_vdpaurb,
#endif
NULL
};
diff --git a/video/filter/vf.h b/video/filter/vf.h
index 7683552..62734c9 100644
--- a/video/filter/vf.h
+++ b/video/filter/vf.h
@@ -144,13 +144,12 @@ enum vf_ctrl {
VFCTRL_SEEK_RESET = 1, // reset on picture and PTS discontinuities
VFCTRL_SET_EQUALIZER, // set color options (brightness,contrast etc)
VFCTRL_GET_EQUALIZER, // get color options (brightness,contrast etc)
- VFCTRL_INIT_OSD, // Filter OSD renderer present?
VFCTRL_SET_DEINTERLACE, // Set deinterlacing status
VFCTRL_GET_DEINTERLACE, // Get deinterlacing status
VFCTRL_GET_METADATA, // Get frame metadata from lavfi filters (e.g., cropdetect)
/* Hack to make the OSD state object available to vf_sub which
* access OSD/subtitle state outside of normal OSD draw time. */
- VFCTRL_SET_OSD_OBJ,
+ VFCTRL_INIT_OSD,
};
struct vf_chain *vf_new(struct mpv_global *global);
diff --git a/video/filter/vf_dlopen.h b/video/filter/vf_dlopen.h
index e225cd3..0c8a4d9 100644
--- a/video/filter/vf_dlopen.h
+++ b/video/filter/vf_dlopen.h
@@ -1,3 +1,7 @@
+/*
+ * Warning: this filter is deprecated.
+ */
+
#ifndef VF_DLOPEN_H
#define VF_DLOPEN_H
diff --git a/video/filter/vf_scale.c b/video/filter/vf_scale.c
index 29d7376..a71f2c1 100644
--- a/video/filter/vf_scale.c
+++ b/video/filter/vf_scale.c
@@ -81,7 +81,7 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
int round_w = 0, round_h = 0;
if (!best) {
- MP_WARN(vf, "SwScale: no supported outfmt found :(\n");
+ MP_WARN(vf, "no supported output format found\n");
return -1;
}
@@ -102,10 +102,7 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
if (vf->priv->w < -3 || vf->priv->h < -3 ||
(vf->priv->w < -1 && vf->priv->h < -1))
{
- // TODO: establish a direct connection to the user's brain
- // and find out what the heck he thinks MPlayer should do
- // with this nonsense.
- MP_ERR(vf, "SwScale: EUSERBROKEN Check your parameters, they make no sense!\n");
+ MP_ERR(vf, "invalid parameters\n");
return -1;
}
@@ -142,9 +139,7 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
}
}
- MP_DBG(vf, "SwScale: scaling %dx%d %s to %dx%d %s \n",
- width, height, vo_format_name(in->imgfmt), vf->priv->w, vf->priv->h,
- vo_format_name(best));
+ MP_DBG(vf, "scaling %dx%d to %dx%d\n", width, height, vf->priv->w, vf->priv->h);
// Compute new d_width and d_height, preserving aspect
// while ensuring that both are >= output size in pixels.
@@ -241,10 +236,6 @@ static int vf_open(vf_instance_t *vf)
vf->priv->sws->log = vf->log;
vf->priv->sws->params[0] = vf->priv->param[0];
vf->priv->sws->params[1] = vf->priv->param[1];
-
- MP_VERBOSE(vf, "SwScale params: %d x %d (-1=no scaling)\n",
- vf->priv->cfg_w, vf->priv->cfg_h);
-
return 1;
}
diff --git a/video/filter/vf_stereo3d.c b/video/filter/vf_stereo3d.c
index acc6629..c478980 100644
--- a/video/filter/vf_stereo3d.c
+++ b/video/filter/vf_stereo3d.c
@@ -66,339 +66,16 @@ typedef enum stereo_code {
STEREO_CODE_COUNT //no value set - TODO: needs autodetection
} stereo_code;
-typedef struct component {
- int fmt;
- unsigned int width;
- unsigned int height;
- unsigned int off_left;
- unsigned int off_right;
- unsigned int row_left;
- unsigned int row_right;
-} component;
-
-//==global variables==//
-static const int ana_coeff[][3][6] = {
- [ANAGLYPH_RC_GRAY] =
- {{19595, 38470, 7471, 0, 0, 0},
- { 0, 0, 0, 19595, 38470, 7471},
- { 0, 0, 0, 19595, 38470, 7471}},
- [ANAGLYPH_RC_HALF] =
- {{19595, 38470, 7471, 0, 0, 0},
- { 0, 0, 0, 0, 65536, 0},
- { 0, 0, 0, 0, 0, 65536}},
- [ANAGLYPH_RC_COLOR] =
- {{65536, 0, 0, 0, 0, 0},
- { 0, 0, 0, 0, 65536, 0},
- { 0, 0, 0, 0, 0, 65536}},
- [ANAGLYPH_RC_DUBOIS] =
- {{29891, 32800, 11559, -2849, -5763, -102},
- {-2627, -2479, -1033, 24804, 48080, -1209},
- { -997, -1350, -358, -4729, -7403, 80373}},
- [ANAGLYPH_GM_GRAY] =
- {{ 0, 0, 0, 19595, 38470, 7471},
- {19595, 38470, 7471, 0, 0, 0},
- { 0, 0, 0, 19595, 38470, 7471}},
- [ANAGLYPH_GM_HALF] =
- {{ 0, 0, 0, 65536, 0, 0},
- {19595, 38470, 7471, 0, 0, 0},
- { 0, 0, 0, 0, 0, 65536}},
- [ANAGLYPH_GM_COLOR] =
- {{ 0, 0, 0, 65536, 0, 0},
- { 0, 65536, 0, 0, 0, 0},
- { 0, 0, 0, 0, 0, 65536}},
- [ANAGLYPH_GM_DUBOIS] =
- {{-4063,-10354, -2556, 34669, 46203, 1573},
- {18612, 43778, 9372, -1049, -983, -4260},
- { -983, -1769, 1376, 590, 4915, 61407}},
- [ANAGLYPH_YB_GRAY] =
- {{ 0, 0, 0, 19595, 38470, 7471},
- { 0, 0, 0, 19595, 38470, 7471},
- {19595, 38470, 7471, 0, 0, 0}},
- [ANAGLYPH_YB_HALF] =
- {{ 0, 0, 0, 65536, 0, 0},
- { 0, 0, 0, 0, 65536, 0},
- {19595, 38470, 7471, 0, 0, 0}},
- [ANAGLYPH_YB_COLOR] =
- {{ 0, 0, 0, 65536, 0, 0},
- { 0, 0, 0, 0, 65536, 0},
- { 0, 0, 65536, 0, 0, 0}},
- [ANAGLYPH_YB_DUBOIS] =
- {{65535,-12650,18451, -987, -7590, -1049},
- {-1604, 56032, 4196, 370, 3826, -1049},
- {-2345,-10676, 1358, 5801, 11416, 56217}},
-};
-
struct vf_priv_s {
- component in;
- component out;
+ int in_fmt;
+ int out_fmt;
bool auto_in;
- int ana_matrix[3][6];
- unsigned int width;
- unsigned int height;
- unsigned int row_step;
struct vf_lw_opts *lw_opts;
} const vf_priv_default = {
- {SIDE_BY_SIDE_LR},
- {ANAGLYPH_RC_DUBOIS}
+ SIDE_BY_SIDE_LR,
+ ANAGLYPH_RC_DUBOIS,
};
-static bool handle_auto_in(struct vf_instance *vf);
-
-//==functions==//
-static inline uint8_t ana_convert(int coeff[6], uint8_t left[3], uint8_t right[3])
-{
- int sum;
-
- sum = coeff[0] * left[0] + coeff[3] * right[0]; //red in
- sum += coeff[1] * left[1] + coeff[4] * right[1]; //green in
- sum += coeff[2] * left[2] + coeff[5] * right[2]; //blue in
- return av_clip_uint8(sum >> 16);
-}
-
-static int config(struct vf_instance *vf, int width, int height, int d_width,
- int d_height, unsigned int flags, unsigned int outfmt)
-{
- if ((width & 1) || (height & 1)) {
- MP_WARN(vf, "[stereo3d] invalid height or width\n");
- return 0;
- }
- //default input values
- vf->priv->width = width;
- vf->priv->height = height;
- vf->priv->row_step = 1;
- vf->priv->in.width = width;
- vf->priv->in.height = height;
- vf->priv->in.off_left = 0;
- vf->priv->in.off_right = 0;
- vf->priv->in.row_left = 0;
- vf->priv->in.row_right = 0;
-
- if (vf->priv->auto_in && !handle_auto_in(vf))
- return 0;
-
- //check input format
- switch (vf->priv->in.fmt) {
- case SIDE_BY_SIDE_2_LR:
- d_width *= 2;
- case SIDE_BY_SIDE_LR:
- vf->priv->width = width / 2;
- vf->priv->in.off_right = vf->priv->width * 3;
- break;
- case SIDE_BY_SIDE_2_RL:
- d_width *= 2;
- case SIDE_BY_SIDE_RL:
- vf->priv->width = width / 2;
- vf->priv->in.off_left = vf->priv->width * 3;
- break;
- case ABOVE_BELOW_2_LR:
- d_height *= 2;
- case ABOVE_BELOW_LR:
- vf->priv->height = height / 2;
- vf->priv->in.row_right = vf->priv->height;
- break;
- case ABOVE_BELOW_2_RL:
- d_height *= 2;
- case ABOVE_BELOW_RL:
- vf->priv->height = height / 2;
- vf->priv->in.row_left = vf->priv->height;
- break;
- default:
- MP_WARN(vf, "[stereo3d] stereo format of input is not supported\n");
- return 0;
- break;
- }
- //default output values
- vf->priv->out.width = vf->priv->width;
- vf->priv->out.height = vf->priv->height;
- vf->priv->out.off_left = 0;
- vf->priv->out.off_right = 0;
- vf->priv->out.row_left = 0;
- vf->priv->out.row_right = 0;
-
- //check output format
- switch (vf->priv->out.fmt) {
- case ANAGLYPH_RC_GRAY:
- case ANAGLYPH_RC_HALF:
- case ANAGLYPH_RC_COLOR:
- case ANAGLYPH_RC_DUBOIS:
- case ANAGLYPH_GM_GRAY:
- case ANAGLYPH_GM_HALF:
- case ANAGLYPH_GM_COLOR:
- case ANAGLYPH_GM_DUBOIS:
- case ANAGLYPH_YB_GRAY:
- case ANAGLYPH_YB_HALF:
- case ANAGLYPH_YB_COLOR:
- case ANAGLYPH_YB_DUBOIS:
- memcpy(vf->priv->ana_matrix, ana_coeff[vf->priv->out.fmt],
- sizeof(vf->priv->ana_matrix));
- break;
- case SIDE_BY_SIDE_2_LR:
- d_width /= 2;
- case SIDE_BY_SIDE_LR:
- vf->priv->out.width = vf->priv->width * 2;
- vf->priv->out.off_right = vf->priv->width * 3;
- break;
- case SIDE_BY_SIDE_2_RL:
- d_width /= 2;
- case SIDE_BY_SIDE_RL:
- vf->priv->out.width = vf->priv->width * 2;
- vf->priv->out.off_left = vf->priv->width * 3;
- break;
- case ABOVE_BELOW_2_LR:
- d_height /= 2;
- case ABOVE_BELOW_LR:
- vf->priv->out.height = vf->priv->height * 2;
- vf->priv->out.row_right = vf->priv->height;
- break;
- case ABOVE_BELOW_2_RL:
- d_height /= 2;
- case ABOVE_BELOW_RL:
- vf->priv->out.height = vf->priv->height * 2;
- vf->priv->out.row_left = vf->priv->height;
- break;
- case INTERLEAVE_ROWS_LR:
- vf->priv->row_step = 2;
- vf->priv->height = vf->priv->height / 2;
- vf->priv->out.off_right = vf->priv->width * 3;
- vf->priv->in.off_right += vf->priv->in.width * 3;
- break;
- case INTERLEAVE_ROWS_RL:
- vf->priv->row_step = 2;
- vf->priv->height = vf->priv->height / 2;
- vf->priv->out.off_left = vf->priv->width * 3;
- vf->priv->in.off_left += vf->priv->in.width * 3;
- break;
- case MONO_R:
- //same as MONO_L only needs switching of input offsets
- vf->priv->in.off_left = vf->priv->in.off_right;
- vf->priv->in.row_left = vf->priv->in.row_right;
- //nobreak;
- case MONO_L:
- //use default settings
- break;
- default:
- MP_WARN(vf, "[stereo3d] stereo format of output is not supported\n");
- return 0;
- break;
- }
- vf_rescale_dsize(&d_width, &d_height, width, height,
- vf->priv->out.width, vf->priv->out.height);
- return vf_next_config(vf, vf->priv->out.width, vf->priv->out.height,
- d_width, d_height, flags, outfmt);
-}
-
-static struct mp_image *filter(struct vf_instance *vf, struct mp_image *mpi)
-{
- if (vf->priv->in.fmt == vf->priv->out.fmt) { //nothing to do
- return mpi;
- } else {
- int out_off_left, out_off_right;
- int in_off_left = vf->priv->in.row_left * mpi->stride[0] +
- vf->priv->in.off_left;
- int in_off_right = vf->priv->in.row_right * mpi->stride[0] +
- vf->priv->in.off_right;
-
- struct mp_image *dmpi = vf_alloc_out_image(vf);
- if (!dmpi)
- return NULL;
- mp_image_copy_attributes(dmpi, mpi);
-
- out_off_left = vf->priv->out.row_left * dmpi->stride[0] +
- vf->priv->out.off_left;
- out_off_right = vf->priv->out.row_right * dmpi->stride[0] +
- vf->priv->out.off_right;
-
- switch (vf->priv->out.fmt) {
- case SIDE_BY_SIDE_LR:
- case SIDE_BY_SIDE_RL:
- case SIDE_BY_SIDE_2_LR:
- case SIDE_BY_SIDE_2_RL:
- case ABOVE_BELOW_LR:
- case ABOVE_BELOW_RL:
- case ABOVE_BELOW_2_LR:
- case ABOVE_BELOW_2_RL:
- case INTERLEAVE_ROWS_LR:
- case INTERLEAVE_ROWS_RL:
- memcpy_pic(dmpi->planes[0] + out_off_left,
- mpi->planes[0] + in_off_left,
- 3 * vf->priv->width,
- vf->priv->height,
- dmpi->stride[0] * vf->priv->row_step,
- mpi->stride[0] * vf->priv->row_step);
- memcpy_pic(dmpi->planes[0] + out_off_right,
- mpi->planes[0] + in_off_right,
- 3 * vf->priv->width,
- vf->priv->height,
- dmpi->stride[0] * vf->priv->row_step,
- mpi->stride[0] * vf->priv->row_step);
- break;
- case MONO_L:
- case MONO_R:
- memcpy_pic(dmpi->planes[0],
- mpi->planes[0] + in_off_left,
- 3 * vf->priv->width,
- vf->priv->height,
- dmpi->stride[0],
- mpi->stride[0]);
- break;
- case ANAGLYPH_RC_GRAY:
- case ANAGLYPH_RC_HALF:
- case ANAGLYPH_RC_COLOR:
- case ANAGLYPH_RC_DUBOIS:
- case ANAGLYPH_GM_GRAY:
- case ANAGLYPH_GM_HALF:
- case ANAGLYPH_GM_COLOR:
- case ANAGLYPH_GM_DUBOIS:
- case ANAGLYPH_YB_GRAY:
- case ANAGLYPH_YB_HALF:
- case ANAGLYPH_YB_COLOR:
- case ANAGLYPH_YB_DUBOIS: {
- int x,y,il,ir,o;
- unsigned char *source = mpi->planes[0];
- unsigned char *dest = dmpi->planes[0];
- unsigned int out_width = vf->priv->out.width;
- int *ana_matrix[3];
-
- for(int i = 0; i < 3; i++)
- ana_matrix[i] = vf->priv->ana_matrix[i];
-
- for (y = 0; y < vf->priv->out.height; y++) {
- o = dmpi->stride[0] * y;
- il = in_off_left + y * mpi->stride[0];
- ir = in_off_right + y * mpi->stride[0];
- for (x = 0; x < out_width; x++) {
- dest[o ] = ana_convert(
- ana_matrix[0], source + il, source + ir); //red out
- dest[o + 1] = ana_convert(
- ana_matrix[1], source + il, source + ir); //green out
- dest[o + 2] = ana_convert(
- ana_matrix[2], source + il, source + ir); //blue out
- il += 3;
- ir += 3;
- o += 3;
- }
- }
- break;
- }
- default:
- MP_WARN(vf, "[stereo3d] stereo format of output is not supported\n");
- return NULL;
- break;
- }
-
- talloc_free(mpi);
- return dmpi;
- }
-}
-
-static int query_format(struct vf_instance *vf, unsigned int fmt)
-{
- switch (fmt)
- case IMGFMT_RGB24:
- return vf_next_query_format(vf, fmt);
- return 0;
-}
-
const struct m_opt_choice_alternatives stereo_code_names[] = {
{"arcg", ANAGLYPH_RC_GRAY},
{"anaglyph_red_cyan_gray", ANAGLYPH_RC_GRAY},
@@ -455,23 +132,12 @@ const struct m_opt_choice_alternatives stereo_code_names[] = {
{ NULL, 0}
};
-// Fortunately, the short names are the same as the libavfilter port.
-// The long names won't work, though.
-static const char *rev_map_name(int val)
-{
- for (int n = 0; stereo_code_names[n].name; n++) {
- if (stereo_code_names[n].value == val)
- return stereo_code_names[n].name;
- }
- return NULL;
-}
-
// Extremely stupid; can be dropped when the internal filter is dropped,
// and OPT_CHOICE_C() can be used instead.
static int opt_to_stereo3dmode(int val)
{
- // Find x for rev_map_name(val) == MP_STEREO3D_NAME(x)
- const char *name = rev_map_name(val);
+ // Find x for name == MP_STEREO3D_NAME(x)
+ const char *name = m_opt_choice_str(stereo_code_names, val);
for (int n = 0; n < MP_STEREO3D_COUNT; n++) {
const char *o = MP_STEREO3D_NAME(val);
if (name && o && strcmp(o, name) == 0)
@@ -479,33 +145,6 @@ static int opt_to_stereo3dmode(int val)
}
return MP_STEREO3D_INVALID;
}
-static int stereo3dmode_to_opt(int val)
-{
- // Find x for rev_map_name(x) == MP_STEREO3D_NAME(val)
- const char *name = MP_STEREO3D_NAME(val);
- for (int n = 0; stereo_code_names[n].name; n++) {
- if (name && strcmp(stereo_code_names[n].name, name) == 0)
- return stereo_code_names[n].value;
- }
- return -1;
-}
-
-static bool handle_auto_in(struct vf_instance *vf)
-{
- if (vf->priv->auto_in) {
- int inv = stereo3dmode_to_opt(vf->fmt_in.stereo_in);
- if (inv < 0) {
- MP_ERR(vf, "Unknown/unsupported 3D mode.\n");
- return false;
- }
- vf->priv->in.fmt = inv;
- vf->fmt_out.stereo_in = vf->fmt_out.stereo_out =
- opt_to_stereo3dmode(vf->priv->out.fmt);
- }
- return true;
-}
-
-#if HAVE_LIBAVFILTER
static int lavfi_reconfig(struct vf_instance *vf,
struct mp_image_params *in,
@@ -519,15 +158,15 @@ static int lavfi_reconfig(struct vf_instance *vf,
return -1;
}
vf_lw_update_graph(vf, "stereo3d", "%s:%s",
- inf, rev_map_name(p->out.fmt));
- out->stereo_in = out->stereo_out = opt_to_stereo3dmode(p->out.fmt);
+ inf, m_opt_choice_str(stereo_code_names, p->out_fmt));
+ out->stereo_in = out->stereo_out = opt_to_stereo3dmode(p->out_fmt);
}
return 0;
}
static void lavfi_init(vf_instance_t *vf)
{
- if (vf->priv->in.fmt == STEREO_AUTO &&
+ if (vf->priv->in_fmt == STEREO_AUTO &&
vf_lw_set_graph(vf, vf->priv->lw_opts, "stereo3d", "null") >= 0)
{
vf_lw_set_reconfig_cb(vf, lavfi_reconfig);
@@ -535,33 +174,18 @@ static void lavfi_init(vf_instance_t *vf)
}
if (vf_lw_set_graph(vf, vf->priv->lw_opts, "stereo3d", "%s:%s",
- rev_map_name(vf->priv->in.fmt),
- rev_map_name(vf->priv->out.fmt)) >= 0)
+ m_opt_choice_str(stereo_code_names, vf->priv->in_fmt),
+ m_opt_choice_str(stereo_code_names, vf->priv->out_fmt)) >= 0)
return;
}
-#else
-
-const struct m_sub_options vf_lw_conf = {0};
-
-static void lavfi_init(vf_instance_t *vf)
-{
- // doing nothing will make it use the internal implementation
-}
-
-#endif
-
static int vf_open(vf_instance_t *vf)
{
- vf->config = config;
- vf->filter = filter;
- vf->query_format = query_format;
-
- if (vf->priv->out.fmt == STEREO_AUTO) {
+ if (vf->priv->out_fmt == STEREO_AUTO) {
MP_FATAL(vf, "No autodetection for stereo output.\n");
return 0;
}
- if (vf->priv->in.fmt == STEREO_AUTO)
+ if (vf->priv->in_fmt == STEREO_AUTO)
vf->priv->auto_in = 1;
lavfi_init(vf);
@@ -570,10 +194,8 @@ static int vf_open(vf_instance_t *vf)
#define OPT_BASE_STRUCT struct vf_priv_s
static const m_option_t vf_opts_fields[] = {
- OPT_GENERAL(int, "in", in.fmt, 0, .type = CONF_TYPE_CHOICE,
- .priv = (void *)stereo_code_names),
- OPT_GENERAL(int, "out", out.fmt, 0, .type = CONF_TYPE_CHOICE,
- .priv = (void *)stereo_code_names),
+ OPT_CHOICE_C("in", in_fmt, 0, stereo_code_names),
+ OPT_CHOICE_C("out", out_fmt, 0, stereo_code_names),
OPT_SUBSTRUCT("", lw_opts, vf_lw_conf, 0),
{0}
};
diff --git a/video/filter/vf_sub.c b/video/filter/vf_sub.c
index 775d944..20069ee 100644
--- a/video/filter/vf_sub.c
+++ b/video/filter/vf_sub.c
@@ -121,10 +121,8 @@ static int query_format(struct vf_instance *vf, unsigned int fmt)
static int control(vf_instance_t *vf, int request, void *data)
{
switch (request) {
- case VFCTRL_SET_OSD_OBJ:
- vf->priv->osd = data;
- return CONTROL_TRUE;
case VFCTRL_INIT_OSD:
+ vf->priv->osd = data;
return CONTROL_TRUE;
}
return CONTROL_UNKNOWN;
diff --git a/video/filter/vf_vapoursynth.c b/video/filter/vf_vapoursynth.c
index ff9421d..56cbf8e 100644
--- a/video/filter/vf_vapoursynth.c
+++ b/video/filter/vf_vapoursynth.c
@@ -1,18 +1,18 @@
/*
* 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 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 General Public License for more details.
+ * GNU Lesser 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/>.
+ * 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 <stdio.h>
diff --git a/video/filter/vf_vavpp.c b/video/filter/vf_vavpp.c
index f3ca627..cb11e69 100644
--- a/video/filter/vf_vavpp.c
+++ b/video/filter/vf_vavpp.c
@@ -15,6 +15,8 @@
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <assert.h>
+
#include <va/va.h>
#include <va/va_vpp.h>
@@ -35,10 +37,16 @@ static bool check_error(struct vf_instance *vf, VAStatus status, const char *msg
struct surface_refs {
VASurfaceID *surfaces;
- int num_allocated;
- int num_required;
+ int num_surfaces;
};
+static void add_surface(void *ta_ctx, struct surface_refs *refs, struct mp_image *s)
+{
+ VASurfaceID id = va_surface_id(s);
+ if (id != VA_INVALID_ID)
+ MP_TARRAY_APPEND(ta_ctx, refs->surfaces, refs->num_surfaces, id);
+}
+
struct pipeline {
VABufferID *filters;
int num_filters;
@@ -49,8 +57,8 @@ struct pipeline {
};
struct vf_priv_s {
- double prev_pts;
int deint_type; // 0: none, 1: discard, 2: double fps
+ int interlaced_only;
bool do_deint;
VABufferID buffers[VAProcFilterCount];
int num_buffers;
@@ -62,13 +70,24 @@ struct vf_priv_s {
struct pipeline pipe;
struct mp_image_pool *pool;
int current_rt_format;
+
+ int needed_future_frames;
+ int needed_past_frames;
+
+ // Queue of input frames, used to determine past/current/future frames.
+ // queue[0] is the newest frame, queue[num_queue - 1] the oldest.
+ struct mp_image **queue;
+ int num_queue;
+ // queue[current_pos] is the current frame, unless current_pos is not a
+ // valid index.
+ int current_pos;
};
static const struct vf_priv_s vf_priv_default = {
- .prev_pts = MP_NOPTS_VALUE,
.config = VA_INVALID_ID,
.context = VA_INVALID_ID,
.deint_type = 2,
+ .interlaced_only = 1,
};
// The array items must match with the "deint" suboption values.
@@ -81,13 +100,13 @@ static const int deint_algorithm[] = {
[5] = VAProcDeinterlacingMotionCompensated,
};
-static inline void realloc_refs(struct surface_refs *refs, int num)
+static void flush_frames(struct vf_instance *vf)
{
- if (refs->num_allocated < num) {
- refs->surfaces = realloc(refs->surfaces, sizeof(VASurfaceID)*num);
- refs->num_allocated = num;
- }
- refs->num_required = num;
+ struct vf_priv_s *p = vf->priv;
+ for (int n = 0; n < p->num_queue; n++)
+ talloc_free(p->queue[n]);
+ p->num_queue = 0;
+ p->current_pos = -1;
}
static bool update_pipeline(struct vf_instance *vf, bool deint)
@@ -96,12 +115,12 @@ static bool update_pipeline(struct vf_instance *vf, bool deint)
VABufferID *filters = p->buffers;
int num_filters = p->num_buffers;
if (p->deint_type && !deint) {
- ++filters;
- --num_filters;
+ filters++;
+ num_filters--;
}
if (filters == p->pipe.filters && num_filters == p->pipe.num_filters)
return true;
- p->pipe.forward.num_required = p->pipe.backward.num_required = 0;
+ p->pipe.forward.num_surfaces = p->pipe.backward.num_surfaces = 0;
p->pipe.num_input_colors = p->pipe.num_output_colors = 0;
p->pipe.num_filters = 0;
p->pipe.filters = NULL;
@@ -120,13 +139,13 @@ static bool update_pipeline(struct vf_instance *vf, bool deint)
p->pipe.num_filters = num_filters;
p->pipe.num_input_colors = caps.num_input_color_standards;
p->pipe.num_output_colors = caps.num_output_color_standards;
- realloc_refs(&p->pipe.forward, caps.num_forward_references);
- realloc_refs(&p->pipe.backward, caps.num_backward_references);
+ p->needed_future_frames = caps.num_forward_references;
+ p->needed_past_frames = caps.num_backward_references;
return true;
}
static inline int get_deint_field(struct vf_priv_s *p, int i,
- const struct mp_image *mpi)
+ struct mp_image *mpi)
{
if (!p->do_deint || !(mpi->fields & MP_IMGFIELD_INTERLACED))
return VA_FRAME_PICTURE;
@@ -140,88 +159,127 @@ static struct mp_image *render(struct vf_instance *vf, struct mp_image *in,
VASurfaceID in_id = va_surface_id(in);
if (!p->pipe.filters || in_id == VA_INVALID_ID)
return NULL;
+
struct mp_image *img = mp_image_pool_get(p->pool, IMGFMT_VAAPI, in->w, in->h);
if (!img)
return NULL;
- enum {Begun = 1, Rendered = 2};
- int state = 0;
- do { // not a loop, just for break
- VASurfaceID id = va_surface_id(img);
- if (id == VA_INVALID_ID)
- break;
- VAStatus status = vaBeginPicture(p->display, p->context, id);
- if (!check_error(vf, status, "vaBeginPicture()"))
- break;
- state |= Begun;
- VABufferID buffer = VA_INVALID_ID;
- VAProcPipelineParameterBuffer *param = NULL;
- status = vaCreateBuffer(p->display, p->context,
- VAProcPipelineParameterBufferType,
- sizeof(*param), 1, NULL, &buffer);
- if (!check_error(vf, status, "vaCreateBuffer()"))
- break;
- status = vaMapBuffer(p->display, buffer, (void**)&param);
- if (!check_error(vf, status, "vaMapBuffer()"))
- break;
-
- VAProcFilterParameterBufferDeinterlacing *filter_params;
- status = vaMapBuffer(p->display, *(p->pipe.filters), (void**)&filter_params);
- if (!check_error(vf, status, "vaMapBuffer()"))
- break;
- filter_params->flags = flags & VA_TOP_FIELD ? 0 : VA_DEINTERLACING_BOTTOM_FIELD;
- vaUnmapBuffer(p->display, *(p->pipe.filters));
-
- param->surface = in_id;
- param->surface_region = NULL;
- param->output_region = NULL;
- param->output_background_color = 0;
- param->filter_flags = flags;
- param->filters = p->pipe.filters;
- param->num_filters = p->pipe.num_filters;
- vaUnmapBuffer(p->display, buffer);
- param->forward_references = p->pipe.forward.surfaces;
- param->backward_references = p->pipe.backward.surfaces;
- param->num_forward_references = p->pipe.forward.num_required;
- param->num_backward_references = p->pipe.backward.num_required;
- status = vaRenderPicture(p->display, p->context, &buffer, 1);
- if (!check_error(vf, status, "vaRenderPicture()"))
- break;
- state |= Rendered;
- } while (false);
- if (state & Begun)
+
+ bool need_end_picture = false;
+ bool success = false;
+
+ VASurfaceID id = va_surface_id(img);
+ if (id == VA_INVALID_ID)
+ goto cleanup;
+
+ VAStatus status = vaBeginPicture(p->display, p->context, id);
+ if (!check_error(vf, status, "vaBeginPicture()"))
+ goto cleanup;
+
+ need_end_picture = true;
+
+ VABufferID buffer = VA_INVALID_ID;
+ VAProcPipelineParameterBuffer *param = NULL;
+ status = vaCreateBuffer(p->display, p->context,
+ VAProcPipelineParameterBufferType,
+ sizeof(*param), 1, NULL, &buffer);
+ if (!check_error(vf, status, "vaCreateBuffer()"))
+ goto cleanup;
+
+ VAProcFilterParameterBufferDeinterlacing *filter_params;
+ status = vaMapBuffer(p->display, *(p->pipe.filters), (void**)&filter_params);
+ if (!check_error(vf, status, "vaMapBuffer()"))
+ goto cleanup;
+
+ filter_params->flags = flags & VA_TOP_FIELD ? 0 : VA_DEINTERLACING_BOTTOM_FIELD;
+ if (!(in->fields & MP_IMGFIELD_TOP_FIRST))
+ filter_params->flags |= VA_DEINTERLACING_BOTTOM_FIELD_FIRST;
+
+ vaUnmapBuffer(p->display, *(p->pipe.filters));
+
+ status = vaMapBuffer(p->display, buffer, (void**)&param);
+ if (!check_error(vf, status, "vaMapBuffer()"))
+ goto cleanup;
+
+ param->surface = in_id;
+ param->surface_region = NULL;
+ param->output_region = NULL;
+ param->output_background_color = 0;
+ param->filter_flags = flags;
+ param->filters = p->pipe.filters;
+ param->num_filters = p->pipe.num_filters;
+
+ for (int n = 0; n < p->needed_future_frames; n++) {
+ int idx = p->current_pos - 1 - n;
+ if (idx >= 0 && idx < p->num_queue)
+ add_surface(p, &p->pipe.forward, p->queue[idx]);
+ }
+ param->forward_references = p->pipe.forward.surfaces;
+ param->num_forward_references = p->pipe.forward.num_surfaces;
+
+ for (int n = 0; n < p->needed_past_frames; n++) {
+ int idx = p->current_pos + 1 + n;
+ if (idx >= 0 && idx < p->num_queue)
+ add_surface(p, &p->pipe.backward, p->queue[idx]);
+ }
+ param->backward_references = p->pipe.backward.surfaces;
+ param->num_backward_references = p->pipe.backward.num_surfaces;
+
+ vaUnmapBuffer(p->display, buffer);
+
+ status = vaRenderPicture(p->display, p->context, &buffer, 1);
+ if (!check_error(vf, status, "vaRenderPicture()"))
+ goto cleanup;
+
+ success = true;
+
+cleanup:
+ if (need_end_picture)
vaEndPicture(p->display, p->context);
- if (state & Rendered)
+ if (success)
return img;
talloc_free(img);
return NULL;
}
-// return value: the number of created images
-static int process(struct vf_instance *vf, struct mp_image *in,
- struct mp_image **out1, struct mp_image **out2)
+static void output_frames(struct vf_instance *vf)
{
struct vf_priv_s *p = vf->priv;
- const bool deint = p->do_deint && p->deint_type > 0;
- if (!update_pipeline(vf, deint) || !p->pipe.filters) // no filtering
- return 0;
- const unsigned int csp = va_get_colorspace_flag(p->params.colorspace);
- const unsigned int field = get_deint_field(p, 0, in);
- *out1 = render(vf, in, field | csp);
- if (!*out1) // cannot render
- return 0;
- mp_image_copy_attributes(*out1, in);
+
+ struct mp_image *in = p->queue[p->current_pos];
+ double prev_pts = p->current_pos + 1 < p->num_queue
+ ? p->queue[p->current_pos + 1]->pts : MP_NOPTS_VALUE;
+
+ bool deint = p->do_deint && p->deint_type > 0;
+ if (!update_pipeline(vf, deint) || !p->pipe.filters) { // no filtering
+ vf_add_output_frame(vf, mp_image_new_ref(in));
+ return;
+ }
+ unsigned int csp = va_get_colorspace_flag(p->params.colorspace);
+ unsigned int field = get_deint_field(p, 0, in);
+ if (field == VA_FRAME_PICTURE && p->interlaced_only) {
+ vf_add_output_frame(vf, mp_image_new_ref(in));
+ return;
+ }
+ struct mp_image *out1 = render(vf, in, field | csp);
+ if (!out1) { // cannot render
+ vf_add_output_frame(vf, mp_image_new_ref(in));
+ return;
+ }
+ mp_image_copy_attributes(out1, in);
+ vf_add_output_frame(vf, out1);
// first-field only
if (field == VA_FRAME_PICTURE || (p->do_deint && p->deint_type < 2))
- return 1;
- const double add = (in->pts - p->prev_pts)*0.5;
- if (p->prev_pts == MP_NOPTS_VALUE || add <= 0.0 || add > 0.5) // no pts, skip it
- return 1;
- *out2 = render(vf, in, get_deint_field(p, 1, in) | csp);
- if (!*out2) // cannot render
- return 1;
- mp_image_copy_attributes(*out2, in);
- (*out2)->pts = in->pts + add;
- return 2;
+ return;
+ double add = (in->pts - prev_pts) * 0.5;
+ if (prev_pts == MP_NOPTS_VALUE || add <= 0.0 || add > 0.5) // no pts, skip it
+ return;
+ struct mp_image *out2 = render(vf, in, get_deint_field(p, 1, in) | csp);
+ if (!out2) // cannot render
+ return;
+ mp_image_copy_attributes(out2, in);
+ out2->pts = in->pts + add;
+ vf_add_output_frame(vf, out2);
+ return;
}
static struct mp_image *upload(struct vf_instance *vf, struct mp_image *in)
@@ -241,36 +299,46 @@ static struct mp_image *upload(struct vf_instance *vf, struct mp_image *in)
static int filter_ext(struct vf_instance *vf, struct mp_image *in)
{
struct vf_priv_s *p = vf->priv;
- if (!in)
- return 0;
- int rt_format = in->imgfmt == IMGFMT_VAAPI ? va_surface_rt_format(in)
- : VA_RT_FORMAT_YUV420;
- if (!p->pool || p->current_rt_format != rt_format) {
- talloc_free(p->pool);
- p->pool = mp_image_pool_new(20);
- va_pool_set_allocator(p->pool, p->va, rt_format);
- p->current_rt_format = rt_format;
+
+ if (in) {
+ int rt_format = in->imgfmt == IMGFMT_VAAPI ? va_surface_rt_format(in)
+ : VA_RT_FORMAT_YUV420;
+ if (!p->pool || p->current_rt_format != rt_format) {
+ talloc_free(p->pool);
+ p->pool = mp_image_pool_new(20);
+ va_pool_set_allocator(p->pool, p->va, rt_format);
+ p->current_rt_format = rt_format;
+ }
+ if (in->imgfmt != IMGFMT_VAAPI) {
+ struct mp_image *tmp = upload(vf, in);
+ talloc_free(in);
+ in = tmp;
+ if (!in)
+ return -1;
+ }
}
- if (in->imgfmt != IMGFMT_VAAPI) {
- struct mp_image *tmp = upload(vf, in);
- talloc_free(in);
- in = tmp;
- if (!in)
- return -1;
+
+ if (in) {
+ MP_TARRAY_INSERT_AT(p, p->queue, p->num_queue, 0, in);
+ p->current_pos++;
+ assert(p->num_queue != 1 || p->current_pos == 0);
}
- struct mp_image *out1, *out2;
- const double pts = in->pts;
- const int num = process(vf, in, &out1, &out2);
- if (!num)
- vf_add_output_frame(vf, in);
- else {
- vf_add_output_frame(vf, out1);
- if (num > 1)
- vf_add_output_frame(vf, out2);
- talloc_free(in);
+ // Discard unneeded past frames.
+ // Note that we keep at least 1 past frame (for PTS calculations).
+ while (p->num_queue - (p->current_pos + 1) > MPMAX(p->needed_past_frames, 1)) {
+ assert(p->num_queue > 0);
+ talloc_free(p->queue[p->num_queue - 1]);
+ p->num_queue--;
+ }
+
+ if (p->current_pos < p->needed_future_frames && in)
+ return 0; // wait until future frames have been filled
+
+ if (p->current_pos >= 0 && p->current_pos < p->num_queue) {
+ output_frames(vf);
+ p->current_pos--;
}
- p->prev_pts = pts;
return 0;
}
@@ -279,25 +347,24 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
{
struct vf_priv_s *p = vf->priv;
- p->prev_pts = MP_NOPTS_VALUE;
p->params = *in;
*out = *in;
out->imgfmt = IMGFMT_VAAPI;
+ flush_frames(vf);
return 0;
}
static void uninit(struct vf_instance *vf)
{
struct vf_priv_s *p = vf->priv;
- for (int i=0; i<p->num_buffers; ++i)
+ for (int i = 0; i < p->num_buffers; i++)
vaDestroyBuffer(p->display, p->buffers[i]);
if (p->context != VA_INVALID_ID)
vaDestroyContext(p->display, p->context);
if (p->config != VA_INVALID_ID)
vaDestroyConfig(p->display, p->config);
- free(p->pipe.forward.surfaces);
- free(p->pipe.backward.surfaces);
talloc_free(p->pool);
+ flush_frames(vf);
}
static int query_format(struct vf_instance *vf, unsigned int imgfmt)
@@ -318,6 +385,9 @@ static int control(struct vf_instance *vf, int request, void* data)
case VFCTRL_SET_DEINTERLACE:
p->do_deint = *(int*)data;
return true;
+ case VFCTRL_SEEK_RESET:
+ flush_frames(vf);
+ return true;
default:
return CONTROL_UNKNOWN;
}
@@ -368,9 +438,9 @@ static bool initialize(struct vf_instance *vf)
return false;
VABufferID buffers[VAProcFilterCount];
- for (int i=0; i<VAProcFilterCount; ++i)
+ for (int i = 0; i < VAProcFilterCount; i++)
buffers[i] = VA_INVALID_ID;
- for (int i=0; i<num_filters; ++i) {
+ for (int i = 0; i < num_filters; i++) {
if (filters[i] == VAProcFilterDeinterlacing) {
if (p->deint_type < 2)
continue;
@@ -433,6 +503,7 @@ static const m_option_t vf_opts_fields[] = {
{"weave", 3},
{"motion-adaptive", 4},
{"motion-compensated", 5})),
+ OPT_FLAG("interlaced-only", interlaced_only, 0),
{0}
};
diff --git a/video/filter/vf_vdpaupp.c b/video/filter/vf_vdpaupp.c
index 26c1eef..882b80d 100644
--- a/video/filter/vf_vdpaupp.c
+++ b/video/filter/vf_vdpaupp.c
@@ -50,6 +50,7 @@ struct vf_priv_s {
int def_deintmode;
int deint_enabled;
+ int interlaced_only;
struct mp_vdpau_mixer_opts opts;
};
@@ -78,7 +79,7 @@ static VdpVideoSurface ref_field(struct vf_priv_s *p,
// pos==0 means last field of latest frame, 1 earlier field of latest frame,
// 2 last field of previous frame and so on
-static bool output_field(struct vf_instance *vf, int pos)
+static bool output_field(struct vf_instance *vf, int pos, bool deint)
{
struct vf_priv_s *p = vf->priv;
@@ -91,7 +92,7 @@ static bool output_field(struct vf_instance *vf, int pos)
struct mp_vdpau_mixer_frame *frame = mp_vdpau_mixed_frame_get(mpi);
frame->field = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME;
- if (p->opts.deint) {
+ if (p->opts.deint && deint) {
int top_field_first = !!(mpi->fields & MP_IMGFIELD_TOP_FIRST);
frame->field = top_field_first ^ (pos & 1) ?
VDP_VIDEO_MIXER_PICTURE_STRUCTURE_BOTTOM_FIELD:
@@ -109,7 +110,7 @@ static bool output_field(struct vf_instance *vf, int pos)
// Interpolate timestamps of extra fields (these always have even indexes)
int idx = pos / 2;
- if (idx > 0 && !(pos & 1) && p->opts.deint >= 2) {
+ if (idx > 0 && !(pos & 1) && p->opts.deint >= 2 && deint) {
double pts1 = p->buffered[idx - 1]->pts;
double pts2 = p->buffered[idx]->pts;
double diff = pts1 - pts2;
@@ -150,17 +151,19 @@ static int filter_ext(struct vf_instance *vf, struct mp_image *mpi)
p->prev_pos += 2;
}
+ bool deint = (mpi && (mpi->fields & MP_IMGFIELD_INTERLACED)) || !p->interlaced_only;
+
while (1) {
int current = p->prev_pos - 1;
if (!FIELD_VALID(p, current))
break;
// No field-splitting deinterlace -> only output first field (odd index)
- if ((current & 1) || p->opts.deint >= 2) {
+ if ((current & 1) || (deint && p->opts.deint >= 2)) {
// Wait for enough future frames being buffered.
// (Past frames are always around if available at all.)
if (!eof && !FIELD_VALID(p, current - 1))
break;
- if (!output_field(vf, current))
+ if (!output_field(vf, current, deint))
break;
}
p->prev_pos = current;
@@ -248,6 +251,7 @@ static const m_option_t vf_opts_fields[] = {
OPT_FLOATRANGE("denoise", opts.denoise, 0, 0, 1),
OPT_FLOATRANGE("sharpen", opts.sharpen, 0, -1, 1),
OPT_INTRANGE("hqscaling", opts.hqscaling, 0, 0, 9),
+ OPT_FLAG("interlaced-only", interlaced_only, 0, OPTDEF_INT(1)),
{0}
};
diff --git a/video/filter/vf_vdpaurb.c b/video/filter/vf_vdpaurb.c
new file mode 100644
index 0000000..8eaeb86
--- /dev/null
+++ b/video/filter/vf_vdpaurb.c
@@ -0,0 +1,116 @@
+/*
+ * 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 <assert.h>
+
+#include "video/vdpau.h"
+#include "video/vdpau_mixer.h"
+#include "vf.h"
+
+// This filter will read back decoded frames that have been decoded by vdpau
+// so they can be post-processed by regular filters. As vdpau is still doing
+// the decoding, a vdpau compatible vo must always be used.
+//
+// NB: This filter assumes the video surface will have a 420 chroma type and
+// can always be read back in NV12 format. This is a safe assumption at the
+// time of writing, but may not always remain true.
+
+struct vf_priv_s {
+ struct mp_vdpau_ctx *ctx;
+};
+
+static int filter_ext(struct vf_instance *vf, struct mp_image *mpi)
+{
+ VdpStatus vdp_st;
+ struct vf_priv_s *p = vf->priv;
+ struct mp_vdpau_ctx *ctx = p->ctx;
+ struct vdp_functions *vdp = &ctx->vdp;
+
+ if (!mpi) {
+ return 0;
+ }
+
+ if (mp_vdpau_mixed_frame_get(mpi)) {
+ MP_ERR(vf, "Can't apply vdpaurb filter after vdpaupp filter.\n");
+ mp_image_unrefp(&mpi);
+ return -1;
+ }
+
+ struct mp_image *out = vf_alloc_out_image(vf);
+ if (!out) {
+ mp_image_unrefp(&mpi);
+ return -1;
+ }
+ mp_image_copy_attributes(out, mpi);
+
+ VdpVideoSurface surface = (uintptr_t)mpi->planes[3];
+ assert(surface > 0);
+
+ vdp_st = vdp->video_surface_get_bits_y_cb_cr(surface,
+ VDP_YCBCR_FORMAT_NV12,
+ (void * const *)out->planes,
+ out->stride);
+ CHECK_VDP_WARNING(vf, "Error when calling vdp_output_surface_get_bits_y_cb_cr");
+
+ vf_add_output_frame(vf, out);
+ mp_image_unrefp(&mpi);
+ return 0;
+}
+
+static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
+ struct mp_image_params *out)
+{
+ *out = *in;
+ out->imgfmt = IMGFMT_NV12;
+ return 0;
+}
+
+static int query_format(struct vf_instance *vf, unsigned int fmt)
+{
+ if (fmt == IMGFMT_VDPAU) {
+ return vf_next_query_format(vf, IMGFMT_NV12);
+ }
+ return 0;
+}
+
+static int vf_open(vf_instance_t *vf)
+{
+ struct vf_priv_s *p = vf->priv;
+
+ vf->filter_ext = filter_ext;
+ vf->filter = NULL;
+ vf->reconfig = reconfig;
+ vf->query_format = query_format;
+
+ if (!vf->hwdec) {
+ return 0;
+ }
+ hwdec_request_api(vf->hwdec, "vdpau");
+ p->ctx = vf->hwdec->hwctx ? vf->hwdec->hwctx->vdpau_ctx : NULL;
+ if (!p->ctx) {
+ return 0;
+ }
+
+ return 1;
+}
+
+const vf_info_t vf_info_vdpaurb = {
+ .description = "vdpau readback",
+ .name = "vdpaurb",
+ .open = vf_open,
+ .priv_size = sizeof(struct vf_priv_s),
+};
diff --git a/video/filter/vf_yadif.c b/video/filter/vf_yadif.c
index 6526283..7bfd209 100644
--- a/video/filter/vf_yadif.c
+++ b/video/filter/vf_yadif.c
@@ -28,14 +28,10 @@
struct vf_priv_s {
int mode;
- int do_deinterlace;
+ int interlaced_only;
struct vf_lw_opts *lw_opts;
};
-static const struct vf_priv_s vf_priv_default = {
- .do_deinterlace = 1,
-};
-
static int vf_open(vf_instance_t *vf)
{
struct vf_priv_s *p = vf->priv;
@@ -43,10 +39,11 @@ static int vf_open(vf_instance_t *vf)
// Earlier libavfilter yadif versions used pure integers for the first
// option. We can't/don't handle this, but at least allow usage of the
// filter with default settings. So use an empty string for "send_frame".
- const char *mode[] = {"", "send_field", "send_frame_nospatial",
+ const char *mode[] = {"send_frame", "send_field", "send_frame_nospatial",
"send_field_nospatial"};
- if (vf_lw_set_graph(vf, p->lw_opts, "yadif", "%s", mode[p->mode]) >= 0)
+ if (vf_lw_set_graph(vf, p->lw_opts, "yadif", "mode=%s:deint=%s", mode[p->mode],
+ p->interlaced_only ? "interlaced" : "all") >= 0)
{
return 1;
}
@@ -62,7 +59,7 @@ static const m_option_t vf_opts_fields[] = {
{"field", 1},
{"frame-nospatial", 2},
{"field-nospatial", 3})),
- OPT_FLAG("enabled", do_deinterlace, 0),
+ OPT_FLAG("interlaced-only", interlaced_only, 0),
OPT_SUBSTRUCT("", lw_opts, vf_lw_conf, 0),
{0}
};
@@ -72,6 +69,5 @@ const vf_info_t vf_info_yadif = {
.name = "yadif",
.open = vf_open,
.priv_size = sizeof(struct vf_priv_s),
- .priv_defaults = &vf_priv_default,
.options = vf_opts_fields,
};
diff --git a/video/fmt-conversion.c b/video/fmt-conversion.c
index b280e36..797d243 100644
--- a/video/fmt-conversion.c
+++ b/video/fmt-conversion.c
@@ -121,6 +121,9 @@ static const struct {
#if HAVE_VDA_HWACCEL
{IMGFMT_VDA, AV_PIX_FMT_VDA},
#endif
+#if HAVE_VIDEOTOOLBOX_HWACCEL
+ {IMGFMT_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX},
+#endif
{IMGFMT_VAAPI, AV_PIX_FMT_VAAPI_VLD},
{IMGFMT_DXVA2, AV_PIX_FMT_DXVA2_VLD},
#if HAVE_AV_PIX_FMT_MMAL
diff --git a/video/hwdec.h b/video/hwdec.h
index b04b7c5..d950a86 100644
--- a/video/hwdec.h
+++ b/video/hwdec.h
@@ -1,20 +1,26 @@
#ifndef MP_HWDEC_H_
#define MP_HWDEC_H_
+#include "options/m_option.h"
+
struct mp_image_pool;
-// keep in sync with --hwdec option
+// keep in sync with --hwdec option (see mp_hwdec_names)
enum hwdec_type {
HWDEC_AUTO = -1,
HWDEC_NONE = 0,
HWDEC_VDPAU = 1,
HWDEC_VDA = 2,
+ HWDEC_VIDEOTOOLBOX = 3,
HWDEC_VAAPI = 4,
HWDEC_VAAPI_COPY = 5,
HWDEC_DXVA2_COPY = 6,
HWDEC_RPI = 7,
};
+// hwdec_type names (options.c)
+extern const struct m_opt_choice_alternatives mp_hwdec_names[];
+
struct mp_hwdec_ctx {
enum hwdec_type type;
@@ -23,6 +29,7 @@ struct mp_hwdec_ctx {
// API-specific, not needed by all backends.
struct mp_vdpau_ctx *vdpau_ctx;
struct mp_vaapi_ctx *vaapi_ctx;
+ struct mp_d3d_ctx *d3d_ctx;
// Optional.
// Allocates a software image from the pool, downloads the hw image from
diff --git a/video/image_writer.c b/video/image_writer.c
index bcb71f4..089afad 100644
--- a/video/image_writer.c
+++ b/video/image_writer.c
@@ -42,13 +42,13 @@
const struct image_writer_opts image_writer_opts_defaults = {
.format = "jpg",
+ .high_bit_depth = 1,
.png_compression = 7,
.png_filter = 5,
.jpeg_quality = 90,
- .jpeg_optimize = 100,
.jpeg_smooth = 0,
- .jpeg_baseline = 1,
- .tag_csp = 1,
+ .jpeg_source_chroma = 1,
+ .tag_csp = 0,
};
#define OPT_BASE_STRUCT struct image_writer_opts
@@ -56,12 +56,12 @@ const struct image_writer_opts image_writer_opts_defaults = {
const struct m_sub_options image_writer_conf = {
.opts = (const m_option_t[]) {
OPT_INTRANGE("jpeg-quality", jpeg_quality, 0, 0, 100),
- OPT_INTRANGE("jpeg-optimize", jpeg_optimize, 0, 0, 100),
OPT_INTRANGE("jpeg-smooth", jpeg_smooth, 0, 0, 100),
- OPT_FLAG("jpeg-baseline", jpeg_baseline, 0),
+ OPT_FLAG("jpeg-source-chroma", jpeg_source_chroma, 0),
OPT_INTRANGE("png-compression", png_compression, 0, 0, 9),
OPT_INTRANGE("png-filter", png_filter, 0, 0, 5),
OPT_STRING("format", format, 0),
+ OPT_FLAG("high-bit-depth", high_bit_depth, 0),
OPT_FLAG("tag-colorspace", tag_csp, 0),
{0},
},
@@ -189,12 +189,13 @@ static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp
cinfo.JFIF_minor_version = 2;
jpeg_set_defaults(&cinfo);
- jpeg_set_quality(&cinfo, ctx->opts->jpeg_quality, ctx->opts->jpeg_baseline);
- cinfo.optimize_coding = ctx->opts->jpeg_optimize;
+ jpeg_set_quality(&cinfo, ctx->opts->jpeg_quality, 0);
cinfo.smoothing_factor = ctx->opts->jpeg_smooth;
- cinfo.comp_info[0].h_samp_factor = 1 << ctx->original_format.chroma_xs;
- cinfo.comp_info[0].v_samp_factor = 1 << ctx->original_format.chroma_ys;
+ if (ctx->opts->jpeg_source_chroma) {
+ cinfo.comp_info[0].h_samp_factor = 1 << ctx->original_format.chroma_xs;
+ cinfo.comp_info[0].v_samp_factor = 1 << ctx->original_format.chroma_ys;
+ }
jpeg_start_compress(&cinfo, TRUE);
@@ -214,6 +215,22 @@ static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp
#endif
+static int get_encoder_format(struct AVCodec *codec, int srcfmt, bool highdepth)
+{
+ const enum AVPixelFormat *pix_fmts = codec->pix_fmts;
+ int current = 0;
+ for (int n = 0; pix_fmts && pix_fmts[n] != AV_PIX_FMT_NONE; n++) {
+ int fmt = pixfmt2imgfmt(pix_fmts[n]);
+ if (!fmt)
+ continue;
+ // Ignore formats larger than 8 bit per pixel.
+ if (!highdepth && IMGFMT_RGB_DEPTH(fmt) > 32)
+ continue;
+ current = current ? mp_imgfmt_select_best(current, fmt, srcfmt) : fmt;
+ }
+ return current;
+}
+
static int get_target_format(struct image_writer_ctx *ctx, int srcfmt)
{
if (!ctx->writer->lavc_codec)
@@ -223,22 +240,14 @@ static int get_target_format(struct image_writer_ctx *ctx, int srcfmt)
if (!codec)
goto unknown;
- const enum AVPixelFormat *pix_fmts = codec->pix_fmts;
- if (!pix_fmts)
- goto unknown;
+ int target = get_encoder_format(codec, srcfmt, ctx->opts->high_bit_depth);
+ if (!target)
+ target = get_encoder_format(codec, srcfmt, true);
- int current = 0;
- for (int n = 0; pix_fmts[n] != AV_PIX_FMT_NONE; n++) {
- int fmt = pixfmt2imgfmt(pix_fmts[n]);
- if (!fmt)
- continue;
- current = current ? mp_imgfmt_select_best(current, fmt, srcfmt) : fmt;
- }
-
- if (!current)
+ if (!target)
goto unknown;
- return current;
+ return target;
unknown:
return IMGFMT_RGB24;
diff --git a/video/image_writer.h b/video/image_writer.h
index b27db39..ce8438a 100644
--- a/video/image_writer.h
+++ b/video/image_writer.h
@@ -20,6 +20,7 @@ struct mp_log;
struct image_writer_opts {
char *format;
+ int high_bit_depth;
int png_compression;
int png_filter;
int jpeg_quality;
@@ -28,6 +29,7 @@ struct image_writer_opts {
int jpeg_dpi;
int jpeg_progressive;
int jpeg_baseline;
+ int jpeg_source_chroma;
int tag_csp;
};
diff --git a/video/img_format.c b/video/img_format.c
index 6cf585d..5defa66 100644
--- a/video/img_format.c
+++ b/video/img_format.c
@@ -36,6 +36,7 @@ static const struct mp_imgfmt_entry mp_imgfmt_list[] = {
{"vdpau_output", IMGFMT_VDPAU_OUTPUT},
// FFmpeg names have an annoying "_vld" suffix
{"vda", IMGFMT_VDA},
+ {"videotoolbox", IMGFMT_VIDEOTOOLBOX},
{"vaapi", IMGFMT_VAAPI},
// names below this are not preferred over the FFmpeg names
// the "none" entry makes mp_imgfmt_to_name prefer FFmpeg names
diff --git a/video/img_format.h b/video/img_format.h
index 8c79d9f..f88592e 100644
--- a/video/img_format.h
+++ b/video/img_format.h
@@ -202,10 +202,14 @@ enum mp_imgfmt {
// structures, instead of pixel data.
IMGFMT_VDPAU, // VdpVideoSurface
IMGFMT_VDPAU_OUTPUT, // VdpOutputSurface
- IMGFMT_VDA,
IMGFMT_VAAPI,
IMGFMT_DXVA2, // IDirect3DSurface9 (NV12)
IMGFMT_MMAL, // MMAL_BUFFER_HEADER_T
+ // These use the same underlying format, but FFmpeg requires us to keep
+ // them separate. The VDA decoder will change the format to
+ // IMGFMT_VIDEOTOOLBOX, though.
+ IMGFMT_VIDEOTOOLBOX,
+ IMGFMT_VDA,
// Generic pass-through of AV_PIX_FMT_*. Used for formats which don't have
// a corresponding IMGFMT_ value.
diff --git a/video/mp_image.c b/video/mp_image.c
index dc305a4..debdbbb 100644
--- a/video/mp_image.c
+++ b/video/mp_image.c
@@ -38,75 +38,10 @@
#include "video/filter/vf.h"
-static pthread_mutex_t refcount_mutex = PTHREAD_MUTEX_INITIALIZER;
-#define refcount_lock() pthread_mutex_lock(&refcount_mutex)
-#define refcount_unlock() pthread_mutex_unlock(&refcount_mutex)
-
-struct m_refcount {
- void *arg;
- // free() is called if refcount reaches 0.
- void (*free)(void *arg);
- bool (*ext_is_unique)(void *arg);
- // Native refcount (there may be additional references if .ext_* are set)
- int refcount;
-};
-
-// Only for checking API usage
-static void m_refcount_destructor(void *ptr)
-{
- struct m_refcount *ref = ptr;
- assert(ref->refcount == 0);
-}
-
-// Starts out with refcount==1, caller can set .arg and .free and .ext_*
-static struct m_refcount *m_refcount_new(void)
-{
- struct m_refcount *ref = talloc_ptrtype(NULL, ref);
- *ref = (struct m_refcount) { .refcount = 1 };
- talloc_set_destructor(ref, m_refcount_destructor);
- return ref;
-}
-
-static void m_refcount_ref(struct m_refcount *ref)
-{
- refcount_lock();
- ref->refcount++;
- refcount_unlock();
-}
-
-static void m_refcount_unref(struct m_refcount *ref)
-{
- bool dead;
- refcount_lock();
- assert(ref->refcount > 0);
- ref->refcount--;
- dead = ref->refcount == 0;
- refcount_unlock();
-
- if (dead) {
- if (ref->free)
- ref->free(ref->arg);
- talloc_free(ref);
- }
-}
-
-static bool m_refcount_is_unique(struct m_refcount *ref)
-{
- bool nonunique;
- refcount_lock();
- nonunique = ref->refcount > 1;
- refcount_unlock();
-
- if (nonunique)
- return false;
- if (ref->ext_is_unique)
- return ref->ext_is_unique(ref->arg); // referenced only by us
- return true;
-}
-
static bool mp_image_alloc_planes(struct mp_image *mpi)
{
assert(!mpi->planes[0]);
+ assert(!mpi->bufs[0]);
if (!mp_image_params_valid(&mpi->params) || mpi->fmt.flags & MP_IMGFLAG_HWACCEL)
return false;
@@ -129,10 +64,12 @@ static bool mp_image_alloc_planes(struct mp_image *mpi)
for (int n = 0; n < MP_MAX_PLANES; n++)
sum += plane_size[n];
- uint8_t *data = av_malloc(FFMAX(sum, 1));
- if (!data)
+ // Note: mp_image_pool assumes this creates only 1 AVBufferRef.
+ mpi->bufs[0] = av_buffer_alloc(FFMAX(sum, 1));
+ if (!mpi->bufs[0])
return false;
+ uint8_t *data = mpi->bufs[0]->data;
for (int n = 0; n < MP_MAX_PLANES; n++) {
mpi->planes[n] = plane_size[n] ? data : NULL;
data += plane_size[n];
@@ -153,7 +90,8 @@ void mp_image_setfmt(struct mp_image *mpi, int out_fmt)
static void mp_image_destructor(void *ptr)
{
mp_image_t *mpi = ptr;
- m_refcount_unref(mpi->refcount);
+ for (int p = 0; p < MP_MAX_PLANES; p++)
+ av_buffer_unref(&mpi->bufs[p]);
}
int mp_chroma_div_up(int size, int shift)
@@ -194,7 +132,6 @@ struct mp_image *mp_image_alloc(int imgfmt, int w, int h)
{
struct mp_image *mpi = talloc_zero(NULL, struct mp_image);
talloc_set_destructor(mpi, mp_image_destructor);
- mpi->refcount = m_refcount_new();
mp_image_set_size(mpi, w, h);
mp_image_setfmt(mpi, imgfmt);
@@ -202,8 +139,6 @@ struct mp_image *mp_image_alloc(int imgfmt, int w, int h)
talloc_free(mpi);
return NULL;
}
- mpi->refcount->free = av_free;
- mpi->refcount->arg = mpi->planes[0];
return mpi;
}
@@ -223,7 +158,7 @@ struct mp_image *mp_image_new_copy(struct mp_image *img)
void mp_image_steal_data(struct mp_image *dst, struct mp_image *src)
{
assert(dst->imgfmt == src->imgfmt && dst->w == src->w && dst->h == src->h);
- assert(dst->refcount && src->refcount);
+ assert(dst->bufs[0] && src->bufs[0]);
for (int p = 0; p < MP_MAX_PLANES; p++) {
dst->planes[p] = src->planes[p];
@@ -231,9 +166,11 @@ void mp_image_steal_data(struct mp_image *dst, struct mp_image *src)
}
mp_image_copy_attributes(dst, src);
- m_refcount_unref(dst->refcount);
- dst->refcount = src->refcount;
- talloc_set_destructor(src, NULL);
+ for (int p = 0; p < MP_MAX_PLANES; p++) {
+ av_buffer_unref(&dst->bufs[p]);
+ dst->bufs[p] = src->bufs[p];
+ src->bufs[p] = NULL;
+ }
talloc_free(src);
}
@@ -244,34 +181,54 @@ struct mp_image *mp_image_new_ref(struct mp_image *img)
if (!img)
return NULL;
- if (!img->refcount)
+ if (!img->bufs[0])
return mp_image_new_copy(img);
struct mp_image *new = talloc_ptrtype(NULL, new);
talloc_set_destructor(new, mp_image_destructor);
*new = *img;
- m_refcount_ref(new->refcount);
- return new;
+ bool fail = false;
+ for (int p = 0; p < MP_MAX_PLANES; p++) {
+ if (new->bufs[p]) {
+ new->bufs[p] = av_buffer_ref(new->bufs[p]);
+ if (!new->bufs[p])
+ fail = true;
+ }
+ }
+
+ if (!fail)
+ return new;
+
+ // Do this after _all_ bufs were changed; we don't want it to free bufs
+ // from the original image if this fails.
+ talloc_free(new);
+ return NULL;
}
-// Return a reference counted reference to img. is_unique us used to connect to
-// an external refcounting API. It is assumed that the new object
-// has an initial reference to that external API. If free is given, that is
-// called after the last unref. All function pointers are optional.
-// On allocation failure, unref the frame and return NULL.
-static struct mp_image *mp_image_new_external_ref(struct mp_image *img, void *arg,
- bool (*is_unique)(void *arg),
- void (*free)(void *arg))
+struct free_args {
+ void *arg;
+ void (*free)(void *arg);
+};
+
+static void call_free(void *opaque, uint8_t *data)
+{
+ struct free_args *args = opaque;
+ args->free(args->arg);
+ talloc_free(args);
+}
+
+// Create a new mp_image based on img, but don't set any buffers.
+// Using this is only valid until the original img is unreferenced (including
+// implicit unreferencing of the data by mp_image_make_writeable()), unless
+// a new reference is set.
+struct mp_image *mp_image_new_dummy_ref(struct mp_image *img)
{
struct mp_image *new = talloc_ptrtype(NULL, new);
talloc_set_destructor(new, mp_image_destructor);
*new = *img;
-
- new->refcount = m_refcount_new();
- new->refcount->ext_is_unique = is_unique;
- new->refcount->free = free;
- new->refcount->arg = arg;
+ for (int p = 0; p < MP_MAX_PLANES; p++)
+ new->bufs[p] = NULL;
return new;
}
@@ -279,17 +236,34 @@ static struct mp_image *mp_image_new_external_ref(struct mp_image *img, void *ar
// 0, call free(free_arg). The data passed by img must not be free'd before
// that. The new reference will be writeable.
// On allocation failure, unref the frame and return NULL.
+// This is only used for hw decoding; this is important, because libav* expects
+// all plane data to be accounted for by AVBufferRefs.
struct mp_image *mp_image_new_custom_ref(struct mp_image *img, void *free_arg,
void (*free)(void *arg))
{
- return mp_image_new_external_ref(img, free_arg, NULL, free);
+ struct mp_image *new = mp_image_new_dummy_ref(img);
+
+ struct free_args *args = talloc_ptrtype(NULL, args);
+ *args = (struct free_args){free_arg, free};
+ new->bufs[0] = av_buffer_create(NULL, 0, call_free, args,
+ AV_BUFFER_FLAG_READONLY);
+ if (new->bufs[0])
+ return new;
+ talloc_free(new);
+ return NULL;
}
bool mp_image_is_writeable(struct mp_image *img)
{
- if (!img->refcount)
+ if (!img->bufs[0])
return true; // not ref-counted => always considered writeable
- return m_refcount_is_unique(img->refcount);
+ for (int p = 0; p < MP_MAX_PLANES; p++) {
+ if (!img->bufs[p])
+ break;
+ if (!av_buffer_is_writable(img->bufs[p]))
+ return false;
+ }
+ return true;
}
// Make the image data referenced by img writeable. This allocates new data
@@ -364,8 +338,10 @@ void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src)
}
mp_image_params_guess_csp(&dst->params); // ensure colorspace consistency
if ((dst->fmt.flags & MP_IMGFLAG_PAL) && (src->fmt.flags & MP_IMGFLAG_PAL)) {
- if (dst->planes[1] && src->planes[1])
- memcpy(dst->planes[1], src->planes[1], MP_PALETTE_SIZE);
+ if (dst->planes[1] && src->planes[1]) {
+ if (mp_image_make_writeable(dst))
+ memcpy(dst->planes[1], src->planes[1], MP_PALETTE_SIZE);
+ }
}
}
@@ -661,40 +637,20 @@ void mp_image_copy_fields_to_av_frame(struct AVFrame *dst,
dst->color_range = mp_csp_levels_to_avcol_range(src->params.colorlevels);
}
-static void frame_free(void *p)
-{
- AVFrame *frame = p;
- av_frame_free(&frame);
-}
-
-static bool frame_is_unique(void *p)
-{
- AVFrame *frame = p;
- return av_frame_is_writable(frame);
-}
-
// Create a new mp_image reference to av_frame.
struct mp_image *mp_image_from_av_frame(struct AVFrame *av_frame)
{
- AVFrame *new_ref = av_frame_clone(av_frame);
- if (!new_ref)
- return NULL;
struct mp_image t = {0};
- mp_image_copy_fields_from_av_frame(&t, new_ref);
- return mp_image_new_external_ref(&t, new_ref, frame_is_unique, frame_free);
-}
-
-static void free_img(void *opaque, uint8_t *data)
-{
- struct mp_image *img = opaque;
- talloc_free(img);
+ mp_image_copy_fields_from_av_frame(&t, av_frame);
+ for (int p = 0; p < MP_MAX_PLANES; p++)
+ t.bufs[p] = av_frame->buf[p];
+ return mp_image_new_ref(&t);
}
// Convert the mp_image reference to a AVFrame reference.
// Warning: img is unreferenced (i.e. free'd). This is asymmetric to
-// mp_image_from_av_frame(). It's done this way to allow marking the
-// resulting AVFrame as writeable if img is the only reference (in
-// other words, it's an optimization).
+// mp_image_from_av_frame(). It was done as some sort of optimization,
+// but now these semantics are pointless.
// On failure, img is only unreffed.
struct AVFrame *mp_image_to_av_frame_and_unref(struct mp_image *img)
{
@@ -708,22 +664,9 @@ struct AVFrame *mp_image_to_av_frame_and_unref(struct mp_image *img)
return NULL;
}
mp_image_copy_fields_to_av_frame(frame, new_ref);
- // Caveat: if img has shared references, and all other references disappear
- // at a later point, the AVFrame will still be read-only.
- int flags = 0;
- if (!mp_image_is_writeable(new_ref))
- flags |= AV_BUFFER_FLAG_READONLY;
- for (int n = 0; n < new_ref->num_planes; n++) {
- // Make it so that the actual image data is freed only if _all_ buffers
- // are unreferenced.
- struct mp_image *dummy_ref = mp_image_new_ref(new_ref);
- if (!dummy_ref)
- abort(); // out of memory (for the ref, not real image data)
- void *ptr = new_ref->planes[n];
- size_t size = new_ref->stride[n] * new_ref->h;
- frame->buf[n] = av_buffer_create(ptr, size, free_img, dummy_ref, flags);
- if (!frame->buf[n])
- abort();
+ for (int p = 0; p < MP_MAX_PLANES; p++) {
+ frame->buf[p] = new_ref->bufs[p];
+ new_ref->bufs[p] = NULL;
}
talloc_free(new_ref);
return frame;
diff --git a/video/mp_image.h b/video/mp_image.h
index b0110c1..f575920 100644
--- a/video/mp_image.h
+++ b/video/mp_image.h
@@ -90,10 +90,16 @@ typedef struct mp_image {
/* only inside filter chain */
double pts;
- /* memory management */
- struct m_refcount *refcount;
/* for private use */
void* priv;
+
+ // Reference-counted data references.
+ // These do not necessarily map directly to planes[]. They can have
+ // different order or count. There shouldn't be more buffers than planes.
+ // If bufs[n] is NULL, bufs[n+1] must also be NULL.
+ // All mp_* functions manage this automatically; do not mess with it.
+ // (See also AVFrame.buf.)
+ struct AVBufferRef *bufs[MP_MAX_PLANES];
} mp_image_t;
int mp_chroma_div_up(int size, int shift);
@@ -120,6 +126,7 @@ int mp_image_plane_h(struct mp_image *mpi, int plane);
void mp_image_setfmt(mp_image_t* mpi, int out_fmt);
void mp_image_steal_data(struct mp_image *dst, struct mp_image *src);
+struct mp_image *mp_image_new_dummy_ref(struct mp_image *img);
struct mp_image *mp_image_new_custom_ref(struct mp_image *img, void *arg,
void (*free)(void *arg));
diff --git a/video/mp_image_pool.c b/video/mp_image_pool.c
index 173c018..5992c63 100644
--- a/video/mp_image_pool.c
+++ b/video/mp_image_pool.c
@@ -22,6 +22,8 @@
#include <pthread.h>
#include <assert.h>
+#include <libavutil/buffer.h>
+
#include "talloc.h"
#include "common/common.h"
@@ -94,9 +96,9 @@ void mp_image_pool_clear(struct mp_image_pool *pool)
// This is the only function that is allowed to run in a different thread.
// (Consider passing an image to another thread, which frees it.)
-static void unref_image(void *ptr)
+static void unref_image(void *opaque, uint8_t *data)
{
- struct mp_image *img = ptr;
+ struct mp_image *img = opaque;
struct image_flags *it = img->priv;
bool alive;
pool_lock();
@@ -135,11 +137,31 @@ struct mp_image *mp_image_pool_get_no_alloc(struct mp_image_pool *pool, int fmt,
pool_unlock();
if (!new)
return NULL;
+
+ // Reference the new image. Since mp_image_pool is not declared thread-safe,
+ // and unreffing images from other threads does not allocate new images,
+ // no synchronization is required here.
+ for (int p = 0; p < MP_MAX_PLANES; p++)
+ assert(!!new->bufs[p] == !p); // only 1 AVBufferRef
+
+ struct mp_image *ref = mp_image_new_dummy_ref(new);
+
+ // This assumes the buffer is at this point exclusively owned by us: we
+ // can't track whether the buffer is unique otherwise.
+ // (av_buffer_is_writable() checks the refcount of the new buffer only.)
+ int flags = av_buffer_is_writable(new->bufs[0]) ? 0 : AV_BUFFER_FLAG_READONLY;
+ ref->bufs[0] = av_buffer_create(new->bufs[0]->data, new->bufs[0]->size,
+ unref_image, new, flags);
+ if (!ref->bufs[0]) {
+ talloc_free(ref);
+ return NULL;
+ }
+
struct image_flags *it = new->priv;
assert(!it->referenced && it->pool_alive);
it->referenced = true;
it->order = ++pool->lru_counter;
- return mp_image_new_custom_ref(new, new, unref_image);
+ return ref;
}
// Return a new image of given format/size. The only difference to
@@ -204,6 +226,10 @@ bool mp_image_pool_make_writeable(struct mp_image_pool *pool,
return true;
}
+// Call cb(cb_data, fmt, w, h) to allocate an image. Note that the resulting
+// image must use only 1 AVBufferRef. The returned image must also be owned
+// exclusively by the image pool, otherwise mp_image_is_writeable() will not
+// work due to FFmpeg restrictions.
void mp_image_pool_set_allocator(struct mp_image_pool *pool,
mp_image_allocator cb, void *cb_data)
{
diff --git a/video/out/aspect.c b/video/out/aspect.c
index 851cca9..2e1093c 100644
--- a/video/out/aspect.c
+++ b/video/out/aspect.c
@@ -49,11 +49,16 @@ static void aspect_calc_panscan(struct mp_log *log, struct mp_vo_opts *opts,
fwidth, fheight, d_w, d_h);
int vo_panscan_area = window_h - fheight;
- if (!vo_panscan_area)
+ double f_w = fwidth / (double)fheight;
+ double f_h = 1;
+ if (!vo_panscan_area) {
vo_panscan_area = window_w - fwidth;
+ f_w = 1;
+ f_h = fheight / (double)fwidth;
+ }
- *out_w = fwidth + vo_panscan_area * opts->panscan * fwidth / fheight;
- *out_h = fheight + vo_panscan_area * opts->panscan;
+ *out_w = fwidth + vo_panscan_area * opts->panscan * f_w;
+ *out_h = fheight + vo_panscan_area * opts->panscan * f_h;
}
// Clamp [start, end) to range [0, size) with various fallbacks.
@@ -67,10 +72,6 @@ static void clamp_size(int size, int *start, int *end)
}
}
-// Round source to a multiple of 2, this is at least needed for vo_direct3d
-// and ATI cards.
-#define VID_SRC_ROUND_UP(x) (((x) + 1) & ~1)
-
static void src_dst_split_scaling(int src_size, int dst_size,
int scaled_src_size, bool unscaled,
float zoom, float align, float pan,
@@ -100,12 +101,12 @@ static void src_dst_split_scaling(int src_size, int dst_size,
int s_dst = *dst_end - *dst_start;
if (*dst_start < 0) {
int border = -(*dst_start) * s_src / s_dst;
- *src_start += VID_SRC_ROUND_UP(border);
+ *src_start += border;
*dst_start = 0;
}
if (*dst_end > dst_size) {
int border = (*dst_end - dst_size) * s_src / s_dst;
- *src_end -= VID_SRC_ROUND_UP(border);
+ *src_end -= border;
*dst_end = dst_size;
}
diff --git a/video/out/cocoa_common.h b/video/out/cocoa_common.h
index 5b44038..0427fc9 100644
--- a/video/out/cocoa_common.h
+++ b/video/out/cocoa_common.h
@@ -20,8 +20,11 @@
#ifndef MPLAYER_COCOA_COMMON_H
#define MPLAYER_COCOA_COMMON_H
-#include "vo.h"
+#include <stdbool.h>
+#include <stdint.h>
+#include <OpenGL/OpenGL.h>
+struct vo;
struct vo_cocoa_state;
int vo_cocoa_init(struct vo *vo);
@@ -29,19 +32,9 @@ void vo_cocoa_uninit(struct vo *vo);
int vo_cocoa_config_window(struct vo *vo, uint32_t flags);
-void vo_cocoa_set_current_context(struct vo *vo, bool current);
-bool vo_cocoa_start_frame(struct vo *vo);
-void vo_cocoa_swap_buffers(struct vo *vo);
-int vo_cocoa_check_events(struct vo *vo);
int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg);
-void vo_cocoa_register_resize_callback(struct vo *vo,
- void (*cb)(struct vo *vo, int w, int h));
-
-void vo_cocoa_register_gl_clear_callback(struct vo *vo, void *ctx,
- void (*cb)(void *ctx));
-
-void vo_cocoa_create_nsgl_ctx(struct vo *vo, void *ctx);
-void vo_cocoa_release_nsgl_ctx(struct vo *vo);
+void vo_cocoa_swap_buffers(struct vo *vo);
+void vo_cocoa_set_opengl_ctx(struct vo *vo, CGLContextObj ctx);
#endif /* MPLAYER_COCOA_COMMON_H */
diff --git a/video/out/cocoa_common.m b/video/out/cocoa_common.m
index fd941b8..f16f7ae 100644
--- a/video/out/cocoa_common.m
+++ b/video/out/cocoa_common.m
@@ -18,9 +18,9 @@
*/
#import <Cocoa/Cocoa.h>
-#import <CoreServices/CoreServices.h> // for CGDisplayHideCursor
#import <IOKit/pwr_mgt/IOPMLib.h>
#import <IOKit/IOKitLib.h>
+#import <AppKit/AppKit.h>
#include <mach/mach.h>
#import "cocoa_common.h"
@@ -30,11 +30,13 @@
#import "video/out/cocoa/mpvadapter.h"
#include "osdep/threads.h"
+#include "osdep/atomics.h"
#include "osdep/macosx_compat.h"
#include "osdep/macosx_events_objc.h"
#include "config.h"
+#include "osdep/timer.h"
#include "osdep/macosx_application.h"
#include "osdep/macosx_application_objc.h"
@@ -47,19 +49,20 @@
#include "common/msg.h"
-#define CF_RELEASE(a) if ((a) != NULL) CFRelease(a)
-#define cocoa_lock(s) pthread_mutex_lock(&s->mutex)
-#define cocoa_unlock(s) pthread_mutex_unlock(&s->mutex)
-
-static void vo_cocoa_fullscreen(struct vo *vo);
+static int vo_cocoa_fullscreen(struct vo *vo);
static void cocoa_rm_fs_screen_profile_observer(struct vo_cocoa_state *s);
struct vo_cocoa_state {
+ // --- The following members can be accessed only by the main thread (i.e.
+ // where Cocoa runs), or if the main thread is fully blocked.
+
NSWindow *window;
NSView *view;
MpvVideoView *video;
MpvCocoaAdapter *adapter;
- NSOpenGLContext *gl_ctx;
+
+ CGLContextObj cgl_ctx;
+ NSOpenGLContext *nsgl_ctx;
NSScreen *current_screen;
NSScreen *fs_screen;
@@ -67,19 +70,16 @@ struct vo_cocoa_state {
NSInteger window_level;
- int pending_events;
-
- bool waiting_frame;
- bool skip_swap_buffer;
bool embedded; // wether we are embedding in another GUI
+ atomic_bool waiting_frame;
+
IOPMAssertionID power_mgmt_assertion;
io_connect_t light_sensor;
uint64_t last_lmuvalue;
int last_lux;
IONotificationPortRef light_sensor_io_port;
- pthread_mutex_t mutex;
struct mp_log *log;
uint32_t old_dwidth;
@@ -89,30 +89,28 @@ struct vo_cocoa_state {
NSData *icc_fs_profile;
id fs_icc_changed_ns_observer;
- void (*resize_redraw)(struct vo *vo, int w, int h);
-};
+ pthread_mutex_t lock;
+ pthread_cond_t wakeup;
-static void with_cocoa_lock(struct vo_cocoa_state *s, void(^block)(void))
-{
- cocoa_lock(s);
- block();
- cocoa_unlock(s);
-}
+ // --- The following members are protected by the lock.
+ // If the VO and main threads are both blocked, locking is optional
+ // for members accessed only by VO and main thread.
-static void with_cocoa_lock_on_main_thread(struct vo *vo, void(^block)(void))
-{
- struct vo_cocoa_state *s = vo->cocoa;
- dispatch_async(dispatch_get_main_queue(), ^{
- with_cocoa_lock(s, block);
- });
-}
+ int pending_events;
+
+ int vo_dwidth; // current or soon-to-be VO size
+ int vo_dheight;
-static void with_cocoa_lock_on_main_thread_sync(struct vo *vo, void(^block)(void))
+ bool vo_ready; // the VO is in a state in which it can
+ // render frames
+ int frame_w, frame_h; // dimensions of the frame rendered
+
+ NSCursor *blankCursor;
+};
+
+static void run_on_main_thread(struct vo *vo, void(^block)(void))
{
- struct vo_cocoa_state *s = vo->cocoa;
- dispatch_sync(dispatch_get_main_queue(), ^{
- with_cocoa_lock(s, block);
- });
+ dispatch_sync(dispatch_get_main_queue(), block);
}
static void queue_new_video_size(struct vo *vo, int w, int h)
@@ -124,6 +122,16 @@ static void queue_new_video_size(struct vo *vo, int w, int h)
}
}
+static void flag_events(struct vo *vo, int events)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ pthread_mutex_lock(&s->lock);
+ s->pending_events |= events;
+ pthread_mutex_unlock(&s->lock);
+ if (events)
+ vo_wakeup(vo);
+}
+
static void enable_power_management(struct vo_cocoa_state *s)
{
if (!s->power_mgmt_assertion) return;
@@ -148,6 +156,9 @@ static const char macosx_icon[] =
static void set_application_icon(NSApplication *app)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSBundle *bundle = [NSBundle mainBundle];
+ if ([bundle pathForResource:@"icon" ofType:@"icns"])
+ return;
NSData *icon_data = [NSData dataWithBytesNoCopy:(void *)macosx_icon
length:sizeof(macosx_icon)
freeWhenDone:NO];
@@ -191,16 +202,14 @@ static void light_sensor_cb(void *ctx, io_service_t srv, natural_t mtype, void *
if (s->last_lmuvalue != mean) {
s->last_lmuvalue = mean;
s->last_lux = lmuvalue_to_lux(s->last_lmuvalue);
- s->pending_events |= VO_EVENT_AMBIENT_LIGHTING_CHANGED;
- vo_wakeup(vo);
- return;
+ flag_events(vo, VO_EVENT_AMBIENT_LIGHTING_CHANGED);
}
}
}
static void cocoa_init_light_sensor(struct vo *vo)
{
- with_cocoa_lock_on_main_thread(vo, ^{
+ run_on_main_thread(vo, ^{
struct vo_cocoa_state *s = vo->cocoa;
io_service_t srv = IOServiceGetMatchingService(
kIOMasterPortDefault, IOServiceMatching("AppleLMUController"));
@@ -243,12 +252,17 @@ int vo_cocoa_init(struct vo *vo)
{
struct vo_cocoa_state *s = talloc_zero(NULL, struct vo_cocoa_state);
*s = (struct vo_cocoa_state){
- .waiting_frame = false,
.power_mgmt_assertion = kIOPMNullAssertionID,
.log = mp_log_new(s, vo->log, "cocoa"),
.embedded = vo->opts->WinID >= 0,
};
- mpthread_mutex_init_recursive(&s->mutex);
+ if (!s->embedded) {
+ NSImage* blankImage = [[NSImage alloc] initWithSize:NSMakeSize(1, 1)];
+ s->blankCursor = [[NSCursor alloc] initWithImage:blankImage hotSpot:NSZeroPoint];
+ [blankImage release];
+ }
+ pthread_mutex_init(&s->lock, NULL);
+ pthread_cond_init(&s->wakeup, NULL);
vo->cocoa = s;
cocoa_init_light_sensor(vo);
return 1;
@@ -264,9 +278,9 @@ static int vo_cocoa_set_cursor_visibility(struct vo *vo, bool *visible)
MpvEventsView *v = (MpvEventsView *) s->view;
if (*visible) {
- CGDisplayShowCursor(kCGDirectMainDisplay);
- } else if ([v canHideCursor]) {
- CGDisplayHideCursor(kCGDirectMainDisplay);
+ [[NSCursor arrowCursor] set];
+ } else if ([v canHideCursor] && s->blankCursor) {
+ [s->blankCursor set];
} else {
*visible = true;
}
@@ -274,23 +288,22 @@ static int vo_cocoa_set_cursor_visibility(struct vo *vo, bool *visible)
return VO_TRUE;
}
-void vo_cocoa_register_resize_callback(struct vo *vo,
- void (*cb)(struct vo *vo, int w, int h))
-{
- struct vo_cocoa_state *s = vo->cocoa;
- s->resize_redraw = cb;
-}
-
void vo_cocoa_uninit(struct vo *vo)
{
struct vo_cocoa_state *s = vo->cocoa;
- with_cocoa_lock_on_main_thread_sync(vo, ^{
+ pthread_mutex_lock(&s->lock);
+ s->vo_ready = false;
+ pthread_cond_signal(&s->wakeup);
+ pthread_mutex_unlock(&s->lock);
+
+ run_on_main_thread(vo, ^{
enable_power_management(s);
cocoa_uninit_light_sensor(s);
cocoa_rm_fs_screen_profile_observer(s);
- [s->gl_ctx release];
+ [s->nsgl_ctx release];
+ CGLReleaseContext(s->cgl_ctx);
// needed to stop resize events triggered by the event's view -clear
// causing many uses after free
@@ -304,6 +317,11 @@ void vo_cocoa_uninit(struct vo *vo)
if (s->window)
[s->window release];
+ if (!s->embedded)
+ [s->blankCursor release];
+
+ pthread_cond_destroy(&s->wakeup);
+ pthread_mutex_destroy(&s->lock);
talloc_free(s);
});
}
@@ -363,7 +381,7 @@ static void vo_cocoa_update_screen_fps(struct vo *vo)
CVDisplayLinkRelease(link);
}
- s->pending_events |= VO_EVENT_WIN_STATE;
+ flag_events(vo, VO_EVENT_WIN_STATE);
}
static void vo_cocoa_update_screen_info(struct vo *vo, struct mp_rect *out_rc)
@@ -382,15 +400,6 @@ static void vo_cocoa_update_screen_info(struct vo *vo, struct mp_rect *out_rc)
}
}
-static void resize_window(struct vo *vo)
-{
- struct vo_cocoa_state *s = vo->cocoa;
- NSRect frame = [s->video frameInPixels];
- vo->dwidth = frame.size.width;
- vo->dheight = frame.size.height;
- [s->gl_ctx update];
-}
-
static void vo_set_level(struct vo *vo, int ontop)
{
struct vo_cocoa_state *s = vo->cocoa;
@@ -478,7 +487,7 @@ static void create_ui(struct vo *vo, struct mp_rect *win, int geo_flags)
[s->video setWantsBestResolutionOpenGLSurface:YES];
[s->view addSubview:s->video];
- [s->gl_ctx setView:s->video];
+ [s->nsgl_ctx setView:s->video];
[s->video release];
s->video.adapter = adapter;
@@ -526,7 +535,7 @@ static void cocoa_add_fs_screen_profile_observer(struct vo *vo)
return;
void (^nblock)(NSNotification *n) = ^(NSNotification *n) {
- s->pending_events |= VO_EVENT_ICC_PROFILE_CHANGED;
+ flag_events(vo, VO_EVENT_ICC_PROFILE_CHANGED);
};
s->fs_icc_changed_ns_observer = [[NSNotificationCenter defaultCenter]
@@ -536,24 +545,19 @@ static void cocoa_add_fs_screen_profile_observer(struct vo *vo)
usingBlock:nblock];
}
-void vo_cocoa_create_nsgl_ctx(struct vo *vo, void *ctx)
-{
- struct vo_cocoa_state *s = vo->cocoa;
- s->gl_ctx = [[NSOpenGLContext alloc] initWithCGLContextObj:ctx];
- [s->gl_ctx makeCurrentContext];
-}
-
-void vo_cocoa_release_nsgl_ctx(struct vo *vo)
+void vo_cocoa_set_opengl_ctx(struct vo *vo, CGLContextObj ctx)
{
struct vo_cocoa_state *s = vo->cocoa;
- [s->gl_ctx release];
- s->gl_ctx = nil;
+ run_on_main_thread(vo, ^{
+ s->cgl_ctx = CGLRetainContext(ctx);
+ s->nsgl_ctx = [[NSOpenGLContext alloc] initWithCGLContextObj:s->cgl_ctx];
+ });
}
int vo_cocoa_config_window(struct vo *vo, uint32_t flags)
{
struct vo_cocoa_state *s = vo->cocoa;
- with_cocoa_lock_on_main_thread(vo, ^{
+ run_on_main_thread(vo, ^{
struct mp_rect screenrc;
vo_cocoa_update_screen_info(vo, &screenrc);
@@ -581,120 +585,121 @@ int vo_cocoa_config_window(struct vo *vo, uint32_t flags)
vo_set_level(vo, vo->opts->ontop);
}
- // trigger a resize -> don't set vo->dwidth and vo->dheight directly
- // since this block is executed asynchronously to the video
- // reconfiguration code.
- s->pending_events |= VO_EVENT_RESIZE;
- });
+ s->vo_ready = true;
- if (!s->embedded) {
- [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
- set_application_icon(NSApp);
- }
+ // Use the actual size of the new window
+ NSRect frame = [s->video frameInPixels];
+ vo->dwidth = s->vo_dwidth = frame.size.width;
+ vo->dheight = s->vo_dheight = frame.size.height;
+
+ [s->nsgl_ctx update];
+
+ if (!s->embedded) {
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+ set_application_icon(NSApp);
+ }
+ });
return 0;
}
-void vo_cocoa_set_current_context(struct vo *vo, bool current)
+// Trigger a VO resize - called from the main thread. This is done async,
+// because the VO must resize and redraw while vo_cocoa_resize_redraw() is
+// blocking.
+static void resize_event(struct vo *vo)
{
struct vo_cocoa_state *s = vo->cocoa;
+ NSRect frame = [s->video frameInPixels];
- if (current) {
- cocoa_lock(s);
- if (s->gl_ctx) [s->gl_ctx makeCurrentContext];
- } else {
- [NSOpenGLContext clearCurrentContext];
- cocoa_unlock(s);
- }
+ pthread_mutex_lock(&s->lock);
+ s->vo_dwidth = frame.size.width;
+ s->vo_dheight = frame.size.height;
+ s->pending_events |= VO_EVENT_RESIZE | VO_EVENT_EXPOSE;
+ // Live-resizing: make sure at least one frame will be drawn
+ s->frame_w = s->frame_h = 0;
+ pthread_mutex_unlock(&s->lock);
+
+ [s->nsgl_ctx update];
+
+ vo_wakeup(vo);
}
static void vo_cocoa_resize_redraw(struct vo *vo, int width, int height)
{
struct vo_cocoa_state *s = vo->cocoa;
- if (!s->gl_ctx)
- return;
+ resize_event(vo);
- if (!s->resize_redraw)
- return;
+ pthread_mutex_lock(&s->lock);
+
+ // Make vo.c not do video timing, which would slow down resizing.
+ vo_event(vo, VO_EVENT_LIVE_RESIZING);
- vo_cocoa_set_current_context(vo, true);
+ // Wait until a new frame with the new size was rendered. For some reason,
+ // Cocoa requires this to be done before drawRect() returns.
+ struct timespec e = mp_time_us_to_timespec(mp_add_timeout(mp_time_us(), 0.1));
+ while (s->frame_w != width && s->frame_h != height && s->vo_ready) {
+ if (pthread_cond_timedwait(&s->wakeup, &s->lock, &e))
+ break;
+ }
- [s->gl_ctx update];
- s->resize_redraw(vo, width, height);
- s->skip_swap_buffer = true;
+ vo_query_and_reset_events(vo, VO_EVENT_LIVE_RESIZING);
- [s->gl_ctx flushBuffer];
- vo_cocoa_set_current_context(vo, false);
+ pthread_mutex_unlock(&s->lock);
}
static void draw_changes_after_next_frame(struct vo *vo)
{
struct vo_cocoa_state *s = vo->cocoa;
- if (!s->waiting_frame) {
- s->waiting_frame = true;
+ if (atomic_compare_exchange_strong(&s->waiting_frame, &(bool){false}, true))
NSDisableScreenUpdates();
- }
}
-bool vo_cocoa_start_frame(struct vo *vo)
+void vo_cocoa_swap_buffers(struct vo *vo)
{
struct vo_cocoa_state *s = vo->cocoa;
- s->skip_swap_buffer = false;
- return true;
-}
+ // Don't swap a frame with wrong size
+ pthread_mutex_lock(&s->lock);
+ bool skip = s->pending_events & VO_EVENT_RESIZE;
+ pthread_mutex_unlock(&s->lock);
+ if (skip)
+ return;
-void vo_cocoa_swap_buffers(struct vo *vo)
-{
- struct vo_cocoa_state *s = vo->cocoa;
+ CGLFlushDrawable(s->cgl_ctx);
- if (s->skip_swap_buffer && !s->waiting_frame) {
- s->skip_swap_buffer = false;
- s->pending_events |= VO_EVENT_EXPOSE;
- } else {
- [s->gl_ctx flushBuffer];
- }
+ pthread_mutex_lock(&s->lock);
+ s->frame_w = vo->dwidth;
+ s->frame_h = vo->dheight;
+ pthread_cond_signal(&s->wakeup);
+ pthread_mutex_unlock(&s->lock);
- if (s->waiting_frame) {
- s->waiting_frame = false;
+ if (atomic_compare_exchange_strong(&s->waiting_frame, &(bool){true}, false))
NSEnableScreenUpdates();
- }
}
-int vo_cocoa_check_events(struct vo *vo)
+static int vo_cocoa_check_events(struct vo *vo)
{
struct vo_cocoa_state *s = vo->cocoa;
+
+ pthread_mutex_lock(&s->lock);
int events = s->pending_events;
s->pending_events = 0;
-
if (events & VO_EVENT_RESIZE) {
- resize_window(vo);
+ vo->dwidth = s->vo_dwidth;
+ vo->dheight = s->vo_dheight;
}
+ pthread_mutex_unlock(&s->lock);
return events;
}
-static int vo_cocoa_fullscreen_sync(struct vo *vo)
-{
- struct vo_cocoa_state *s = vo->cocoa;
-
- if (s->embedded)
- return VO_NOTIMPL;
-
- with_cocoa_lock_on_main_thread(vo, ^{
- vo_cocoa_fullscreen(vo);
- });
-
- return VO_TRUE;
-}
-
-static void vo_cocoa_fullscreen(struct vo *vo)
+static int vo_cocoa_fullscreen(struct vo *vo)
{
struct vo_cocoa_state *s = vo->cocoa;
struct mp_vo_opts *opts = vo->opts;
if (s->embedded)
- return;
+ return VO_NOTIMPL;
vo_cocoa_update_screen_info(vo, NULL);
@@ -708,8 +713,10 @@ static void vo_cocoa_fullscreen(struct vo *vo)
[[s->view window] setDelegate:s->adapter];
}
- s->pending_events |= VO_EVENT_ICC_PROFILE_CHANGED;
- s->pending_events |= VO_EVENT_RESIZE;
+ flag_events(vo, VO_EVENT_ICC_PROFILE_CHANGED);
+ resize_event(vo);
+
+ return VO_TRUE;
}
static void vo_cocoa_control_get_icc_profile(struct vo *vo, void *arg)
@@ -726,26 +733,21 @@ static void vo_cocoa_control_get_icc_profile(struct vo *vo, void *arg)
p->len = [profile length];
}
-int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg)
+static int vo_cocoa_control_on_main_thread(struct vo *vo, int request, void *arg)
{
struct mp_vo_opts *opts = vo->opts;
switch (request) {
- case VOCTRL_CHECK_EVENTS:
- *events |= vo_cocoa_check_events(vo);
- return VO_TRUE;
case VOCTRL_FULLSCREEN:
opts->fullscreen = !opts->fullscreen;
- return vo_cocoa_fullscreen_sync(vo);
+ return vo_cocoa_fullscreen(vo);
case VOCTRL_ONTOP:
return vo_cocoa_ontop(vo);
case VOCTRL_GET_UNFS_WINDOW_SIZE: {
int *s = arg;
- with_cocoa_lock(vo->cocoa, ^{
- NSSize size = [vo->cocoa->view frame].size;
- s[0] = size.width;
- s[1] = size.height;
- });
+ NSSize size = [vo->cocoa->view frame].size;
+ s[0] = size.width;
+ s[1] = size.height;
return VO_TRUE;
}
case VOCTRL_SET_UNFS_WINDOW_SIZE: {
@@ -753,16 +755,12 @@ int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg)
int w, h;
w = s[0];
h = s[1];
- with_cocoa_lock_on_main_thread(vo, ^{
- queue_new_video_size(vo, w, h);
- });
+ queue_new_video_size(vo, w, h);
return VO_TRUE;
}
case VOCTRL_GET_WIN_STATE: {
- with_cocoa_lock(vo->cocoa, ^{
- const bool minimized = [[vo->cocoa->view window] isMiniaturized];
- *(int *)arg = minimized ? VO_WIN_STATE_MINIMIZED : 0;
- });
+ const bool minimized = [[vo->cocoa->view window] isMiniaturized];
+ *(int *)arg = minimized ? VO_WIN_STATE_MINIMIZED : 0;
return VO_TRUE;
}
case VOCTRL_SET_CURSOR_VISIBILITY:
@@ -794,12 +792,35 @@ int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg)
return VO_NOTIMPL;
}
+static int vo_cocoa_control_async(struct vo *vo, int *events, int request, void *arg)
+{
+ switch (request) {
+ case VOCTRL_CHECK_EVENTS:
+ *events |= vo_cocoa_check_events(vo);
+ return VO_TRUE;
+ case VOCTRL_GET_RECENT_FLIP_TIME:
+ return VO_FALSE; // unsupported, but avoid syncing with main thread
+ }
+ return VO_NOTIMPL;
+}
+
+int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg)
+{
+ __block int r = vo_cocoa_control_async(vo, events, request, arg);
+ if (r == VO_NOTIMPL) {
+ run_on_main_thread(vo, ^{
+ r = vo_cocoa_control_on_main_thread(vo, request, arg);
+ });
+ }
+ return r;
+}
+
@implementation MpvCocoaAdapter
@synthesize vout = _video_output;
- (void)performAsyncResize:(NSSize)size {
struct vo_cocoa_state *s = self.vout->cocoa;
- if (!s->waiting_frame)
+ if (!atomic_load(&s->waiting_frame))
vo_cocoa_resize_redraw(self.vout, size.width, size.height);
}
@@ -812,9 +833,7 @@ int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg)
}
- (void)setNeedsResize {
- struct vo_cocoa_state *s = self.vout->cocoa;
- s->pending_events |= VO_EVENT_RESIZE;
- vo_wakeup(self.vout);
+ resize_event(self.vout);
}
- (void)recalcMovableByWindowBackground:(NSPoint)p
@@ -876,8 +895,7 @@ int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg)
- (void)didChangeWindowedScreenProfile:(NSScreen *)screen
{
- struct vo_cocoa_state *s = self.vout->cocoa;
- s->pending_events |= VO_EVENT_ICC_PROFILE_CHANGED;
+ flag_events(self.vout, VO_EVENT_ICC_PROFILE_CHANGED);
}
- (void)didChangeMousePosition
@@ -898,14 +916,12 @@ int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg)
- (void)windowDidMiniaturize:(NSNotification *)notification
{
- struct vo_cocoa_state *s = self.vout->cocoa;
- s->pending_events |= VO_EVENT_WIN_STATE;
+ flag_events(self.vout, VO_EVENT_WIN_STATE);
}
- (void)windowDidDeminiaturize:(NSNotification *)notification
{
- struct vo_cocoa_state *s = self.vout->cocoa;
- s->pending_events |= VO_EVENT_WIN_STATE;
+ flag_events(self.vout, VO_EVENT_WIN_STATE);
}
@end
diff --git a/video/out/drm_common.c b/video/out/drm_common.c
index c61ad69..1ea0e45 100644
--- a/video/out/drm_common.c
+++ b/video/out/drm_common.c
@@ -52,13 +52,13 @@ int vt_switcher_init(struct vt_switcher *s, struct mp_log *log)
vt_switcher_pipe[1] = -1;
if (mp_make_cloexec_pipe(vt_switcher_pipe)) {
- MP_ERR(s, "Creating pipe failed: %s", mp_strerror(errno));
+ MP_ERR(s, "Creating pipe failed: %s\n", mp_strerror(errno));
return -1;
}
s->tty_fd = open("/dev/tty", O_RDWR | O_CLOEXEC);
if (s->tty_fd < 0) {
- MP_ERR(s, "Can't open TTY for VT control: %s", mp_strerror(errno));
+ MP_ERR(s, "Can't open TTY for VT control: %s\n", mp_strerror(errno));
return -1;
}
@@ -71,7 +71,7 @@ int vt_switcher_init(struct vt_switcher *s, struct mp_log *log)
struct vt_mode vt_mode;
if (ioctl(s->tty_fd, VT_GETMODE, &vt_mode) < 0) {
- MP_ERR(s, "VT_GETMODE failed: %s", mp_strerror(errno));
+ MP_ERR(s, "VT_GETMODE failed: %s\n", mp_strerror(errno));
return -1;
}
@@ -79,7 +79,7 @@ int vt_switcher_init(struct vt_switcher *s, struct mp_log *log)
vt_mode.relsig = SIGUSR1;
vt_mode.acqsig = SIGUSR2;
if (ioctl(s->tty_fd, VT_SETMODE, &vt_mode) < 0) {
- MP_ERR(s, "VT_SETMODE failed: %s", mp_strerror(errno));
+ MP_ERR(s, "VT_SETMODE failed: %s\n", mp_strerror(errno));
return -1;
}
diff --git a/video/out/filter_kernels.c b/video/out/filter_kernels.c
index c759cbb..0f6d5a9 100644
--- a/video/out/filter_kernels.c
+++ b/video/out/filter_kernels.c
@@ -113,7 +113,8 @@ static double sample_filter(struct filter_kernel *filter,
double w = window->weight ? window->weight(window, x/bw * window->radius
/ filter->f.radius)
: 1.0;
- return c < filter->f.radius ? w * filter->f.weight(&filter->f, c) : 0.0;
+ double v = c < filter->f.radius ? w * filter->f.weight(&filter->f, c) : 0.0;
+ return filter->clamp ? fmax(0.0, fmin(1.0, v)) : v;
}
// Calculate the 1D filtering kernel for N sample points.
@@ -366,9 +367,11 @@ const struct filter_kernel mp_filter_kernels[] = {
{{"bcspline", 2, cubic_bc, .params = {0.5, 0.5} }},
{{"catmull_rom", 2, cubic_bc, .params = {0.0, 0.5} }},
{{"mitchell", 2, cubic_bc, .params = {1.0/3.0, 1.0/3.0} }},
- {{"robidoux", 2, cubic_bc, .params = {0.3782, 0.3109}}, .polar = true},
- {{"robidouxsharp", 2, cubic_bc, .params = {0.2620, 0.3690}}, .polar = true},
- // Miscalleaneous filters
+ {{"robidoux", 2, cubic_bc, .params = {0.3782, 0.3109} }},
+ {{"robidouxsharp", 2, cubic_bc, .params = {0.2620, 0.3690} }},
+ {{"ewa_robidoux", 2, cubic_bc, .params = {0.3782, 0.3109}}, .polar = true},
+ {{"ewa_robidouxsharp", 2, cubic_bc, .params = {0.2620, 0.3690}}, .polar = true},
+ // Miscellaneous filters
{{"box", 1, box, .resizable = true}},
{{"nearest", 0.5, box}},
{{"triangle", 1, triangle, .resizable = true}},
diff --git a/video/out/filter_kernels.h b/video/out/filter_kernels.h
index 9e61762..7b101cd 100644
--- a/video/out/filter_kernels.h
+++ b/video/out/filter_kernels.h
@@ -33,6 +33,7 @@ struct filter_window {
struct filter_kernel {
struct filter_window f; // the kernel itself
struct filter_window w; // window storage
+ bool clamp; // clamp to the range [0-1]
// Constant values
const char *window; // default window
bool polar; // whether or not the filter uses polar coordinates
diff --git a/video/out/gl_cocoa.c b/video/out/gl_cocoa.c
index 1ff9c66..d04983e 100644
--- a/video/out/gl_cocoa.c
+++ b/video/out/gl_cocoa.c
@@ -121,7 +121,9 @@ static bool create_gl_context(struct MPGLContext *ctx)
return false;
}
- vo_cocoa_create_nsgl_ctx(ctx->vo, p->ctx);
+ vo_cocoa_set_opengl_ctx(ctx->vo, p->ctx);
+ CGLSetCurrentContext(p->ctx);
+
ctx->depth_r = ctx->depth_g = ctx->depth_b = cgl_color_size(ctx);
mpgl_load_functions(ctx->gl, (void *)cocoa_glgetaddr, NULL, ctx->vo->log);
@@ -149,35 +151,21 @@ static bool config_window_cocoa(struct MPGLContext *ctx, int flags)
static void releaseGlContext_cocoa(MPGLContext *ctx)
{
struct cgl_context *p = ctx->priv;
- vo_cocoa_release_nsgl_ctx(ctx->vo);
CGLReleaseContext(p->ctx);
}
-static bool start_frame_cocoa(MPGLContext *ctx)
-{
- return vo_cocoa_start_frame(ctx->vo);
-}
-
static void swapGlBuffers_cocoa(MPGLContext *ctx)
{
vo_cocoa_swap_buffers(ctx->vo);
}
-static void set_current_cocoa(MPGLContext *ctx, bool current)
-{
- vo_cocoa_set_current_context(ctx->vo, current);
-}
-
void mpgl_set_backend_cocoa(MPGLContext *ctx)
{
ctx->priv = talloc_zero(ctx, struct cgl_context);
ctx->config_window = config_window_cocoa;
ctx->releaseGlContext = releaseGlContext_cocoa;
ctx->swapGlBuffers = swapGlBuffers_cocoa;
- ctx->start_frame = start_frame_cocoa;
ctx->vo_init = vo_cocoa_init;
- ctx->register_resize_callback = vo_cocoa_register_resize_callback;
ctx->vo_uninit = vo_cocoa_uninit;
ctx->vo_control = vo_cocoa_control;
- ctx->set_current = set_current_cocoa;
}
diff --git a/video/out/gl_common.c b/video/out/gl_common.c
index fc3e9f0..2f78f11 100644
--- a/video/out/gl_common.c
+++ b/video/out/gl_common.c
@@ -44,23 +44,6 @@
#include "options/options.h"
#include "options/m_option.h"
-struct feature {
- int id;
- const char *name;
-};
-
-static const struct feature features[] = {
- {MPGL_CAP_FB, "Framebuffers"},
- {MPGL_CAP_VAO, "VAOs"},
- {MPGL_CAP_FLOAT_TEX, "Float textures"},
- {MPGL_CAP_TEX_RG, "RG textures"},
- {MPGL_CAP_1D_TEX, "1D textures"},
- {MPGL_CAP_3D_TEX, "3D textures"},
- {MPGL_CAP_DEBUG, "debugging extensions"},
- {MPGL_CAP_SW, "suspected software renderer"},
- {0},
-};
-
// This guesses if the current GL context is a suspected software renderer.
static bool is_software_gl(GL *gl)
{
@@ -142,7 +125,6 @@ static const struct gl_functions gl_functions[] = {
DEF_FN(Flush),
DEF_FN(GenBuffers),
DEF_FN(GenTextures),
- DEF_FN(GetBooleanv),
DEF_FN(GetAttribLocation),
DEF_FN(GetError),
DEF_FN(GetIntegerv),
@@ -190,7 +172,6 @@ static const struct gl_functions gl_functions[] = {
{
.ver_core = 300,
.ver_es_core = 300,
- .provides = MPGL_CAP_3D_TEX,
.functions = (const struct gl_function[]) {
DEF_FN(GetStringi),
// for ES 3.0
@@ -210,7 +191,7 @@ static const struct gl_functions gl_functions[] = {
// Framebuffers, extension in GL 2.x, core in GL 3.x core.
{
.ver_core = 300,
- .ver_es_core = 300,
+ .ver_es_core = 200,
.extension = "GL_ARB_framebuffer_object",
.provides = MPGL_CAP_FB,
.functions = (const struct gl_function[]) {
@@ -307,6 +288,15 @@ static const struct gl_functions gl_functions[] = {
{0}
},
},
+ // These don't exist - they are for the sake of mpv internals, and libmpv
+ // interaction (see libmpv/opengl_cb.h).
+ {
+ .extension = "GL_MP_D3D_interfaces",
+ .functions = (const struct gl_function[]) {
+ DEF_FN(MPGetD3DInterface),
+ {0}
+ },
+ },
};
#undef FN_OFFS
@@ -366,12 +356,6 @@ void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
if (shader)
mp_verbose(log, "GL_SHADING_LANGUAGE_VERSION='%s'\n", shader);
- // Note: This code doesn't handle CONTEXT_FORWARD_COMPATIBLE_BIT_ARB
- // on OpenGL 3.0 correctly. Apparently there's no way to detect this
- // situation, because GL_ARB_compatibility is specified only for 3.1
- // and above.
-
- bool has_legacy = false;
if (gl->version >= 300) {
gl->GetStringi = get_fn(fn_ctx, "glGetStringi");
gl->GetIntegerv = get_fn(fn_ctx, "glGetIntegerv");
@@ -384,38 +368,22 @@ void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
for (int n = 0; n < exts; n++) {
const char *ext = gl->GetStringi(GL_EXTENSIONS, n);
gl->extensions = talloc_asprintf_append(gl->extensions, " %s", ext);
- if (strcmp(ext, "GL_ARB_compatibility") == 0)
- has_legacy = true;
}
- // This version doesn't have GL_ARB_compatibility yet, and always
- // includes legacy (except with CONTEXT_FORWARD_COMPATIBLE_BIT_ARB).
- if (gl->version == 300)
- has_legacy = true;
} else {
const char *ext = (char*)gl->GetString(GL_EXTENSIONS);
gl->extensions = talloc_asprintf_append(gl->extensions, " %s", ext);
-
- has_legacy = true;
}
- if (gl->es)
- has_legacy = false;
-
- if (has_legacy)
- mp_verbose(log, "OpenGL legacy compat. found.\n");
mp_dbg(log, "Combined OpenGL extensions string:\n%s\n", gl->extensions);
- for (int n = 0; n < sizeof(gl_functions) / sizeof(gl_functions[0]); n++) {
+ for (int n = 0; n < MP_ARRAY_SIZE(gl_functions); n++) {
const struct gl_functions *section = &gl_functions[n];
int version = gl->es ? gl->es : gl->version;
int ver_core = gl->es ? section->ver_es_core : section->ver_core;
int ver_removed = gl->es ? section->ver_es_removed : section->ver_removed;
- // With has_legacy, the legacy functions are still available, and
- // functions are never actually removed. (E.g. the context could be at
- // version >= 3.0, but functions like glBegin still exist and work.)
- if (!has_legacy && ver_removed && version >= ver_removed)
+ if (ver_removed && version >= ver_removed)
continue;
// NOTE: Function entrypoints can exist, even if they do not work.
@@ -446,10 +414,8 @@ void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
section->extension ? section->extension : "builtin",
MPGL_VER_GET_MAJOR(ver_core),
MPGL_VER_GET_MINOR(ver_core));
- if (must_exist) {
- gl->mpgl_caps = 0;
+ if (must_exist)
goto error;
- }
break;
}
assert(i < MAX_FN_COUNT);
@@ -464,6 +430,8 @@ void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
if (loaded[i])
*funcptr = loaded[i];
}
+ mp_verbose(log, "Loaded functions for %d/%s.\n", ver_core,
+ section->extension ? section->extension : "builtin");
}
}
@@ -486,15 +454,9 @@ void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
gl->glsl_version = 150;
}
- if (is_software_gl(gl))
+ if (is_software_gl(gl)) {
gl->mpgl_caps |= MPGL_CAP_SW;
-
- if (gl->mpgl_caps) {
- mp_verbose(log, "Detected OpenGL features:\n");
- for (const struct feature *f = &features[0]; f->id; f++) {
- if ((f->id & gl->mpgl_caps))
- mp_verbose(log, " - %s\n", f->name);
- }
+ mp_verbose(log, "Detected suspected software renderer.\n");
}
// Provided for simpler handling if no framebuffer support is available.
@@ -525,10 +487,14 @@ typedef void (*MPGLSetBackendFn)(MPGLContext *ctx);
struct backend {
const char *name;
MPGLSetBackendFn init;
+ const struct mpgl_driver *driver;
};
+extern const struct mpgl_driver mpgl_driver_x11;
+extern const struct mpgl_driver mpgl_driver_x11egl;
+
static const struct backend backends[] = {
-#if HAVE_RPI_GLES
+#if HAVE_RPI
{"rpi", mpgl_set_backend_rpi},
#endif
#if HAVE_GL_COCOA
@@ -543,22 +509,21 @@ static const struct backend backends[] = {
{"wayland", mpgl_set_backend_wayland},
#endif
#if HAVE_GL_X11
- {"x11", mpgl_set_backend_x11},
- {"x11es", mpgl_set_backend_x11es},
+ {.driver = &mpgl_driver_x11},
#endif
#if HAVE_EGL_X11
- {"x11egl", mpgl_set_backend_x11egl},
- {"x11egles", mpgl_set_backend_x11egles},
+ {.driver = &mpgl_driver_x11egl},
#endif
- {0}
};
int mpgl_find_backend(const char *name)
{
if (name == NULL || strcmp(name, "auto") == 0)
return -1;
- for (const struct backend *entry = backends; entry->name; entry++) {
- if (strcmp(entry->name, name) == 0)
+ for (int n = 0; n < MP_ARRAY_SIZE(backends); n++) {
+ const struct backend *entry = &backends[n];
+ const char *ename = entry->driver ? entry->driver->name : entry->name;
+ if (strcmp(ename, name) == 0)
return entry - backends;
}
return -2;
@@ -570,8 +535,11 @@ int mpgl_validate_backend_opt(struct mp_log *log, const struct m_option *opt,
if (bstr_equals0(param, "help")) {
mp_info(log, "OpenGL windowing backends:\n");
mp_info(log, " auto (autodetect)\n");
- for (const struct backend *entry = backends; entry->name; entry++)
- mp_info(log, " %s\n", entry->name);
+ for (int n = 0; n < MP_ARRAY_SIZE(backends); n++) {
+ const struct backend *entry = &backends[n];
+ const char *ename = entry->driver ? entry->driver->name : entry->name;
+ mp_info(log, " %s\n", ename);
+ }
return M_OPT_EXIT - 1;
}
char s[20];
@@ -579,52 +547,35 @@ int mpgl_validate_backend_opt(struct mp_log *log, const struct m_option *opt,
return mpgl_find_backend(s) >= -1 ? 1 : M_OPT_INVALID;
}
-static MPGLContext *init_backend(struct vo *vo, MPGLSetBackendFn set_backend,
- bool probing)
+static MPGLContext *init_backend(struct vo *vo, const struct backend *backend,
+ bool probing, int vo_flags)
{
MPGLContext *ctx = talloc_ptrtype(NULL, ctx);
*ctx = (MPGLContext) {
.gl = talloc_zero(ctx, GL),
.vo = vo,
+ .driver = backend->driver,
};
bool old_probing = vo->probing;
vo->probing = probing; // hack; kill it once backends are separate
- set_backend(ctx);
- if (!ctx->vo_init(vo)) {
- talloc_free(ctx);
- ctx = NULL;
- }
- vo->probing = old_probing;
- return ctx;
-}
-
-static MPGLContext *mpgl_create(struct vo *vo, const char *backend_name)
-{
- MPGLContext *ctx = NULL;
- int index = mpgl_find_backend(backend_name);
- if (index == -1) {
- for (const struct backend *entry = backends; entry->name; entry++) {
- ctx = init_backend(vo, entry->init, true);
- if (ctx)
- break;
+ if (ctx->driver) {
+ MP_VERBOSE(vo, "Initializing OpenGL backend '%s'\n", ctx->driver->name);
+ ctx->priv = talloc_zero_size(ctx, ctx->driver->priv_size);
+ if (ctx->driver->init(ctx, vo_flags) < 0) {
+ talloc_free(ctx);
+ return NULL;
+ }
+ } else {
+ MP_VERBOSE(vo, "Initializing OpenGL backend '%s'\n", backend->name);
+ backend->init(ctx);
+ if (!ctx->vo_init(vo)) {
+ talloc_free(ctx);
+ return NULL;
}
- } else if (index >= 0) {
- ctx = init_backend(vo, backends[index].init, false);
}
- return ctx;
-}
-
-// Create a VO window and create a GL context on it.
-// vo_flags: passed to the backend's create window function
-MPGLContext *mpgl_init(struct vo *vo, const char *backend_name, int vo_flags)
-{
- MPGLContext *ctx = mpgl_create(vo, backend_name);
- if (!ctx)
- goto cleanup;
-
- ctx->requested_gl_version = 300;
+ vo->probing = old_probing;
- if (!ctx->config_window(ctx, vo_flags | VOFLAG_HIDDEN))
+ if (!ctx->driver && !ctx->config_window(ctx, vo_flags | VOFLAG_HIDDEN))
goto cleanup;
if (!ctx->gl->version && !ctx->gl->es)
@@ -650,29 +601,61 @@ cleanup:
return NULL;
}
+// Create a VO window and create a GL context on it.
+// vo_flags: passed to the backend's create window function
+MPGLContext *mpgl_init(struct vo *vo, const char *backend_name, int vo_flags)
+{
+ MPGLContext *ctx = NULL;
+ int index = mpgl_find_backend(backend_name);
+ if (index == -1) {
+ for (int n = 0; n < MP_ARRAY_SIZE(backends); n++) {
+ ctx = init_backend(vo, &backends[n], true, vo_flags);
+ if (ctx)
+ break;
+ }
+ } else if (index >= 0) {
+ ctx = init_backend(vo, &backends[index], false, vo_flags);
+ }
+ return ctx;
+}
+
// flags: passed to the backend function
bool mpgl_reconfig_window(struct MPGLContext *ctx, int vo_flags)
{
- return ctx->config_window(ctx, vo_flags);
+ if (ctx->driver) {
+ return ctx->driver->reconfig(ctx, vo_flags) >= 0;
+ } else {
+ return ctx->config_window(ctx, vo_flags);
+ }
}
-void mpgl_uninit(MPGLContext *ctx)
+int mpgl_control(struct MPGLContext *ctx, int *events, int request, void *arg)
{
- if (ctx) {
- ctx->releaseGlContext(ctx);
- ctx->vo_uninit(ctx->vo);
+ if (ctx->driver) {
+ return ctx->driver->control(ctx, events, request, arg);
+ } else {
+ return ctx->vo_control(ctx->vo, events, request, arg);
}
- talloc_free(ctx);
}
-void mpgl_lock(MPGLContext *ctx)
+void mpgl_swap_buffers(struct MPGLContext *ctx)
{
- if (ctx->set_current)
- ctx->set_current(ctx, true);
+ if (ctx->driver) {
+ ctx->driver->swap_buffers(ctx);
+ } else {
+ ctx->swapGlBuffers(ctx);
+ }
}
-void mpgl_unlock(MPGLContext *ctx)
+void mpgl_uninit(MPGLContext *ctx)
{
- if (ctx->set_current)
- ctx->set_current(ctx, false);
+ if (ctx) {
+ if (ctx->driver) {
+ ctx->driver->uninit(ctx);
+ } else {
+ ctx->releaseGlContext(ctx);
+ ctx->vo_uninit(ctx->vo);
+ }
+ }
+ talloc_free(ctx);
}
diff --git a/video/out/gl_common.h b/video/out/gl_common.h
index feeb3e5..5081709 100644
--- a/video/out/gl_common.h
+++ b/video/out/gl_common.h
@@ -74,23 +74,58 @@ enum {
#define MPGL_VER_P(ver) MPGL_VER_GET_MAJOR(ver), MPGL_VER_GET_MINOR(ver)
+struct MPGLContext;
+
+// A backend (like X11, win32, ...), which provides OpenGL rendering.
+// This should be preferred for new code, instead of setting the callbacks
+// in MPGLContext directly.
+struct mpgl_driver {
+ const char *name;
+
+ // Size of the struct allocated for MPGLContext.priv
+ int priv_size;
+
+ // Init the GL context and possibly the underlying VO backend.
+ // The created context should be compatible to GL 3.2 core profile, but
+ // some other GL versions are supported as well (e.g. GL 2.1 or GLES 2).
+ // Return 0 on success, negative value (-1) on error.
+ int (*init)(struct MPGLContext *ctx, int vo_flags);
+
+ // Resize the window, or create a new window if there isn't one yet.
+ // Currently, there is an unfortunate interaction with ctx->vo, and
+ // display size etc. are determined by it.
+ // Return 0 on success, negative value (-1) on error.
+ int (*reconfig)(struct MPGLContext *ctx, int flags);
+
+ // Present the frame.
+ void (*swap_buffers)(struct MPGLContext *ctx);
+
+ // This behaves exactly like vo_driver.control().
+ int (*control)(struct MPGLContext *ctx, int *events, int request, void *arg);
+
+ // Destroy the GL context and possibly the underlying VO backend.
+ void (*uninit)(struct MPGLContext *ctx);
+};
+
typedef struct MPGLContext {
GL *gl;
struct vo *vo;
+ const struct mpgl_driver *driver;
// Bit size of each component in the created framebuffer. 0 if unknown.
int depth_r, depth_g, depth_b;
- // GL version requested from config_window_gl3 backend (MPGL_VER mangled).
- // (Might be different from the actual version in gl->version.)
- int requested_gl_version;
+ // For free use by the mpgl_driver.
+ void *priv;
+
+ // Warning: all callbacks below are legacy. Newer code should use
+ // a mpgl_driver struct, which replaces these callbacks.
void (*swapGlBuffers)(struct MPGLContext *);
int (*vo_init)(struct vo *vo);
void (*vo_uninit)(struct vo *vo);
int (*vo_control)(struct vo *vo, int *events, int request, void *arg);
void (*releaseGlContext)(struct MPGLContext *);
- void (*set_current)(struct MPGLContext *, bool current);
// Used on windows only, tries to vsync with the DWM, and modifies SwapInterval
// when it does so. Returns the possibly modified swapinterval value.
@@ -99,36 +134,15 @@ typedef struct MPGLContext {
// Resize the window, or create a new window if there isn't one yet.
- // On the first call, it creates a GL context according to what's specified
- // in MPGLContext.requested_gl_version. This is just a hint, and if the
- // requested version is not available, it may return a completely different
- // GL context. (The caller must check if the created GL version is ok. The
- // callee must try to fall back to an older version if the requested
- // version is not available, and newer versions are incompatible.)
+ // On the first call, it creates a GL context.
bool (*config_window)(struct MPGLContext *ctx, int flags);
-
- // An optional function to register a resize callback in the backend that
- // can be called on separate thread to handle resize events immediately
- // (without waiting for vo_check_events, which will come later for the
- // proper resize)
- void (*register_resize_callback)(struct vo *vo,
- void (*cb)(struct vo *vo, int w, int h));
-
- // Optional callback on the beginning of a frame. The frame will be finished
- // with swapGlBuffers(). This returns false if use of the OpenGL context
- // should be avoided.
- bool (*start_frame)(struct MPGLContext *);
-
- // For free use by the backend.
- void *priv;
} MPGLContext;
-void mpgl_lock(MPGLContext *ctx);
-void mpgl_unlock(MPGLContext *ctx);
-
MPGLContext *mpgl_init(struct vo *vo, const char *backend_name, int vo_flags);
void mpgl_uninit(MPGLContext *ctx);
bool mpgl_reconfig_window(struct MPGLContext *ctx, int vo_flags);
+int mpgl_control(struct MPGLContext *ctx, int *events, int request, void *arg);
+void mpgl_swap_buffers(struct MPGLContext *ctx);
int mpgl_find_backend(const char *name);
@@ -138,10 +152,6 @@ int mpgl_validate_backend_opt(struct mp_log *log, const struct m_option *opt,
void mpgl_set_backend_cocoa(MPGLContext *ctx);
void mpgl_set_backend_w32(MPGLContext *ctx);
-void mpgl_set_backend_x11(MPGLContext *ctx);
-void mpgl_set_backend_x11es(MPGLContext *ctx);
-void mpgl_set_backend_x11egl(MPGLContext *ctx);
-void mpgl_set_backend_x11egles(MPGLContext *ctx);
void mpgl_set_backend_wayland(MPGLContext *ctx);
void mpgl_set_backend_rpi(MPGLContext *ctx);
@@ -166,8 +176,6 @@ struct GL {
void (GLAPIENTRY *Clear)(GLbitfield);
void (GLAPIENTRY *GenTextures)(GLsizei, GLuint *);
void (GLAPIENTRY *DeleteTextures)(GLsizei, const GLuint *);
- void (GLAPIENTRY *Color4ub)(GLubyte, GLubyte, GLubyte, GLubyte);
- void (GLAPIENTRY *Color4f)(GLfloat, GLfloat, GLfloat, GLfloat);
void (GLAPIENTRY *ClearColor)(GLclampf, GLclampf, GLclampf, GLclampf);
void (GLAPIENTRY *Enable)(GLenum);
void (GLAPIENTRY *Disable)(GLenum);
@@ -186,7 +194,6 @@ struct GL {
const GLvoid *);
void (GLAPIENTRY *TexParameteri)(GLenum, GLenum, GLint);
void (GLAPIENTRY *GetIntegerv)(GLenum, GLint *);
- void (GLAPIENTRY *GetBooleanv)(GLenum, GLboolean *);
void (GLAPIENTRY *ReadPixels)(GLint, GLint, GLsizei, GLsizei, GLenum,
GLenum, GLvoid *);
void (GLAPIENTRY *ReadBuffer)(GLenum);
@@ -263,6 +270,8 @@ struct GL {
void (GLAPIENTRY *DebugMessageCallback)(MP_GLDEBUGPROC callback,
const void *userParam);
+
+ void *(GLAPIENTRY *MPGetD3DInterface)(const char *name);
};
#endif /* MPLAYER_GL_COMMON_H */
diff --git a/video/out/gl_hwdec.c b/video/out/gl_hwdec.c
index 9f6928b..e5af2b7 100644
--- a/video/out/gl_hwdec.c
+++ b/video/out/gl_hwdec.c
@@ -31,7 +31,9 @@
extern const struct gl_hwdec_driver gl_hwdec_vaglx;
extern const struct gl_hwdec_driver gl_hwdec_vda;
+extern const struct gl_hwdec_driver gl_hwdec_videotoolbox;
extern const struct gl_hwdec_driver gl_hwdec_vdpau;
+extern const struct gl_hwdec_driver gl_hwdec_dxva2;
static const struct gl_hwdec_driver *const mpgl_hwdec_drivers[] = {
#if HAVE_VAAPI_GLX
@@ -40,8 +42,11 @@ static const struct gl_hwdec_driver *const mpgl_hwdec_drivers[] = {
#if HAVE_VDPAU_GL_X11
&gl_hwdec_vdpau,
#endif
-#if HAVE_VDA_GL
- &gl_hwdec_vda,
+#if HAVE_VIDEOTOOLBOX_VDA_GL
+ &gl_hwdec_videotoolbox,
+#endif
+#if HAVE_DXVA2_HWACCEL
+ &gl_hwdec_dxva2,
#endif
NULL
};
@@ -58,9 +63,10 @@ static struct gl_hwdec *load_hwdec_driver(struct mp_log *log, GL *gl,
.gl_texture_target = GL_TEXTURE_2D,
.reject_emulated = is_auto,
};
+ mp_verbose(log, "Loading hwdec driver '%s'\n", drv->api_name);
if (hwdec->driver->create(hwdec) < 0) {
talloc_free(hwdec);
- mp_err(log, "Couldn't load hwdec driver '%s'\n", drv->api_name);
+ mp_verbose(log, "Loading failed.\n");
return NULL;
}
return hwdec;
@@ -81,6 +87,12 @@ struct gl_hwdec *gl_hwdec_load_api(struct mp_log *log, GL *gl,
return NULL;
}
+// Like gl_hwdec_load_api(), but use HWDEC_... identifiers.
+struct gl_hwdec *gl_hwdec_load_api_id(struct mp_log *log, GL *gl, int id)
+{
+ return gl_hwdec_load_api(log, gl, m_opt_choice_str(mp_hwdec_names, id));
+}
+
void gl_hwdec_uninit(struct gl_hwdec *hwdec)
{
if (hwdec)
diff --git a/video/out/gl_hwdec.h b/video/out/gl_hwdec.h
index e60218f..f2b3fd5 100644
--- a/video/out/gl_hwdec.h
+++ b/video/out/gl_hwdec.h
@@ -46,6 +46,7 @@ struct gl_hwdec_driver {
struct gl_hwdec *gl_hwdec_load_api(struct mp_log *log, GL *gl,
const char *api_name);
+struct gl_hwdec *gl_hwdec_load_api_id(struct mp_log *log, GL *gl, int id);
void gl_hwdec_uninit(struct gl_hwdec *hwdec);
diff --git a/video/out/gl_hwdec_dxva2.c b/video/out/gl_hwdec_dxva2.c
new file mode 100644
index 0000000..52a73de
--- /dev/null
+++ b/video/out/gl_hwdec_dxva2.c
@@ -0,0 +1,64 @@
+#include "common/common.h"
+
+#include "gl_hwdec.h"
+#include "gl_utils.h"
+#include "video/d3d.h"
+#include "video/hwdec.h"
+
+// This does not provide real (zero-copy) interop - it merely exists for
+// making sure the same D3D device is used for decoding and display, which
+// may help with OpenGL fullscreen mode.
+
+struct priv {
+ struct mp_d3d_ctx ctx;
+};
+
+static void destroy(struct gl_hwdec *hw)
+{
+ struct priv *p = hw->priv;
+ if (p->ctx.d3d9_device)
+ IDirect3DDevice9_Release(p->ctx.d3d9_device);
+}
+
+static int create(struct gl_hwdec *hw)
+{
+ GL *gl = hw->gl;
+ if (hw->hwctx || !gl->MPGetD3DInterface)
+ return -1;
+
+ struct priv *p = talloc_zero(hw, struct priv);
+ hw->priv = p;
+
+ p->ctx.d3d9_device = gl->MPGetD3DInterface("IDirect3DDevice9");
+ if (!p->ctx.d3d9_device)
+ return -1;
+
+ p->ctx.hwctx.type = HWDEC_DXVA2_COPY;
+ p->ctx.hwctx.d3d_ctx = &p->ctx;
+
+ MP_VERBOSE(hw, "Using libmpv supplied device %p.\n", p->ctx.d3d9_device);
+
+ hw->hwctx = &p->ctx.hwctx;
+ hw->converted_imgfmt = 0;
+ return 0;
+}
+
+static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
+{
+ return -1;
+}
+
+static int map_image(struct gl_hwdec *hw, struct mp_image *hw_image,
+ GLuint *out_textures)
+{
+ return -1;
+}
+
+const struct gl_hwdec_driver gl_hwdec_dxva2 = {
+ .api_name = "dxva2",
+ .imgfmt = -1,
+ .create = create,
+ .reinit = reinit,
+ .map_image = map_image,
+ .destroy = destroy,
+};
diff --git a/video/out/gl_hwdec_vaglx.c b/video/out/gl_hwdec_vaglx.c
index 86ec3b5..00fecff 100644
--- a/video/out/gl_hwdec_vaglx.c
+++ b/video/out/gl_hwdec_vaglx.c
@@ -90,7 +90,7 @@ static int create(struct gl_hwdec *hw)
p->display = vaGetDisplay(x11disp);
if (!p->display)
return -1;
- p->ctx = va_initialize(p->display, p->log);
+ p->ctx = va_initialize(p->display, p->log, true);
if (!p->ctx) {
vaTerminate(p->display);
return -1;
diff --git a/video/out/gl_hwdec_vda.c b/video/out/gl_hwdec_vda.c
index dd28884..3fbcda0 100644
--- a/video/out/gl_hwdec_vda.c
+++ b/video/out/gl_hwdec_vda.c
@@ -17,6 +17,8 @@
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
+// Note: handles both VDA and VideoToolbox
+
#include <IOSurface/IOSurface.h>
#include <CoreVideo/CoreVideo.h>
#include <OpenGL/OpenGL.h>
@@ -25,31 +27,91 @@
#include "video/mp_image_pool.h"
#include "gl_hwdec.h"
+struct vda_gl_plane_format {
+ GLenum gl_format;
+ GLenum gl_type;
+ GLenum gl_internal_format;
+};
+
+struct vda_format {
+ uint32_t cvpixfmt;
+ int imgfmt;
+ int planes;
+ struct vda_gl_plane_format gl[MP_MAX_PLANES];
+};
+
struct priv {
CVPixelBufferRef pbuf;
- GLuint gl_texture;
+ GLuint gl_planes[MP_MAX_PLANES];
struct mp_hwdec_ctx hwctx;
};
+static struct vda_format vda_formats[] = {
+ {
+ .cvpixfmt = kCVPixelFormatType_422YpCbCr8,
+ .imgfmt = IMGFMT_UYVY,
+ .planes = 1,
+ .gl = {
+ { GL_RGB_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE, GL_RGB }
+ }
+ }, {
+ .cvpixfmt = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
+ .imgfmt = IMGFMT_NV12,
+ .planes = 2,
+ .gl = {
+ { GL_RED, GL_UNSIGNED_BYTE, GL_RED },
+ { GL_RG, GL_UNSIGNED_BYTE, GL_RG } ,
+ }
+ }
+};
+
+static struct vda_format *vda_get_gl_format(uint32_t cvpixfmt)
+{
+ for (int i = 0; i < MP_ARRAY_SIZE(vda_formats); i++) {
+ if (vda_formats[i].cvpixfmt == cvpixfmt)
+ return &vda_formats[i];
+ }
+ return NULL;
+}
+
+static struct vda_format *vda_get_gl_format_from_imgfmt(uint32_t imgfmt)
+{
+ for (int i = 0; i < MP_ARRAY_SIZE(vda_formats); i++) {
+ if (vda_formats[i].imgfmt == imgfmt)
+ return &vda_formats[i];
+ }
+ return NULL;
+}
+
static struct mp_image *download_image(struct mp_hwdec_ctx *ctx,
struct mp_image *hw_image,
struct mp_image_pool *swpool)
{
- if (hw_image->imgfmt != IMGFMT_VDA)
+ if (hw_image->imgfmt != IMGFMT_VDA && hw_image->imgfmt != IMGFMT_VIDEOTOOLBOX)
return NULL;
CVPixelBufferRef pbuf = (CVPixelBufferRef)hw_image->planes[3];
CVPixelBufferLockBaseAddress(pbuf, 0);
- void *base = CVPixelBufferGetBaseAddress(pbuf);
size_t width = CVPixelBufferGetWidth(pbuf);
size_t height = CVPixelBufferGetHeight(pbuf);
- size_t stride = CVPixelBufferGetBytesPerRow(pbuf);
+ uint32_t cvpixfmt = CVPixelBufferGetPixelFormatType(pbuf);
+ struct vda_format *f = vda_get_gl_format(cvpixfmt);
+ if (!f) {
+ CVPixelBufferUnlockBaseAddress(pbuf, 0);
+ return NULL;
+ }
struct mp_image img = {0};
- mp_image_setfmt(&img, IMGFMT_UYVY);
+ mp_image_setfmt(&img, f->imgfmt);
mp_image_set_size(&img, width, height);
- img.planes[0] = base;
- img.stride[0] = stride;
+
+ for (int i = 0; i < f->planes; i++) {
+ void *base = CVPixelBufferGetBaseAddressOfPlane(pbuf, i);
+ size_t stride = CVPixelBufferGetBytesPerRowOfPlane(pbuf, i);
+ img.planes[i] = base;
+ img.stride[i] = stride;
+ }
+
mp_image_copy_attributes(&img, hw_image);
struct mp_image *image = mp_image_pool_new_copy(swpool, &img);
@@ -60,11 +122,6 @@ static struct mp_image *download_image(struct mp_hwdec_ctx *ctx,
static bool check_hwdec(struct gl_hwdec *hw)
{
- if (hw->gl_texture_target != GL_TEXTURE_RECTANGLE) {
- MP_ERR(hw, "must use rectangle video textures with VDA\n");
- return false;
- }
-
if (hw->gl->version < 300) {
MP_ERR(hw, "need >= OpenGL 3.0 for core rectangle texture support\n");
return false;
@@ -78,22 +135,39 @@ static bool check_hwdec(struct gl_hwdec *hw)
return true;
}
-static int create(struct gl_hwdec *hw)
+static int create_common(struct gl_hwdec *hw, struct vda_format *format)
{
struct priv *p = talloc_zero(hw, struct priv);
+
hw->priv = p;
- hw->converted_imgfmt = IMGFMT_UYVY;
hw->gl_texture_target = GL_TEXTURE_RECTANGLE;
+ hw->converted_imgfmt = format->imgfmt;
+
if (!check_hwdec(hw))
return -1;
hw->hwctx = &p->hwctx;
- hw->hwctx->type = HWDEC_VDA;
hw->hwctx->download_image = download_image;
GL *gl = hw->gl;
- gl->GenTextures(1, &p->gl_texture);
+ gl->GenTextures(MP_MAX_PLANES, p->gl_planes);
+
+ return 0;
+}
+
+static int create(struct gl_hwdec *hw)
+{
+ // For videotoolbox, we always request NV12.
+#if HAVE_VDA_DEFAULT_INIT2
+ struct vda_format *f = vda_get_gl_format_from_imgfmt(IMGFMT_NV12);
+#else
+ struct vda_format *f = vda_get_gl_format_from_imgfmt(IMGFMT_UYVY);
+#endif
+ if (create_common(hw, f))
+ return -1;
+
+ hw->hwctx->type = HWDEC_VIDEOTOOLBOX;
return 0;
}
@@ -118,20 +192,36 @@ static int map_image(struct gl_hwdec *hw, struct mp_image *hw_image,
CVPixelBufferRetain(p->pbuf);
IOSurfaceRef surface = CVPixelBufferGetIOSurface(p->pbuf);
- gl->BindTexture(hw->gl_texture_target, p->gl_texture);
+ uint32_t cvpixfmt = CVPixelBufferGetPixelFormatType(p->pbuf);
+ struct vda_format *f = vda_get_gl_format(cvpixfmt);
+ if (!f) {
+ MP_ERR(hw, "CVPixelBuffer has unsupported format type\n");
+ return -1;
+ }
+
+ const bool planar = CVPixelBufferIsPlanar(p->pbuf);
+ const int planes = CVPixelBufferGetPlaneCount(p->pbuf);
+ assert(planar && planes == f->planes || f->planes == 1);
+
+ for (int i = 0; i < f->planes; i++) {
+ gl->BindTexture(hw->gl_texture_target, p->gl_planes[i]);
- CGLError err = CGLTexImageIOSurface2D(
- CGLGetCurrentContext(), hw->gl_texture_target, GL_RGB,
- CVPixelBufferGetWidth(p->pbuf), CVPixelBufferGetHeight(p->pbuf),
- GL_RGB_422_APPLE, GL_UNSIGNED_SHORT_8_8_APPLE, surface, 0);
+ CGLError err = CGLTexImageIOSurface2D(
+ CGLGetCurrentContext(), hw->gl_texture_target,
+ f->gl[i].gl_internal_format,
+ IOSurfaceGetWidthOfPlane(surface, i),
+ IOSurfaceGetHeightOfPlane(surface, i),
+ f->gl[i].gl_format, f->gl[i].gl_type, surface, i);
- if (err != kCGLNoError)
- MP_ERR(hw, "error creating IOSurface texture: %s (%x)\n",
- CGLErrorString(err), gl->GetError());
+ if (err != kCGLNoError)
+ MP_ERR(hw, "error creating IOSurface texture for plane %d: %s (%x)\n",
+ i, CGLErrorString(err), gl->GetError());
- gl->BindTexture(hw->gl_texture_target, 0);
+ gl->BindTexture(hw->gl_texture_target, 0);
+
+ out_textures[i] = p->gl_planes[i];
+ }
- out_textures[0] = p->gl_texture;
return 0;
}
@@ -141,13 +231,12 @@ static void destroy(struct gl_hwdec *hw)
GL *gl = hw->gl;
CVPixelBufferRelease(p->pbuf);
- gl->DeleteTextures(1, &p->gl_texture);
- p->gl_texture = 0;
+ gl->DeleteTextures(MP_MAX_PLANES, p->gl_planes);
}
-const struct gl_hwdec_driver gl_hwdec_vda = {
- .api_name = "vda",
- .imgfmt = IMGFMT_VDA,
+const struct gl_hwdec_driver gl_hwdec_videotoolbox = {
+ .api_name = "videotoolbox",
+ .imgfmt = IMGFMT_VIDEOTOOLBOX,
.create = create,
.reinit = reinit,
.map_image = map_image,
diff --git a/video/out/gl_hwdec_vdpau.c b/video/out/gl_hwdec_vdpau.c
index bb502ad..851f793 100644
--- a/video/out/gl_hwdec_vdpau.c
+++ b/video/out/gl_hwdec_vdpau.c
@@ -109,7 +109,7 @@ static int create(struct gl_hwdec *hw)
struct priv *p = talloc_zero(hw, struct priv);
hw->priv = p;
p->log = hw->log;
- p->ctx = mp_vdpau_create_device_x11(hw->log, x11disp);
+ p->ctx = mp_vdpau_create_device_x11(hw->log, x11disp, true);
if (!p->ctx)
return -1;
if (mp_vdpau_handle_preemption(p->ctx, &p->preemption_counter) < 1)
diff --git a/video/out/gl_lcms.c b/video/out/gl_lcms.c
index 09441c3..ec3013c 100644
--- a/video/out/gl_lcms.c
+++ b/video/out/gl_lcms.c
@@ -41,6 +41,8 @@
#if HAVE_LCMS2
#include <lcms2.h>
+#include <libavutil/sha.h>
+#include <libavutil/mem.h>
struct gl_lcms {
void *icc_data;
@@ -79,9 +81,11 @@ const struct m_sub_options mp_icc_conf = {
.opts = (const m_option_t[]) {
OPT_STRING("icc-profile", profile, 0),
OPT_FLAG("icc-profile-auto", profile_auto, 0),
- OPT_STRING("icc-cache", cache, 0),
+ OPT_STRING("icc-cache-dir", cache_dir, 0),
OPT_INT("icc-intent", intent, 0),
OPT_STRING_VALIDATE("3dlut-size", size_str, 0, validate_3dlut_size_opt),
+
+ OPT_REMOVED("icc-cache", "see icc-cache-dir"),
{0}
},
.size = sizeof(struct mp_icc_opts),
@@ -98,20 +102,6 @@ static void lcms2_error_handler(cmsContext ctx, cmsUInt32Number code,
MP_ERR(p, "lcms2: %s\n", msg);
}
-static struct bstr load_file(void *talloc_ctx, const char *filename,
- struct mpv_global *global)
-{
- struct bstr res = {0};
- char *fname = mp_get_user_path(NULL, global, filename);
- stream_t *s = stream_open(fname, global);
- if (s) {
- res = stream_read_complete(s, talloc_ctx, 1000000000);
- free_stream(s);
- }
- talloc_free(fname);
- return res;
-}
-
static bool load_profile(struct gl_lcms *p)
{
if (p->icc_data && p->icc_size)
@@ -120,8 +110,11 @@ static bool load_profile(struct gl_lcms *p)
if (!p->icc_path)
return false;
- MP_INFO(p, "Opening ICC profile '%s'\n", p->icc_path);
- struct bstr iccdata = load_file(p, p->icc_path, p->global);
+ char *fname = mp_get_user_path(NULL, p->global, p->icc_path);
+ MP_VERBOSE(p, "Opening ICC profile '%s'\n", fname);
+ struct bstr iccdata = stream_read_file(fname, p, p->global,
+ 100000000); // 100 MB
+ talloc_free(fname);
if (!iccdata.len)
return false;
@@ -187,8 +180,6 @@ bool gl_lcms_has_changed(struct gl_lcms *p)
return change;
}
-#define LUT3D_CACHE_HEADER "mpv 3dlut cache 1.0\n"
-
bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d)
{
int s_r, s_g, s_b;
@@ -205,27 +196,40 @@ bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d)
struct lut3d *lut = NULL;
cmsContext cms = NULL;
- char *cache_info =
+ char *cache_file = NULL;
+ if (p->opts.cache_dir && p->opts.cache_dir[0]) {
// Gamma is included in the header to help uniquely identify it,
// because we may change the parameter in the future or make it
// customizable, same for the primaries.
- talloc_asprintf(tmp, "intent=%d, size=%dx%dx%d, gamma=2.4, prim=bt2020\n",
- p->opts.intent, s_r, s_g, s_b);
-
- bstr iccdata = (bstr) {
- .start = p->icc_data,
- .len = p->icc_size,
- };
+ char *cache_info = talloc_asprintf(tmp,
+ "ver=1.1, intent=%d, size=%dx%dx%d, gamma=2.4, prim=bt2020\n",
+ p->opts.intent, s_r, s_g, s_b);
+
+ uint8_t hash[32];
+ struct AVSHA *sha = av_sha_alloc();
+ if (!sha)
+ abort();
+ av_sha_init(sha, 256);
+ av_sha_update(sha, cache_info, strlen(cache_info));
+ av_sha_update(sha, p->icc_data, p->icc_size);
+ av_sha_final(sha, hash);
+ av_free(sha);
+
+ char *cache_dir = mp_get_user_path(tmp, p->global, p->opts.cache_dir);
+ cache_file = talloc_strdup(tmp, "");
+ for (int i = 0; i < sizeof(hash); i++)
+ cache_file = talloc_asprintf_append(cache_file, "%02X", hash[i]);
+ cache_file = mp_path_join(tmp, cache_dir, cache_file);
+
+ mp_mkdirp(cache_dir);
+ }
// check cache
- if (p->opts.cache) {
- MP_INFO(p, "Opening 3D LUT cache in file '%s'.\n", p->opts.cache);
- struct bstr cachedata = load_file(tmp, p->opts.cache, p->global);
- if (bstr_eatstart(&cachedata, bstr0(LUT3D_CACHE_HEADER))
- && bstr_eatstart(&cachedata, bstr0(cache_info))
- && bstr_eatstart(&cachedata, iccdata)
- && cachedata.len == talloc_get_size(output))
- {
+ if (cache_file) {
+ MP_VERBOSE(p, "Opening 3D LUT cache in file '%s'.\n", cache_file);
+ struct bstr cachedata = stream_read_file(cache_file, tmp, p->global,
+ 1000000000); // 1 GB
+ if (cachedata.len == talloc_get_size(output)) {
memcpy(output, cachedata.start, cachedata.len);
goto done;
} else {
@@ -286,16 +290,12 @@ bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d)
cmsDeleteTransform(trafo);
- if (p->opts.cache) {
- char *fname = mp_get_user_path(NULL, p->global, p->opts.cache);
- FILE *out = fopen(fname, "wb");
+ if (cache_file) {
+ FILE *out = fopen(cache_file, "wb");
if (out) {
- fprintf(out, "%s%s", LUT3D_CACHE_HEADER, cache_info);
- fwrite(p->icc_data, p->icc_size, 1, out);
fwrite(output, talloc_get_size(output), 1, out);
fclose(out);
}
- talloc_free(fname);
}
done: ;
diff --git a/video/out/gl_lcms.h b/video/out/gl_lcms.h
index 883b485..5ad08b7 100644
--- a/video/out/gl_lcms.h
+++ b/video/out/gl_lcms.h
@@ -10,7 +10,7 @@ extern const struct m_sub_options mp_icc_conf;
struct mp_icc_opts {
char *profile;
int profile_auto;
- char *cache;
+ char *cache_dir;
char *size_str;
int intent;
};
diff --git a/video/out/gl_osd.c b/video/out/gl_osd.c
index a42c208..0307904 100644
--- a/video/out/gl_osd.c
+++ b/video/out/gl_osd.c
@@ -13,6 +13,11 @@
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * You can alternatively redistribute this file 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.
*/
#include <stdlib.h>
@@ -73,6 +78,7 @@ struct mpgl_osd_part {
int w, h;
GLuint buffer;
int num_subparts;
+ int prev_num_subparts;
struct sub_bitmap *subparts;
struct vertex *vertices;
struct bitmap_packer *packer;
@@ -88,6 +94,7 @@ struct mpgl_osd {
const struct osd_fmt_entry *fmt_table;
bool formats[SUBBITMAP_COUNT];
struct gl_vao vao;
+ int64_t change_counter;
// temporary
int stereo_mode;
int display_size[2];
@@ -283,6 +290,7 @@ static void gen_osd_cb(void *pctx, struct sub_bitmaps *imgs)
osd->packer->count = 0;
osd->change_id = imgs->change_id;
+ ctx->change_counter += 1;
}
osd->num_subparts = osd->packer->count;
@@ -415,4 +423,18 @@ void mpgl_osd_generate(struct mpgl_osd *ctx, struct mp_osd_res res, double pts,
osd_draw(ctx->osd, s_res, pts, draw_flags, ctx->formats, gen_osd_cb, ctx);
ctx->stereo_mode = stereo_mode;
+
+ // Parts going away does not necessarily result in gen_osd_cb() being called
+ // (not even with num_parts==0), so check this separately.
+ for (int n = 0; n < MAX_OSD_PARTS; n++) {
+ struct mpgl_osd_part *part = ctx->parts[n];
+ if (part->num_subparts != part->prev_num_subparts)
+ ctx->change_counter += 1;
+ part->prev_num_subparts = part->num_subparts;
+ }
+}
+
+int64_t mpgl_get_change_counter(struct mpgl_osd *ctx)
+{
+ return ctx->change_counter;
}
diff --git a/video/out/gl_osd.h b/video/out/gl_osd.h
index 130008a..e00565a 100644
--- a/video/out/gl_osd.h
+++ b/video/out/gl_osd.h
@@ -17,5 +17,6 @@ void mpgl_osd_generate(struct mpgl_osd *ctx, struct mp_osd_res res, double pts,
enum sub_bitmap_format mpgl_osd_get_part_format(struct mpgl_osd *ctx, int index);
struct gl_vao *mpgl_osd_get_vao(struct mpgl_osd *ctx);
void mpgl_osd_draw_part(struct mpgl_osd *ctx, int vp_w, int vp_h, int index);
+int64_t mpgl_get_change_counter(struct mpgl_osd *ctx);
#endif
diff --git a/video/out/gl_rpi.c b/video/out/gl_rpi.c
index f82046e..9109662 100644
--- a/video/out/gl_rpi.c
+++ b/video/out/gl_rpi.c
@@ -23,25 +23,11 @@
#include <stddef.h>
#include <assert.h>
-#include <bcm_host.h>
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-
#include "common/common.h"
#include "x11_common.h"
#include "gl_common.h"
-struct priv {
- EGLDisplay egl_display;
- EGLContext egl_context;
- EGLSurface egl_surface;
- DISPMANX_DISPLAY_HANDLE_T display;
- DISPMANX_ELEMENT_HANDLE_T window;
- DISPMANX_UPDATE_HANDLE_T update;
- // yep, the API keeps a pointer to it
- EGL_DISPMANX_WINDOW_T egl_window;
- int w, h;
-};
+#include "gl_rpi.h"
static void *get_proc_address(const GLubyte *name)
{
@@ -60,10 +46,8 @@ static void *get_proc_address(const GLubyte *name)
return p;
}
-static EGLConfig select_fb_config_egl(struct MPGLContext *ctx)
+static EGLConfig select_fb_config_egl(struct mp_egl_rpi *p)
{
- struct priv *p = ctx->priv;
-
EGLint attributes[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
@@ -80,19 +64,104 @@ static EGLConfig select_fb_config_egl(struct MPGLContext *ctx)
eglChooseConfig(p->egl_display, attributes, &config, 1, &config_count);
if (!config_count) {
- MP_FATAL(ctx->vo, "Could find EGL configuration!\n");
+ MP_FATAL(p, "Could find EGL configuration!\n");
return NULL;
}
return config;
}
+int mp_egl_rpi_init(struct mp_egl_rpi *p, DISPMANX_ELEMENT_HANDLE_T window,
+ int w, int h)
+{
+ p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (!eglInitialize(p->egl_display, NULL, NULL)) {
+ MP_FATAL(p, "EGL failed to initialize.\n");
+ goto fail;
+ }
+
+ eglBindAPI(EGL_OPENGL_ES_API);
+
+ EGLConfig config = select_fb_config_egl(p);
+ if (!config)
+ goto fail;
+
+ p->egl_window = (EGL_DISPMANX_WINDOW_T){
+ .element = window,
+ .width = w,
+ .height = h,
+ };
+ p->egl_surface = eglCreateWindowSurface(p->egl_display, config,
+ &p->egl_window, NULL);
+
+ if (p->egl_surface == EGL_NO_SURFACE) {
+ MP_FATAL(p, "Could not create EGL surface!\n");
+ goto fail;
+ }
+
+ EGLint context_attributes[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+ p->egl_context = eglCreateContext(p->egl_display, config,
+ EGL_NO_CONTEXT, context_attributes);
+
+ if (p->egl_context == EGL_NO_CONTEXT) {
+ MP_FATAL(p, "Could not create EGL context!\n");
+ goto fail;
+ }
+
+ eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface,
+ p->egl_context);
+
+ p->gl = talloc_zero(NULL, struct GL);
+
+ const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS);
+ mpgl_load_functions(p->gl, get_proc_address, exts, p->log);
+
+ if (!p->gl->version && !p->gl->es)
+ goto fail;
+
+ return 0;
+
+fail:
+ mp_egl_rpi_destroy(p);
+ return -1;
+}
+
+void mp_egl_rpi_destroy(struct mp_egl_rpi *p)
+{
+ if (p->egl_display) {
+ eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ }
+ if (p->egl_surface)
+ eglDestroySurface(p->egl_display, p->egl_surface);
+ if (p->egl_context)
+ eglDestroyContext(p->egl_display, p->egl_context);
+ p->egl_context = EGL_NO_CONTEXT;
+ eglReleaseThread();
+ p->egl_display = EGL_NO_DISPLAY;
+ talloc_free(p->gl);
+ p->gl = NULL;
+}
+
+struct priv {
+ DISPMANX_DISPLAY_HANDLE_T display;
+ DISPMANX_ELEMENT_HANDLE_T window;
+ DISPMANX_UPDATE_HANDLE_T update;
+ struct mp_egl_rpi egl;
+ int w, h;
+};
+
static bool sc_config_window(struct MPGLContext *ctx, int flags)
{
struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
- if (p->egl_context) {
+ p->egl.log = vo->log;
+
+ if (p->egl.gl) {
vo->dwidth = p->w;
vo->dheight = p->h;
return true;
@@ -130,43 +199,10 @@ static bool sc_config_window(struct MPGLContext *ctx, int flags)
vc_dispmanx_update_submit_sync(p->update);
- p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- if (!eglInitialize(p->egl_display, NULL, NULL)) {
- MP_FATAL(ctx->vo, "EGL failed to initialize.\n");
- return false;
- }
-
- eglBindAPI(EGL_OPENGL_ES_API);
-
- EGLConfig config = select_fb_config_egl(ctx);
- if (!config)
- return false;
-
- p->egl_window = (EGL_DISPMANX_WINDOW_T){.element = p->window, .width = w, .height = h};
- p->egl_surface = eglCreateWindowSurface(p->egl_display, config, &p->egl_window, NULL);
-
- if (p->egl_surface == EGL_NO_SURFACE) {
- MP_FATAL(ctx->vo, "Could not create EGL surface!\n");
+ if (mp_egl_rpi_init(&p->egl, p->window, w, h) < 0)
return false;
- }
- EGLint context_attributes[] = {
- EGL_CONTEXT_CLIENT_VERSION, 2,
- EGL_NONE
- };
- p->egl_context = eglCreateContext(p->egl_display, config,
- EGL_NO_CONTEXT, context_attributes);
-
- if (p->egl_context == EGL_NO_CONTEXT) {
- MP_FATAL(ctx->vo, "Could not create EGL context!\n");
- return false;
- }
-
- eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface,
- p->egl_context);
-
- const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS);
- mpgl_load_functions(ctx->gl, get_proc_address, exts, vo->log);
+ ctx->gl = p->egl.gl;
vo->dwidth = p->w = w;
vo->dheight = p->h = h;
@@ -177,20 +213,14 @@ static bool sc_config_window(struct MPGLContext *ctx, int flags)
static void sc_releaseGlContext(MPGLContext *ctx)
{
struct priv *p = ctx->priv;
- if (p->egl_context) {
- eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
- EGL_NO_CONTEXT);
- eglDestroyContext(p->egl_display, p->egl_context);
- }
- p->egl_context = EGL_NO_CONTEXT;
- eglTerminate(p->egl_display);
+ mp_egl_rpi_destroy(&p->egl);
vc_dispmanx_display_close(p->display);
}
static void sc_swapGlBuffers(MPGLContext *ctx)
{
struct priv *p = ctx->priv;
- eglSwapBuffers(p->egl_display, p->egl_surface);
+ eglSwapBuffers(p->egl.egl_display, p->egl.egl_surface);
}
static int sc_vo_init(struct vo *vo)
diff --git a/video/out/gl_rpi.h b/video/out/gl_rpi.h
new file mode 100644
index 0000000..e00762b
--- /dev/null
+++ b/video/out/gl_rpi.h
@@ -0,0 +1,17 @@
+#include <bcm_host.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+struct mp_egl_rpi {
+ struct mp_log *log;
+ struct GL *gl;
+ EGLDisplay egl_display;
+ EGLContext egl_context;
+ EGLSurface egl_surface;
+ // yep, the API keeps a pointer to it
+ EGL_DISPMANX_WINDOW_T egl_window;
+};
+
+int mp_egl_rpi_init(struct mp_egl_rpi *p, DISPMANX_ELEMENT_HANDLE_T window,
+ int w, int h);
+void mp_egl_rpi_destroy(struct mp_egl_rpi *p);
diff --git a/video/out/gl_utils.c b/video/out/gl_utils.c
index f8b46d9..7e13f83 100644
--- a/video/out/gl_utils.c
+++ b/video/out/gl_utils.c
@@ -28,6 +28,7 @@
#include <stdarg.h>
#include <assert.h>
+#include "stream/stream.h"
#include "common/common.h"
#include "gl_utils.h"
@@ -334,12 +335,12 @@ bool fbotex_change(struct fbotex *fbo, GL *gl, struct mp_log *log, int w, int h,
int cw = w, ch = h;
- if ((flags & FBOTEX_FUZZY_W) && cw < fbo->tex_w)
- cw = fbo->tex_w;
- if ((flags & FBOTEX_FUZZY_H) && ch < fbo->tex_h)
- ch = fbo->tex_h;
+ if ((flags & FBOTEX_FUZZY_W) && cw < fbo->w)
+ cw = fbo->w;
+ if ((flags & FBOTEX_FUZZY_H) && ch < fbo->h)
+ ch = fbo->h;
- if (fbo->tex_w == cw && fbo->tex_h == ch && fbo->iformat == iformat)
+ if (fbo->w == cw && fbo->h == ch && fbo->iformat == iformat)
return true;
if (flags & FBOTEX_FUZZY_W)
@@ -351,12 +352,12 @@ bool fbotex_change(struct fbotex *fbo, GL *gl, struct mp_log *log, int w, int h,
*fbo = (struct fbotex) {
.gl = gl,
- .tex_w = w,
- .tex_h = h,
+ .w = w,
+ .h = h,
.iformat = iformat,
};
- mp_verbose(log, "Create FBO: %dx%d\n", fbo->tex_w, fbo->tex_h);
+ mp_verbose(log, "Create FBO: %dx%d\n", fbo->w, fbo->h);
if (!(gl->mpgl_caps & MPGL_CAP_FB))
return false;
@@ -364,7 +365,7 @@ bool fbotex_change(struct fbotex *fbo, GL *gl, struct mp_log *log, int w, int h,
gl->GenFramebuffers(1, &fbo->fbo);
gl->GenTextures(1, &fbo->texture);
gl->BindTexture(GL_TEXTURE_2D, fbo->texture);
- gl->TexImage2D(GL_TEXTURE_2D, 0, iformat, fbo->tex_w, fbo->tex_h, 0,
+ gl->TexImage2D(GL_TEXTURE_2D, 0, iformat, fbo->w, fbo->h, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
@@ -464,6 +465,7 @@ void gl_set_debug_logger(GL *gl, struct mp_log *log)
#define SC_ENTRIES 16
#define SC_UNIFORM_ENTRIES 20
+#define SC_FILE_ENTRIES 10
enum uniform_type {
UT_invalid,
@@ -484,6 +486,11 @@ struct sc_uniform {
} v;
};
+struct sc_file {
+ char *path;
+ char *body;
+};
+
struct sc_entry {
GLuint gl_shader;
// the following fields define the shader's contents
@@ -494,9 +501,11 @@ struct sc_entry {
struct gl_shader_cache {
GL *gl;
struct mp_log *log;
+ struct mpv_global *global;
// this is modified during use (gl_sc_add() etc.)
char *text;
+ char *header_text;
struct gl_vao *vao;
struct sc_entry entries[SC_ENTRIES];
@@ -504,15 +513,21 @@ struct gl_shader_cache {
struct sc_uniform uniforms[SC_UNIFORM_ENTRIES];
int num_uniforms;
+
+ struct sc_file files[SC_FILE_ENTRIES];
+ int num_files;
};
-struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log)
+struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log,
+ struct mpv_global *global)
{
struct gl_shader_cache *sc = talloc_ptrtype(NULL, sc);
*sc = (struct gl_shader_cache){
.gl = gl,
.log = log,
+ .global = global,
.text = talloc_strdup(sc, ""),
+ .header_text = talloc_strdup(sc, ""),
};
return sc;
}
@@ -520,6 +535,7 @@ struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log)
void gl_sc_reset(struct gl_shader_cache *sc)
{
sc->text[0] = '\0';
+ sc->header_text[0] = '\0';
for (int n = 0; n < sc->num_uniforms; n++)
talloc_free(sc->uniforms[n].name);
sc->num_uniforms = 0;
@@ -537,6 +553,8 @@ static void sc_flush_cache(struct gl_shader_cache *sc)
void gl_sc_destroy(struct gl_shader_cache *sc)
{
+ if (!sc)
+ return;
gl_sc_reset(sc);
sc_flush_cache(sc);
talloc_free(sc);
@@ -555,6 +573,40 @@ void gl_sc_addf(struct gl_shader_cache *sc, const char *textf, ...)
va_end(ap);
}
+void gl_sc_hadd(struct gl_shader_cache *sc, const char *text)
+{
+ sc->header_text = talloc_strdup_append(sc->header_text, text);
+}
+
+const char *gl_sc_loadfile(struct gl_shader_cache *sc, const char *path)
+{
+ if (!path || !path[0] || !sc->global)
+ return NULL;
+ for (int n = 0; n < sc->num_files; n++) {
+ if (strcmp(sc->files[n].path, path) == 0)
+ return sc->files[n].body;
+ }
+ // not found -> load it
+ if (sc->num_files == SC_FILE_ENTRIES) {
+ // empty cache when it overflows
+ for (int n = 0; n < sc->num_files; n++) {
+ talloc_free(sc->files[n].path);
+ talloc_free(sc->files[n].body);
+ }
+ sc->num_files = 0;
+ }
+ struct bstr s = stream_read_file(path, sc, sc->global, 100000); // 100 kB
+ if (s.len) {
+ struct sc_file *new = &sc->files[sc->num_files++];
+ *new = (struct sc_file) {
+ .path = talloc_strdup(sc, path),
+ .body = s.start
+ };
+ return new->body;
+ }
+ return NULL;
+}
+
static struct sc_uniform *find_uniform(struct gl_shader_cache *sc,
const char *name)
{
@@ -594,6 +646,15 @@ void gl_sc_uniform_f(struct gl_shader_cache *sc, char *name, GLfloat f)
u->v.f[0] = f;
}
+void gl_sc_uniform_i(struct gl_shader_cache *sc, char *name, GLint i)
+{
+ struct sc_uniform *u = find_uniform(sc, name);
+ u->type = UT_i;
+ u->size = 1;
+ u->glsl_type = "int";
+ u->v.i[0] = i;
+}
+
void gl_sc_uniform_vec2(struct gl_shader_cache *sc, char *name, GLfloat f[2])
{
struct sc_uniform *u = find_uniform(sc, name);
@@ -762,6 +823,11 @@ static GLuint create_program(struct gl_shader_cache *sc, const char *vertex,
{
GL *gl = sc->gl;
MP_VERBOSE(sc, "recompiling a shader program:\n");
+ if (sc->header_text[0]) {
+ MP_VERBOSE(sc, "header:\n");
+ mp_log_source(sc->log, MSGL_V, sc->header_text);
+ MP_VERBOSE(sc, "body:\n");
+ }
mp_log_source(sc->log, MSGL_V, sc->text);
GLuint prog = gl->CreateProgram();
compile_attach_shader(sc, prog, GL_VERTEX_SHADER, vertex);
@@ -838,6 +904,12 @@ void gl_sc_gen_shader_and_reset(struct gl_shader_cache *sc)
struct sc_uniform *u = &sc->uniforms[n];
ADD(frag, "uniform %s %s;\n", u->glsl_type, u->name);
}
+ // custom shader header
+ if (sc->header_text[0]) {
+ ADD(frag, "// header\n");
+ ADD(frag, "%s\n", sc->header_text);
+ ADD(frag, "// body\n");
+ }
ADD(frag, "void main() {\n");
ADD(frag, "%s", sc->text);
// we require _all_ frag shaders to write to a "vec4 color"
diff --git a/video/out/gl_utils.h b/video/out/gl_utils.h
index 2c55e72..1dfca78 100644
--- a/video/out/gl_utils.h
+++ b/video/out/gl_utils.h
@@ -74,7 +74,7 @@ struct fbotex {
GLuint texture;
GLenum iformat;
GLenum tex_filter;
- int tex_w, tex_h; // size of .texture
+ int w, h; // size of .texture
};
bool fbotex_init(struct fbotex *fbo, GL *gl, struct mp_log *log, int w, int h,
@@ -119,13 +119,17 @@ void gl_set_debug_logger(GL *gl, struct mp_log *log);
struct gl_shader_cache;
-struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log);
+struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log,
+ struct mpv_global *global);
void gl_sc_destroy(struct gl_shader_cache *sc);
void gl_sc_add(struct gl_shader_cache *sc, const char *text);
void gl_sc_addf(struct gl_shader_cache *sc, const char *textf, ...);
+void gl_sc_hadd(struct gl_shader_cache *sc, const char *text);
+const char *gl_sc_loadfile(struct gl_shader_cache *sc, const char *path);
void gl_sc_uniform_sampler(struct gl_shader_cache *sc, char *name, GLenum target,
int unit);
void gl_sc_uniform_f(struct gl_shader_cache *sc, char *name, GLfloat f);
+void gl_sc_uniform_i(struct gl_shader_cache *sc, char *name, GLint f);
void gl_sc_uniform_vec2(struct gl_shader_cache *sc, char *name, GLfloat f[2]);
void gl_sc_uniform_vec3(struct gl_shader_cache *sc, char *name, GLfloat f[3]);
void gl_sc_uniform_mat2(struct gl_shader_cache *sc, char *name,
diff --git a/video/out/gl_video.c b/video/out/gl_video.c
index 4ffb432..7d27c73 100644
--- a/video/out/gl_video.c
+++ b/video/out/gl_video.c
@@ -27,6 +27,7 @@
#include <assert.h>
#include <libavutil/common.h>
+#include <libavutil/lfg.h>
#include "gl_video.h"
@@ -39,6 +40,7 @@
#include "aspect.h"
#include "bitmap_packer.h"
#include "dither.h"
+#include "vo.h"
// Pixel width of 1D lookup textures.
#define LOOKUP_TEXTURE_SIZE 256
@@ -59,6 +61,7 @@ static const char *const fixed_scale_filters[] = {
"sharpen3",
"sharpen5",
"oversample",
+ "custom",
NULL
};
static const char *const fixed_tscale_filters[] = {
@@ -93,21 +96,17 @@ static const struct gl_vao_entry vertex_vao[] = {
struct texplane {
int w, h;
- int tex_w, tex_h;
GLint gl_internal_format;
GLenum gl_target;
GLenum gl_format;
GLenum gl_type;
GLuint gl_texture;
int gl_buffer;
- int buffer_size;
- void *buffer_ptr;
};
struct video_image {
struct texplane planes[4];
bool image_flipped;
- bool needs_upload;
struct mp_image *mpi; // original input image
};
@@ -128,8 +127,7 @@ struct scaler {
struct fbosurface {
struct fbotex fbotex;
- int64_t pts;
- double vpts; // used for synchronizing subtitles only
+ double pts;
};
#define FBOSURFACES_MAX 10
@@ -137,13 +135,14 @@ struct fbosurface {
struct src_tex {
GLuint gl_tex;
GLenum gl_target;
- int tex_w, tex_h;
+ int w, h;
struct mp_rect_f src;
};
struct gl_video {
GL *gl;
+ struct mpv_global *global;
struct mp_log *log;
struct gl_video_opts opts;
bool gl_debug;
@@ -179,13 +178,20 @@ struct gl_video {
struct video_image image;
- struct fbotex indirect_fbo; // RGB target
struct fbotex chroma_merge_fbo;
+ struct fbotex source_fbo;
+ struct fbotex indirect_fbo;
struct fbotex blend_subs_fbo;
struct fbosurface surfaces[FBOSURFACES_MAX];
+ // these are duplicated so we can keep rendering back and forth between
+ // them to support an unlimited number of shader passes per step
+ struct fbotex pre_fbo[2];
+ struct fbotex post_fbo[2];
+
int surface_idx;
int surface_now;
+ int frames_drawn;
bool is_interpolated;
// state for luma (0), luma-down(1), chroma (2) and temporal (3) scalers
@@ -202,9 +208,12 @@ struct gl_video {
struct src_tex pass_tex[TEXUNIT_VIDEO_NUM];
bool use_indirect;
bool use_linear;
+ bool use_normalized_range;
float user_gamma;
+ int frames_uploaded;
int frames_rendered;
+ AVLFG lfg;
// Cached because computing it can take relatively long
int last_dither_matrix_size;
@@ -318,9 +327,9 @@ static const struct packed_fmt_entry mp_packed_formats[] = {
};
const struct gl_video_opts gl_video_opts_def = {
- .npot = 1,
.dither_depth = -1,
.dither_size = 6,
+ .temporal_dither_period = 1,
.fbo_format = GL_RGBA16,
.sigmoid_center = 0.75,
.sigmoid_slope = 6.5,
@@ -336,9 +345,9 @@ const struct gl_video_opts gl_video_opts_def = {
};
const struct gl_video_opts gl_video_opts_hq_def = {
- .npot = 1,
.dither_depth = 0,
.dither_size = 6,
+ .temporal_dither_period = 1,
.fbo_format = GL_RGBA16,
.fancy_downscaling = 1,
.sigmoid_center = 0.75,
@@ -354,6 +363,7 @@ const struct gl_video_opts gl_video_opts_hq_def = {
.background = {0, 0, 0, 255},
.gamma = 1.0f,
.blend_subs = 0,
+ .pbo = 1,
};
static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt,
@@ -369,7 +379,6 @@ const struct m_sub_options gl_video_conf = {
OPT_FLAG("gamma-auto", gamma_auto, 0),
OPT_CHOICE_C("target-prim", target_prim, 0, mp_csp_prim_names),
OPT_CHOICE_C("target-trc", target_trc, 0, mp_csp_trc_names),
- OPT_FLAG("npot", npot, 0),
OPT_FLAG("pbo", pbo, 0),
OPT_STRING_VALIDATE("scale", scaler[0].kernel.name, 0, validate_scaler_opt),
OPT_STRING_VALIDATE("dscale", scaler[1].kernel.name, 0, validate_scaler_opt),
@@ -403,6 +412,7 @@ const struct m_sub_options gl_video_conf = {
OPT_FLOATRANGE("dscale-antiring", scaler[1].antiring, 0, 0.0, 1.0),
OPT_FLOATRANGE("cscale-antiring", scaler[2].antiring, 0, 0.0, 1.0),
OPT_FLOATRANGE("tscale-antiring", scaler[3].antiring, 0, 0.0, 1.0),
+ OPT_FLAG("tscale-clamp", scaler[3].clamp, 0),
OPT_FLAG("scaler-resizes-only", scaler_resizes_only, 0),
OPT_FLAG("linear-scaling", linear_scaling, 0),
OPT_FLAG("fancy-downscaling", fancy_downscaling, 0),
@@ -428,6 +438,7 @@ const struct m_sub_options gl_video_conf = {
({"fruit", 0}, {"ordered", 1}, {"no", -1})),
OPT_INTRANGE("dither-size-fruit", dither_size, 0, 2, 8),
OPT_FLAG("temporal-dither", temporal_dither, 0),
+ OPT_INTRANGE("temporal-dither-period", temporal_dither_period, 0, 1, 128),
OPT_CHOICE("alpha", alpha_mode, 0,
({"no", 0},
{"yes", 1},
@@ -439,6 +450,10 @@ const struct m_sub_options gl_video_conf = {
({"no", 0},
{"yes", 1},
{"video", 2})),
+ OPT_STRING("source-shader", source_shader, 0),
+ OPT_STRING("scale-shader", scale_shader, 0),
+ OPT_STRINGLIST("pre-shaders", pre_shaders, 0),
+ OPT_STRINGLIST("post-shaders", post_shaders, 0),
OPT_REMOVED("approx-gamma", "this is always enabled now"),
OPT_REMOVED("cscale-down", "chroma is never downscaled"),
@@ -470,7 +485,7 @@ static void uninit_rendering(struct gl_video *p);
static void uninit_scaler(struct gl_video *p, struct scaler *scaler);
static void check_gl_features(struct gl_video *p);
static bool init_format(int fmt, struct gl_video *init);
-static void gl_video_upload_image(struct gl_video *p);
+static void gl_video_upload_image(struct gl_video *p, struct mp_image *mpi);
#define GLSL(x) gl_sc_add(p->sc, #x "\n");
#define GLSLF(...) gl_sc_addf(p->sc, __VA_ARGS__)
@@ -509,11 +524,11 @@ void gl_video_set_debug(struct gl_video *p, bool enable)
static void gl_video_reset_surfaces(struct gl_video *p)
{
for (int i = 0; i < FBOSURFACES_MAX; i++) {
- p->surfaces[i].pts = 0;
- p->surfaces[i].vpts = MP_NOPTS_VALUE;
+ p->surfaces[i].pts = MP_NOPTS_VALUE;
}
p->surface_idx = 0;
p->surface_now = 0;
+ p->frames_drawn = 0;
}
static inline int fbosurface_wrap(int id)
@@ -553,10 +568,16 @@ static void uninit_rendering(struct gl_video *p)
gl->DeleteTextures(1, &p->dither_texture);
p->dither_texture = 0;
- fbotex_uninit(&p->indirect_fbo);
fbotex_uninit(&p->chroma_merge_fbo);
+ fbotex_uninit(&p->source_fbo);
+ fbotex_uninit(&p->indirect_fbo);
fbotex_uninit(&p->blend_subs_fbo);
+ for (int n = 0; n < 2; n++) {
+ fbotex_uninit(&p->pre_fbo[n]);
+ fbotex_uninit(&p->post_fbo[n]);
+ }
+
for (int n = 0; n < FBOSURFACES_MAX; n++)
fbotex_uninit(&p->surfaces[n].fbotex);
@@ -606,8 +627,8 @@ static void pass_load_fbotex(struct gl_video *p, struct fbotex *src_fbo, int id,
p->pass_tex[id] = (struct src_tex){
.gl_tex = src_fbo->texture,
.gl_target = GL_TEXTURE_2D,
- .tex_w = src_fbo->tex_w,
- .tex_h = src_fbo->tex_h,
+ .w = src_fbo->w,
+ .h = src_fbo->h,
.src = {0, 0, w, h},
};
}
@@ -615,7 +636,6 @@ static void pass_load_fbotex(struct gl_video *p, struct fbotex *src_fbo, int id,
static void pass_set_image_textures(struct gl_video *p, struct video_image *vimg,
struct gl_transform *chroma)
{
- GLuint imgtex[4] = {0};
*chroma = (struct gl_transform){{{0}}};
assert(vimg->mpi);
@@ -638,38 +658,23 @@ static void pass_set_image_textures(struct gl_video *p, struct video_image *vimg
// Make sure luma/chroma sizes are aligned.
// Example: For 4:2:0 with size 3x3, the subsampled chroma plane is 2x2
// so luma (3,3) has to align with chroma (2,2).
- chroma->m[0][0] = ls_w * (float)vimg->planes[0].tex_w
- / vimg->planes[1].tex_w;
- chroma->m[1][1] = ls_h * (float)vimg->planes[0].tex_h
- / vimg->planes[1].tex_h;
-
- if (p->hwdec_active) {
- p->hwdec->driver->map_image(p->hwdec, vimg->mpi, imgtex);
- } else {
- for (int n = 0; n < p->plane_count; n++)
- imgtex[n] = vimg->planes[n].gl_texture;
- }
+ chroma->m[0][0] = ls_w * (float)vimg->planes[0].w
+ / vimg->planes[1].w;
+ chroma->m[1][1] = ls_h * (float)vimg->planes[0].h
+ / vimg->planes[1].h;
for (int n = 0; n < p->plane_count; n++) {
struct texplane *t = &vimg->planes[n];
p->pass_tex[n] = (struct src_tex){
- .gl_tex = imgtex[n],
+ .gl_tex = vimg->planes[n].gl_texture,
.gl_target = t->gl_target,
- .tex_w = t->tex_w,
- .tex_h = t->tex_h,
+ .w = t->w,
+ .h = t->h,
.src = {0, 0, t->w, t->h},
};
}
}
-static int align_pow2(int s)
-{
- int r = 1;
- while (r < s)
- r *= 2;
- return r;
-}
-
static void init_video(struct gl_video *p)
{
GL *gl = p->gl;
@@ -698,33 +703,30 @@ static void init_video(struct gl_video *p)
eq_caps |= MP_CSP_EQ_CAPS_BRIGHTNESS;
p->video_eq.capabilities = eq_caps;
+ av_lfg_init(&p->lfg, 1);
+
debug_check_gl(p, "before video texture creation");
struct video_image *vimg = &p->image;
+ struct mp_image layout = {0};
+ mp_image_set_params(&layout, &p->image_params);
+
for (int n = 0; n < p->plane_count; n++) {
struct texplane *plane = &vimg->planes[n];
plane->gl_target = p->gl_target;
- plane->w = mp_chroma_div_up(p->image_w, p->image_desc.xs[n]);
- plane->h = mp_chroma_div_up(p->image_h, p->image_desc.ys[n]);
-
- plane->tex_w = plane->w;
- plane->tex_h = plane->h;
+ plane->w = mp_image_plane_w(&layout, n);
+ plane->h = mp_image_plane_h(&layout, n);
if (!p->hwdec_active) {
- if (!p->opts.npot) {
- plane->tex_w = align_pow2(plane->tex_w);
- plane->tex_h = align_pow2(plane->tex_h);
- }
-
gl->ActiveTexture(GL_TEXTURE0 + n);
gl->GenTextures(1, &plane->gl_texture);
gl->BindTexture(p->gl_target, plane->gl_texture);
gl->TexImage2D(p->gl_target, 0, plane->gl_internal_format,
- plane->tex_w, plane->tex_h, 0,
+ plane->w, plane->h, 0,
plane->gl_format, plane->gl_type, NULL);
gl->TexParameteri(p->gl_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@@ -733,8 +735,7 @@ static void init_video(struct gl_video *p)
gl->TexParameteri(p->gl_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
- MP_VERBOSE(p, "Texture for plane %d: %dx%d\n",
- n, plane->tex_w, plane->tex_h);
+ MP_VERBOSE(p, "Texture for plane %d: %dx%d\n", n, plane->w, plane->h);
}
gl->ActiveTexture(GL_TEXTURE0);
@@ -754,12 +755,11 @@ static void uninit_video(struct gl_video *p)
for (int n = 0; n < p->plane_count; n++) {
struct texplane *plane = &vimg->planes[n];
- gl->DeleteTextures(1, &plane->gl_texture);
+ if (!p->hwdec_active)
+ gl->DeleteTextures(1, &plane->gl_texture);
plane->gl_texture = 0;
gl->DeleteBuffers(1, &plane->gl_buffer);
plane->gl_buffer = 0;
- plane->buffer_ptr = NULL;
- plane->buffer_size = 0;
}
mp_image_unrefp(&vimg->mpi);
@@ -787,8 +787,8 @@ static void pass_prepare_src_tex(struct gl_video *p)
gl_sc_uniform_sampler(sc, texture_name, s->gl_target, n);
float f[2] = {1, 1};
if (s->gl_target != GL_TEXTURE_RECTANGLE) {
- f[0] = s->tex_w;
- f[1] = s->tex_h;
+ f[0] = s->w;
+ f[1] = s->h;
}
gl_sc_uniform_vec2(sc, texture_size, f);
@@ -824,8 +824,8 @@ static void render_pass_quad(struct gl_video *p, int vp_w, int vp_h,
if (flags & 4)
MPSWAP(float, ty[0], ty[1]);
bool rect = s->gl_target == GL_TEXTURE_RECTANGLE;
- v->texcoord[i].x = tx[n / 2] / (rect ? 1 : s->tex_w);
- v->texcoord[i].y = ty[n % 2] / (rect ? 1 : s->tex_h);
+ v->texcoord[i].x = tx[n / 2] / (rect ? 1 : s->w);
+ v->texcoord[i].y = ty[n % 2] / (rect ? 1 : s->h);
}
}
}
@@ -871,7 +871,7 @@ static void finish_pass_fbo(struct gl_video *p, struct fbotex *dst_fbo,
{
fbotex_change(dst_fbo, p->gl, p->log, w, h, p->opts.fbo_format, flags);
- finish_pass_direct(p, dst_fbo->fbo, dst_fbo->tex_w, dst_fbo->tex_h,
+ finish_pass_direct(p, dst_fbo->fbo, dst_fbo->w, dst_fbo->h,
&(struct mp_rect){0, 0, w, h}, 0);
pass_load_fbotex(p, dst_fbo, tex, w, h);
}
@@ -886,6 +886,38 @@ static void uninit_scaler(struct gl_video *p, struct scaler *scaler)
scaler->initialized = false;
}
+static void load_shader(struct gl_video *p, const char *body)
+{
+ gl_sc_hadd(p->sc, body);
+ gl_sc_uniform_f(p->sc, "random", (double)av_lfg_get(&p->lfg) / UINT32_MAX);
+ gl_sc_uniform_f(p->sc, "frame", p->frames_uploaded);
+ gl_sc_uniform_vec2(p->sc, "image_size", (GLfloat[]){p->image_w, p->image_h});
+}
+
+// Applies an arbitrary number of shaders in sequence, using the given pair
+// of FBOs as intermediate buffers. Returns whether any shaders were applied.
+static bool apply_shaders(struct gl_video *p, char **shaders,
+ struct fbotex textures[2], int tex_num, int w, int h)
+{
+ if (!shaders)
+ return false;
+ bool success = false;
+ int tex = 0;
+ for (int n = 0; shaders[n]; n++) {
+ const char *body = gl_sc_loadfile(p->sc, shaders[n]);
+ if (!body)
+ continue;
+ finish_pass_fbo(p, &textures[tex], w, h, tex_num, 0);
+ load_shader(p, body);
+ GLSLF("// custom shader\n");
+ GLSLF("vec4 color = sample(texture%d, texcoord%d, texture_size%d);\n",
+ tex_num, tex_num, tex_num);
+ tex = (tex+1) % 2;
+ success = true;
+ }
+ return success;
+}
+
// Semantic equality
static bool double_seq(double a, double b)
{
@@ -909,7 +941,8 @@ static bool scaler_conf_eq(struct scaler_config a, struct scaler_config b)
// generation
return scaler_fun_eq(a.kernel, b.kernel) &&
scaler_fun_eq(a.window, b.window) &&
- a.radius == b.radius;
+ a.radius == b.radius &&
+ a.clamp == b.clamp;
}
static void reinit_scaler(struct gl_video *p, struct scaler *scaler,
@@ -960,6 +993,8 @@ static void reinit_scaler(struct gl_video *p, struct scaler *scaler,
if (scaler->kernel->f.resizable && conf->radius > 0.0)
scaler->kernel->f.radius = conf->radius;
+ scaler->kernel->clamp = conf->clamp;
+
scaler->insufficient = !mp_init_filter(scaler->kernel, sizes, scale_factor);
if (scaler->kernel->polar) {
@@ -1263,7 +1298,7 @@ static void pass_sample_oversample(struct gl_video *p, struct scaler *scaler,
GLSLF("coeff = mix(coeff, vec2(0.0), "
"lessThanEqual(coeff, vec2(%f)));\n", threshold);
GLSLF("coeff = mix(coeff, vec2(1.0), "
- "greaterThanEqual(coeff, vec2(%f)));\n", threshold);
+ "greaterThanEqual(coeff, vec2(%f)));\n", 1.0 - threshold);
}
// Compute the right blend of colors
GLSL(vec4 left = mix(texture(tex, baseSW),
@@ -1308,6 +1343,15 @@ static void pass_sample(struct gl_video *p, int src_tex, struct scaler *scaler,
pass_sample_sharpen5(p, scaler);
} else if (strcmp(name, "oversample") == 0) {
pass_sample_oversample(p, scaler, w, h);
+ } else if (strcmp(name, "custom") == 0) {
+ const char *body = gl_sc_loadfile(p->sc, p->opts.scale_shader);
+ if (body) {
+ load_shader(p, body);
+ GLSLF("// custom scale-shader\n");
+ GLSL(vec4 color = sample(tex, pos, size);)
+ } else {
+ p->opts.scale_shader = NULL;
+ }
} else if (scaler->kernel && scaler->kernel->polar) {
pass_sample_polar(p, scaler);
} else if (scaler->kernel) {
@@ -1328,53 +1372,123 @@ static void pass_read_video(struct gl_video *p)
struct gl_transform chromafix;
pass_set_image_textures(p, &p->image, &chromafix);
+ // The custom shader logic is a bit tricky, but there are basically three
+ // different places it can occur: RGB, or chroma *and* luma (which are
+ // treated separately even for 4:4:4 content, but the minor speed loss
+ // is not worth the complexity it would require).
+ const char *shader = gl_sc_loadfile(p->sc, p->opts.source_shader);
+
+ // Since this is before normalization, we have to take into account
+ // the bit depth. Specifically, we want the shader to perform normalization
+ // to 16 bit because otherwise it results in bad quantization, especially
+ // with 8-bit FBOs (where it just destroys the image completely)
+ int in_bits = p->image_desc.component_bits,
+ tx_bits = (in_bits + 7) & ~7;
+ float cmul = ((1 << tx_bits) - 1.0) / ((1 << in_bits) - 1.0);
+ // Custom source shaders are required to output at range [0.0, 1.0]
+ p->use_normalized_range = shader != NULL;
+
+ if (p->image_desc.flags & MP_IMGFLAG_XYZ) {
+ cmul = 1.0;
+ p->use_normalized_range = true;
+ }
+
+ // Special case for non-planar content
if (p->plane_count == 1) {
- GLSL(vec4 color = texture(texture0, texcoord0);)
+ if (shader) {
+ load_shader(p, shader);
+ GLSLF("// custom source-shader (RGB)\n");
+ gl_sc_uniform_f(p->sc, "cmul", cmul);
+ GLSL(vec4 color = sample(texture0, texcoord0, texture_size0);)
+ p->use_indirect = true;
+ } else {
+ GLSL(vec4 color = texture(texture0, texcoord0);)
+ }
return;
}
+ // Chroma preprocessing (merging -> shaders -> scaling)
+ struct src_tex luma = p->pass_tex[0];
+ struct src_tex alpha = p->pass_tex[3];
+ int c_w = p->pass_tex[1].src.x1 - p->pass_tex[1].src.x0;
+ int c_h = p->pass_tex[1].src.y1 - p->pass_tex[1].src.y0;
const struct scaler_config *cscale = &p->opts.scaler[2];
- if (p->image_desc.flags & MP_IMGFLAG_SUBSAMPLED &&
- strcmp(cscale->kernel.name, "bilinear") != 0) {
- struct src_tex luma = p->pass_tex[0];
- if (p->plane_count > 2) {
- // For simplicity and performance, we merge the chroma planes
- // into a single texture before scaling, so the scaler doesn't
- // need to run multiple times.
- GLSLF("// chroma merging\n");
- GLSL(vec4 color = vec4(texture(texture1, texcoord1).r,
- texture(texture2, texcoord2).r,
- 0.0, 1.0);)
- int c_w = p->pass_tex[1].src.x1 - p->pass_tex[1].src.x0;
- int c_h = p->pass_tex[1].src.y1 - p->pass_tex[1].src.y0;
- assert(c_w == p->pass_tex[2].src.x1 - p->pass_tex[2].src.x0);
- assert(c_h == p->pass_tex[2].src.y1 - p->pass_tex[2].src.y0);
- finish_pass_fbo(p, &p->chroma_merge_fbo, c_w, c_h, 1, 0);
- }
+ // Non-trivial sampling is needed on the chroma plane
+ bool nontrivial = p->image_desc.flags & MP_IMGFLAG_SUBSAMPLED &&
+ strcmp(cscale->kernel.name, "bilinear") != 0;
+
+ bool merged = false;
+ if (p->plane_count > 2 && (nontrivial || shader)) {
+ // For simplicity and performance, we merge the chroma planes
+ // into a single texture before scaling or shading, so the shader
+ // doesn't need to run multiple times.
+ GLSLF("// chroma merging\n");
+ GLSL(vec4 color = vec4(texture(texture1, texcoord1).r,
+ texture(texture2, texcoord2).r,
+ 0.0, 1.0);)
+ // We also pull up here in this case to avoid the issues described
+ // above.
+ GLSLF("color.rg *= %f;\n", cmul);
+ p->use_normalized_range = true;
+ merged = true;
+ assert(c_w == p->pass_tex[2].src.x1 - p->pass_tex[2].src.x0);
+ assert(c_h == p->pass_tex[2].src.y1 - p->pass_tex[2].src.y0);
+ finish_pass_fbo(p, &p->chroma_merge_fbo, c_w, c_h, 1, 0);
+ }
+
+ if (shader) {
+ // Chroma plane shader logic
+ load_shader(p, shader);
+ gl_sc_uniform_f(p->sc, "cmul", merged ? 1.0 : cmul);
+ GLSLF("// custom source-shader (chroma)\n");
+ GLSL(vec4 color = sample(texture1, texcoord1, texture_size1);)
+ GLSL(color.ba = vec2(0.0, 1.0);) // skip unused
+ finish_pass_fbo(p, &p->source_fbo, c_w, c_h, 1, 0);
+ p->use_indirect = true;
+ }
+
+ if (p->image_desc.flags & MP_IMGFLAG_SUBSAMPLED && nontrivial) {
GLSLF("// chroma scaling\n");
pass_sample(p, 1, &p->scaler[2], cscale, 1.0, p->image_w, p->image_h,
chromafix);
GLSL(vec2 chroma = color.rg;)
- // Always force rendering to a FBO before main scaling, or we would
- // scale chroma incorrectly.
p->use_indirect = true;
- p->pass_tex[0] = luma; // Restore luma after scaling
} else {
+ // No explicit scaling needed, either because it's trivial (ie.
+ // bilinear), or because there's no subsampling. We have to manually
+ // apply the fix to the chroma coordinates because it's not implied by
+ // pass_sample.
GLSL(vec4 color;)
- if (p->plane_count == 2) {
- gl_transform_rect(chromafix, &p->pass_tex[1].src);
- GLSL(vec2 chroma = texture(texture1, texcoord1).rg;) // NV formats
- } else {
- gl_transform_rect(chromafix, &p->pass_tex[1].src);
+ gl_transform_rect(chromafix, &p->pass_tex[1].src);
+ if (p->plane_count > 2 && !merged) {
gl_transform_rect(chromafix, &p->pass_tex[2].src);
GLSL(vec2 chroma = vec2(texture(texture1, texcoord1).r,
texture(texture2, texcoord2).r);)
+ } else {
+ GLSL(vec2 chroma = texture(texture1, texcoord1).rg;)
}
}
- GLSL(color = vec4(texture(texture0, texcoord0).r, chroma, 1.0);)
- if (p->has_alpha && p->plane_count >= 4)
+ p->pass_tex[0] = luma; // Restore the luma plane
+ p->pass_tex[3] = alpha; // Restore the alpha plane (if set)
+ if (shader) {
+ load_shader(p, shader);
+ gl_sc_uniform_f(p->sc, "cmul", cmul);
+ GLSLF("// custom source-shader (luma)\n");
+ GLSL(float luma = sample(texture0, texcoord0, texture_size0).r;)
+ p->use_indirect = true;
+ } else {
+ GLSL(float luma = texture(texture0, texcoord0).r;)
+ if (p->use_normalized_range)
+ GLSLF("luma *= %f;\n", cmul);
+ }
+
+ GLSL(color = vec4(luma, chroma, 1.0);)
+ if (p->has_alpha && p->plane_count >= 4) {
GLSL(color.a = texture(texture3, texcoord3).r;)
+ if (p->use_normalized_range)
+ GLSLF("color.a *= %f;\n", cmul);
+ }
}
// yuv conversion, and any other conversions before main up/down-scaling
@@ -1398,14 +1512,16 @@ static void pass_convert_yuv(struct gl_video *p)
// Pre-colormatrix input gamma correction
if (p->image_desc.flags & MP_IMGFLAG_XYZ) {
cparams.colorspace = MP_CSP_XYZ;
- cparams.input_bits = 8;
- cparams.texture_bits = 8;
// Pre-colormatrix input gamma correction. Note that this results in
// linear light
GLSL(color.rgb = pow(color.rgb, vec3(2.6));)
}
+ // Something already took care of expansion
+ if (p->use_normalized_range)
+ cparams.input_bits = cparams.texture_bits;
+
// Conversion from Y'CbCr or other linear spaces to RGB
if (!p->is_rgb) {
struct mp_cmat m = {{{0}}};
@@ -1765,7 +1881,7 @@ static void pass_dither(struct gl_video *p)
GLSLF("vec2 dither_pos = gl_FragCoord.xy / %d;\n", p->dither_size);
if (p->opts.temporal_dither) {
- int phase = p->frames_rendered % 8u;
+ int phase = (p->frames_rendered / p->opts.temporal_dither_period) % 8u;
float r = phase * (M_PI / 2); // rotate
float m = phase < 4 ? 1 : -1; // mirror
@@ -1822,13 +1938,10 @@ static void pass_draw_osd(struct gl_video *p, int draw_flags, double pts,
}
// The main rendering function, takes care of everything up to and including
-// upscaling
+// upscaling. p->image is rendered.
static void pass_render_frame(struct gl_video *p)
{
- bool use_cms = p->use_lut_3d || p->opts.target_prim != MP_CSP_PRIM_AUTO
- || p->opts.target_trc != MP_CSP_TRC_AUTO;
- p->use_linear = p->opts.linear_scaling || p->opts.sigmoid_upscaling
- || use_cms || p->image_params.gamma == MP_CSP_TRC_LINEAR;
+ p->use_linear = p->opts.linear_scaling || p->opts.sigmoid_upscaling;
p->use_indirect = false; // set to true as needed by pass_*
pass_read_video(p);
pass_convert_yuv(p);
@@ -1851,12 +1964,18 @@ static void pass_render_frame(struct gl_video *p)
GLSL(vec4 color = texture(texture0, texcoord0);)
}
+ if (apply_shaders(p, p->opts.pre_shaders, &p->pre_fbo[0], 0,
+ p->image_w, p->image_h))
+ {
+ p->use_indirect = true;
+ }
+
pass_scale_main(p);
+ int vp_w = p->dst_rect.x1 - p->dst_rect.x0,
+ vp_h = p->dst_rect.y1 - p->dst_rect.y0;
if (p->osd && p->opts.blend_subs == 1) {
// Recreate the real video size from the src/dst rects
- int vp_w = p->dst_rect.x1 - p->dst_rect.x0,
- vp_h = p->dst_rect.y1 - p->dst_rect.y0;
struct mp_osd_res rect = {
.w = vp_w, .h = vp_h,
.ml = -p->src_rect.x0, .mr = p->src_rect.x1 - p->image_w,
@@ -1878,6 +1997,8 @@ static void pass_render_frame(struct gl_video *p)
if (p->use_linear)
pass_linearize(p, p->image_params.gamma);
}
+
+ apply_shaders(p, p->opts.post_shaders, &p->post_fbo[0], 0, vp_w, vp_h);
}
static void pass_draw_to_screen(struct gl_video *p, int fbo)
@@ -1897,23 +2018,41 @@ static void pass_draw_to_screen(struct gl_video *p, int fbo)
}
// Draws an interpolate frame to fbo, based on the frame timing in t
-static void gl_video_interpolate_frame(struct gl_video *p, int fbo,
- struct frame_timing *t)
+static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
+ int fbo)
{
int vp_w = p->dst_rect.x1 - p->dst_rect.x0,
vp_h = p->dst_rect.y1 - p->dst_rect.y0;
+ // Reset the queue completely if this is a still image, to avoid any
+ // interpolation artifacts from surrounding frames when unpausing or
+ // framestepping
+ if (t->still)
+ gl_video_reset_surfaces(p);
+
// First of all, figure out if we have a frame availble at all, and draw
// it manually + reset the queue if not
- if (!p->surfaces[p->surface_now].pts) {
+ if (p->surfaces[p->surface_now].pts == MP_NOPTS_VALUE) {
+ gl_video_upload_image(p, t->current);
pass_render_frame(p);
finish_pass_fbo(p, &p->surfaces[p->surface_now].fbotex,
vp_w, vp_h, 0, FBOTEX_FUZZY);
- p->surfaces[p->surface_now].pts = t ? t->pts : 0;
- p->surfaces[p->surface_now].vpts = p->image.mpi->pts;
+ p->surfaces[p->surface_now].pts = p->image.mpi->pts;
p->surface_idx = p->surface_now;
}
+ // Find the right frame for this instant
+ if (t->current&& t->current->pts != MP_NOPTS_VALUE) {
+ int next = fbosurface_wrap(p->surface_now + 1);
+ while (p->surfaces[next].pts != MP_NOPTS_VALUE &&
+ p->surfaces[next].pts > p->surfaces[p->surface_now].pts &&
+ p->surfaces[p->surface_now].pts < t->current->pts)
+ {
+ p->surface_now = next;
+ next = fbosurface_wrap(next + 1);
+ }
+ }
+
// Figure out the queue size. For illustration, a filter radius of 2 would
// look like this: _ A [B] C D _
// A is surface_bse, B is surface_now, C is surface_nxt and D is
@@ -1922,6 +2061,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo,
reinit_scaler(p, tscale, &p->opts.scaler[3], 1, tscale_sizes);
bool oversample = strcmp(tscale->conf.kernel.name, "oversample") == 0;
int size;
+
if (oversample) {
size = 2;
} else {
@@ -1929,25 +2069,39 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo,
size = ceil(tscale->kernel->size);
assert(size <= TEXUNIT_VIDEO_NUM);
}
- int radius = size/2;
+ int radius = size/2;
int surface_now = p->surface_now;
int surface_nxt = fbosurface_wrap(surface_now + 1);
int surface_bse = fbosurface_wrap(surface_now - (radius-1));
int surface_end = fbosurface_wrap(surface_now + radius);
assert(fbosurface_wrap(surface_bse + size-1) == surface_end);
- // Render a new frame if it came in and there's room in the queue
+ // Render new frames while there's room in the queue. Note that technically,
+ // this should be done before the step where we find the right frame, but
+ // it only barely matters at the very beginning of playback, and this way
+ // makes the code much more linear.
int surface_dst = fbosurface_wrap(p->surface_idx+1);
- if (t && surface_dst != surface_bse &&
- p->surfaces[p->surface_idx].pts < t->pts) {
- MP_STATS(p, "new-pts");
- pass_render_frame(p);
- finish_pass_fbo(p, &p->surfaces[surface_dst].fbotex,
- vp_w, vp_h, 0, FBOTEX_FUZZY);
- p->surfaces[surface_dst].pts = t->pts;
- p->surfaces[surface_dst].vpts = p->image.mpi->pts;
- p->surface_idx = surface_dst;
+ for (int i = 0; i < t->num_frames; i++) {
+ // Avoid overwriting data we might still need
+ if (surface_dst == surface_bse - 1)
+ break;
+
+ struct mp_image *f = t->frames[i];
+ if (!mp_image_params_equal(&f->params, &p->real_image_params) ||
+ f->pts == MP_NOPTS_VALUE)
+ continue;
+
+ if (f->pts > p->surfaces[p->surface_idx].pts) {
+ MP_STATS(p, "new-pts");
+ gl_video_upload_image(p, f);
+ pass_render_frame(p);
+ finish_pass_fbo(p, &p->surfaces[surface_dst].fbotex,
+ vp_w, vp_h, 0, FBOTEX_FUZZY);
+ p->surfaces[surface_dst].pts = f->pts;
+ p->surface_idx = surface_dst;
+ surface_dst = fbosurface_wrap(surface_dst+1);
+ }
}
// Figure out whether the queue is "valid". A queue is invalid if the
@@ -1958,7 +2112,9 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo,
bool valid = true;
for (int i = surface_bse, ii; valid && i != surface_end; i = ii) {
ii = fbosurface_wrap(i+1);
- if (!p->surfaces[i].pts || !p->surfaces[ii].pts) {
+ if (p->surfaces[i].pts == MP_NOPTS_VALUE ||
+ p->surfaces[ii].pts == MP_NOPTS_VALUE)
+ {
valid = false;
} else if (p->surfaces[ii].pts < p->surfaces[i].pts) {
valid = false;
@@ -1967,36 +2123,42 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo,
}
// Update OSD PTS to synchronize subtitles with the displayed frame
- if (t) {
- double vpts_now = p->surfaces[surface_now].vpts,
- vpts_nxt = p->surfaces[surface_nxt].vpts,
- vpts_new = p->image.mpi->pts;
- if (vpts_now != MP_NOPTS_VALUE &&
- vpts_nxt != MP_NOPTS_VALUE &&
- vpts_new != MP_NOPTS_VALUE)
- {
- // Round to nearest neighbour
- double vpts_vsync = (t->next_vsync - t->pts)/1e6 + vpts_new;
- p->osd_pts = fabs(vpts_vsync-vpts_now) < fabs(vpts_vsync-vpts_nxt)
- ? vpts_now : vpts_nxt;
- }
- }
+ p->osd_pts = p->surfaces[surface_now].pts;
// Finally, draw the right mix of frames to the screen.
- if (!t || !valid) {
+ if (!valid || t->still) {
// surface_now is guaranteed to be valid, so we can safely use it.
pass_load_fbotex(p, &p->surfaces[surface_now].fbotex, 0, vp_w, vp_h);
GLSL(vec4 color = texture(texture0, texcoord0);)
p->is_interpolated = false;
} else {
- int64_t pts_now = p->surfaces[surface_now].pts,
- pts_nxt = p->surfaces[surface_nxt].pts;
- double fscale = pts_nxt - pts_now, mix;
+ double pts_now = p->surfaces[surface_now].pts,
+ pts_nxt = p->surfaces[surface_nxt].pts;
+
+ double mix = (t->vsync_offset / 1e6) / (pts_nxt - pts_now);
+ // The scaler code always wants the fcoord to be between 0 and 1,
+ // so we try to adjust by using the previous set of N frames instead
+ // (which requires some extra checking to make sure it's valid)
+ if (mix < 0.0) {
+ int prev = fbosurface_wrap(surface_bse - 1);
+ if (p->surfaces[prev].pts != MP_NOPTS_VALUE &&
+ p->surfaces[prev].pts < p->surfaces[surface_bse].pts)
+ {
+ mix += 1.0;
+ surface_bse = prev;
+ } else {
+ mix = 0.0; // at least don't blow up, this should only
+ // ever happen at the start of playback
+ }
+ }
+
+ // Blend the frames together
if (oversample) {
- double vsync_interval = t->next_vsync - t->prev_vsync,
+ double vsync_dist = (t->next_vsync - t->prev_vsync)/1e6
+ / (pts_nxt - pts_now),
threshold = tscale->conf.kernel.params[0];
threshold = isnan(threshold) ? 0.0 : threshold;
- mix = (pts_nxt - t->next_vsync) / vsync_interval;
+ mix = (1 - mix) / vsync_dist;
mix = mix <= 0 + threshold ? 0 : mix;
mix = mix >= 1 - threshold ? 1 : mix;
mix = 1 - mix;
@@ -2005,43 +2167,37 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo,
texture(texture1, texcoord1),
inter_coeff);)
} else {
- mix = (t->next_vsync - pts_now) / fscale;
gl_sc_uniform_f(p->sc, "fcoord", mix);
pass_sample_separated_gen(p, tscale, 0, 0);
}
+
+ // Load all the required frames
for (int i = 0; i < size; i++) {
pass_load_fbotex(p, &p->surfaces[fbosurface_wrap(surface_bse+i)].fbotex,
i, vp_w, vp_h);
}
+
MP_STATS(p, "frame-mix");
- MP_DBG(p, "inter frame ppts: %lld, pts: %lld, vsync: %lld, mix: %f\n",
- (long long)pts_now, (long long)pts_nxt,
- (long long)t->next_vsync, mix);
+ MP_DBG(p, "inter frame pts: %lld, vsync: %lld, mix: %f\n",
+ (long long)t->pts, (long long)t->next_vsync, mix);
p->is_interpolated = true;
}
pass_draw_to_screen(p, fbo);
- // Dequeue frames if necessary
- if (t) {
- int64_t vsync_interval = t->next_vsync - t->prev_vsync;
- int64_t vsync_guess = t->next_vsync + vsync_interval;
- if (p->surfaces[surface_nxt].pts > p->surfaces[p->surface_now].pts &&
- p->surfaces[surface_nxt].pts < vsync_guess)
- {
- p->surface_now = surface_nxt;
- }
- }
+ p->frames_drawn += 1;
}
// (fbo==0 makes BindFramebuffer select the screen backbuffer)
-void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t)
+void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, int fbo)
{
GL *gl = p->gl;
struct video_image *vimg = &p->image;
gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
- if (!vimg->mpi || p->dst_rect.x0 > 0 || p->dst_rect.y0 > 0 ||
+ bool has_frame = frame->current || vimg->mpi;
+
+ if (!has_frame || p->dst_rect.x0 > 0 || p->dst_rect.y0 > 0 ||
p->dst_rect.x1 < p->vp_w || p->dst_rect.y1 < abs(p->vp_h))
{
struct m_color c = p->opts.background;
@@ -2049,22 +2205,22 @@ void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t)
gl->Clear(GL_COLOR_BUFFER_BIT);
}
- if (vimg->mpi) {
- gl_video_upload_image(p);
-
+ if (has_frame) {
gl_sc_set_vao(p->sc, &p->vao);
- if (p->opts.interpolation) {
- gl_video_interpolate_frame(p, fbo, t);
+ if (p->opts.interpolation && (p->frames_drawn || !frame->still)) {
+ gl_video_interpolate_frame(p, frame, fbo);
} else {
// Skip interpolation if there's nothing to be done
+ if (!frame->redraw || !vimg->mpi)
+ gl_video_upload_image(p, frame->current);
pass_render_frame(p);
pass_draw_to_screen(p, fbo);
}
-
- debug_check_gl(p, "after video rendering");
}
+ debug_check_gl(p, "after video rendering");
+
gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
if (p->osd) {
@@ -2094,7 +2250,22 @@ void gl_video_resize(struct gl_video *p, int vp_w, int vp_h,
gl_video_reset_surfaces(p);
}
-static bool get_image(struct gl_video *p, struct mp_image *mpi)
+static bool unmap_image(struct gl_video *p, struct mp_image *mpi)
+{
+ GL *gl = p->gl;
+ bool ok = true;
+ struct video_image *vimg = &p->image;
+ for (int n = 0; n < p->plane_count; n++) {
+ struct texplane *plane = &vimg->planes[n];
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer);
+ ok = gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER) && ok;
+ mpi->planes[n] = NULL; // PBO offset 0
+ }
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ return ok;
+}
+
+static bool map_image(struct gl_video *p, struct mp_image *mpi)
{
GL *gl = p->gl;
@@ -2103,84 +2274,74 @@ static bool get_image(struct gl_video *p, struct mp_image *mpi)
struct video_image *vimg = &p->image;
- // See comments in init_video() about odd video sizes.
- // The normal upload path does this too, but less explicit.
- mp_image_set_size(mpi, vimg->planes[0].w, vimg->planes[0].h);
-
for (int n = 0; n < p->plane_count; n++) {
struct texplane *plane = &vimg->planes[n];
mpi->stride[n] = mp_image_plane_w(mpi, n) * p->image_desc.bytes[n];
- int needed_size = mp_image_plane_h(mpi, n) * mpi->stride[n];
- if (!plane->gl_buffer)
+ if (!plane->gl_buffer) {
gl->GenBuffers(1, &plane->gl_buffer);
- gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer);
- if (needed_size > plane->buffer_size) {
- plane->buffer_size = needed_size;
- gl->BufferData(GL_PIXEL_UNPACK_BUFFER, plane->buffer_size,
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer);
+ size_t buffer_size = mp_image_plane_h(mpi, n) * mpi->stride[n];
+ gl->BufferData(GL_PIXEL_UNPACK_BUFFER, buffer_size,
NULL, GL_DYNAMIC_DRAW);
}
- if (!plane->buffer_ptr)
- plane->buffer_ptr = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER,
- GL_WRITE_ONLY);
- mpi->planes[n] = plane->buffer_ptr;
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer);
+ mpi->planes[n] = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ if (!mpi->planes[n]) {
+ unmap_image(p, mpi);
+ return false;
+ }
}
+ memset(mpi->bufs, 0, sizeof(mpi->bufs));
return true;
}
-void gl_video_set_image(struct gl_video *p, struct mp_image *mpi)
+static void gl_video_upload_image(struct gl_video *p, struct mp_image *mpi)
{
- assert(mpi);
-
+ GL *gl = p->gl;
struct video_image *vimg = &p->image;
+
+ mpi = mp_image_new_ref(mpi);
+ if (!mpi)
+ abort();
+
talloc_free(vimg->mpi);
vimg->mpi = mpi;
- vimg->needs_upload = true;
-
p->osd_pts = mpi->pts;
-}
+ p->frames_uploaded++;
-static void gl_video_upload_image(struct gl_video *p)
-{
- GL *gl = p->gl;
-
- struct video_image *vimg = &p->image;
- struct mp_image *mpi = vimg->mpi;
-
- if (p->hwdec_active || !mpi || !vimg->needs_upload)
+ if (p->hwdec_active) {
+ GLuint imgtex[4] = {0};
+ bool ok = p->hwdec->driver->map_image(p->hwdec, vimg->mpi, imgtex) >= 0;
+ for (int n = 0; n < p->plane_count; n++)
+ vimg->planes[n].gl_texture = ok ? imgtex[n] : -1;
return;
-
- vimg->needs_upload = false;
+ }
assert(mpi->num_planes == p->plane_count);
- mp_image_t mpi2 = *mpi;
- bool pbo = false;
- if (!vimg->planes[0].buffer_ptr && get_image(p, &mpi2)) {
- for (int n = 0; n < p->plane_count; n++) {
- int line_bytes = mp_image_plane_w(mpi, n) * p->image_desc.bytes[n];
- int plane_h = mp_image_plane_h(mpi, n);
- memcpy_pic(mpi2.planes[n], mpi->planes[n], line_bytes, plane_h,
- mpi2.stride[n], mpi->stride[n]);
+ mp_image_t pbo_mpi = *mpi;
+ bool pbo = map_image(p, &pbo_mpi);
+ if (pbo) {
+ mp_image_copy(&pbo_mpi, mpi);
+ if (unmap_image(p, &pbo_mpi)) {
+ mpi = &pbo_mpi;
+ } else {
+ MP_FATAL(p, "Video PBO upload failed. Disabling PBOs.\n");
+ pbo = false;
+ p->opts.pbo = 0;
}
- pbo = true;
}
- vimg->image_flipped = mpi2.stride[0] < 0;
+
+ vimg->image_flipped = mpi->stride[0] < 0;
for (int n = 0; n < p->plane_count; n++) {
struct texplane *plane = &vimg->planes[n];
- void *plane_ptr = mpi2.planes[n];
- if (pbo) {
+ if (pbo)
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer);
- if (!gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER))
- MP_FATAL(p, "Video PBO upload failed. "
- "Remove the 'pbo' suboption.\n");
- plane->buffer_ptr = NULL;
- plane_ptr = NULL; // PBO offset 0
- }
gl->ActiveTexture(GL_TEXTURE0 + n);
gl->BindTexture(p->gl_target, plane->gl_texture);
glUploadTex(gl, p->gl_target, plane->gl_format, plane->gl_type,
- plane_ptr, mpi2.stride[n], 0, 0, plane->w, plane->h, 0);
+ mpi->planes[n], mpi->stride[n], 0, 0, plane->w, plane->h, 0);
}
gl->ActiveTexture(GL_TEXTURE0);
if (pbo)
@@ -2227,14 +2388,14 @@ static void check_gl_features(struct gl_video *p)
if (kernel) {
char *reason = NULL;
if (!test_fbo(p, &have_fbo))
- reason = "scaler (FBOs missing)";
+ reason = "(FBOs missing)";
if (!have_float_tex)
- reason = "scaler (float tex. missing)";
+ reason = "(float tex. missing)";
if (!have_1d_tex && kernel->polar)
- reason = "scaler (1D tex. missing)";
+ reason = "(1D tex. missing)";
if (reason) {
p->opts.scaler[n].kernel.name = "bilinear";
- MP_WARN(p, "Disabling %s.\n", reason);
+ MP_WARN(p, "Disabling scaler #%d %s.\n", n, reason);
}
}
}
@@ -2555,7 +2716,7 @@ void gl_video_set_osd_source(struct gl_video *p, struct osd_state *osd)
recreate_osd(p);
}
-struct gl_video *gl_video_init(GL *gl, struct mp_log *log)
+struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct mpv_global *g)
{
if (gl->version < 210 && gl->es < 200) {
mp_err(log, "At least OpenGL 2.1 or OpenGL ES 2.0 required.\n");
@@ -2565,12 +2726,13 @@ struct gl_video *gl_video_init(GL *gl, struct mp_log *log)
struct gl_video *p = talloc_ptrtype(NULL, p);
*p = (struct gl_video) {
.gl = gl,
+ .global = g,
.log = log,
.opts = gl_video_opts_def,
.gl_target = GL_TEXTURE_2D,
.texture_16bit_depth = 16,
.scaler = {{.index = 0}, {.index = 1}, {.index = 2}, {.index = 3}},
- .sc = gl_sc_create(gl, log),
+ .sc = gl_sc_create(gl, log, g),
};
gl_video_set_debug(p, true);
init_gl(p);
@@ -2597,33 +2759,64 @@ static const char *handle_scaler_opt(const char *name, bool tscale)
return NULL;
}
+static char **dup_str_array(void *parent, char **src)
+{
+ if (!src)
+ return NULL;
+
+ char **res = talloc_new(parent);
+ int num = 0;
+ for (int n = 0; src && src[n]; n++)
+ MP_TARRAY_APPEND(res, res, num, talloc_strdup(res, src[n]));
+ MP_TARRAY_APPEND(res, res, num, NULL);
+ return res;
+}
+
// Set the options, and possibly update the filter chain too.
// Note: assumes all options are valid and verified by the option parser.
-void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts,
- int *queue_size)
+void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts)
{
+ talloc_free(p->opts.source_shader);
+ talloc_free(p->opts.scale_shader);
+ talloc_free(p->opts.pre_shaders);
+ talloc_free(p->opts.post_shaders);
+
p->opts = *opts;
+
for (int n = 0; n < 4; n++) {
p->opts.scaler[n].kernel.name =
(char *)handle_scaler_opt(p->opts.scaler[n].kernel.name, n==3);
}
+ p->opts.source_shader = talloc_strdup(p, p->opts.source_shader);
+ p->opts.scale_shader = talloc_strdup(p, p->opts.scale_shader);
+ p->opts.pre_shaders = dup_str_array(p, p->opts.pre_shaders);
+ p->opts.post_shaders = dup_str_array(p, p->opts.post_shaders);
+
+ check_gl_features(p);
+ uninit_rendering(p);
+}
+
+void gl_video_configure_queue(struct gl_video *p, struct vo *vo)
+{
+ int queue_size = 1;
+
// Figure out an adequate size for the interpolation queue. The larger
- // the radius, the earlier we need to queue frames. This rough heuristic
- // seems to work for now, but ideally we want to rework the pause/unpause
- // logic to make larger queue sizes the default.
- if (queue_size && p->opts.interpolation) {
+ // the radius, the earlier we need to queue frames.
+ if (p->opts.interpolation) {
const struct filter_kernel *kernel =
mp_find_filter_kernel(p->opts.scaler[3].kernel.name);
if (kernel) {
double radius = kernel->f.radius;
radius = radius > 0 ? radius : p->opts.scaler[3].radius;
- *queue_size = 50e3 * ceil(radius);
+ queue_size += 1 + ceil(radius);
+ } else {
+ // Oversample case
+ queue_size += 2;
}
}
- check_gl_features(p);
- uninit_rendering(p);
+ vo_set_queue_params(vo, 0, p->opts.interpolation, queue_size);
}
struct mp_csp_equalizer *gl_video_eq_ptr(struct gl_video *p)
@@ -2690,18 +2883,6 @@ static int validate_window_opt(struct mp_log *log, const m_option_t *opt,
return r;
}
-
-// Resize and redraw the contents of the window without further configuration.
-// Intended to be used in situations where the frontend can't really be
-// involved with reconfiguring the VO properly.
-// gl_video_resize() should be called when user interaction is done.
-void gl_video_resize_redraw(struct gl_video *p, int w, int h)
-{
- p->vp_w = w;
- p->vp_h = h;
- gl_video_render_frame(p, 0, NULL);
-}
-
float gl_video_scale_ambient_lux(float lmin, float lmax,
float rmin, float rmax, float lux)
{
diff --git a/video/out/gl_video.h b/video/out/gl_video.h
index 7760876..ef6ed76 100644
--- a/video/out/gl_video.h
+++ b/video/out/gl_video.h
@@ -39,6 +39,7 @@ struct scaler_config {
struct scaler_fun window;
float radius;
float antiring;
+ int clamp;
};
struct gl_video_opts {
@@ -53,12 +54,12 @@ struct gl_video_opts {
float sigmoid_center;
float sigmoid_slope;
int scaler_resizes_only;
- int npot;
int pbo;
int dither_depth;
int dither_algo;
int dither_size;
int temporal_dither;
+ int temporal_dither_period;
int fbo_format;
int alpha_mode;
int chroma_location;
@@ -66,6 +67,10 @@ struct gl_video_opts {
struct m_color background;
int interpolation;
int blend_subs;
+ char *source_shader;
+ char *scale_shader;
+ char **pre_shaders;
+ char **post_shaders;
};
extern const struct m_sub_options gl_video_conf;
@@ -73,18 +78,17 @@ extern const struct gl_video_opts gl_video_opts_hq_def;
extern const struct gl_video_opts gl_video_opts_def;
struct gl_video;
+struct vo_frame;
-struct gl_video *gl_video_init(GL *gl, struct mp_log *log);
+struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct mpv_global *g);
void gl_video_uninit(struct gl_video *p);
void gl_video_set_osd_source(struct gl_video *p, struct osd_state *osd);
-void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts,
- int *queue_size);
+void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts);
bool gl_video_check_format(struct gl_video *p, int mp_format);
void gl_video_config(struct gl_video *p, struct mp_image_params *params);
void gl_video_set_output_depth(struct gl_video *p, int r, int g, int b);
void gl_video_set_lut3d(struct gl_video *p, struct lut3d *lut3d);
-void gl_video_set_image(struct gl_video *p, struct mp_image *img);
-void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t);
+void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, int fbo);
void gl_video_resize(struct gl_video *p, int vp_w, int vp_h,
struct mp_rect *src, struct mp_rect *dst,
struct mp_osd_res *osd);
@@ -93,7 +97,6 @@ struct mp_csp_equalizer *gl_video_eq_ptr(struct gl_video *p);
void gl_video_eq_update(struct gl_video *p);
void gl_video_set_debug(struct gl_video *p, bool enable);
-void gl_video_resize_redraw(struct gl_video *p, int w, int h);
float gl_video_scale_ambient_lux(float lmin, float lmax,
float rmin, float rmax, float lux);
@@ -107,4 +110,7 @@ bool gl_video_showing_interpolated_frame(struct gl_video *p);
struct gl_hwdec;
void gl_video_set_hwdec(struct gl_video *p, struct gl_hwdec *hwdec);
+struct vo;
+void gl_video_configure_queue(struct gl_video *p, struct vo *vo);
+
#endif
diff --git a/video/out/gl_w32.c b/video/out/gl_w32.c
index ccb7679..87f98f1 100644
--- a/video/out/gl_w32.c
+++ b/video/out/gl_w32.c
@@ -150,10 +150,9 @@ static bool create_context_w32_gl3(struct MPGLContext *ctx)
if (!wglCreateContextAttribsARB)
goto unsupported;
- int gl_version = ctx->requested_gl_version;
int attribs[] = {
- WGL_CONTEXT_MAJOR_VERSION_ARB, MPGL_VER_GET_MAJOR(gl_version),
- WGL_CONTEXT_MINOR_VERSION_ARB, MPGL_VER_GET_MINOR(gl_version),
+ WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
+ WGL_CONTEXT_MINOR_VERSION_ARB, 0,
WGL_CONTEXT_FLAGS_ARB, 0,
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
0
diff --git a/video/out/gl_wayland.c b/video/out/gl_wayland.c
index 3307087..a6a23bd 100644
--- a/video/out/gl_wayland.c
+++ b/video/out/gl_wayland.c
@@ -89,8 +89,7 @@ static bool egl_create_context(struct vo_wayland_state *wl,
MP_VERBOSE(wl, "EGL version %d.%d\n", major, minor);
EGLint context_attribs[] = {
- EGL_CONTEXT_MAJOR_VERSION_KHR,
- MPGL_VER_GET_MAJOR(ctx->requested_gl_version),
+ EGL_CONTEXT_MAJOR_VERSION_KHR, 3,
EGL_NONE
};
@@ -145,6 +144,20 @@ static void egl_create_window(struct vo_wayland_state *wl)
wl->egl_context.egl.ctx);
wl_display_dispatch_pending(wl->display.display);
+
+ /**
+ * <http://lists.freedesktop.org/archives/wayland-devel/2013-November/012019.html>
+ *
+ * The main change is that if the swap interval is 0 then Mesa won't install a
+ * frame callback so that eglSwapBuffers can be executed as often as necessary.
+ * Instead it will do a sync request after the swap buffers. It will block for
+ * sync complete event in get_back_bo instead of the frame callback. The
+ * compositor is likely to send a release event while processing the new buffer
+ * attach and this makes sure we will receive that before deciding whether to
+ * allocate a new buffer.
+ */
+
+ eglSwapInterval(wl->egl_context.egl.dpy, 0);
}
static bool config_window_wayland(struct MPGLContext *ctx, int flags)
@@ -197,11 +210,13 @@ static void swapGlBuffers_wayland(MPGLContext *ctx)
{
struct vo_wayland_state *wl = ctx->vo->wayland;
- if (!wl->frame.pending)
- return;
+ if (!wl->frame.callback)
+ vo_wayland_request_frame(ctx->vo, NULL, NULL);
+
+ if (!vo_wayland_wait_frame(ctx->vo))
+ MP_DBG(wl, "discarding frame callback\n");
eglSwapBuffers(wl->egl_context.egl.dpy, wl->egl_context.egl_surface);
- wl->frame.pending = false;
}
static int control(struct vo *vo, int *events, int request, void *data)
@@ -215,12 +230,6 @@ static int control(struct vo *vo, int *events, int request, void *data)
return r;
}
-static bool start_frame(struct MPGLContext *ctx)
-{
- struct vo_wayland_state *wl = ctx->vo->wayland;
- return wl->frame.pending;
-}
-
void mpgl_set_backend_wayland(MPGLContext *ctx)
{
ctx->config_window = config_window_wayland;
@@ -229,5 +238,4 @@ void mpgl_set_backend_wayland(MPGLContext *ctx)
ctx->vo_control = control;
ctx->vo_init = vo_wayland_init;
ctx->vo_uninit = vo_wayland_uninit;
- ctx->start_frame = start_frame;
}
diff --git a/video/out/gl_x11.c b/video/out/gl_x11.c
index 8fb4e59..9226356 100644
--- a/video/out/gl_x11.c
+++ b/video/out/gl_x11.c
@@ -33,7 +33,6 @@ struct glx_context {
XVisualInfo *vinfo;
GLXContext context;
GLXFBConfig fbc;
- bool force_es;
};
static bool create_context_x11_old(struct MPGLContext *ctx)
@@ -188,13 +187,6 @@ static bool config_window_x11(struct MPGLContext *ctx, int flags)
struct vo *vo = ctx->vo;
struct glx_context *glx_ctx = ctx->priv;
- if (glx_ctx->context) {
- // GL context and window already exist.
- // Only update window geometry etc.
- vo_x11_config_vo_window(vo, glx_ctx->vinfo, flags, "gl");
- return true;
- }
-
int glx_major, glx_minor;
if (!glXQueryVersion(vo->x11->display, &glx_major, &glx_minor)) {
@@ -248,12 +240,11 @@ static bool config_window_x11(struct MPGLContext *ctx, int flags)
glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_GREEN_SIZE, &ctx->depth_g);
glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_BLUE_SIZE, &ctx->depth_b);
- vo_x11_config_vo_window(vo, glx_ctx->vinfo, flags, "gl");
+ vo_x11_config_vo_window(vo, glx_ctx->vinfo, flags | VOFLAG_HIDDEN, "gl");
- int gl_version = ctx->requested_gl_version;
bool success = false;
- if (!glx_ctx->force_es) {
- success = create_context_x11_gl3(ctx, flags, gl_version, false);
+ if (!(flags & VOFLAG_GLES)) {
+ success = create_context_x11_gl3(ctx, flags, 300, false);
if (!success)
success = create_context_x11_old(ctx);
}
@@ -264,7 +255,28 @@ static bool config_window_x11(struct MPGLContext *ctx, int flags)
return success;
}
-static void releaseGlContext_x11(MPGLContext *ctx)
+static int glx_init(struct MPGLContext *ctx, int vo_flags)
+{
+ if (vo_x11_init(ctx->vo) && config_window_x11(ctx, vo_flags))
+ return 0;
+ vo_x11_uninit(ctx->vo);
+ return -1;
+}
+
+static int glx_reconfig(struct MPGLContext *ctx, int flags)
+{
+ struct glx_context *glx_ctx = ctx->priv;
+ vo_x11_config_vo_window(ctx->vo, glx_ctx->vinfo, flags, "gl");
+ return 0;
+}
+
+static int glx_control(struct MPGLContext *ctx, int *events, int request,
+ void *arg)
+{
+ return vo_x11_control(ctx->vo, events, request, arg);
+}
+
+static void glx_uninit(MPGLContext *ctx)
{
struct glx_context *glx_ctx = ctx->priv;
XVisualInfo **vinfo = &glx_ctx->vinfo;
@@ -272,33 +284,24 @@ static void releaseGlContext_x11(MPGLContext *ctx)
Display *display = ctx->vo->x11->display;
if (*vinfo)
XFree(*vinfo);
- *vinfo = NULL;
if (*context) {
glXMakeCurrent(display, None, NULL);
glXDestroyContext(display, *context);
}
- *context = 0;
+ vo_x11_uninit(ctx->vo);
}
-static void swapGlBuffers_x11(MPGLContext *ctx)
+static void glx_swap_buffers(struct MPGLContext *ctx)
{
glXSwapBuffers(ctx->vo->x11->display, ctx->vo->x11->window);
}
-void mpgl_set_backend_x11(MPGLContext *ctx)
-{
- ctx->priv = talloc_zero(ctx, struct glx_context);
- ctx->config_window = config_window_x11;
- ctx->releaseGlContext = releaseGlContext_x11;
- ctx->swapGlBuffers = swapGlBuffers_x11;
- ctx->vo_init = vo_x11_init;
- ctx->vo_uninit = vo_x11_uninit;
- ctx->vo_control = vo_x11_control;
-}
-
-void mpgl_set_backend_x11es(MPGLContext *ctx)
-{
- mpgl_set_backend_x11(ctx);
- struct glx_context *glx_ctx = ctx->priv;
- glx_ctx->force_es = true;
-}
+const struct mpgl_driver mpgl_driver_x11 = {
+ .name = "x11",
+ .priv_size = sizeof(struct glx_context),
+ .init = glx_init,
+ .reconfig = glx_reconfig,
+ .swap_buffers = glx_swap_buffers,
+ .control = glx_control,
+ .uninit = glx_uninit,
+}; \ No newline at end of file
diff --git a/video/out/gl_x11egl.c b/video/out/gl_x11egl.c
index 1f6f328..c23ac70 100644
--- a/video/out/gl_x11egl.c
+++ b/video/out/gl_x11egl.c
@@ -67,8 +67,8 @@ static bool create_context_egl(MPGLContext *ctx, EGLConfig config,
struct priv *p = ctx->priv;
EGLint context_attributes[] = {
- EGL_CONTEXT_CLIENT_VERSION, // aka EGL_CONTEXT_MAJOR_VERSION_KHR
- es ? 2 : MPGL_VER_GET_MAJOR(ctx->requested_gl_version),
+ // aka EGL_CONTEXT_MAJOR_VERSION_KHR
+ EGL_CONTEXT_CLIENT_VERSION, es ? 2 : 3,
EGL_NONE
};
@@ -93,22 +93,22 @@ static bool create_context_egl(MPGLContext *ctx, EGLConfig config,
return true;
}
-static bool config_window_x11_egl_(struct MPGLContext *ctx, int flags, bool es)
+static bool config_window_x11_egl(struct MPGLContext *ctx, int flags)
{
struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
+ bool es = flags & VOFLAG_GLES;
- if (p->egl_context) {
- vo_x11_config_vo_window(vo, NULL, flags, "gl");
- return true;
- }
-
- if (!eglBindAPI(es ? EGL_OPENGL_ES_API : EGL_OPENGL_API))
+ if (!eglBindAPI(es ? EGL_OPENGL_ES_API : EGL_OPENGL_API)) {
+ MP_FATAL(vo, "Could not bind API (%s).\n", es ? "GLES" : "GL");
return false;
+ }
p->egl_display = eglGetDisplay(vo->x11->display);
- if (!eglInitialize(p->egl_display, NULL, NULL))
+ if (!eglInitialize(p->egl_display, NULL, NULL)) {
+ MP_FATAL(vo, "Could not initialize EGL.\n");
return false;
+ }
EGLConfig config = select_fb_config_egl(ctx, es);
if (!config)
@@ -124,7 +124,7 @@ static bool config_window_x11_egl_(struct MPGLContext *ctx, int flags, bool es)
return false;
}
- vo_x11_config_vo_window(vo, vi, flags, "gl");
+ vo_x11_config_vo_window(vo, vi, flags | VOFLAG_HIDDEN, "gl");
XFree(vi);
@@ -140,17 +140,27 @@ static bool config_window_x11_egl_(struct MPGLContext *ctx, int flags, bool es)
return true;
}
-static bool config_window_x11_egl(struct MPGLContext *ctx, int flags)
+static int mpegl_init(struct MPGLContext *ctx, int vo_flags)
+{
+ if (vo_x11_init(ctx->vo) && config_window_x11_egl(ctx, vo_flags))
+ return 0;
+ vo_x11_uninit(ctx->vo);
+ return -1;
+}
+
+static int mpegl_reconfig(struct MPGLContext *ctx, int flags)
{
- return config_window_x11_egl_(ctx, flags, false);
+ vo_x11_config_vo_window(ctx->vo, NULL, flags, "gl");
+ return 0;
}
-static bool config_window_x11_egles(struct MPGLContext *ctx, int flags)
+static int mpegl_control(struct MPGLContext *ctx, int *events, int request,
+ void *arg)
{
- return config_window_x11_egl_(ctx, flags, true);
+ return vo_x11_control(ctx->vo, events, request, arg);
}
-static void releaseGlContext_egl(MPGLContext *ctx)
+static void mpegl_uninit(MPGLContext *ctx)
{
struct priv *p = ctx->priv;
if (p->egl_context) {
@@ -159,32 +169,21 @@ static void releaseGlContext_egl(MPGLContext *ctx)
eglDestroyContext(p->egl_display, p->egl_context);
}
p->egl_context = EGL_NO_CONTEXT;
+ vo_x11_uninit(ctx->vo);
}
-static void swapGlBuffers_egl(MPGLContext *ctx)
+static void mpegl_swap_buffers(MPGLContext *ctx)
{
struct priv *p = ctx->priv;
eglSwapBuffers(p->egl_display, p->egl_surface);
}
-void mpgl_set_backend_x11egl(MPGLContext *ctx)
-{
- ctx->priv = talloc_zero(ctx, struct priv);
- ctx->config_window = config_window_x11_egl;
- ctx->releaseGlContext = releaseGlContext_egl;
- ctx->swapGlBuffers = swapGlBuffers_egl;
- ctx->vo_init = vo_x11_init;
- ctx->vo_uninit = vo_x11_uninit;
- ctx->vo_control = vo_x11_control;
-}
-
-void mpgl_set_backend_x11egles(MPGLContext *ctx)
-{
- ctx->priv = talloc_zero(ctx, struct priv);
- ctx->config_window = config_window_x11_egles;
- ctx->releaseGlContext = releaseGlContext_egl;
- ctx->swapGlBuffers = swapGlBuffers_egl;
- ctx->vo_init = vo_x11_init;
- ctx->vo_uninit = vo_x11_uninit;
- ctx->vo_control = vo_x11_control;
-}
+const struct mpgl_driver mpgl_driver_x11egl = {
+ .name = "x11egl",
+ .priv_size = sizeof(struct priv),
+ .init = mpegl_init,
+ .reconfig = mpegl_reconfig,
+ .swap_buffers = mpegl_swap_buffers,
+ .control = mpegl_control,
+ .uninit = mpegl_uninit,
+};
diff --git a/video/out/vo.c b/video/out/vo.c
index f7e5c89..880a2e5 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -47,7 +47,6 @@
#include "osdep/io.h"
#include "osdep/threads.h"
-extern const struct vo_driver video_out_x11;
extern const struct vo_driver video_out_vdpau;
extern const struct vo_driver video_out_xv;
extern const struct vo_driver video_out_opengl;
@@ -80,6 +79,9 @@ const struct vo_driver *const video_out_drivers[] =
&video_out_direct3d_shaders,
&video_out_direct3d,
#endif
+#if HAVE_WAYLAND
+ &video_out_wayland,
+#endif
#if HAVE_XV
&video_out_xv,
#endif
@@ -89,9 +91,6 @@ const struct vo_driver *const video_out_drivers[] =
#if HAVE_VAAPI
&video_out_vaapi,
#endif
-#if HAVE_X11
- &video_out_x11,
-#endif
&video_out_null,
// should not be auto-selected
&video_out_image,
@@ -108,9 +107,6 @@ const struct vo_driver *const video_out_drivers[] =
&video_out_opengl_hq,
&video_out_opengl_cb,
#endif
-#if HAVE_WAYLAND
- &video_out_wayland,
-#endif
NULL
};
@@ -139,24 +135,25 @@ struct vo_internal {
int queued_events; // event mask for the user
int internal_events; // event mask for us
+ int64_t vsync_interval;
+
int64_t flip_queue_offset; // queue flip events at most this much in advance
+ int64_t missed_count;
int64_t drop_count;
bool dropped_frame; // the previous frame was dropped
- struct mp_image *current_frame; // last frame queued to the VO
+ struct vo_frame *current_frame; // last frame queued to the VO
int64_t wakeup_pts; // time at which to pull frame from decoder
bool rendering; // true if an image is being rendered
- struct mp_image *frame_queued; // the image that should be rendered
- int64_t frame_pts; // realtime of intended display
- int64_t frame_duration; // realtime frame duration (for framedrop)
+ struct vo_frame *frame_queued; // should be drawn next
+ int req_frames; // VO's requested value of num_frames
double display_fps;
// --- The following fields can be accessed from the VO thread only
- int64_t vsync_interval;
int64_t vsync_interval_approx;
int64_t last_flip;
char *window_title;
@@ -240,6 +237,7 @@ static struct vo *vo_create(bool probing, struct mpv_global *global,
talloc_steal(vo, log);
*vo->in = (struct vo_internal) {
.dispatch = mp_dispatch_create(vo),
+ .req_frames = 1,
};
mp_make_wakeup_pipe(vo->in->wakeup_pipe);
mp_dispatch_set_wakeup_fn(vo->in->dispatch, dispatch_wakeup_cb, vo);
@@ -378,7 +376,8 @@ static void run_reconfig(void *p)
}
pthread_mutex_lock(&in->lock);
- mp_image_unrefp(&in->current_frame);
+ talloc_free(in->current_frame);
+ in->current_frame = NULL;
forget_frames(vo);
pthread_mutex_unlock(&in->lock);
@@ -420,8 +419,14 @@ static void forget_frames(struct vo *vo)
in->hasframe = false;
in->hasframe_rendered = false;
in->drop_count = 0;
- mp_image_unrefp(&in->frame_queued);
+ in->missed_count = 0;
+ talloc_free(in->frame_queued);
+ in->frame_queued = NULL;
// don't unref current_frame; we always want to be able to redraw it
+ if (in->current_frame) {
+ in->current_frame->num_vsyncs = 0; // but reset future repeats
+ in->current_frame->display_synced = false; // mark discontinuity
+ }
}
#ifndef __MINGW32__
@@ -484,7 +489,7 @@ static void wakeup_locked(struct vo *vo)
{
struct vo_internal *in = vo->in;
- pthread_cond_signal(&in->wakeup);
+ pthread_cond_broadcast(&in->wakeup);
if (vo->event_fd >= 0)
wakeup_event_fd(vo);
if (vo->driver->wakeup)
@@ -509,12 +514,15 @@ void vo_wakeup(struct vo *vo)
// next_pts is the exact time when the next frame should be displayed. If the
// VO is ready, but the time is too "early", return false, and call the wakeup
// callback once the time is right.
+// If next_pts is negative, disable any timing and draw the frame as fast as
+// possible.
bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts)
{
struct vo_internal *in = vo->in;
pthread_mutex_lock(&in->lock);
- bool r = vo->config_ok && !in->frame_queued;
- if (r) {
+ bool r = vo->config_ok && !in->frame_queued &&
+ (!in->current_frame || in->current_frame->num_vsyncs < 1);
+ if (r && next_pts >= 0) {
// Don't show the frame too early - it would basically freeze the
// display by disallowing OSD redrawing or VO interaction.
// Actually render the frame at earliest 50ms before target time.
@@ -534,18 +542,17 @@ bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts)
// Direct the VO thread to put the currently queued image on the screen.
// vo_is_ready_for_frame() must have returned true before this call.
-// Ownership of the image is handed to the vo.
-void vo_queue_frame(struct vo *vo, struct mp_image *image,
- int64_t pts_us, int64_t duration)
+// Ownership of frame is handed to the vo.
+void vo_queue_frame(struct vo *vo, struct vo_frame *frame)
{
struct vo_internal *in = vo->in;
pthread_mutex_lock(&in->lock);
- assert(vo->config_ok && !in->frame_queued);
+ assert(vo->config_ok && !in->frame_queued &&
+ (!in->current_frame || in->current_frame->num_vsyncs < 1));
in->hasframe = true;
- in->frame_queued = image;
- in->frame_pts = pts_us;
- in->frame_duration = duration;
- in->wakeup_pts = in->vsync_timed ? 0 : in->frame_pts + MPMAX(duration, 0);
+ in->frame_queued = frame;
+ in->wakeup_pts = (frame->display_synced || in->vsync_timed)
+ ? 0 : frame->pts + MPMAX(frame->duration, 0);
wakeup_locked(vo);
pthread_mutex_unlock(&in->lock);
}
@@ -561,6 +568,22 @@ void vo_wait_frame(struct vo *vo)
pthread_mutex_unlock(&in->lock);
}
+// Wait until realtime is >= ts
+// called without lock
+static void wait_until(struct vo *vo, int64_t target)
+{
+ struct vo_internal *in = vo->in;
+ struct timespec ts = mp_time_us_to_timespec(target);
+ pthread_mutex_lock(&in->lock);
+ while (target > mp_time_us()) {
+ if (in->queued_events & VO_EVENT_LIVE_RESIZING)
+ break;
+ if (pthread_cond_timedwait(&in->wakeup, &in->lock, &ts))
+ break;
+ }
+ pthread_mutex_unlock(&in->lock);
+}
+
// needs lock
static int64_t prev_sync(struct vo *vo, int64_t ts)
{
@@ -576,6 +599,8 @@ static int64_t prev_sync(struct vo *vo, int64_t ts)
static bool render_frame(struct vo *vo)
{
struct vo_internal *in = vo->in;
+ struct vo_frame *frame = NULL;
+ bool got_frame = false;
update_display_fps(vo);
@@ -584,56 +609,76 @@ static bool render_frame(struct vo *vo)
vo->in->vsync_interval = in->display_fps > 0 ? 1e6 / in->display_fps : 0;
vo->in->vsync_interval = MPMAX(vo->in->vsync_interval, 1);
- int64_t pts = in->frame_pts;
- int64_t duration = in->frame_duration;
- struct mp_image *img = in->frame_queued;
+ bool continuous = in->current_frame && in->current_frame->display_synced;
- if (!img && (!in->vsync_timed || in->paused))
- goto nothing_done;
+ if (in->frame_queued) {
+ talloc_free(in->current_frame);
+ in->current_frame = in->frame_queued;
+ in->frame_queued = NULL;
+ } else if (in->paused || !in->current_frame || !in->hasframe ||
+ (!in->vsync_timed && !in->current_frame->display_synced))
+ {
+ goto done;
+ }
- if (in->vsync_timed && !in->hasframe)
- goto nothing_done;
+ if (in->current_frame->display_synced && in->current_frame->num_vsyncs < 1)
+ goto done;
- if (img)
- mp_image_setrefp(&in->current_frame, img);
+ frame = vo_frame_ref(in->current_frame);
+ assert(frame);
- in->frame_queued = NULL;
+ if (frame->display_synced) {
+ frame->pts = 0;
+ frame->duration = -1;
+ }
+
+ int64_t pts = frame->pts;
+ int64_t duration = frame->duration;
+ int64_t end_time = pts + duration;
// The next time a flip (probably) happens.
int64_t prev_vsync = prev_sync(vo, mp_time_us());
int64_t next_vsync = prev_vsync + in->vsync_interval;
- int64_t end_time = pts + duration;
+
+ frame->next_vsync = next_vsync;
+ frame->prev_vsync = prev_vsync;
+ frame->num_vsyncs = 1;
// Time at which we should flip_page on the VO.
- int64_t target = pts - in->flip_queue_offset;
-
- if (!in->hasframe_rendered)
- duration = -1; // disable framedrop
-
- // If the clip and display have similar/identical fps, it's possible that
- // we'll be very slightly late frequently due to timing jitter, or if the
- // clip/container timestamps are not very accurate.
- // So if we dropped the previous frame, keep dropping until we're aligned
- // perfectly, else, allow some slack (1 vsync) to let it settle into a rhythm.
- // On low clip fps, we don't drop anyway and the slack logic doesn't matter.
- // If the clip fps is more than ~5% above screen fps, we remove this slack
- // and use "normal" logic to allow more regular drops of 1 frame at a time.
- bool use_slack = duration > (0.95 * in->vsync_interval);
- in->dropped_frame = duration >= 0 &&
- use_slack ?
- ((in->dropped_frame && end_time < next_vsync) ||
- (end_time < prev_vsync)) // hard threshold - 1 vsync late
- :
- end_time < next_vsync; // normal frequent drops
+ int64_t target = frame->display_synced ? 0 : pts - in->flip_queue_offset;
+
+ bool prev_dropped_frame = in->dropped_frame;
+
+ // "normal" strict drop threshold.
+ in->dropped_frame = duration >= 0 && end_time < next_vsync;
+
+ // Clip has similar (within ~5%) or lower fps than the display.
+ if (duration >= 0 && duration > 0.95 * in->vsync_interval) {
+ // If the clip and display have similar/identical fps, it's possible that
+ // due to the very tight timing, we'll drop frames frequently even if on
+ // average we can keep up - especially if we have timing jitter (inaccurate
+ // clip timestamps, inaccurate timers, vsync block jitter, etc).
+ // So we're allowing to be 1 vsync late to prevent these frequent drops.
+ // However, once we've dropped a frame - "catch up" by using the strict
+ // threshold - which will typically be dropping 2 frames in a row.
+ // On low clip fps, we don't drop anyway and this logic doesn't matter.
+ // if we dropped previously - "catch up" by keeping the strict threshold.
+ in->dropped_frame &= prev_dropped_frame;
+ // if we haven't dropped - allow 1 frame late (prev_vsync as threshold).
+ in->dropped_frame |= end_time < prev_vsync;
+ }
+
+ in->dropped_frame &= !frame->display_synced;
in->dropped_frame &= !(vo->driver->caps & VO_CAP_FRAMEDROP);
in->dropped_frame &= (vo->global->opts->frame_dropping & 1);
// Even if we're hopelessly behind, rather degrade to 10 FPS playback,
// instead of just freezing the display forever.
in->dropped_frame &= mp_time_us() - in->last_flip < 100 * 1000;
+ in->dropped_frame &= in->hasframe_rendered;
- if (in->vsync_timed) {
+ if (in->vsync_timed && !frame->display_synced) {
// this is a heuristic that wakes the thread up some
// time before the next vsync
target = next_vsync - MPMIN(in->vsync_interval / 2, 8e3);
@@ -641,49 +686,45 @@ static bool render_frame(struct vo *vo)
// We are very late with the frame and using vsync timing: probably
// no new frames are coming in. This must be done whether or not
// framedrop is enabled. Also, if the frame is to be dropped, even
- // though it's an interpolated frame (img==NULL), exit early.
- if (!img && ((in->hasframe_rendered &&
- prev_vsync > pts + duration + in->vsync_interval_approx)
- || in->dropped_frame))
+ // though it's an interpolated frame (repeat set), exit early.
+ bool late = prev_vsync > pts + duration + in->vsync_interval_approx;
+ if (frame->repeat && ((in->hasframe_rendered && late) || in->dropped_frame))
{
in->dropped_frame = false;
- goto nothing_done;
+ goto done;
}
+
+ frame->vsync_offset = next_vsync - pts;
}
+ // Setup parameters for the next time this frame is drawn. ("frame" is the
+ // frame currently drawn, while in->current_frame is the potentially next.)
+ in->current_frame->repeat = true;
+ if (frame->display_synced)
+ in->current_frame->vsync_offset += in->vsync_interval;
+ if (in->current_frame->num_vsyncs > 0)
+ in->current_frame->num_vsyncs -= 1;
+
if (in->dropped_frame) {
- talloc_free(img);
+ in->drop_count += 1;
} else {
in->rendering = true;
in->hasframe_rendered = true;
+ int64_t prev_drop_count = vo->in->drop_count;
pthread_mutex_unlock(&in->lock);
mp_input_wakeup(vo->input_ctx); // core can queue new video now
MP_STATS(vo, "start video");
- if (vo->driver->draw_image_timed) {
- struct frame_timing t = (struct frame_timing) {
- .pts = pts,
- .next_vsync = next_vsync,
- .prev_vsync = prev_vsync,
- };
- vo->driver->draw_image_timed(vo, img, &t);
+ if (vo->driver->draw_frame) {
+ vo->driver->draw_frame(vo, frame);
} else {
- vo->driver->draw_image(vo, img);
+ vo->driver->draw_image(vo, mp_image_new_ref(frame->current));
}
- while (1) {
- int64_t now = mp_time_us();
- if (target <= now)
- break;
- mp_sleep_us(target - now);
- }
+ wait_until(vo, target);
- bool drop = false;
- if (vo->driver->flip_page_timed)
- drop = vo->driver->flip_page_timed(vo, pts, duration) < 1;
- else
- vo->driver->flip_page(vo);
+ vo->driver->flip_page(vo);
int64_t prev_flip = in->last_flip;
@@ -697,29 +738,32 @@ static bool render_frame(struct vo *vo)
in->vsync_interval_approx = in->last_flip - prev_flip;
MP_STATS(vo, "end video");
+ MP_STATS(vo, "video_end");
pthread_mutex_lock(&in->lock);
- in->dropped_frame = drop;
+ in->dropped_frame = prev_drop_count < vo->in->drop_count;
in->rendering = false;
+
+ if (in->current_frame && in->current_frame->display_synced &&
+ continuous && in->vsync_interval_approx > in->vsync_interval * 3 / 2)
+ in->missed_count += 1;
}
- if (in->dropped_frame) {
- in->drop_count += 1;
- } else {
+ if (!in->dropped_frame) {
vo->want_redraw = false;
in->want_redraw = false;
in->request_redraw = false;
}
- pthread_cond_signal(&in->wakeup); // for vo_wait_frame()
+ pthread_cond_broadcast(&in->wakeup); // for vo_wait_frame()
mp_input_wakeup(vo->input_ctx);
- pthread_mutex_unlock(&in->lock);
- return true;
+ got_frame = true;
-nothing_done:
+done:
+ talloc_free(frame);
pthread_mutex_unlock(&in->lock);
- return false;
+ return got_frame;
}
static void do_redraw(struct vo *vo)
@@ -728,28 +772,39 @@ static void do_redraw(struct vo *vo)
vo->want_redraw = false;
+ if (!vo->config_ok)
+ return;
+
pthread_mutex_lock(&in->lock);
in->request_redraw = false;
in->want_redraw = false;
bool full_redraw = in->dropped_frame;
- struct mp_image *img = NULL;
- if (vo->config_ok && !(vo->driver->untimed))
- img = mp_image_new_ref(in->current_frame);
- if (img)
+ struct vo_frame *frame = NULL;
+ if (!vo->driver->untimed)
+ frame = vo_frame_ref(in->current_frame);
+ if (frame)
in->dropped_frame = false;
+ struct vo_frame dummy = {0};
+ if (!frame)
+ frame = &dummy;
+ frame->redraw = !full_redraw; // unconditionally redraw if it was dropped
+ frame->still = true;
+ frame->pts = 0;
+ frame->duration = -1;
pthread_mutex_unlock(&in->lock);
- if (full_redraw || vo->driver->control(vo, VOCTRL_REDRAW_FRAME, NULL) < 1) {
- if (img)
- vo->driver->draw_image(vo, img);
- } else {
- talloc_free(img);
+ if (vo->driver->draw_frame) {
+ vo->driver->draw_frame(vo, frame);
+ } else if ((full_redraw || vo->driver->control(vo, VOCTRL_REDRAW_FRAME, NULL) < 1)
+ && frame->current)
+ {
+ vo->driver->draw_image(vo, mp_image_new_ref(frame->current));
}
- if (vo->driver->flip_page_timed)
- vo->driver->flip_page_timed(vo, 0, -1);
- else
- vo->driver->flip_page(vo);
+ vo->driver->flip_page(vo);
+
+ if (frame != &dummy)
+ talloc_free(frame);
}
static void *vo_thread(void *ptr)
@@ -801,7 +856,8 @@ static void *vo_thread(void *ptr)
wait_vo(vo, wait_until);
}
forget_frames(vo); // implicitly synchronized
- mp_image_unrefp(&in->current_frame);
+ talloc_free(in->current_frame);
+ in->current_frame = NULL;
vo->driver->uninit(vo);
return NULL;
}
@@ -871,7 +927,12 @@ bool vo_still_displaying(struct vo *vo)
struct vo_internal *in = vo->in;
pthread_mutex_lock(&vo->in->lock);
int64_t now = mp_time_us();
- int64_t frame_end = in->frame_pts + MPMAX(in->frame_duration, 0);
+ int64_t frame_end = 0;
+ if (in->current_frame) {
+ frame_end = in->current_frame->pts + MPMAX(in->current_frame->duration, 0);
+ if (in->current_frame->display_synced)
+ frame_end = in->current_frame->num_vsyncs > 0 ? INT64_MAX : 0;
+ }
bool working = now < frame_end || in->rendering || in->frame_queued;
pthread_mutex_unlock(&vo->in->lock);
return working && in->hasframe;
@@ -934,22 +995,66 @@ const char *vo_get_window_title(struct vo *vo)
// flip_page[_timed] will be called offset_us microseconds too early.
// (For vo_vdpau, which does its own timing.)
// Setting vsync_timed to true redraws as fast as possible.
-// (For vo_opengl smoothmotion.)
-void vo_set_flip_queue_params(struct vo *vo, int64_t offset_us, bool vsync_timed)
+// num_req_frames set the requested number of requested vo_frame.frames.
+// (For vo_opengl interpolation.)
+void vo_set_queue_params(struct vo *vo, int64_t offset_us, bool vsync_timed,
+ int num_req_frames)
{
struct vo_internal *in = vo->in;
pthread_mutex_lock(&in->lock);
in->flip_queue_offset = offset_us;
in->vsync_timed = vsync_timed;
+ in->req_frames = MPCLAMP(num_req_frames, 1, VO_MAX_REQ_FRAMES);
+ pthread_mutex_unlock(&in->lock);
+}
+
+int vo_get_num_req_frames(struct vo *vo)
+{
+ struct vo_internal *in = vo->in;
+ pthread_mutex_lock(&in->lock);
+ int res = in->req_frames;
pthread_mutex_unlock(&in->lock);
+ return res;
}
-// to be called from the VO thread only
int64_t vo_get_vsync_interval(struct vo *vo)
{
struct vo_internal *in = vo->in;
pthread_mutex_lock(&in->lock);
- int64_t res = vo->in->vsync_interval;
+ int64_t res = vo->in->vsync_interval > 1 ? vo->in->vsync_interval : -1;
+ pthread_mutex_unlock(&in->lock);
+ return res;
+}
+
+// Get the mp_time_us() time at which the currently rendering frame will end
+// (i.e. time of the last flip call needed to display it).
+// This can only be called while no new frame is queued (after
+// vo_is_ready_for_frame). Returns 0 for non-display synced frames, or if the
+// deadline for continuous display was missed.
+int64_t vo_get_next_frame_start_time(struct vo *vo)
+{
+ struct vo_internal *in = vo->in;
+ pthread_mutex_lock(&in->lock);
+ assert (!in->frame_queued);
+ int64_t res = 0;
+ if (in->last_flip && in->vsync_interval > 1 && in->current_frame) {
+ res = in->last_flip;
+ int extra = !!in->rendering;
+ res += (in->current_frame->num_vsyncs + extra) * in->vsync_interval;
+ if (!in->current_frame->display_synced)
+ res = 0;
+ if (in->current_frame->num_vsyncs < 1 && !in->rendering)
+ res = 0;
+ }
+ pthread_mutex_unlock(&in->lock);
+ return res;
+}
+
+int64_t vo_get_missed_count(struct vo *vo)
+{
+ struct vo_internal *in = vo->in;
+ pthread_mutex_lock(&in->lock);
+ int64_t res = vo->in->missed_count;
pthread_mutex_unlock(&in->lock);
return res;
}
@@ -994,11 +1099,40 @@ struct mp_image *vo_get_current_frame(struct vo *vo)
{
struct vo_internal *in = vo->in;
pthread_mutex_lock(&in->lock);
- struct mp_image *r = mp_image_new_ref(vo->in->current_frame);
+ struct mp_image *r = NULL;
+ if (vo->in->current_frame)
+ r = mp_image_new_ref(vo->in->current_frame->current);
pthread_mutex_unlock(&in->lock);
return r;
}
+static void destroy_frame(void *p)
+{
+ struct vo_frame *frame = p;
+ for (int n = 0; n < frame->num_frames; n++)
+ talloc_free(frame->frames[n]);
+}
+
+// Return a new reference to the given frame. The image pointers are also new
+// references. Calling talloc_free() on the frame unrefs all currently set
+// image references. (Assuming current==frames[0].)
+struct vo_frame *vo_frame_ref(struct vo_frame *frame)
+{
+ if (!frame)
+ return NULL;
+
+ struct vo_frame *new = talloc_ptrtype(NULL, new);
+ talloc_set_destructor(new, destroy_frame);
+ *new = *frame;
+ for (int n = 0; n < frame->num_frames; n++) {
+ new->frames[n] = mp_image_new_ref(frame->frames[n]);
+ if (!new->frames[n])
+ abort(); // OOM on tiny allocs
+ }
+ new->current = new->num_frames ? new->frames[0] : NULL;
+ return new;
+}
+
/*
* lookup an integer in a table, table must have 0 as the last key
* param: key key to search for
diff --git a/video/out/vo.h b/video/out/vo.h
index 26df707..4404500 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -40,6 +40,8 @@
#define VO_EVENT_WIN_STATE 8
// The ambient light conditions changed and need to be reloaded
#define VO_EVENT_AMBIENT_LIGHTING_CHANGED 16
+// Special mechanism for making resizing with Cocoa react faster
+#define VO_EVENT_LIVE_RESIZING 32
// Set of events the player core may be interested in.
#define VO_EVENTS_USER (VO_EVENT_RESIZE | VO_EVENT_WIN_STATE)
@@ -129,6 +131,7 @@ struct voctrl_get_equalizer_args {
#define VO_NOTIMPL -3
#define VOFLAG_HIDDEN 0x10 //< Use to create a hidden window
+#define VOFLAG_GLES 0x20 // Hint to prefer GLES2 if possible
#define VOFLAG_GL_DEBUG 0x40 // Hint to request debug OpenGL context
#define VOFLAG_ALPHA 0x80 // Hint to request alpha framebuffer
@@ -137,6 +140,8 @@ struct voctrl_get_equalizer_args {
// VO does framedrop itself (vo_vdpau). Untimed/encoding VOs never drop.
#define VO_CAP_FRAMEDROP 2
+#define VO_MAX_REQ_FRAMES 10
+
struct vo;
struct osd_state;
struct mp_image;
@@ -149,10 +154,42 @@ struct vo_extra {
struct mpv_opengl_cb_context *opengl_cb_context;
};
-struct frame_timing {
+struct vo_frame {
+ // If > 0, realtime when frame should be shown, in mp_time_us() units.
+ // If 0, present immediately.
int64_t pts;
+ // Approximate frame duration, in us.
+ int duration;
+ // Realtime of estimated previous and next vsync events.
int64_t next_vsync;
int64_t prev_vsync;
+ // "ideal" display time within the vsync
+ int64_t vsync_offset;
+ // how often the frame will be repeated (does not include OSD redraws)
+ int num_vsyncs;
+ // Set if the current frame is repeated from the previous. It's guaranteed
+ // that the current is the same as the previous one, even if the image
+ // pointer is different.
+ // The repeat flag is additionally set if the OSD does not need to be
+ // redrawn.
+ bool redraw, repeat;
+ // The frame is not in movement - e.g. redrawing while paused.
+ bool still;
+ // Frames are output as fast as possible, with implied vsync blocking.
+ bool display_synced;
+ // The current frame to be drawn.
+ // Warning: When OSD should be redrawn in --force-window --idle mode, this
+ // can be NULL. The VO should draw a black background, OSD on top.
+ struct mp_image *current;
+ // List of future images, starting with the current one. This does not
+ // care about repeated frames - it simply contains the next real frames.
+ // vo_set_queue_params() sets how many future frames this should include.
+ // The actual number of frames delivered to the VO can be lower.
+ // frames[0] is current, frames[1] is the next frame.
+ // Note that some future frames may never be sent as current frame to the
+ // VO if frames are dropped.
+ int num_frames;
+ struct mp_image *frames[VO_MAX_REQ_FRAMES];
};
struct vo_driver {
@@ -199,31 +236,21 @@ struct vo_driver {
* mpi belongs to the VO; the VO must free it eventually.
*
* This also should draw the OSD.
+ *
+ * Deprecated for draw_frame. A VO should have only either callback set.
*/
void (*draw_image)(struct vo *vo, struct mp_image *mpi);
- /* Like draw image, but is called before every vsync with timing
- * information
+ /* Render the given frame. Note that this is also called when repeating
+ * or redrawing frames.
*/
- void (*draw_image_timed)(struct vo *vo, struct mp_image *mpi,
- struct frame_timing *t);
+ void (*draw_frame)(struct vo *vo, struct vo_frame *frame);
/*
* Blit/Flip buffer to the screen. Must be called after each frame!
*/
void (*flip_page)(struct vo *vo);
- /*
- * Timed version of flip_page (optional).
- * pts_us is the frame presentation time, linked to mp_time_us().
- * pts_us is 0 if the frame should be presented immediately.
- * duration is estimated time in us until the next frame is shown.
- * duration is -1 if it is unknown or unset (also: disable framedrop).
- * If the VO does manual framedropping, VO_CAP_FRAMEDROP should be set.
- * Returns 1 on display, or 0 if the frame was dropped.
- */
- int (*flip_page_timed)(struct vo *vo, int64_t pts_us, int duration);
-
/* These optional callbacks can be provided if the GUI framework used by
* the VO requires entering a message loop for receiving events, does not
* provide event_fd, and does not call vo_wakeup() from a separate thread
@@ -299,8 +326,7 @@ int vo_reconfig(struct vo *vo, struct mp_image_params *p, int flags);
int vo_control(struct vo *vo, uint32_t request, void *data);
bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts);
-void vo_queue_frame(struct vo *vo, struct mp_image *image,
- int64_t pts_us, int64_t duration);
+void vo_queue_frame(struct vo *vo, struct vo_frame *frame);
void vo_wait_frame(struct vo *vo);
bool vo_still_displaying(struct vo *vo);
bool vo_has_frame(struct vo *vo);
@@ -311,14 +337,17 @@ void vo_destroy(struct vo *vo);
void vo_set_paused(struct vo *vo, bool paused);
int64_t vo_get_drop_count(struct vo *vo);
void vo_increment_drop_count(struct vo *vo, int64_t n);
+int64_t vo_get_missed_count(struct vo *vo);
void vo_query_formats(struct vo *vo, uint8_t *list);
void vo_event(struct vo *vo, int event);
int vo_query_and_reset_events(struct vo *vo, int events);
struct mp_image *vo_get_current_frame(struct vo *vo);
-
-void vo_set_flip_queue_params(struct vo *vo, int64_t offset_us, bool vsync_timed);
+void vo_set_queue_params(struct vo *vo, int64_t offset_us, bool vsync_timed,
+ int num_req_frames);
+int vo_get_num_req_frames(struct vo *vo);
int64_t vo_get_vsync_interval(struct vo *vo);
double vo_get_display_fps(struct vo *vo);
+int64_t vo_get_next_frame_start_time(struct vo *vo);
void vo_wakeup(struct vo *vo);
@@ -334,4 +363,6 @@ struct mp_osd_res;
void vo_get_src_dst_rects(struct vo *vo, struct mp_rect *out_src,
struct mp_rect *out_dst, struct mp_osd_res *out_osd);
+struct vo_frame *vo_frame_ref(struct vo_frame *frame);
+
#endif /* MPLAYER_VIDEO_OUT_H */
diff --git a/video/out/vo_direct3d.c b/video/out/vo_direct3d.c
index 54a9269..61e9ce3 100644
--- a/video/out/vo_direct3d.c
+++ b/video/out/vo_direct3d.c
@@ -34,6 +34,7 @@
#include "video/csputils.h"
#include "video/mp_image.h"
#include "video/img_format.h"
+#include "video/d3d.h"
#include "common/msg.h"
#include "common/common.h"
#include "w32_common.h"
@@ -191,6 +192,10 @@ typedef struct d3d_priv {
struct mp_csp_equalizer video_eq;
struct osdpart *osd[MAX_OSD_PARTS];
+
+ struct mp_hwdec_info hwdec_info;
+ struct mp_hwdec_ctx hwdec_ctx;
+ struct mp_d3d_ctx hwdec_d3d;
} d3d_priv;
struct fmt_entry {
@@ -733,12 +738,16 @@ static bool change_d3d_backbuffer(d3d_priv *priv)
D3DADAPTER_DEFAULT,
DEVTYPE, vo_w32_hwnd(priv->vo),
D3DCREATE_SOFTWARE_VERTEXPROCESSING
- | D3DCREATE_FPU_PRESERVE,
+ | D3DCREATE_FPU_PRESERVE
+ | D3DCREATE_MULTITHREADED,
&present_params, &priv->d3d_device)))
{
MP_VERBOSE(priv, "Creating Direct3D device failed.\n");
return 0;
}
+
+ // (race condition if this is called when recovering from a "lost" device)
+ priv->hwdec_d3d.d3d9_device = priv->d3d_device;
} else {
if (FAILED(IDirect3DDevice9_Reset(priv->d3d_device, &present_params))) {
MP_ERR(priv, "Reseting Direct3D device failed.\n");
@@ -772,6 +781,8 @@ static bool change_d3d_backbuffer(d3d_priv *priv)
static void destroy_d3d(d3d_priv *priv)
{
+ priv->hwdec_d3d.d3d9_device = NULL;
+
destroy_d3d_surfaces(priv);
for (int n = 0; n < NUM_SHADERS; n++) {
@@ -887,6 +898,9 @@ static uint32_t d3d_draw_frame(d3d_priv *priv)
if (!priv->have_image)
goto render_osd;
+ RECT rm = priv->fs_movie_rect;
+ RECT rs = priv->fs_panscan_rect;
+
if (priv->use_textures) {
for (n = 0; n < priv->plane_count; n++) {
@@ -894,9 +908,6 @@ static uint32_t d3d_draw_frame(d3d_priv *priv)
d3dtex_get_render_texture(priv, &priv->planes[n].texture));
}
- RECT rm = priv->fs_movie_rect;
- RECT rs = priv->fs_panscan_rect;
-
vertex_video vb[] = {
{ rm.left, rm.top, 0.0f},
{ rm.right, rm.top, 0.0f},
@@ -941,11 +952,15 @@ static uint32_t d3d_draw_frame(d3d_priv *priv)
}
} else {
+ rs.left &= ~(ULONG)1;
+ rs.top &= ~(ULONG)1;
+ rs.right &= ~(ULONG)1;
+ rs.bottom &= ~(ULONG)1;
if (FAILED(IDirect3DDevice9_StretchRect(priv->d3d_device,
priv->d3d_surface,
- &priv->fs_panscan_rect,
+ &rs,
priv->d3d_backbuf,
- &priv->fs_movie_rect,
+ &rm,
D3DTEXF_LINEAR))) {
MP_ERR(priv, "Copying frame to the backbuffer failed.\n");
return VO_ERROR;
@@ -1212,6 +1227,9 @@ static int preinit(struct vo *vo)
priv->vo = vo;
priv->log = vo->log;
+ priv->hwdec_info.hwctx = &priv->hwdec_ctx;
+ priv->hwdec_ctx.d3d_ctx = &priv->hwdec_d3d;
+
for (int n = 0; n < MAX_OSD_PARTS; n++) {
struct osdpart *osd = talloc_ptrtype(priv, osd);
*osd = (struct osdpart) {
@@ -1259,6 +1277,11 @@ static int control(struct vo *vo, uint32_t request, void *data)
d3d_priv *priv = vo->priv;
switch (request) {
+ case VOCTRL_GET_HWDEC_INFO: {
+ struct mp_hwdec_info **arg = data;
+ *arg = &priv->hwdec_info;
+ return true;
+ }
case VOCTRL_REDRAW_FRAME:
d3d_draw_frame(priv);
return VO_TRUE;
diff --git a/video/out/vo_drm.c b/video/out/vo_drm.c
index 9ef371e..f853b90 100644
--- a/video/out/vo_drm.c
+++ b/video/out/vo_drm.c
@@ -41,6 +41,9 @@
#include "video/sws_utils.h"
#include "vo.h"
+#define IMGFMT IMGFMT_BGR0
+#define BYTES_PER_PIXEL 4
+#define BITS_PER_PIXEL 32
#define USE_MASTER 0
#define BUF_COUNT 2
@@ -66,19 +69,21 @@ struct modeset_dev {
struct priv {
char *device_path;
int connector_id;
+ int mode_id;
int fd;
- struct vt_switcher vt_switcher;
struct modeset_dev *dev;
drmModeCrtc *old_crtc;
drmEventContext ev;
+ bool vt_switcher_active;
+ struct vt_switcher vt_switcher;
+
bool active;
bool pflip_happening;
int32_t device_w;
int32_t device_h;
- int32_t x, y;
struct mp_image *last_input;
struct mp_image *cur_frame;
struct mp_rect src;
@@ -133,7 +138,7 @@ static int modeset_create_fb(struct vo *vo, int fd, struct modeset_buf *buf)
struct drm_mode_create_dumb creq = {
.width = buf->width,
.height = buf->height,
- .bpp = 32,
+ .bpp = BITS_PER_PIXEL,
};
ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
if (ret < 0) {
@@ -146,7 +151,7 @@ static int modeset_create_fb(struct vo *vo, int fd, struct modeset_buf *buf)
buf->handle = creq.handle;
// create framebuffer object for the dumb-buffer
- ret = drmModeAddFB(fd, buf->width, buf->height, 24, 32, buf->stride,
+ ret = drmModeAddFB(fd, buf->width, buf->height, 24, creq.bpp, buf->stride,
buf->handle, &buf->fb);
if (ret) {
MP_ERR(vo, "Cannot create framebuffer: %s\n", mp_strerror(errno));
@@ -242,7 +247,7 @@ static bool is_connector_valid(struct vo *vo, int conn_id,
return true;
}
-static int modeset_prepare_dev(struct vo *vo, int fd, int conn_id,
+static int modeset_prepare_dev(struct vo *vo, int fd, int conn_id, int mode_id,
struct modeset_dev **out)
{
struct modeset_dev *dev = NULL;
@@ -291,17 +296,23 @@ static int modeset_prepare_dev(struct vo *vo, int fd, int conn_id,
goto end;
}
+ if (mode_id < 0 || mode_id >= conn->count_modes) {
+ MP_ERR(vo, "Bad mode ID (max = %d).\n", conn->count_modes - 1);
+ MP_INFO(vo, "Available modes:\n");
+ for (unsigned int i = 0; i < conn->count_modes; i++) {
+ MP_INFO(vo, "Mode %d: %s (%dx%d)\n", i, conn->modes[i].name,
+ conn->modes[i].hdisplay, conn->modes[i].vdisplay);
+ }
+ }
+
dev = talloc_zero(vo->priv, struct modeset_dev);
dev->conn = conn->connector_id;
dev->front_buf = 0;
- dev->mode = conn->modes[0];
- dev->bufs[0].width = conn->modes[0].hdisplay;
- dev->bufs[0].height = conn->modes[0].vdisplay;
- dev->bufs[1].width = conn->modes[0].hdisplay;
- dev->bufs[1].height = conn->modes[0].vdisplay;
-
- MP_INFO(vo, "Connector using mode %ux%u\n",
- dev->bufs[0].width, dev->bufs[0].height);
+ dev->mode = conn->modes[mode_id];
+ for (unsigned int i = 0; i < 2; i++) {
+ dev->bufs[i].width = dev->mode.hdisplay;
+ dev->bufs[i].height = dev->mode.vdisplay;
+ }
ret = modeset_find_crtc(vo, fd, res, conn, dev);
if (ret) {
@@ -385,7 +396,7 @@ static void release_vo_crtc(struct vo *vo)
p->old_crtc->y,
&p->dev->conn,
1,
- &p->dev->mode);
+ &p->old_crtc->mode);
drmModeFreeCrtc(p->old_crtc);
p->old_crtc = NULL;
}
@@ -424,16 +435,19 @@ static void acquire_vt(void *data)
static int wait_events(struct vo *vo, int64_t until_time_us)
{
struct priv *p = vo->priv;
- int64_t wait_us = until_time_us - mp_time_us();
- int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000);
- vt_switcher_poll(&p->vt_switcher, timeout_ms);
+ if (p->vt_switcher_active) {
+ int64_t wait_us = until_time_us - mp_time_us();
+ int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000);
+ vt_switcher_poll(&p->vt_switcher, timeout_ms);
+ }
return 0;
}
static void wakeup(struct vo *vo)
{
struct priv *p = vo->priv;
- vt_switcher_interrupt_poll(&p->vt_switcher);
+ if (p->vt_switcher_active)
+ vt_switcher_interrupt_poll(&p->vt_switcher);
}
static int reconfig(struct vo *vo, struct mp_image_params *params, int flags)
@@ -444,8 +458,8 @@ static int reconfig(struct vo *vo, struct mp_image_params *params, int flags)
vo->dheight = p->device_h;
vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd);
- int32_t w = p->dst.x1 - p->dst.x0;
- int32_t h = p->dst.y1 - p->dst.y0;
+ int w = p->dst.x1 - p->dst.x0;
+ int h = p->dst.y1 - p->dst.y0;
// p->osd contains the parameters assuming OSD rendering in window
// coordinates, but OSD can only be rendered in the intersection
@@ -457,13 +471,10 @@ static int reconfig(struct vo *vo, struct mp_image_params *params, int flags)
p->osd.mr = MPMIN(0, p->osd.mr);
p->osd.ml = MPMIN(0, p->osd.ml);
- p->x = (p->device_w - w) >> 1;
- p->y = (p->device_h - h) >> 1;
-
mp_sws_set_from_cmdline(p->sws, vo->opts->sws_opts);
p->sws->src = *params;
p->sws->dst = (struct mp_image_params) {
- .imgfmt = IMGFMT_BGR0,
+ .imgfmt = IMGFMT,
.w = w,
.h = h,
.d_w = w,
@@ -471,13 +482,13 @@ static int reconfig(struct vo *vo, struct mp_image_params *params, int flags)
};
talloc_free(p->cur_frame);
- p->cur_frame = mp_image_alloc(IMGFMT_BGR0, p->device_w, p->device_h);
+ p->cur_frame = mp_image_alloc(IMGFMT, p->device_w, p->device_h);
mp_image_params_guess_csp(&p->sws->dst);
mp_image_set_params(p->cur_frame, &p->sws->dst);
struct modeset_buf *buf = p->dev->bufs;
- memset(buf[0].map, 0, buf[0].size);
- memset(buf[1].map, 0, buf[1].size);
+ for (unsigned int i = 0; i < BUF_COUNT; i++)
+ memset(buf[i].map, 0, buf[i].size);
if (mp_sws_reinit(p->sws) < 0)
return -1;
@@ -490,7 +501,7 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
{
struct priv *p = vo->priv;
- if (p->active) {
+ if (p->active && mpi) {
struct mp_image src = *mpi;
struct mp_rect src_rc = p->src;
src_rc.x0 = MP_ALIGN_DOWN(src_rc.x0, mpi->fmt.align_x);
@@ -500,12 +511,16 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
osd_draw_on_image(vo->osd, p->osd, src.pts, 0, p->cur_frame);
struct modeset_buf *front_buf = &p->dev->bufs[p->dev->front_buf];
- int32_t shift = (p->device_w * p->y + p->x) * 4;
+ int w = p->dst.x1 - p->dst.x0;
+ int h = p->dst.y1 - p->dst.y0;
+ int x = (p->device_w - w) >> 1;
+ int y = (p->device_h - h) >> 1;
+ int shift = y * front_buf->stride + x * BYTES_PER_PIXEL;
memcpy_pic(front_buf->map + shift,
p->cur_frame->planes[0],
- (p->dst.x1 - p->dst.x0) * 4,
- p->dst.y1 - p->dst.y0,
- p->device_w * 4,
+ w * BYTES_PER_PIXEL,
+ h,
+ front_buf->stride,
p->cur_frame->stride[0]);
}
@@ -553,13 +568,14 @@ static void uninit(struct vo *vo)
if (p->dev) {
release_vo_crtc(vo);
-
- modeset_destroy_fb(p->fd, &p->dev->bufs[1]);
- modeset_destroy_fb(p->fd, &p->dev->bufs[0]);
+ for (unsigned int i = 0; i < BUF_COUNT; i++)
+ modeset_destroy_fb(p->fd, &p->dev->bufs[i]);
drmModeFreeEncoder(p->dev->enc);
}
- vt_switcher_destroy(&p->vt_switcher);
+ if (p->vt_switcher_active)
+ vt_switcher_destroy(&p->vt_switcher);
+
talloc_free(p->last_input);
talloc_free(p->cur_frame);
talloc_free(p->dev);
@@ -574,16 +590,18 @@ static int preinit(struct vo *vo)
p->ev.version = DRM_EVENT_CONTEXT_VERSION;
p->ev.page_flip_handler = modeset_page_flipped;
- if (vt_switcher_init(&p->vt_switcher, vo->log))
- goto err;
-
- vt_switcher_acquire(&p->vt_switcher, acquire_vt, vo);
- vt_switcher_release(&p->vt_switcher, release_vt, vo);
+ p->vt_switcher_active = vt_switcher_init(&p->vt_switcher, vo->log) == 0;
+ if (p->vt_switcher_active) {
+ vt_switcher_acquire(&p->vt_switcher, acquire_vt, vo);
+ vt_switcher_release(&p->vt_switcher, release_vt, vo);
+ } else {
+ MP_WARN(vo, "Failed to set up VT switcher. Terminal switching will be unavailable.\n");
+ }
if (modeset_open(vo, &p->fd, p->device_path))
goto err;
- if (modeset_prepare_dev(vo, p->fd, p->connector_id, &p->dev))
+ if (modeset_prepare_dev(vo, p->fd, p->connector_id, p->mode_id, &p->dev))
goto err;
assert(p->dev);
@@ -646,10 +664,12 @@ const struct vo_driver video_out_drm = {
.options = (const struct m_option[]) {
OPT_STRING("devpath", device_path, 0),
OPT_INT("connector", connector_id, 0),
+ OPT_INT("mode", mode_id, 0),
{0},
},
.priv_defaults = &(const struct priv) {
.device_path = "/dev/dri/card0",
.connector_id = -1,
+ .mode_id = 0,
},
};
diff --git a/video/out/vo_image.c b/video/out/vo_image.c
index 4a87d82..e8f9e1f 100644
--- a/video/out/vo_image.c
+++ b/video/out/vo_image.c
@@ -95,7 +95,7 @@ static void flip_page(struct vo *vo)
image_writer_file_ext(p->opts));
if (p->outdir && strlen(p->outdir))
- filename = mp_path_join(t, bstr0(p->outdir), bstr0(filename));
+ filename = mp_path_join(t, p->outdir, filename);
MP_INFO(vo, "Saving %s\n", filename);
write_image(p->current, p->opts, filename, vo->log);
diff --git a/video/out/vo_null.c b/video/out/vo_null.c
index 6455210..52ec934 100644
--- a/video/out/vo_null.c
+++ b/video/out/vo_null.c
@@ -19,14 +19,18 @@
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <stdio.h>
#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include "config.h"
#include "common/msg.h"
#include "vo.h"
#include "video/mp_image.h"
+#include "osdep/timer.h"
+#include "options/m_option.h"
+
+struct priv {
+ int64_t last_vsync;
+
+ double cfg_fps;
+};
static void draw_image(struct vo *vo, mp_image_t *mpi)
{
@@ -35,6 +39,18 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
static void flip_page(struct vo *vo)
{
+ struct priv *p = vo->priv;
+ if (p->cfg_fps) {
+ int64_t ft = 1e6 / p->cfg_fps;
+ int64_t prev_vsync = mp_time_us() / ft;
+ int64_t target_time = (prev_vsync + 1) * ft;
+ for (;;) {
+ int64_t now = mp_time_us();
+ if (now >= target_time)
+ break;
+ mp_sleep_us(target_time - now);
+ }
+ }
}
static int query_format(struct vo *vo, int format)
@@ -58,9 +74,18 @@ static int preinit(struct vo *vo)
static int control(struct vo *vo, uint32_t request, void *data)
{
+ struct priv *p = vo->priv;
+ switch (request) {
+ case VOCTRL_GET_DISPLAY_FPS:
+ if (!p->cfg_fps)
+ break;
+ *(double *)data = p->cfg_fps;
+ return VO_TRUE;
+ }
return VO_NOTIMPL;
}
+#define OPT_BASE_STRUCT struct priv
const struct vo_driver video_out_null = {
.description = "Null video output",
.name = "null",
@@ -71,4 +96,9 @@ const struct vo_driver video_out_null = {
.draw_image = draw_image,
.flip_page = flip_page,
.uninit = uninit,
+ .priv_size = sizeof(struct priv),
+ .options = (const struct m_option[]) {
+ OPT_DOUBLE("fps", cfg_fps, M_OPT_RANGE, .min = 0, .max = 10000),
+ {0},
+ },
};
diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c
index dda9f1e..3985ac4 100644
--- a/video/out/vo_opengl.c
+++ b/video/out/vo_opengl.c
@@ -75,8 +75,7 @@ struct gl_priv {
int dwm_flush;
char *backend;
-
- bool frame_started;
+ int es;
int frames_rendered;
unsigned int prev_sgi_sync_count;
@@ -87,7 +86,6 @@ struct gl_priv {
int matches, mismatches;
};
-// Always called under mpgl_lock
static void resize(struct gl_priv *p)
{
struct vo *vo = p->vo;
@@ -124,15 +122,7 @@ static void flip_page(struct vo *vo)
struct gl_priv *p = vo->priv;
GL *gl = p->gl;
- if (!p->frame_started) {
- vo_increment_drop_count(vo, 1);
- return;
- }
- p->frame_started = false;
-
- mpgl_lock(p->glctx);
-
- p->glctx->swapGlBuffers(p->glctx);
+ mpgl_swap_buffers(p->glctx);
p->frames_rendered++;
if (p->frames_rendered > 5 && !p->use_gl_debug)
@@ -164,28 +154,14 @@ static void flip_page(struct vo *vo)
p->swap_interval,
p->current_swap_interval);
}
-
- mpgl_unlock(p->glctx);
}
-static void draw_image_timed(struct vo *vo, mp_image_t *mpi,
- struct frame_timing *t)
+static void draw_frame(struct vo *vo, struct vo_frame *frame)
{
struct gl_priv *p = vo->priv;
GL *gl = p->gl;
- mpgl_lock(p->glctx);
-
- if (mpi)
- gl_video_set_image(p->renderer, mpi);
-
- if (p->glctx->start_frame && !p->glctx->start_frame(p->glctx)) {
- mpgl_unlock(p->glctx);
- return;
- }
-
- p->frame_started = true;
- gl_video_render_frame(p->renderer, 0, t);
+ gl_video_render_frame(p->renderer, frame, 0);
// The playloop calls this last before waiting some time until it decides
// to call flip_page(). Tell OpenGL to start execution of the GPU commands
@@ -194,13 +170,6 @@ static void draw_image_timed(struct vo *vo, mp_image_t *mpi,
if (p->use_glFinish)
gl->Finish();
-
- mpgl_unlock(p->glctx);
-}
-
-static void draw_image(struct vo *vo, mp_image_t *mpi)
-{
- draw_image_timed(vo, mpi, NULL);
}
static int query_format(struct vo *vo, int format)
@@ -211,34 +180,17 @@ static int query_format(struct vo *vo, int format)
return 1;
}
-static void video_resize_redraw_callback(struct vo *vo, int w, int h)
-{
- struct gl_priv *p = vo->priv;
- gl_video_resize_redraw(p->renderer, w, -h);
-
-}
-
static int reconfig(struct vo *vo, struct mp_image_params *params, int flags)
{
struct gl_priv *p = vo->priv;
- mpgl_lock(p->glctx);
-
- if (!mpgl_reconfig_window(p->glctx, flags)) {
- mpgl_unlock(p->glctx);
+ if (!mpgl_reconfig_window(p->glctx, flags))
return -1;
- }
-
- if (p->glctx->register_resize_callback) {
- p->glctx->register_resize_callback(vo, video_resize_redraw_callback);
- }
resize(p);
gl_video_config(p->renderer, params);
- mpgl_unlock(p->glctx);
-
return 0;
}
@@ -246,12 +198,11 @@ static void request_hwdec_api(struct gl_priv *p, const char *api_name)
{
if (p->hwdec)
return;
- mpgl_lock(p->glctx);
+
p->hwdec = gl_hwdec_load_api(p->vo->log, p->gl, api_name);
gl_video_set_hwdec(p->renderer, p->hwdec);
if (p->hwdec)
p->hwdec_info.hwctx = p->hwdec->hwctx;
- mpgl_unlock(p->glctx);
}
static void call_request_hwdec_api(struct mp_hwdec_info *info,
@@ -266,10 +217,11 @@ static void call_request_hwdec_api(struct mp_hwdec_info *info,
static bool get_and_update_icc_profile(struct gl_priv *p, int *events)
{
- if (p->icc_opts->profile_auto) {
+ bool has_profile = p->icc_opts->profile && p->icc_opts->profile[0];
+ if (p->icc_opts->profile_auto && !has_profile) {
MP_VERBOSE(p, "Querying ICC profile...\n");
bstr icc = bstr0(NULL);
- int r = p->glctx->vo_control(p->vo, events, VOCTRL_GET_ICC_PROFILE, &icc);
+ int r = mpgl_control(p->glctx, events, VOCTRL_GET_ICC_PROFILE, &icc);
if (r != VO_NOTAVAIL) {
if (r == VO_FALSE) {
@@ -329,12 +281,9 @@ static bool reparse_cmdline(struct gl_priv *p, char *args)
}
if (r >= 0) {
- mpgl_lock(p->glctx);
- int queue = 0;
- gl_video_set_options(p->renderer, opts->renderer_opts, &queue);
- vo_set_flip_queue_params(p->vo, queue, opts->renderer_opts->interpolation);
+ gl_video_set_options(p->renderer, opts->renderer_opts);
+ gl_video_configure_queue(p->renderer, p->vo);
p->vo->want_redraw = true;
- mpgl_unlock(p->glctx);
}
talloc_free(cfg);
@@ -349,35 +298,34 @@ static int control(struct vo *vo, uint32_t request, void *data)
case VOCTRL_GET_PANSCAN:
return VO_TRUE;
case VOCTRL_SET_PANSCAN:
- mpgl_lock(p->glctx);
resize(p);
- mpgl_unlock(p->glctx);
return VO_TRUE;
case VOCTRL_GET_EQUALIZER: {
struct voctrl_get_equalizer_args *args = data;
- mpgl_lock(p->glctx);
struct mp_csp_equalizer *eq = gl_video_eq_ptr(p->renderer);
bool r = mp_csp_equalizer_get(eq, args->name, args->valueptr) >= 0;
- mpgl_unlock(p->glctx);
return r ? VO_TRUE : VO_NOTIMPL;
}
case VOCTRL_SET_EQUALIZER: {
struct voctrl_set_equalizer_args *args = data;
- mpgl_lock(p->glctx);
struct mp_csp_equalizer *eq = gl_video_eq_ptr(p->renderer);
- bool r = mp_csp_equalizer_set(eq, args->name, args->value) >= 0;
- if (r)
+ if (mp_csp_equalizer_set(eq, args->name, args->value) >= 0) {
gl_video_eq_update(p->renderer);
- mpgl_unlock(p->glctx);
- if (r)
vo->want_redraw = true;
- return r ? VO_TRUE : VO_NOTIMPL;
+ return VO_TRUE;
+ }
+ return VO_NOTIMPL;
}
- case VOCTRL_SCREENSHOT_WIN:
- mpgl_lock(p->glctx);
- *(struct mp_image **)data = glGetWindowScreenshot(p->gl);
- mpgl_unlock(p->glctx);
+ case VOCTRL_SCREENSHOT_WIN: {
+ struct mp_image *screen = glGetWindowScreenshot(p->gl);
+ // set image parameters according to the display, if possible
+ if (screen) {
+ screen->params.primaries = p->renderer_opts->target_prim;
+ screen->params.gamma = p->renderer_opts->target_trc;
+ }
+ *(struct mp_image **)data = screen;
return true;
+ }
case VOCTRL_GET_HWDEC_INFO: {
struct mp_hwdec_info **arg = data;
*arg = &p->hwdec_info;
@@ -386,34 +334,23 @@ static int control(struct vo *vo, uint32_t request, void *data)
case VOCTRL_LOAD_HWDEC_API:
request_hwdec_api(p, data);
return true;
- case VOCTRL_REDRAW_FRAME:
- mpgl_lock(p->glctx);
- if (!(p->glctx->start_frame && !p->glctx->start_frame(p->glctx))) {
- p->frame_started = true;
- gl_video_render_frame(p->renderer, 0, NULL);
- }
- mpgl_unlock(p->glctx);
- return true;
case VOCTRL_SET_COMMAND_LINE: {
char *arg = data;
return reparse_cmdline(p, arg);
}
case VOCTRL_RESET:
- mpgl_lock(p->glctx);
gl_video_reset(p->renderer);
- mpgl_unlock(p->glctx);
return true;
case VOCTRL_PAUSE:
- mpgl_lock(p->glctx);
- if (gl_video_showing_interpolated_frame(p->renderer))
+ if (gl_video_showing_interpolated_frame(p->renderer)) {
vo->want_redraw = true;
- mpgl_unlock(p->glctx);
+ vo_wakeup(vo);
+ }
return true;
}
- mpgl_lock(p->glctx);
int events = 0;
- int r = p->glctx->vo_control(vo, &events, request, data);
+ int r = mpgl_control(p->glctx, &events, request, data);
if (events & VO_EVENT_ICC_PROFILE_CHANGED) {
get_and_update_icc_profile(p, &events);
vo->want_redraw = true;
@@ -427,7 +364,6 @@ static int control(struct vo *vo, uint32_t request, void *data)
if (events & VO_EVENT_EXPOSE)
vo->want_redraw = true;
vo_event(vo, events);
- mpgl_unlock(p->glctx);
return r;
}
@@ -455,6 +391,9 @@ static int preinit(struct vo *vo)
if (p->use_gl_debug)
vo_flags |= VOFLAG_GL_DEBUG;
+ if (p->es)
+ vo_flags |= VOFLAG_GLES;
+
if (p->allow_sw)
vo->probing = false;
@@ -463,8 +402,6 @@ static int preinit(struct vo *vo)
goto err_out;
p->gl = p->glctx->gl;
- mpgl_lock(p->glctx);
-
if (p->gl->SwapInterval) {
p->gl->SwapInterval(p->swap_interval);
} else {
@@ -472,15 +409,14 @@ static int preinit(struct vo *vo)
}
p->current_swap_interval = p->swap_interval;
- p->renderer = gl_video_init(p->gl, vo->log);
+ p->renderer = gl_video_init(p->gl, vo->log, vo->global);
if (!p->renderer)
goto err_out;
gl_video_set_osd_source(p->renderer, vo->osd);
gl_video_set_output_depth(p->renderer, p->glctx->depth_r, p->glctx->depth_g,
p->glctx->depth_b);
- int queue = 0;
- gl_video_set_options(p->renderer, p->renderer_opts, &queue);
- vo_set_flip_queue_params(p->vo, queue, p->renderer_opts->interpolation);
+ gl_video_set_options(p->renderer, p->renderer_opts);
+ gl_video_configure_queue(p->renderer, vo);
p->cms = gl_lcms_init(p, vo->log, vo->global);
if (!p->cms)
@@ -489,11 +425,17 @@ static int preinit(struct vo *vo)
if (!get_and_update_icc_profile(p, &(int){0}))
goto err_out;
- mpgl_unlock(p->glctx);
-
p->hwdec_info.load_api = call_request_hwdec_api;
p->hwdec_info.load_api_ctx = vo;
+ if (vo->opts->hwdec_preload_api != HWDEC_NONE) {
+ p->hwdec =
+ gl_hwdec_load_api_id(p->vo->log, p->gl, vo->opts->hwdec_preload_api);
+ gl_video_set_hwdec(p->renderer, p->hwdec);
+ if (p->hwdec)
+ p->hwdec_info.hwctx = p->hwdec->hwctx;
+ }
+
return 0;
err_out:
@@ -511,6 +453,7 @@ static const struct m_option options[] = {
OPT_FLAG("debug", use_gl_debug, 0),
OPT_STRING_VALIDATE("backend", backend, 0, mpgl_validate_backend_opt),
OPT_FLAG("sw", allow_sw, 0),
+ OPT_FLAG("es", es, 0),
OPT_INTPAIR("check-pattern", opt_pattern, 0),
OPT_SUBSTRUCT("", renderer_opts, gl_video_conf, 0),
@@ -528,8 +471,7 @@ const struct vo_driver video_out_opengl = {
.query_format = query_format,
.reconfig = reconfig,
.control = control,
- .draw_image = draw_image,
- .draw_image_timed = draw_image_timed,
+ .draw_frame = draw_frame,
.flip_page = flip_page,
.uninit = uninit,
.priv_size = sizeof(struct gl_priv),
@@ -544,8 +486,7 @@ const struct vo_driver video_out_opengl_hq = {
.query_format = query_format,
.reconfig = reconfig,
.control = control,
- .draw_image = draw_image,
- .draw_image_timed = draw_image_timed,
+ .draw_frame = draw_frame,
.flip_page = flip_page,
.uninit = uninit,
.priv_size = sizeof(struct gl_priv),
diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c
index ad8487e..fe7ed86 100644
--- a/video/out/vo_opengl_cb.c
+++ b/video/out/vo_opengl_cb.c
@@ -39,6 +39,7 @@
#define FRAME_DROP_POP 0 // drop the oldest frame in queue
#define FRAME_DROP_CLEAR 1 // drop all frames in queue
+#define FRAME_DROP_BLOCK 2
struct vo_priv {
struct vo *vo;
@@ -57,14 +58,16 @@ struct mpv_opengl_cb_context {
struct mp_client_api *client_api;
pthread_mutex_t lock;
+ pthread_cond_t wakeup;
// --- Protected by lock
bool initialized;
mpv_opengl_cb_update_fn update_cb;
void *update_cb_ctx;
- struct mp_image *waiting_frame;
- struct mp_image **frame_queue;
+ struct vo_frame *waiting_frame;
+ struct vo_frame **frame_queue;
int queued_frames;
+ struct vo_frame *cur_frame;
struct mp_image_params img_params;
bool reconfigured;
int vp_w, vp_h;
@@ -78,6 +81,10 @@ struct mpv_opengl_cb_context {
bool eq_changed;
struct mp_csp_equalizer eq;
int64_t recent_flip;
+ int64_t approx_vsync;
+ bool frozen; // libmpv user is not redrawing frames
+ struct vo *active;
+ int hwdec_api;
// --- All of these can only be accessed from the thread where the host
// application's OpenGL context is current - i.e. only while the
@@ -86,34 +93,30 @@ struct mpv_opengl_cb_context {
struct gl_video *renderer;
struct gl_hwdec *hwdec;
struct mp_hwdec_info hwdec_info; // it's also semi-immutable after init
-
- // --- Immutable or semi-threadsafe.
-
- const char *hwapi;
-
- struct vo *active;
};
static void update(struct vo_priv *p);
// all queue manipulation functions shold be called under locked state
-static struct mp_image *frame_queue_pop(struct mpv_opengl_cb_context *ctx)
+static struct vo_frame *frame_queue_pop(struct mpv_opengl_cb_context *ctx)
{
if (ctx->queued_frames == 0)
return NULL;
- struct mp_image *ret = ctx->frame_queue[0];
+ struct vo_frame *ret = ctx->frame_queue[0];
MP_TARRAY_REMOVE_AT(ctx->frame_queue, ctx->queued_frames, 0);
+ pthread_cond_broadcast(&ctx->wakeup);
return ret;
}
static void frame_queue_drop(struct mpv_opengl_cb_context *ctx)
{
- struct mp_image *mpi = frame_queue_pop(ctx);
- if (mpi) {
- talloc_free(mpi);
+ struct vo_frame *frame = frame_queue_pop(ctx);
+ if (frame) {
+ talloc_free(frame);
if (ctx->active)
vo_increment_drop_count(ctx->active, 1);
+ pthread_cond_broadcast(&ctx->wakeup);
}
}
@@ -124,6 +127,7 @@ static void frame_queue_clear(struct mpv_opengl_cb_context *ctx)
talloc_free(ctx->frame_queue);
ctx->frame_queue = NULL;
ctx->queued_frames = 0;
+ pthread_cond_broadcast(&ctx->wakeup);
}
static void frame_queue_drop_all(struct mpv_opengl_cb_context *ctx)
@@ -132,23 +136,33 @@ static void frame_queue_drop_all(struct mpv_opengl_cb_context *ctx)
frame_queue_clear(ctx);
if (ctx->active && frames > 0)
vo_increment_drop_count(ctx->active, frames);
+ pthread_cond_broadcast(&ctx->wakeup);
}
-static void frame_queue_push(struct mpv_opengl_cb_context *ctx, struct mp_image *mpi)
+static void frame_queue_push(struct mpv_opengl_cb_context *ctx,
+ struct vo_frame *frame)
{
- MP_TARRAY_APPEND(ctx, ctx->frame_queue, ctx->queued_frames, mpi);
+ MP_TARRAY_APPEND(ctx, ctx->frame_queue, ctx->queued_frames, frame);
+ pthread_cond_broadcast(&ctx->wakeup);
}
static void frame_queue_shrink(struct mpv_opengl_cb_context *ctx, int size)
{
+ pthread_cond_broadcast(&ctx->wakeup);
while (ctx->queued_frames > size)
frame_queue_drop(ctx);
}
-static void forget_frames(struct mpv_opengl_cb_context *ctx)
+static void forget_frames(struct mpv_opengl_cb_context *ctx, bool all)
{
+ pthread_cond_broadcast(&ctx->wakeup);
frame_queue_clear(ctx);
- mp_image_unrefp(&ctx->waiting_frame);
+ talloc_free(ctx->waiting_frame);
+ ctx->waiting_frame = NULL;
+ if (all) {
+ talloc_free(ctx->cur_frame);
+ ctx->cur_frame = NULL;
+ }
}
static void free_ctx(void *ptr)
@@ -159,6 +173,7 @@ static void free_ctx(void *ptr)
// mpv_opengl_cb_uninit_gl() properly.
assert(!ctx->initialized);
+ pthread_cond_destroy(&ctx->wakeup);
pthread_mutex_destroy(&ctx->lock);
}
@@ -168,19 +183,16 @@ struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g,
mpv_opengl_cb_context *ctx = talloc_zero(NULL, mpv_opengl_cb_context);
talloc_set_destructor(ctx, free_ctx);
pthread_mutex_init(&ctx->lock, NULL);
+ pthread_cond_init(&ctx->wakeup, NULL);
ctx->gl = talloc_zero(ctx, GL);
ctx->log = mp_log_new(ctx, g->log, "opengl-cb");
ctx->client_api = client_api;
- switch (g->opts->hwdec_api) {
- case HWDEC_AUTO: ctx->hwapi = "auto"; break;
- case HWDEC_VDPAU: ctx->hwapi = "vdpau"; break;
- case HWDEC_VDA: ctx->hwapi = "vda"; break;
- case HWDEC_VAAPI: ctx->hwapi = "vaapi"; break;
- default: ctx->hwapi = "";
- }
+ ctx->hwdec_api = g->opts->vo.hwdec_preload_api;
+ if (ctx->hwdec_api == HWDEC_NONE)
+ ctx->hwdec_api = g->opts->hwdec_api;
return ctx;
}
@@ -219,11 +231,11 @@ int mpv_opengl_cb_init_gl(struct mpv_opengl_cb_context *ctx, const char *exts,
mpgl_load_functions2(ctx->gl, get_proc_address, get_proc_address_ctx,
exts, ctx->log);
- ctx->renderer = gl_video_init(ctx->gl, ctx->log);
+ ctx->renderer = gl_video_init(ctx->gl, ctx->log, NULL);
if (!ctx->renderer)
return MPV_ERROR_UNSUPPORTED;
- ctx->hwdec = gl_hwdec_load_api(ctx->log, ctx->gl, ctx->hwapi);
+ ctx->hwdec = gl_hwdec_load_api_id(ctx->log, ctx->gl, ctx->hwdec_api);
gl_video_set_hwdec(ctx->renderer, ctx->hwdec);
if (ctx->hwdec)
ctx->hwdec_info.hwctx = ctx->hwdec->hwctx;
@@ -247,7 +259,7 @@ int mpv_opengl_cb_uninit_gl(struct mpv_opengl_cb_context *ctx)
// context. Setting initialized=false guarantees it can't come back.
pthread_mutex_lock(&ctx->lock);
- forget_frames(ctx);
+ forget_frames(ctx, true);
ctx->initialized = false;
pthread_mutex_unlock(&ctx->lock);
@@ -269,6 +281,16 @@ int mpv_opengl_cb_uninit_gl(struct mpv_opengl_cb_context *ctx)
return 0;
}
+// needs lock
+static int64_t prev_sync(mpv_opengl_cb_context *ctx, int64_t ts)
+{
+ int64_t diff = (int64_t)(ts - ctx->recent_flip);
+ int64_t offset = diff % ctx->approx_vsync;
+ if (offset < 0)
+ offset += ctx->approx_vsync;
+ return ts - offset;
+}
+
int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h)
{
assert(ctx->renderer);
@@ -280,6 +302,7 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h)
struct vo *vo = ctx->active;
ctx->force_update |= ctx->reconfigured;
+ ctx->frozen = false;
if (ctx->vp_w != vp_w || ctx->vp_h != vp_h)
ctx->force_update = true;
@@ -306,7 +329,8 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h)
struct vo_priv *p = vo ? vo->priv : NULL;
struct vo_priv *opts = ctx->new_opts ? ctx->new_opts : p;
if (opts) {
- gl_video_set_options(ctx->renderer, opts->renderer_opts, NULL);
+ gl_video_set_options(ctx->renderer, opts->renderer_opts);
+ gl_video_configure_queue(ctx->renderer, vo);
ctx->gl->debug_context = opts->use_gl_debug;
gl_video_set_debug(ctx->renderer, opts->use_gl_debug);
frame_queue_shrink(ctx, opts->frame_queue_size);
@@ -323,17 +347,31 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h)
ctx->eq_changed = false;
ctx->eq = *eq;
- struct mp_image *mpi = frame_queue_pop(ctx);
+ struct vo_frame *frame = frame_queue_pop(ctx);
+ if (frame) {
+ talloc_free(ctx->cur_frame);
+ ctx->cur_frame = vo_frame_ref(frame);
+ } else {
+ frame = vo_frame_ref(ctx->cur_frame);
+ }
+ struct vo_frame dummy = {0};
+ if (!frame)
+ frame = &dummy;
- pthread_mutex_unlock(&ctx->lock);
+ if (ctx->approx_vsync > 0) {
+ frame->prev_vsync = prev_sync(ctx, mp_time_us());
+ frame->next_vsync = frame->prev_vsync + ctx->approx_vsync;
+ }
- if (mpi)
- gl_video_set_image(ctx->renderer, mpi);
+ pthread_mutex_unlock(&ctx->lock);
- gl_video_render_frame(ctx->renderer, fbo, NULL);
+ gl_video_render_frame(ctx->renderer, frame, fbo);
gl_video_unset_gl_state(ctx->renderer);
+ if (frame != &dummy)
+ talloc_free(frame);
+
pthread_mutex_lock(&ctx->lock);
const int left = ctx->queued_frames;
if (vo && left > 0)
@@ -346,22 +384,15 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h)
int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time)
{
pthread_mutex_lock(&ctx->lock);
- ctx->recent_flip = time > 0 ? time : mp_time_us();
+ int64_t next = time > 0 ? time : mp_time_us();
+ if (ctx->recent_flip)
+ ctx->approx_vsync = next - ctx->recent_flip;
+ ctx->recent_flip = next;
pthread_mutex_unlock(&ctx->lock);
return 0;
}
-static void draw_image(struct vo *vo, mp_image_t *mpi)
-{
- struct vo_priv *p = vo->priv;
-
- pthread_mutex_lock(&p->ctx->lock);
- mp_image_setrefp(&p->ctx->waiting_frame, mpi);
- talloc_free(mpi);
- pthread_mutex_unlock(&p->ctx->lock);
-}
-
// Called locked.
static void update(struct vo_priv *p)
{
@@ -369,16 +400,39 @@ static void update(struct vo_priv *p)
p->ctx->update_cb(p->ctx->update_cb_ctx);
}
+static void draw_frame(struct vo *vo, struct vo_frame *frame)
+{
+ struct vo_priv *p = vo->priv;
+
+ pthread_mutex_lock(&p->ctx->lock);
+ talloc_free(p->ctx->waiting_frame);
+ p->ctx->waiting_frame = vo_frame_ref(frame);
+ pthread_mutex_unlock(&p->ctx->lock);
+}
+
static void flip_page(struct vo *vo)
{
struct vo_priv *p = vo->priv;
pthread_mutex_lock(&p->ctx->lock);
- if (p->ctx->queued_frames >= p->frame_queue_size) {
- if (p->frame_drop_mode == FRAME_DROP_CLEAR)
+ while (p->ctx->queued_frames >= p->frame_queue_size) {
+ switch (p->frame_drop_mode) {
+ case FRAME_DROP_CLEAR:
frame_queue_drop_all(p->ctx);
- else // FRAME_DROP_POP mode
+ break;
+ case FRAME_DROP_POP:
frame_queue_shrink(p->ctx, p->frame_queue_size - 1);
+ break;
+ case FRAME_DROP_BLOCK: ;
+ struct timespec ts = mp_rel_time_to_timespec(0.2);
+ if (p->ctx->frozen ||
+ pthread_cond_timedwait(&p->ctx->wakeup, &p->ctx->lock, &ts))
+ {
+ frame_queue_drop_all(p->ctx);
+ p->ctx->frozen = true;
+ }
+ break;
+ }
}
frame_queue_push(p->ctx, p->ctx->waiting_frame);
p->ctx->waiting_frame = NULL;
@@ -403,7 +457,7 @@ static int reconfig(struct vo *vo, struct mp_image_params *params, int flags)
struct vo_priv *p = vo->priv;
pthread_mutex_lock(&p->ctx->lock);
- forget_frames(p->ctx);
+ forget_frames(p->ctx, true);
p->ctx->img_params = *params;
p->ctx->reconfigured = true;
pthread_mutex_unlock(&p->ctx->lock);
@@ -418,7 +472,8 @@ static const struct m_option change_opts[] = {
OPT_INTRANGE("frame-queue-size", frame_queue_size, 0, 1, 100, OPTDEF_INT(2)),
OPT_CHOICE("frame-drop-mode", frame_drop_mode, 0,
({"pop", FRAME_DROP_POP},
- {"clear", FRAME_DROP_CLEAR})),
+ {"clear", FRAME_DROP_CLEAR},
+ {"block", FRAME_DROP_BLOCK})),
OPT_SUBSTRUCT("", renderer_opts, gl_video_conf, 0),
{0}
};
@@ -475,11 +530,6 @@ static int control(struct vo *vo, uint32_t request, void *data)
pthread_mutex_unlock(&p->ctx->lock);
return r ? VO_TRUE : VO_NOTIMPL;
}
- case VOCTRL_REDRAW_FRAME:
- pthread_mutex_lock(&p->ctx->lock);
- update(p);
- pthread_mutex_unlock(&p->ctx->lock);
- return VO_TRUE;
case VOCTRL_SET_PANSCAN:
pthread_mutex_lock(&p->ctx->lock);
copy_vo_opts(vo);
@@ -516,7 +566,7 @@ static void uninit(struct vo *vo)
struct vo_priv *p = vo->priv;
pthread_mutex_lock(&p->ctx->lock);
- forget_frames(p->ctx);
+ forget_frames(p->ctx, true);
p->ctx->img_params = (struct mp_image_params){0};
p->ctx->reconfigured = true;
p->ctx->active = NULL;
@@ -555,7 +605,8 @@ static const struct m_option options[] = {
OPT_INTRANGE("frame-queue-size", frame_queue_size, 0, 1, 100, OPTDEF_INT(2)),
OPT_CHOICE("frame-drop-mode", frame_drop_mode, 0,
({"pop", FRAME_DROP_POP},
- {"clear", FRAME_DROP_CLEAR})),
+ {"clear", FRAME_DROP_CLEAR},
+ {"block", FRAME_DROP_BLOCK}), OPTDEF_INT(FRAME_DROP_BLOCK)),
OPT_SUBSTRUCT("", renderer_opts, gl_video_conf, 0),
{0},
};
@@ -568,7 +619,7 @@ const struct vo_driver video_out_opengl_cb = {
.query_format = query_format,
.reconfig = reconfig,
.control = control,
- .draw_image = draw_image,
+ .draw_frame = draw_frame,
.flip_page = flip_page,
.uninit = uninit,
.priv_size = sizeof(struct vo_priv),
diff --git a/video/out/vo_rpi.c b/video/out/vo_rpi.c
index f144deb..d637eec 100644
--- a/video/out/vo_rpi.c
+++ b/video/out/vo_rpi.c
@@ -38,38 +38,30 @@
#include "vo.h"
#include "video/mp_image.h"
#include "sub/osd.h"
-#include "sub/img_convert.h"
+#include "gl_osd.h"
-// In theory, the number of RGBA subbitmaps the OSD code could give us is
-// unlimited; but in practice there will be rarely many elements.
-#define MAX_OSD_ELEMS MP_SUB_BB_LIST_MAX
-
-struct osd_elem {
- DISPMANX_RESOURCE_HANDLE_T resource;
- DISPMANX_ELEMENT_HANDLE_T element;
-};
-
-struct osd_part {
- struct osd_elem elems[MAX_OSD_ELEMS];
- int num_elems;
- int change_id;
- bool needed;
-};
+#include "gl_rpi.h"
struct priv {
DISPMANX_DISPLAY_HANDLE_T display;
DISPMANX_ELEMENT_HANDLE_T window;
- DISPMANX_RESOURCE_HANDLE_T window_back;
+ DISPMANX_ELEMENT_HANDLE_T osd_overlay;
DISPMANX_UPDATE_HANDLE_T update;
uint32_t w, h;
+ double display_fps;
- struct osd_part osd_parts[MAX_OSD_PARTS];
double osd_pts;
struct mp_osd_res osd_res;
+ struct mp_egl_rpi egl;
+ struct gl_shader_cache *sc;
+ struct mpgl_osd *osd;
+ int64_t osd_change_counter;
+
MMAL_COMPONENT_T *renderer;
bool renderer_enabled;
+ bool display_synced, skip_osd;
struct mp_image *next_image;
// for RAM input
@@ -77,12 +69,17 @@ struct priv {
atomic_bool update_display;
+ pthread_mutex_t vsync_mutex;
+ pthread_cond_t vsync_cond;
+ int64_t vsync_counter;
+
int background_layer;
int video_layer;
int osd_layer;
int display_nr;
int layer;
+ int background;
};
// Magic alignments (in pixels) expected by the MMAL internals.
@@ -113,108 +110,48 @@ static size_t layout_buffer(struct mp_image *mpi, MMAL_BUFFER_HEADER_T *buffer,
return size;
}
-static void wipe_osd_part(struct vo *vo, struct osd_part *part)
-{
- struct priv *p = vo->priv;
-
- for (int n = 0; n < part->num_elems; n++) {
- vc_dispmanx_element_remove(p->update, part->elems[n].element);
- vc_dispmanx_resource_delete(part->elems[n].resource);
- }
- part->num_elems = 0;
- part->change_id = -1;
-}
-
-static void wipe_osd(struct vo *vo)
-{
- struct priv *p = vo->priv;
-
- for (int x = 0; x < MAX_OSD_PARTS; x++)
- wipe_osd_part(vo, &p->osd_parts[x]);
-}
-
-static int add_element(struct vo *vo, struct osd_part *part, int index,
- struct sub_bitmap *sub)
-{
- struct priv *p = vo->priv;
- VC_IMAGE_TYPE_T format = VC_IMAGE_ARGB8888; // assuming RPI is always LE
-
- struct osd_elem *elem = &part->elems[index];
- *elem = (struct osd_elem){0};
-
- // I have no idea why stride must be passed in such a hacky way. It's not
- // documented. Other software does it too. Other software claims aligning
- // the width and "probably" the height is required too, but for me it works
- // just fine without on rpi2. (See Weston's rpi renderer.)
- elem->resource = vc_dispmanx_resource_create(format,
- sub->w | (sub->stride << 16),
- sub->h,
- &(int32_t){0});
- if (!elem->resource) {
- MP_ERR(vo, "Could not create %dx%d sub-bitmap\n", sub->w, sub->h);
- return -1;
- }
-
- VC_RECT_T rc = {.width = sub->w, .height = sub->h};
- vc_dispmanx_resource_write_data(elem->resource, format,
- sub->stride, sub->bitmap, &rc);
- VC_RECT_T src = {.width = sub->w << 16, .height = sub->h << 16};
- VC_RECT_T dst = {.x = sub->x, .y = sub->y, .width = sub->dw, .height = sub->dh};
- VC_DISPMANX_ALPHA_T alpha = {
- .flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE | DISPMANX_FLAGS_ALPHA_PREMULT,
- .opacity = 0xFF,
- };
- elem->element = vc_dispmanx_element_add(p->update, p->display, p->osd_layer,
- &dst, elem->resource, &src,
- DISPMANX_PROTECTION_NONE,
- &alpha, 0, 0);
- if (!elem->element) {
- MP_ERR(vo, "Could not create sub-bitmap element\n");
- return -1;
- }
-
- return 0;
-}
+#define GLSL(x) gl_sc_add(p->sc, #x "\n");
+#define GLSLF(...) gl_sc_addf(p->sc, __VA_ARGS__)
-static void osd_draw_cb(void *ctx, struct sub_bitmaps *imgs)
+static void update_osd(struct vo *vo)
{
- struct vo *vo = ctx;
struct priv *p = vo->priv;
- struct osd_part *part = &p->osd_parts[imgs->render_index];
- part->needed = true;
+ mpgl_osd_generate(p->osd, p->osd_res, p->osd_pts, 0, 0);
- if (imgs->change_id == part->change_id)
+ int64_t osd_change_counter = mpgl_get_change_counter(p->osd);
+ if (p->osd_change_counter == osd_change_counter) {
+ p->skip_osd = true;
return;
-
- wipe_osd_part(vo, part);
- part->change_id = imgs->change_id;
-
- for (int n = 0; n < imgs->num_parts; n++) {
- if (part->num_elems == MAX_OSD_ELEMS) {
- MP_ERR(vo, "Too many OSD elements.\n");
+ }
+ p->osd_change_counter = osd_change_counter;
+
+ p->egl.gl->ClearColor(0, 0, 0, 0);
+ p->egl.gl->Clear(GL_COLOR_BUFFER_BIT);
+
+ for (int n = 0; n < MAX_OSD_PARTS; n++) {
+ enum sub_bitmap_format fmt = mpgl_osd_get_part_format(p->osd, n);
+ if (!fmt)
+ continue;
+ gl_sc_uniform_sampler(p->sc, "osdtex", GL_TEXTURE_2D, 0);
+ switch (fmt) {
+ case SUBBITMAP_RGBA: {
+ GLSLF("// OSD (RGBA)\n");
+ GLSL(vec4 color = texture(osdtex, texcoord).bgra;)
break;
}
- int index = part->num_elems++;
- if (add_element(vo, part, index, &imgs->parts[n]) < 0)
+ case SUBBITMAP_LIBASS: {
+ GLSLF("// OSD (libass)\n");
+ GLSL(vec4 color =
+ vec4(ass_color.rgb, ass_color.a * texture(osdtex, texcoord).r);)
break;
- }
-}
-
-static void update_osd(struct vo *vo)
-{
- struct priv *p = vo->priv;
-
- for (int x = 0; x < MAX_OSD_PARTS; x++)
- p->osd_parts[x].needed = false;
-
- static const bool formats[SUBBITMAP_COUNT] = {[SUBBITMAP_RGBA] = true};
- osd_draw(vo->osd, p->osd_res, p->osd_pts, 0, formats, osd_draw_cb, vo);
-
- for (int x = 0; x < MAX_OSD_PARTS; x++) {
- struct osd_part *part = &p->osd_parts[x];
- if (!part->needed)
- wipe_osd_part(vo, part);
+ }
+ default:
+ abort();
+ }
+ gl_sc_set_vao(p->sc, mpgl_osd_get_vao(p->osd));
+ gl_sc_gen_shader_and_reset(p->sc);
+ mpgl_osd_draw_part(p->osd, p->w, -p->h, n);
}
}
@@ -244,6 +181,25 @@ static void resize(struct vo *vo)
MP_WARN(vo, "could not set video rectangle\n");
}
+static void destroy_overlays(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+
+ if (p->window)
+ vc_dispmanx_element_remove(p->update, p->window);
+ p->window = 0;
+
+ mpgl_osd_destroy(p->osd);
+ p->osd = NULL;
+ gl_sc_destroy(p->sc);
+ p->sc = NULL;
+ mp_egl_rpi_destroy(&p->egl);
+
+ if (p->osd_overlay)
+ vc_dispmanx_element_remove(p->update, p->osd_overlay);
+ p->osd_overlay = 0;
+}
+
static int update_display_size(struct vo *vo)
{
struct priv *p = vo->priv;
@@ -262,24 +218,7 @@ static int update_display_size(struct vo *vo)
MP_VERBOSE(vo, "Display size: %dx%d\n", p->w, p->h);
- if (p->window)
- vc_dispmanx_element_remove(p->update, p->window);
- if (p->window_back)
- vc_dispmanx_resource_delete(p->window_back);
- p->window = 0;
- p->window_back = 0;
-
- VC_IMAGE_TYPE_T format = VC_IMAGE_ARGB8888; // assuming RPI is always LE
- p->window_back = vc_dispmanx_resource_create(format, 1 | (4 << 16), 1,
- &(int32_t){0});
- if (!p->window_back) {
- MP_ERR(vo, "Could not create background bitmap.\n");
- return -1;
- }
-
- uint32_t px = 0;
- VC_RECT_T rc = {.width = 1, .height = 1};
- vc_dispmanx_resource_write_data(p->window_back, format, 4, &px, &rc);
+ destroy_overlays(vo);
// Use the whole screen.
VC_RECT_T dst = {.width = p->w, .height = p->h};
@@ -288,20 +227,77 @@ static int update_display_size(struct vo *vo)
.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS,
.opacity = 0xFF,
};
- p->window = vc_dispmanx_element_add(p->update, p->display, p->background_layer,
- &dst, p->window_back, &src,
- DISPMANX_PROTECTION_NONE, &alpha, 0, 0);
- if (!p->window) {
+
+ if (p->background) {
+ p->window = vc_dispmanx_element_add(p->update, p->display,
+ p->background_layer,
+ &dst, 0, &src,
+ DISPMANX_PROTECTION_NONE,
+ &alpha, 0, 0);
+ if (!p->window) {
+ MP_FATAL(vo, "Could not add DISPMANX element.\n");
+ return -1;
+ }
+ }
+
+ alpha = (VC_DISPMANX_ALPHA_T){
+ .flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE,
+ .opacity = 0xFF,
+ };
+ p->osd_overlay = vc_dispmanx_element_add(p->update, p->display,
+ p->osd_layer,
+ &dst, 0, &src,
+ DISPMANX_PROTECTION_NONE,
+ &alpha, 0, 0);
+ if (!p->osd_overlay) {
MP_FATAL(vo, "Could not add DISPMANX element.\n");
return -1;
}
+ if (mp_egl_rpi_init(&p->egl, p->osd_overlay, p->w, p->h) < 0) {
+ MP_FATAL(vo, "EGL/GLES initialization for OSD renderer failed.\n");
+ return -1;
+ }
+ p->sc = gl_sc_create(p->egl.gl, vo->log, vo->global),
+ p->osd = mpgl_osd_init(p->egl.gl, vo->log, vo->osd);
+ p->osd_change_counter = -1; // force initial overlay rendering
+
+ p->display_fps = 0;
+ TV_GET_STATE_RESP_T tvstate;
+ TV_DISPLAY_STATE_T tvstate_disp;
+ if (!vc_tv_get_state(&tvstate) && !vc_tv_get_display_state(&tvstate_disp)) {
+ if (tvstate_disp.state & (VC_HDMI_HDMI | VC_HDMI_DVI)) {
+ p->display_fps = tvstate_disp.display.hdmi.frame_rate;
+
+ HDMI_PROPERTY_PARAM_T param = {
+ .property = HDMI_PROPERTY_PIXEL_CLOCK_TYPE,
+ };
+ if (!vc_tv_hdmi_get_property(&param) &&
+ param.param1 == HDMI_PIXEL_CLOCK_TYPE_NTSC)
+ p->display_fps = p->display_fps / 1.001;
+ } else {
+ p->display_fps = tvstate_disp.display.sdtv.frame_rate;
+ }
+ }
+
+ vo_event(vo, VO_EVENT_WIN_STATE);
+
vc_dispmanx_update_submit_sync(p->update);
p->update = vc_dispmanx_update_start(10);
return 0;
}
+static void wait_next_vsync(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ pthread_mutex_lock(&p->vsync_mutex);
+ int64_t old = p->vsync_counter;
+ while (old == p->vsync_counter)
+ pthread_cond_wait(&p->vsync_cond, &p->vsync_mutex);
+ pthread_mutex_unlock(&p->vsync_mutex);
+}
+
static void flip_page(struct vo *vo)
{
struct priv *p = vo->priv;
@@ -309,8 +305,9 @@ static void flip_page(struct vo *vo)
p->next_image = NULL;
// For OSD
- vc_dispmanx_update_submit_sync(p->update);
- p->update = vc_dispmanx_update_start(10);
+ if (!p->skip_osd && p->egl.gl)
+ eglSwapBuffers(p->egl.egl_display, p->egl.egl_surface);
+ p->skip_osd = false;
if (mpi) {
MMAL_PORT_T *input = p->renderer->input[0];
@@ -324,6 +321,9 @@ static void flip_page(struct vo *vo)
talloc_free(mpi);
}
}
+
+ if (p->display_synced)
+ wait_next_vsync(vo);
}
static void free_mmal_buffer(void *arg)
@@ -332,17 +332,30 @@ static void free_mmal_buffer(void *arg)
mmal_buffer_header_release(buffer);
}
-static void draw_image(struct vo *vo, mp_image_t *mpi)
+static void draw_frame(struct vo *vo, struct vo_frame *frame)
{
struct priv *p = vo->priv;
+ mp_image_t *mpi = NULL;
+ if (!frame->redraw && !frame->repeat)
+ mpi = mp_image_new_ref(frame->current);
+
talloc_free(p->next_image);
p->next_image = NULL;
- p->osd_pts = mpi->pts;
- update_osd(vo);
+ if (mpi)
+ p->osd_pts = mpi->pts;
+
+ // Redraw only if the OSD has meaningfully changed, which we assume it
+ // hasn't when a frame is merely repeated for display sync.
+ p->skip_osd = !frame->redraw && frame->repeat;
+
+ if (!p->skip_osd && p->egl.gl)
+ update_osd(vo);
+
+ p->display_synced = frame->display_synced;
- if (vo->params->imgfmt != IMGFMT_MMAL) {
+ if (mpi && mpi->imgfmt != IMGFMT_MMAL) {
MMAL_BUFFER_HEADER_T *buffer = mmal_queue_wait(p->swpool->queue);
if (!buffer) {
talloc_free(mpi);
@@ -522,8 +535,10 @@ static int control(struct vo *vo, uint32_t request, void *data)
case VOCTRL_SET_PANSCAN:
if (p->renderer_enabled)
resize(vo);
+ vo->want_redraw = true;
return VO_TRUE;
case VOCTRL_REDRAW_FRAME:
+ p->osd_change_counter = -1;
update_osd(vo);
return VO_TRUE;
case VOCTRL_SCREENSHOT_WIN:
@@ -537,6 +552,9 @@ static int control(struct vo *vo, uint32_t request, void *data)
resize(vo);
}
return VO_TRUE;
+ case VOCTRL_GET_DISPLAY_FPS:
+ *(double *)data = p->display_fps;
+ return VO_TRUE;
}
return VO_NOTIMPL;
@@ -551,6 +569,16 @@ static void tv_callback(void *callback_data, uint32_t reason, uint32_t param1,
vo_wakeup(vo);
}
+static void vsync_callback(DISPMANX_UPDATE_HANDLE_T u, void *arg)
+{
+ struct vo *vo = arg;
+ struct priv *p = vo->priv;
+ pthread_mutex_lock(&p->vsync_mutex);
+ p->vsync_counter += 1;
+ pthread_cond_signal(&p->vsync_cond);
+ pthread_mutex_unlock(&p->vsync_mutex);
+}
+
static void uninit(struct vo *vo)
{
struct priv *p = vo->priv;
@@ -559,12 +587,7 @@ static void uninit(struct vo *vo)
talloc_free(p->next_image);
- wipe_osd(vo);
-
- if (p->window)
- vc_dispmanx_element_remove(p->update, p->window);
- if (p->window_back)
- vc_dispmanx_resource_delete(p->window_back);
+ destroy_overlays(vo);
if (p->update)
vc_dispmanx_update_submit_sync(p->update);
@@ -574,10 +597,15 @@ static void uninit(struct vo *vo)
mmal_component_release(p->renderer);
}
- if (p->display)
+ if (p->display) {
+ vc_dispmanx_vsync_callback(p->display, NULL, NULL);
vc_dispmanx_display_close(p->display);
+ }
mmal_vc_deinit();
+
+ pthread_cond_destroy(&p->vsync_cond);
+ pthread_mutex_destroy(&p->vsync_mutex);
}
static int preinit(struct vo *vo)
@@ -588,6 +616,8 @@ static int preinit(struct vo *vo)
p->video_layer = p->layer + 1;
p->osd_layer = p->layer + 2;
+ p->egl.log = vo->log;
+
bcm_host_init();
if (mmal_vc_init()) {
@@ -613,6 +643,11 @@ static int preinit(struct vo *vo)
vc_tv_register_callback(tv_callback, vo);
+ pthread_mutex_init(&p->vsync_mutex, NULL);
+ pthread_cond_init(&p->vsync_cond, NULL);
+
+ vc_dispmanx_vsync_callback(p->display, vsync_callback, vo);
+
return 0;
fail:
@@ -624,6 +659,7 @@ fail:
static const struct m_option options[] = {
OPT_INT("display", display_nr, 0),
OPT_INT("layer", layer, 0, OPTDEF_INT(-10)),
+ OPT_FLAG("background", background, 0),
{0},
};
@@ -634,7 +670,7 @@ const struct vo_driver video_out_rpi = {
.query_format = query_format,
.reconfig = reconfig,
.control = control,
- .draw_image = draw_image,
+ .draw_frame = draw_frame,
.flip_page = flip_page,
.uninit = uninit,
.priv_size = sizeof(struct priv),
diff --git a/video/out/vo_sdl.c b/video/out/vo_sdl.c
index 03a0484..d465902 100644
--- a/video/out/vo_sdl.c
+++ b/video/out/vo_sdl.c
@@ -907,6 +907,8 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
mp_image_copy(&texmpi, mpi);
SDL_UnlockTexture(vc->tex);
+
+ talloc_free(mpi);
}
SDL_Rect src, dst;
diff --git a/video/out/vo_vaapi.c b/video/out/vo_vaapi.c
index b34acb1..d5b035e 100644
--- a/video/out/vo_vaapi.c
+++ b/video/out/vo_vaapi.c
@@ -82,16 +82,12 @@ struct priv {
int visible_surface;
int scaling;
int force_scaled_osd;
- // with old libva versions only
- int deint;
- int deint_type;
VAImageFormat osd_format; // corresponds to OSD_VA_FORMAT
struct vaapi_osd_part osd_parts[MAX_OSD_PARTS];
bool osd_screen;
struct mp_image_pool *pool;
- struct va_image_formats *va_image_formats;
struct mp_image *black_surface;
@@ -207,7 +203,6 @@ static bool render_to_screen(struct priv *p, struct mp_image *mpi)
surface = va_surface_id(p->black_surface);
}
- int fields = mpi ? mpi->fields : 0;
if (surface == VA_INVALID_ID)
return false;
@@ -220,23 +215,19 @@ static bool render_to_screen(struct priv *p, struct mp_image *mpi)
int flags = 0;
if (p->osd_screen)
flags |= VA_SUBPICTURE_DESTINATION_IS_SCREEN_COORD;
- status = vaAssociateSubpicture2(p->display,
- sp->id, &surface, 1,
- sp->src_x, sp->src_y,
- sp->src_w, sp->src_h,
- sp->dst_x, sp->dst_y,
- sp->dst_w, sp->dst_h,
- flags);
+ status = vaAssociateSubpicture(p->display,
+ sp->id, &surface, 1,
+ sp->src_x, sp->src_y,
+ sp->src_w, sp->src_h,
+ sp->dst_x, sp->dst_y,
+ sp->dst_w, sp->dst_h,
+ flags);
CHECK_VA_STATUS(p, "vaAssociateSubpicture()");
}
}
- int flags = va_get_colorspace_flag(p->image_params.colorspace) | p->scaling;
- if (p->deint && (fields & MP_IMGFIELD_INTERLACED)) {
- flags |= (fields & MP_IMGFIELD_TOP_FIRST) ? VA_BOTTOM_FIELD : VA_TOP_FIELD;
- } else {
- flags |= VA_FRAME_PICTURE;
- }
+ int flags = va_get_colorspace_flag(p->image_params.colorspace) |
+ p->scaling | VA_FRAME_PICTURE;
status = vaPutSurface(p->display,
surface,
p->vo->x11->window,
@@ -524,16 +515,6 @@ static int control(struct vo *vo, uint32_t request, void *data)
struct priv *p = vo->priv;
switch (request) {
- case VOCTRL_GET_DEINTERLACE:
- if (!p->deint_type)
- break;
- *(int*)data = !!p->deint;
- return VO_TRUE;
- case VOCTRL_SET_DEINTERLACE:
- if (!p->deint_type)
- break;
- p->deint = *(int*)data ? p->deint_type : 0;
- return VO_TRUE;
case VOCTRL_GET_HWDEC_INFO: {
struct mp_hwdec_info **arg = data;
*arg = &p->hwdec_info;
@@ -600,7 +581,7 @@ static int preinit(struct vo *vo)
if (!p->display)
goto fail;
- p->mpvaapi = va_initialize(p->display, p->log);
+ p->mpvaapi = va_initialize(p->display, p->log, false);
if (!p->mpvaapi) {
vaTerminate(p->display);
p->display = NULL;
@@ -616,7 +597,6 @@ static int preinit(struct vo *vo)
p->pool = mp_image_pool_new(MAX_OUTPUT_SURFACES + 3);
va_pool_set_allocator(p->pool, p->mpvaapi, VA_RT_FORMAT_YUV420);
- p->va_image_formats = p->mpvaapi->image_formats;
int max_subpic_formats = vaMaxNumSubpictureFormats(p->display);
p->va_subpic_formats = talloc_array(vo, VAImageFormat, max_subpic_formats);
@@ -684,23 +664,13 @@ const struct vo_driver video_out_vaapi = {
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.scaling = VA_FILTER_SCALING_DEFAULT,
- .deint = 0,
-#if !HAVE_VAAPI_VPP
- .deint_type = 2,
-#endif
},
.options = (const struct m_option[]) {
-#if USE_VAAPI_SCALING
OPT_CHOICE("scaling", scaling, 0,
({"default", VA_FILTER_SCALING_DEFAULT},
{"fast", VA_FILTER_SCALING_FAST},
{"hq", VA_FILTER_SCALING_HQ},
{"nla", VA_FILTER_SCALING_NL_ANAMORPHIC})),
-#endif
- OPT_CHOICE("deint", deint_type, 0,
- ({"no", 0},
- {"first-field", 1},
- {"bob", 2})),
OPT_FLAG("scaled-osd", force_scaled_osd, 0),
{0}
},
diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c
index b92a7f2..20457b6 100644
--- a/video/out/vo_vdpau.c
+++ b/video/out/vo_vdpau.c
@@ -81,10 +81,13 @@ struct vdpctx {
VdpOutputSurface output_surfaces[MAX_OUTPUT_SURFACES];
int num_output_surfaces;
VdpOutputSurface black_pixel;
+ VdpOutputSurface rotation_surface;
struct mp_image *current_image;
+ int64_t current_pts;
+ int current_duration;
- int output_surface_width, output_surface_height;
+ int output_surface_w, output_surface_h;
int force_yuv;
struct mp_vdpau_mixer *video_mixer;
@@ -107,7 +110,7 @@ struct vdpctx {
VdpTime recent_vsync_time;
float user_fps;
int composite_detect;
- unsigned int vsync_interval;
+ int vsync_interval;
uint64_t last_queue_time;
uint64_t queue_time[MAX_OUTPUT_SURFACES];
uint64_t last_ideal_time;
@@ -115,7 +118,6 @@ struct vdpctx {
uint64_t dropped_time;
uint32_t vid_width, vid_height;
uint32_t image_format;
- VdpChromaType vdp_chroma_type;
VdpYCbCrFormat vdp_pixel_format;
bool rgb_mode;
@@ -164,7 +166,7 @@ static int render_video_to_output_surface(struct vo *vo,
// Clear the borders between video and window (if there are any).
// For some reason, video_mixer_render doesn't need it for YUV.
// Also, if there is nothing to render, at least clear the screen.
- if (vc->rgb_mode || !mpi) {
+ if (vc->rgb_mode || !mpi || mpi->params.rotate != 0) {
int flags = VDP_OUTPUT_SURFACE_RENDER_ROTATE_0;
vdp_st = vdp->output_surface_render_output_surface(output_surface,
NULL, vc->black_pixel,
@@ -189,8 +191,50 @@ static int render_video_to_output_surface(struct vo *vo,
if (vc->hqscaling)
opts.hqscaling = vc->hqscaling;
- mp_vdpau_mixer_render(vc->video_mixer, &opts, output_surface, output_rect,
- mpi, video_rect);
+ if (mpi->params.rotate != 0) {
+ int flags;
+ VdpRect r_rect;
+ switch (mpi->params.rotate) {
+ case 90:
+ r_rect.y0 = output_rect->x0;
+ r_rect.y1 = output_rect->x1;
+ r_rect.x0 = output_rect->y0;
+ r_rect.x1 = output_rect->y1;
+ flags = VDP_OUTPUT_SURFACE_RENDER_ROTATE_90;
+ break;
+ case 180:
+ r_rect.x0 = output_rect->x0;
+ r_rect.x1 = output_rect->x1;
+ r_rect.y0 = output_rect->y0;
+ r_rect.y1 = output_rect->y1;
+ flags = VDP_OUTPUT_SURFACE_RENDER_ROTATE_180;
+ break;
+ case 270:
+ r_rect.y0 = output_rect->x0;
+ r_rect.y1 = output_rect->x1;
+ r_rect.x0 = output_rect->y0;
+ r_rect.x1 = output_rect->y1;
+ flags = VDP_OUTPUT_SURFACE_RENDER_ROTATE_270;
+ break;
+ default:
+ MP_ERR(vo, "Unsupported rotation angle: %u\n", mpi->params.rotate);
+ return -1;
+ }
+
+ mp_vdpau_mixer_render(vc->video_mixer, &opts, vc->rotation_surface,
+ &r_rect, mpi, video_rect);
+ vdp_st = vdp->output_surface_render_output_surface(output_surface,
+ output_rect,
+ vc->rotation_surface,
+ &r_rect,
+ NULL,
+ NULL,
+ flags);
+ CHECK_VDP_WARNING(vo, "Error rendering rotated frame");
+ } else {
+ mp_vdpau_mixer_render(vc->video_mixer, &opts, output_surface,
+ output_rect, mpi, video_rect);
+ }
return 0;
}
@@ -215,11 +259,11 @@ static void forget_frames(struct vo *vo, bool seek_reset)
vc->dropped_frame = false;
}
-static int s_size(int s, int disp)
+static int s_size(int max, int s, int disp)
{
disp = MPMAX(1, disp);
s += s / 2;
- return s >= disp ? s : disp;
+ return MPMIN(max, s >= disp ? s : disp);
}
static void resize(struct vo *vo)
@@ -234,20 +278,34 @@ static void resize(struct vo *vo)
vc->out_rect_vid.x1 = dst_rect.x1;
vc->out_rect_vid.y0 = dst_rect.y0;
vc->out_rect_vid.y1 = dst_rect.y1;
- vc->src_rect_vid.x0 = src_rect.x0;
- vc->src_rect_vid.x1 = src_rect.x1;
- vc->src_rect_vid.y0 = src_rect.y0;
- vc->src_rect_vid.y1 = src_rect.y1;
+ if (vo->params->rotate == 90 || vo->params->rotate == 270) {
+ vc->src_rect_vid.y0 = src_rect.x0;
+ vc->src_rect_vid.y1 = src_rect.x1;
+ vc->src_rect_vid.x0 = src_rect.y0;
+ vc->src_rect_vid.x1 = src_rect.y1;
+ } else {
+ vc->src_rect_vid.x0 = src_rect.x0;
+ vc->src_rect_vid.x1 = src_rect.x1;
+ vc->src_rect_vid.y0 = src_rect.y0;
+ vc->src_rect_vid.y1 = src_rect.y1;
+ }
+
+ VdpBool ok;
+ uint32_t max_w, max_h;
+ vdp_st = vdp->output_surface_query_capabilities(vc->vdp_device,
+ OUTPUT_RGBA_FORMAT,
+ &ok, &max_w, &max_h);
+ if (vdp_st != VDP_STATUS_OK || !ok)
+ return;
vc->flip_offset_us = vo->opts->fullscreen ?
1000LL * vc->flip_offset_fs :
1000LL * vc->flip_offset_window;
- vo_set_flip_queue_params(vo, vc->flip_offset_us, false);
+ vo_set_queue_params(vo, vc->flip_offset_us, false, 1);
- if (vc->output_surface_width < vo->dwidth
- || vc->output_surface_height < vo->dheight) {
- vc->output_surface_width = s_size(vc->output_surface_width, vo->dwidth);
- vc->output_surface_height = s_size(vc->output_surface_height, vo->dheight);
+ if (vc->output_surface_w < vo->dwidth || vc->output_surface_h < vo->dheight) {
+ vc->output_surface_w = s_size(max_w, vc->output_surface_w, vo->dwidth);
+ vc->output_surface_h = s_size(max_h, vc->output_surface_h, vo->dheight);
// Creation of output_surfaces
for (int i = 0; i < vc->num_output_surfaces; i++)
if (vc->output_surfaces[i] != VDP_INVALID_HANDLE) {
@@ -258,13 +316,34 @@ static void resize(struct vo *vo)
for (int i = 0; i < vc->num_output_surfaces; i++) {
vdp_st = vdp->output_surface_create(vc->vdp_device,
OUTPUT_RGBA_FORMAT,
- vc->output_surface_width,
- vc->output_surface_height,
+ vc->output_surface_w,
+ vc->output_surface_h,
&vc->output_surfaces[i]);
CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_create");
MP_DBG(vo, "vdpau out create: %u\n",
vc->output_surfaces[i]);
}
+ if (vc->rotation_surface != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->output_surface_destroy(vc->rotation_surface);
+ CHECK_VDP_WARNING(vo, "Error when calling "
+ "vdp_output_surface_destroy");
+ }
+ if (vo->params->rotate == 90 || vo->params->rotate == 270) {
+ vdp_st = vdp->output_surface_create(vc->vdp_device,
+ OUTPUT_RGBA_FORMAT,
+ vc->output_surface_h,
+ vc->output_surface_w,
+ &vc->rotation_surface);
+ } else if (vo->params->rotate == 180) {
+ vdp_st = vdp->output_surface_create(vc->vdp_device,
+ OUTPUT_RGBA_FORMAT,
+ vc->output_surface_w,
+ vc->output_surface_h,
+ &vc->rotation_surface);
+ }
+ CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_create");
+ MP_DBG(vo, "vdpau rotation surface create: %u\n",
+ vc->rotation_surface);
}
vo->want_redraw = true;
}
@@ -339,10 +418,8 @@ static int initialize_vdpau_objects(struct vo *vo)
struct vdp_functions *vdp = vc->vdp;
VdpStatus vdp_st;
- mp_vdpau_get_format(vc->image_format, &vc->vdp_chroma_type,
- &vc->vdp_pixel_format);
+ mp_vdpau_get_format(vc->image_format, NULL, &vc->vdp_pixel_format);
- vc->video_mixer->chroma_type = vc->vdp_chroma_type;
vc->video_mixer->initialized = false;
if (win_x11_init_vdpau_flip_queue(vo) < 0)
@@ -375,6 +452,7 @@ static void mark_vdpau_objects_uninitialized(struct vo *vo)
vc->flip_target = VDP_INVALID_HANDLE;
for (int i = 0; i < MAX_OUTPUT_SURFACES; i++)
vc->output_surfaces[i] = VDP_INVALID_HANDLE;
+ vc->rotation_surface = VDP_INVALID_HANDLE;
vc->vdp_device = VDP_INVALID_HANDLE;
for (int i = 0; i < MAX_OSD_PARTS; i++) {
struct osd_bitmap_surface *sfc = &vc->osd_surfaces[i];
@@ -384,7 +462,7 @@ static void mark_vdpau_objects_uninitialized(struct vo *vo)
.surface = VDP_INVALID_HANDLE,
};
}
- vc->output_surface_width = vc->output_surface_height = -1;
+ vc->output_surface_w = vc->output_surface_h = -1;
}
static bool check_preemption(struct vo *vo)
@@ -415,10 +493,29 @@ static bool status_ok(struct vo *vo)
static int reconfig(struct vo *vo, struct mp_image_params *params, int flags)
{
struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+ VdpStatus vdp_st;
if (!check_preemption(vo))
return -1;
+ VdpChromaType chroma_type = VDP_CHROMA_TYPE_420;
+ mp_vdpau_get_format(params->imgfmt, &chroma_type, NULL);
+
+ VdpBool ok;
+ uint32_t max_w, max_h;
+ vdp_st = vdp->video_surface_query_capabilities(vc->vdp_device, chroma_type,
+ &ok, &max_w, &max_h);
+ CHECK_VDP_ERROR(vo, "Error when calling vdp_video_surface_query_capabilities");
+
+ if (!ok)
+ return -1;
+ if (params->w > max_w || params->h > max_h) {
+ if (ok)
+ MP_ERR(vo, "Video too large for vdpau.\n");
+ return -1;
+ }
+
vc->image_format = params->imgfmt;
vc->vid_width = params->w;
vc->vid_height = params->h;
@@ -678,16 +775,19 @@ static inline uint64_t prev_vsync(struct vdpctx *vc, uint64_t ts)
return ts - offset;
}
-static int flip_page_timed(struct vo *vo, int64_t pts_us, int duration)
+static void flip_page(struct vo *vo)
{
struct vdpctx *vc = vo->priv;
struct vdp_functions *vdp = vc->vdp;
VdpStatus vdp_st;
+ int64_t pts_us = vc->current_pts;
+ int duration = vc->current_duration;
+
vc->dropped_frame = true; // changed at end if false
if (!check_preemption(vo))
- return 0;
+ goto drop;
vc->vsync_interval = 1;
if (vc->user_fps > 0) {
@@ -695,6 +795,7 @@ static int flip_page_timed(struct vo *vo, int64_t pts_us, int duration)
} else if (vc->user_fps == 0) {
vc->vsync_interval = vo_get_vsync_interval(vo) * 1000;
}
+ vc->vsync_interval = MPMAX(vc->vsync_interval, 1);
if (duration > INT_MAX / 1000)
duration = -1;
@@ -772,7 +873,7 @@ static int flip_page_timed(struct vo *vo, int64_t pts_us, int duration)
pts = FFMAX(pts, vc->last_queue_time + vc->vsync_interval);
pts = FFMAX(pts, now);
if (npts < PREV_VSYNC(pts) + vc->vsync_interval)
- return 0;
+ goto drop;
int num_flips = update_presentation_queue_status(vo);
vsync = vc->recent_vsync_time + num_flips * vc->vsync_interval;
@@ -780,7 +881,7 @@ static int flip_page_timed(struct vo *vo, int64_t pts_us, int duration)
pts = FFMAX(pts, vsync + (vc->vsync_interval >> 2));
vsync = PREV_VSYNC(pts);
if (npts < vsync + vc->vsync_interval)
- return 0;
+ goto drop;
pts = vsync + (vc->vsync_interval >> 2);
VdpOutputSurface frame = vc->output_surfaces[vc->surface_num];
vdp_st = vdp->presentation_queue_display(vc->flip_queue, frame,
@@ -795,22 +896,30 @@ static int flip_page_timed(struct vo *vo, int64_t pts_us, int duration)
vc->last_ideal_time = ideal_pts;
vc->dropped_frame = false;
vc->surface_num = WRAP_ADD(vc->surface_num, 1, vc->num_output_surfaces);
- return 1;
+ return;
+
+drop:
+ vo_increment_drop_count(vo, 1);
}
-static void draw_image(struct vo *vo, struct mp_image *mpi)
+static void draw_frame(struct vo *vo, struct vo_frame *frame)
{
struct vdpctx *vc = vo->priv;
check_preemption(vo);
- struct mp_image *vdp_mpi = mp_vdpau_upload_video_surface(vc->mpvdp, mpi);
- if (!vdp_mpi)
- MP_ERR(vo, "Could not upload image.\n");
- talloc_free(mpi);
+ if (frame->current && !frame->redraw) {
+ struct mp_image *vdp_mpi =
+ mp_vdpau_upload_video_surface(vc->mpvdp, frame->current);
+ if (!vdp_mpi)
+ MP_ERR(vo, "Could not upload image.\n");
+
+ talloc_free(vc->current_image);
+ vc->current_image = vdp_mpi;
+ }
- talloc_free(vc->current_image);
- vc->current_image = vdp_mpi;
+ vc->current_pts = frame->pts;
+ vc->current_duration = frame->duration;
if (status_ok(vo))
video_to_output_surface(vo);
@@ -819,8 +928,7 @@ static void draw_image(struct vo *vo, struct mp_image *mpi)
// warning: the size and pixel format of surface must match that of the
// surfaces in vc->output_surfaces
static struct mp_image *read_output_surface(struct vo *vo,
- VdpOutputSurface surface,
- int width, int height)
+ VdpOutputSurface surface)
{
struct vdpctx *vc = vo->priv;
VdpStatus vdp_st;
@@ -828,7 +936,15 @@ static struct mp_image *read_output_surface(struct vo *vo,
if (!vo->params)
return NULL;
- struct mp_image *image = mp_image_alloc(IMGFMT_BGR0, width, height);
+ VdpRGBAFormat fmt;
+ uint32_t w, h;
+ vdp_st = vdp->output_surface_get_parameters(surface, &fmt, &w, &h);
+ if (vdp_st != VDP_STATUS_OK)
+ return NULL;
+
+ assert(fmt == OUTPUT_RGBA_FORMAT);
+
+ struct mp_image *image = mp_image_alloc(IMGFMT_BGR0, w, h);
if (!image)
return NULL;
@@ -850,10 +966,9 @@ static struct mp_image *get_window_screenshot(struct vo *vo)
struct vdpctx *vc = vo->priv;
int last_surface = WRAP_ADD(vc->surface_num, -1, vc->num_output_surfaces);
VdpOutputSurface screen = vc->output_surfaces[last_surface];
- struct mp_image *image = read_output_surface(vo, screen,
- vc->output_surface_width,
- vc->output_surface_height);
- mp_image_set_size(image, vo->dwidth, vo->dheight);
+ struct mp_image *image = read_output_surface(vo, screen);
+ if (image && image->w >= vo->dwidth && image->h >= vo->dheight)
+ mp_image_set_size(image, vo->dwidth, vo->dheight);
return image;
}
@@ -894,6 +1009,10 @@ static void destroy_vdpau_objects(struct vo *vo)
vdp_st = vdp->output_surface_destroy(vc->output_surfaces[i]);
CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_destroy");
}
+ if (vc->rotation_surface != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->output_surface_destroy(vc->rotation_surface);
+ CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_destroy");
+ }
for (int i = 0; i < MAX_OSD_PARTS; i++) {
struct osd_bitmap_surface *sfc = &vc->osd_surfaces[i];
@@ -925,7 +1044,7 @@ static int preinit(struct vo *vo)
if (!vo_x11_init(vo))
return -1;
- vc->mpvdp = mp_vdpau_create_device_x11(vo->log, vo->x11->display);
+ vc->mpvdp = mp_vdpau_create_device_x11(vo->log, vo->x11->display, false);
if (!vc->mpvdp) {
vo_x11_uninit(vo);
return -1;
@@ -1011,10 +1130,6 @@ static int control(struct vo *vo, uint32_t request, void *data)
struct voctrl_get_equalizer_args *args = data;
return get_equalizer(vo, args->name, args->valueptr);
}
- case VOCTRL_REDRAW_FRAME:
- if (status_ok(vo))
- video_to_output_surface(vo);
- return true;
case VOCTRL_RESET:
forget_frames(vo, true);
return true;
@@ -1046,13 +1161,13 @@ static int control(struct vo *vo, uint32_t request, void *data)
const struct vo_driver video_out_vdpau = {
.description = "VDPAU with X11",
.name = "vdpau",
- .caps = VO_CAP_FRAMEDROP,
+ .caps = VO_CAP_FRAMEDROP | VO_CAP_ROTATE90,
.preinit = preinit,
.query_format = query_format,
.reconfig = reconfig,
.control = control,
- .draw_image = draw_image,
- .flip_page_timed = flip_page_timed,
+ .draw_frame = draw_frame,
+ .flip_page = flip_page,
.uninit = uninit,
.priv_size = sizeof(struct vdpctx),
.options = (const struct m_option []){
diff --git a/video/out/vo_wayland.c b/video/out/vo_wayland.c
index 40f06aa..5d3c77e 100644
--- a/video/out/vo_wayland.c
+++ b/video/out/vo_wayland.c
@@ -87,7 +87,7 @@ static const format_t format_table[] = {
struct priv;
// We only use double buffering but the creation and usage is still open to
-// triple buffering. Tripple buffering is now removed, because double buffering
+// triple buffering. Triple buffering is now removed, because double buffering
// is now pixel-perfect.
struct buffer_pool {
shm_buffer_t **buffers;
@@ -159,7 +159,7 @@ static const format_t* is_wayland_format_supported(struct priv *p,
return NULL;
}
-// additinal buffer functions
+// additional buffer functions
static void buffer_finalise_front(shm_buffer_t *buf)
{
@@ -269,7 +269,7 @@ static bool resize(struct priv *p)
struct vo_wayland_state *wl = p->wl;
if (!p->video_bufpool.back_buffer || SHM_BUFFER_IS_BUSY(p->video_bufpool.back_buffer))
- return false; // skip resizing if we can't garantuee pixel perfectness!
+ return false; // skip resizing if we can't guarantee pixel perfectness!
int32_t x = wl->window.sh_x;
int32_t y = wl->window.sh_y;
@@ -389,14 +389,12 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
p->original_image = mpi;
}
- if (!p->wl->frame.pending)
- return;
+ if (!vo_wayland_wait_frame(vo))
+ MP_DBG(p->wl, "discarding frame callback\n");
shm_buffer_t *buf = buffer_pool_get_back(&p->video_bufpool);
if (!buf) {
- // TODO: use similar handling of busy buffers as the osd buffers
- // if the need arises
MP_VERBOSE(p->wl, "can't draw, back buffer is busy\n");
return;
}
@@ -446,7 +444,7 @@ static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs)
}
else if (SHM_BUFFER_IS_BUSY(p->osd_buffers[id])) {
// freed on release in buffer_listener
- // garantuees pixel perfect resizing of subtitles and osd
+ // guarantees pixel perfect resizing of subtitles and osd
SHM_BUFFER_SET_ONESHOT(p->osd_buffers[id]);
p->osd_buffers[id] = shm_buffer_create(width,
height,
@@ -475,7 +473,7 @@ static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs)
wl_surface_commit(s);
}
else {
- // p->osd_buffer, garantueed to exist here
+ // p->osd_buffer, guaranteed to exist here
assert(p->osd_buffers[id]);
wl_surface_attach(s, p->osd_buffers[id]->buffer, 0, 0);
wl_surface_commit(s);
@@ -490,7 +488,7 @@ static void draw_osd(struct vo *vo)
{
struct priv *p = vo->priv;
- // deattach all buffers and attach all needed buffers in osd_draw
+ // detach all buffers and attach all needed buffers in osd_draw
// only the most recent attach & commit is applied once the parent surface
// is committed
for (int i = 0; i < MAX_OSD_PARTS; ++i) {
@@ -504,25 +502,31 @@ static void draw_osd(struct vo *vo)
osd_draw(vo->osd, p->osd, pts, 0, osd_formats, draw_osd_cb, p);
}
-static void flip_page(struct vo *vo)
+static void redraw(void *data, uint32_t time)
{
- struct priv *p = vo->priv;
-
- if (!p->wl->frame.pending)
- return;
-
- buffer_pool_swap(&p->video_bufpool);
+ struct priv *p = data;
shm_buffer_t *buf = buffer_pool_get_front(&p->video_bufpool);
wl_surface_attach(p->wl->window.video_surface, buf->buffer, p->x, p->y);
wl_surface_damage(p->wl->window.video_surface, 0, 0, p->dst_w, p->dst_h);
- wl_surface_commit(p->wl->window.video_surface);
buffer_finalise_front(buf);
p->x = 0;
p->y = 0;
p->recent_flip_time = mp_time_us();
- p->wl->frame.pending = false;
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+
+ buffer_pool_swap(&p->video_bufpool);
+
+ if (!p->wl->frame.callback)
+ vo_wayland_request_frame(vo, p, redraw);
+
+ if (!vo_wayland_wait_frame(vo))
+ MP_DBG(p->wl, "discarding frame callback\n");
}
static int query_format(struct vo *vo, int format)
@@ -569,7 +573,7 @@ static int reconfig(struct vo *vo, struct mp_image_params *fmt, int flags)
p->video_format = &format_table[DEFAULT_FORMAT_ENTRY];
}
- // overides alpha
+ // overrides alpha
// use rgb565 if performance is your main concern
if (p->use_rgb565) {
MP_INFO(p->wl, "using rgb565\n");
@@ -625,7 +629,7 @@ static int preinit(struct vo *vo)
wl_display_dispatch(wl->display.display);
// Commits on surfaces bound to a subsurface are cached until the parent
- // surface is commited, in this case the video surface.
+ // surface is committed, in this case the video surface.
// Which means we can call commit anywhere.
struct wl_region *input =
wl_compositor_create_region(wl->display.compositor);
diff --git a/video/out/vo_x11.c b/video/out/vo_x11.c
deleted file mode 100644
index eca6c2a..0000000
--- a/video/out/vo_x11.c
+++ /dev/null
@@ -1,633 +0,0 @@
-/*
- * Original author: Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
- *
- * 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 <libswscale/swscale.h>
-
-#include "config.h"
-#include "vo.h"
-#include "video/csputils.h"
-#include "video/mp_image.h"
-#include "video/filter/vf.h"
-
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-
-#include <errno.h>
-
-#include "x11_common.h"
-
-#if HAVE_SHM
-#include <sys/ipc.h>
-#include <sys/shm.h>
-#include <X11/extensions/XShm.h>
-#endif
-
-#include "sub/osd.h"
-#include "sub/draw_bmp.h"
-
-#include "video/sws_utils.h"
-#include "video/fmt-conversion.h"
-
-#include "common/msg.h"
-#include "input/input.h"
-#include "options/options.h"
-#include "osdep/timer.h"
-
-struct priv {
- struct vo *vo;
-
- struct mp_image *original_image;
-
- /* local data */
- unsigned char *ImageData[2];
- //! original unaligned pointer for free
- unsigned char *ImageDataOrig[2];
-
- /* X11 related variables */
- XImage *myximage[2];
- int depth, bpp;
- XWindowAttributes attribs;
-
- uint32_t image_width;
- uint32_t image_height;
- struct mp_image_params in_format;
-
- struct mp_rect src;
- struct mp_rect dst;
- int src_w, src_h;
- int dst_w, dst_h;
- struct mp_osd_res osd;
-
- struct mp_sws_context *sws;
-
- XVisualInfo vinfo;
- int ximage_depth;
-
- int firstTime;
-
- int current_buf;
- int num_buffers;
-
- int Shmem_Flag;
-#if HAVE_SHM
- int Shm_Warned_Slow;
-
- XShmSegmentInfo Shminfo[2];
-#endif
-};
-
-static bool resize(struct vo *vo);
-
-/* Scan the available visuals on this Display/Screen. Try to find
- * the 'best' available TrueColor visual that has a decent color
- * depth (at least 15bit). If there are multiple visuals with depth
- * >= 15bit, we prefer visuals with a smaller color depth. */
-static int find_depth_from_visuals(struct vo *vo, Visual ** visual_return)
-{
- Display *dpy = vo->x11->display;
- int screen = vo->x11->screen;
- XVisualInfo visual_tmpl;
- XVisualInfo *visuals;
- int nvisuals, i;
- int bestvisual = -1;
- int bestvisual_depth = -1;
-
- visual_tmpl.screen = screen;
- visual_tmpl.class = TrueColor;
- visuals = XGetVisualInfo(dpy,
- VisualScreenMask | VisualClassMask,
- &visual_tmpl, &nvisuals);
- if (visuals != NULL)
- {
- for (i = 0; i < nvisuals; i++)
- {
- MP_VERBOSE(vo,
- "truecolor visual %#lx, depth %d, R:%lX G:%lX B:%lX\n",
- visuals[i].visualid, visuals[i].depth,
- visuals[i].red_mask, visuals[i].green_mask,
- visuals[i].blue_mask);
- /*
- * Save the visual index and its depth, if this is the first
- * truecolor visul, or a visual that is 'preferred' over the
- * previous 'best' visual.
- */
- if (bestvisual_depth == -1
- || (visuals[i].depth >= 15
- && (visuals[i].depth < bestvisual_depth
- || bestvisual_depth < 15)))
- {
- bestvisual = i;
- bestvisual_depth = visuals[i].depth;
- }
- }
-
- if (bestvisual != -1 && visual_return != NULL)
- *visual_return = visuals[bestvisual].visual;
-
- XFree(visuals);
- }
- return bestvisual_depth;
-}
-
-static bool getMyXImage(struct priv *p, int foo)
-{
- struct vo *vo = p->vo;
-#if HAVE_SHM && HAVE_XEXT
- if (vo->x11->display_is_local && XShmQueryExtension(vo->x11->display)) {
- p->Shmem_Flag = 1;
- vo->x11->ShmCompletionEvent = XShmGetEventBase(vo->x11->display)
- + ShmCompletion;
- } else {
- p->Shmem_Flag = 0;
- MP_WARN(vo, "Shared memory not supported\nReverting to normal Xlib\n");
- }
-
- if (p->Shmem_Flag) {
- p->myximage[foo] =
- XShmCreateImage(vo->x11->display, p->vinfo.visual, p->depth,
- ZPixmap, NULL, &p->Shminfo[foo], p->image_width,
- p->image_height);
- if (p->myximage[foo] == NULL) {
- MP_WARN(vo, "Shared memory error,disabling ( Ximage error )\n");
- goto shmemerror;
- }
- p->Shminfo[foo].shmid = shmget(IPC_PRIVATE,
- p->myximage[foo]->bytes_per_line *
- p->myximage[foo]->height,
- IPC_CREAT | 0777);
- if (p->Shminfo[foo].shmid < 0) {
- XDestroyImage(p->myximage[foo]);
- MP_WARN(vo, "Shared memory error,disabling ( seg id error )\n");
- goto shmemerror;
- }
- p->Shminfo[foo].shmaddr = (char *) shmat(p->Shminfo[foo].shmid, 0, 0);
-
- if (p->Shminfo[foo].shmaddr == ((char *) -1)) {
- XDestroyImage(p->myximage[foo]);
- MP_WARN(vo, "Shared memory error,disabling ( address error )\n");
- goto shmemerror;
- }
- p->myximage[foo]->data = p->Shminfo[foo].shmaddr;
- p->ImageData[foo] = (unsigned char *) p->myximage[foo]->data;
- p->Shminfo[foo].readOnly = False;
- XShmAttach(vo->x11->display, &p->Shminfo[foo]);
-
- XSync(vo->x11->display, False);
-
- shmctl(p->Shminfo[foo].shmid, IPC_RMID, 0);
-
- if (!p->firstTime) {
- MP_VERBOSE(vo, "Sharing memory.\n");
- p->firstTime = 1;
- }
- } else {
-shmemerror:
- p->Shmem_Flag = 0;
-#endif
- p->myximage[foo] =
- XCreateImage(vo->x11->display, p->vinfo.visual, p->depth, ZPixmap,
- 0, NULL, p->image_width, p->image_height, 8, 0);
- if (!p->myximage[foo]) {
- MP_WARN(vo, "could not allocate image");
- return false;
- }
- size_t sz = p->myximage[foo]->bytes_per_line * p->image_height + 32;
- p->ImageDataOrig[foo] = calloc(1, sz);
- p->myximage[foo]->data = p->ImageDataOrig[foo] + 16
- - ((long)p->ImageDataOrig & 15);
- p->ImageData[foo] = p->myximage[foo]->data;
-#if HAVE_SHM && HAVE_XEXT
-}
-#endif
- return true;
-}
-
-static void freeMyXImage(struct priv *p, int foo)
-{
-#if HAVE_SHM && HAVE_XEXT
- struct vo *vo = p->vo;
- if (p->Shmem_Flag) {
- XShmDetach(vo->x11->display, &p->Shminfo[foo]);
- XDestroyImage(p->myximage[foo]);
- shmdt(p->Shminfo[foo].shmaddr);
- } else
-#endif
- {
- if (p->myximage[foo]) {
- p->myximage[foo]->data = p->ImageDataOrig[foo];
- XDestroyImage(p->myximage[foo]);
- p->ImageDataOrig[foo] = NULL;
- }
- }
- p->myximage[foo] = NULL;
- p->ImageData[foo] = NULL;
-}
-
-#if BYTE_ORDER == BIG_ENDIAN
-#define BO_NATIVE MSBFirst
-#define BO_NONNATIVE LSBFirst
-#else
-#define BO_NATIVE LSBFirst
-#define BO_NONNATIVE MSBFirst
-#endif
-const struct fmt2Xfmtentry_s {
- uint32_t mpfmt;
- int byte_order;
- unsigned red_mask;
- unsigned green_mask;
- unsigned blue_mask;
-} fmt2Xfmt[] = {
- {IMGFMT_BGR8, BO_NATIVE, 0x00000007, 0x00000038, 0x000000C0},
- {IMGFMT_BGR8, BO_NONNATIVE, 0x00000007, 0x00000038, 0x000000C0},
- {IMGFMT_RGB8, BO_NATIVE, 0x000000E0, 0x0000001C, 0x00000003},
- {IMGFMT_RGB8, BO_NONNATIVE, 0x000000E0, 0x0000001C, 0x00000003},
- {IMGFMT_BGR555, BO_NATIVE, 0x0000001F, 0x000003E0, 0x00007C00},
- {IMGFMT_BGR555, BO_NATIVE, 0x00007C00, 0x000003E0, 0x0000001F},
- {IMGFMT_BGR565, BO_NATIVE, 0x0000001F, 0x000007E0, 0x0000F800},
- {IMGFMT_RGB565, BO_NATIVE, 0x0000F800, 0x000007E0, 0x0000001F},
- {IMGFMT_RGB24, MSBFirst, 0x00FF0000, 0x0000FF00, 0x000000FF},
- {IMGFMT_RGB24, LSBFirst, 0x000000FF, 0x0000FF00, 0x00FF0000},
- {IMGFMT_BGR24, MSBFirst, 0x000000FF, 0x0000FF00, 0x00FF0000},
- {IMGFMT_BGR24, LSBFirst, 0x00FF0000, 0x0000FF00, 0x000000FF},
- {IMGFMT_RGB32, BO_NATIVE, 0x000000FF, 0x0000FF00, 0x00FF0000},
- {IMGFMT_RGB32, BO_NONNATIVE, 0xFF000000, 0x00FF0000, 0x0000FF00},
- {IMGFMT_BGR32, BO_NATIVE, 0x00FF0000, 0x0000FF00, 0x000000FF},
- {IMGFMT_BGR32, BO_NONNATIVE, 0x0000FF00, 0x00FF0000, 0xFF000000},
- {IMGFMT_ARGB, MSBFirst, 0x00FF0000, 0x0000FF00, 0x000000FF},
- {IMGFMT_ARGB, LSBFirst, 0x0000FF00, 0x00FF0000, 0xFF000000},
- {IMGFMT_ABGR, MSBFirst, 0x000000FF, 0x0000FF00, 0x00FF0000},
- {IMGFMT_ABGR, LSBFirst, 0xFF000000, 0x00FF0000, 0x0000FF00},
- {IMGFMT_RGBA, MSBFirst, 0xFF000000, 0x00FF0000, 0x0000FF00},
- {IMGFMT_RGBA, LSBFirst, 0x000000FF, 0x0000FF00, 0x00FF0000},
- {IMGFMT_BGRA, MSBFirst, 0x0000FF00, 0x00FF0000, 0xFF000000},
- {IMGFMT_BGRA, LSBFirst, 0x00FF0000, 0x0000FF00, 0x000000FF},
- {0}
-};
-
-static int reconfig(struct vo *vo, struct mp_image_params *fmt, int flags)
-{
- struct priv *p = vo->priv;
-
- mp_image_unrefp(&p->original_image);
-
- p->in_format = *fmt;
-
- XGetWindowAttributes(vo->x11->display, vo->x11->rootwin, &p->attribs);
- p->depth = p->attribs.depth;
-
- if (p->depth != 15 && p->depth != 16 && p->depth != 24 && p->depth != 32) {
- Visual *visual;
-
- p->depth = find_depth_from_visuals(vo, &visual);
- }
- if (!XMatchVisualInfo(vo->x11->display, vo->x11->screen, p->depth,
- TrueColor, &p->vinfo))
- return -1;
-
- vo_x11_config_vo_window(vo, &p->vinfo, flags, "x11");
-
- if (!resize(vo))
- return -1;
-
- return 0;
-}
-
-static bool resize(struct vo *vo)
-{
- struct priv *p = vo->priv;
-
- for (int i = 0; i < p->num_buffers; i++)
- freeMyXImage(p, i);
-
- vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd);
-
- p->src_w = p->src.x1 - p->src.x0;
- p->src_h = p->src.y1 - p->src.y0;
- p->dst_w = p->dst.x1 - p->dst.x0;
- p->dst_h = p->dst.y1 - p->dst.y0;
-
- // p->osd contains the parameters assuming OSD rendering in window
- // coordinates, but OSD can only be rendered in the intersection
- // between window and video rectangle (i.e. not into panscan borders).
- p->osd.w = p->dst_w;
- p->osd.h = p->dst_h;
- p->osd.mt = MPMIN(0, p->osd.mt);
- p->osd.mb = MPMIN(0, p->osd.mb);
- p->osd.mr = MPMIN(0, p->osd.mr);
- p->osd.ml = MPMIN(0, p->osd.ml);
-
- mp_input_set_mouse_transform(vo->input_ctx, &p->dst, NULL);
-
- p->image_width = (p->dst_w + 7) & (~7);
- p->image_height = p->dst_h;
-
- p->num_buffers = 2;
- for (int i = 0; i < p->num_buffers; i++) {
- if (!getMyXImage(p, i))
- return -1;
- }
-
- const struct fmt2Xfmtentry_s *fmte = fmt2Xfmt;
- while (fmte->mpfmt) {
- int depth = IMGFMT_RGB_DEPTH(fmte->mpfmt);
- /* bits_per_pixel in X seems to be set to 16 for 15 bit formats
- => force depth to 16 so that only the color masks are used for the format check */
- if (depth == 15)
- depth = 16;
-
- if (depth == p->myximage[0]->bits_per_pixel &&
- fmte->byte_order == p->myximage[0]->byte_order &&
- fmte->red_mask == p->myximage[0]->red_mask &&
- fmte->green_mask == p->myximage[0]->green_mask &&
- fmte->blue_mask == p->myximage[0]->blue_mask)
- break;
- fmte++;
- }
- if (!fmte->mpfmt) {
- MP_ERR(vo, "X server image format not supported,"
- " please contact the developers\n");
- return -1;
- }
- p->bpp = p->myximage[0]->bits_per_pixel;
-
- mp_sws_set_from_cmdline(p->sws, vo->opts->sws_opts);
- p->sws->src = p->in_format;
- p->sws->dst = (struct mp_image_params) {
- .imgfmt = fmte->mpfmt,
- .w = p->dst_w,
- .h = p->dst_h,
- .d_w = p->dst_w,
- .d_h = p->dst_h,
- };
- mp_image_params_guess_csp(&p->sws->dst);
-
- if (mp_sws_reinit(p->sws) < 0)
- return false;
-
- vo_x11_clear_background(vo, &p->dst);
-
- vo->want_redraw = true;
- return true;
-}
-
-static void Display_Image(struct priv *p, XImage *myximage)
-{
- struct vo *vo = p->vo;
-
- XImage *x_image = p->myximage[p->current_buf];
-
-#if HAVE_SHM && HAVE_XEXT
- if (p->Shmem_Flag) {
- XShmPutImage(vo->x11->display, vo->x11->window, vo->x11->vo_gc, x_image,
- 0, 0, p->dst.x0, p->dst.y0, p->dst_w, p->dst_h,
- True);
- vo->x11->ShmCompletionWaitCount++;
- } else
-#endif
- {
- XPutImage(vo->x11->display, vo->x11->window, vo->x11->vo_gc, x_image,
- 0, 0, p->dst.x0, p->dst.y0, p->dst_w, p->dst_h);
- }
-}
-
-static struct mp_image get_x_buffer(struct priv *p, int buf_index)
-{
- struct mp_image img = {0};
- mp_image_set_params(&img, &p->sws->dst);
-
- img.planes[0] = p->ImageData[buf_index];
- img.stride[0] = p->image_width * ((p->bpp + 7) / 8);
-
- return img;
-}
-
-static void wait_for_completion(struct vo *vo, int max_outstanding)
-{
-#if HAVE_SHM && HAVE_XEXT
- struct priv *ctx = vo->priv;
- struct vo_x11_state *x11 = vo->x11;
- if (ctx->Shmem_Flag) {
- while (x11->ShmCompletionWaitCount > max_outstanding) {
- if (!ctx->Shm_Warned_Slow) {
- MP_WARN(vo, "can't keep up! Waiting"
- " for XShm completion events...\n");
- ctx->Shm_Warned_Slow = 1;
- }
- mp_sleep_us(1000);
- vo_x11_check_events(vo);
- }
- }
-#endif
-}
-
-static void flip_page(struct vo *vo)
-{
- struct priv *p = vo->priv;
- Display_Image(p, p->myximage[p->current_buf]);
- p->current_buf = (p->current_buf + 1) % p->num_buffers;
-
- if (!p->Shmem_Flag)
- XSync(vo->x11->display, False);
-}
-
-// Note: REDRAW_FRAME can call this with NULL.
-static void draw_image(struct vo *vo, mp_image_t *mpi)
-{
- struct priv *p = vo->priv;
-
- wait_for_completion(vo, p->num_buffers - 1);
-
- struct mp_image img = get_x_buffer(p, p->current_buf);
-
- if (mpi) {
- struct mp_image src = *mpi;
- struct mp_rect src_rc = p->src;
- src_rc.x0 = MP_ALIGN_DOWN(src_rc.x0, src.fmt.align_x);
- src_rc.y0 = MP_ALIGN_DOWN(src_rc.y0, src.fmt.align_y);
- mp_image_crop_rc(&src, src_rc);
-
- mp_sws_scale(p->sws, &img, &src);
- } else {
- mp_image_clear(&img, 0, 0, img.w, img.h);
- }
-
- osd_draw_on_image(vo->osd, p->osd, mpi ? mpi->pts : 0, 0, &img);
-
- if (mpi != p->original_image) {
- talloc_free(p->original_image);
- p->original_image = mpi;
- }
-}
-
-static int query_format(struct vo *vo, int format)
-{
- MP_DBG(vo, "query_format was called: %x (%s)\n", format,
- vo_format_name(format));
- if (IMGFMT_IS_RGB(format)) {
- for (int n = 0; fmt2Xfmt[n].mpfmt; n++) {
- if (fmt2Xfmt[n].mpfmt == format)
- return 1;
- }
- }
-
- if (sws_isSupportedInput(imgfmt2pixfmt(format)))
- return 1;
- return 0;
-}
-
-static void find_x11_depth(struct vo *vo)
-{
- struct vo_x11_state *x11 = vo->x11;
- struct priv *p = vo->priv;
- XImage *mXImage = NULL;
- int depth, bpp, ximage_depth;
- unsigned int mask;
- XWindowAttributes attribs;
-
- // get color depth (from root window, or the best visual):
- XGetWindowAttributes(x11->display, x11->rootwin, &attribs);
- depth = attribs.depth;
-
- if (depth != 15 && depth != 16 && depth != 24 && depth != 32)
- {
- Visual *visual;
-
- depth = find_depth_from_visuals(vo, &visual);
- if (depth != -1)
- mXImage = XCreateImage(x11->display, visual, depth, ZPixmap,
- 0, NULL, 1, 1, 8, 1);
- } else
- mXImage =
- XGetImage(x11->display, x11->rootwin, 0, 0, 1, 1, AllPlanes, ZPixmap);
-
- ximage_depth = depth; // display depth on screen
-
- // get bits/pixel from XImage structure:
- if (mXImage == NULL)
- {
- mask = 0;
- } else
- {
- /* for the depth==24 case, the XImage structures might use
- * 24 or 32 bits of data per pixel. */
- bpp = mXImage->bits_per_pixel;
- if ((ximage_depth + 7) / 8 != (bpp + 7) / 8)
- ximage_depth = bpp; // by A'rpi
- mask =
- mXImage->red_mask | mXImage->green_mask | mXImage->blue_mask;
- MP_VERBOSE(vo, "color mask: %X (R:%lX G:%lX B:%lX)\n", mask,
- mXImage->red_mask, mXImage->green_mask, mXImage->blue_mask);
- XDestroyImage(mXImage);
- }
- if (((ximage_depth + 7) / 8) == 2)
- {
- if (mask == 0x7FFF)
- ximage_depth = 15;
- else if (mask == 0xFFFF)
- ximage_depth = 16;
- }
-
- MP_VERBOSE(vo, "depth %d and %d bpp.\n", depth, ximage_depth);
-
- p->ximage_depth = ximage_depth;
-}
-
-static void uninit(struct vo *vo)
-{
- struct priv *p = vo->priv;
- if (p->myximage[0])
- freeMyXImage(p, 0);
- if (p->myximage[1])
- freeMyXImage(p, 1);
-
- talloc_free(p->original_image);
-
- vo_x11_uninit(vo);
-}
-
-static int preinit(struct vo *vo)
-{
- struct priv *p = vo->priv;
- p->vo = vo;
-
- if (!vo_x11_init(vo))
- return -1; // Can't open X11
- find_x11_depth(vo);
- p->sws = mp_sws_alloc(vo);
- return 0;
-}
-
-static int control(struct vo *vo, uint32_t request, void *data)
-{
- struct priv *p = vo->priv;
- switch (request) {
- case VOCTRL_SET_EQUALIZER:
- {
- struct voctrl_set_equalizer_args *args = data;
- struct vf_seteq eq = {args->name, args->value};
- if (mp_sws_set_vf_equalizer(p->sws, &eq) == 0)
- break;
- return true;
- }
- case VOCTRL_GET_EQUALIZER:
- {
- struct voctrl_get_equalizer_args *args = data;
- struct vf_seteq eq = {args->name};
- if (mp_sws_get_vf_equalizer(p->sws, &eq) == 0)
- break;
- *(int *)args->valueptr = eq.value;
- return true;
- }
- case VOCTRL_GET_PANSCAN:
- return VO_TRUE;
- case VOCTRL_SET_PANSCAN:
- if (vo->config_ok)
- resize(vo);
- return VO_TRUE;
- case VOCTRL_REDRAW_FRAME:
- draw_image(vo, p->original_image);
- return true;
- }
-
- int events = 0;
- int r = vo_x11_control(vo, &events, request, data);
- if (vo->config_ok && (events & (VO_EVENT_EXPOSE | VO_EVENT_RESIZE)))
- resize(vo);
- vo_event(vo, events);
- return r;
-}
-
-const struct vo_driver video_out_x11 = {
- .description = "X11 ( XImage/Shm )",
- .name = "x11",
- .priv_size = sizeof(struct priv),
- .options = (const struct m_option []){{0}},
- .preinit = preinit,
- .query_format = query_format,
- .reconfig = reconfig,
- .control = control,
- .draw_image = draw_image,
- .flip_page = flip_page,
- .uninit = uninit,
-};
diff --git a/video/out/vo_xv.c b/video/out/vo_xv.c
index e6578b1..c2f9c9d 100644
--- a/video/out/vo_xv.c
+++ b/video/out/vo_xv.c
@@ -63,6 +63,8 @@
#define CK_SRC_SET 1 // use and set specified / default colorkey
#define CK_SRC_CUR 2 // use current colorkey (get it from xv)
+#define MAX_BUFFERS 10
+
struct xvctx {
struct xv_ck_info_s {
int method; // CK_METHOD_* constants
@@ -72,13 +74,14 @@ struct xvctx {
unsigned long xv_colorkey;
int xv_port;
int cfg_xv_adaptor;
+ int cfg_buffers;
XvAdaptorInfo *ai;
XvImageFormatValues *fo;
unsigned int formats, adaptors, xv_format;
int current_buf;
int current_ip_buf;
int num_buffers;
- XvImage *xvimage[2];
+ XvImage *xvimage[MAX_BUFFERS];
struct mp_image *original_image;
uint32_t image_width;
uint32_t image_height;
@@ -87,9 +90,11 @@ struct xvctx {
struct mp_rect src_rect;
struct mp_rect dst_rect;
uint32_t max_width, max_height; // zero means: not set
+ GC f_gc; // used to paint background
+ GC vo_gc; // used to paint video
int Shmem_Flag;
#if HAVE_SHM && HAVE_XEXT
- XShmSegmentInfo Shminfo[2];
+ XShmSegmentInfo Shminfo[MAX_BUFFERS];
int Shm_Warned_Slow;
#endif
};
@@ -378,11 +383,11 @@ static void xv_draw_colorkey(struct vo *vo, const struct mp_rect *rc)
if (ctx->xv_ck_info.method == CK_METHOD_MANUALFILL ||
ctx->xv_ck_info.method == CK_METHOD_BACKGROUND)
{
- if (!x11->vo_gc)
+ if (!ctx->vo_gc)
return;
//less tearing than XClearWindow()
- XSetForeground(x11->display, x11->vo_gc, ctx->xv_colorkey);
- XFillRectangle(x11->display, x11->window, x11->vo_gc, rc->x0, rc->y0,
+ XSetForeground(x11->display, ctx->vo_gc, ctx->xv_colorkey);
+ XFillRectangle(x11->display, x11->window, ctx->vo_gc, rc->x0, rc->y0,
rc->x1 - rc->x0, rc->y1 - rc->y0);
}
}
@@ -396,6 +401,38 @@ static void read_xv_csp(struct vo *vo)
ctx->cached_csp = bt709_enabled == 100 ? MP_CSP_BT_709 : MP_CSP_BT_601;
}
+
+static void fill_rect(struct vo *vo, GC gc, int x0, int y0, int x1, int y1)
+{
+ struct vo_x11_state *x11 = vo->x11;
+
+ x0 = MPMAX(x0, 0);
+ y0 = MPMAX(y0, 0);
+ x1 = MPMIN(x1, vo->dwidth);
+ y1 = MPMIN(y1, vo->dheight);
+
+ if (x11->window && x1 > x0 && y1 > y0)
+ XFillRectangle(x11->display, x11->window, gc, x0, y0, x1 - x0, y1 - y0);
+}
+
+// Clear everything outside of rc with the background color
+static void vo_x11_clear_background(struct vo *vo, const struct mp_rect *rc)
+{
+ struct vo_x11_state *x11 = vo->x11;
+ struct xvctx *ctx = vo->priv;
+ GC gc = ctx->f_gc;
+
+ int w = vo->dwidth;
+ int h = vo->dheight;
+
+ fill_rect(vo, gc, 0, 0, w, rc->y0); // top
+ fill_rect(vo, gc, 0, rc->y1, w, h); // bottom
+ fill_rect(vo, gc, 0, rc->y0, rc->x0, rc->y1); // left
+ fill_rect(vo, gc, rc->x1, rc->y0, w, rc->y1); // right
+
+ XFlush(x11->display);
+}
+
static void resize(struct vo *vo)
{
struct xvctx *ctx = vo->priv;
@@ -454,6 +491,12 @@ static int reconfig(struct vo *vo, struct mp_image_params *params, int flags)
vo_x11_config_vo_window(vo, NULL, flags, "xv");
+ if (!ctx->f_gc && !ctx->vo_gc) {
+ ctx->f_gc = XCreateGC(x11->display, x11->window, 0, 0);
+ ctx->vo_gc = XCreateGC(x11->display, x11->window, 0, NULL);
+ XSetForeground(x11->display, ctx->f_gc, 0);
+ }
+
if (ctx->xv_ck_info.method == CK_METHOD_BACKGROUND)
XSetWindowBackground(x11->display, x11->window, ctx->xv_colorkey);
@@ -463,7 +506,7 @@ static int reconfig(struct vo *vo, struct mp_image_params *params, int flags)
for (i = 0; i < ctx->num_buffers; i++)
deallocate_xvimage(vo, i);
- ctx->num_buffers = 2;
+ ctx->num_buffers = ctx->cfg_buffers;
for (i = 0; i < ctx->num_buffers; i++) {
if (!allocate_xvimage(vo, i)) {
@@ -574,7 +617,7 @@ static inline void put_xvimage(struct vo *vo, XvImage *xvi)
int sw = src->x1 - src->x0, sh = src->y1 - src->y0;
#if HAVE_SHM && HAVE_XEXT
if (ctx->Shmem_Flag) {
- XvShmPutImage(x11->display, ctx->xv_port, x11->window, x11->vo_gc, xvi,
+ XvShmPutImage(x11->display, ctx->xv_port, x11->window, ctx->vo_gc, xvi,
src->x0, src->y0, sw, sh,
dst->x0, dst->y0, dw, dh,
True);
@@ -582,7 +625,7 @@ static inline void put_xvimage(struct vo *vo, XvImage *xvi)
} else
#endif
{
- XvPutImage(x11->display, ctx->xv_port, x11->window, x11->vo_gc, xvi,
+ XvPutImage(x11->display, ctx->xv_port, x11->window, ctx->vo_gc, xvi,
src->x0, src->y0, sw, sh,
dst->x0, dst->y0, dw, dh);
}
@@ -699,6 +742,10 @@ static void uninit(struct vo *vo)
}
for (i = 0; i < ctx->num_buffers; i++)
deallocate_xvimage(vo, i);
+ if (ctx->f_gc != None)
+ XFreeGC(vo->x11->display, ctx->f_gc);
+ if (ctx->vo_gc != None)
+ XFreeGC(vo->x11->display, ctx->vo_gc);
// uninit() shouldn't get called unless initialization went past vo_init()
vo_x11_uninit(vo);
}
@@ -848,6 +895,7 @@ const struct vo_driver video_out_xv = {
.xv_ck_info = {CK_METHOD_MANUALFILL, CK_SRC_CUR},
.colorkey = 0x0000ff00, // default colorkey is green
// (0xff000000 means that colorkey has been disabled)
+ .cfg_buffers = 2,
},
.options = (const struct m_option[]) {
OPT_INT("port", xv_port, M_OPT_MIN, .min = 0),
@@ -862,6 +910,7 @@ const struct vo_driver video_out_xv = {
{"auto", CK_METHOD_AUTOPAINT})),
OPT_INT("colorkey", colorkey, 0),
OPT_FLAG_STORE("no-colorkey", colorkey, 0, 0x1000000),
+ OPT_INTRANGE("buffers", cfg_buffers, 0, 1, MAX_BUFFERS),
{0}
},
};
diff --git a/video/out/w32_common.c b/video/out/w32_common.c
index 075bda5..c327403 100644
--- a/video/out/w32_common.c
+++ b/video/out/w32_common.c
@@ -37,6 +37,7 @@
#include "osdep/io.h"
#include "osdep/threads.h"
#include "osdep/w32_keyboard.h"
+#include "osdep/atomics.h"
#include "misc/dispatch.h"
#include "misc/rendezvous.h"
#include "talloc.h"
@@ -111,7 +112,7 @@ struct vo_w32_state {
typedef struct tagDropTarget {
IDropTarget iface;
- ULONG refCnt;
+ atomic_int refCnt;
DWORD lastEffect;
IDataObject* dataObj;
struct vo_w32_state *w32;
@@ -148,13 +149,13 @@ static HRESULT STDMETHODCALLTYPE DropTarget_QueryInterface(IDropTarget* This,
static ULONG STDMETHODCALLTYPE DropTarget_AddRef(IDropTarget* This)
{
DropTarget* t = (DropTarget*)This;
- return ++(t->refCnt);
+ return atomic_fetch_add(&t->refCnt, 1) + 1;
}
static ULONG STDMETHODCALLTYPE DropTarget_Release(IDropTarget* This)
{
DropTarget* t = (DropTarget*)This;
- ULONG cRef = --(t->refCnt);
+ ULONG cRef = atomic_fetch_add(&t->refCnt, -1) - 1;
if (cRef == 0) {
DropTarget_Destroy(t);
@@ -224,6 +225,8 @@ static HRESULT STDMETHODCALLTYPE DropTarget_Drop(IDropTarget* This,
t->dataObj = NULL;
}
+ enum mp_dnd_action action = (grfKeyState & MK_SHIFT) ? DND_APPEND : DND_REPLACE;
+
pDataObj->lpVtbl->AddRef(pDataObj);
if (pDataObj->lpVtbl->GetData(pDataObj, &fmtetc_file, &medium) == S_OK) {
@@ -252,7 +255,8 @@ static HRESULT STDMETHODCALLTYPE DropTarget_Drop(IDropTarget* This,
}
GlobalUnlock(medium.hGlobal);
- mp_event_drop_files(t->w32->input_ctx, nrecvd_files, files);
+ mp_event_drop_files(t->w32->input_ctx, nrecvd_files, files,
+ action);
talloc_free(files);
}
@@ -264,7 +268,7 @@ static HRESULT STDMETHODCALLTYPE DropTarget_Drop(IDropTarget* This,
char* url = (char*)GlobalLock(medium.hGlobal);
if (url != NULL) {
if (mp_event_drop_mime_data(t->w32->input_ctx, "text/uri-list",
- bstr0(url)) > 0) {
+ bstr0(url), action) > 0) {
MP_VERBOSE(t->w32, "received dropped URL: %s\n", url);
} else {
MP_ERR(t->w32, "error getting dropped URL\n");
@@ -295,7 +299,7 @@ static void DropTarget_Init(DropTarget* This, struct vo_w32_state *w32)
};
This->iface.lpVtbl = vtbl;
- This->refCnt = 0;
+ atomic_store(&This->refCnt, 0);
This->lastEffect = 0;
This->dataObj = NULL;
This->w32 = w32;
@@ -657,7 +661,7 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
}
case WM_SIZING:
if (w32->opts->keepaspect && w32->opts->keepaspect_window &&
- !w32->opts->fullscreen && !w32->parent)
+ !w32->current_fs && !w32->parent)
{
RECT *rc = (RECT*)lParam;
// get client area of the windows if it had the rect rc
@@ -700,7 +704,7 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
break;
case WM_NCHITTEST:
// Provide sizing handles for borderless windows
- if (!w32->opts->border && !w32->opts->fullscreen) {
+ if (!w32->opts->border && !w32->current_fs) {
return borderless_nchittest(w32, GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam));
}
@@ -810,7 +814,7 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
int y = GET_Y_LPARAM(lParam);
if (mouse_button == (MP_MOUSE_BTN0 | MP_KEY_STATE_DOWN) &&
- !w32->opts->fullscreen &&
+ !w32->current_fs &&
!mp_input_test_dragging(w32->input_ctx, x, y))
{
// Window dragging hack
@@ -883,9 +887,9 @@ static BOOL CALLBACK mon_enum(HMONITOR hmon, HDC hdc, LPRECT r, LPARAM p)
static void w32_update_xinerama_info(struct vo_w32_state *w32)
{
struct mp_vo_opts *opts = w32->opts;
- int screen = opts->fullscreen ? opts->fsscreen_id : opts->screen_id;
+ int screen = w32->current_fs ? opts->fsscreen_id : opts->screen_id;
- if (opts->fullscreen && screen == -2) {
+ if (w32->current_fs && screen == -2) {
struct mp_rect rc = {
GetSystemMetrics(SM_XVIRTUALSCREEN),
GetSystemMetrics(SM_YVIRTUALSCREEN),
@@ -937,7 +941,7 @@ static DWORD update_style(struct vo_w32_state *w32, DWORD style)
const DWORD NO_FRAME = WS_POPUP;
const DWORD FRAME = WS_OVERLAPPEDWINDOW | WS_SIZEBOX;
style &= ~(NO_FRAME | FRAME);
- style |= (w32->opts->border && !w32->opts->fullscreen) ? FRAME : NO_FRAME;
+ style |= (w32->opts->border && !w32->current_fs) ? FRAME : NO_FRAME;
return style;
}
@@ -950,12 +954,13 @@ static int reinit_window_state(struct vo_w32_state *w32)
if (w32->parent)
return 1;
- bool toggle_fs = w32->current_fs != w32->opts->fullscreen;
- w32->current_fs = w32->opts->fullscreen;
+ bool new_fs = w32->opts->fullscreen;
+ bool toggle_fs = w32->current_fs != new_fs;
+ w32->current_fs = new_fs;
if (w32->taskbar_list) {
ITaskbarList2_MarkFullscreenWindow(w32->taskbar_list,
- w32->window, w32->opts->fullscreen);
+ w32->window, w32->current_fs);
}
DWORD style = update_style(w32, GetWindowLong(w32->window, GWL_STYLE));
@@ -969,7 +974,7 @@ static int reinit_window_state(struct vo_w32_state *w32)
int screen_w = w32->screenrc.x1 - w32->screenrc.x0;
int screen_h = w32->screenrc.y1 - w32->screenrc.y0;
- if (w32->opts->fullscreen) {
+ if (w32->current_fs) {
// Save window position and size when switching to fullscreen.
if (toggle_fs) {
w32->prev_width = w32->dw;
@@ -1006,7 +1011,7 @@ static int reinit_window_state(struct vo_w32_state *w32)
RECT cr = r;
add_window_borders(w32->window, &r);
- if (!w32->opts->fullscreen &&
+ if (!w32->current_fs &&
((r.right - r.left) >= screen_w || (r.bottom - r.top) >= screen_h))
{
MP_VERBOSE(w32, "requested window size larger than the screen\n");
@@ -1038,14 +1043,7 @@ static int reinit_window_state(struct vo_w32_state *w32)
(int)(r.bottom - r.top));
SetWindowPos(w32->window, layer, r.left, r.top, r.right - r.left,
- r.bottom - r.top, SWP_FRAMECHANGED);
- // For some reason, moving SWP_SHOWWINDOW to a second call works better
- // with wine: returning from fullscreen doesn't cause a bogus resize to
- // screen size.
- // It's not needed on Windows XP or wine with a virtual desktop.
- // It doesn't seem to have any negative effects.
- SetWindowPos(w32->window, NULL, 0, 0, 0, 0,
- SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);
+ r.bottom - r.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
signal_events(w32, VO_EVENT_RESIZE);
@@ -1070,8 +1068,6 @@ static void gui_thread_reconfig(void *ptr)
struct vo_win_geometry geo;
vo_calc_window_geometry(vo, &w32->screenrc, &geo);
vo_apply_window_geometry(vo, &geo);
- w32->dw = vo->dwidth;
- w32->dh = vo->dheight;
bool reset_size = w32->o_dwidth != vo->dwidth || w32->o_dheight != vo->dheight;
@@ -1105,6 +1101,9 @@ static void gui_thread_reconfig(void *ptr)
vo->dheight = r.bottom;
}
+ w32->dw = vo->dwidth;
+ w32->dh = vo->dheight;
+
*res = reinit_window_state(w32);
}
diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c
index aff3d7c..289ed04 100644
--- a/video/out/wayland_common.c
+++ b/video/out/wayland_common.c
@@ -812,6 +812,9 @@ static void frame_callback(void *data,
{
struct vo_wayland_state *wl = data;
+ if (wl->frame.function)
+ wl->frame.function(wl->frame.data, time);
+
if (callback)
wl_callback_destroy(callback);
@@ -823,7 +826,11 @@ static void frame_callback(void *data,
}
wl_callback_add_listener(wl->frame.callback, &frame_listener, wl);
+ wl_surface_commit(wl->window.video_surface);
+
+ wl->frame.last_us = mp_time_us();
wl->frame.pending = true;
+ wl->frame.dropping = false;
}
static const struct wl_callback_listener frame_listener = {
@@ -913,7 +920,6 @@ static bool create_window (struct vo_wayland_state *wl)
wl_shell_surface_set_class(wl->window.shell_surface, "mpv");
}
- frame_callback(wl, NULL, 0);
return true;
}
@@ -1083,7 +1089,7 @@ static void vo_wayland_fullscreen (struct vo *vo)
}
}
-static int vo_wayland_check_events (struct vo *vo)
+static int vo_wayland_poll (struct vo *vo, int timeout_msecs)
{
struct vo_wayland_state *wl = vo->wayland;
struct wl_display *dp = wl->display.display;
@@ -1102,7 +1108,8 @@ static int vo_wayland_check_events (struct vo *vo)
*
* when pausing no input events get queued so we have to check if there
* are events to read from the file descriptor through poll */
- if (poll(&fd, 1, 0) > 0) {
+ int polled;
+ if ((polled = poll(&fd, 1, timeout_msecs)) > 0) {
if (fd.revents & POLLERR || fd.revents & POLLHUP) {
MP_FATAL(wl, "error occurred on the display fd: "
"closing file descriptor\n");
@@ -1115,13 +1122,25 @@ static int vo_wayland_check_events (struct vo *vo)
wl_display_flush(dp);
}
+ return polled;
+}
+
+static int vo_wayland_check_events (struct vo *vo)
+{
+ struct vo_wayland_state *wl = vo->wayland;
+
+ vo_wayland_poll(vo, 0);
+
/* If drag & drop was ended poll the file descriptor from the offer if
* there is data to read.
* We only accept the mime type text/uri-list.
*/
if (wl->input.dnd_fd != -1) {
- fd.fd = wl->input.dnd_fd;
- fd.events = POLLIN | POLLHUP | POLLERR;
+ struct pollfd fd = {
+ wl->input.dnd_fd,
+ POLLIN | POLLERR | POLLHUP,
+ 0
+ };
if (poll(&fd, 1, 0) > 0) {
if (fd.revents & POLLERR) {
@@ -1157,7 +1176,7 @@ static int vo_wayland_check_events (struct vo *vo)
buffer[str_len] = 0;
struct bstr file_list = bstr0(buffer);
mp_event_drop_mime_data(vo->input_ctx, "text/uri-list",
- file_list);
+ file_list, DND_REPLACE);
break;
}
}
@@ -1297,3 +1316,39 @@ bool vo_wayland_config (struct vo *vo, uint32_t flags)
return true;
}
+
+void vo_wayland_request_frame(struct vo *vo, void *data, vo_wayland_frame_cb cb)
+{
+ struct vo_wayland_state *wl = vo->wayland;
+ wl->frame.data = data;
+ wl->frame.function = cb;
+ MP_DBG(wl, "restart frame callback\n");
+ frame_callback(wl, NULL, 0);
+}
+
+bool vo_wayland_wait_frame(struct vo *vo)
+{
+ struct vo_wayland_state *wl = vo->wayland;
+
+ if (!wl->frame.callback || wl->frame.dropping)
+ return false;
+
+ // If mpv isn't receiving frame callbacks (for 100ms), this usually means that
+ // mpv window is not visible and compositor tells kindly to not draw anything.
+ while (!wl->frame.pending) {
+ int64_t timeout = wl->frame.last_us + (100 * 1000) - mp_time_us();
+
+ if (timeout <= 0)
+ break;
+
+ if (vo_wayland_poll(vo, timeout) <= 0)
+ break;
+ }
+
+ wl->frame.dropping = !wl->frame.pending;
+ wl->frame.pending = false;
+
+ // Return false if the frame callback was not received
+ // Handler should act accordingly.
+ return !wl->frame.dropping;
+}
diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h
index d7c5052..6f3ac72 100644
--- a/video/out/wayland_common.h
+++ b/video/out/wayland_common.h
@@ -47,14 +47,19 @@ struct vo_wayland_output {
struct wl_list link;
};
+typedef void (*vo_wayland_frame_cb)(void *data, uint32_t time);
struct vo_wayland_state {
struct vo *vo;
struct mp_log* log;
struct {
+ void *data;
+ vo_wayland_frame_cb function;
struct wl_callback *callback;
+ uint64_t last_us;
bool pending;
+ bool dropping;
} frame;
#if HAVE_GL_WAYLAND
@@ -144,6 +149,8 @@ int vo_wayland_init(struct vo *vo);
void vo_wayland_uninit(struct vo *vo);
bool vo_wayland_config(struct vo *vo, uint32_t flags);
int vo_wayland_control(struct vo *vo, int *events, int request, void *arg);
+void vo_wayland_request_frame(struct vo *vo, void *data, vo_wayland_frame_cb cb);
+bool vo_wayland_wait_frame(struct vo *vo);
#endif /* MPLAYER_WAYLAND_COMMON_H */
diff --git a/video/out/x11_common.c b/video/out/x11_common.c
index 9a1165a..d077957 100644
--- a/video/out/x11_common.c
+++ b/video/out/x11_common.c
@@ -135,6 +135,8 @@ static void vo_x11_selectinput_witherr(struct vo *vo, Display *display,
static void vo_x11_setlayer(struct vo *vo, bool ontop);
static void vo_x11_xembed_handle_message(struct vo *vo, XClientMessageEvent *ce);
static void vo_x11_xembed_send_message(struct vo_x11_state *x11, long m[4]);
+static void vo_x11_move_resize(struct vo *vo, bool move, bool resize,
+ struct mp_rect rc);
#define XA(x11, s) (XInternAtom((x11)->display, # s, False))
#define XAs(x11, s) XInternAtom((x11)->display, s, False)
@@ -487,8 +489,10 @@ static void *screensaver_thread(void *arg)
break;
char *args[] = {"xdg-screensaver", "reset", NULL};
- if (mp_subprocess(args, NULL, NULL, NULL, NULL, &(char*){0})) {
- MP_WARN(x11, "Disabling screensaver failed.\n");
+ int status = mp_subprocess(args, NULL, NULL, NULL, NULL, &(char*){0});
+ if (status) {
+ MP_WARN(x11, "Disabling screensaver failed (%d). Make sure the "
+ "xdg-screensaver script is installed.\n", status);
break;
}
}
@@ -706,10 +710,6 @@ void vo_x11_uninit(struct vo *vo)
if (x11->window != None)
vo_set_cursor_hidden(vo, false);
- if (x11->f_gc != None)
- XFreeGC(vo->x11->display, x11->f_gc);
- if (x11->vo_gc != None)
- XFreeGC(vo->x11->display, x11->vo_gc);
if (x11->window != None && x11->window != x11->rootwin) {
XUnmapWindow(x11->display, x11->window);
XDestroyWindow(x11->display, x11->window);
@@ -793,6 +793,8 @@ static void vo_x11_dnd_handle_message(struct vo *vo, XClientMessageEvent *ce)
dnd_select_format(x11, args, 3);
}
} else if (ce->message_type == XA(x11, XdndPosition)) {
+ x11->dnd_requested_action = ce->data.l[4];
+
Window src = ce->data.l[0];
XEvent xev;
@@ -836,9 +838,13 @@ static void vo_x11_dnd_handle_selection(struct vo *vo, XSelectionEvent *se)
void *prop = x11_get_property(x11, x11->window, XAs(x11, DND_PROPERTY),
x11->dnd_requested_format, 8, &nitems);
if (prop) {
+ enum mp_dnd_action action =
+ x11->dnd_requested_action == XA(x11, XdndActionCopy) ?
+ DND_REPLACE : DND_APPEND;
+
// No idea if this is guaranteed to be \0-padded, so use bstr.
success = mp_event_drop_mime_data(vo->input_ctx, "text/uri-list",
- (bstr){prop, nitems}) > 0;
+ (bstr){prop, nitems}, action) > 0;
XFree(prop);
}
}
@@ -887,6 +893,46 @@ static int get_mods(unsigned int state)
return modifiers;
}
+static void vo_x11_check_net_wm_state_fullscreen_change(struct vo *vo)
+{
+ struct vo_x11_state *x11 = vo->x11;
+
+ if (x11->wm_type & vo_wm_FULLSCREEN) {
+ int num_elems;
+ long *elems = x11_get_property(x11, x11->window, XA(x11, _NET_WM_STATE),
+ XA_ATOM, 32, &num_elems);
+ int is_fullscreen = 0;
+ if (elems) {
+ Atom fullscreen_prop = XA(x11, _NET_WM_STATE_FULLSCREEN);
+ for (int n = 0; n < num_elems; n++) {
+ if (elems[n] == fullscreen_prop) {
+ is_fullscreen = 1;
+ break;
+ }
+ }
+ XFree(elems);
+ }
+
+ if ((vo->opts->fullscreen && !is_fullscreen) ||
+ (!vo->opts->fullscreen && is_fullscreen))
+ {
+ vo->opts->fullscreen = is_fullscreen;
+ x11->fs = is_fullscreen;
+
+ if (!is_fullscreen && (x11->pos_changed_during_fs ||
+ x11->size_changed_during_fs))
+ {
+ vo_x11_move_resize(vo, x11->pos_changed_during_fs,
+ x11->size_changed_during_fs,
+ x11->nofsrc);
+ }
+
+ x11->size_changed_during_fs = false;
+ x11->pos_changed_during_fs = false;
+ }
+ }
+}
+
int vo_x11_check_events(struct vo *vo)
{
struct vo_x11_state *x11 = vo->x11;
@@ -997,8 +1043,6 @@ int vo_x11_check_events(struct vo *vo)
get_mods(Event.xbutton.state) | MP_KEY_STATE_UP);
break;
case MapNotify:
- if (x11->window_hidden)
- vo_x11_clearwindow(vo, x11->window);
x11->window_hidden = false;
x11->pseudo_mapped = true;
vo_x11_update_geometry(vo);
@@ -1026,6 +1070,7 @@ int vo_x11_check_events(struct vo *vo)
}
} else if (Event.xproperty.atom == XA(x11, _NET_WM_STATE)) {
x11->pending_vo_events |= VO_EVENT_WIN_STATE;
+ vo_x11_check_net_wm_state_fullscreen_change(vo);
} else if (Event.xproperty.atom == x11->icc_profile_property) {
x11->pending_vo_events |= VO_EVENT_ICC_PROFILE_CHANGED;
}
@@ -1473,12 +1518,6 @@ void vo_x11_config_vo_window(struct vo *vo, XVisualInfo *vis, int flags,
x11->winrc = geo.win;
}
- if (!x11->f_gc && !x11->vo_gc) {
- x11->f_gc = XCreateGC(x11->display, x11->window, 0, 0);
- x11->vo_gc = XCreateGC(x11->display, x11->window, 0, NULL);
- XSetForeground(x11->display, x11->f_gc, 0);
- }
-
if (flags & VOFLAG_HIDDEN)
return;
@@ -1504,45 +1543,6 @@ void vo_x11_config_vo_window(struct vo *vo, XVisualInfo *vis, int flags,
x11->pending_vo_events &= ~VO_EVENT_RESIZE; // implicitly done by the VO
}
-static void fill_rect(struct vo *vo, GC gc, int x0, int y0, int x1, int y1)
-{
- struct vo_x11_state *x11 = vo->x11;
-
- x0 = MPMAX(x0, 0);
- y0 = MPMAX(y0, 0);
- x1 = MPMIN(x1, RC_W(x11->winrc));
- y1 = MPMIN(y1, RC_H(x11->winrc));
-
- if (x11->window && x1 > x0 && y1 > y0)
- XFillRectangle(x11->display, x11->window, gc, x0, y0, x1 - x0, y1 - y0);
-}
-
-// Clear everything outside of rc with the background color
-void vo_x11_clear_background(struct vo *vo, const struct mp_rect *rc)
-{
- struct vo_x11_state *x11 = vo->x11;
- GC gc = x11->f_gc;
-
- int w = RC_W(x11->winrc);
- int h = RC_H(x11->winrc);
-
- fill_rect(vo, gc, 0, 0, w, rc->y0); // top
- fill_rect(vo, gc, 0, rc->y1, w, h); // bottom
- fill_rect(vo, gc, 0, rc->y0, rc->x0, rc->y1); // left
- fill_rect(vo, gc, rc->x1, rc->y0, w, rc->y1); // right
-
- XFlush(x11->display);
-}
-
-void vo_x11_clearwindow(struct vo *vo, Window vo_window)
-{
- struct vo_x11_state *x11 = vo->x11;
- if (x11->f_gc == None)
- return;
- XFillRectangle(x11->display, vo_window, x11->f_gc, 0, 0, INT_MAX, INT_MAX);
- XFlush(x11->display);
-}
-
static void vo_x11_setlayer(struct vo *vo, bool ontop)
{
struct vo_x11_state *x11 = vo->x11;
diff --git a/video/out/x11_common.h b/video/out/x11_common.h
index c8ed22b..50892cf 100644
--- a/video/out/x11_common.h
+++ b/video/out/x11_common.h
@@ -70,8 +70,6 @@ struct vo_x11_state {
XIC xic;
bool no_autorepeat;
- GC f_gc; // used to paint background
- GC vo_gc; // used to paint video
Colormap colormap;
int wm_type;
@@ -114,6 +112,7 @@ struct vo_x11_state {
/* drag and drop */
Atom dnd_requested_format;
+ Atom dnd_requested_action;
Window dnd_src_window;
/* dragging the window */
@@ -128,8 +127,6 @@ int vo_x11_check_events(struct vo *vo);
bool vo_x11_screen_is_composited(struct vo *vo);
void vo_x11_config_vo_window(struct vo *vo, XVisualInfo *vis, int flags,
const char *classname);
-void vo_x11_clear_background(struct vo *vo, const struct mp_rect *rc);
-void vo_x11_clearwindow(struct vo *vo, Window vo_window);
int vo_x11_control(struct vo *vo, int *events, int request, void *arg);
#endif /* MPLAYER_X11_COMMON_H */
diff --git a/video/vaapi.c b/video/vaapi.c
index 8b95dde..9bc5953 100644
--- a/video/vaapi.c
+++ b/video/vaapi.c
@@ -36,13 +36,11 @@ bool check_va_status(struct mp_log *log, VAStatus status, const char *msg)
int va_get_colorspace_flag(enum mp_csp csp)
{
-#if USE_VAAPI_COLORSPACE
switch (csp) {
case MP_CSP_BT_601: return VA_SRC_BT601;
case MP_CSP_BT_709: return VA_SRC_BT709;
case MP_CSP_SMPTE_240M: return VA_SRC_SMPTE_240;
}
-#endif
return 0;
}
@@ -52,10 +50,10 @@ struct fmtentry {
};
static const struct fmtentry va_to_imgfmt[] = {
+ {VA_FOURCC_NV12, IMGFMT_NV12},
{VA_FOURCC_YV12, IMGFMT_420P},
{VA_FOURCC_I420, IMGFMT_420P},
{VA_FOURCC_IYUV, IMGFMT_420P},
- {VA_FOURCC_NV12, IMGFMT_NV12},
{VA_FOURCC_UYVY, IMGFMT_UYVY},
{VA_FOURCC_YUY2, IMGFMT_YUYV},
// Note: not sure about endian issues (the mp formats are byte-addressed)
@@ -114,12 +112,15 @@ static void va_get_formats(struct mp_vaapi_ctx *ctx)
ctx->image_formats = formats;
}
-struct mp_vaapi_ctx *va_initialize(VADisplay *display, struct mp_log *plog)
+struct mp_vaapi_ctx *va_initialize(VADisplay *display, struct mp_log *plog,
+ bool probing)
{
struct mp_vaapi_ctx *res = NULL;
struct mp_log *log = mp_log_new(NULL, plog, "/vaapi");
int major_version, minor_version;
int status = vaInitialize(display, &major_version, &minor_version);
+ if (status != VA_STATUS_SUCCESS && probing)
+ goto error;
if (!check_va_status(log, status, "vaInitialize()"))
goto error;
@@ -182,6 +183,11 @@ struct va_surface {
VASurfaceID id;
int rt_format;
+ // The actually allocated surface size (needed for cropping).
+ // mp_images can have a smaller size than this, which means they are
+ // cropped down to a smaller size by removing right/bottom pixels.
+ int w, h;
+
VAImage image; // used for software decoding case
bool is_derived; // is image derived by vaDeriveImage()?
};
@@ -224,7 +230,7 @@ static struct mp_image *alloc_surface(struct mp_vaapi_ctx *ctx, int rt_format,
VASurfaceID id = VA_INVALID_ID;
VAStatus status;
va_lock(ctx);
- status = vaCreateSurfaces(ctx->display, w, h, rt_format, 1, &id);
+ status = vaCreateSurfaces(ctx->display, rt_format, w, h, &id, 1, NULL, 0);
va_unlock(ctx);
if (!CHECK_VA_STATUS(ctx, "vaCreateSurfaces()"))
return NULL;
@@ -237,6 +243,8 @@ static struct mp_image *alloc_surface(struct mp_vaapi_ctx *ctx, int rt_format,
.ctx = ctx,
.id = id,
.rt_format = rt_format,
+ .w = w,
+ .h = h,
.display = ctx->display,
.image = { .image_id = VA_INVALID_ID, .buf = VA_INVALID_ID },
};
@@ -278,7 +286,7 @@ static int va_surface_image_alloc(struct mp_image *img, VAImageFormat *format)
if (status == VA_STATUS_SUCCESS) {
/* vaDeriveImage() is supported, check format */
if (p->image.format.fourcc == format->fourcc &&
- p->image.width == img->w && p->image.height == img->h)
+ p->image.width == p->w && p->image.height == p->h)
{
p->is_derived = true;
MP_VERBOSE(p->ctx, "Using vaDeriveImage()\n");
@@ -289,7 +297,7 @@ static int va_surface_image_alloc(struct mp_image *img, VAImageFormat *format)
}
if (status != VA_STATUS_SUCCESS) {
p->image.image_id = VA_INVALID_ID;
- status = vaCreateImage(p->display, format, img->w, img->h, &p->image);
+ status = vaCreateImage(p->display, format, p->w, p->h, &p->image);
if (!CHECK_VA_STATUS(p->ctx, "vaCreateImage()")) {
p->image.image_id = VA_INVALID_ID;
r = -1;
@@ -371,20 +379,24 @@ int va_surface_upload(struct mp_image *va_dst, struct mp_image *sw_src)
struct mp_image img;
if (!va_image_map(p->ctx, &p->image, &img))
return -1;
+ assert(sw_src->w <= img.w && sw_src->h <= img.h);
+ mp_image_set_size(&img, sw_src->w, sw_src->h); // copy only visible part
mp_image_copy(&img, sw_src);
va_image_unmap(p->ctx, &p->image);
if (!p->is_derived) {
va_lock(p->ctx);
- VAStatus status = vaPutImage2(p->display, p->id,
- p->image.image_id,
- 0, 0, sw_src->w, sw_src->h,
- 0, 0, sw_src->w, sw_src->h);
+ VAStatus status = vaPutImage(p->display, p->id,
+ p->image.image_id,
+ 0, 0, sw_src->w, sw_src->h,
+ 0, 0, sw_src->w, sw_src->h);
va_unlock(p->ctx);
if (!CHECK_VA_STATUS(p->ctx, "vaPutImage()"))
return -1;
}
+ if (p->is_derived)
+ va_surface_image_destroy(p);
return 0;
}
@@ -405,7 +417,7 @@ static struct mp_image *try_download(struct mp_image *src,
if (!p->is_derived) {
va_lock(p->ctx);
status = vaGetImage(p->display, p->id, 0, 0,
- src->w, src->h, image->image_id);
+ p->w, p->h, image->image_id);
va_unlock(p->ctx);
if (status != VA_STATUS_SUCCESS)
return NULL;
@@ -414,12 +426,17 @@ static struct mp_image *try_download(struct mp_image *src,
struct mp_image *dst = NULL;
struct mp_image tmp;
if (va_image_map(p->ctx, image, &tmp)) {
+ assert(src->w <= tmp.w && src->h <= tmp.h);
+ mp_image_set_size(&tmp, src->w, src->h); // copy only visible part
dst = mp_image_pool_get(pool, tmp.imgfmt, tmp.w, tmp.h);
- if (dst)
+ if (dst) {
mp_image_copy(dst, &tmp);
+ mp_image_copy_attributes(dst, src);
+ }
va_image_unmap(p->ctx, image);
}
- mp_image_copy_attributes(dst, src);
+ if (p->is_derived)
+ va_surface_image_destroy(p);
return dst;
}
@@ -443,13 +460,18 @@ struct mp_image *va_surface_download(struct mp_image *src,
return mpi;
// We have no clue which format will work, so try them all.
- for (int i = 0; i < ctx->image_formats->num; i++) {
- VAImageFormat *format = &ctx->image_formats->entries[i];
- if (va_surface_image_alloc(src, format) < 0)
- continue;
- mpi = try_download(src, pool);
- if (mpi)
- return mpi;
+ // Make sure to start with the most preferred format (nv12), to avoid
+ // slower code paths.
+ for (int n = 0; va_to_imgfmt[n].mp; n++) {
+ VAImageFormat *format =
+ va_image_format_from_imgfmt(ctx, va_to_imgfmt[n].mp);
+ if (format) {
+ if (va_surface_image_alloc(src, format) < 0)
+ continue;
+ mpi = try_download(src, pool);
+ if (mpi)
+ return mpi;
+ }
}
MP_ERR(ctx, "failed to get surface data.\n");
diff --git a/video/vaapi.h b/video/vaapi.h
index 98e21d5..7ed6166 100644
--- a/video/vaapi.h
+++ b/video/vaapi.h
@@ -1,3 +1,20 @@
+/*
+ * 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/>.
+ */
+
#ifndef MPV_VAAPI_H
#define MPV_VAAPI_H
@@ -7,57 +24,6 @@
#include <va/va.h>
#include <va/va_x11.h>
-/* Compatibility glue with VA-API >= 0.31 */
-#if defined VA_CHECK_VERSION
-#if VA_CHECK_VERSION(0,31,0)
-#define vaPutImage2 vaPutImage
-#define vaAssociateSubpicture2 vaAssociateSubpicture
-#endif
-#endif
-
-/* Compatibility glue with VA-API >= 0.34 */
-#if VA_CHECK_VERSION(0,34,0)
-#include <va/va_compat.h>
-#endif
-
-/* Compatibility glue with upstream libva */
-#ifndef VA_SDS_VERSION
-#define VA_SDS_VERSION 0
-#endif
-
-/* Compatibility glue with VA-API >= 0.30 */
-#ifndef VA_INVALID_ID
-#define VA_INVALID_ID 0xffffffff
-#endif
-#ifndef VA_FOURCC
-#define VA_FOURCC(ch0, ch1, ch2, ch3) \
- ((uint32_t)(uint8_t)(ch0) | \
- ((uint32_t)(uint8_t)(ch1) << 8) | \
- ((uint32_t)(uint8_t)(ch2) << 16) | \
- ((uint32_t)(uint8_t)(ch3) << 24 ))
-#endif
-#if defined VA_SRC_BT601 && defined VA_SRC_BT709
-# define USE_VAAPI_COLORSPACE 1
-#else
-# define USE_VAAPI_COLORSPACE 0
-#endif
-
-/* Compatibility glue with VA-API >= 0.31.1 */
-#ifndef VA_SRC_SMPTE_240
-#define VA_SRC_SMPTE_240 0x00000040
-#endif
-#if defined VA_FILTER_SCALING_MASK
-# define USE_VAAPI_SCALING 1
-#else
-# define USE_VAAPI_SCALING 0
-#endif
-
-#ifndef VA_FOURCC_YV12
-#define VA_FOURCC_YV12 0x32315659
-#endif
-#ifndef VA_FOURCC_IYUV
-#define VA_FOURCC_IYUV 0x56555949
-#endif
#ifndef VA_FOURCC_I420
#define VA_FOURCC_I420 VA_FOURCC('I', '4', '2', '0')
#endif
@@ -94,7 +60,7 @@ bool check_va_status(struct mp_log *log, VAStatus status, const char *msg);
int va_get_colorspace_flag(enum mp_csp csp);
-struct mp_vaapi_ctx *va_initialize(VADisplay *display, struct mp_log *log);
+struct mp_vaapi_ctx * va_initialize(VADisplay *display, struct mp_log *plog, bool probing);
void va_destroy(struct mp_vaapi_ctx *ctx);
enum mp_imgfmt va_fourcc_to_imgfmt(uint32_t fourcc);
diff --git a/video/vdpau.c b/video/vdpau.c
index 2fd75a2..55ca8d9 100644
--- a/video/vdpau.c
+++ b/video/vdpau.c
@@ -113,7 +113,7 @@ static void preemption_callback(VdpDevice device, void *context)
pthread_mutex_unlock(&ctx->preempt_lock);
}
-static int win_x11_init_vdpau_procs(struct mp_vdpau_ctx *ctx)
+static int win_x11_init_vdpau_procs(struct mp_vdpau_ctx *ctx, bool probing)
{
Display *x11 = ctx->x11;
VdpStatus vdp_st;
@@ -142,11 +142,14 @@ static int win_x11_init_vdpau_procs(struct mp_vdpau_ctx *ctx)
vdp_st = vdp_device_create_x11(x11, DefaultScreen(x11), &ctx->vdp_device,
&get_proc_address);
if (vdp_st != VDP_STATUS_OK) {
- if (ctx->is_preempted)
+ if (ctx->is_preempted) {
MP_DBG(ctx, "Error calling vdp_device_create_x11 while preempted: %d\n",
vdp_st);
- else
- MP_ERR(ctx, "Error when calling vdp_device_create_x11: %d\n", vdp_st);
+ } else {
+ int lev = probing ? MSGL_V : MSGL_ERR;
+ mp_msg(ctx->log, lev, "Error when calling vdp_device_create_x11: %d\n",
+ vdp_st);
+ }
return -1;
}
@@ -182,7 +185,7 @@ static int handle_preemption(struct mp_vdpau_ctx *ctx)
if (ctx->last_preemption_retry_fail &&
mp_time_sec() - ctx->last_preemption_retry_fail < 1.0)
return -1;
- if (win_x11_init_vdpau_procs(ctx) < 0) {
+ if (win_x11_init_vdpau_procs(ctx, false) < 0) {
ctx->last_preemption_retry_fail = mp_time_sec();
return -1;
}
@@ -197,6 +200,7 @@ static int handle_preemption(struct mp_vdpau_ctx *ctx)
// Check whether vdpau display preemption happened. The caller provides a
// preemption counter, which contains the logical timestamp of the last
// preemption handled by the caller. The counter can be 0 for init.
+// If counter is NULL, only ever return -1 or 1.
// Return values:
// -1: the display is currently preempted, and vdpau can't be used
// 0: a preemption event happened, and the caller must recover
@@ -208,13 +212,13 @@ int mp_vdpau_handle_preemption(struct mp_vdpau_ctx *ctx, uint64_t *counter)
pthread_mutex_lock(&ctx->preempt_lock);
// First time init
- if (!*counter)
+ if (counter && !*counter)
*counter = ctx->preemption_counter;
if (handle_preemption(ctx) < 0)
r = -1;
- if (r > 0 && *counter < ctx->preemption_counter) {
+ if (counter && r > 0 && *counter < ctx->preemption_counter) {
*counter = ctx->preemption_counter;
r = 0; // signal recovery after preemption
}
@@ -329,16 +333,22 @@ static struct mp_image *mp_vdpau_get_surface(struct mp_vdpau_ctx *ctx,
e->rgb = rgb;
e->w = w;
e->h = h;
- if (rgb) {
- vdp_st = vdp->output_surface_create(ctx->vdp_device, rgb_format,
- w, h, &e->osurface);
- e->allocated = e->osurface != VDP_INVALID_HANDLE;
+ if (mp_vdpau_handle_preemption(ctx, NULL) >= 0) {
+ if (rgb) {
+ vdp_st = vdp->output_surface_create(ctx->vdp_device, rgb_format,
+ w, h, &e->osurface);
+ e->allocated = e->osurface != VDP_INVALID_HANDLE;
+ } else {
+ vdp_st = vdp->video_surface_create(ctx->vdp_device, chroma,
+ w, h, &e->surface);
+ e->allocated = e->surface != VDP_INVALID_HANDLE;
+ }
+ CHECK_VDP_WARNING(ctx, "Error when allocating surface");
} else {
- vdp_st = vdp->video_surface_create(ctx->vdp_device, chroma,
- w, h, &e->surface);
- e->allocated = e->surface != VDP_INVALID_HANDLE;
+ e->allocated = false;
+ e->osurface = VDP_INVALID_HANDLE;
+ e->surface = VDP_INVALID_HANDLE;
}
- CHECK_VDP_WARNING(ctx, "Error when allocating surface");
surface_index = n;
goto done;
}
@@ -362,7 +372,8 @@ struct mp_image *mp_vdpau_get_video_surface(struct mp_vdpau_ctx *ctx,
return mp_vdpau_get_surface(ctx, chroma, 0, false, w, h);
}
-struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, Display *x11)
+struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, Display *x11,
+ bool probing)
{
struct mp_vdpau_ctx *ctx = talloc_ptrtype(NULL, ctx);
*ctx = (struct mp_vdpau_ctx) {
@@ -382,7 +393,7 @@ struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, Display *x11
mark_vdpau_objects_uninitialized(ctx);
- if (win_x11_init_vdpau_procs(ctx) < 0) {
+ if (win_x11_init_vdpau_procs(ctx, probing) < 0) {
mp_vdpau_destroy(ctx);
return NULL;
}
diff --git a/video/vdpau.h b/video/vdpau.h
index 2304ecd..2e9269a 100644
--- a/video/vdpau.h
+++ b/video/vdpau.h
@@ -79,7 +79,8 @@ struct mp_vdpau_ctx {
int getimg_w, getimg_h;
};
-struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, Display *x11);
+struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, Display *x11,
+ bool probing);
void mp_vdpau_destroy(struct mp_vdpau_ctx *ctx);
int mp_vdpau_handle_preemption(struct mp_vdpau_ctx *ctx, uint64_t *counter);
diff --git a/video/vdpau_functions.inc b/video/vdpau_functions.inc
index 001a0e9..22c612c 100644
--- a/video/vdpau_functions.inc
+++ b/video/vdpau_functions.inc
@@ -45,3 +45,6 @@ VDP_FUNCTION(VdpVideoSurfaceDestroy, VDP_FUNC_ID_VIDEO_SURFACE_DESTROY, video_su
VDP_FUNCTION(VdpVideoSurfacePutBitsYCbCr, VDP_FUNC_ID_VIDEO_SURFACE_PUT_BITS_Y_CB_CR, video_surface_put_bits_y_cb_cr)
VDP_FUNCTION(VdpVideoSurfaceGetBitsYCbCr, VDP_FUNC_ID_VIDEO_SURFACE_GET_BITS_Y_CB_CR, video_surface_get_bits_y_cb_cr)
VDP_FUNCTION(VdpVideoSurfaceGetParameters, VDP_FUNC_ID_VIDEO_SURFACE_GET_PARAMETERS, video_surface_get_parameters)
+VDP_FUNCTION(VdpVideoSurfaceQueryCapabilities, VDP_FUNC_ID_VIDEO_SURFACE_QUERY_CAPABILITIES, video_surface_query_capabilities)
+VDP_FUNCTION(VdpOutputSurfaceQueryCapabilities, VDP_FUNC_ID_OUTPUT_SURFACE_QUERY_CAPABILITIES, output_surface_query_capabilities)
+VDP_FUNCTION(VdpOutputSurfaceGetParameters, VDP_FUNC_ID_OUTPUT_SURFACE_GET_PARAMETERS, output_surface_get_parameters)
diff --git a/video/vdpau_mixer.c b/video/vdpau_mixer.c
index 1c62113..d568285 100644
--- a/video/vdpau_mixer.c
+++ b/video/vdpau_mixer.c
@@ -67,7 +67,6 @@ struct mp_vdpau_mixer *mp_vdpau_mixer_create(struct mp_vdpau_ctx *vdp_ctx,
.ctx = vdp_ctx,
.log = log,
.video_mixer = VDP_INVALID_HANDLE,
- .chroma_type = VDP_CHROMA_TYPE_420,
.video_eq = {
.capabilities = MP_CSP_EQ_CAPS_COLORMATRIX,
},
@@ -114,7 +113,8 @@ static int set_video_attribute(struct mp_vdpau_mixer *mixer,
#define SET_VIDEO_ATTR(attr_name, attr_type, value) set_video_attribute(mixer, \
VDP_VIDEO_MIXER_ATTRIBUTE_ ## attr_name, &(attr_type){value},\
# attr_name)
-static int create_vdp_mixer(struct mp_vdpau_mixer *mixer)
+static int create_vdp_mixer(struct mp_vdpau_mixer *mixer,
+ VdpChromaType chroma_type, uint32_t w, uint32_t h)
{
struct vdp_functions *vdp = &mixer->ctx->vdp;
VdpDevice vdp_device = mixer->ctx->vdp_device;
@@ -135,9 +135,9 @@ static int create_vdp_mixer(struct mp_vdpau_mixer *mixer)
VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE,
};
const void *const parameter_values[VDP_NUM_MIXER_PARAMETER] = {
- &(uint32_t){mixer->image_params.w},
- &(uint32_t){mixer->image_params.h},
- &(VdpChromaType){mixer->chroma_type},
+ &(uint32_t){w},
+ &(uint32_t){h},
+ &(VdpChromaType){chroma_type},
};
if (opts->deint >= 3)
features[feature_count++] = VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL;
@@ -176,6 +176,9 @@ static int create_vdp_mixer(struct mp_vdpau_mixer *mixer)
CHECK_VDP_ERROR(mixer, "Error when calling vdp_video_mixer_create");
mixer->initialized = true;
+ mixer->current_chroma_type = chroma_type;
+ mixer->current_w = w;
+ mixer->current_h = h;
for (i = 0; i < feature_count; i++)
feature_enables[i] = VDP_TRUE;
@@ -257,8 +260,17 @@ int mp_vdpau_mixer_render(struct mp_vdpau_mixer *mixer,
if (mixer->video_mixer == VDP_INVALID_HANDLE)
mixer->initialized = false;
+ VdpChromaType s_chroma_type;
+ uint32_t s_w, s_h;
+
+ vdp_st = vdp->video_surface_get_parameters(frame->current, &s_chroma_type,
+ &s_w, &s_h);
+ CHECK_VDP_ERROR(mixer, "Error when calling vdp_video_surface_get_parameters");
+
if (!mixer->initialized || !opts_equal(opts, &mixer->opts) ||
- !mp_image_params_equal(&video->params, &mixer->image_params))
+ !mp_image_params_equal(&video->params, &mixer->image_params) ||
+ mixer->current_w != s_w || mixer->current_h != s_h ||
+ mixer->current_chroma_type != s_chroma_type)
{
mixer->opts = *opts;
mixer->image_params = video->params;
@@ -268,7 +280,7 @@ int mp_vdpau_mixer_render(struct mp_vdpau_mixer *mixer,
}
mixer->video_mixer = VDP_INVALID_HANDLE;
mixer->initialized = false;
- if (create_vdp_mixer(mixer) < 0)
+ if (create_vdp_mixer(mixer, s_chroma_type, s_w, s_h) < 0)
return -1;
}
diff --git a/video/vdpau_mixer.h b/video/vdpau_mixer.h
index f9a7953..97bef86 100644
--- a/video/vdpau_mixer.h
+++ b/video/vdpau_mixer.h
@@ -34,7 +34,9 @@ struct mp_vdpau_mixer {
struct mp_image_params image_params;
struct mp_vdpau_mixer_opts opts;
- VdpChromaType chroma_type;
+
+ VdpChromaType current_chroma_type;
+ int current_w, current_h;
// set initialized=false to force reinit when changed
struct mp_csp_equalizer video_eq;
diff --git a/waftools/checks/custom.py b/waftools/checks/custom.py
index dfda552..dcdaf2f 100644
--- a/waftools/checks/custom.py
+++ b/waftools/checks/custom.py
@@ -1,4 +1,4 @@
-from waftools.inflectors import DependencyInflector
+from waftools import inflector
from waftools.checks.generic import *
from waflib import Utils
import os
@@ -88,8 +88,7 @@ def check_oss_4front(ctx, dependency_identifier):
# avoid false positive from native sys/soundcard.h
if not oss_libdir:
- defkey = DependencyInflector(dependency_identifier).define_key()
- ctx.undefine(defkey)
+ ctx.undefine(inflector.define_key(dependency_identifier))
return False
soundcard_h = os.path.join(oss_libdir, "include/sys/soundcard.h")
diff --git a/waftools/checks/generic.py b/waftools/checks/generic.py
index 4b3a64f..703ff89 100644
--- a/waftools/checks/generic.py
+++ b/waftools/checks/generic.py
@@ -1,5 +1,5 @@
import os
-from inflectors import DependencyInflector
+import inflector
from waflib.ConfigSet import ConfigSet
from waflib import Utils
@@ -14,10 +14,10 @@ def even(n):
return n % 2 == 0
def __define_options__(dependency_identifier):
- return DependencyInflector(dependency_identifier).define_dict()
+ return inflector.define_dict(dependency_identifier)
def __merge_options__(dependency_identifier, *args):
- options_accu = DependencyInflector(dependency_identifier).storage_dict()
+ options_accu = inflector.storage_dict(dependency_identifier)
options_accu['mandatory'] = False
[options_accu.update(arg) for arg in args if arg]
return options_accu
@@ -99,7 +99,7 @@ def check_pkg_config(*args, **kw_ext):
result = bool(ctx.check_cfg(**opts))
ConfigSet.append_unique = original_append_unique
- defkey = DependencyInflector(dependency_identifier).define_key()
+ defkey = inflector.define_key(dependency_identifier)
if result:
ctx.define(defkey, 1)
else:
@@ -113,8 +113,7 @@ def check_headers(*headers, **kw_ext):
def undef_others(ctx, headers, found):
not_found_hs = set(headers) - set([found])
for not_found_h in not_found_hs:
- defkey = DependencyInflector(not_found_h).define_key()
- ctx.undefine(defkey)
+ ctx.undefine(inflector.define_key(not_found_h))
def fn(ctx, dependency_identifier):
for header in headers:
@@ -122,16 +121,14 @@ def check_headers(*headers, **kw_ext):
options = __merge_options__(dependency_identifier, defaults, kw_ext)
if ctx.check(**options):
undef_others(ctx, headers, header)
- defkey = DependencyInflector(dependency_identifier).define_key()
- ctx.define(defkey, 1)
+ ctx.define(inflector.define_key(dependency_identifier), 1)
return True
undef_others(ctx, headers, None)
return False
return fn
def check_true(ctx, dependency_identifier):
- defkey = DependencyInflector(dependency_identifier).define_key()
- ctx.define(defkey, 1)
+ ctx.define(inflector.define_key(dependency_identifier), 1)
return True
def check_ctx_vars(*variables):
@@ -151,8 +148,7 @@ def check_ctx_vars(*variables):
return fn
def check_stub(ctx, dependency_identifier):
- defkey = DependencyInflector(dependency_identifier).define_key()
- ctx.undefine(defkey)
+ ctx.undefine(inflector.define_key(dependency_identifier))
return False
def compose_checks(*checks):
diff --git a/waftools/dependencies.py b/waftools/dependencies.py
index aa638f0..c1f3fdb 100644
--- a/waftools/dependencies.py
+++ b/waftools/dependencies.py
@@ -2,7 +2,7 @@ from waflib.Errors import ConfigurationError, WafError
from waflib.Configure import conf
from waflib.Build import BuildContext
from waflib.Logs import pprint
-from inflectors import DependencyInflector
+import inflector
class DependencyError(Exception):
pass
@@ -43,8 +43,7 @@ class Dependency(object):
# No check was run, since the prerequisites of the dependency are
# not satisfied. Make sure the define is 'undefined' so that we
# get a `#define YYY 0` in `config.h`.
- def_key = DependencyInflector(self.identifier).define_key()
- self.ctx.undefine(def_key)
+ self.ctx.undefine(inflector.define_key(self.identifier))
self.fatal_if_needed()
return
@@ -96,6 +95,7 @@ the autodetection check failed.".format(self.identifier)
self.success(self.identifier)
else:
self.fail()
+ self.ctx.undefine(inflector.define_key(self.identifier))
self.fatal_if_needed()
def enabled_option(self, identifier=None):
@@ -220,8 +220,7 @@ def env_fetch(tx):
return fn
def dependencies_use(ctx):
- return [DependencyInflector(dep).storage_key() for \
- dep in ctx.env.satisfied_deps]
+ return [inflector.storage_key(dep) for dep in ctx.env.satisfied_deps]
BuildContext.filtered_sources = filtered_sources
BuildContext.dependencies_use = dependencies_use
diff --git a/waftools/detections/compiler.py b/waftools/detections/compiler.py
index 7cae75e..d3e6b34 100644
--- a/waftools/detections/compiler.py
+++ b/waftools/detections/compiler.py
@@ -35,12 +35,13 @@ def __add_generic_flags__(ctx):
"-Wdisabled-optimization",
"-Wstrict-prototypes",
"-Wno-format-zero-length",
- "-Werror=format-security"])
+ "-Werror=format-security",
+ "-Wno-redundant-decls"])
def __add_gcc_flags__(ctx):
ctx.env.CFLAGS += ["-Wall", "-Wundef", "-Wmissing-prototypes", "-Wshadow",
"-Wno-switch", "-Wparentheses", "-Wpointer-arith",
- "-Wredundant-decls", "-Wno-pointer-sign"]
+ "-Wno-pointer-sign"]
def __add_clang_flags__(ctx):
ctx.env.CFLAGS += ["-Wno-logical-op-parentheses", "-fcolor-diagnostics",
diff --git a/waftools/generators/headers.py b/waftools/generators/headers.py
index 546dd03..3c83292 100644
--- a/waftools/generators/headers.py
+++ b/waftools/generators/headers.py
@@ -4,46 +4,21 @@ def __cp_to_variant__(ctx, variant, basename):
node.parent.mkdir()
node.write(src)
-def __get_version__(ctx):
- import subprocess
- process = subprocess.Popen(["sh", "./version.sh", "--print"],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- cwd=ctx.srcnode.abspath())
- (version, err) = process.communicate()
- version = version.strip()
- if not isinstance(version, str):
- version = version.decode('utf-8')
- return version
-
-def __get_build_date__():
- import time
- return time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())
-
def __write_config_h__(ctx):
ctx.start_msg("Writing configuration header:")
ctx.write_config_header('config.h')
__cp_to_variant__(ctx, ctx.options.variant, 'config.h')
ctx.end_msg("config.h", "PINK")
-def __write_version_h__(ctx):
- ctx.start_msg("Writing header:")
- ctx.env.VERSION = __get_version__(ctx)
- ctx.define("VERSION", ctx.env.VERSION)
- ctx.define("BUILDDATE", __get_build_date__())
- ctx.write_config_header("version.h")
- __cp_to_variant__(ctx, ctx.options.variant, 'version.h')
- ctx.end_msg("version.h", "PINK")
-
# Approximately escape the string as C string literal
def __escape_c_string(s):
return s.replace("\"", "\\\"").replace("\n", "\\n")
def __get_features_string__(ctx):
- from inflectors import DependencyInflector
+ import inflector
stuff = []
for dependency_identifier in ctx.satisfied_deps:
- defkey = DependencyInflector(dependency_identifier).define_key()
+ defkey = inflector.define_key(dependency_identifier)
if ctx.is_defined(defkey) and ctx.get_define(defkey) == "1":
stuff.append(dependency_identifier)
stuff.sort()
@@ -58,4 +33,3 @@ def __add_mpv_defines__(ctx):
def configure(ctx):
__add_mpv_defines__(ctx)
__write_config_h__(ctx)
- __write_version_h__(ctx)
diff --git a/waftools/inflector.py b/waftools/inflector.py
new file mode 100644
index 0000000..184c74a
--- /dev/null
+++ b/waftools/inflector.py
@@ -0,0 +1,22 @@
+import re
+
+def _underscore(word):
+ """ Converts a word "into_it_s_underscored_version"
+ Convert any "CamelCased" or "ordinary Word" into an
+ "underscored_word"."""
+
+ return re.sub('[^A-Z^a-z^0-9]+', '_', \
+ re.sub('([a-z\d])([A-Z])', '\\1_\\2', \
+ re.sub('([A-Z]+)([A-Z][a-z])', '\\1_\\2', re.sub('::', '/', word)))).lower()
+
+def storage_key(dep):
+ return _underscore(dep)
+
+def define_key(dep):
+ return ("have_" + storage_key(dep)).upper()
+
+def define_dict(dep):
+ return {'define_name': define_key(dep)}
+
+def storage_dict(dep):
+ return {'uselib_store': storage_key(dep)}
diff --git a/waftools/inflectors.py b/waftools/inflectors.py
deleted file mode 100644
index 3641de8..0000000
--- a/waftools/inflectors.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import re
-
-class DependencyInflector(object):
- def __init__(self, dependency):
- self.dep = dependency
-
- def storage_key(self):
- return self.__underscore__(self.dep)
-
- def define_key(self):
- return ("have_" + self.storage_key()).upper()
-
- def define_dict(self):
- return {'define_name': self.define_key()}
-
- def storage_dict(self):
- return {'uselib_store': self.storage_key()}
-
- def __underscore__(self, word):
- """ Converts a word "into_it_s_underscored_version"
- Convert any "CamelCased" or "ordinary Word" into an
- "underscored_word"."""
-
- return re.sub('[^A-Z^a-z^0-9]+', '_', \
- re.sub('([a-z\d])([A-Z])', '\\1_\\2', \
- re.sub('([A-Z]+)([A-Z][a-z])', '\\1_\\2', re.sub('::', '/', word)))).lower()
diff --git a/wscript b/wscript
index 0bf62e9..13b13a2 100644
--- a/wscript
+++ b/wscript
@@ -83,7 +83,7 @@ build_options = [
}, {
'name': '--test',
'desc': 'test suite (using cmocka)',
- 'func': check_pkg_config('cmocka', '>= 0.4.1'),
+ 'func': check_pkg_config('cmocka', '>= 1.0.0'),
'default': 'disable',
}, {
'name': '--clang-database',
@@ -277,10 +277,6 @@ iconv support use --disable-iconv.",
'func': check_statement('sys/vfs.h',
'struct statfs fs; fstatfs(0, &fs); fs.f_namelen')
}, {
- 'name': '--libguess',
- 'desc': 'libguess support',
- 'func': check_pkg_config('libguess', '>= 1.0'),
- }, {
'name': '--libsmbclient',
'desc': 'Samba support',
'deps': [ 'libdl' ],
@@ -339,8 +335,19 @@ iconv support use --disable-iconv.",
}, {
'name': '--enca',
'desc': 'ENCA support',
+ 'deps': [ 'iconv' ],
'func': check_statement('enca.h', 'enca_get_languages(NULL)', lib='enca'),
}, {
+ 'name': '--libguess',
+ 'desc': 'libguess support',
+ 'deps': [ 'iconv' ],
+ 'func': check_pkg_config('libguess', '>= 1.0'),
+ }, {
+ 'name': '--uchardet',
+ 'desc': 'uchardet support',
+ 'deps': [ 'iconv' ],
+ 'func': check_pkg_config('uchardet'),
+ }, {
'name': '--ladspa',
'desc': 'LADSPA plugin support',
'func': check_statement('ladspa.h', 'LADSPA_Descriptor ld = {0}'),
@@ -370,6 +377,11 @@ iconv support use --disable-iconv.",
'desc': 'VapourSynth filter bridge (Lazy Lua)',
'deps': ['vapoursynth-core', 'lua'],
'func': check_true,
+ }, {
+ 'name': '--libarchive',
+ 'desc': 'libarchive wrapper for reading zip files and more',
+ 'func': check_pkg_config('libarchive >= 3.0.0'),
+ 'default': 'disable',
}
]
@@ -437,6 +449,12 @@ FFmpeg/Libav libraries. You need at least {0}. Aborting.".format(libav_versions_
'func': check_statement('libavutil/pixfmt.h',
'int x = AV_PIX_FMT_MMAL',
use='libav'),
+ }, {
+ 'name': 'av-version-info',
+ 'desc': 'libavtuil av_version_info()',
+ 'func': check_statement('libavutil/avutil.h',
+ 'const char *x = av_version_info()',
+ use='libav'),
}
]
@@ -621,7 +639,7 @@ video_output_features = [
'desc': 'VAAPI acceleration',
'deps': [ 'x11', 'libdl' ],
'func': check_pkg_config(
- 'libva', '>= 0.32.0', 'libva-x11', '>= 0.32.0'),
+ 'libva', '>= 0.34.0', 'libva-x11', '>= 0.34.0'),
}, {
'name': '--vaapi-vpp',
'desc': 'VAAPI VPP',
@@ -658,7 +676,7 @@ video_output_features = [
# including them (compensate with -isystem and -fgnu89-inline).
'name': '--rpi',
'desc': 'Raspberry Pi support',
- 'func':
+ 'func': compose_checks(
check_cc(cflags="-isystem/opt/vc/include/ "+
"-isystem/opt/vc/include/interface/vcos/pthreads " +
"-isystem/opt/vc/include/interface/vmcs_host/linux " +
@@ -666,23 +684,17 @@ video_output_features = [
linkflags="-L/opt/vc/lib",
header_name="bcm_host.h",
lib=['mmal_core', 'mmal_util', 'mmal_vc_client', 'bcm_host']),
- }, {
- 'name': '--rpi-gles',
- 'desc': 'GLES on Raspberry Pi',
- 'groups': [ 'gl' ],
- 'deps': ['rpi'],
- # We still need all OpenGL symbols, because the vo_opengl code is
- # generic and supports anything from GLES2/OpenGL 2.1 to OpenGL 4 core.
- 'func': compose_checks(
+ # We still need all OpenGL symbols, because the vo_opengl code is
+ # generic and supports anything from GLES2/OpenGL 2.1 to OpenGL 4 core.
check_cc(lib="EGL"),
check_cc(lib="GLESv2"),
check_statement('GL/gl.h', '(void)GL_RGB32F'), # arbitrary OpenGL 3.0 symbol
check_statement('GL/gl.h', '(void)GL_LUMINANCE16') # arbitrary OpenGL legacy-only symbol
- ),
+ ),
} , {
'name': '--gl',
'desc': 'OpenGL video outputs',
- 'deps_any': [ 'gl-cocoa', 'gl-x11', 'gl-win32', 'gl-wayland', 'rpi-gles' ],
+ 'deps_any': [ 'gl-cocoa', 'gl-x11', 'gl-win32', 'gl-wayland', 'rpi' ],
'func': check_true
}
]
@@ -702,17 +714,43 @@ hwaccel_features = [
'av_vda_alloc_context()',
framework='IOSurface',
use='libav')),
+ } , {
+ 'name': 'vda-default-init2',
+ 'desc': 'libavcodec VDA hwaccel (configurable AVVDAContext)',
+ 'deps': [ 'vda-hwaccel' ],
+ 'func': check_statement('libavcodec/vda.h',
+ 'av_vda_default_init2(NULL, NULL)',
+ use='libav'),
}, {
'name': '--vda-gl',
'desc': 'VDA with OpenGL',
'deps': [ 'gl-cocoa', 'vda-hwaccel' ],
'func': check_true
}, {
+ 'name': '--videotoolbox-hwaccel',
+ 'desc': 'libavcodec videotoolbox hwaccel',
+ 'func': compose_checks(
+ check_headers('VideoToolbox/VideoToolbox.h'),
+ check_statement('libavcodec/videotoolbox.h',
+ 'av_videotoolbox_alloc_context()',
+ framework='IOSurface',
+ use='libav')),
+ } , {
+ 'name': '--videotoolbox-gl',
+ 'desc': 'Videotoolbox with OpenGL',
+ 'deps': [ 'gl-cocoa', 'videotoolbox-hwaccel' ],
+ 'func': check_true
+ } , {
+ 'name': 'videotoolbox-vda-gl',
+ 'desc': 'Videotoolbox or VDA with OpenGL',
+ 'deps_any': [ 'videotoolbox-gl', 'vda-gl' ],
+ 'func': check_true
+ }, {
'name': '--vdpau-hwaccel',
'desc': 'libavcodec VDPAU hwaccel',
'deps': [ 'vdpau' ],
'func': check_statement('libavcodec/vdpau.h',
- 'av_vdpau_alloc_context()',
+ 'av_vdpau_bind_context(0,0,0,AV_HWACCEL_FLAG_ALLOW_HIGH_DEPTH)',
use='libav'),
}, {
'name': '--dxva2-hwaccel',
@@ -750,6 +788,7 @@ radio_and_tv_features = [
}, {
'name': '--pvr',
'desc': 'Video4Linux2 MPEG PVR interface',
+ 'deps': [ 'tv' ],
'func': check_cc(fragment=load_fragment('pvr.c')),
}, {
'name': '--audio-input',
@@ -896,6 +935,18 @@ def configure(ctx):
ctx.store_dependencies_lists()
+def __write_version__(ctx):
+ ctx.env.VERSIONH_ST = '--versionh="%s"'
+ ctx.env.CWD_ST = '--cwd="%s"'
+ ctx.env.VERSIONSH_CWD = [ctx.srcnode.abspath()]
+
+ ctx(
+ source = 'version.sh',
+ target = 'version.h',
+ rule = 'sh ${SRC} ${CWD_ST:VERSIONSH_CWD} ${VERSIONH_ST:TGT}',
+ always = True,
+ update_outputs = True)
+
def build(ctx):
if ctx.options.variant not in ctx.all_envs:
from waflib import Errors
@@ -903,6 +954,7 @@ def build(ctx):
'The project was not configured: run "waf --variant={0} configure" first!'
.format(ctx.options.variant))
ctx.unpack_dependencies_lists()
+ __write_version__(ctx)
ctx.load('wscript_build')
def init(ctx):
diff --git a/wscript_build.py b/wscript_build.py
index 93642c4..f306740 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -103,11 +103,8 @@ def build(ctx):
( "audio/filter/af_bs2b.c", "libbs2b" ),
( "audio/filter/af_center.c" ),
( "audio/filter/af_channels.c" ),
- ( "audio/filter/af_convert24.c" ),
- ( "audio/filter/af_convertsignendian.c" ),
( "audio/filter/af_delay.c" ),
( "audio/filter/af_drc.c" ),
- ( "audio/filter/af_dummy.c" ),
( "audio/filter/af_equalizer.c" ),
( "audio/filter/af_export.c" ),
( "audio/filter/af_extrastereo.c" ),
@@ -132,6 +129,7 @@ def build(ctx):
( "audio/out/ao.c" ),
( "audio/out/ao_alsa.c", "alsa" ),
( "audio/out/ao_coreaudio.c", "coreaudio" ),
+ ( "audio/out/ao_coreaudio_chmap.c", "coreaudio" ),
( "audio/out/ao_coreaudio_exclusive.c", "coreaudio" ),
( "audio/out/ao_coreaudio_properties.c", "coreaudio" ),
( "audio/out/ao_coreaudio_utils.c", "coreaudio" ),
@@ -166,11 +164,13 @@ def build(ctx):
## Demuxers
( "demux/codec_tags.c" ),
+ ( "demux/cue.c" ),
( "demux/demux.c" ),
( "demux/demux_cue.c" ),
( "demux/demux_disc.c" ),
( "demux/demux_edl.c" ),
( "demux/demux_lavf.c" ),
+ ( "demux/demux_libarchive.c", "libarchive" ),
( "demux/demux_libass.c", "libass"),
( "demux/demux_mf.c" ),
( "demux/demux_mkv.c" ),
@@ -215,7 +215,6 @@ def build(ctx):
( "player/client.c" ),
( "player/command.c" ),
( "player/configfiles.c" ),
- ( "player/discnav.c" ),
( "player/loadfile.c" ),
( "player/main.c" ),
( "player/misc.c" ),
@@ -249,6 +248,7 @@ def build(ctx):
( "stream/stream_edl.c" ),
( "stream/stream_file.c" ),
( "stream/stream_lavf.c" ),
+ ( "stream/stream_libarchive.c", "libarchive" ),
( "stream/stream_memory.c" ),
( "stream/stream_mf.c" ),
( "stream/stream_null.c" ),
@@ -294,6 +294,7 @@ def build(ctx):
( "video/decode/vaapi.c", "vaapi-hwaccel" ),
( "video/decode/vd_lavc.c" ),
( "video/decode/vda.c", "vda-hwaccel" ),
+ ( "video/decode/videotoolbox.c", "videotoolbox-hwaccel" ),
( "video/decode/vdpau.c", "vdpau-hwaccel" ),
( "video/filter/vf.c" ),
( "video/filter/vf_buffer.c" ),
@@ -320,6 +321,7 @@ def build(ctx):
( "video/filter/vf_vapoursynth.c", "vapoursynth-core" ),
( "video/filter/vf_vavpp.c", "vaapi-vpp"),
( "video/filter/vf_vdpaupp.c", "vdpau" ),
+ ( "video/filter/vf_vdpaurb.c", "vdpau" ),
( "video/filter/vf_yadif.c", "libavfilter"),
( "video/out/aspect.c" ),
( "video/out/bitmap_packer.c" ),
@@ -331,10 +333,11 @@ def build(ctx):
( "video/out/filter_kernels.c" ),
( "video/out/gl_cocoa.c", "gl-cocoa" ),
( "video/out/gl_common.c", "gl" ),
- ( "video/out/gl_rpi.c", "rpi-gles" ),
+ ( "video/out/gl_rpi.c", "rpi" ),
( "video/out/gl_hwdec.c", "gl" ),
+ ( "video/out/gl_hwdec_dxva2.c", "gl-win32" ),
( "video/out/gl_hwdec_vaglx.c", "vaapi-glx" ),
- ( "video/out/gl_hwdec_vda.c", "vda-gl" ),
+ ( "video/out/gl_hwdec_vda.c", "videotoolbox-vda-gl" ),
( "video/out/gl_hwdec_vdpau.c", "vdpau-gl-x11" ),
( "video/out/gl_lcms.c", "gl" ),
( "video/out/gl_osd.c", "gl" ),
@@ -358,7 +361,6 @@ def build(ctx):
( "video/out/vo_vaapi.c", "vaapi" ),
( "video/out/vo_vdpau.c", "vdpau" ),
( "video/out/vo_wayland.c", "wayland" ),
- ( "video/out/vo_x11.c" , "x11" ),
( "video/out/vo_xv.c", "xv" ),
( "video/out/w32_common.c", "win32" ),
( "video/out/wayland_common.c", "wayland" ),
@@ -416,6 +418,12 @@ def build(ctx):
ctx.path.find_node('osdep/mpv.rc'),
ctx.path.find_node(node))
+ version = ctx.bldnode.find_node('version.h')
+ if version:
+ ctx.add_manual_dependency(
+ ctx.path.find_node('osdep/mpv.rc'),
+ version)
+
if ctx.dependency_satisfied('cplayer') or ctx.dependency_satisfied('test'):
ctx(
target = "objects",