diff --git a/DOCS/client_api_examples/Copyright b/DOCS/client_api_examples/Copyright deleted file mode 100644 index 38d1891eb7..0000000000 --- a/DOCS/client_api_examples/Copyright +++ /dev/null @@ -1,14 +0,0 @@ -All examples in this directory and its sub-directories are licensed -under one of the following licenses: - - WTFPL, ISC, Ms-PL, AGPLv3, BSD (any) - -Pick any license of your liking, and disregard the others. - -(The full text of each license is available on this website: - http://opensource.org/licenses/alphabetical ) - -Additionally, you may consider the example code to be public domain. -You are free to use any of the example code without further -requirements or need for attribution. - diff --git a/DOCS/client_api_examples/README.md b/DOCS/client_api_examples/README.md deleted file mode 100644 index c21deb5579..0000000000 --- a/DOCS/client_api_examples/README.md +++ /dev/null @@ -1,114 +0,0 @@ -# Client API examples - -All these examples use the mpv client API through libmpv. - -## Where are the docs? - -The libmpv C API is documented directly in the header files (on normal Unix -systems, this is in `/usr/include/mpv/client.h`. - -libmpv merely gives you access to mpv's command interface, which is documented -here: -* Options (`mpv_set_option()` and friends): http://mpv.io/manual/master/#options -* Commands (`mpv_command()` and friends): http://mpv.io/manual/master/#list-of-input-commands -* Properties (`mpv_set_property()` and friends): http://mpv.io/manual/master/#properties - -Essentially everything is done with them, including loading a file, retrieving -playback progress, and so on. - -## Methods of embedding the video window - -All of these examples concentrate on how to integrate mpv's video renderers -with your own GUI. This is generally the hardest part. libmpv enforces a -somewhat efficient video output method, rather than e.g. returning a RGBA -surface in memory for each frame. The latter would be prohibitively inefficient, -because it would require conversion on the CPU. The goal is also not requiring -the API users to reinvent their own video rendering/scaling/presentation -mechanisms. - -There are currently 2 methods of embedding video. - -### Native window embedding - -This uses the platform's native method of nesting multiple windows. For example, -Linux/X11 can nest a window from a completely different process. The nested -window can redraw contents on its own, and receive user input if the user -interacts with this window. - -libmpv lets you specify a parent window for its own video window via the `wid` -option. Then libmpv will create its window with your window as parent, and -render its video inside of it. - -This method is highly OS-dependent. Some behavior is OS-specific. There are -problems with focusing on X11 (the ancient X11 focus policy mismatches with -that of modern UI toolkits - it's normally worked around, but this is not -easily possible with raw window embedding). It seems to have stability problems -on OSX when using the Qt toolkit. - -### OpenGL embedding - -This method lets you use libmpv's OpenGL renderer directly. You create an -OpenGL context, and then use `mpv_opengl_cb_draw()` to render the video on -each frame. - -This is more complicated, because libmpv will work directly on your own OpenGL -state. It's also not possible to have mpv automatically receive user input. -You will have to simulate this with the `mouse`/`keypress`/`keydown`/`keyup` -commands. - -You also get much more flexibility. For example, you can actually render your -own OSD on top of the video, something that is not possible with raw window -embedding. - -### Which one to use? - -Due to the various platform-specific behavior and problems (in particular on -OSX), OpenGL embedding is recommended. If you're not comfortable with requiring -OpenGL, or want to support "direct" video output such as vdpau (which might -win when it comes to performance and energy-saving), you should probably -support both methods if possible. - -## List of examples - -### simple - -Very primitive terminal-only example. Shows some most basic API usage. - -### cocoa - -Shows how to embed the mpv video window in Objective-C/Cocoa. - -### cocoa-openglcb - -Similar to the cocoa sample, but shows how to integrate mpv's OpenGL renderer -using libmpv's opengl-cb API. Since it does not require complicated interaction -with Cocoa elements from different libraries, it's more robust. - -### qt - -Shows how to embed the mpv video window in Qt (using normal desktop widgets). - -### qt_opengl - -Shows how to use mpv's OpenGL video renderer in Qt. This uses the opengl-cb API -for video. Since it does not rely on embedding "foreign" native Windows, it's -usually more robust, potentially faster, and it's easier to control how your -GUI interacts with the video. You can do your own OpenGL rendering on top of -the video as well. - -### qml - -Shows how to use mpv's OpenGL video renderer in QtQuick2 with QML. Uses the -opengl-cb API for video. Since the video is a normal QML element, it's trivial -to create OSD overlays with QML-native graphical elements as well. - -### qml_direct - -Alternative example, which typically avoids a FBO indirection. Might be -slightly faster, but is less flexible and harder to use. In particular, the -video is not a normal QML element. Uses the opengl-cb API for video. - -### sdl - -Show how to embed the mpv OpenGL renderer in SDL. Uses the opengl-cb API for -video. diff --git a/DOCS/client_api_examples/cocoa-openglcb/cocoa-openglcb.m b/DOCS/client_api_examples/cocoa-openglcb/cocoa-openglcb.m deleted file mode 100644 index 4a4e8aa533..0000000000 --- a/DOCS/client_api_examples/cocoa-openglcb/cocoa-openglcb.m +++ /dev/null @@ -1,304 +0,0 @@ -// 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 -#import - -#import -#import -#import - -#import - - -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 -{ - 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 deleted file mode 100644 index 5a78250c3a..0000000000 --- a/DOCS/client_api_examples/cocoa/cocoabasic.m +++ /dev/null @@ -1,209 +0,0 @@ -// 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` -framework cocoa - -#include - -#include -#include - -static inline void check_error(int status) -{ - if (status < 0) { - printf("mpv API error: %s\n", mpv_error_string(status)); - exit(1); - } -} - -#import - -@interface CocoaWindow : NSWindow -@end - -@implementation CocoaWindow -- (BOOL)canBecomeMainWindow { return YES; } -- (BOOL)canBecomeKeyWindow { return YES; } -@end - -@interface AppDelegate : NSObject -{ - mpv_handle *mpv; - dispatch_queue_t queue; - NSWindow *w; - NSView *wrapper; -} -@end - -static void wakeup(void *); - -@implementation AppDelegate - -- (void)createWindow { - - int mask = NSTitledWindowMask|NSClosableWindowMask| - NSMiniaturizableWindowMask|NSResizableWindowMask; - - self->w = [[CocoaWindow alloc] - initWithContentRect:NSMakeRect(0,0, 1280, 720) - styleMask:mask - backing:NSBackingStoreBuffered - defer:NO]; - - [self->w setTitle:@"cocoabasic example"]; - [self->w makeMainWindow]; - [self->w makeKeyAndOrderFront:nil]; - - NSRect frame = [[self->w contentView] bounds]; - self->wrapper = [[NSView alloc] initWithFrame:frame]; - [self->wrapper setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; - [[self->w contentView] addSubview:self->wrapper]; - [self->wrapper release]; - - 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: @"mpv_command('stop')" action:@selector(mpv_stop) keyEquivalent:@""]; - [sm addItemWithTitle: @"mpv_command('quit')" action:@selector(mpv_quit) keyEquivalent:@""]; - [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]; - - // Deal with MPV in the background. - queue = dispatch_queue_create("mpv", DISPATCH_QUEUE_SERIAL); - dispatch_async(queue, ^{ - - mpv = mpv_create(); - if (!mpv) { - printf("failed creating context\n"); - exit(1); - } - - int64_t wid = (intptr_t) self->wrapper; - check_error(mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid)); - - // Maybe set some options here, like default key bindings. - // NOTE: Interaction with the window seems to be broken for now. - check_error(mpv_set_option_string(mpv, "input-default-bindings", "yes")); - - // for testing! - check_error(mpv_set_option_string(mpv, "input-media-keys", "yes")); - check_error(mpv_set_option_string(mpv, "input-cursor", "no")); - check_error(mpv_set_option_string(mpv, "input-vo-keyboard", "yes")); - - // request important errors - check_error(mpv_request_log_messages(mpv, "warn")); - - check_error(mpv_initialize(mpv)); - - // 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)); - }); -} - -- (void) handleEvent:(mpv_event *)event -{ - switch (event->event_id) { - case MPV_EVENT_SHUTDOWN: { - mpv_detach_destroy(mpv); - 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); - } - - case MPV_EVENT_VIDEO_RECONFIG: { - dispatch_async(dispatch_get_main_queue(), ^{ - NSArray *subviews = [self->wrapper subviews]; - if ([subviews count] > 0) { - // mpv's events view - NSView *eview = [self->wrapper subviews][0]; - [self->w makeFirstResponder:eview]; - } - }); - } - - default: - printf("event: %s\n", mpv_event_name(event->event_id)); - } -} - -- (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]; -} - -// Ostensibly, mpv's window would be hooked up to this. -- (BOOL) windowShouldClose:(id)sender -{ - return NO; -} - -- (void) mpv_stop -{ - if (mpv) { - const char *args[] = {"stop", NULL}; - mpv_command(mpv, args); - } -} - -- (void) mpv_quit -{ - if (mpv) { - const char *args[] = {"quit", NULL}; - mpv_command(mpv, args); - } -} -@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/qml/main.cpp b/DOCS/client_api_examples/qml/main.cpp deleted file mode 100644 index 2122cd8c5c..0000000000 --- a/DOCS/client_api_examples/qml/main.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "main.h" - -#include -#include - -#include -#include -#include -#include - -#include - -#include -#include - -class MpvRenderer : public QQuickFramebufferObject::Renderer -{ - static void *get_proc_address(void *ctx, const char *name) { - (void)ctx; - QOpenGLContext *glctx = QOpenGLContext::currentContext(); - if (!glctx) - return NULL; - return (void *)glctx->getProcAddress(QByteArray(name)); - } - - mpv::qt::Handle mpv; - QQuickWindow *window; - mpv_opengl_cb_context *mpv_gl; -public: - MpvRenderer(const MpvObject *obj) - : mpv(obj->mpv), window(obj->window()), mpv_gl(obj->mpv_gl) - { - int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL); - if (r < 0) - throw std::runtime_error("could not initialize OpenGL"); - } - - virtual ~MpvRenderer() - { - // Until this call is done, we need to make sure the player remains - // alive. This is done implicitly with the mpv::qt::Handle instance - // in this class. - mpv_opengl_cb_uninit_gl(mpv_gl); - } - - void render() - { - QOpenGLFramebufferObject *fbo = framebufferObject(); - window->resetOpenGLState(); - mpv_opengl_cb_draw(mpv_gl, fbo->handle(), fbo->width(), fbo->height()); - window->resetOpenGLState(); - } -}; - -MpvObject::MpvObject(QQuickItem * parent) - : QQuickFramebufferObject(parent), mpv_gl(0) -{ - mpv = mpv::qt::Handle::FromRawHandle(mpv_create()); - if (!mpv) - throw std::runtime_error("could not create mpv context"); - - mpv_set_option_string(mpv, "terminal", "yes"); - mpv_set_option_string(mpv, "msg-level", "all=v"); - - if (mpv_initialize(mpv) < 0) - throw std::runtime_error("could not initialize mpv context"); - - // Make use of the MPV_SUB_API_OPENGL_CB API. - mpv::qt::set_option_variant(mpv, "vo", "opengl-cb"); - - // Request hw decoding, just for testing. - mpv::qt::set_option_variant(mpv, "hwdec", "auto"); - - // Setup the callback that will make QtQuick update and redraw if there - // is a new video frame. Use a queued connection: this makes sure the - // doUpdate() function is run on the GUI thread. - mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB); - if (!mpv_gl) - throw std::runtime_error("OpenGL not compiled in"); - mpv_opengl_cb_set_update_callback(mpv_gl, MpvObject::on_update, (void *)this); - connect(this, &MpvObject::onUpdate, this, &MpvObject::doUpdate, - Qt::QueuedConnection); -} - -MpvObject::~MpvObject() -{ - if (mpv_gl) - mpv_opengl_cb_set_update_callback(mpv_gl, NULL, NULL); -} - -void MpvObject::on_update(void *ctx) -{ - MpvObject *self = (MpvObject *)ctx; - emit self->onUpdate(); -} - -// connected to onUpdate(); signal makes sure it runs on the GUI thread -void MpvObject::doUpdate() -{ - update(); -} - -void MpvObject::command(const QVariant& params) -{ - mpv::qt::command_variant(mpv, params); -} - -void MpvObject::setProperty(const QString& name, const QVariant& value) -{ - mpv::qt::set_property_variant(mpv, name, value); -} - -QQuickFramebufferObject::Renderer *MpvObject::createRenderer() const -{ - window()->setPersistentOpenGLContext(true); - window()->setPersistentSceneGraph(true); - return new MpvRenderer(this); -} - -int main(int argc, char **argv) -{ - QGuiApplication app(argc, argv); - - // Qt sets the locale in the QGuiApplication constructor, but libmpv - // requires the LC_NUMERIC category to be set to "C", so change it back. - std::setlocale(LC_NUMERIC, "C"); - - qmlRegisterType("mpvtest", 1, 0, "MpvObject"); - - QQuickView view; - view.setResizeMode(QQuickView::SizeRootObjectToView); - view.setSource(QUrl("qrc:///mpvtest/main.qml")); - view.show(); - - return app.exec(); -} diff --git a/DOCS/client_api_examples/qml/main.h b/DOCS/client_api_examples/qml/main.h deleted file mode 100644 index 9a65ae5350..0000000000 --- a/DOCS/client_api_examples/qml/main.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef MPVRENDERER_H_ -#define MPVRENDERER_H_ - -#include - -#include -#include -#include - -class MpvRenderer; - -class MpvObject : public QQuickFramebufferObject -{ - Q_OBJECT - - mpv::qt::Handle mpv; - mpv_opengl_cb_context *mpv_gl; - - friend class MpvRenderer; - -public: - MpvObject(QQuickItem * parent = 0); - virtual ~MpvObject(); - virtual Renderer *createRenderer() const; -public slots: - void command(const QVariant& params); - void setProperty(const QString& name, const QVariant& value); -signals: - void onUpdate(); -private slots: - void doUpdate(); -private: - static void on_update(void *ctx); -}; - -#endif diff --git a/DOCS/client_api_examples/qml/main.qml b/DOCS/client_api_examples/qml/main.qml deleted file mode 100644 index ec06790e86..0000000000 --- a/DOCS/client_api_examples/qml/main.qml +++ /dev/null @@ -1,71 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0 - -import mpvtest 1.0 - -Item { - width: 1280 - height: 720 - - MpvObject { - id: renderer - anchors.fill: parent - - MouseArea { - anchors.fill: parent - onClicked: renderer.command(["loadfile", "../../../test.mkv"]) - } - } - - Rectangle { - id: labelFrame - anchors.margins: -50 - radius: 5 - color: "white" - border.color: "black" - opacity: 0.8 - anchors.fill: box - } - - Row { - id: box - anchors.bottom: renderer.bottom - anchors.left: renderer.left - anchors.right: renderer.right - anchors.margins: 100 - - Text { - anchors.margins: 10 - wrapMode: Text.WordWrap - text: "QtQuick and mpv are both rendering stuff.\n - Click to load ../../../test.mkv" - } - - // Don't take these controls too seriously. They're for testing. - Column { - CheckBox { - id: checkbox - anchors.margins: 10 - // Heavily filtered means good, right? - text: "Make video look like on a Smart TV" - onClicked: { - if (checkbox.checked) { - renderer.command(["vo_cmdline", "sharpen=5.0"]) - } else { - renderer.command(["vo_cmdline", ""]) - } - } - } - Slider { - id: slider - anchors.margins: 10 - anchors.left: checkbox.left - anchors.right: checkbox.right - minimumValue: -100 - maximumValue: 100 - value: 0 - onValueChanged: renderer.setProperty("gamma", slider.value | 0) - } - } - } -} diff --git a/DOCS/client_api_examples/qml/mpvtest.pro b/DOCS/client_api_examples/qml/mpvtest.pro deleted file mode 100644 index 0b2e96fe99..0000000000 --- a/DOCS/client_api_examples/qml/mpvtest.pro +++ /dev/null @@ -1,12 +0,0 @@ -QT += qml quick - -HEADERS += main.h -SOURCES += main.cpp - -QT_CONFIG -= no-pkg-config -CONFIG += link_pkgconfig debug -PKGCONFIG += mpv - -RESOURCES += mpvtest.qrc - -OTHER_FILES += main.qml diff --git a/DOCS/client_api_examples/qml/mpvtest.qrc b/DOCS/client_api_examples/qml/mpvtest.qrc deleted file mode 100644 index bb672657e5..0000000000 --- a/DOCS/client_api_examples/qml/mpvtest.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - main.qml - - diff --git a/DOCS/client_api_examples/qml_direct/main.cpp b/DOCS/client_api_examples/qml_direct/main.cpp deleted file mode 100644 index 37c6119707..0000000000 --- a/DOCS/client_api_examples/qml_direct/main.cpp +++ /dev/null @@ -1,159 +0,0 @@ -#include "main.h" - -#include -#include - -#include -#include -#include - -#include -#include -#include - -static void *get_proc_address(void *ctx, const char *name) { - (void)ctx; - QOpenGLContext *glctx = QOpenGLContext::currentContext(); - if (!glctx) - return NULL; - return (void *)glctx->getProcAddress(QByteArray(name)); -} - -MpvRenderer::MpvRenderer(mpv::qt::Handle a_mpv, mpv_opengl_cb_context *a_mpv_gl) - : mpv(a_mpv), mpv_gl(a_mpv_gl), window(0), size() -{ - int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL); - if (r < 0) - throw std::runtime_error("could not initialize OpenGL"); -} - -MpvRenderer::~MpvRenderer() -{ - // Until this call is done, we need to make sure the player remains - // alive. This is done implicitly with the mpv::qt::Handle instance - // in this class. - mpv_opengl_cb_uninit_gl(mpv_gl); -} - -void MpvRenderer::paint() -{ - window->resetOpenGLState(); - - // This uses 0 as framebuffer, which indicates that mpv will render directly - // to the frontbuffer. Note that mpv will always switch framebuffers - // explicitly. Some QWindow setups (such as using QQuickWidget) actually - // want you to render into a FBO in the beforeRendering() signal, and this - // code won't work there. - // The negation is used for rendering with OpenGL's flipped coordinates. - mpv_opengl_cb_draw(mpv_gl, 0, size.width(), -size.height()); - - window->resetOpenGLState(); -} - -MpvObject::MpvObject(QQuickItem * parent) - : QQuickItem(parent), mpv_gl(0), renderer(0) -{ - mpv = mpv::qt::Handle::FromRawHandle(mpv_create()); - if (!mpv) - throw std::runtime_error("could not create mpv context"); - - mpv_set_option_string(mpv, "terminal", "yes"); - mpv_set_option_string(mpv, "msg-level", "all=v"); - - if (mpv_initialize(mpv) < 0) - throw std::runtime_error("could not initialize mpv context"); - - // Make use of the MPV_SUB_API_OPENGL_CB API. - mpv::qt::set_option_variant(mpv, "vo", "opengl-cb"); - - // Setup the callback that will make QtQuick update and redraw if there - // is a new video frame. Use a queued connection: this makes sure the - // doUpdate() function is run on the GUI thread. - mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB); - if (!mpv_gl) - throw std::runtime_error("OpenGL not compiled in"); - mpv_opengl_cb_set_update_callback(mpv_gl, MpvObject::on_update, (void *)this); - connect(this, &MpvObject::onUpdate, this, &MpvObject::doUpdate, - Qt::QueuedConnection); - - connect(this, &QQuickItem::windowChanged, - this, &MpvObject::handleWindowChanged); -} - -MpvObject::~MpvObject() -{ - if (mpv_gl) - mpv_opengl_cb_set_update_callback(mpv_gl, NULL, NULL); -} - -void MpvObject::handleWindowChanged(QQuickWindow *win) -{ - if (!win) - return; - connect(win, &QQuickWindow::beforeSynchronizing, - this, &MpvObject::sync, Qt::DirectConnection); - connect(win, &QQuickWindow::sceneGraphInvalidated, - this, &MpvObject::cleanup, Qt::DirectConnection); - connect(win, &QQuickWindow::frameSwapped, - this, &MpvObject::swapped, Qt::DirectConnection); - win->setClearBeforeRendering(false); -} - -void MpvObject::sync() -{ - if (!renderer) { - renderer = new MpvRenderer(mpv, mpv_gl); - connect(window(), &QQuickWindow::beforeRendering, - renderer, &MpvRenderer::paint, Qt::DirectConnection); - } - renderer->window = window(); - renderer->size = window()->size() * window()->devicePixelRatio(); -} - -void MpvObject::swapped() -{ - mpv_opengl_cb_report_flip(mpv_gl, 0); -} - -void MpvObject::cleanup() -{ - if (renderer) { - delete renderer; - renderer = 0; - } -} - -void MpvObject::on_update(void *ctx) -{ - MpvObject *self = (MpvObject *)ctx; - emit self->onUpdate(); -} - -// connected to onUpdate(); signal makes sure it runs on the GUI thread -void MpvObject::doUpdate() -{ - window()->update(); -} - -void MpvObject::command(const QVariant& params) -{ - mpv::qt::command_variant(mpv, params); -} - -int main(int argc, char **argv) -{ - QGuiApplication app(argc, argv); - - // Qt sets the locale in the QGuiApplication constructor, but libmpv - // requires the LC_NUMERIC category to be set to "C", so change it back. - std::setlocale(LC_NUMERIC, "C"); - - qmlRegisterType("mpvtest", 1, 0, "MpvObject"); - - QQuickView view; - view.setResizeMode(QQuickView::SizeRootObjectToView); - view.setSource(QUrl("qrc:///mpvtest/main.qml")); - view.show(); - - return app.exec(); -} diff --git a/DOCS/client_api_examples/qml_direct/main.h b/DOCS/client_api_examples/qml_direct/main.h deleted file mode 100644 index b0310ff0be..0000000000 --- a/DOCS/client_api_examples/qml_direct/main.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef MPVRENDERER_H_ -#define MPVRENDERER_H_ - -#include - -#include -#include -#include - -class MpvRenderer : public QObject -{ - Q_OBJECT - mpv::qt::Handle mpv; - mpv_opengl_cb_context *mpv_gl; - QQuickWindow *window; - QSize size; - - friend class MpvObject; -public: - MpvRenderer(mpv::qt::Handle a_mpv, mpv_opengl_cb_context *a_mpv_gl); - virtual ~MpvRenderer(); -public slots: - void paint(); -}; - -class MpvObject : public QQuickItem -{ - Q_OBJECT - - mpv::qt::Handle mpv; - mpv_opengl_cb_context *mpv_gl; - MpvRenderer *renderer; - -public: - MpvObject(QQuickItem * parent = 0); - virtual ~MpvObject(); -public slots: - void command(const QVariant& params); - void sync(); - void swapped(); - void cleanup(); -signals: - void onUpdate(); -private slots: - void doUpdate(); - void handleWindowChanged(QQuickWindow *win); -private: - static void on_update(void *ctx); -}; - -#endif diff --git a/DOCS/client_api_examples/qml_direct/main.qml b/DOCS/client_api_examples/qml_direct/main.qml deleted file mode 100644 index 92be9bc405..0000000000 --- a/DOCS/client_api_examples/qml_direct/main.qml +++ /dev/null @@ -1,49 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0 - -import mpvtest 1.0 - -Item { - width: 1280 - height: 720 - - MpvObject { - id: renderer - - // This object isn't real and not visible; it just renders into the - // background of the containing Window. - width: 0 - height: 0 - } - - MouseArea { - anchors.fill: parent - onClicked: renderer.command(["loadfile", "../../../test.mkv"]) - } - - Rectangle { - id: labelFrame - anchors.margins: -50 - radius: 5 - color: "white" - border.color: "black" - opacity: 0.8 - anchors.fill: box - } - - Row { - id: box - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: 100 - - Text { - anchors.margins: 10 - wrapMode: Text.WordWrap - text: "QtQuick and mpv are both rendering stuff.\n - In this example, mpv is always in the background.\n - Click to load ../../../test.mkv" - } - } -} diff --git a/DOCS/client_api_examples/qml_direct/mpvtest.pro b/DOCS/client_api_examples/qml_direct/mpvtest.pro deleted file mode 100644 index 0b2e96fe99..0000000000 --- a/DOCS/client_api_examples/qml_direct/mpvtest.pro +++ /dev/null @@ -1,12 +0,0 @@ -QT += qml quick - -HEADERS += main.h -SOURCES += main.cpp - -QT_CONFIG -= no-pkg-config -CONFIG += link_pkgconfig debug -PKGCONFIG += mpv - -RESOURCES += mpvtest.qrc - -OTHER_FILES += main.qml diff --git a/DOCS/client_api_examples/qml_direct/mpvtest.qrc b/DOCS/client_api_examples/qml_direct/mpvtest.qrc deleted file mode 100644 index bb672657e5..0000000000 --- a/DOCS/client_api_examples/qml_direct/mpvtest.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - main.qml - - diff --git a/DOCS/client_api_examples/qt/qtexample.cpp b/DOCS/client_api_examples/qt/qtexample.cpp deleted file mode 100644 index 5f4e86e891..0000000000 --- a/DOCS/client_api_examples/qt/qtexample.cpp +++ /dev/null @@ -1,225 +0,0 @@ -// This example can be built with: qmake && make - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#if QT_VERSION >= 0x050000 -#include -#endif - -#include - -#include "qtexample.h" - -static void wakeup(void *ctx) -{ - // This callback is invoked from any mpv thread (but possibly also - // recursively from a thread that is calling the mpv API). Just notify - // the Qt GUI thread to wake up (so that it can process events with - // mpv_wait_event()), and return as quickly as possible. - MainWindow *mainwindow = (MainWindow *)ctx; - emit mainwindow->mpv_events(); -} - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent) -{ - setWindowTitle("Qt embedding demo"); - setMinimumSize(640, 480); - - QMenu *menu = menuBar()->addMenu(tr("&File")); - QAction *on_open = new QAction(tr("&Open"), this); - on_open->setShortcuts(QKeySequence::Open); - on_open->setStatusTip(tr("Open a file")); - connect(on_open, &QAction::triggered, this, &MainWindow::on_file_open); - menu->addAction(on_open); - - QAction *on_new = new QAction(tr("&New window"), this); - connect(on_new, &QAction::triggered, this, &MainWindow::on_new_window); - menu->addAction(on_new); - - statusBar(); - - QMainWindow *log_window = new QMainWindow(this); - log = new QTextEdit(log_window); - log->setReadOnly(true); - log_window->setCentralWidget(log); - log_window->setWindowTitle("mpv log window"); - log_window->setMinimumSize(500, 50); - log_window->show(); - - mpv = mpv_create(); - if (!mpv) - throw std::runtime_error("can't create mpv instance"); - - // Create a video child window. Force Qt to create a native window, and - // pass the window ID to the mpv wid option. Works on: X11, win32, Cocoa - mpv_container = new QWidget(this); - setCentralWidget(mpv_container); - mpv_container->setAttribute(Qt::WA_DontCreateNativeAncestors); - mpv_container->setAttribute(Qt::WA_NativeWindow); - // If you have a HWND, use: int64_t wid = (intptr_t)hwnd; - int64_t wid = mpv_container->winId(); - mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid); - - // Enable default bindings, because we're lazy. Normally, a player using - // mpv as backend would implement its own key bindings. - mpv_set_option_string(mpv, "input-default-bindings", "yes"); - - // Enable keyboard input on the X11 window. For the messy details, see - // --input-vo-keyboard on the manpage. - mpv_set_option_string(mpv, "input-vo-keyboard", "yes"); - - // Let us receive property change events with MPV_EVENT_PROPERTY_CHANGE if - // this property changes. - mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE); - - mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE); - mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE); - - // Request log messages with level "info" or higher. - // They are received as MPV_EVENT_LOG_MESSAGE. - mpv_request_log_messages(mpv, "info"); - - // From this point on, the wakeup function will be called. The callback - // can come from any thread, so we use the QueuedConnection mechanism to - // relay the wakeup in a thread-safe way. - connect(this, &MainWindow::mpv_events, this, &MainWindow::on_mpv_events, - Qt::QueuedConnection); - mpv_set_wakeup_callback(mpv, wakeup, this); - - if (mpv_initialize(mpv) < 0) - throw std::runtime_error("mpv failed to initialize"); -} - -void MainWindow::handle_mpv_event(mpv_event *event) -{ - switch (event->event_id) { - case MPV_EVENT_PROPERTY_CHANGE: { - mpv_event_property *prop = (mpv_event_property *)event->data; - if (strcmp(prop->name, "time-pos") == 0) { - if (prop->format == MPV_FORMAT_DOUBLE) { - double time = *(double *)prop->data; - std::stringstream ss; - ss << "At: " << time; - statusBar()->showMessage(QString::fromStdString(ss.str())); - } else if (prop->format == MPV_FORMAT_NONE) { - // The property is unavailable, which probably means playback - // was stopped. - statusBar()->showMessage(""); - } - } else if (strcmp(prop->name, "chapter-list") == 0 || - strcmp(prop->name, "track-list") == 0) - { - // Dump the properties as JSON for demo purposes. -#if QT_VERSION >= 0x050000 - if (prop->format == MPV_FORMAT_NODE) { - QVariant v = mpv::qt::node_to_variant((mpv_node *)prop->data); - // Abuse JSON support for easily printing the mpv_node contents. - QJsonDocument d = QJsonDocument::fromVariant(v); - append_log("Change property " + QString(prop->name) + ":\n"); - append_log(d.toJson().data()); - } -#endif - } - break; - } - case MPV_EVENT_VIDEO_RECONFIG: { - // Retrieve the new video size. - int64_t w, h; - if (mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &w) >= 0 && - mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) >= 0 && - w > 0 && h > 0) - { - // Note that the MPV_EVENT_VIDEO_RECONFIG event doesn't necessarily - // imply a resize, and you should check yourself if the video - // dimensions really changed. - // mpv itself will scale/letter box the video to the container size - // if the video doesn't fit. - std::stringstream ss; - ss << "Reconfig: " << w << " " << h; - statusBar()->showMessage(QString::fromStdString(ss.str())); - } - break; - } - case MPV_EVENT_LOG_MESSAGE: { - struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data; - std::stringstream ss; - ss << "[" << msg->prefix << "] " << msg->level << ": " << msg->text; - append_log(QString::fromStdString(ss.str())); - break; - } - case MPV_EVENT_SHUTDOWN: { - mpv_terminate_destroy(mpv); - mpv = NULL; - break; - } - default: ; - // Ignore uninteresting or unknown events. - } -} - -// This slot is invoked by wakeup() (through the mpv_events signal). -void MainWindow::on_mpv_events() -{ - // Process all events, until the event queue is empty. - while (mpv) { - mpv_event *event = mpv_wait_event(mpv, 0); - if (event->event_id == MPV_EVENT_NONE) - break; - handle_mpv_event(event); - } -} - -void MainWindow::on_file_open() -{ - QString filename = QFileDialog::getOpenFileName(this, "Open file"); - if (mpv) { - const QByteArray c_filename = filename.toUtf8(); - const char *args[] = {"loadfile", c_filename.data(), NULL}; - mpv_command_async(mpv, 0, args); - } -} - -void MainWindow::on_new_window() -{ - (new MainWindow())->show(); -} - -void MainWindow::append_log(const QString &text) -{ - QTextCursor cursor = log->textCursor(); - cursor.movePosition(QTextCursor::End); - cursor.insertText(text); - log->setTextCursor(cursor); -} - -MainWindow::~MainWindow() -{ - if (mpv) - mpv_terminate_destroy(mpv); -} - -int main(int argc, char *argv[]) -{ - QApplication a(argc, argv); - - // Qt sets the locale in the QApplication constructor, but libmpv requires - // the LC_NUMERIC category to be set to "C", so change it back. - std::setlocale(LC_NUMERIC, "C"); - - MainWindow w; - w.show(); - - return a.exec(); -} diff --git a/DOCS/client_api_examples/qt/qtexample.h b/DOCS/client_api_examples/qt/qtexample.h deleted file mode 100644 index c3891cf4a7..0000000000 --- a/DOCS/client_api_examples/qt/qtexample.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef QTEXAMPLE_H -#define QTEXAMPLE_H - -#include - -#include - -class QTextEdit; - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = 0); - ~MainWindow(); - -private slots: - void on_file_open(); - void on_new_window(); - void on_mpv_events(); - -signals: - void mpv_events(); - -private: - QWidget *mpv_container; - mpv_handle *mpv; - QTextEdit *log; - - void append_log(const QString &text); - - void create_player(); - void handle_mpv_event(mpv_event *event); -}; - -#endif // QTEXAMPLE_H diff --git a/DOCS/client_api_examples/qt/qtexample.pro b/DOCS/client_api_examples/qt/qtexample.pro deleted file mode 100644 index 7c0a6ade02..0000000000 --- a/DOCS/client_api_examples/qt/qtexample.pro +++ /dev/null @@ -1,13 +0,0 @@ -QT += core gui - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -TARGET = qtexample -TEMPLATE = app - -QT_CONFIG -= no-pkg-config -CONFIG += link_pkgconfig debug -PKGCONFIG += mpv - -SOURCES += qtexample.cpp -HEADERS += qtexample.h diff --git a/DOCS/client_api_examples/qt_opengl/main.cpp b/DOCS/client_api_examples/qt_opengl/main.cpp deleted file mode 100644 index 086a4b4617..0000000000 --- a/DOCS/client_api_examples/qt_opengl/main.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include "mainwindow.h" - -int main(int argc, char *argv[]) -{ - QApplication a(argc, argv); - // Qt sets the locale in the QApplication constructor, but libmpv requires - // the LC_NUMERIC category to be set to "C", so change it back. - setlocale(LC_NUMERIC, "C"); - MainWindow w; - w.show(); - return a.exec(); -} diff --git a/DOCS/client_api_examples/qt_opengl/mainwindow.cpp b/DOCS/client_api_examples/qt_opengl/mainwindow.cpp deleted file mode 100644 index 3ecd0a196d..0000000000 --- a/DOCS/client_api_examples/qt_opengl/mainwindow.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "mainwindow.h" -#include "mpvwidget.h" -#include -#include -#include -#include - -MainWindow::MainWindow(QWidget *parent) : QWidget(parent) -{ - m_mpv = new MpvWidget(this); - m_slider = new QSlider(); - m_slider->setOrientation(Qt::Horizontal); - m_openBtn = new QPushButton("Open"); - m_playBtn = new QPushButton("Pause"); - QHBoxLayout *hb = new QHBoxLayout(); - hb->addWidget(m_openBtn); - hb->addWidget(m_playBtn); - QVBoxLayout *vl = new QVBoxLayout(); - vl->addWidget(m_mpv); - vl->addWidget(m_slider); - vl->addLayout(hb); - setLayout(vl); - connect(m_slider, SIGNAL(sliderMoved(int)), SLOT(seek(int))); - connect(m_openBtn, SIGNAL(clicked()), SLOT(openMedia())); - connect(m_playBtn, SIGNAL(clicked()), SLOT(pauseResume())); - connect(m_mpv, SIGNAL(positionChanged(int)), m_slider, SLOT(setValue(int))); - connect(m_mpv, SIGNAL(durationChanged(int)), this, SLOT(setSliderRange(int))); -} - -void MainWindow::openMedia() -{ - QString file = QFileDialog::getOpenFileName(0, "Open a video"); - if (file.isEmpty()) - return; - m_mpv->command(QStringList() << "loadfile" << file); -} - -void MainWindow::seek(int pos) -{ - m_mpv->command(QVariantList() << "seek" << pos << "absolute"); -} - -void MainWindow::pauseResume() -{ - const bool paused = m_mpv->getProperty("pause").toBool(); - m_mpv->setProperty("pause", !paused); -} - -void MainWindow::setSliderRange(int duration) -{ - m_slider->setRange(0, duration); -} diff --git a/DOCS/client_api_examples/qt_opengl/mainwindow.h b/DOCS/client_api_examples/qt_opengl/mainwindow.h deleted file mode 100644 index 523b385bb4..0000000000 --- a/DOCS/client_api_examples/qt_opengl/mainwindow.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef MainWindow_H -#define MainWindow_H - -#include - -class MpvWidget; -class QSlider; -class QPushButton; -class MainWindow : public QWidget -{ - Q_OBJECT -public: - explicit MainWindow(QWidget *parent = 0); -public Q_SLOTS: - void openMedia(); - void seek(int pos); - void pauseResume(); -private Q_SLOTS: - void setSliderRange(int duration); -private: - MpvWidget *m_mpv; - QSlider *m_slider; - QPushButton *m_openBtn; - QPushButton *m_playBtn; -}; - -#endif // MainWindow_H diff --git a/DOCS/client_api_examples/qt_opengl/mpvwidget.cpp b/DOCS/client_api_examples/qt_opengl/mpvwidget.cpp deleted file mode 100644 index 4d2cad6241..0000000000 --- a/DOCS/client_api_examples/qt_opengl/mpvwidget.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include "mpvwidget.h" -#include -#include -#include - -static void wakeup(void *ctx) -{ - QMetaObject::invokeMethod((MpvWidget*)ctx, "on_mpv_events", Qt::QueuedConnection); -} - -static void *get_proc_address(void *ctx, const char *name) { - Q_UNUSED(ctx); - QOpenGLContext *glctx = QOpenGLContext::currentContext(); - if (!glctx) - return NULL; - return (void *)glctx->getProcAddress(QByteArray(name)); -} - -MpvWidget::MpvWidget(QWidget *parent, Qt::WindowFlags f) - : QOpenGLWidget(parent, f) -{ - mpv = mpv::qt::Handle::FromRawHandle(mpv_create()); - if (!mpv) - throw std::runtime_error("could not create mpv context"); - - mpv_set_option_string(mpv, "terminal", "yes"); - mpv_set_option_string(mpv, "msg-level", "all=v"); - if (mpv_initialize(mpv) < 0) - throw std::runtime_error("could not initialize mpv context"); - - // Make use of the MPV_SUB_API_OPENGL_CB API. - mpv::qt::set_option_variant(mpv, "vo", "opengl-cb"); - - // Request hw decoding, just for testing. - mpv::qt::set_option_variant(mpv, "hwdec", "auto"); - - mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB); - if (!mpv_gl) - throw std::runtime_error("OpenGL not compiled in"); - mpv_opengl_cb_set_update_callback(mpv_gl, MpvWidget::on_update, (void *)this); - connect(this, SIGNAL(frameSwapped()), SLOT(swapped())); - - mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE); - mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE); - mpv_set_wakeup_callback(mpv, wakeup, this); -} - -MpvWidget::~MpvWidget() -{ - makeCurrent(); - if (mpv_gl) - mpv_opengl_cb_set_update_callback(mpv_gl, NULL, NULL); - // Until this call is done, we need to make sure the player remains - // alive. This is done implicitly with the mpv::qt::Handle instance - // in this class. - mpv_opengl_cb_uninit_gl(mpv_gl); -} - -void MpvWidget::command(const QVariant& params) -{ - mpv::qt::command_variant(mpv, params); -} - -void MpvWidget::setProperty(const QString& name, const QVariant& value) -{ - mpv::qt::set_property_variant(mpv, name, value); -} - -QVariant MpvWidget::getProperty(const QString &name) const -{ - return mpv::qt::get_property_variant(mpv, name); -} - -void MpvWidget::initializeGL() -{ - int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL); - if (r < 0) - throw std::runtime_error("could not initialize OpenGL"); -} - -void MpvWidget::paintGL() -{ - mpv_opengl_cb_draw(mpv_gl, defaultFramebufferObject(), width(), -height()); -} - -void MpvWidget::swapped() -{ - mpv_opengl_cb_report_flip(mpv_gl, 0); -} - -void MpvWidget::on_mpv_events() -{ - // Process all events, until the event queue is empty. - while (mpv) { - mpv_event *event = mpv_wait_event(mpv, 0); - if (event->event_id == MPV_EVENT_NONE) { - break; - } - handle_mpv_event(event); - } -} - -void MpvWidget::handle_mpv_event(mpv_event *event) -{ - switch (event->event_id) { - case MPV_EVENT_PROPERTY_CHANGE: { - mpv_event_property *prop = (mpv_event_property *)event->data; - if (strcmp(prop->name, "time-pos") == 0) { - if (prop->format == MPV_FORMAT_DOUBLE) { - double time = *(double *)prop->data; - Q_EMIT positionChanged(time); - } - } else if (strcmp(prop->name, "duration") == 0) { - if (prop->format == MPV_FORMAT_DOUBLE) { - double time = *(double *)prop->data; - Q_EMIT durationChanged(time); - } - } - break; - } - default: ; - // Ignore uninteresting or unknown events. - } -} - -void MpvWidget::on_update(void *ctx) -{ - QMetaObject::invokeMethod((MpvWidget*)ctx, "update"); -} diff --git a/DOCS/client_api_examples/qt_opengl/mpvwidget.h b/DOCS/client_api_examples/qt_opengl/mpvwidget.h deleted file mode 100644 index a219681393..0000000000 --- a/DOCS/client_api_examples/qt_opengl/mpvwidget.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef PLAYERWINDOW_H -#define PLAYERWINDOW_H - -#include -#include -#include -#include - -class MpvWidget Q_DECL_FINAL: public QOpenGLWidget -{ - Q_OBJECT -public: - MpvWidget(QWidget *parent = 0, Qt::WindowFlags f = 0); - ~MpvWidget(); - void command(const QVariant& params); - void setProperty(const QString& name, const QVariant& value); - QVariant getProperty(const QString& name) const; - QSize sizeHint() const { return QSize(480, 270);} -Q_SIGNALS: - void durationChanged(int value); - void positionChanged(int value); -protected: - void initializeGL() Q_DECL_OVERRIDE; - void paintGL() Q_DECL_OVERRIDE; -private Q_SLOTS: - void swapped(); - void on_mpv_events(); -private: - void handle_mpv_event(mpv_event *event); - static void on_update(void *ctx); - - mpv::qt::Handle mpv; - mpv_opengl_cb_context *mpv_gl; -}; - - - -#endif // PLAYERWINDOW_H diff --git a/DOCS/client_api_examples/qt_opengl/qt_opengl.pro b/DOCS/client_api_examples/qt_opengl/qt_opengl.pro deleted file mode 100644 index 8a2bcc665d..0000000000 --- a/DOCS/client_api_examples/qt_opengl/qt_opengl.pro +++ /dev/null @@ -1,13 +0,0 @@ -CONFIG -= app_bundle -QT += widgets - -QT_CONFIG -= no-pkg-config -CONFIG += link_pkgconfig debug -PKGCONFIG += mpv - -HEADERS = \ - mpvwidget.h \ - mainwindow.h -SOURCES = main.cpp \ - mpvwidget.cpp \ - mainwindow.cpp diff --git a/DOCS/client_api_examples/sdl/main.c b/DOCS/client_api_examples/sdl/main.c deleted file mode 100644 index b3b5952668..0000000000 --- a/DOCS/client_api_examples/sdl/main.c +++ /dev/null @@ -1,158 +0,0 @@ -// Build with: gcc -o main main.c `pkg-config --libs --cflags mpv sdl2` - -#include -#include -#include - -#include - -#include -#include - -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 | - SDL_WINDOW_RESIZABLE); - 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/client_api_examples/simple/simple.c b/DOCS/client_api_examples/simple/simple.c deleted file mode 100644 index 3ba1c35686..0000000000 --- a/DOCS/client_api_examples/simple/simple.c +++ /dev/null @@ -1,54 +0,0 @@ -// Build with: gcc -o simple simple.c `pkg-config --libs --cflags mpv` - -#include -#include -#include - -#include - -static inline void check_error(int status) -{ - if (status < 0) { - printf("mpv API error: %s\n", mpv_error_string(status)); - exit(1); - } -} - -int main(int argc, char *argv[]) -{ - if (argc != 2) { - printf("pass a single media file as argument\n"); - return 1; - } - - mpv_handle *ctx = mpv_create(); - if (!ctx) { - printf("failed creating context\n"); - return 1; - } - - // Enable default key bindings, so the user can actually interact with - // the player (and e.g. close the window). - check_error(mpv_set_option_string(ctx, "input-default-bindings", "yes")); - mpv_set_option_string(ctx, "input-vo-keyboard", "yes"); - int val = 1; - check_error(mpv_set_option(ctx, "osc", MPV_FORMAT_FLAG, &val)); - - // Done setting up options. - check_error(mpv_initialize(ctx)); - - // Play this file. - const char *cmd[] = {"loadfile", argv[1], NULL}; - check_error(mpv_command(ctx, cmd)); - - // Let it play, and wait until the user quits. - while (1) { - mpv_event *event = mpv_wait_event(ctx, 10000); - printf("event: %s\n", mpv_event_name(event->event_id)); - if (event->event_id == MPV_EVENT_SHUTDOWN) - break; - } - - mpv_terminate_destroy(ctx); - return 0; -} diff --git a/DOCS/compile-windows.md b/DOCS/compile-windows.md index 6e08d78ed7..364a057183 100644 --- a/DOCS/compile-windows.md +++ b/DOCS/compile-windows.md @@ -104,22 +104,21 @@ Installing MSYS2 It doesn't matter whether the i686 or the x86_64 version is used. Both can build 32-bit and 64-bit binaries when running on a 64-bit version of Windows. -2. Add ``C:\msys64\mingw64\bin`` to your ``%PATH%``. mpv will depend on several - DLLs in this folder. +2. Start a MinGW-w64 shell (``mingw64_shell.bat``). Note that this is different + from the MSYS2 shell that is started from the final installation dialog. -3. Start a MinGW-w64 shell (``mingw64_shell.bat``). For a 32-bit build, use - ``mingw32_shell.bat``. + For a 32-bit build, use ``mingw32_shell.bat``. Updating MSYS2 -------------- -To prevent errors during post-install, ``msys2-runtime`` and ``pacman`` must be -updated first. +To prevent errors during post-install, the MSYS2 core runtime must be updated +separately. ```bash -# Check for updates to msys2-runtime and pacman. If there were updates, restart -# MSYS2 before continuing. -pacman -Sy --needed msys2-runtime pacman +# Check for core updates. If instructed, close the shell window and reopen it +# before continuing. +update-core # Update everything else pacman -Su @@ -130,14 +129,14 @@ Installing mpv dependencies ```bash # Install MSYS2 build dependencies and a MinGW-w64 compiler -pacman -S git pkg-config python3 mingw-w64-x86_64-gcc +pacman -S git mingw-w64-x86_64-pkg-config python 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-lua51 # Install additional (optional) dependencies -pacman -S mingw-w64-x86_64-libdvdnav mingw-w64-x86_64-libguess +pacman -S mingw-w64-x86_64-libdvdnav mingw-w64-x86_64-libguess mingw-w64-x86_64-angleproject-git ``` For a 32-bit build, install ``mingw-w64-i686-*`` packages instead. @@ -157,14 +156,14 @@ Finally, compile and install mpv. Binaries will be installed to ```bash # For a 32-bit build, use --prefix=/mingw32 instead -./waf configure CC=gcc --prefix=/mingw64 +./waf configure CC=gcc.exe --check-c-compiler=gcc --prefix=/mingw64 ./waf install ``` Or, compile and install both libmpv and mpv: ```bash -./waf configure CC=gcc --enable-libmpv-shared --prefix=/mingw64 +./waf configure CC=gcc.exe --check-c-compiler=gcc --enable-libmpv-shared --prefix=/mingw64 ./waf install # waf installs libmpv to the wrong directory, so fix it up @@ -173,3 +172,29 @@ mv -f /mingw64/bin/libmpv.dll.a /mingw64/lib/ sed -i 's_/mingw64/bin_/mingw64/lib_' /mingw64/lib/pkgconfig/mpv.pc rmdir /mingw64/bin/pkgconfig ``` + +Running mpv +----------- + +If you want to run mpv from the MinGW-w64 shell, you will find the experience +much more pleasant if you use the ``winpty`` utility + +```bash +pacman -S winpty +winpty mpv.com ToS-4k-1920.mov +``` + +If you want to move / copy ``mpv.exe`` and ``mpv.com`` to somewhere other than +``/mingw64/bin/`` for use outside the MinGW-w64 shell, they will still depend on +DLLs in that folder. The simplest solution is to add ``C:\msys64\mingw64\bin`` +to the windows system ``%PATH%``. Beware though that this can cause problems or +confusion in Cygwin if that is also installed on the machine. + +Use of the ANGLE OpenGL backend requires a copy of ``d3dcompiler_43.dll`` (yes, +exactly 43) in the path or in the same folder as mpv. It must be of the same +architecture (x86_64 / i686) as the mpv you compiled. You can find a copy in the +official mpv builds: + +https://mpv.srsfckn.biz/mpv-x86_64-20160118.7z + +https://mpv.srsfckn.biz/mpv-i686-20160118.7z diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index 4217de86fc..ee6e135808 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -19,6 +19,17 @@ Interface changes :: + --- mpv 0.17.0 --- + - deprecate "track-list/N/audio-channels" property (use + "track-list/N/demux-channel-count" instead) + - remove write access to "stream-pos", and change semantics for read access + - Lua scripts now don't suspend mpv by default while script code is run + - add "cache-speed" property + - rename --input-unix-socket to --input-ipc-server, and make it work on + Windows too + - change the exact behavior of the "video-zoom" property + - --video-unscaled no longer disables --video-zoom and --video-aspect + To force the old behavior, set --video-zoom=0 and --video-aspect=0 --- mpv 0.16.0 --- - change --audio-channels default to stereo (use --audio-channels=auto to get the old default) diff --git a/DOCS/libmpv-client-api.rst b/DOCS/libmpv-client-api.rst new file mode 100644 index 0000000000..eeb5fe0408 --- /dev/null +++ b/DOCS/libmpv-client-api.rst @@ -0,0 +1,14 @@ +Client API (libmpv) documentation and examples +============================================== + +libmpv lets you embed mpv as a library. It is meant to enable development of +GUI frontends. + +Since libmpv merely allows access to underlying mechanisms that can control +mpv, documentation is spread over a few places: + +- https://github.com/mpv-player/mpv/blob/master/libmpv/client.h +- http://mpv.io/manual/master/#options +- http://mpv.io/manual/master/#list-of-input-commands +- http://mpv.io/manual/master/#properties +- https://github.com/mpv-player/mpv-examples/tree/master/libmpv diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 11a8f0021f..241f6740fb 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -921,8 +921,9 @@ Property list useless. It looks like this can be different from ``path`` only when using e.g. ordered chapters.) -``stream-pos`` (RW) - Raw byte position in source stream. +``stream-pos`` + Raw byte position in source stream. Technically, this returns the position + of the most recent packet passed to a decoder. ``stream-end`` Raw end position in bytes in source stream. @@ -1194,6 +1195,11 @@ Property list ``cache-used`` (R) Total used cache size in KB. +``cache-speed`` (R) + Current I/O read speed between the cache and the lower layer (like network). + This gives the number bytes per seconds over a 1 second window (using + the type ``MPV_FORMAT_INT64`` for the client API). + ``cache-idle`` (R) Returns ``yes`` if the cache is idle, which means the cache is filled as much as possible, and is currently not reading more data. @@ -1237,10 +1243,10 @@ Property list ``mixer-active`` Return ``yes`` if the audio mixer is active, ``no`` otherwise. This has - implications for ``--softvol=no`` mode: if the mixer is active, changing + implications for ``--softvol=no`` mode: if the mixer is inactive, changing ``volume`` doesn't actually change anything on the system mixer. If the ``--volume`` or ``--mute`` option are used, these might not be applied - property until the mixer becomes active either. (The options, if set, will + properly until the mixer becomes active either. (The options, if set, will just overwrite the mixer state at audio initialization.) While the behavior with ``mixer-active==yes`` is relatively well-defined, @@ -1707,10 +1713,6 @@ 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 @@ -1746,6 +1748,29 @@ Property list match even if the default (builtin) demuxer is used, but there is no hard guarantee. + ``track-list/N/decoder-desc`` + If this track is being decoded, the human-readable decoder name, + + ``track-list/N/demux-w``, ``track-list/N/demux-h`` + Video size hint as indicated by the container. (Not always accurate.) + + ``track-list/N/demux-channel-count`` + Number of audio channels as indicated by the container. (Not always + accurate - in particular, the track could be decoded as a different + number of channels.) + + ``track-list/N/demux-channels`` + Channel layout as indicated by the container. (Not always accurate.) + + ``track-list/N/demux-samplerate`` + Audio sample rate as indicated by the container. (Not always accurate.) + + ``track-list/N/demux-fps`` + Video FPS as indicated by the container. (Not always accurate.) + + ``track-list/N/audio-channels`` (deprecated) + Deprecated alias for ``track-list/N/demux-channel-count``. + 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: @@ -1759,13 +1784,20 @@ 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 + "decoder-desc" MPV_FORMAT_STRING + "demux-w" MPV_FORMAT_INT64 + "demux-h" MPV_FORMAT_INT64 + "demux-channel-count" MPV_FORMAT_INT64 + "demux-channels" MPV_FORMAT_STRING + "demux-samplerate" MPV_FORMAT_INT64 + "demux-fps" MPV_FORMAT_DOUBLE + "audio-channels" MPV_FORMAT_INT64 ``chapter-list`` List of chapters, current entry marked. Currently, the raw property value @@ -1819,6 +1851,9 @@ Property list ``video-rotate`` (RW) See ``--video-rotate`` option. +``video-stereo-mode`` (RW) + See ``--video-stereo-mode`` option. + ``seekable`` Return whether it's generally possible to seek in the current file. @@ -1962,6 +1997,43 @@ Property list In some cases, the protocol will not actually be supported (consider ``https`` if ffmpeg is not compiled with TLS support). +``decoder-list`` + List of decoders supported. This lists decoders which can be passed to + ``--vd`` and ``--ad``. + + ``family`` + Decoder driver. Usually ``lavc`` for libavcodec. + + ``codec`` + Canonical codec name, which identifies the format the decoder can + handle. + + ``driver`` + The name of the decoder itself. Often, this is the same as ``codec``. + Sometimes it can be different. It is used to distinguish multiple + decoders for the same codec. + + ``description`` + Human readable description of the decoder and codec. + + 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 decoder entry) + "family" MPV_FORMAT_STRING + "codec" MPV_FORMAT_STRING + "driver" MPV_FORMAT_STRING + "description" MPV_FORMAT_STRING + +``encoder-list`` + List of libavcodec encoders. This has the same format as ``decoder-list``. + The encoder names (``driver`` entries) can be passed to ``--ovc`` and + ``--oac`` (without the ``lavc:`` prefix required by ``--vd`` and ``--ad``). + ``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. @@ -1970,6 +2042,13 @@ Property list Return the configuration arguments which were passed to the build system (typically the way ``./waf configure ...`` was invoked). +``ffmpeg-version`` + Return the contents of the ``av_version_info()`` API call. This is a string + which identifies the build in some way, either through a release version + number, or a git hash. This applies to Libav as well (the property is + still named the same.) This property is unavailable if mpv is linked against + older FFmpeg and Libav versions. + ``options/`` (RW) Read-only access to value of option ``--``. Most options can be changed at runtime by writing to this property. Note that many options diff --git a/DOCS/man/ipc.rst b/DOCS/man/ipc.rst index e3ff1bd243..7a0ed69723 100644 --- a/DOCS/man/ipc.rst +++ b/DOCS/man/ipc.rst @@ -1,10 +1,10 @@ JSON IPC ======== -mpv can be controlled by external programs using the JSON-based IPC protocol. It -can be enabled by specifying the path to a unix socket using the option -``--input-unix-socket``. Clients can connect to this socket and send commands to -the player or receive events from it. +mpv can be controlled by external programs using the JSON-based IPC protocol. +It can be enabled by specifying the path to a unix socket or a named pipe using +the option ``--input-ipc-server``. Clients can connect to this socket and send +commands to the player or receive events from it. .. warning:: @@ -17,12 +17,12 @@ the player or receive events from it. Socat example ------------- -You can use the ``socat`` tool to send commands (and receive reply) from the +You can use the ``socat`` tool to send commands (and receive replies) from the shell. Assuming mpv was started with: :: - mpv file.mkv --input-unix-socket=/tmp/mpvsocket + mpv file.mkv --input-ipc-server=/tmp/mpvsocket Then you can control it using socat: @@ -46,6 +46,31 @@ It's also possible to send input.conf style text-only commands: But you won't get a reply over the socket. (This particular command shows the playback time on the player's OSD.) +Command Prompt example +---------------------- + +Unfortunately, it's not as easy to test the IPC protocol on Windows, since +Windows ports of socat (in Cygwin and MSYS2) don't understand named pipes. In +the absence of a simple tool to send and receive from bidirectional pipes, the +``echo`` command can be used to send commands, but not receive replies from the +command prompt. + +Assuming mpv was started with: + +:: + + mpv file.mkv --input-ipc-server=\\.\pipe\mpvsocket + +You can send commands from a command prompt: + +:: + + echo show_text ${playback-time} >\\.\pipe\mpvsocket + +To be able to simultaneously read and write from the IPC pipe, like on Linux, +it's necessary to write an external program that uses overlapped file I/O (or +some wrapper like .NET's NamedPipeClientStream.) + Protocol -------- @@ -102,7 +127,7 @@ break character (``\n``). If the first character (after skipping whitespace) is not ``{``, the command will be interpreted as non-JSON text command, as they are used in input.conf -(or ``mpv_command_string()`` in the client API). Additionally, line starting +(or ``mpv_command_string()`` in the client API). Additionally, lines starting with ``#`` and empty lines are ignored. Currently, embedded 0 bytes terminate the current line, but you should not diff --git a/DOCS/man/lua.rst b/DOCS/man/lua.rst index 4d47774319..6b2c5af031 100644 --- a/DOCS/man/lua.rst +++ b/DOCS/man/lua.rst @@ -192,7 +192,8 @@ The ``mp`` module is preloaded, although it can be loaded manually with Register callback to be run on a key binding. The binding will be mapped to the given ``key``, which is a string describing the physical key. This uses the same key names as in input.conf, and also allows combinations - (e.g. ``ctrl+a``). + (e.g. ``ctrl+a``). If the key is empty or ``nil``, no physical key is + registered, but the user still can create own bindings (see below). After calling this function, key presses will cause the function ``fn`` to be called (unless the user remapped the key with another binding). @@ -255,7 +256,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with :: - y script-binding fooscript.something + y script-binding fooscript/something ``mp.add_forced_key_binding(...)`` This works almost the same as ``mp.add_key_binding``, but registers the @@ -412,7 +413,7 @@ are useful only in special situations. from displaying the next video frame, so that you don't get blocked when trying to access the player. - This is automatically called by the event handler. + Before mpv 0.17.0, this was automatically called by the event handler. ``mp.resume()`` Undo one ``mp.suspend()`` call. ``mp.suspend()`` increments an internal @@ -423,10 +424,6 @@ are useful only in special situations. This resets the internal suspend counter and resumes the player. (It's like calling ``mp.resume()`` until the player is actually resumed.) - You might want to call this if you're about to do something that takes a - long time, but doesn't really need access to the player (like a network - operation). Note that you still can access the player at any time. - ``mp.get_wakeup_pipe()`` Calls ``mpv_get_wakeup_pipe()`` and returns the read end of the wakeup pipe. (See ``client.h`` for details.) @@ -449,8 +446,6 @@ are useful only in special situations. ``mp.get_wakeup_pipe()`` if you're interested in properly working notification of new events and working timers. - This function calls ``mp.suspend()`` and ``mp.resume_all()`` on its own. - ``mp.enable_messages(level)`` Set the minimum log level of which mpv message output to receive. These messages are normally printed to the terminal. By calling this function, diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst index 6716c382f2..ea0681c5ae 100644 --- a/DOCS/man/mpv.rst +++ b/DOCS/man/mpv.rst @@ -54,7 +54,7 @@ UP and DOWN Ctrl+LEFT and Ctrl+RIGHT Seek to the previous/next subtitle. Subject to some restrictions and - might not always work; see ``sub_seek`` command. + might not always work; see ``sub-seek`` command. [ and ] Decrease/increase current playback speed by 10%. @@ -708,7 +708,9 @@ PROTOCOLS Stitch together parts of multiple files and play them. ``null://`` - Simulate an empty file. + Simulate an empty file. If opened for writing, it will discard all data. + The ``null`` demuxer will specifically pass autoprobing if this protocol + is used (while it's not automatically invoked for empty files). ``memory://data`` Use the ``data`` part as source data. @@ -795,9 +797,6 @@ behavior of mpv. If set, XDG-style system configuration directories are used. Otherwise, the UNIX convention (``PREFIX/etc/mpv/``) is used. -``TERM`` - Used to determine terminal type. - ``MPV_HOME`` Directory where mpv looks for user settings. Overrides ``HOME``, and mpv will try to load the config file as ``$MPV_HOME/mpv.conf``. @@ -808,8 +807,7 @@ behavior of mpv. of ``--v`` options passed to the command line. ``MPV_LEAK_REPORT`` - If set to ``1``, enable internal talloc leak reporting. Note that this can - cause trouble with multithreading, so only developers should use this. + If set to ``1``, enable internal talloc leak reporting. ``LADSPA_PATH`` Specifies the search path for LADSPA plugins. If it is unset, fully diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index ec71dd762b..18ac7fd2f0 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -580,7 +580,10 @@ Video :vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux only) :vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only) :videotoolbox: requires ``--vo=opengl`` (OS X 10.8 and up only) + :dxva2: requires ``--vo=opengl:backend=angle`` or + ``--vo=opengl:backend=dxinterop`` (Windows only) :dxva2-copy: copies video back to system RAM (Windows only) + :d3d11va-copy: experimental (Windows only) :rpi: requires ``--vo=rpi`` (Raspberry Pi only - default if available) ``auto`` tries to automatically enable hardware decoding using the first @@ -596,9 +599,14 @@ Video forcing it with ``--vo=opengl:backend=x11``, but the vaapi/GLX interop is said to be slower than ``vaapi-copy``. - The ``vaapi-copy`` mode allows you to use vaapi with any VO. Because - this copies the decoded video back to system RAM, it's likely less efficient - than the ``vaapi`` mode. + Most video filters will not work with hardware decoding as they are + primarily implemented on the CPU. Some exceptions are ``vdpaupp``, + ``vdpaurb`` and ``vavpp``. See `VIDEO FILTERS`_ for more details. + + The ``vaapi-copy`` and ``dxva2-copy`` modes allow you to use hardware + decoding with any VO, backend or filter. Because these copy the decoded + video back to system RAM, they're likely less efficient than the ``vaapi`` + or ``dxva2`` modes respectively. .. note:: @@ -676,15 +684,12 @@ Video ``--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 - can be influenced by the other ``--video-...`` options. (But not all; for - example ``--video-zoom`` does nothing if this option is enabled.) - - The video and monitor aspects aspect will be ignored. Aspect correction - would require scaling the video in the X or Y direction, but this option - disables scaling, disabling all aspect correction. + can be influenced by the other ``--video-...`` options. Note that the scaler algorithm may still be used, even if the video isn't - scaled. For example, this can influence chroma conversion. + scaled. For example, this can influence chroma conversion. The video will + also still be scaled in one dimension if the source uses non-square pixels + (e.g. anamorphic widescreen DVDs). This option is disabled if the ``--no-keepaspect`` option is used. @@ -724,12 +729,10 @@ Video appear in files, but can't be handled properly by mpv. ``--video-zoom=`` - Adjust the video display scale factor by the given value. The unit is in - fractions of the (scaled) window video size. - - For example, given a 1280x720 video shown in a 1280x720 window, - ``--video-zoom=-0.1`` would make the video by 128 pixels smaller in - X direction, and 72 pixels in Y direction. + Adjust the video display scale factor by the given value. The parameter is + given log 2. For example, ``--video-zoom=0`` is unscaled, + ``--video-zoom=1`` is twice the size, ``--video-zoom=-2`` is one fourth of + the size, and so on. This option is disabled if the ``--no-keepaspect`` option is used. @@ -2066,8 +2069,8 @@ Window This option might be removed in the future. ``--x11-bypass-compositor=`` - If set to ``yes`` (default), then ask the compositor to unredirect the - mpv window. This uses the ``_NET_WM_BYPASS_COMPOSITOR`` hint. + If set to ``yes``, then ask the compositor to unredirect the mpv window + (default: no). This uses the ``_NET_WM_BYPASS_COMPOSITOR`` hint. Disc Devices @@ -2433,7 +2436,7 @@ Input ``--input-file=`` Read commands from the given file. Mostly useful with a FIFO. Since mpv 0.7.0 also understands JSON commands (see `JSON IPC`_), but you can't - get replies or events. Use ``--input-unix-socket`` for something + get replies or events. Use ``--input-ipc-server`` for something bi-directional. On MS Windows, JSON commands are not available. This can also specify a direct file descriptor with ``fd://N`` (UNIX only). @@ -2453,12 +2456,18 @@ Input or intend to read from stdin later on via the loadfile or loadlist slave commands. -``--input-unix-socket=`` +``--input-ipc-server=`` Enable the IPC support and create the listening socket at the given path. - See `JSON IPC`_ for details. + On Linux and Unix, the given path is a regular filesystem path. On Windows, + named pipes are used, so the path refers to the pipe namespace + (``\\.\pipe\``). If the ``\\.\pipe\`` prefix is missing, mpv will add + it automatically before creating the pipe, so + ``--input-ipc-server=/tmp/mpv-socket`` and + ``--input-ipc-server=\\.\pipe\tmp\mpv-socket`` are equivalent for IPC on + Windows. - Not available on MS Windows. + See `JSON IPC`_ for details. ``--input-appleremote=`` (OS X only) @@ -2613,6 +2622,9 @@ OSD ``--osd-bold=``, ``--sub-text-bold=`` Format text on bold. +``--osd-italic=``, ``--sub-text-italic=`` + Format text on italic. + ``--osd-border-color=``, ``--sub-text-border-color=`` See ``--osd-color``. Color used for the OSD/sub font border. @@ -2953,6 +2965,22 @@ Terminal :debug: debug messages :trace: very noisy debug messages + .. admonition:: Example + + :: + + mpv --msg-level=ao/sndio=no + + Completely silences the output of ao_sndio, which uses the log + prefix ``[ao/sndio]``. + + :: + + mpv --msg-level=all=warn,ao/alsa=error + + Only show warnings or worse, and let the ao_alsa output show errors + only. + ``--term-osd, --no-term-osd``, ``--term-osd=force`` Display OSD messages on the console when no video output is available. Enabled by default. @@ -3603,7 +3631,7 @@ Miscellaneous - ``--lavfi-complex='[aid1] asplit [t1] [ao] ; [t1] showvolume [t2] ; [vid1] [t2] overlay [vo]'`` Play audio track 1, and overlay the measured volume for each speaker over video track 1. + - ``null:// --lavfi-complex='life [vo]'`` + Conways' Life Game. See the FFmpeg libavfilter documentation for details on the filter. - - diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst index 58bd91cd55..18e3f4ae64 100644 --- a/DOCS/man/vo.rst +++ b/DOCS/man/vo.rst @@ -582,12 +582,10 @@ Available video output drivers are: better than without it) since it will extend the size to match only the milder of the scale factors between the axes. - ``prescale=`` - This option provides non-convolution-based filters for upscaling. These - filters resize the video to multiple of the original size (all currently - supported prescalers can only perform image doubling in a single pass). - Generally another convolution based filter (the main scaler) will be - applied after prescaler to match the target display size. + ``prescale-luma=`` + Apply additional pre-scaling (image doubling) on the luma plane + (if present). As the name implies, these will run before the main + upscaling pass. ``none`` Disable all prescalers. This is the default. @@ -605,10 +603,6 @@ Available video output drivers are: Extremely slow and requires a recent mid or high end graphics card to work smoothly (as of 2015). - Note that all the filters above are designed (or implemented) to process - luma plane only and probably won't work as intended for video in RGB - format. - ``prescale-passes=<1..5>`` The number of passes to apply the prescaler (defaults to be 1). Setting it to 2 will perform a 4x upscaling. @@ -817,7 +811,8 @@ Available video output drivers are: profiles, and the ``nnedi3`` prescaler. dxinterop (experimental) Win32, using WGL for rendering and Direct3D 9Ex for presentation. - Works on Nvidia and AMD only. + Works on Nvidia and AMD. Newer Intel chips with the latest drivers + may also work. x11 X11/GLX wayland @@ -892,6 +887,8 @@ Available video output drivers are: ProPhoto RGB (ROMM) cie1931 CIE 1931 RGB (not to be confused with CIE XYZ) + dci-p3 + DCI-P3 (Digital Cinema Colorspace), SMPTE RP431-2 ``target-trc=`` Specifies the transfer characteristics (gamma) of the display. Video diff --git a/README.md b/README.md index 6405add93b..4c84266964 100644 --- a/README.md +++ b/README.md @@ -123,20 +123,28 @@ of both FFmpeg and Libav. But FFmpeg is preferred, and some mpv features work with FFmpeg only (subtitle formats in particular). +## Preferred FFmpeg version + + +Using the latest FFmpeg release (or FFmpeg git master) is strongly recommended. +Older versions are unsupported, even if the build system still happens to +accept them. The main reason mpv still builds with older FFmpeg versions is to +evade arguing with people (users, distros) who insist on using older FFmpeg +versions for no rational reason. + +If you want to use a stable FFmpeg release, use the latest release, which has +most likely the best maintenance out of all stable releases. Older releases +are for distros, and at best receive basic changes like fixing critical security +issues or build fixes, and at worst are completely abandoned. + ## Release cycle +Every other month, an arbitrary git snapshot is made, and is assigned +a 0.X.0 version number. No further maintenance is done. -Every few months, a new release is cut off of the master branch and is assigned -a 0.X.0 version number. - -As part of the maintenance process, minor releases are made, which are assigned -0.X.Y version numbers. Minor releases contain bug fixes only. They never merge -the master branch, and no features are added to it. Only the latest release is -maintained. - -The goal of releases is to provide stability and an unchanged base for the sake -of Linux distributions. If you want the newest features, just use the master -branch, which is stable most of the time, except sometimes, when it's not. +The goal of releases is to make Linux distributions happy. Linux distributions +are also expected to apply their own patches in case of bugs and security +issues. Releases other than the latest release are unsupported and unmaintained. diff --git a/TOOLS/lua/README.md b/TOOLS/lua/README.md index e9044823cd..79b67d6e8a 100644 --- a/TOOLS/lua/README.md +++ b/TOOLS/lua/README.md @@ -11,4 +11,10 @@ to mpv's command line. Where appropriate, they may also be placed in ~/.config/mpv/scripts/ from where they will be automatically loaded when mpv starts. -Some of these are just for testing mpv internals. +This is only a small selection of internally maintained scripts. Some of them +are just for testing mpv internals, or serve as examples. An extensive +user-edited list of 3rd party scripts is available here: + + https://github.com/mpv-player/mpv/wiki/User-Scripts + +(Anyone can add their own scripts to that list.) diff --git a/TOOLS/lua/zones.lua b/TOOLS/lua/zones.lua deleted file mode 100644 index 5b4924ce0f..0000000000 --- a/TOOLS/lua/zones.lua +++ /dev/null @@ -1,74 +0,0 @@ --- 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/TOOLS/zsh.pl b/TOOLS/zsh.pl index cdf97d6843..d6f7714174 100755 --- a/TOOLS/zsh.pl +++ b/TOOLS/zsh.pl @@ -69,11 +69,17 @@ chomp $profile_comp; my $tmpl = <<"EOS"; #compdef mpv -# mpv zsh completion +# For customization, see: +# https://github.com/mpv-player/mpv/wiki/Zsh-completion-customization local curcontext="\$curcontext" state state_descr line typeset -A opt_args +# By default, don't complete URLs unless no files match +local -a tag_order +zstyle -a ":completion:*:*:\$service:*" tag-order tag_order || \ + zstyle ":completion:*:*:\$service:*" tag-order '!urls' + local rc=1 _arguments -C -S \\ @@ -120,7 +126,7 @@ $profile_comp _tags files urls while _tags; do _requested files expl 'media file' _files -g \\ - "*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|opus|qt|rm|ts|vob|wav|webm|wma|wmv)(-.)" && rc=0 + "*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|opus|qt|rm|ts|vob|wav|webm|wma|wmv|wv)(-.)" && rc=0 if _requested urls; then while _next_label urls expl URL; do _urls "\$expl[@]" && rc=0 diff --git a/audio/decode/ad_lavc.c b/audio/decode/ad_lavc.c index c30aff7fd5..8a6bb4061b 100644 --- a/audio/decode/ad_lavc.c +++ b/audio/decode/ad_lavc.c @@ -45,6 +45,7 @@ struct priv { uint32_t skip_samples, trim_samples; bool preroll_done; double next_pts; + AVRational codec_timebase; }; static void uninit(struct dec_audio *da); @@ -84,6 +85,8 @@ static int init(struct dec_audio *da, const char *decoder) struct priv *ctx = talloc_zero(NULL, struct priv); da->priv = ctx; + ctx->codec_timebase = (AVRational){0}; + ctx->force_channel_map = c->force_channels; lavc_codec = avcodec_find_decoder_by_name(decoder); @@ -128,8 +131,7 @@ static int init(struct dec_audio *da, const char *decoder) // demux_mkv mp_lavc_set_extradata(lavc_context, c->extradata, c->extradata_size); - if (c->lav_headers) - mp_copy_lav_codec_headers(lavc_context, c->lav_headers); + mp_set_lav_codec_headers(lavc_context, c); mp_set_avcodec_threads(da->log, lavc_context, opts->threads); @@ -185,10 +187,23 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt, int in_len = mpkt ? mpkt->len : 0; AVPacket pkt; - mp_set_av_packet(&pkt, mpkt, NULL); + mp_set_av_packet(&pkt, mpkt, &priv->codec_timebase); int got_frame = 0; av_frame_unref(priv->avframe); + +#if HAVE_AVCODEC_NEW_CODEC_API + int ret = avcodec_send_packet(avctx, &pkt); + if (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + if (ret >= 0 && mpkt) + mpkt->len = 0; + ret = avcodec_receive_frame(avctx, priv->avframe); + if (ret >= 0) + got_frame = 1; + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + ret = 0; + } +#else int ret = avcodec_decode_audio4(avctx, priv->avframe, &got_frame, &pkt); if (mpkt) { // At least "shorten" decodes sub-frames, instead of the whole packet. @@ -205,6 +220,7 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt, return 0; } } +#endif if (ret < 0) { MP_ERR(da, "Error decoding audio.\n"); return -1; @@ -212,7 +228,7 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt, if (!got_frame) return 0; - double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, NULL); + double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, &priv->codec_timebase); struct mp_audio *mpframe = mp_audio_from_avframe(priv->avframe); if (!mpframe) diff --git a/audio/decode/ad_spdif.c b/audio/decode/ad_spdif.c index 7298d9e7d7..eb2e2bb0a9 100644 --- a/audio/decode/ad_spdif.c +++ b/audio/decode/ad_spdif.c @@ -171,7 +171,11 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt) if (!stream) goto fail; +#if HAVE_AVCODEC_HAS_CODECPAR + stream->codecpar->codec_id = spdif_ctx->codec_id; +#else stream->codec->codec_id = spdif_ctx->codec_id; +#endif AVDictionary *format_opts = NULL; diff --git a/audio/format.c b/audio/format.c index 06360b884b..bbe0cec9bb 100644 --- a/audio/format.c +++ b/audio/format.c @@ -245,6 +245,37 @@ void af_get_best_sample_formats(int src_format, int out_formats[AF_FORMAT_COUNT] out_formats[num] = 0; } +// Return the best match to src_samplerate from the list provided in the array +// *available, which must be terminated by 0, or itself NULL. If *available is +// empty or NULL, return a negative value. Exact match to src_samplerate is +// most preferred, followed by the lowest integer multiple, followed by the +// maximum of *available. +int af_select_best_samplerate(int src_samplerate, const int *available) +{ + if (!available) + return -1; + + int min_mult_rate = INT_MAX; + int max_rate = INT_MIN; + for (int i = 0; available[i]; i++) { + if (available[i] == src_samplerate) + return available[i]; + + if (!(available[i] % src_samplerate)) + min_mult_rate = MPMIN(min_mult_rate, available[i]); + + max_rate = MPMAX(max_rate, available[i]); + } + + if (min_mult_rate < INT_MAX) + return min_mult_rate; + + if (max_rate > INT_MIN) + return max_rate; + + return -1; +} + // Return the number of samples that make up one frame in this format. // You get the byte size by multiplying them with sample size and channel count. int af_format_sample_alignment(int format) diff --git a/audio/format.h b/audio/format.h index de1ff1a30d..3d5cc1024b 100644 --- a/audio/format.h +++ b/audio/format.h @@ -76,6 +76,7 @@ int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int sampler void af_fill_silence(void *dst, size_t bytes, int format); void af_get_best_sample_formats(int src_format, int out_formats[AF_FORMAT_COUNT]); +int af_select_best_samplerate(int src_sampelrate, const int *available); int af_format_sample_alignment(int format); diff --git a/audio/out/ao_coreaudio_exclusive.c b/audio/out/ao_coreaudio_exclusive.c index af07fe0f3e..510fa3a529 100644 --- a/audio/out/ao_coreaudio_exclusive.c +++ b/audio/out/ao_coreaudio_exclusive.c @@ -367,7 +367,7 @@ static void uninit(struct ao *ao) CHECK_CA_WARN("failed to remove device render callback"); if (!ca_change_physical_format_sync(ao, p->stream, p->original_asbd)) - MP_WARN(ao, "can't revert to original device format"); + MP_WARN(ao, "can't revert to original device format\n"); err = ca_enable_mixing(ao, p->device, p->changed_mixing); CHECK_CA_WARN("can't re-enable mixing"); diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c index b671301922..bf1728682a 100644 --- a/audio/out/ao_lavc.c +++ b/audio/out/ao_lavc.c @@ -108,6 +108,11 @@ static int init(struct ao *ao) codec = encode_lavc_get_codec(ao->encode_lavc_ctx, ac->stream); + int samplerate = af_select_best_samplerate(ao->samplerate, + codec->supported_samplerates); + if (samplerate > 0) + ao->samplerate = samplerate; + // TODO: Remove this redundancy with encode_lavc_alloc_stream also // setting the time base. // Using codec->time_bvase is deprecated, but needed for older lavf. diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 8d0ea30bd7..0e7dec885a 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -364,17 +364,9 @@ static bool search_samplerates(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat, } } - for (int i = 0; supported[i]; i++) { - // first choose the lowest integer multiple of the sample rate - if (!(supported[i] % ao->samplerate)) { - change_waveformat_samplerate(wformat, supported[i]); - return true; - } - } - - // then choose the highest supported (if any) - if (n) { - change_waveformat_samplerate(wformat, supported[n-1]); + int samplerate = af_select_best_samplerate(ao->samplerate, supported); + if (samplerate > 0) { + change_waveformat_samplerate(wformat, samplerate); return true; } diff --git a/common/av_common.c b/common/av_common.c index 8b979cabba..e40c751e9f 100644 --- a/common/av_common.c +++ b/common/av_common.c @@ -25,9 +25,12 @@ #include #include +#include "config.h" + #include "common/common.h" #include "common/msg.h" #include "demux/packet.h" +#include "demux/stheader.h" #include "av_common.h" #include "codecs.h" @@ -68,6 +71,19 @@ void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st) avctx->has_b_frames = st->has_b_frames; } +// This only copies ffmpeg-native codec parameters. Parameters produced by +// other demuxers must be handled manually. +void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c) +{ +#if HAVE_AVCODEC_HAS_CODECPAR + if (c->lav_codecpar) + avcodec_parameters_to_context(avctx, c->lav_codecpar); +#else + if (c->lav_headers) + mp_copy_lav_codec_headers(avctx, c->lav_headers); +#endif +} + // We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it. union pts { int64_t i; double d; }; @@ -144,6 +160,11 @@ void mp_set_avcodec_threads(struct mp_log *l, AVCodecContext *avctx, int threads avctx->thread_count = threads; } +static bool is_crap(AVCodec *codec) +{ + return !!strstr(codec->name, "_vdpau"); +} + void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type) { AVCodec *cur = NULL; @@ -151,7 +172,22 @@ void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type) cur = av_codec_next(cur); if (!cur) break; - if (av_codec_is_decoder(cur) && cur->type == type) { + if (av_codec_is_decoder(cur) && cur->type == type && !is_crap(cur)) { + mp_add_decoder(list, "lavc", mp_codec_from_av_codec_id(cur->id), + cur->name, cur->long_name); + } + } +} + +// (Abuses the decoder list data structures.) +void mp_add_lavc_encoders(struct mp_decoder_list *list) +{ + AVCodec *cur = NULL; + for (;;) { + cur = av_codec_next(cur); + if (!cur) + break; + if (av_codec_is_encoder(cur)) { mp_add_decoder(list, "lavc", mp_codec_from_av_codec_id(cur->id), cur->name, cur->long_name); } diff --git a/common/av_common.h b/common/av_common.h index e9df328fbf..e2b86bfe40 100644 --- a/common/av_common.h +++ b/common/av_common.h @@ -26,16 +26,19 @@ struct mp_decoder_list; struct demux_packet; +struct mp_codec_params; struct AVDictionary; struct mp_log; int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size); void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st); +void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c); void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb); int64_t mp_pts_to_av(double mp_pts, AVRational *tb); double mp_pts_from_av(int64_t av_pts, AVRational *tb); void mp_set_avcodec_threads(struct mp_log *l, AVCodecContext *avctx, int threads); void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type); +void mp_add_lavc_encoders(struct mp_decoder_list *list); int mp_codec_to_av_codec_id(const char *codec); const char *mp_codec_from_av_codec_id(int codec_id); void mp_set_avdict(struct AVDictionary **dict, char **kv); diff --git a/common/av_log.c b/common/av_log.c index 7e0b271c81..bfd9a836e8 100644 --- a/common/av_log.c +++ b/common/av_log.c @@ -131,12 +131,17 @@ 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)) { + char buffer[4096] = ""; + int pos = 0; const char *prefix = avc ? avc->item_name(ptr) : NULL; if (log_print_prefix && prefix) - mp_msg(log, mp_level, "%s: ", prefix); + pos = snprintf(buffer, sizeof(buffer), "%s: ", prefix); log_print_prefix = fmt[strlen(fmt) - 1] == '\n'; - mp_msg_va(log, mp_level, fmt, vl); + pos = MPMIN(MPMAX(pos, 0), sizeof(buffer)); + vsnprintf(buffer + pos, sizeof(buffer) - pos, fmt, vl); + + mp_msg(log, mp_level, "%s", buffer); } pthread_mutex_unlock(&log_lock); diff --git a/common/msg.c b/common/msg.c index 01d0bb78a0..534ba41cb5 100644 --- a/common/msg.c +++ b/common/msg.c @@ -31,6 +31,7 @@ #include "common/common.h" #include "common/global.h" #include "misc/ring.h" +#include "misc/bstr.h" #include "options/options.h" #include "osdep/terminal.h" #include "osdep/io.h" @@ -41,9 +42,6 @@ #include "msg.h" #include "msg_control.h" -/* maximum message length of mp_msg */ -#define MSGSIZE_MAX 6144 - struct mp_log_root { struct mpv_global *global; // --- protected by mp_msg_lock @@ -67,7 +65,7 @@ struct mp_log_root { * synchronized mp_log tree.) */ atomic_ulong reload_counter; // --- protected by mp_msg_lock - char buffer[MSGSIZE_MAX]; + bstr buffer; }; struct mp_log { @@ -77,6 +75,7 @@ struct mp_log { int level; // minimum log level for any outputs int terminal_level; // minimum log level for terminal output atomic_ulong reload_counter; + char *partial; }; struct mp_log_buffer { @@ -239,7 +238,8 @@ static bool test_terminal_level(struct mp_log *log, int lev) !(lev == MSGL_STATUS && terminal_in_background()); } -static void print_terminal_line(struct mp_log *log, int lev, char *text) +static void print_terminal_line(struct mp_log *log, int lev, + char *text, char *trail) { if (!test_terminal_level(log, lev)) return; @@ -268,7 +268,7 @@ static void print_terminal_line(struct mp_log *log, int lev, char *text) } } - fprintf(stream, "%s", text); + fprintf(stream, "%s%s", text, trail); if (root->color) set_term_color(stream, -1); @@ -337,17 +337,17 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va) pthread_mutex_lock(&mp_msg_lock); - char tmp[MSGSIZE_MAX]; - bool use_tmp = lev == MSGL_STATUS || lev == MSGL_STATS; - struct mp_log_root *root = log->root; - char *text = use_tmp ? tmp : root->buffer; - int len = use_tmp ? 0 : strlen(text); - if (vsnprintf(text + len, MSGSIZE_MAX - len, format, va) < 0) - snprintf(text + len, MSGSIZE_MAX - len, "[fprintf error]\n"); - text[MSGSIZE_MAX - 2] = '\n'; - text[MSGSIZE_MAX - 1] = 0; + root->buffer.len = 0; + + if (log->partial[0]) + bstr_xappend_asprintf(root, &root->buffer, "%s", log->partial); + log->partial[0] = '\0'; + + bstr_xappend_vasprintf(root, &root->buffer, format, va); + + char *text = root->buffer.start; if (lev == MSGL_STATS) { dump_stats(log, lev, text); @@ -366,7 +366,7 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va) char *next = &end[1]; char saved = next[0]; next[0] = '\0'; - print_terminal_line(log, lev, text); + print_terminal_line(log, lev, text, ""); write_log_file(log, lev, text); write_msg_to_buffers(log, lev, text); next[0] = saved; @@ -374,23 +374,27 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va) } if (lev == MSGL_STATUS) { - if (text[0]) { - len = strlen(text); - if (len < MSGSIZE_MAX - 1) { - text[len] = root->termosd ? '\r' : '\n'; - text[len + 1] = '\0'; - } - print_terminal_line(log, lev, text); - } - } else { - int leftover = strlen(text); - memmove(root->buffer, text, leftover + 1); + if (text[0]) + print_terminal_line(log, lev, text, root->termosd ? "\r" : "\n"); + } else if (text[0]) { + int size = strlen(text) + 1; + if (talloc_get_size(log->partial) < size) + log->partial = talloc_realloc(NULL, log->partial, char, size); + memcpy(log->partial, text, size); } } pthread_mutex_unlock(&mp_msg_lock); } +static void destroy_log(void *ptr) +{ + struct mp_log *log = ptr; + // This is not managed via talloc itself, because mp_msg calls must be + // thread-safe, while talloc is not thread-safe. + talloc_free(log->partial); +} + // Create a new log context, which uses talloc_ctx as talloc parent, and parent // as logical parent. // The name is the prefix put before the output. It's usually prefixed by the @@ -407,7 +411,9 @@ struct mp_log *mp_log_new(void *talloc_ctx, struct mp_log *parent, struct mp_log *log = talloc_zero(talloc_ctx, struct mp_log); if (!parent->root) return log; // same as null_log + talloc_set_destructor(log, destroy_log); log->root = parent->root; + log->partial = talloc_strdup(NULL, ""); if (name) { if (name[0] == '!') { name = &name[1]; diff --git a/common/playlist.c b/common/playlist.c index 4c003c5f21..b2b297d671 100644 --- a/common/playlist.c +++ b/common/playlist.c @@ -169,11 +169,9 @@ void playlist_shuffle(struct playlist *pl) arr[n] = pl->first; playlist_unlink(pl, pl->first); } - for (int n = 0; n < count; n++) { - int other = (int)((double)(count) * rand() / (RAND_MAX + 1.0)); - struct playlist_entry *tmp = arr[n]; - arr[n] = arr[other]; - arr[other] = tmp; + for (int n = 0; n < count - 1; n++) { + int j = (int)((double)(count - n) * rand() / (RAND_MAX + 1.0)); + MPSWAP(struct playlist_entry *, arr[n], arr[n + j]); } for (int n = 0; n < count; n++) playlist_add(pl, arr[n]); diff --git a/demux/demux.c b/demux/demux.c index 05a8551608..bedc2bf294 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -53,6 +53,7 @@ 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; +extern const demuxer_desc_t demuxer_desc_null; extern const demuxer_desc_t demuxer_desc_timeline; /* Please do not add any new demuxers here. If you want to implement a new @@ -76,6 +77,7 @@ const demuxer_desc_t *const demuxer_list[] = { &demuxer_desc_lavf, &demuxer_desc_mf, &demuxer_desc_playlist, + &demuxer_desc_null, NULL }; @@ -91,15 +93,13 @@ struct demux_internal { struct demuxer *d_buffer; // protected by lock; used to sync d_user/thread // The lock protects the packet queues (struct demux_stream), d_buffer, - // and some minor fields like thread_paused. + // and the fields below. pthread_mutex_t lock; pthread_cond_t wakeup; pthread_t thread; // -- All the following fields are protected by lock. - bool thread_paused; - int thread_request_pause; // counter, if >0, make demuxer thread pause bool thread_terminate; bool threading; void (*wakeup_cb)(void *ctx); @@ -130,14 +130,15 @@ struct demux_internal { double ts_offset; // timestamp offset to apply everything + void (*run_fn)(void *); // if non-NULL, function queued to be run on + void *run_fn_arg; // the thread as run_fn(run_fn_arg) + // Cached state. bool force_cache_update; double time_length; struct mp_tags *stream_metadata; + struct stream_cache_info stream_cache_info; int64_t stream_size; - int64_t stream_cache_size; - int64_t stream_cache_fill; - int stream_cache_idle; // Updated during init only. char *stream_base_filename; }; @@ -631,10 +632,10 @@ static void *demux_thread(void *pctx) mpthread_set_name("demux"); pthread_mutex_lock(&in->lock); while (!in->thread_terminate) { - in->thread_paused = in->thread_request_pause > 0; - if (in->thread_paused) { + if (in->run_fn) { + in->run_fn(in->run_fn_arg); + in->run_fn = NULL; pthread_cond_signal(&in->wakeup); - pthread_cond_wait(&in->wakeup, &in->lock); continue; } if (in->tracks_switched) { @@ -679,11 +680,11 @@ static struct demux_packet *dequeue_packet(struct demux_stream *ds) if (ts != MP_NOPTS_VALUE) ds->base_ts = ts; - if (pkt->keyframe) { + if (pkt->keyframe && ts != MP_NOPTS_VALUE) { // Update bitrate - only at keyframe points, because we use the // (possibly) reordered packet timestamps instead of realtime. double d = ts - ds->last_br_ts; - if (ts == MP_NOPTS_VALUE || ds->last_br_ts == MP_NOPTS_VALUE || d < 0) { + if (ds->last_br_ts == MP_NOPTS_VALUE || d < 0) { ds->bitrate = -1; ds->last_br_ts = ts; ds->last_br_bytes = 0; @@ -1404,9 +1405,7 @@ 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_cache_size = -1; - int64_t stream_cache_fill = -1; - int stream_cache_idle = -1; + struct stream_cache_info stream_cache_info = {.size = -1}; if (demuxer->desc->control) { demuxer->desc->control(demuxer, DEMUXER_CTRL_GET_TIME_LENGTH, @@ -1415,16 +1414,12 @@ static void update_cache(struct demux_internal *in) int64_t stream_size = stream_get_size(stream); stream_control(stream, STREAM_CTRL_GET_METADATA, &stream_metadata); - 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); + stream_control(stream, STREAM_CTRL_GET_CACHE_INFO, &stream_cache_info); pthread_mutex_lock(&in->lock); in->time_length = time_length; in->stream_size = stream_size; - in->stream_cache_size = stream_cache_size; - in->stream_cache_fill = stream_cache_fill; - in->stream_cache_idle = stream_cache_idle; + in->stream_cache_info = stream_cache_info; if (stream_metadata) { talloc_free(in->stream_metadata); in->stream_metadata = talloc_steal(in, stream_metadata); @@ -1437,26 +1432,16 @@ static void update_cache(struct demux_internal *in) static int cached_stream_control(struct demux_internal *in, int cmd, void *arg) { // If the cache is active, wake up the thread to possibly update cache state. - if (in->stream_cache_size >= 0) { + if (in->stream_cache_info.size >= 0) { in->force_cache_update = true; pthread_cond_signal(&in->wakeup); } switch (cmd) { - case STREAM_CTRL_GET_CACHE_SIZE: - if (in->stream_cache_size < 0) + case STREAM_CTRL_GET_CACHE_INFO: + if (in->stream_cache_info.size < 0) return STREAM_UNSUPPORTED; - *(int64_t *)arg = in->stream_cache_size; - return STREAM_OK; - case STREAM_CTRL_GET_CACHE_FILL: - if (in->stream_cache_fill < 0) - return STREAM_UNSUPPORTED; - *(int64_t *)arg = in->stream_cache_fill; - return STREAM_OK; - case STREAM_CTRL_GET_CACHE_IDLE: - if (in->stream_cache_idle < 0) - return STREAM_UNSUPPORTED; - *(int *)arg = in->stream_cache_idle; + *(struct stream_cache_info *)arg = in->stream_cache_info; return STREAM_OK; case STREAM_CTRL_GET_SIZE: if (in->stream_size < 0) @@ -1531,20 +1516,22 @@ static int cached_demux_control(struct demux_internal *in, int cmd, void *arg) return DEMUXER_CTRL_DONTKNOW; } -int demux_control(demuxer_t *demuxer, int cmd, void *arg) +struct demux_control_args { + struct demuxer *demuxer; + int cmd; + void *arg; + int *r; +}; + +static void thread_demux_control(void *p) { + struct demux_control_args *args = p; + struct demuxer *demuxer = args->demuxer; + int cmd = args->cmd; + void *arg = args->arg; struct demux_internal *in = demuxer->in; - - if (in->threading) { - pthread_mutex_lock(&in->lock); - int cr = cached_demux_control(in, cmd, arg); - pthread_mutex_unlock(&in->lock); - if (cr != DEMUXER_CTRL_DONTKNOW) - return cr; - } - int r = DEMUXER_CTRL_NOTIMPL; - demux_pause(demuxer); + if (cmd == DEMUXER_CTRL_STREAM_CTRL) { struct demux_ctrl_stream_ctrl *c = arg; if (in->threading) @@ -1559,7 +1546,26 @@ int demux_control(demuxer_t *demuxer, int cmd, void *arg) if (demuxer->desc->control) r = demuxer->desc->control(demuxer->in->d_thread, cmd, arg); } - demux_unpause(demuxer); + + *args->r = r; +} + +int demux_control(demuxer_t *demuxer, int cmd, void *arg) +{ + struct demux_internal *in = demuxer->in; + + if (in->threading) { + pthread_mutex_lock(&in->lock); + int cr = cached_demux_control(in, cmd, arg); + pthread_mutex_unlock(&in->lock); + if (cr != DEMUXER_CTRL_DONTKNOW) + return cr; + } + + int r = 0; + struct demux_control_args args = {demuxer, cmd, arg, &r}; + demux_run_on_thread(demuxer, thread_demux_control, &args); + return r; } @@ -1570,40 +1576,25 @@ int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg) return c.res; } -// Make the demuxer thread stop doing anything. -// demux_unpause() wakes up the thread again. -// Can be nested with other calls, but trying to read packets may deadlock. -void demux_pause(demuxer_t *demuxer) +void demux_run_on_thread(struct demuxer *demuxer, void (*fn)(void *), void *ctx) { struct demux_internal *in = demuxer->in; assert(demuxer == in->d_user); - if (!in->threading) - return; - - MP_VERBOSE(in, "pause demux thread\n"); - - pthread_mutex_lock(&in->lock); - in->thread_request_pause++; - pthread_cond_signal(&in->wakeup); - while (!in->thread_paused) - pthread_cond_wait(&in->wakeup, &in->lock); - pthread_mutex_unlock(&in->lock); -} - -void demux_unpause(demuxer_t *demuxer) -{ - struct demux_internal *in = demuxer->in; - assert(demuxer == in->d_user); - - if (!in->threading) - return; - - pthread_mutex_lock(&in->lock); - assert(in->thread_request_pause > 0); - in->thread_request_pause--; - pthread_cond_signal(&in->wakeup); - pthread_mutex_unlock(&in->lock); + if (in->threading) { + MP_VERBOSE(in, "blocking on demuxer thread\n"); + pthread_mutex_lock(&in->lock); + while (in->run_fn) + pthread_cond_wait(&in->wakeup, &in->lock); + in->run_fn = fn; + in->run_fn_arg = ctx; + pthread_cond_signal(&in->wakeup); + while (in->run_fn) + pthread_cond_wait(&in->wakeup, &in->lock); + pthread_mutex_unlock(&in->lock); + } else { + fn(ctx); + } } bool demux_cancel_test(struct demuxer *demuxer) diff --git a/demux/demux.h b/demux/demux.h index e882e90da8..a26dada6c8 100644 --- a/demux/demux.h +++ b/demux/demux.h @@ -174,7 +174,7 @@ typedef struct demuxer { int64_t filepos; // input stream current pos. char *filename; // same as stream->url bool seekable; - bool partially_seekable; // implies seekable=true + bool partially_seekable; // true if _maybe_ seekable; implies seekable=true double start_time; // File format allows PTS resets (even if the current file is without) bool ts_resets_possible; @@ -285,8 +285,7 @@ double demuxer_get_time_length(struct demuxer *demuxer); int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg); -void demux_pause(demuxer_t *demuxer); -void demux_unpause(demuxer_t *demuxer); +void demux_run_on_thread(struct demuxer *demuxer, void (*fn)(void *), void *ctx); void demux_changed(demuxer_t *demuxer, int events); void demux_update(demuxer_t *demuxer); diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c index 8a92a2886f..e4ad5881ca 100644 --- a/demux/demux_lavf.c +++ b/demux/demux_lavf.c @@ -550,8 +550,14 @@ static void handle_new_stream(demuxer_t *demuxer, int i) lavf_priv_t *priv = demuxer->priv; AVFormatContext *avfc = priv->avfc; AVStream *st = avfc->streams[i]; - AVCodecContext *codec = st->codec; struct sh_stream *sh = NULL; +#if HAVE_AVCODEC_HAS_CODECPAR + AVCodecParameters *codec = st->codecpar; + int lavc_delay = codec->initial_padding; +#else + AVCodecContext *codec = st->codec; + int lavc_delay = codec->delay; +#endif switch (codec->codec_type) { case AVMEDIA_TYPE_AUDIO: { @@ -566,7 +572,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i) double delay = 0; if (codec->sample_rate > 0) - delay = codec->delay / (double)codec->sample_rate; + delay = lavc_delay / (double)codec->sample_rate; priv->seek_delay = MPMAX(priv->seek_delay, delay); export_replaygain(demuxer, sh->codec, st); @@ -577,8 +583,8 @@ static void handle_new_stream(demuxer_t *demuxer, int i) sh = demux_alloc_sh_stream(STREAM_VIDEO); if (st->disposition & AV_DISPOSITION_ATTACHED_PIC) { - sh->attached_picture = new_demux_packet_from(st->attached_pic.data, - st->attached_pic.size); + sh->attached_picture = + new_demux_packet_from_avpacket(&st->attached_pic); if (sh->attached_picture) { sh->attached_picture->pts = 0; talloc_steal(sh, sh->attached_picture); @@ -588,21 +594,8 @@ static void handle_new_stream(demuxer_t *demuxer, int i) sh->codec->disp_w = codec->width; sh->codec->disp_h = codec->height; - /* Try to make up some frame rate value, even if it's not reliable. - * FPS information is needed to support subtitle formats which base - * timing on frame numbers. - * Libavformat seems to report no "reliable" FPS value for AVI files, - * while they are typically constant enough FPS that the value this - * heuristic makes up works with subtitles in practice. - */ - double fps; if (st->avg_frame_rate.num) - fps = av_q2d(st->avg_frame_rate); - else - fps = 1.0 / FFMAX(av_q2d(st->time_base), - av_q2d(st->codec->time_base) * - st->codec->ticks_per_frame); - sh->codec->fps = fps; + sh->codec->fps = av_q2d(st->avg_frame_rate); if (priv->format_hack.image_format) sh->codec->fps = demuxer->opts->mf_fps; sh->codec->par_w = st->sample_aspect_ratio.num; @@ -634,12 +627,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i) if (av_opt_get_q(avfc, "subfps", AV_OPT_SEARCH_CHILDREN, &r) >= 0) { // File headers don't have a FPS set. if (r.num < 1 || r.den < 1) - sh->codec->frame_based = av_q2d(av_inv_q(codec->time_base)); - } else { - // Older libavformat versions. If the FPS matches the microdvd - // reader's default, assume it uses frame based timing. - if (codec->time_base.num == 125 && codec->time_base.den == 2997) - sh->codec->frame_based = 23.976; + sh->codec->frame_based = 23.976; // default timebase } } break; @@ -665,9 +653,17 @@ static void handle_new_stream(demuxer_t *demuxer, int i) sh->ff_index = st->index; sh->codec->codec = mp_codec_from_av_codec_id(codec->codec_id); sh->codec->codec_tag = codec->codec_tag; +#if HAVE_AVCODEC_HAS_CODECPAR + sh->codec->lav_codecpar = avcodec_parameters_alloc(); + if (sh->codec->lav_codecpar) + avcodec_parameters_copy(sh->codec->lav_codecpar, codec); +#else + sh->codec->codec = mp_codec_from_av_codec_id(codec->codec_id); + sh->codec->codec_tag = codec->codec_tag; sh->codec->lav_headers = avcodec_alloc_context3(NULL); if (sh->codec->lav_headers) mp_copy_lav_codec_headers(sh->codec->lav_headers, codec); +#endif if (st->disposition & AV_DISPOSITION_DEFAULT) sh->default_track = true; diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c index c932b45f39..b0a910bf68 100644 --- a/demux/demux_mkv.c +++ b/demux/demux_mkv.c @@ -152,7 +152,7 @@ typedef struct mkv_index { struct block_info { uint64_t duration, discardpadding; - bool simple, keyframe; + bool simple, keyframe, duration_known; int64_t timecode; mkv_track_t *track; bstr data; @@ -1687,9 +1687,11 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track) sh_a->samplerate = 48000; } - // This field tends to be broken, and our decoder can interpolate the - // missing timestamps anyway. - track->default_duration = 0; + // 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(codec, "ac3") == 0) + track->default_duration = 0; sh_a->extradata = extradata; sh_a->extradata_size = extradata_len; @@ -2461,7 +2463,7 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info) dp->pts = current_pts + i * track->default_duration; if (stream->codec->avi_dts) MPSWAP(double, dp->pts, dp->dts); - if (i == 0) + if (i == 0 && block_info->duration_known) dp->duration = block_duration / 1e9; if (stream->type == STREAM_AUDIO) { unsigned int srate = stream->codec->samplerate; @@ -2504,6 +2506,7 @@ static int read_block_group(demuxer_t *demuxer, int64_t end, if (block->duration == EBML_UINT_INVALID) goto error; block->duration *= mkv_d->tc_scale; + block->duration_known = true; break; case MATROSKA_ID_DISCARDPADDING: diff --git a/demux/demux_null.c b/demux/demux_null.c new file mode 100644 index 0000000000..1d4f4df794 --- /dev/null +++ b/demux/demux_null.c @@ -0,0 +1,34 @@ +/* + * 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 . + */ + +#include "misc/bstr.h" +#include "stream/stream.h" +#include "demux.h" + +static int try_open_file(struct demuxer *demux, enum demux_check check) +{ + if (!bstr_startswith0(bstr0(demux->filename), "null://") && + check != DEMUX_CHECK_REQUEST) + return -1; + return 0; +} + +const struct demuxer_desc demuxer_desc_null = { + .name = "null", + .desc = "null demuxer", + .open = try_open_file, +}; diff --git a/demux/demux_timeline.c b/demux/demux_timeline.c index 0c6c3986cc..af3389ba73 100644 --- a/demux/demux_timeline.c +++ b/demux/demux_timeline.c @@ -93,12 +93,10 @@ static void switch_segment(struct demuxer *demuxer, struct segment *new, double start_pts, int flags) { struct priv *p = demuxer->priv; - - if (p->current == new) - return; + bool new_segment = p->current != new; if (!(flags & (SEEK_FORWARD | SEEK_BACKWARD))) - flags |= SEEK_BACKWARD; + flags |= SEEK_BACKWARD | SEEK_HR; MP_VERBOSE(demuxer, "switch to segment %d\n", new->index); @@ -109,7 +107,8 @@ static void switch_segment(struct demuxer *demuxer, struct segment *new, for (int n = 0; n < p->num_streams; n++) { struct virtual_stream *vs = &p->streams[n]; - vs->new_segment = true; + if (new_segment) + vs->new_segment = true; vs->eos_packets = 0; } @@ -132,7 +131,6 @@ static void d_seek(struct demuxer *demuxer, double seek_pts, int flags) } } - p->current = NULL; // force seek switch_segment(demuxer, new, pts, flags); } @@ -344,7 +342,7 @@ static int d_open(struct demuxer *demuxer, enum demux_check check) print_timeline(demuxer); demuxer->seekable = true; - demuxer->partially_seekable = true; + demuxer->partially_seekable = false; demuxer->filetype = meta->filetype ? meta->filetype : meta->desc->name; diff --git a/demux/stheader.h b/demux/stheader.h index 35be20885a..77d0eb1da3 100644 --- a/demux/stheader.h +++ b/demux/stheader.h @@ -68,7 +68,9 @@ struct mp_codec_params { int extradata_size; // Codec specific header data (set by demux_lavf.c only) + // Which one is in use depends on HAVE_AVCODEC_HAS_CODECPAR. struct AVCodecContext *lav_headers; + struct AVCodecParameters *lav_codecpar; // STREAM_AUDIO int samplerate; diff --git a/etc/mplayer-input.conf b/etc/mplayer-input.conf index 97eafaefea..c88177733e 100644 --- a/etc/mplayer-input.conf +++ b/etc/mplayer-input.conf @@ -1,7 +1,7 @@ ## ## MPlayer-style key bindings ## -## Save it as ~/.config/input.conf to use it. +## Save it as ~/.config/mpv/input.conf to use it. ## ## Generally, it's recommended to use this as reference-only. ## diff --git a/input/event.c b/input/event.c index f765342518..188e800699 100644 --- a/input/event.c +++ b/input/event.c @@ -56,8 +56,8 @@ 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, enum mp_dnd_action action) { - // X11 and Wayland file list format. - if (strcmp(mime_type, "text/uri-list") == 0) { + // (text lists are the only format supported right now) + if (mp_event_get_mime_type_score(ictx, mime_type) >= 0) { void *tmp = talloc_new(NULL); int num_files = 0; char **files = NULL; @@ -76,3 +76,18 @@ int mp_event_drop_mime_data(struct input_ctx *ictx, const char *mime_type, return -1; } } + +int mp_event_get_mime_type_score(struct input_ctx *ictx, const char *mime_type) +{ + // X11 and Wayland file list format. + if (strcmp(mime_type, "text/uri-list") == 0) + return 10; + // Just text; treat it the same for convenience. + if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) + return 5; + if (strcmp(mime_type, "text/plain") == 0) + return 4; + if (strcmp(mime_type, "text") == 0) + return 0; + return -1; +} diff --git a/input/event.h b/input/event.h index 3a06ce1d1d..1e2149bb89 100644 --- a/input/event.h +++ b/input/event.h @@ -14,6 +14,8 @@ * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see . */ +#ifndef MP_INPUT_EVENT_H_ +#define MP_INPUT_EVENT_H_ #include "misc/bstr.h" @@ -32,3 +34,10 @@ void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files, // 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, enum mp_dnd_action append); + +// Many drag & drop APIs support multiple mime types, and this function returns +// whether a type is preferred (higher integer score), or supported (scores +// below 0 indicate unsupported types). +int mp_event_get_mime_type_score(struct input_ctx *ictx, const char *mime_type); + +#endif diff --git a/input/input.c b/input/input.c index ff7dcea67e..b6ed77debf 100644 --- a/input/input.c +++ b/input/input.c @@ -625,7 +625,7 @@ static void mp_input_feed_key(struct input_ctx *ictx, int code, double scale, } double now = mp_time_sec(); // ignore system-doubleclick if we generate these events ourselves - if (opts->doubleclick_time && MP_KEY_IS_MOUSE_BTN_DBL(unmod)) + if (!force_mouse && opts->doubleclick_time && MP_KEY_IS_MOUSE_BTN_DBL(unmod)) return; interpret_key(ictx, code, scale); if (code & MP_KEY_STATE_DOWN) { diff --git a/input/input.h b/input/input.h index 41432eb54d..a5710b6065 100644 --- a/input/input.h +++ b/input/input.h @@ -257,4 +257,13 @@ struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api, struct mpv_global *global); void mp_uninit_ipc(struct mp_ipc_ctx *ctx); +// Serialize the given mpv_event structure to JSON. Returns an allocated string. +struct mpv_event; +char *mp_json_encode_event(struct mpv_event *event); + +// Given the raw IPC input buffer "buf", remove the first newline-separated +// command, execute it and return the result (if any) as an allocated string. +struct mpv_handle; +char *mp_ipc_consume_next_command(struct mpv_handle *client, void *ctx, bstr *buf); + #endif /* MPLAYER_INPUT_H */ diff --git a/input/ipc-unix.c b/input/ipc-unix.c new file mode 100644 index 0000000000..8b8a158c5f --- /dev/null +++ b/input/ipc-unix.c @@ -0,0 +1,422 @@ +/* + * 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 . + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "config.h" + +#include "osdep/io.h" +#include "osdep/threads.h" + +#include "common/common.h" +#include "common/global.h" +#include "common/msg.h" +#include "input/input.h" +#include "libmpv/client.h" +#include "options/options.h" +#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; + const char *path; + + pthread_t thread; + int death_pipe[2]; +}; + +struct client_arg { + struct mp_log *log; + struct mpv_handle *client; + + char *client_name; + int client_fd; + bool close_client_fd; + + bool writable; +}; + +static int ipc_write_str(struct client_arg *client, const char *buf) +{ + size_t count = strlen(buf); + while (count > 0) { + ssize_t rc = send(client->client_fd, buf, count, MSG_NOSIGNAL); + if (rc <= 0) { + if (rc == 0) + return -1; + + if (errno == EBADF) { + client->writable = false; + return 0; + } + + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + return 0; + + return rc; + } + + count -= rc; + buf += rc; + } + + return 0; +} + +static void *client_thread(void *p) +{ + pthread_detach(pthread_self()); + + int rc; + + struct client_arg *arg = p; + bstr client_msg = { talloc_strdup(NULL, ""), 0 }; + + mpthread_set_name(arg->client_name); + + int pipe_fd = mpv_get_wakeup_pipe(arg->client); + if (pipe_fd < 0) { + MP_ERR(arg, "Could not get wakeup pipe\n"); + goto done; + } + + MP_VERBOSE(arg, "Client connected\n"); + + struct pollfd fds[2] = { + {.events = POLLIN, .fd = pipe_fd}, + {.events = POLLIN, .fd = arg->client_fd}, + }; + + fcntl(arg->client_fd, F_SETFL, fcntl(arg->client_fd, F_GETFL, 0) | O_NONBLOCK); + mpv_suspend(arg->client); + + while (1) { + rc = poll(fds, 2, 0); + if (rc == 0) { + mpv_resume(arg->client); + rc = poll(fds, 2, -1); + mpv_suspend(arg->client); + } + if (rc < 0) { + MP_ERR(arg, "Poll error\n"); + continue; + } + + if (fds[0].revents & POLLIN) { + char discard[100]; + read(pipe_fd, discard, sizeof(discard)); + + while (1) { + mpv_event *event = mpv_wait_event(arg->client, 0); + + if (event->event_id == MPV_EVENT_NONE) + break; + + if (event->event_id == MPV_EVENT_SHUTDOWN) + goto done; + + if (!arg->writable) + continue; + + char *event_msg = mp_json_encode_event(event); + if (!event_msg) { + MP_ERR(arg, "Encoding error\n"); + goto done; + } + + rc = ipc_write_str(arg, event_msg); + talloc_free(event_msg); + if (rc < 0) { + MP_ERR(arg, "Write error (%s)\n", mp_strerror(errno)); + goto done; + } + } + } + + if (fds[1].revents & (POLLIN | POLLHUP)) { + while (1) { + char buf[128]; + bstr append = { buf, 0 }; + + ssize_t bytes = read(arg->client_fd, buf, sizeof(buf)); + if (bytes < 0) { + if (errno == EAGAIN) + break; + + MP_ERR(arg, "Read error (%s)\n", mp_strerror(errno)); + goto done; + } + + if (bytes == 0) { + MP_VERBOSE(arg, "Client disconnected\n"); + goto done; + } + + append.len = bytes; + + bstr_xappend(NULL, &client_msg, append); + + while (bstrchr(client_msg, '\n') != -1) { + char *reply_msg = mp_ipc_consume_next_command(arg->client, + NULL, &client_msg); + + if (reply_msg && arg->writable) { + rc = ipc_write_str(arg, reply_msg); + if (rc < 0) { + MP_ERR(arg, "Write error (%s)\n", mp_strerror(errno)); + talloc_free(reply_msg); + goto done; + } + } + + talloc_free(reply_msg); + } + } + } + } + +done: + if (client_msg.len > 0) + MP_WARN(arg, "Ignoring unterminated command on disconnect.\n"); + talloc_free(client_msg.start); + if (arg->close_client_fd) + close(arg->client_fd); + mpv_detach_destroy(arg->client); + talloc_free(arg); + return NULL; +} + +static void ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client) +{ + client->client = mp_new_client(ctx->client_api, client->client_name), + client->log = mp_client_get_log(client->client); + + pthread_t client_thr; + if (pthread_create(&client_thr, NULL, client_thread, client)) { + mpv_detach_destroy(client->client); + if (client->close_client_fd) + close(client->client_fd); + talloc_free(client); + } +} + +static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, int fd) +{ + struct client_arg *client = talloc_ptrtype(NULL, client); + *client = (struct client_arg){ + .client_name = talloc_asprintf(client, "ipc-%d", id), + .client_fd = fd, + .close_client_fd = true, + + .writable = true, + }; + + ipc_start_client(ctx, client); +} + +static void ipc_start_client_text(struct mp_ipc_ctx *ctx, const char *path) +{ + int mode = O_RDONLY; + int client_fd = -1; + bool close_client_fd = true; + bool writable = false; + + if (strcmp(path, "/dev/stdin") == 0) { // for symmetry with Linux + client_fd = STDIN_FILENO; + close_client_fd = false; + } else if (strncmp(path, "fd://", 5) == 0) { + char *end = NULL; + client_fd = strtol(path + 5, &end, 0); + if (!end || end == path + 5 || end[0]) { + MP_ERR(ctx, "Invalid FD: %s\n", path); + return; + } + close_client_fd = false; + writable = true; // maybe + } else { + // Use RDWR for FIFOs to ensure they stay open over multiple accesses. + struct stat st; + if (stat(path, &st) == 0 && S_ISFIFO(st.st_mode)) + mode = O_RDWR; + client_fd = open(path, mode); + } + if (client_fd < 0) { + MP_ERR(ctx, "Could not open '%s'\n", path); + return; + } + + struct client_arg *client = talloc_ptrtype(NULL, client); + *client = (struct client_arg){ + .client_name = "input-file", + .client_fd = client_fd, + .close_client_fd = close_client_fd, + .writable = writable, + }; + + ipc_start_client(ctx, client); +} + +static void *ipc_thread(void *p) +{ + int rc; + + int ipc_fd; + struct sockaddr_un ipc_un = {0}; + + struct mp_ipc_ctx *arg = p; + + mpthread_set_name("ipc socket listener"); + + MP_VERBOSE(arg, "Starting IPC master\n"); + + ipc_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (ipc_fd < 0) { + MP_ERR(arg, "Could not create IPC socket\n"); + goto done; + } + +#if HAVE_FCHMOD + fchmod(ipc_fd, 0600); +#endif + + size_t path_len = strlen(arg->path); + if (path_len >= sizeof(ipc_un.sun_path) - 1) { + MP_ERR(arg, "Could not create IPC socket\n"); + goto done; + } + + ipc_un.sun_family = AF_UNIX, + strncpy(ipc_un.sun_path, arg->path, sizeof(ipc_un.sun_path)); + + unlink(ipc_un.sun_path); + + if (ipc_un.sun_path[0] == '@') { + ipc_un.sun_path[0] = '\0'; + path_len--; + } + + size_t addr_len = offsetof(struct sockaddr_un, sun_path) + 1 + path_len; + rc = bind(ipc_fd, (struct sockaddr *) &ipc_un, addr_len); + if (rc < 0) { + MP_ERR(arg, "Could not bind IPC socket\n"); + goto done; + } + + rc = listen(ipc_fd, 10); + if (rc < 0) { + MP_ERR(arg, "Could not listen on IPC socket\n"); + goto done; + } + + int client_num = 0; + + struct pollfd fds[2] = { + {.events = POLLIN, .fd = arg->death_pipe[0]}, + {.events = POLLIN, .fd = ipc_fd}, + }; + + while (1) { + rc = poll(fds, 2, -1); + if (rc < 0) { + MP_ERR(arg, "Poll error\n"); + continue; + } + + if (fds[0].revents & POLLIN) + goto done; + + if (fds[1].revents & POLLIN) { + int client_fd = accept(ipc_fd, NULL, NULL); + if (client_fd < 0) { + MP_ERR(arg, "Could not accept IPC client\n"); + goto done; + } + + ipc_start_client_json(arg, client_num++, client_fd); + } + } + +done: + if (ipc_fd >= 0) + close(ipc_fd); + + return NULL; +} + +struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api, + struct mpv_global *global) +{ + struct MPOpts *opts = global->opts; + + struct mp_ipc_ctx *arg = talloc_ptrtype(NULL, arg); + *arg = (struct mp_ipc_ctx){ + .log = mp_log_new(arg, global->log, "ipc"), + .client_api = client_api, + .path = mp_get_user_path(arg, global, opts->ipc_path), + .death_pipe = {-1, -1}, + }; + char *input_file = mp_get_user_path(arg, global, opts->input_file); + + if (input_file && *input_file) + ipc_start_client_text(arg, input_file); + + if (!opts->ipc_path || !*opts->ipc_path) + goto out; + + if (mp_make_wakeup_pipe(arg->death_pipe) < 0) + goto out; + + if (pthread_create(&arg->thread, NULL, ipc_thread, arg)) + goto out; + + return arg; + +out: + close(arg->death_pipe[0]); + close(arg->death_pipe[1]); + talloc_free(arg); + return NULL; +} + +void mp_uninit_ipc(struct mp_ipc_ctx *arg) +{ + if (!arg) + return; + + write(arg->death_pipe[1], &(char){0}, 1); + pthread_join(arg->thread, NULL); + + close(arg->death_pipe[0]); + close(arg->death_pipe[1]); + talloc_free(arg); +} diff --git a/input/ipc-win.c b/input/ipc-win.c new file mode 100644 index 0000000000..b0010cba7d --- /dev/null +++ b/input/ipc-win.c @@ -0,0 +1,502 @@ +/* + * 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 . + */ + +#include +#include + +#include "config.h" + +#include "osdep/io.h" +#include "osdep/threads.h" +#include "osdep/windows_utils.h" + +#include "common/common.h" +#include "common/global.h" +#include "common/msg.h" +#include "input/input.h" +#include "libmpv/client.h" +#include "options/options.h" +#include "player/client.h" + +struct mp_ipc_ctx { + struct mp_log *log; + struct mp_client_api *client_api; + const wchar_t *path; + + pthread_t thread; + HANDLE death_event; +}; + +struct client_arg { + struct mp_log *log; + struct mpv_handle *client; + + char *client_name; + HANDLE client_h; + bool writable; + OVERLAPPED write_ol; +}; + +// Get a string SID representing the current user. Must be freed by LocalFree. +static char *get_user_sid(void) +{ + char *ssid = NULL; + TOKEN_USER *info = NULL; + HANDLE t; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &t)) + goto done; + + DWORD info_len; + if (!GetTokenInformation(t, TokenUser, NULL, 0, &info_len) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + goto done; + + info = talloc_size(NULL, info_len); + if (!GetTokenInformation(t, TokenUser, info, info_len, &info_len)) + goto done; + if (!info->User.Sid) + goto done; + + ConvertSidToStringSidA(info->User.Sid, &ssid); +done: + if (t) + CloseHandle(t); + talloc_free(info); + return ssid; +} + +// Get a string SID for the process integrity level. Must be freed by LocalFree. +static char *get_integrity_sid(void) +{ + char *ssid = NULL; + TOKEN_MANDATORY_LABEL *info = NULL; + HANDLE t; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &t)) + goto done; + + DWORD info_len; + if (!GetTokenInformation(t, TokenIntegrityLevel, NULL, 0, &info_len) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + goto done; + + info = talloc_size(NULL, info_len); + if (!GetTokenInformation(t, TokenIntegrityLevel, info, info_len, &info_len)) + goto done; + if (!info->Label.Sid) + goto done; + + ConvertSidToStringSidA(info->Label.Sid, &ssid); +done: + if (t) + CloseHandle(t); + talloc_free(info); + return ssid; +} + +// Create a security descriptor that only grants access to processes running +// under the current user at the current integrity level or higher +static PSECURITY_DESCRIPTOR create_restricted_sd(void) +{ + char *user_sid = get_user_sid(); + char *integrity_sid = get_integrity_sid(); + if (!user_sid || !integrity_sid) + return NULL; + + char *sddl = talloc_asprintf(NULL, + "O:%s" // Set the owner to user_sid + "D:(A;;GRGW;;;%s)" // Grant GENERIC_{READ,WRITE} access to user_sid + "S:(ML;;NRNWNX;;;%s)", // Disallow read, write and execute permissions + // to integrity levels below integrity_sid + user_sid, user_sid, integrity_sid); + LocalFree(user_sid); + LocalFree(integrity_sid); + + PSECURITY_DESCRIPTOR sd = NULL; + ConvertStringSecurityDescriptorToSecurityDescriptorA(sddl, SDDL_REVISION_1, + &sd, NULL); + talloc_free(sddl); + + return sd; +} + +static void wakeup_cb(void *d) +{ + HANDLE event = d; + SetEvent(event); +} + +// Wrapper for ReadFile that treats ERROR_IO_PENDING as success +static DWORD async_read(HANDLE file, void *buf, unsigned size, OVERLAPPED* ol) +{ + DWORD err = ReadFile(file, buf, size, NULL, ol) ? 0 : GetLastError(); + return err == ERROR_IO_PENDING ? 0 : err; +} + +// Wrapper for WriteFile that treats ERROR_IO_PENDING as success +static DWORD async_write(HANDLE file, const void *buf, unsigned size, OVERLAPPED* ol) +{ + DWORD err = WriteFile(file, buf, size, NULL, ol) ? 0 : GetLastError(); + return err == ERROR_IO_PENDING ? 0 : err; +} + +static bool pipe_error_is_fatal(DWORD error) +{ + switch (error) { + case 0: + case ERROR_HANDLE_EOF: + case ERROR_BROKEN_PIPE: + case ERROR_PIPE_NOT_CONNECTED: + case ERROR_NO_DATA: + return false; + } + return true; +} + +static DWORD ipc_write_str(struct client_arg *arg, const char *buf) +{ + DWORD error = 0; + + if ((error = async_write(arg->client_h, buf, strlen(buf), &arg->write_ol))) + goto done; + if (!GetOverlappedResult(arg->client_h, &arg->write_ol, &(DWORD){0}, TRUE)) { + error = GetLastError(); + goto done; + } + +done: + if (pipe_error_is_fatal(error)) { + MP_VERBOSE(arg, "Error writing to pipe: %s\n", + mp_HRESULT_to_str(HRESULT_FROM_WIN32(error))); + } + + if (error) + arg->writable = false; + return error; +} + +static void report_read_error(struct client_arg *arg, DWORD error) +{ + // Only report the error if it's not just due to the pipe closing + if (pipe_error_is_fatal(error)) { + MP_ERR(arg, "Error reading from pipe: %s\n", + mp_HRESULT_to_str(HRESULT_FROM_WIN32(error))); + } else { + MP_VERBOSE(arg, "Client disconnected\n"); + } +} + +static void *client_thread(void *p) +{ + pthread_detach(pthread_self()); + + struct client_arg *arg = p; + char buf[4096]; + HANDLE wakeup_event = CreateEventW(NULL, TRUE, FALSE, NULL); + OVERLAPPED ol = { .hEvent = CreateEventW(NULL, TRUE, TRUE, NULL) }; + bstr client_msg = { talloc_strdup(NULL, ""), 0 }; + DWORD ioerr = 0; + DWORD r; + + mpthread_set_name(arg->client_name); + + arg->write_ol.hEvent = CreateEventW(NULL, TRUE, TRUE, NULL); + if (!wakeup_event || !ol.hEvent || !arg->write_ol.hEvent) { + MP_ERR(arg, "Couldn't create events\n"); + goto done; + } + + MP_VERBOSE(arg, "Client connected\n"); + + mpv_set_wakeup_callback(arg->client, wakeup_cb, wakeup_event); + mpv_suspend(arg->client); + + // Do the first read operation on the pipe + if ((ioerr = async_read(arg->client_h, buf, 4096, &ol))) { + report_read_error(arg, ioerr); + goto done; + } + + while (1) { + HANDLE handles[] = { wakeup_event, ol.hEvent }; + int n = WaitForMultipleObjects(2, handles, FALSE, 0); + if (n == WAIT_TIMEOUT) { + mpv_resume(arg->client); + n = WaitForMultipleObjects(2, handles, FALSE, INFINITE); + mpv_suspend(arg->client); + } + + switch (n) { + case WAIT_OBJECT_0: // wakeup_event + ResetEvent(wakeup_event); + + while (1) { + mpv_event *event = mpv_wait_event(arg->client, 0); + + if (event->event_id == MPV_EVENT_NONE) + break; + + if (event->event_id == MPV_EVENT_SHUTDOWN) + goto done; + + if (!arg->writable) + continue; + + char *event_msg = mp_json_encode_event(event); + if (!event_msg) { + MP_ERR(arg, "Encoding error\n"); + goto done; + } + + ipc_write_str(arg, event_msg); + talloc_free(event_msg); + } + + break; + case WAIT_OBJECT_0 + 1: // ol.hEvent + // Complete the read operation on the pipe + if (!GetOverlappedResult(arg->client_h, &ol, &r, TRUE)) { + report_read_error(arg, GetLastError()); + goto done; + } + + bstr_xappend(NULL, &client_msg, (bstr){buf, r}); + while (bstrchr(client_msg, '\n') != -1) { + char *reply_msg = mp_ipc_consume_next_command(arg->client, + NULL, &client_msg); + if (reply_msg && arg->writable) + ipc_write_str(arg, reply_msg); + talloc_free(reply_msg); + } + + // Begin the next read operation on the pipe + if ((ioerr = async_read(arg->client_h, buf, 4096, &ol))) { + report_read_error(arg, ioerr); + goto done; + } + break; + default: + MP_ERR(arg, "WaitForMultipleObjects failed\n"); + goto done; + } + } + +done: + if (client_msg.len > 0) + MP_WARN(arg, "Ignoring unterminated command on disconnect.\n"); + + if (CancelIoEx(arg->client_h, &ol) || GetLastError() != ERROR_NOT_FOUND) + GetOverlappedResult(arg->client_h, &ol, &(DWORD){0}, TRUE); + if (wakeup_event) + CloseHandle(wakeup_event); + if (ol.hEvent) + CloseHandle(ol.hEvent); + if (arg->write_ol.hEvent) + CloseHandle(arg->write_ol.hEvent); + + CloseHandle(arg->client_h); + mpv_detach_destroy(arg->client); + talloc_free(arg); + return NULL; +} + +static void ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client) +{ + client->client = mp_new_client(ctx->client_api, client->client_name), + client->log = mp_client_get_log(client->client); + + pthread_t client_thr; + if (pthread_create(&client_thr, NULL, client_thread, client)) { + mpv_detach_destroy(client->client); + CloseHandle(client->client_h); + talloc_free(client); + } +} + +static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, HANDLE h) +{ + struct client_arg *client = talloc_ptrtype(NULL, client); + *client = (struct client_arg){ + .client_name = talloc_asprintf(client, "ipc-%d", id), + .client_h = h, + .writable = true, + }; + + ipc_start_client(ctx, client); +} + +static void *ipc_thread(void *p) +{ + // Use PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE so message framing is + // maintained for message-mode clients, but byte-mode clients can still + // connect, send and receive data. This is the most compatible mode. + static const DWORD state = + PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT | + PIPE_REJECT_REMOTE_CLIENTS; + static const DWORD mode = + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED; + static const DWORD bufsiz = 4096; + + struct mp_ipc_ctx *arg = p; + HANDLE server = INVALID_HANDLE_VALUE; + HANDLE client = INVALID_HANDLE_VALUE; + int client_num = 0; + + mpthread_set_name("ipc named pipe listener"); + MP_VERBOSE(arg, "Starting IPC master\n"); + + SECURITY_ATTRIBUTES sa = { + .nLength = sizeof sa, + .lpSecurityDescriptor = create_restricted_sd(), + }; + if (!sa.lpSecurityDescriptor) { + MP_ERR(arg, "Couldn't create security descriptor"); + goto done; + } + + OVERLAPPED ol = { .hEvent = CreateEventW(NULL, TRUE, TRUE, NULL) }; + if (!ol.hEvent) { + MP_ERR(arg, "Couldn't create event"); + goto done; + } + + server = CreateNamedPipeW(arg->path, mode | FILE_FLAG_FIRST_PIPE_INSTANCE, + state, PIPE_UNLIMITED_INSTANCES, bufsiz, bufsiz, 0, &sa); + if (server == INVALID_HANDLE_VALUE) { + MP_ERR(arg, "Couldn't create first pipe instance: %s\n", + mp_LastError_to_str()); + goto done; + } + + while (1) { + DWORD err = ConnectNamedPipe(server, &ol) ? 0 : GetLastError(); + + if (err == ERROR_IO_PENDING) { + int n = WaitForMultipleObjects(2, (HANDLE[]) { + arg->death_event, + ol.hEvent, + }, FALSE, INFINITE) - WAIT_OBJECT_0; + + switch (n) { + case 0: + // Stop waiting for new clients + CancelIo(server); + GetOverlappedResult(server, &ol, &(DWORD){0}, TRUE); + goto done; + case 1: + // Complete the ConnectNamedPipe request + err = GetOverlappedResult(server, &ol, &(DWORD){0}, TRUE) + ? 0 : GetLastError(); + break; + default: + MP_ERR(arg, "WaitForMultipleObjects failed\n"); + goto done; + } + } + + // ERROR_PIPE_CONNECTED is returned if a client connects before + // ConnectNamedPipe is called. ERROR_NO_DATA is returned if a client + // connects, (possibly) writes data and exits before ConnectNamedPipe + // is called. Both cases should be handled as normal connections. + if (err == ERROR_PIPE_CONNECTED || err == ERROR_NO_DATA) + err = 0; + + if (err) { + MP_ERR(arg, "ConnectNamedPipe failed: %s\n", + mp_HRESULT_to_str(HRESULT_FROM_WIN32(err))); + goto done; + } + + // Create the next pipe instance before the client thread to avoid the + // theoretical race condition where the client thread immediately + // closes the handle and there are no active instances of the pipe + client = server; + server = CreateNamedPipeW(arg->path, mode, state, + PIPE_UNLIMITED_INSTANCES, bufsiz, bufsiz, 0, &sa); + if (server == INVALID_HANDLE_VALUE) { + MP_ERR(arg, "Couldn't create additional pipe instance: %s\n", + mp_LastError_to_str()); + goto done; + } + + ipc_start_client_json(arg, client_num++, client); + client = NULL; + } + +done: + if (sa.lpSecurityDescriptor) + LocalFree(sa.lpSecurityDescriptor); + if (client != INVALID_HANDLE_VALUE) + CloseHandle(client); + if (server != INVALID_HANDLE_VALUE) + CloseHandle(server); + if (ol.hEvent) + CloseHandle(ol.hEvent); + return NULL; +} + +struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api, + struct mpv_global *global) +{ + struct MPOpts *opts = global->opts; + + struct mp_ipc_ctx *arg = talloc_ptrtype(NULL, arg); + *arg = (struct mp_ipc_ctx){ + .log = mp_log_new(arg, global->log, "ipc"), + .client_api = client_api, + }; + + if (!opts->ipc_path || !*opts->ipc_path) + goto out; + + // Ensure the path is a legal Win32 pipe name by prepending \\.\pipe\ if + // it's not already present. Qt's QLocalSocket uses the same logic, so + // cross-platform programs that use paths like /tmp/mpv-socket should just + // work. (Win32 converts this path to \Device\NamedPipe\tmp\mpv-socket) + if (!strncmp(opts->ipc_path, "\\\\.\\pipe\\", 9)) { + arg->path = mp_from_utf8(arg, opts->ipc_path); + } else { + char *path = talloc_asprintf(NULL, "\\\\.\\pipe\\%s", opts->ipc_path); + arg->path = mp_from_utf8(arg, path); + talloc_free(path); + } + + if (!(arg->death_event = CreateEventW(NULL, TRUE, FALSE, NULL))) + goto out; + + if (pthread_create(&arg->thread, NULL, ipc_thread, arg)) + goto out; + + return arg; + +out: + if (arg->death_event) + CloseHandle(arg->death_event); + talloc_free(arg); + return NULL; +} + +void mp_uninit_ipc(struct mp_ipc_ctx *arg) +{ + if (!arg) + return; + + SetEvent(arg->death_event); + pthread_join(arg->thread, NULL); + + CloseHandle(arg->death_event); + talloc_free(arg); +} diff --git a/input/ipc.c b/input/ipc.c index c628fc9ea4..c7563e30d8 100644 --- a/input/ipc.c +++ b/input/ipc.c @@ -15,58 +15,16 @@ * License along with mpv. If not, see . */ -#include -#include -#include -#include - -#include -#include -#include -#include -#include - #include "config.h" -#include "osdep/io.h" -#include "osdep/threads.h" - -#include "common/common.h" -#include "common/global.h" #include "common/msg.h" #include "input/input.h" -#include "libmpv/client.h" -#include "misc/bstr.h" #include "misc/json.h" #include "options/m_option.h" #include "options/options.h" #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; - const char *path; - - pthread_t thread; - int death_pipe[2]; -}; - -struct client_arg { - struct mp_log *log; - struct mpv_handle *client; - - char *client_name; - int client_fd; - bool close_client_fd; - - bool writable; -}; - static mpv_node *mpv_node_map_get(mpv_node *src, const char *key) { if (src->format != MPV_FORMAT_NODE_MAP) @@ -219,7 +177,7 @@ static void mpv_event_to_node(void *ta_parent, mpv_event *event, mpv_node *dst) } } -static char *json_encode_event(mpv_event *event) +char *mp_json_encode_event(mpv_event *event) { void *ta_parent = talloc_new(NULL); mpv_node event_node = {.format = MPV_FORMAT_NODE_MAP, .u.list = NULL}; @@ -236,11 +194,12 @@ static char *json_encode_event(mpv_event *event) } // Function is allowed to modify src[n]. -static char *json_execute_command(struct client_arg *arg, void *ta_parent, +static char *json_execute_command(struct mpv_handle *client, void *ta_parent, char *src) { int rc; const char *cmd = NULL; + struct mp_log *log = mp_client_get_log(client); mpv_node msg_node; mpv_node reply_node = {.format = MPV_FORMAT_NODE_MAP, .u.list = NULL}; @@ -248,7 +207,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent, rc = json_parse(ta_parent, &msg_node, &src, 3); if (rc < 0) { - MP_ERR(arg, "malformed JSON received\n"); + mp_err(log, "malformed JSON received\n"); rc = MPV_ERROR_INVALID_PARAMETER; goto error; } @@ -278,11 +237,11 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent, cmd = cmd_str_node->u.string; if (!strcmp("client_name", cmd)) { - const char *client_name = mpv_client_name(arg->client); + const char *client_name = mpv_client_name(client); mpv_node_map_add_string(ta_parent, &reply_node, "data", client_name); rc = MPV_ERROR_SUCCESS; } else if (!strcmp("get_time_us", cmd)) { - int64_t time_us = mpv_get_time_us(arg->client); + int64_t time_us = mpv_get_time_us(client); mpv_node_map_add_int64(ta_parent, &reply_node, "data", time_us); rc = MPV_ERROR_SUCCESS; } else if (!strcmp("get_version", cmd)) { @@ -302,7 +261,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent, goto error; } - rc = mpv_get_property(arg->client, cmd_node->u.list->values[1].u.string, + rc = mpv_get_property(client, cmd_node->u.list->values[1].u.string, MPV_FORMAT_NODE, &result_node); if (rc >= 0) { mpv_node_map_add(ta_parent, &reply_node, "data", &result_node); @@ -319,7 +278,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent, goto error; } - char *result = mpv_get_property_string(arg->client, + char *result = mpv_get_property_string(client, cmd_node->u.list->values[1].u.string); if (!result) { mpv_node_map_add_null(ta_parent, &reply_node, "data"); @@ -338,7 +297,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent, goto error; } - rc = mpv_set_property(arg->client, cmd_node->u.list->values[1].u.string, + rc = mpv_set_property(client, cmd_node->u.list->values[1].u.string, MPV_FORMAT_NODE, &cmd_node->u.list->values[2]); } else if (!strcmp("set_property_string", cmd)) { if (cmd_node->u.list->num != 3) { @@ -356,7 +315,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent, goto error; } - rc = mpv_set_property_string(arg->client, + rc = mpv_set_property_string(client, cmd_node->u.list->values[1].u.string, cmd_node->u.list->values[2].u.string); } else if (!strcmp("observe_property", cmd)) { @@ -375,7 +334,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent, goto error; } - rc = mpv_observe_property(arg->client, + rc = mpv_observe_property(client, cmd_node->u.list->values[1].u.int64, cmd_node->u.list->values[2].u.string, MPV_FORMAT_NODE); @@ -395,7 +354,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent, goto error; } - rc = mpv_observe_property(arg->client, + rc = mpv_observe_property(client, cmd_node->u.list->values[1].u.int64, cmd_node->u.list->values[2].u.string, MPV_FORMAT_STRING); @@ -410,8 +369,8 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent, goto error; } - rc = mpv_unobserve_property(arg->client, - cmd_node->u.list->values[1].u.int64); + rc = mpv_unobserve_property(client, + cmd_node->u.list->values[1].u.int64); } else if (!strcmp("request_log_messages", cmd)) { if (cmd_node->u.list->num != 2) { rc = MPV_ERROR_INVALID_PARAMETER; @@ -423,13 +382,13 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent, goto error; } - rc = mpv_request_log_messages(arg->client, + rc = mpv_request_log_messages(client, cmd_node->u.list->values[1].u.string); } else if (!strcmp("suspend", cmd)) { - mpv_suspend(arg->client); + mpv_suspend(client); rc = MPV_ERROR_SUCCESS; } else if (!strcmp("resume", cmd)) { - mpv_resume(arg->client); + mpv_resume(client); rc = MPV_ERROR_SUCCESS; } else if (!strcmp("enable_event", cmd) || !strcmp("disable_event", cmd)) @@ -449,7 +408,7 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent, char *name = cmd_node->u.list->values[1].u.string; if (strcmp(name, "all") == 0) { for (int n = 0; n < 64; n++) - mpv_request_event(arg->client, n, enable); + mpv_request_event(client, n, enable); rc = MPV_ERROR_SUCCESS; } else { int event = -1; @@ -462,12 +421,12 @@ static char *json_execute_command(struct client_arg *arg, void *ta_parent, rc = MPV_ERROR_INVALID_PARAMETER; goto error; } - rc = mpv_request_event(arg->client, event, enable); + rc = mpv_request_event(client, event, enable); } } else { mpv_node result_node; - rc = mpv_command_node(arg->client, cmd_node, &result_node); + rc = mpv_command_node(client, cmd_node, &result_node); if (rc >= 0) mpv_node_map_add(ta_parent, &reply_node, "data", &result_node); } @@ -490,382 +449,35 @@ error: return output; } -static char *text_execute_command(struct client_arg *arg, void *tmp, char *src) +static char *text_execute_command(struct mpv_handle *client, void *tmp, char *src) { - mpv_command_string(arg->client, src); + mpv_command_string(client, src); return NULL; } -static int ipc_write_str(struct client_arg *client, const char *buf) +char *mp_ipc_consume_next_command(struct mpv_handle *client, void *ctx, bstr *buf) { - size_t count = strlen(buf); - while (count > 0) { - ssize_t rc = send(client->client_fd, buf, count, MSG_NOSIGNAL); - if (rc <= 0) { - if (rc == 0) - return -1; + void *tmp = talloc_new(NULL); - if (errno == EBADF) { - client->writable = false; - return 0; - } + bstr rest; + bstr line = bstr_getline(*buf, &rest); + char *line0 = bstrto0(tmp, line); + talloc_steal(tmp, buf->start); + *buf = bstrdup(NULL, rest); - if (errno == EINTR) - continue; + json_skip_whitespace(&line0); - if (errno == EAGAIN) - return 0; - - return rc; - } - - count -= rc; - buf += rc; - } - - return 0; -} - -static void *client_thread(void *p) -{ - pthread_detach(pthread_self()); - - int rc; - - struct client_arg *arg = p; - bstr client_msg = { talloc_strdup(NULL, ""), 0 }; - - mpthread_set_name(arg->client_name); - - int pipe_fd = mpv_get_wakeup_pipe(arg->client); - if (pipe_fd < 0) { - MP_ERR(arg, "Could not get wakeup pipe\n"); - goto done; - } - - MP_VERBOSE(arg, "Client connected\n"); - - struct pollfd fds[2] = { - {.events = POLLIN, .fd = pipe_fd}, - {.events = POLLIN, .fd = arg->client_fd}, - }; - - fcntl(arg->client_fd, F_SETFL, fcntl(arg->client_fd, F_GETFL, 0) | O_NONBLOCK); - mpv_suspend(arg->client); - - while (1) { - rc = poll(fds, 2, 0); - if (rc == 0) { - mpv_resume(arg->client); - rc = poll(fds, 2, -1); - mpv_suspend(arg->client); - } - if (rc < 0) { - MP_ERR(arg, "Poll error\n"); - continue; - } - - if (fds[0].revents & POLLIN) { - char discard[100]; - read(pipe_fd, discard, sizeof(discard)); - - while (1) { - mpv_event *event = mpv_wait_event(arg->client, 0); - - if (event->event_id == MPV_EVENT_NONE) - break; - - if (event->event_id == MPV_EVENT_SHUTDOWN) - goto done; - - if (!arg->writable) - continue; - - char *event_msg = json_encode_event(event); - if (!event_msg) { - MP_ERR(arg, "Encoding error\n"); - goto done; - } - - rc = ipc_write_str(arg, event_msg); - talloc_free(event_msg); - if (rc < 0) { - MP_ERR(arg, "Write error (%s)\n", mp_strerror(errno)); - goto done; - } - } - } - - if (fds[1].revents & (POLLIN | POLLHUP)) { - while (1) { - char buf[128]; - bstr append = { buf, 0 }; - - ssize_t bytes = read(arg->client_fd, buf, sizeof(buf)); - if (bytes < 0) { - if (errno == EAGAIN) - break; - - MP_ERR(arg, "Read error (%s)\n", mp_strerror(errno)); - goto done; - } - - if (bytes == 0) { - MP_VERBOSE(arg, "Client disconnected\n"); - goto done; - } - - append.len = bytes; - - bstr_xappend(NULL, &client_msg, append); - - while (bstrchr(client_msg, '\n') != -1) { - void *tmp = talloc_new(NULL); - bstr rest; - bstr line = bstr_getline(client_msg, &rest); - char *line0 = bstrto0(tmp, line); - talloc_steal(tmp, client_msg.start); - client_msg = bstrdup(NULL, rest); - - json_skip_whitespace(&line0); - - char *reply_msg = NULL; - if (line0[0] == '\0' || line0[0] == '#') { - // skip - } else if (line0[0] == '{') { - reply_msg = json_execute_command(arg, tmp, line0); - } else { - reply_msg = text_execute_command(arg, tmp, line0); - } - - if (reply_msg && arg->writable) { - rc = ipc_write_str(arg, reply_msg); - if (rc < 0) { - MP_ERR(arg, "Write error (%s)\n", mp_strerror(errno)); - talloc_free(tmp); - goto done; - } - } - - talloc_free(tmp); - } - } - } - } - -done: - if (client_msg.len > 0) - MP_WARN(arg, "Ignoring unterminated command on disconnect.\n"); - talloc_free(client_msg.start); - if (arg->close_client_fd) - close(arg->client_fd); - mpv_detach_destroy(arg->client); - talloc_free(arg); - return NULL; -} - -static void ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client) -{ - client->client = mp_new_client(ctx->client_api, client->client_name), - client->log = mp_client_get_log(client->client); - - pthread_t client_thr; - if (pthread_create(&client_thr, NULL, client_thread, client)) { - mpv_detach_destroy(client->client); - if (client->close_client_fd) - close(client->client_fd); - talloc_free(client); - } -} - -static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, int fd) -{ - struct client_arg *client = talloc_ptrtype(NULL, client); - *client = (struct client_arg){ - .client_name = talloc_asprintf(client, "ipc-%d", id), - .client_fd = fd, - .close_client_fd = true, - - .writable = true, - }; - - ipc_start_client(ctx, client); -} - -static void ipc_start_client_text(struct mp_ipc_ctx *ctx, const char *path) -{ - int mode = O_RDONLY; - int client_fd = -1; - bool close_client_fd = true; - bool writable = false; - - if (strcmp(path, "/dev/stdin") == 0) { // for symmetry with Linux - client_fd = STDIN_FILENO; - close_client_fd = false; - } else if (strncmp(path, "fd://", 5) == 0) { - char *end = NULL; - client_fd = strtol(path + 5, &end, 0); - if (!end || end == path + 5 || end[0]) { - MP_ERR(ctx, "Invalid FD: %s\n", path); - return; - } - close_client_fd = false; - writable = true; // maybe + char *reply_msg = NULL; + if (line0[0] == '\0' || line0[0] == '#') { + // skip + } else if (line0[0] == '{') { + reply_msg = json_execute_command(client, tmp, line0); } else { - // Use RDWR for FIFOs to ensure they stay open over multiple accesses. - struct stat st; - if (stat(path, &st) == 0 && S_ISFIFO(st.st_mode)) - mode = O_RDWR; - client_fd = open(path, mode); - } - if (client_fd < 0) { - MP_ERR(ctx, "Could not open '%s'\n", path); - return; + reply_msg = text_execute_command(client, tmp, line0); } - struct client_arg *client = talloc_ptrtype(NULL, client); - *client = (struct client_arg){ - .client_name = "input-file", - .client_fd = client_fd, - .close_client_fd = close_client_fd, - .writable = writable, - }; - - ipc_start_client(ctx, client); -} - -static void *ipc_thread(void *p) -{ - int rc; - - int ipc_fd; - struct sockaddr_un ipc_un = {0}; - - struct mp_ipc_ctx *arg = p; - - mpthread_set_name("ipc socket listener"); - - MP_VERBOSE(arg, "Starting IPC master\n"); - - ipc_fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (ipc_fd < 0) { - MP_ERR(arg, "Could not create IPC socket\n"); - goto done; - } - -#if HAVE_FCHMOD - fchmod(ipc_fd, 0600); -#endif - - size_t path_len = strlen(arg->path); - if (path_len >= sizeof(ipc_un.sun_path) - 1) { - MP_ERR(arg, "Could not create IPC socket\n"); - goto done; - } - - ipc_un.sun_family = AF_UNIX, - strncpy(ipc_un.sun_path, arg->path, sizeof(ipc_un.sun_path)); - - unlink(ipc_un.sun_path); - - if (ipc_un.sun_path[0] == '@') { - ipc_un.sun_path[0] = '\0'; - path_len--; - } - - size_t addr_len = offsetof(struct sockaddr_un, sun_path) + 1 + path_len; - rc = bind(ipc_fd, (struct sockaddr *) &ipc_un, addr_len); - if (rc < 0) { - MP_ERR(arg, "Could not bind IPC socket\n"); - goto done; - } - - rc = listen(ipc_fd, 10); - if (rc < 0) { - MP_ERR(arg, "Could not listen on IPC socket\n"); - goto done; - } - - int client_num = 0; - - struct pollfd fds[2] = { - {.events = POLLIN, .fd = arg->death_pipe[0]}, - {.events = POLLIN, .fd = ipc_fd}, - }; - - while (1) { - rc = poll(fds, 2, -1); - if (rc < 0) { - MP_ERR(arg, "Poll error\n"); - continue; - } - - if (fds[0].revents & POLLIN) - goto done; - - if (fds[1].revents & POLLIN) { - int client_fd = accept(ipc_fd, NULL, NULL); - if (client_fd < 0) { - MP_ERR(arg, "Could not accept IPC client\n"); - goto done; - } - - ipc_start_client_json(arg, client_num++, client_fd); - } - } - -done: - if (ipc_fd >= 0) - close(ipc_fd); - - return NULL; -} - -struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api, - struct mpv_global *global) -{ - struct MPOpts *opts = global->opts; - - struct mp_ipc_ctx *arg = talloc_ptrtype(NULL, arg); - *arg = (struct mp_ipc_ctx){ - .log = mp_log_new(arg, global->log, "ipc"), - .client_api = client_api, - .path = mp_get_user_path(arg, global, opts->ipc_path), - .death_pipe = {-1, -1}, - }; - char *input_file = mp_get_user_path(arg, global, opts->input_file); - - if (input_file && *input_file) - ipc_start_client_text(arg, input_file); - - if (!opts->ipc_path || !*opts->ipc_path) - goto out; - - if (mp_make_wakeup_pipe(arg->death_pipe) < 0) - goto out; - - if (pthread_create(&arg->thread, NULL, ipc_thread, arg)) - goto out; - - return arg; - -out: - close(arg->death_pipe[0]); - close(arg->death_pipe[1]); - talloc_free(arg); - return NULL; -} - -void mp_uninit_ipc(struct mp_ipc_ctx *arg) -{ - if (!arg) - return; - - write(arg->death_pipe[1], &(char){0}, 1); - pthread_join(arg->thread, NULL); - - close(arg->death_pipe[0]); - close(arg->death_pipe[1]); - talloc_free(arg); + talloc_steal(ctx, reply_msg); + talloc_free(tmp); + return reply_msg; } diff --git a/libmpv/client.h b/libmpv/client.h index d5faf45ba7..4ba96bd861 100644 --- a/libmpv/client.h +++ b/libmpv/client.h @@ -63,7 +63,8 @@ extern "C" { * * http://mpv.io/manual/master/#list-of-input-commands * * http://mpv.io/manual/master/#properties * - * You can also look at the examples in DOCS/client_api_examples/. + * You can also look at the examples here: + * * https://github.com/mpv-player/mpv-examples/tree/master/libmpv * * Event loop * ---------- diff --git a/libmpv/qthelper.hpp b/libmpv/qthelper.hpp index c25315f3b4..70b3baffc4 100644 --- a/libmpv/qthelper.hpp +++ b/libmpv/qthelper.hpp @@ -202,9 +202,9 @@ private: if (list->values) free_node(&list->values[n]); } + delete[] list->keys; + delete[] list->values; } - delete[] list->keys; - delete[] list->values; delete list; break; } diff --git a/misc/bstr.c b/misc/bstr.c index 13441eede9..1a2676c537 100644 --- a/misc/bstr.c +++ b/misc/bstr.c @@ -401,15 +401,21 @@ void bstr_xappend_vasprintf(void *talloc_ctx, bstr *s, const char *fmt, int size; va_list copy; va_copy(copy, ap); + size_t avail = talloc_get_size(s->start) - s->len; + char *dest = s->start ? s->start + s->len : NULL; char c; - size = vsnprintf(&c, 1, fmt, copy); + if (avail < 1) + dest = &c; + size = vsnprintf(dest, MPMAX(avail, 1), fmt, copy); va_end(copy); if (size < 0) abort(); - resize_append(talloc_ctx, s, size + 1); - vsnprintf(s->start + s->len, size + 1, fmt, ap); + if (avail < 1 || size + 1 > avail) { + resize_append(talloc_ctx, s, size + 1); + vsnprintf(s->start + s->len, size + 1, fmt, ap); + } s->len += size; } diff --git a/options/options.c b/options/options.c index ac7d706222..1ae48a6fc2 100644 --- a/options/options.c +++ b/options/options.c @@ -88,7 +88,9 @@ const struct m_opt_choice_alternatives mp_hwdec_names[] = { {"vaapi-copy", HWDEC_VAAPI_COPY}, {"dxva2", HWDEC_DXVA2}, {"dxva2-copy", HWDEC_DXVA2_COPY}, + {"d3d11va-copy",HWDEC_D3D11VA_COPY}, {"rpi", HWDEC_RPI}, + {"mediacodec", HWDEC_MEDIACODEC}, {0} }; @@ -581,7 +583,7 @@ const m_option_t mp_opts[] = { OPT_FLAG("input-terminal", consolecontrols, CONF_GLOBAL), OPT_STRING("input-file", input_file, M_OPT_FILE | M_OPT_GLOBAL), - OPT_STRING("input-unix-socket", ipc_path, M_OPT_FILE), + OPT_STRING("input-ipc-server", ipc_path, M_OPT_FILE), OPT_SUBSTRUCT("screenshot", screenshot_image_opts, image_writer_conf, 0), OPT_STRING("screenshot-template", screenshot_template, 0), @@ -676,6 +678,7 @@ const m_option_t mp_opts[] = { OPT_REPLACED("dtshd", "ad-spdif-dtshd"), OPT_REPLACED("ass-use-margins", "sub-use-margins"), OPT_REPLACED("media-title", "force-media-title"), + OPT_REPLACED("input-unix-socket", "input-ipc-server"), {0} }; @@ -706,7 +709,7 @@ const struct MPOpts mp_default_opts = { .border = 1, .WinID = -1, .window_scale = 1.0, - .x11_bypass_compositor = 1, + .x11_bypass_compositor = 0, .mmcss_profile = "Playback", }, .allow_win_drag = 1, diff --git a/osdep/mpv.rc b/osdep/mpv.rc index 9615c8ae8d..638d020b36 100644 --- a/osdep/mpv.rc +++ b/osdep/mpv.rc @@ -18,7 +18,6 @@ */ #include -#include "version.h" 1 VERSIONINFO FILEVERSION 2,0,0,0 @@ -26,26 +25,6 @@ PRODUCTVERSION 2,0,0,0 FILEOS VOS__WINDOWS32 FILETYPE VFT_APP { - BLOCK "StringFileInfo" - { - BLOCK "000004b0" // LANG_NEUTRAL,UNICODE_CP - { - VALUE "Comments","mpv is distributed under the terms of the GNU General Public License Version 2 or later.\000" - VALUE "CompanyName", "mpv\000" - VALUE "FileDescription", "mpv\000" - VALUE "FileVersion",VERSION - VALUE "LegalCopyright", " (C) 2000-2016 mpv/mplayer2/MPlayer\000" - //VALUE "LegalTrademarks"," \000"; - VALUE "OriginalFilename", "mpv.exe\000" - VALUE "ProductName", "mpv\000" - VALUE "ProductVersion",VERSION - //VALUE "SpecialBuild","\000" - } - } - BLOCK "VarFileInfo" - { - VALUE "Translation",0,0x04b0 - } } IDI_ICON1 ICON DISCARDABLE "etc/mpv-icon.ico" diff --git a/player/audio.c b/player/audio.c index f17587a1ca..3a2c60b8ab 100644 --- a/player/audio.c +++ b/player/audio.c @@ -134,6 +134,8 @@ static int recreate_audio_filters(struct MPContext *mpctx) mixer_reinit_audio(mpctx->mixer, mpctx->ao, afs); + mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL); + return 0; fail: @@ -368,6 +370,8 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx) update_playback_speed(mpctx); + mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL); + return; init_error: diff --git a/player/command.c b/player/command.c index 642330e34f..8a4e9805f1 100644 --- a/player/command.c +++ b/player/command.c @@ -32,6 +32,8 @@ #include "config.h" #include "mpv_talloc.h" #include "client.h" +#include "common/av_common.h" +#include "common/codecs.h" #include "common/msg.h" #include "common/msg_control.h" #include "command.h" @@ -417,6 +419,17 @@ static int mp_property_stream_path(void *ctx, struct m_property *prop, return m_property_strdup_ro(action, arg, stream->url); } +struct change_stream_capture_args { + char *filename; + struct demuxer *demux; +}; + +static void do_change_stream_capture(void *p) +{ + struct change_stream_capture_args *args = p; + stream_set_capture_file(args->demux->stream, args->filename); +} + static int mp_property_stream_capture(void *ctx, struct m_property *prop, int action, void *arg) { @@ -425,10 +438,8 @@ static int mp_property_stream_capture(void *ctx, struct m_property *prop, return M_PROPERTY_UNAVAILABLE; if (action == M_PROPERTY_SET) { - char *filename = *(char **)arg; - demux_pause(mpctx->demuxer); - stream_set_capture_file(mpctx->demuxer->stream, filename); - demux_unpause(mpctx->demuxer); + struct change_stream_capture_args args = {*(char **)arg, mpctx->demuxer}; + demux_run_on_thread(mpctx->demuxer, do_change_stream_capture, &args); // fall through to mp_property_generic_option } return mp_property_generic_option(mpctx, prop, action, arg); @@ -456,24 +467,14 @@ static int mp_property_file_format(void *ctx, struct m_property *prop, return m_property_strdup_ro(action, arg, name); } -/// Position in the stream (RW) static int mp_property_stream_pos(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; struct demuxer *demuxer = mpctx->demuxer; - if (!demuxer) + if (!demuxer || demuxer->filepos < 0) return M_PROPERTY_UNAVAILABLE; - demux_pause(demuxer); - int r; - if (action == M_PROPERTY_SET) { - stream_seek(demuxer->stream, *(int64_t *) arg); - r = M_PROPERTY_OK; - } else { - r = m_property_int64_ro(action, arg, stream_tell(demuxer->stream)); - } - demux_unpause(demuxer); - return r; + return m_property_int64_ro(action, arg, demuxer->filepos); } /// Stream end offset (RO) @@ -1085,11 +1086,12 @@ static int mp_property_angle(void *ctx, struct m_property *prop, if (angle < 0 || angle > angles) return M_PROPERTY_ERROR; - demux_pause(demuxer); demux_flush(demuxer); ris = demux_stream_control(demuxer, STREAM_CTRL_SET_ANGLE, &angle); - demux_control(demuxer, DEMUXER_CTRL_RESYNC, NULL); - demux_unpause(demuxer); + if (ris == STREAM_OK) { + demux_control(demuxer, DEMUXER_CTRL_RESYNC, NULL); + demux_flush(demuxer); + } reset_audio_state(mpctx); reset_video_state(mpctx); @@ -1368,11 +1370,11 @@ static int mp_property_cache_size(void *ctx, struct m_property *prop, switch (action) { case M_PROPERTY_GET: case M_PROPERTY_PRINT: { - int64_t size = -1; - demux_stream_control(demuxer, STREAM_CTRL_GET_CACHE_SIZE, &size); - if (size <= 0) + struct stream_cache_info info = {0}; + demux_stream_control(demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); + if (info.size <= 0) break; - return property_int_kb_size(size / 1024, action, arg); + return property_int_kb_size(info.size / 1024, action, arg); } case M_PROPERTY_GET_TYPE: *(struct m_option *)arg = (struct m_option){ @@ -1401,11 +1403,11 @@ static int mp_property_cache_used(void *ctx, struct m_property *prop, if (!mpctx->demuxer) return M_PROPERTY_UNAVAILABLE; - int64_t size = -1; - demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_FILL, &size); - if (size < 0) + struct stream_cache_info info = {0}; + demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); + if (info.size <= 0) return M_PROPERTY_UNAVAILABLE; - return property_int_kb_size(size / 1024, action, arg); + return property_int_kb_size(info.fill / 1024, action, arg); } static int mp_property_cache_free(void *ctx, struct m_property *prop, @@ -1415,29 +1417,43 @@ static int mp_property_cache_free(void *ctx, struct m_property *prop, if (!mpctx->demuxer) return M_PROPERTY_UNAVAILABLE; - int64_t size_used = -1; - demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_FILL, &size_used); - if (size_used < 0) + struct stream_cache_info info = {0}; + demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); + if (info.size <= 0) return M_PROPERTY_UNAVAILABLE; - int64_t size = -1; - demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_SIZE, &size); - if (size <= 0) + return property_int_kb_size((info.size - info.fill) / 1024, action, arg); +} + +static int mp_property_cache_speed(void *ctx, struct m_property *prop, + int action, void *arg) +{ + MPContext *mpctx = ctx; + if (!mpctx->demuxer) return M_PROPERTY_UNAVAILABLE; - return property_int_kb_size((size - size_used) / 1024, action, arg); + struct stream_cache_info info = {0}; + demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); + if (info.size <= 0) + return M_PROPERTY_UNAVAILABLE; + + if (action == M_PROPERTY_PRINT) { + *(char **)arg = talloc_strdup_append(format_file_size(info.speed), "/s"); + return M_PROPERTY_OK; + } + return m_property_int64_ro(action, arg, info.speed); } static int mp_property_cache_idle(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - int idle = -1; + struct stream_cache_info info = {0}; if (mpctx->demuxer) - demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_IDLE, &idle); - if (idle < 0) + demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); + if (info.size <= 0) return M_PROPERTY_UNAVAILABLE; - return m_property_flag_ro(action, arg, !!idle); + return m_property_flag_ro(action, arg, info.idle); } static int mp_property_demuxer_cache_duration(void *ctx, struct m_property *prop, @@ -1944,7 +1960,14 @@ static int get_track_entry(int item, int action, void *arg, void *ctx) struct MPContext *mpctx = ctx; struct track *track = mpctx->tracks[item]; - const char *codec = track->stream ? track->stream->codec->codec : NULL; + struct mp_codec_params p = + track->stream ? *track->stream->codec : (struct mp_codec_params){0}; + + const char *decoder_desc = NULL; + if (track->d_video) + decoder_desc = track->d_video->decoder_desc; + if (track->d_audio) + decoder_desc = track->d_audio->decoder_desc; struct m_sub_property props[] = { {"id", SUB_PROP_INT(track->user_tid)}, @@ -1965,9 +1988,20 @@ static int get_track_entry(int item, int action, void *arg, void *ctx) {"selected", SUB_PROP_FLAG(track->selected)}, {"external-filename", SUB_PROP_STR(track->external_filename), .unavailable = !track->external_filename}, - {"codec", SUB_PROP_STR(codec), - .unavailable = !codec}, {"ff-index", SUB_PROP_INT(track->ff_index)}, + {"decoder-desc", SUB_PROP_STR(decoder_desc), + .unavailable = !decoder_desc}, + {"codec", SUB_PROP_STR(p.codec), + .unavailable = !p.codec}, + {"demux-w", SUB_PROP_INT(p.disp_w), .unavailable = !p.disp_w}, + {"demux-h", SUB_PROP_INT(p.disp_h), .unavailable = !p.disp_h}, + {"demux-channel-count", SUB_PROP_INT(p.channels.num), + .unavailable = !p.channels.num}, + {"demux-channels", SUB_PROP_STR(mp_chmap_to_str(&p.channels)), + .unavailable = !p.channels.num}, + {"demux-samplerate", SUB_PROP_INT(p.samplerate), + .unavailable = !p.samplerate}, + {"demux-fps", SUB_PROP_DOUBLE(p.fps), .unavailable = p.fps <= 0}, {0} }; @@ -2699,7 +2733,7 @@ static int mp_property_osd_w(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd, OSDTYPE_OSD); + struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd); return m_property_int_ro(action, arg, vo_res.w); } @@ -2707,7 +2741,7 @@ static int mp_property_osd_h(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd, OSDTYPE_OSD); + struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd); return m_property_int_ro(action, arg, vo_res.h); } @@ -2715,7 +2749,7 @@ static int mp_property_osd_par(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd, OSDTYPE_OSD); + struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd); return m_property_double_ro(action, arg, vo_res.display_par); } @@ -3272,6 +3306,47 @@ static int mp_property_protocols(void *ctx, struct m_property *prop, return M_PROPERTY_NOT_IMPLEMENTED; } +static int get_decoder_entry(int item, int action, void *arg, void *ctx) +{ + struct mp_decoder_list *codecs = ctx; + struct mp_decoder_entry *c = &codecs->entries[item]; + + struct m_sub_property props[] = { + {"family", SUB_PROP_STR(c->family)}, + {"codec", SUB_PROP_STR(c->codec)}, + {"driver" , SUB_PROP_STR(c->decoder)}, + {"description", SUB_PROP_STR(c->desc)}, + {0} + }; + + return m_property_read_sub(props, action, arg); +} + +static int mp_property_decoders(void *ctx, struct m_property *prop, + int action, void *arg) +{ + struct mp_decoder_list *codecs = talloc_zero(NULL, struct mp_decoder_list); + struct mp_decoder_list *v = talloc_steal(codecs, video_decoder_list()); + struct mp_decoder_list *a = talloc_steal(codecs, audio_decoder_list()); + mp_append_decoders(codecs, v); + mp_append_decoders(codecs, a); + int r = m_property_read_list(action, arg, codecs->num_entries, + get_decoder_entry, codecs); + talloc_free(codecs); + return r; +} + +static int mp_property_encoders(void *ctx, struct m_property *prop, + int action, void *arg) +{ + struct mp_decoder_list *codecs = talloc_zero(NULL, struct mp_decoder_list); + mp_add_lavc_encoders(codecs); + int r = m_property_read_list(action, arg, codecs->num_entries, + get_decoder_entry, codecs); + talloc_free(codecs); + return r; +} + static int mp_property_version(void *ctx, struct m_property *prop, int action, void *arg) { @@ -3284,6 +3359,16 @@ static int mp_property_configuration(void *ctx, struct m_property *prop, return m_property_strdup_ro(action, arg, CONFIGURATION); } +static int mp_property_ffmpeg(void *ctx, struct m_property *prop, + int action, void *arg) +{ +#if HAVE_AV_VERSION_INFO + return m_property_strdup_ro(action, arg, av_version_info()); +#else + return M_PROPERTY_UNAVAILABLE; +#endif +} + static int mp_property_alias(void *ctx, struct m_property *prop, int action, void *arg) { @@ -3535,6 +3620,7 @@ static const struct m_property mp_properties[] = { {"cache-used", mp_property_cache_used}, {"cache-size", mp_property_cache_size}, {"cache-idle", mp_property_cache_idle}, + {"cache-speed", mp_property_cache_speed}, {"demuxer-cache-duration", mp_property_demuxer_cache_duration}, {"demuxer-cache-time", mp_property_demuxer_cache_time}, {"demuxer-cache-idle", mp_property_demuxer_cache_idle}, @@ -3591,6 +3677,7 @@ static const struct m_property mp_properties[] = { {"video-output-levels", mp_property_video_color, .priv = (void *)"output-levels"}, {"panscan", panscan_property_helper}, + {"keepaspect", panscan_property_helper}, {"video-zoom", panscan_property_helper}, {"video-align-x", panscan_property_helper}, {"video-align-y", panscan_property_helper}, @@ -3645,6 +3732,7 @@ static const struct m_property mp_properties[] = { {"af", mp_property_af}, {"video-rotate", video_simple_refresh_property}, + {"video-stereo-mode", video_simple_refresh_property}, {"ab-loop-a", mp_property_ab_loop}, {"ab-loop-b", mp_property_ab_loop}, @@ -3689,9 +3777,12 @@ static const struct m_property mp_properties[] = { {"working-directory", mp_property_cwd}, {"protocol-list", mp_property_protocols}, + {"decoder-list", mp_property_decoders}, + {"encoder-list", mp_property_encoders}, {"mpv-version", mp_property_version}, {"mpv-configuration", mp_property_configuration}, + {"ffmpeg-version", mp_property_ffmpeg}, {"options", mp_property_options}, {"file-local-options", mp_property_local_options}, @@ -3746,7 +3837,7 @@ static const char *const *const mp_event_property_change[] = { E(MPV_EVENT_CHAPTER_CHANGE, "chapter", "chapter-metadata"), E(MP_EVENT_CACHE_UPDATE, "cache", "cache-free", "cache-used", "cache-idle", "demuxer-cache-duration", "demuxer-cache-idle", "paused-for-cache", - "demuxer-cache-time"), + "demuxer-cache-time", "cache-buffering-state", "cache-speed"), E(MP_EVENT_WIN_RESIZE, "window-scale", "osd-width", "osd-height", "osd-par"), E(MP_EVENT_WIN_STATE, "window-minimized", "display-names", "display-fps", "fullscreen"), }; @@ -4753,6 +4844,8 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re struct track *t = find_track_with_url(mpctx, type, cmd->args[0].v.s); if (t) { mp_switch_track(mpctx, t->type, t, FLAG_MARK_SELECTION); + if (mpctx->playback_initialized) + print_track_list(mpctx, "Track switched:"); return 0; } } diff --git a/player/core.h b/player/core.h index f26cacbb51..21129d6ea3 100644 --- a/player/core.h +++ b/player/core.h @@ -147,10 +147,6 @@ struct track { struct vo_chain *vo_c; struct ao_chain *ao_c; struct lavfi_pad *sink; - - // For external subtitles, which are read fully on init. Do not attempt - // to read packets from them. - bool preloaded; }; // Summarizes video filtering and output. diff --git a/player/external_files.c b/player/external_files.c index c1b7d53c3c..dfd1ca6802 100644 --- a/player/external_files.c +++ b/player/external_files.c @@ -20,6 +20,7 @@ static const char *const sub_exts[] = {"utf", "utf8", "utf-8", "idx", "sub", "sr static const char *const audio_exts[] = {"mp3", "aac", "mka", "dts", "flac", "ogg", "m4a", "ac3", "opus", "wav", + "wv", NULL}; static bool test_ext_list(bstr ext, const char *const *list) diff --git a/player/loadfile.c b/player/loadfile.c index b1e9cd2057..5cc1ef5f77 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -1027,7 +1027,7 @@ reopen_file: goto terminate_playback; if (opts->stream_dump && opts->stream_dump[0]) { - if (stream_dump(mpctx, mpctx->stream_open_filename) < 0) + if (stream_dump(mpctx, mpctx->stream_open_filename) >= 0) mpctx->error_playing = 1; goto terminate_playback; } diff --git a/player/lua.c b/player/lua.c index 9fe1d0deca..84d7295640 100644 --- a/player/lua.c +++ b/player/lua.c @@ -391,6 +391,7 @@ static int load_lua(struct mpv_handle *client, const char *fname) r = 0; error_out: + osd_set_external(ctx->mpctx->osd, client, 0, 0, NULL); // remove overlay mp_resume_all(client); if (ctx->state) lua_close(ctx->state); @@ -963,31 +964,21 @@ static int script_command_native(lua_State *L) static int script_set_osd_ass(lua_State *L) { - struct MPContext *mpctx = get_mpctx(L); + struct script_ctx *ctx = get_ctx(L); int res_x = luaL_checkinteger(L, 1); int res_y = luaL_checkinteger(L, 2); const char *text = luaL_checkstring(L, 3); if (!text[0]) text = " "; // force external OSD initialization - osd_set_external(mpctx->osd, res_x, res_y, (char *)text); - mp_input_wakeup(mpctx->input); + osd_set_external(ctx->mpctx->osd, ctx->client, res_x, res_y, (char *)text); + mp_input_wakeup(ctx->mpctx->input); return 0; } -static int script_get_osd_resolution(lua_State *L) +static int script_get_osd_size(lua_State *L) { struct MPContext *mpctx = get_mpctx(L); - int w, h; - osd_object_get_resolution(mpctx->osd, OSDTYPE_EXTERNAL, &w, &h); - lua_pushnumber(L, w); - lua_pushnumber(L, h); - return 2; -} - -static int script_get_screen_size(lua_State *L) -{ - struct MPContext *mpctx = get_mpctx(L); - struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd, OSDTYPE_EXTERNAL); + struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd); double aspect = 1.0 * vo_res.w / MPMAX(vo_res.h, 1) / (vo_res.display_par ? vo_res.display_par : 1); lua_pushnumber(L, vo_res.w); @@ -996,10 +987,10 @@ static int script_get_screen_size(lua_State *L) return 3; } -static int script_get_screen_margins(lua_State *L) +static int script_get_osd_margins(lua_State *L) { struct MPContext *mpctx = get_mpctx(L); - struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd, OSDTYPE_EXTERNAL); + struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd); lua_pushnumber(L, vo_res.ml); lua_pushnumber(L, vo_res.mt); lua_pushnumber(L, vo_res.mr); @@ -1012,10 +1003,8 @@ static int script_get_mouse_pos(lua_State *L) struct MPContext *mpctx = get_mpctx(L); int px, py; mp_input_get_mouse_pos(mpctx->input, &px, &py); - double sw, sh; - osd_object_get_scale_factor(mpctx->osd, OSDTYPE_EXTERNAL, &sw, &sh); - lua_pushnumber(L, px * sw); - lua_pushnumber(L, py * sh); + lua_pushnumber(L, px); + lua_pushnumber(L, py); return 2; } @@ -1030,14 +1019,11 @@ static int script_input_set_section_mouse_area(lua_State *L) { struct MPContext *mpctx = get_mpctx(L); - double sw, sh; - osd_object_get_scale_factor(mpctx->osd, OSDTYPE_EXTERNAL, &sw, &sh); - char *section = (char *)luaL_checkstring(L, 1); - int x0 = sw ? luaL_checkinteger(L, 2) / sw : 0; - int y0 = sh ? luaL_checkinteger(L, 3) / sh : 0; - int x1 = sw ? luaL_checkinteger(L, 4) / sw : 0; - int y1 = sh ? luaL_checkinteger(L, 5) / sh : 0; + int x0 = luaL_checkinteger(L, 2); + int y0 = luaL_checkinteger(L, 3); + int x1 = luaL_checkinteger(L, 4); + int y1 = luaL_checkinteger(L, 5); mp_input_set_section_mouse_area(mpctx->input, section, x0, y0, x1, y1); return 0; } @@ -1266,9 +1252,8 @@ static const struct fn_entry main_fns[] = { FN_ENTRY(raw_observe_property), FN_ENTRY(raw_unobserve_property), FN_ENTRY(set_osd_ass), - FN_ENTRY(get_osd_resolution), - FN_ENTRY(get_screen_size), - FN_ENTRY(get_screen_margins), + FN_ENTRY(get_osd_size), + FN_ENTRY(get_osd_margins), FN_ENTRY(get_mouse_pos), FN_ENTRY(get_time), FN_ENTRY(input_set_section_mouse_area), diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua index d0ef57cccd..a12e5bd3c3 100644 --- a/player/lua/defaults.lua +++ b/player/lua/defaults.lua @@ -145,7 +145,7 @@ local function update_key_bindings() end local cfg = "" for k, v in pairs(key_bindings) do - if v.forced ~= def then + if v.bind and v.forced ~= def then cfg = cfg .. v.bind .. "\n" end end @@ -161,7 +161,6 @@ local function add_binding(attrs, key, name, fn, rp) fn = name name = reserve_binding() end - local bind = key local repeatable = rp == "repeatable" or rp["repeatable"] if rp["forced"] then attrs.forced = true @@ -205,7 +204,9 @@ local function add_binding(attrs, key, name, fn, rp) end msg_cb = fn end - attrs.bind = bind .. " script-binding " .. mp.script_name .. "/" .. name + if key and #key > 0 then + attrs.bind = key .. " script-binding " .. mp.script_name .. "/" .. name + end attrs.name = name key_bindings[name] = attrs update_key_bindings() @@ -438,7 +439,7 @@ local function call_event_handlers(e) end end -mp.use_suspend = true +mp.use_suspend = false function mp.dispatch_events(allow_wait) local more_events = true diff --git a/player/lua/osc.lua b/player/lua/osc.lua index 24afeef970..23f7cc6d19 100644 --- a/player/lua/osc.lua +++ b/player/lua/osc.lua @@ -99,6 +99,27 @@ local state = { -- Helperfunctions -- +-- scale factor for translating between real and virtual ASS coordinates +function get_virt_scale_factor() + local w, h = mp.get_osd_size() + if w <= 0 or h <= 0 then + return 0, 0 + end + return osc_param.playresx / w, osc_param.playresy / h +end + +-- return mouse position in virtual ASS coordinates (playresx/y) +function get_virt_mouse_pos() + local sx, sy = get_virt_scale_factor() + local x, y = mp.get_mouse_pos() + return x * sx, y * sy +end + +function set_virt_mouse_area(x0, y0, x1, y1, name) + local sx, sy = get_virt_scale_factor() + mp.set_mouse_area(x0 / sx, y0 / sy, x1 / sx, y1 / sy, name) +end + function scale_value(x0, x1, y0, y1, val) local m = (y1 - y0) / (x1 - x0) local b = y0 - (m * x0) @@ -141,7 +162,7 @@ function mouse_hit(element) end function mouse_hit_coords(bX1, bY1, bX2, bY2) - local mX, mY = mp.get_mouse_pos() + local mX, mY = get_virt_mouse_pos() return (mX >= bX1 and mX <= bX2 and mY >= bY1 and mY <= bY2) end @@ -182,7 +203,7 @@ end -- get value at current mouse position function get_slider_value(element) - return get_slider_value_at(element, mp.get_mouse_pos()) + return get_slider_value_at(element, get_virt_mouse_pos()) end function countone(val) @@ -589,7 +610,7 @@ function render_elements(master_ass) end elem_ass:new_event() - elem_ass:pos(mp.get_mouse_pos(), ty) + elem_ass:pos(get_virt_mouse_pos(), ty) elem_ass:an(an) elem_ass:append(slider_lo.tooltip_style) elem_ass:append(tooltiplabel) @@ -1298,7 +1319,7 @@ function osc_init() -- set canvas resolution according to display aspect and scaling setting local baseResY = 720 - local display_w, display_h, display_aspect = mp.get_screen_size() + local display_w, display_h, display_aspect = mp.get_osd_size() local scale = 1 if (mp.get_property("video") == "no") then -- dummy/forced window @@ -1718,8 +1739,8 @@ end function render() msg.debug("rendering") - local current_screen_sizeX, current_screen_sizeY, aspect = mp.get_screen_size() - local mouseX, mouseY = mp.get_mouse_pos() + local current_screen_sizeX, current_screen_sizeY, aspect = mp.get_osd_size() + local mouseX, mouseY = get_virt_mouse_pos() local now = mp.get_time() -- check if display changed, if so request reinit @@ -1782,7 +1803,7 @@ function render() --mouse show/hide area for k,cords in pairs(osc_param.areas["showhide"]) do - mp.set_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "showhide") + set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "showhide") end do_enable_keybindings() @@ -1791,7 +1812,7 @@ 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") + set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "input") end if state.osc_visible ~= state.input_enabled then if state.osc_visible then @@ -1887,7 +1908,7 @@ function process_event(source, what) state.mouse_down_counter = 0 elseif source == "mouse_move" then - local mouseX, mouseY = mp.get_mouse_pos() + local mouseX, mouseY = get_virt_mouse_pos() if (user_opts.minmousemove == 0) or (not ((state.last_mouseX == nil) or (state.last_mouseY == nil)) and ((math.abs(mouseX - state.last_mouseX) >= user_opts.minmousemove) diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua index 5b90b95cdd..c0ce8b97c4 100644 --- a/player/lua/ytdl_hook.lua +++ b/player/lua/ytdl_hook.lua @@ -207,7 +207,7 @@ mp.add_hook("on_load", 10, function () -- audio url mp.commandv("audio-add", json["requested_formats"][2].url, - "select", json["requested_formats"][2]["format_note"]) + "select", json["requested_formats"][2]["format_note"] or "") elseif not (json.url == nil) then -- normal video diff --git a/player/main.c b/player/main.c index 70176f2d35..21c273338c 100644 --- a/player/main.c +++ b/player/main.c @@ -472,9 +472,7 @@ int mp_initialize(struct MPContext *mpctx, char **options) if (opts->force_vo == 2 && handle_force_window(mpctx, false) < 0) return -1; -#if !defined(__MINGW32__) mpctx->ipc_ctx = mp_init_ipc(mpctx->clients, mpctx->global); -#endif #ifdef _WIN32 if (opts->w32_priority > 0) diff --git a/player/misc.c b/player/misc.c index d68ad1db3e..f14f5a43e3 100644 --- a/player/misc.c +++ b/player/misc.c @@ -95,23 +95,20 @@ double get_play_end_pts(struct MPContext *mpctx) float mp_get_cache_percent(struct MPContext *mpctx) { - if (mpctx->demuxer) { - int64_t size = -1; - int64_t fill = -1; - demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_SIZE, &size); - demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_FILL, &fill); - if (size > 0 && fill >= 0) - return fill / (size / 100.0); - } + struct stream_cache_info info = {0}; + if (mpctx->demuxer) + demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); + if (info.size > 0 && info.fill >= 0) + return info.fill / (info.size / 100.0); return -1; } bool mp_get_cache_idle(struct MPContext *mpctx) { - int idle = 0; + struct stream_cache_info info = {0}; if (mpctx->demuxer) - demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_IDLE, &idle); - return idle; + demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); + return info.idle; } void update_vo_playback_state(struct MPContext *mpctx) diff --git a/player/osd.c b/player/osd.c index 2a09911ac7..7da4a038bf 100644 --- a/player/osd.c +++ b/player/osd.c @@ -257,9 +257,9 @@ static void print_status(struct MPContext *mpctx) } if (mpctx->demuxer) { - int64_t fill = -1; - demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_FILL, &fill); - if (fill >= 0) { + struct stream_cache_info info = {0}; + demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); + if (info.size > 0) { saddf(&line, " Cache: "); struct demux_ctrl_reader_state s = {.ts_duration = -1}; @@ -270,10 +270,10 @@ static void print_status(struct MPContext *mpctx) } else { saddf(&line, "%2ds", (int)s.ts_duration); } - if (fill >= 1024 * 1024) { - saddf(&line, "+%lldMB", (long long)(fill / 1024 / 1024)); + if (info.fill >= 1024 * 1024) { + saddf(&line, "+%lldMB", (long long)(info.fill / 1024 / 1024)); } else { - saddf(&line, "+%lldKB", (long long)(fill / 1024)); + saddf(&line, "+%lldKB", (long long)(info.fill / 1024)); } } } @@ -561,6 +561,6 @@ void update_osd_msg(struct MPContext *mpctx) text = talloc_asprintf_append(text, "%s%s", text ? "\n" : "", mpctx->osd_msg_text); } - osd_set_text(osd, OSDTYPE_OSD, text); + osd_set_text(osd, text); talloc_free(text); } diff --git a/player/playloop.c b/player/playloop.c index 63234812d4..fcbb86ac04 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -522,8 +522,9 @@ static void handle_pause_on_low_cache(struct MPContext *mpctx) if (!mpctx->demuxer) return; - int idle = -1; - demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_IDLE, &idle); + struct stream_cache_info info = {0}; + demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); + int idle = info.size > 0 ? info.idle : -1; struct demux_ctrl_reader_state s = {.idle = true, .ts_duration = -1}; demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s); diff --git a/player/sub.c b/player/sub.c index 6892ac935b..6d01e0ca5d 100644 --- a/player/sub.c +++ b/player/sub.c @@ -71,8 +71,7 @@ void uninit_sub(struct MPContext *mpctx, struct track *track) reset_subtitles(mpctx, track); sub_select(track->d_sub, false); int order = get_order(mpctx, track); - if (order >= 0 && order <= 1) - osd_set_sub(mpctx->osd, OSDTYPE_SUB + order, NULL); + osd_set_sub(mpctx->osd, order, NULL); } } @@ -99,18 +98,15 @@ static bool update_subtitle(struct MPContext *mpctx, double video_pts, video_pts -= opts->sub_delay; - if (!track->preloaded && track->demuxer->fully_read && !opts->sub_clear_on_seek) - { + if (track->demuxer->fully_read && sub_can_preload(dec_sub)) { // Assume fully_read implies no interleaved audio/video streams. // (Reading packets will change the demuxer position.) demux_seek(track->demuxer, 0, 0); - track->preloaded = sub_read_all_packets(track->d_sub); + sub_preload(dec_sub); } - if (!track->preloaded) { - if (!sub_read_packets(dec_sub, video_pts)) - return false; - } + if (!sub_read_packets(dec_sub, video_pts)) + return false; // Handle displaying subtitles on terminal; never done for secondary subs if (mpctx->current_track[0][STREAM_SUB] == track && !mpctx->video_out) @@ -129,6 +125,29 @@ bool update_subtitles(struct MPContext *mpctx, double video_pts) return ok; } +static struct attachment_list *get_all_attachments(struct MPContext *mpctx) +{ + struct attachment_list *list = talloc_zero(NULL, struct attachment_list); + struct demuxer *prev_demuxer = NULL; + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *t = mpctx->tracks[n]; + if (!t->demuxer || prev_demuxer == t->demuxer) + continue; + prev_demuxer = t->demuxer; + for (int i = 0; i < t->demuxer->num_attachments; i++) { + struct demux_attachment *att = &t->demuxer->attachments[i]; + struct demux_attachment copy = { + .name = talloc_strdup(list, att->name), + .type = talloc_strdup(list, att->type), + .data = talloc_memdup(list, att->data, att->data_size), + .data_size = att->data_size, + }; + MP_TARRAY_APPEND(list, list->entries, list->num_entries, copy); + } + } + return list; +} + static bool init_subdec(struct MPContext *mpctx, struct track *track) { assert(!track->d_sub); @@ -136,7 +155,8 @@ static bool init_subdec(struct MPContext *mpctx, struct track *track) if (!track->demuxer || !track->stream) return false; - track->d_sub = sub_create(mpctx->global, track->demuxer, track->stream); + track->d_sub = sub_create(mpctx->global, track->stream, + get_all_attachments(mpctx)); if (!track->d_sub) return false; @@ -161,8 +181,7 @@ void reinit_sub(struct MPContext *mpctx, struct track *track) sub_select(track->d_sub, true); int order = get_order(mpctx, track); - if (order >= 0 && order <= 1) - osd_set_sub(mpctx->osd, OSDTYPE_SUB + order, track->d_sub); + osd_set_sub(mpctx->osd, order, track->d_sub); sub_control(track->d_sub, SD_CTRL_SET_TOP, &(bool){!!order}); } diff --git a/player/video.c b/player/video.c index 24e01d3c06..0af0b9065a 100644 --- a/player/video.c +++ b/player/video.c @@ -136,8 +136,7 @@ static void set_allowed_vo_formats(struct vo_chain *vo_c) vo_query_formats(vo_c->vo, vo_c->vf->allowed_output_formats); } -static int try_filter(struct vo_chain *vo_c, struct mp_image_params params, - char *name, char *label, char **args) +static int try_filter(struct vo_chain *vo_c, char *name, char *label, char **args) { struct vf_instance *vf = vf_append_filter(vo_c->vf, name, args); if (!vf) @@ -145,10 +144,10 @@ static int try_filter(struct vo_chain *vo_c, struct mp_image_params params, vf->label = talloc_strdup(vf, label); - if (vf_reconfig(vo_c->vf, ¶ms) < 0) { + if (vf_reconfig(vo_c->vf, &vo_c->input_format) < 0) { vf_remove_filter(vo_c->vf, vf); // restore - vf_reconfig(vo_c->vf, ¶ms); + vf_reconfig(vo_c->vf, &vo_c->input_format); return -1; } return 0; @@ -166,15 +165,22 @@ static void filter_reconfig(struct vo_chain *vo_c) if (vf_reconfig(vo_c->vf, ¶ms) < 0) return; + char *filters[] = {"autorotate", "autostereo3d", NULL}; + for (int n = 0; filters[n]; n++) { + struct vf_instance *vf = vf_find_by_label(vo_c->vf, filters[n]); + if (vf) { + vf_remove_filter(vo_c->vf, vf); + if (vf_reconfig(vo_c->vf, ¶ms) < 0) + return; + } + } + if (params.rotate && (params.rotate % 90 == 0)) { if (!(vo_c->vo->driver->caps & VO_CAP_ROTATE90)) { // Try to insert a rotation filter. char *args[] = {"angle", "auto", NULL}; - if (try_filter(vo_c, params, "rotate", "autorotate", args) >= 0) { - params.rotate = 0; - } else { + if (try_filter(vo_c, "rotate", "autorotate", args) < 0) MP_ERR(vo_c, "Can't insert rotation filter.\n"); - } } } @@ -184,7 +190,7 @@ static void filter_reconfig(struct vo_chain *vo_c) char *to = (char *)MP_STEREO3D_NAME(params.stereo_out); if (to) { char *args[] = {"in", "auto", "out", to, NULL, NULL}; - if (try_filter(vo_c, params, "stereo3d", "stereo3d", args) < 0) + if (try_filter(vo_c, "stereo3d", "autostereo3d", args) < 0) MP_ERR(vo_c, "Can't insert 3D conversion filter.\n"); } } diff --git a/stream/cache.c b/stream/cache.c index 8c18d18b1a..f8304df8b9 100644 --- a/stream/cache.c +++ b/stream/cache.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -95,6 +96,9 @@ struct priv { bool idle; // cache thread has stopped reading int64_t reads; // number of actual read attempts performed + int64_t speed_start; // start time (us) for calculating download speed + int64_t speed_amount; // bytes read since speed_start + double speed; bool enable_readahead; // actively read beyond read() position int64_t read_filepos; // client read position (mirrors cache->pos) @@ -132,7 +136,7 @@ static void cache_wakeup_and_wait(struct priv *s, double *retry_time) { double start = mp_time_sec(); if (*retry_time >= CACHE_WAIT_TIME) { - MP_WARN(s, "Cache is not responding - slow/stuck network connection?\n"); + MP_VERBOSE(s, "Cache is not responding - slow/stuck network connection?\n"); *retry_time = -1; // do not warn again for this call } @@ -152,6 +156,16 @@ static void cache_drop_contents(struct priv *s) s->start_pts = MP_NOPTS_VALUE; } +static void update_speed(struct priv *s) +{ + int64_t now = mp_time_us(); + s->speed = 0; + if (s->speed_start && s->speed_start < now) + s->speed = s->speed_amount * 1e6 / (now - s->speed_start); + s->speed_amount = 0; + s->speed_start = now; +} + // Copy at most dst_size from the cache at the given absolute file position pos. // Return number of bytes that could actually be read. // Does not advance the file position, or change anything else. @@ -190,6 +204,7 @@ static size_t read_buffer(struct priv *s, unsigned char *dst, static void cache_fill(struct priv *s) { int64_t read = s->read_filepos; + bool read_attempted = false; int len = 0; // drop cache contents only if seeking backward or too much fwd. @@ -211,14 +226,17 @@ static void cache_fill(struct priv *s) goto done; } - if (!s->enable_readahead && s->read_min <= s->max_filepos) { - s->idle = true; - return; - } + if (!s->enable_readahead && s->read_min <= s->max_filepos) + goto done; if (mp_cancel_test(s->cache->cancel)) goto done; + if (!s->speed_start) { + s->speed_start = mp_time_us(); + s->speed_amount = 0; + } + // number of buffer bytes which should be preserved in backwards direction int64_t back = MPCLAMP(read - s->min_filepos, 0, s->back_size); @@ -240,11 +258,8 @@ static void cache_fill(struct priv *s) if (pos >= s->buffer_size) pos -= s->buffer_size; // wrap-around - if (space < FILL_LIMIT) { - s->idle = true; - s->reads++; // don't stuck main thread - return; - } + if (space < FILL_LIMIT) + goto done; // limit to end of buffer (without wrapping) if (pos + space >= s->buffer_size) @@ -274,15 +289,31 @@ static void cache_fill(struct priv *s) s->max_filepos += len; if (pos + len == s->buffer_size) s->offset += s->buffer_size; // wrap... + s->speed_amount += len; -done: - s->eof = len <= 0; - s->idle = s->eof; - s->reads++; - if (s->eof) { + read_attempted = true; + +done: ; + + bool prev_eof = s->eof; + if (read_attempted) + s->eof = len <= 0; + if (!prev_eof && s->eof) { s->eof_pos = stream_tell(s->stream); - MP_TRACE(s, "EOF reached.\n"); + s->speed_start = 0; + MP_VERBOSE(s, "EOF reached.\n"); } + s->idle = s->eof || !read_attempted; + s->reads++; + + if (s->idle) { + update_speed(s); + s->speed_start = 0; + } + + int64_t now = mp_time_us(); + if (s->speed_start && s->speed_start + 1000000 <= now) + update_speed(s); pthread_cond_signal(&s->wakeup); } @@ -335,6 +366,7 @@ static int resize_cache(struct priv *s, int64_t size) s->buffer_size = buffer_size; s->buffer = buffer; s->idle = false; + s->speed_start = 0; s->eof = false; //make sure that we won't wait from cache_fill @@ -371,14 +403,13 @@ static int cache_get_cached_control(stream_t *cache, int cmd, void *arg) { struct priv *s = cache->priv; switch (cmd) { - case STREAM_CTRL_GET_CACHE_SIZE: - *(int64_t *)arg = s->buffer_size - s->back_size; - return STREAM_OK; - case STREAM_CTRL_GET_CACHE_FILL: - *(int64_t *)arg = s->max_filepos - s->read_filepos; - return STREAM_OK; - case STREAM_CTRL_GET_CACHE_IDLE: - *(int *)arg = s->idle; + case STREAM_CTRL_GET_CACHE_INFO: + *(struct stream_cache_info *)arg = (struct stream_cache_info) { + .size = s->buffer_size - s->back_size, + .fill = s->max_filepos - s->read_filepos, + .idle = s->idle, + .speed = llrint(s->speed), + }; return STREAM_OK; case STREAM_CTRL_SET_READAHEAD: s->enable_readahead = *(int *)arg; @@ -409,10 +440,6 @@ static int cache_get_cached_control(stream_t *cache, int cmd, void *arg) } return STREAM_UNSUPPORTED; } - case STREAM_CTRL_RESUME_CACHE: - s->idle = s->eof = false; - pthread_cond_signal(&s->wakeup); - return STREAM_OK; case STREAM_CTRL_AVSEEK: if (!s->has_avseek) return STREAM_UNSUPPORTED; @@ -683,17 +710,15 @@ int stream_cache_init(stream_t *cache, stream_t *stream, for (;;) { if (mp_cancel_test(cache->cancel)) return -1; - int64_t fill; - int idle; - if (stream_control(s->cache, STREAM_CTRL_GET_CACHE_FILL, &fill) < 0) - break; - if (stream_control(s->cache, STREAM_CTRL_GET_CACHE_IDLE, &idle) < 0) + struct stream_cache_info info; + if (stream_control(s->cache, STREAM_CTRL_GET_CACHE_INFO, &info) < 0) break; MP_INFO(s, "\rCache fill: %5.2f%% " - "(%" PRId64 " bytes) ", 100.0 * fill / s->buffer_size, fill); - if (fill >= min) + "(%" PRId64 " bytes) ", 100.0 * info.fill / s->buffer_size, + info.fill); + if (info.fill >= min) break; - if (idle) + if (info.idle) break; // file is smaller than prefill size // Wake up if the cache is done reading some data (or on timeout/abort) pthread_mutex_lock(&s->mutex); diff --git a/stream/dvbin.h b/stream/dvbin.h index 9c2eb35e26..e461364c48 100644 --- a/stream/dvbin.h +++ b/stream/dvbin.h @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include #undef DVB_ATSC diff --git a/stream/stream.h b/stream/stream.h index ab37c3b4bd..21e497e563 100644 --- a/stream/stream.h +++ b/stream/stream.h @@ -67,11 +67,8 @@ enum stream_ctrl { STREAM_CTRL_GET_SIZE = 1, // Cache - STREAM_CTRL_GET_CACHE_SIZE, + STREAM_CTRL_GET_CACHE_INFO, STREAM_CTRL_SET_CACHE_SIZE, - STREAM_CTRL_GET_CACHE_FILL, - STREAM_CTRL_GET_CACHE_IDLE, - STREAM_CTRL_RESUME_CACHE, STREAM_CTRL_SET_READAHEAD, // stream_memory.c @@ -122,6 +119,14 @@ enum stream_ctrl { STREAM_CTRL_SET_CURRENT_TITLE, }; +// for STREAM_CTRL_GET_CACHE_INFO +struct stream_cache_info { + int64_t size; + int64_t fill; + bool idle; + int64_t speed; +}; + struct stream_lang_req { int type; // STREAM_AUDIO, STREAM_SUB int id; diff --git a/sub/ass_mp.c b/sub/ass_mp.c index 8d40b5256a..2f9b39740f 100644 --- a/sub/ass_mp.c +++ b/sub/ass_mp.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -76,6 +77,7 @@ void mp_ass_set_style(ASS_Style *style, double res_y, style->Alignment = 1 + (opts->align_x + 1) + (opts->align_y + 2) % 3 * 4; style->Blur = opts->blur; style->Bold = opts->bold; + style->Italic = opts->italic; } void mp_ass_configure_fonts(ASS_Renderer *priv, struct osd_style_opts *opts, @@ -96,16 +98,15 @@ void mp_ass_configure_fonts(ASS_Renderer *priv, struct osd_style_opts *opts, } void mp_ass_render_frame(ASS_Renderer *renderer, ASS_Track *track, double time, - struct sub_bitmap **parts, struct sub_bitmaps *res) + struct sub_bitmaps *res) { int changed; ASS_Image *imgs = ass_render_frame(renderer, track, time, &changed); if (changed) res->change_id++; + assert(res->format == 0 || res->format == SUBBITMAP_LIBASS); res->format = SUBBITMAP_LIBASS; - res->parts = *parts; - res->num_parts = 0; int num_parts_alloc = MP_TALLOC_AVAIL(res->parts); for (struct ass_image *img = imgs; img; img = img->next) { if (img->w == 0 || img->h == 0) @@ -125,7 +126,6 @@ void mp_ass_render_frame(ASS_Renderer *renderer, ASS_Track *track, double time, p->y = img->dst_y; res->num_parts++; } - *parts = res->parts; } static const int map_ass_level[] = { diff --git a/sub/ass_mp.h b/sub/ass_mp.h index 789a53acb1..b4cae24ddc 100644 --- a/sub/ass_mp.h +++ b/sub/ass_mp.h @@ -55,7 +55,7 @@ ASS_Library *mp_ass_init(struct mpv_global *global, struct mp_log *log); struct sub_bitmap; struct sub_bitmaps; void mp_ass_render_frame(ASS_Renderer *renderer, ASS_Track *track, double time, - struct sub_bitmap **parts, struct sub_bitmaps *res); + struct sub_bitmaps *res); #endif /* HAVE_LIBASS */ #endif /* MPLAYER_ASS_MP_H */ diff --git a/sub/dec_sub.c b/sub/dec_sub.c index 75f5509c62..3b1e957038 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -49,10 +49,11 @@ struct dec_sub { struct mpv_global *global; struct MPOpts *opts; - struct demuxer *demuxer; + struct attachment_list *attachments; struct sh_stream *sh; double last_pkt_pts; + bool preload_attempted; struct mp_codec_params *codec; double start, end; @@ -94,8 +95,9 @@ static struct sd *init_decoder(struct dec_sub *sub) .log = mp_log_new(sd, sub->log, driver->name), .opts = sub->opts, .driver = driver, - .demuxer = sub->demuxer, + .attachments = sub->attachments, .codec = sub->codec, + .preload_ok = true, }; if (sd->driver->init(sd) >= 0) @@ -112,10 +114,12 @@ static struct sd *init_decoder(struct dec_sub *sub) // Thread-safety of the returned object: all functions are thread-safe, // except sub_get_bitmaps() and sub_get_text(). Decoder backends (sd_*) // do not need to acquire locks. -struct dec_sub *sub_create(struct mpv_global *global, struct demuxer *demuxer, - struct sh_stream *sh) +// Ownership of attachments goes to the caller, and is released with +// talloc_free() (even on failure). +struct dec_sub *sub_create(struct mpv_global *global, struct sh_stream *sh, + struct attachment_list *attachments) { - assert(demuxer && sh && sh->type == STREAM_SUB); + assert(sh && sh->type == STREAM_SUB); struct dec_sub *sub = talloc(NULL, struct dec_sub); *sub = (struct dec_sub){ @@ -124,7 +128,7 @@ struct dec_sub *sub_create(struct mpv_global *global, struct demuxer *demuxer, .opts = global->opts, .sh = sh, .codec = sh->codec, - .demuxer = demuxer, + .attachments = talloc_steal(sub, attachments), .last_pkt_pts = MP_NOPTS_VALUE, .last_vo_pts = MP_NOPTS_VALUE, .start = MP_NOPTS_VALUE, @@ -146,6 +150,9 @@ static void update_segment(struct dec_sub *sub) if (sub->new_segment && sub->last_vo_pts != MP_NOPTS_VALUE && sub->last_vo_pts >= sub->new_segment->start) { + MP_VERBOSE(sub, "Switch segment: %f at %f\n", sub->new_segment->start, + sub->last_vo_pts); + sub->codec = sub->new_segment->codec; sub->start = sub->new_segment->start; sub->end = sub->new_segment->end; @@ -165,16 +172,20 @@ static void update_segment(struct dec_sub *sub) } } -// Read all packets from the demuxer and decode/add them. Returns false if -// there are circumstances which makes this not possible. -bool sub_read_all_packets(struct dec_sub *sub) +bool sub_can_preload(struct dec_sub *sub) +{ + bool r; + pthread_mutex_lock(&sub->lock); + r = sub->sd->driver->accept_packets_in_advance && !sub->preload_attempted; + pthread_mutex_unlock(&sub->lock); + return r; +} + +void sub_preload(struct dec_sub *sub) { pthread_mutex_lock(&sub->lock); - if (!sub->sd->driver->accept_packets_in_advance) { - pthread_mutex_unlock(&sub->lock); - return false; - } + sub->preload_attempted = true; for (;;) { struct demux_packet *pkt = demux_read_packet(sub->sh); @@ -185,7 +196,6 @@ bool sub_read_all_packets(struct dec_sub *sub) } pthread_mutex_unlock(&sub->lock); - return true; } // Read packets from the demuxer stream passed to sub_create(). Return true if @@ -203,6 +213,11 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts) if (!read_more) break; + if (sub->new_segment && sub->new_segment->start < video_pts) { + sub->last_vo_pts = video_pts; + update_segment(sub); + } + if (sub->new_segment) break; @@ -228,7 +243,9 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts) break; } - sub->sd->driver->decode(sub->sd, pkt); + if (!(sub->preload_attempted && sub->sd->preload_ok)) + sub->sd->driver->decode(sub->sd, pkt); + talloc_free(pkt); } pthread_mutex_unlock(&sub->lock); @@ -243,8 +260,6 @@ void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts, { struct MPOpts *opts = sub->opts; - *res = (struct sub_bitmaps) {0}; - sub->last_vo_pts = pts; update_segment(sub); diff --git a/sub/dec_sub.h b/sub/dec_sub.h index b3f30520e3..63603e2174 100644 --- a/sub/dec_sub.h +++ b/sub/dec_sub.h @@ -6,7 +6,6 @@ #include "osd.h" -struct demuxer; struct sh_stream; struct mpv_global; struct demux_packet; @@ -22,13 +21,19 @@ enum sd_ctrl { SD_CTRL_SET_VIDEO_DEF_FPS, }; -struct dec_sub *sub_create(struct mpv_global *global, struct demuxer *demuxer, - struct sh_stream *sh); +struct attachment_list { + struct demux_attachment *entries; + int num_entries; +}; + +struct dec_sub *sub_create(struct mpv_global *global, struct sh_stream *sh, + struct attachment_list *attachments); void sub_destroy(struct dec_sub *sub); void sub_lock(struct dec_sub *sub); void sub_unlock(struct dec_sub *sub); -bool sub_read_all_packets(struct dec_sub *sub); +bool sub_can_preload(struct dec_sub *sub); +void sub_preload(struct dec_sub *sub); bool sub_read_packets(struct dec_sub *sub, double video_pts); void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts, struct sub_bitmaps *res); diff --git a/sub/lavc_conv.c b/sub/lavc_conv.c index 3dd6097d0f..3e0165a84c 100644 --- a/sub/lavc_conv.c +++ b/sub/lavc_conv.c @@ -47,6 +47,9 @@ static const char *get_lavc_format(const char *format) // For the hack involving parse_webvtt(). if (format && strcmp(format, "webvtt-webm") == 0) format = "webvtt"; + // Most text subtitles are srt/html style anyway. + if (format && strcmp(format, "text") == 0) + format = "subrip"; return format; } @@ -79,11 +82,8 @@ struct lavc_conv *lavc_conv_create(struct mp_log *log, const char *codec_name, avctx = avcodec_alloc_context3(codec); if (!avctx) goto error; - avctx->extradata_size = extradata_len; - avctx->extradata = av_malloc(extradata_len); - if (!avctx->extradata) + if (mp_lavc_set_extradata(avctx, extradata, extradata_len) < 0) goto error; - memcpy(avctx->extradata, extradata, extradata_len); if (strcmp(codec_name, "eia_608") == 0) av_dict_set(&opts, "real_time", "1", 0); if (avcodec_open2(avctx, codec, &opts) < 0) diff --git a/sub/osd.c b/sub/osd.c index fd821923e5..4e269ce5aa 100644 --- a/sub/osd.c +++ b/sub/osd.c @@ -61,6 +61,7 @@ static const m_option_t style_opts[] = { ({"top", -1}, {"center", 0}, {"bottom", +1})), OPT_FLOATRANGE("blur", blur, 0, 0, 20), OPT_FLAG("bold", bold, 0), + OPT_FLAG("italic", italic, 0), {0} }; @@ -109,6 +110,8 @@ static bool osd_res_equals(struct mp_osd_res a, struct mp_osd_res b) struct osd_state *osd_create(struct mpv_global *global) { + assert(MAX_OSD_PARTS >= OSDTYPE_COUNT); + struct osd_state *osd = talloc_zero(NULL, struct osd_state); *osd = (struct osd_state) { .opts = global->opts, @@ -145,30 +148,31 @@ void osd_free(struct osd_state *osd) talloc_free(osd); } -static void osd_changed_unlocked(struct osd_state *osd, int obj) +void osd_changed_unlocked(struct osd_state *osd, int obj) { osd->objs[obj]->force_redraw = true; osd->want_redraw = true; } -void osd_set_text(struct osd_state *osd, int obj, const char *text) +void osd_set_text(struct osd_state *osd, const char *text) { pthread_mutex_lock(&osd->lock); - struct osd_object *osd_obj = osd->objs[obj]; + struct osd_object *osd_obj = osd->objs[OSDTYPE_OSD]; if (!text) text = ""; if (strcmp(osd_obj->text, text) != 0) { talloc_free(osd_obj->text); osd_obj->text = talloc_strdup(osd_obj, text); - osd_changed_unlocked(osd, obj); + osd_changed_unlocked(osd, osd_obj->type); } pthread_mutex_unlock(&osd->lock); } -void osd_set_sub(struct osd_state *osd, int obj, struct dec_sub *dec_sub) +void osd_set_sub(struct osd_state *osd, int index, struct dec_sub *dec_sub) { pthread_mutex_lock(&osd->lock); - osd->objs[obj]->sub = dec_sub; + if (index >= 0 && index < 2) + osd->objs[OSDTYPE_SUB + index]->sub = dec_sub; pthread_mutex_unlock(&osd->lock); } @@ -201,23 +205,6 @@ void osd_set_progbar(struct osd_state *osd, struct osd_progbar_state *s) pthread_mutex_unlock(&osd->lock); } -void osd_set_external(struct osd_state *osd, int res_x, int res_y, char *text) -{ - pthread_mutex_lock(&osd->lock); - struct osd_object *osd_obj = osd->objs[OSDTYPE_EXTERNAL]; - if (strcmp(osd_obj->text, text) != 0 || - osd_obj->external_res_x != res_x || - osd_obj->external_res_y != res_y) - { - talloc_free(osd_obj->text); - osd_obj->text = talloc_strdup(osd_obj, text); - osd_obj->external_res_x = res_x; - osd_obj->external_res_y = res_y; - osd_changed_unlocked(osd, osd_obj->type); - } - pthread_mutex_unlock(&osd->lock); -} - void osd_set_external2(struct osd_state *osd, struct sub_bitmaps *imgs) { pthread_mutex_lock(&osd->lock); @@ -226,6 +213,33 @@ void osd_set_external2(struct osd_state *osd, struct sub_bitmaps *imgs) pthread_mutex_unlock(&osd->lock); } +static void check_obj_resize(struct osd_state *osd, struct mp_osd_res res, + struct osd_object *obj) +{ + if (!osd_res_equals(res, obj->vo_res)) { + obj->vo_res = res; + obj->force_redraw = true; + mp_client_broadcast_event(mp_client_api_get_core(osd->global->client_api), + MP_EVENT_WIN_RESIZE, NULL); + } +} + +// Optional. Can be called for faster reaction of OSD-generating scripts like +// osc.lua. This can achieve that the resize happens first, so that the OSD is +// generated at the correct resolution the first time the resized frame is +// rendered. Since the OSD doesn't (and can't) wait for the script, this +// increases the time in which the script can react, and also gets rid of the +// unavoidable redraw delay (though it will still be racy). +// Unnecessary for anything else. +void osd_resize(struct osd_state *osd, struct mp_osd_res res) +{ + pthread_mutex_lock(&osd->lock); + int types[] = {OSDTYPE_OSD, OSDTYPE_EXTERNAL, OSDTYPE_EXTERNAL2, -1}; + for (int n = 0; types[n] >= 0; n++) + check_obj_resize(osd, res, osd->objs[types[n]]); + 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], @@ -240,12 +254,7 @@ static void render_object(struct osd_state *osd, struct osd_object *obj, *out_imgs = (struct sub_bitmaps) {0}; - if (!osd_res_equals(res, obj->vo_res)) { - obj->vo_res = res; - obj->force_redraw = true; - mp_client_broadcast_event(mp_client_api_get_core(osd->global->client_api), - MP_EVENT_WIN_RESIZE, NULL); - } + check_obj_resize(osd, res, obj); if (obj->type == OSDTYPE_SUB || obj->type == OSDTYPE_SUB2) { if (obj->sub) { @@ -422,44 +431,11 @@ bool osd_query_and_reset_want_redraw(struct osd_state *osd) return r; } -// Scale factor to translate OSD coordinates to what the obj uses internally. -// osd_coordinates * (sw, sh) = obj_coordinates -void osd_object_get_scale_factor(struct osd_state *osd, int obj, - double *sw, double *sh) -{ - int nw, nh; - osd_object_get_resolution(osd, obj, &nw, &nh); - pthread_mutex_lock(&osd->lock); - int vow = osd->objs[obj]->vo_res.w; - int voh = osd->objs[obj]->vo_res.h; - pthread_mutex_unlock(&osd->lock); - *sw = vow ? nw / (double)vow : 0; - *sh = voh ? nh / (double)voh : 0; -} - -// Turn *x and *y, which are given in OSD coordinates, to video coordinates. -// frame_w and frame_h give the dimensions of the original, unscaled video. -// (This gives correct results only after the OSD has been updated after a -// resize or video reconfig.) -void osd_coords_to_video(struct osd_state *osd, int frame_w, int frame_h, - int *x, int *y) +struct mp_osd_res osd_get_vo_res(struct osd_state *osd) { pthread_mutex_lock(&osd->lock); + // Any OSDTYPE is fine; but it mustn't be a subtitle one (can have lower res.) struct mp_osd_res res = osd->objs[OSDTYPE_OSD]->vo_res; - int vidw = res.w - res.ml - res.mr; - int vidh = res.h - res.mt - res.mb; - double xscale = (double)vidw / frame_w; - double yscale = (double)vidh / frame_h; - // The OSD size + margins make up the scaled rectangle of the video. - *x = (*x - res.ml) / xscale; - *y = (*y - res.mt) / yscale; - pthread_mutex_unlock(&osd->lock); -} - -struct mp_osd_res osd_get_vo_res(struct osd_state *osd, int obj) -{ - pthread_mutex_lock(&osd->lock); - struct mp_osd_res res = osd->objs[obj]->vo_res; pthread_mutex_unlock(&osd->lock); return res; } diff --git a/sub/osd.h b/sub/osd.h index b41a2eec7a..550b31e5a7 100644 --- a/sub/osd.h +++ b/sub/osd.h @@ -78,17 +78,8 @@ struct mp_osd_res { double display_par; }; -enum mp_osdtype { - OSDTYPE_SUB, - OSDTYPE_SUB2, // IDs must be numerically successive - - OSDTYPE_OSD, - - OSDTYPE_EXTERNAL, - OSDTYPE_EXTERNAL2, - - MAX_OSD_PARTS -}; +// 0 <= sub_bitmaps.render_index < MAX_OSD_PARTS +#define MAX_OSD_PARTS 5 // Start of OSD symbols in osd_font.pfb #define OSD_CODEPOINTS 0xE000 @@ -132,6 +123,7 @@ struct osd_style_opts { int align_y; float blur; int bold; + int italic; }; extern const struct m_sub_options osd_style_conf; @@ -149,8 +141,8 @@ void osd_free(struct osd_state *osd); bool osd_query_and_reset_want_redraw(struct osd_state *osd); -void osd_set_text(struct osd_state *osd, int obj, const char *text); -void osd_set_sub(struct osd_state *osd, int obj, struct dec_sub *dec_sub); +void osd_set_text(struct osd_state *osd, const char *text); +void osd_set_sub(struct osd_state *osd, int index, struct dec_sub *dec_sub); bool osd_get_render_subs_in_filter(struct osd_state *osd); void osd_set_render_subs_in_filter(struct osd_state *osd, bool s); @@ -163,8 +155,6 @@ struct osd_progbar_state { }; void osd_set_progbar(struct osd_state *osd, struct osd_progbar_state *s); -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); enum mp_osd_draw_flags { @@ -187,16 +177,12 @@ void osd_draw_on_image_p(struct osd_state *osd, struct mp_osd_res res, double video_pts, int draw_flags, struct mp_image_pool *pool, struct mp_image *dest); +void osd_resize(struct osd_state *osd, struct mp_osd_res res); + struct mp_image_params; struct mp_osd_res osd_res_from_image_params(const struct mp_image_params *p); -void osd_object_get_scale_factor(struct osd_state *osd, int obj, - double *sw, double *sh); - -void osd_coords_to_video(struct osd_state *osd, int frame_w, int frame_h, - int *x, int *y); - -struct mp_osd_res osd_get_vo_res(struct osd_state *osd, int obj); +struct mp_osd_res osd_get_vo_res(struct osd_state *osd); void osd_rescale_bitmaps(struct sub_bitmaps *imgs, int frame_w, int frame_h, struct mp_osd_res res, double compensate_par); @@ -209,13 +195,12 @@ void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj, void osd_init_backend(struct osd_state *osd); void osd_destroy_backend(struct osd_state *osd); +void osd_set_external(struct osd_state *osd, void *id, int res_x, int res_y, + char *text); + // doesn't need locking void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function); extern const char *const osd_ass_0; extern const char *const osd_ass_1; -// defined in backend, but locks if required -void osd_object_get_resolution(struct osd_state *osd, int obj, - int *out_w, int *out_h); - #endif /* MPLAYER_SUB_H */ diff --git a/sub/osd_dummy.c b/sub/osd_dummy.c index 5c50569dd6..5c3cc76200 100644 --- a/sub/osd_dummy.c +++ b/sub/osd_dummy.c @@ -27,9 +27,7 @@ void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj, *out_imgs = (struct sub_bitmaps) {0}; } -void osd_object_get_resolution(struct osd_state *osd, int obj, - int *out_w, int *out_h) +void osd_set_external(struct osd_state *osd, void *id, int res_x, int res_y, + char *text) { - *out_w = 0; - *out_h = 0; } diff --git a/sub/osd_libass.c b/sub/osd_libass.c index eed2822380..30d404d8c6 100644 --- a/sub/osd_libass.c +++ b/sub/osd_libass.c @@ -45,49 +45,66 @@ void osd_init_backend(struct osd_state *osd) { } -static void create_ass_renderer(struct osd_state *osd, struct osd_object *obj) +static void create_ass_renderer(struct osd_state *osd, struct ass_state *ass) { - if (obj->osd_render) + if (ass->render) return; - struct mp_log *ass_log = mp_log_new(obj, osd->log, "libass"); - obj->osd_ass_library = mp_ass_init(osd->global, ass_log); - ass_add_font(obj->osd_ass_library, "mpv-osd-symbols", (void *)osd_font_pfb, + ass->log = mp_log_new(NULL, osd->log, "libass"); + ass->library = mp_ass_init(osd->global, ass->log); + ass_add_font(ass->library, "mpv-osd-symbols", (void *)osd_font_pfb, sizeof(osd_font_pfb) - 1); - obj->osd_render = ass_renderer_init(obj->osd_ass_library); - if (!obj->osd_render) + ass->render = ass_renderer_init(ass->library); + if (!ass->render) abort(); - mp_ass_configure_fonts(obj->osd_render, osd->opts->osd_style, - osd->global, ass_log); - ass_set_aspect_ratio(obj->osd_render, 1.0, 1.0); + mp_ass_configure_fonts(ass->render, osd->opts->osd_style, + osd->global, ass->log); + ass_set_aspect_ratio(ass->render, 1.0, 1.0); +} + +static void destroy_ass_renderer(struct ass_state *ass) +{ + if (ass->track) + ass_free_track(ass->track); + ass->track = NULL; + if (ass->render) + ass_renderer_done(ass->render); + ass->render = NULL; + if (ass->library) + ass_library_done(ass->library); + ass->library = NULL; + talloc_free(ass->log); + ass->log = NULL; +} + +static void destroy_external(struct osd_external *ext) +{ + talloc_free(ext->text); + destroy_ass_renderer(&ext->ass); } void osd_destroy_backend(struct osd_state *osd) { for (int n = 0; n < MAX_OSD_PARTS; n++) { struct osd_object *obj = osd->objs[n]; - if (obj->osd_track) - ass_free_track(obj->osd_track); - obj->osd_track = NULL; - if (obj->osd_render) - ass_renderer_done(obj->osd_render); - obj->osd_render = NULL; - if (obj->osd_ass_library) - ass_library_done(obj->osd_ass_library); - obj->osd_ass_library = NULL; + destroy_ass_renderer(&obj->ass); + talloc_free(obj->parts_cache.parts); + for (int i = 0; i < obj->num_externals; i++) + destroy_external(&obj->externals[i]); + obj->num_externals = 0; } } static void create_ass_track(struct osd_state *osd, struct osd_object *obj, - int res_x, int res_y) + struct ass_state *ass, int res_x, int res_y) { - create_ass_renderer(osd, obj); + create_ass_renderer(osd, ass); - ASS_Track *track = obj->osd_track; + ASS_Track *track = ass->track; if (!track) - track = ass_new_track(obj->osd_ass_library); + track = ass_new_track(ass->library); int old_res_x = track->PlayResX; int old_res_y = track->PlayResY; @@ -105,9 +122,9 @@ static void create_ass_track(struct osd_state *osd, struct osd_object *obj, // Force libass to clear its internal cache - it doesn't check for // PlayRes changes itself. if (old_res_x != track->PlayResX || old_res_y != track->PlayResY) - ass_set_frame_size(obj->osd_render, 1, 1); + ass_set_frame_size(ass->render, 1, 1); - obj->osd_track = track; + ass->track = track; } static int find_style(ASS_Track *track, const char *name, int def) @@ -120,10 +137,9 @@ static int find_style(ASS_Track *track, const char *name, int def) } // Find a given style, or add it if it's missing. -static ASS_Style *get_style(struct osd_state *osd, struct osd_object *obj, - char *name) +static ASS_Style *get_style(struct ass_state *ass, char *name) { - ASS_Track *track = obj->osd_track; + ASS_Track *track = ass->track; if (!track) return NULL; @@ -154,10 +170,10 @@ static ASS_Event *add_osd_ass_event(ASS_Track *track, const char *style, return event; } -static void clear_obj(struct osd_object *obj) +static void clear_ass(struct ass_state *ass) { - if (obj->osd_track) - ass_flush_events(obj->osd_track); + if (ass->track) + ass_flush_events(ass->track); } void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function) @@ -214,18 +230,18 @@ static void update_osd_text(struct osd_state *osd, struct osd_object *obj) if (!obj->text[0]) return; - create_ass_track(osd, obj, 0, 0); + create_ass_track(osd, obj, &obj->ass, 0, 0); struct osd_style_opts font = *opts->osd_style; font.font_size *= opts->osd_scale; - double playresy = obj->osd_track->PlayResY; + double playresy = obj->ass.track->PlayResY; // Compensate for libass and mp_ass_set_style scaling the font etc. if (!opts->osd_scale_by_window) playresy *= 720.0 / obj->vo_res.h; - mp_ass_set_style(get_style(osd, obj, "OSD"), playresy, &font); - add_osd_ass_event_escaped(obj->osd_track, "OSD", obj->text); + mp_ass_set_style(get_style(&obj->ass, "OSD"), playresy, &font); + add_osd_ass_event_escaped(obj->ass.track, "OSD", obj->text); } // align: -1 .. +1 @@ -308,10 +324,10 @@ static void get_osd_bar_box(struct osd_state *osd, struct osd_object *obj, { struct MPOpts *opts = osd->opts; - create_ass_track(osd, obj, 0, 0); - ASS_Track *track = obj->osd_track; + create_ass_track(osd, obj, &obj->ass, 0, 0); + ASS_Track *track = obj->ass.track; - ASS_Style *style = get_style(osd, obj, "progbar"); + ASS_Style *style = get_style(&obj->ass, "progbar"); if (!style) { *o_x = *o_y = *o_w = *o_h = *o_border = 0; return; @@ -347,6 +363,8 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj) float px, py, width, height, border; get_osd_bar_box(osd, obj, &px, &py, &width, &height, &border); + ASS_Track *track = obj->ass.track; + float sx = px - border * 2 - height / 4; // includes additional spacing float sy = py + height / 2; @@ -362,7 +380,7 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj) bstr_xappend(NULL, &buf, bstr0("{\\r}")); } - add_osd_ass_event(obj->osd_track, "progbar", buf.start); + add_osd_ass_event(track, "progbar", buf.start); talloc_free(buf.start); struct ass_draw *d = &(struct ass_draw) { .scale = 4 }; @@ -372,7 +390,7 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj) float pos = obj->progbar_state.value * width - border / 2; ass_draw_rect_cw(d, 0, 0, pos, height); ass_draw_stop(d); - add_osd_ass_event(obj->osd_track, "progbar", d->text); + add_osd_ass_event(track, "progbar", d->text); ass_draw_reset(d); // position marker @@ -382,7 +400,7 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj) ass_draw_move_to(d, pos + border / 2, 0); ass_draw_line_to(d, pos + border / 2, height); ass_draw_stop(d); - add_osd_ass_event(obj->osd_track, "progbar", d->text); + add_osd_ass_event(track, "progbar", d->text); ass_draw_reset(d); d->text = talloc_asprintf_append(d->text, "{\\pos(%f,%f)}", px, py); @@ -411,79 +429,112 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj) } ass_draw_stop(d); - add_osd_ass_event(obj->osd_track, "progbar", d->text); + add_osd_ass_event(track, "progbar", d->text); ass_draw_reset(d); } static void update_osd(struct osd_state *osd, struct osd_object *obj) { - clear_obj(obj); + clear_ass(&obj->ass); update_osd_text(osd, obj); update_progbar(osd, obj); } -static void update_external(struct osd_state *osd, struct osd_object *obj) +static void update_external(struct osd_state *osd, struct osd_object *obj, + struct osd_external *ext) { - clear_obj(obj); - - bstr t = bstr0(obj->text); + bstr t = bstr0(ext->text); if (!t.len) return; - create_ass_track(osd, obj, obj->external_res_x, obj->external_res_y); + create_ass_track(osd, obj, &ext->ass, ext->res_x, ext->res_y); - int resy = obj->osd_track->PlayResY; - mp_ass_set_style(get_style(osd, obj, "OSD"), resy, osd->opts->osd_style); + clear_ass(&ext->ass); + + int resy = ext->ass.track->PlayResY; + mp_ass_set_style(get_style(&ext->ass, "OSD"), resy, osd->opts->osd_style); // Some scripts will reference this style name with \r tags. const struct osd_style_opts *def = osd_style_conf.defaults; - mp_ass_set_style(get_style(osd, obj, "Default"), resy, def); + mp_ass_set_style(get_style(&ext->ass, "Default"), resy, def); while (t.len) { bstr line; bstr_split_tok(t, "\n", &line, &t); if (line.len) { char *tmp = bstrdup0(NULL, line); - add_osd_ass_event(obj->osd_track, "OSD", tmp); + add_osd_ass_event(ext->ass.track, "OSD", tmp); talloc_free(tmp); } } } -static void update_object(struct osd_state *osd, struct osd_object *obj) +void osd_set_external(struct osd_state *osd, void *id, int res_x, int res_y, + char *text) { - switch (obj->type) { - case OSDTYPE_OSD: - update_osd(osd, obj); - break; - case OSDTYPE_EXTERNAL: - update_external(osd, obj); - break; + pthread_mutex_lock(&osd->lock); + struct osd_object *obj = osd->objs[OSDTYPE_EXTERNAL]; + struct osd_external *entry = 0; + for (int n = 0; n < obj->num_externals; n++) { + if (obj->externals[n].id == id) { + entry = &obj->externals[n]; + break; + } } + if (!entry && !text) + goto done; + + if (!entry) { + struct osd_external new = { .id = id }; + MP_TARRAY_APPEND(obj, obj->externals, obj->num_externals, new); + entry = &obj->externals[obj->num_externals - 1]; + } + + if (!text) { + int index = entry - &obj->externals[0]; + destroy_external(entry); + MP_TARRAY_REMOVE_AT(obj->externals, obj->num_externals, index); + goto done; + } + + if (!entry->text || strcmp(entry->text, text) != 0 || + entry->res_x != res_x || entry->res_y != res_y) + { + talloc_free(entry->text); + entry->text = talloc_strdup(NULL, text); + entry->res_x = res_x; + entry->res_y = res_y; + update_external(osd, obj, entry); + obj->parts_cache.change_id = 1; + osd_changed_unlocked(osd, obj->type); + } + +done: + pthread_mutex_unlock(&osd->lock); +} + +static void append_ass(struct ass_state *ass, struct mp_osd_res *res, + struct sub_bitmaps *imgs) +{ + if (!ass->render || !ass->track) + return; + + ass_set_frame_size(ass->render, res->w, res->h); + ass_set_aspect_ratio(ass->render, res->display_par, 1.0); + mp_ass_render_frame(ass->render, ass->track, 0, imgs); } void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj, struct sub_bitmaps *out_imgs) { - if (obj->force_redraw) - update_object(osd, obj); + if (obj->force_redraw && obj->type == OSDTYPE_OSD) + update_osd(osd, obj); - *out_imgs = (struct sub_bitmaps) {0}; - if (!obj->osd_track) - return; + append_ass(&obj->ass, &obj->vo_res, &obj->parts_cache); + for (int n = 0; n < obj->num_externals; n++) + append_ass(&obj->externals[n].ass, &obj->vo_res, &obj->parts_cache); - ass_set_frame_size(obj->osd_render, obj->vo_res.w, obj->vo_res.h); - ass_set_aspect_ratio(obj->osd_render, obj->vo_res.display_par, 1.0); - mp_ass_render_frame(obj->osd_render, obj->osd_track, 0, - &obj->parts_cache, out_imgs); - talloc_steal(obj, obj->parts_cache); -} + *out_imgs = obj->parts_cache; -void osd_object_get_resolution(struct osd_state *osd, int obj, - int *out_w, int *out_h) -{ - pthread_mutex_lock(&osd->lock); - struct osd_object *osd_obj = osd->objs[obj]; - *out_w = osd_obj->osd_track ? osd_obj->osd_track->PlayResX : 0; - *out_h = osd_obj->osd_track ? osd_obj->osd_track->PlayResY : 0; - pthread_mutex_unlock(&osd->lock); + obj->parts_cache.change_id = 0; + obj->parts_cache.num_parts = 0; } diff --git a/sub/osd_state.h b/sub/osd_state.h index 30f04d8449..0fff668d2d 100644 --- a/sub/osd_state.h +++ b/sub/osd_state.h @@ -7,13 +7,32 @@ #define OSD_CONV_CACHE_MAX 4 +enum mp_osdtype { + OSDTYPE_SUB, + OSDTYPE_SUB2, // IDs must be numerically successive + + OSDTYPE_OSD, + + OSDTYPE_EXTERNAL, + OSDTYPE_EXTERNAL2, + + OSDTYPE_COUNT +}; + +struct ass_state { + struct mp_log *log; + struct ass_track *track; + struct ass_renderer *render; + struct ass_library *library; +}; + struct osd_object { int type; // OSDTYPE_* bool is_sub; bool force_redraw; - // OSDTYPE_SUB/OSDTYPE_SUB2/OSDTYPE_OSD/OSDTYPE_EXTERNAL + // OSDTYPE_OSD char *text; // OSDTYPE_OSD @@ -23,7 +42,8 @@ struct osd_object { struct dec_sub *sub; // OSDTYPE_EXTERNAL - int external_res_x, external_res_y; + struct osd_external *externals; + int num_externals; // OSDTYPE_EXTERNAL2 struct sub_bitmaps *external2; @@ -37,10 +57,15 @@ struct osd_object { struct mp_osd_res vo_res; // Internally used by osd_libass.c - struct sub_bitmap *parts_cache; - struct ass_track *osd_track; - struct ass_renderer *osd_render; - struct ass_library *osd_ass_library; + struct sub_bitmaps parts_cache; + struct ass_state ass; +}; + +struct osd_external { + void *id; + char *text; + int res_x, res_y; + struct ass_state ass; }; struct osd_state { @@ -59,4 +84,6 @@ struct osd_state { struct mp_draw_sub_cache *draw_cache; }; +void osd_changed_unlocked(struct osd_state *osd, int obj); + #endif diff --git a/sub/sd.h b/sub/sd.h index b142654ed1..fe64163b1b 100644 --- a/sub/sd.h +++ b/sub/sd.h @@ -17,8 +17,12 @@ struct sd { const struct sd_functions *driver; void *priv; - struct demuxer *demuxer; + struct attachment_list *attachments; struct mp_codec_params *codec; + + // Set to false as soon as the decoder discards old subtitle events. + // (only needed if sd_functions.accept_packets_in_advance == false) + bool preload_ok; }; struct sd_functions { diff --git a/sub/sd_ass.c b/sub/sd_ass.c index 5c56d3e0df..788b78f3a4 100644 --- a/sub/sd_ass.c +++ b/sub/sd_ass.c @@ -44,7 +44,7 @@ struct sd_ass_priv { bool is_converted; struct lavc_conv *converter; bool on_top; - struct sub_bitmap *parts; + struct sub_bitmaps part_cache; char last_text[500]; struct mp_image_params video_params; struct mp_image_params last_params; @@ -117,10 +117,10 @@ static void add_subtitle_fonts(struct sd *sd) { struct sd_ass_priv *ctx = sd->priv; struct MPOpts *opts = sd->opts; - if (!opts->ass_enabled || !sd->demuxer) + if (!opts->ass_enabled || !sd->attachments) return; - for (int i = 0; i < sd->demuxer->num_attachments; i++) { - struct demux_attachment *f = &sd->demuxer->attachments[i]; + for (int i = 0; i < sd->attachments->num_entries; i++) { + struct demux_attachment *f = &sd->attachments->entries[i]; if (opts->use_embedded_fonts && attachment_is_font(sd->log, f)) ass_add_font(ctx->ass_library, f->name, f->data, f->data_size); } @@ -238,6 +238,8 @@ static bool check_packet_seen(struct sd *sd, int64_t pos) return false; } +#define UNKNOWN_DURATION (INT_MAX / 1000) + static void decode(struct sd *sd, struct demux_packet *packet) { struct sd_ass_priv *ctx = sd->priv; @@ -246,13 +248,22 @@ static void decode(struct sd *sd, struct demux_packet *packet) if (!sd->opts->sub_clear_on_seek && packet->pos >= 0 && check_packet_seen(sd, packet->pos)) return; + if (packet->duration < 0) { + if (!ctx->duration_unknown) { + MP_WARN(sd, "Subtitle with unknown duration.\n"); + ctx->duration_unknown = true; + } + packet->duration = UNKNOWN_DURATION; + } char **r = lavc_conv_decode(ctx->converter, packet); for (int n = 0; r && r[n]; n++) ass_process_data(track, r[n], strlen(r[n])); if (ctx->duration_unknown) { for (int n = 0; n < track->n_events - 1; n++) { - track->events[n].Duration = track->events[n + 1].Start - - track->events[n].Start; + if (track->events[n].Duration == UNKNOWN_DURATION * 1000) { + track->events[n].Duration = track->events[n + 1].Start - + track->events[n].Start; + } } } } else { @@ -440,14 +451,22 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, double pts, long long ts = find_timestamp(sd, pts); if (ctx->duration_unknown && pts != MP_NOPTS_VALUE) { mp_ass_flush_old_events(track, ts); + ctx->num_seen_packets = 0; + sd->preload_ok = false; } + if (no_ass) fill_plaintext(sd, pts); - mp_ass_render_frame(renderer, track, ts, &ctx->parts, res); - talloc_steal(ctx, ctx->parts); + + ctx->part_cache.change_id = 0; + ctx->part_cache.num_parts = 0; + mp_ass_render_frame(renderer, track, ts, &ctx->part_cache); + talloc_steal(ctx, ctx->part_cache.parts); if (!converted) - mangle_colors(sd, res); + mangle_colors(sd, &ctx->part_cache); + + *res = ctx->part_cache; } struct buf { @@ -597,9 +616,10 @@ static void fill_plaintext(struct sd *sd, double pts) static void reset(struct sd *sd) { struct sd_ass_priv *ctx = sd->priv; - if (sd->opts->sub_clear_on_seek) { + if (sd->opts->sub_clear_on_seek || ctx->duration_unknown) { ass_flush_events(ctx->ass_track); ctx->num_seen_packets = 0; + sd->preload_ok = false; } if (ctx->converter) lavc_conv_reset(ctx->converter); diff --git a/sub/sd_lavc.c b/sub/sd_lavc.c index 4fbabaf864..56377b213e 100644 --- a/sub/sd_lavc.c +++ b/sub/sd_lavc.c @@ -54,6 +54,7 @@ struct seekpoint { struct sd_lavc_priv { AVCodecContext *avctx; + AVRational pkt_timebase; struct sub subs[MAX_QUEUE]; // most recent event first struct sub_bitmap *outbitmaps; int64_t displayed_id; @@ -117,6 +118,22 @@ static int init(struct sd *sd) if (!ctx) goto error; mp_lavc_set_extradata(ctx, sd->codec->extradata, sd->codec->extradata_size); + if (cid == AV_CODEC_ID_HDMV_PGS_SUBTITLE) { + // We don't always want to set this, because the ridiculously shitty + // libavcodec API will mess with certain fields (end_display_time) + // when setting it. On the other hand, PGS in particular needs PTS + // mangling. While the PGS decoder doesn't modify the timestamps (just + // reorder it), the ridiculously shitty libavcodec wants a timebase + // anyway and for no good reason. It always sets end_display_time to + // UINT32_MAX (which is a broken and undocumented way to say "unknown"), + // which coincidentally won't be overridden by the ridiculously shitty + // pkt_timebase code. also, Libav doesn't have the pkt_timebase field, + // because Libav tends to avoid _adding_ ridiculously shitty APIs. +#if LIBAVCODEC_VERSION_MICRO >= 100 + priv->pkt_timebase = (AVRational){1, AV_TIME_BASE}; + ctx->pkt_timebase = priv->pkt_timebase; +#endif + } if (avcodec_open2(ctx, sub_codec, NULL) < 0) goto error; priv->avctx = ctx; @@ -166,7 +183,9 @@ static void decode(struct sd *sd, struct demux_packet *packet) AVSubtitle sub; AVPacket pkt; - // libavformat sets duration==0, even if the duration is unknown. + // libavformat sets duration==0, even if the duration is unknown. Some files + // also have actually subtitle packets with duration explicitly set to 0 + // (yes, at least some of such mkv files were muxed by libavformat). // Assume there are no bitmap subs that actually use duration==0 for // hidden subtitle events. if (duration == 0) @@ -175,14 +194,15 @@ static void decode(struct sd *sd, struct demux_packet *packet) if (pts == MP_NOPTS_VALUE) MP_WARN(sd, "Subtitle with unknown start time.\n"); - av_init_packet(&pkt); - pkt.data = packet->buffer; - pkt.size = packet->len; + mp_set_av_packet(&pkt, packet, &priv->pkt_timebase); int got_sub; int res = avcodec_decode_subtitle2(ctx, &sub, &got_sub, &pkt); if (res < 0 || !got_sub) return; + if (sub.pts != AV_NOPTS_VALUE) + pts = sub.pts / (double)AV_TIME_BASE; + if (pts != MP_NOPTS_VALUE) { if (sub.end_display_time > sub.start_display_time && sub.end_display_time != UINT32_MAX) @@ -284,7 +304,7 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res d, double pts, priv->current_pts = pts; struct sub *current = NULL; - for (int n = MAX_QUEUE - 1; n >= 0; n--) { + for (int n = 0; n < MAX_QUEUE; n++) { struct sub *sub = &priv->subs[n]; if (!sub->valid) continue; diff --git a/ta/ta_talloc.h b/ta/ta_talloc.h index 2331b4e08e..7194b86601 100644 --- a/ta/ta_talloc.h +++ b/ta/ta_talloc.h @@ -73,6 +73,8 @@ char *ta_talloc_asprintf_append_buffer(char *s, const char *fmt, ...) TA_PRF(2, // mpv specific stuff - should be made part of proper TA API +#define TA_FREEP(pctx) do {if (pctx) {talloc_free(*pctx); (*pctx) = NULL;}} while(0) + #define TA_EXPAND_ARGS(...) __VA_ARGS__ #define MP_TALLOC_AVAIL(p) (talloc_get_size(p) / sizeof((p)[0])) diff --git a/video/csputils.c b/video/csputils.c index e446d8e724..69d3b80944 100644 --- a/video/csputils.c +++ b/video/csputils.c @@ -64,6 +64,7 @@ const struct m_opt_choice_alternatives mp_csp_prim_names[] = { {"adobe", MP_CSP_PRIM_ADOBE}, {"prophoto", MP_CSP_PRIM_PRO_PHOTO}, {"cie1931", MP_CSP_PRIM_CIE_1931}, + {"dci-p3", MP_CSP_PRIM_DCI_P3}, {0} }; @@ -403,6 +404,14 @@ struct mp_csp_primaries mp_get_csp_primaries(enum mp_csp_prim spc) .blue = {0.1666, 0.0089}, .white = e }; + // From SMPTE RP 431-2 + case MP_CSP_PRIM_DCI_P3: + return (struct mp_csp_primaries) { + .red = {0.680, 0.320}, + .green = {0.265, 0.690}, + .blue = {0.150, 0.060}, + .white = d65 + }; default: return (struct mp_csp_primaries) {{0}}; } diff --git a/video/csputils.h b/video/csputils.h index d5990d242f..1d8d3b1d14 100644 --- a/video/csputils.h +++ b/video/csputils.h @@ -63,6 +63,7 @@ enum mp_csp_prim { MP_CSP_PRIM_ADOBE, MP_CSP_PRIM_PRO_PHOTO, MP_CSP_PRIM_CIE_1931, + MP_CSP_PRIM_DCI_P3, MP_CSP_PRIM_COUNT }; diff --git a/video/d3d11va.c b/video/d3d11va.c new file mode 100644 index 0000000000..a9be571e9c --- /dev/null +++ b/video/d3d11va.c @@ -0,0 +1,86 @@ +/* + * 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 . + */ + +#include "mp_image.h" +#include "d3d11va.h" + +struct d3d11va_surface { + HMODULE d3d11_dll; + ID3D11Texture2D *texture; + ID3D11VideoDecoderOutputView *surface; +}; + +ID3D11VideoDecoderOutputView *d3d11_surface_in_mp_image(struct mp_image *mpi) +{ + return mpi && mpi->imgfmt == IMGFMT_D3D11VA ? + (ID3D11VideoDecoderOutputView *)mpi->planes[3] : NULL; +} + +ID3D11Texture2D *d3d11_texture_in_mp_image(struct mp_image *mpi) +{ + if (!mpi || mpi->imgfmt != IMGFMT_D3D11VA) + return NULL; + struct d3d11va_surface *surface = (void *)mpi->planes[0]; + return surface->texture; +} + +static void d3d11va_release_img(void *arg) +{ + struct d3d11va_surface *surface = arg; + if (surface->surface) + ID3D11VideoDecoderOutputView_Release(surface->surface); + + if (surface->texture) + ID3D11Texture2D_Release(surface->texture); + + if (surface->d3d11_dll) + FreeLibrary(surface->d3d11_dll); + + talloc_free(surface); +} + +struct mp_image *d3d11va_new_ref(ID3D11VideoDecoderOutputView *view, + int w, int h) +{ + if (!view) + return NULL; + struct d3d11va_surface *surface = talloc_zero(NULL, struct d3d11va_surface); + + surface->d3d11_dll = LoadLibrary(L"d3d11.dll"); + if (!surface->d3d11_dll) + goto fail; + + surface->surface = view; + ID3D11VideoDecoderOutputView_AddRef(surface->surface); + ID3D11VideoDecoderOutputView_GetResource( + surface->surface, (ID3D11Resource **)&surface->texture); + + struct mp_image *mpi = mp_image_new_custom_ref( + &(struct mp_image){0}, surface, d3d11va_release_img); + if (!mpi) + abort(); + + mp_image_setfmt(mpi, IMGFMT_D3D11VA); + mp_image_set_size(mpi, w, h); + mpi->planes[0] = (void *)surface; + mpi->planes[3] = (void *)surface->surface; + + return mpi; +fail: + d3d11va_release_img(surface); + return NULL; +} diff --git a/video/d3d11va.h b/video/d3d11va.h new file mode 100644 index 0000000000..db2f295241 --- /dev/null +++ b/video/d3d11va.h @@ -0,0 +1,30 @@ +/* + * 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 . + */ + +#ifndef MPV_D3D11_H +#define MPV_D3D11_H + +#include + +struct mp_image; + +ID3D11VideoDecoderOutputView *d3d11_surface_in_mp_image(struct mp_image *mpi); +ID3D11Texture2D *d3d11_texture_in_mp_image(struct mp_image *mpi); +struct mp_image *d3d11va_new_ref(ID3D11VideoDecoderOutputView *view, + int w, int h); + +#endif diff --git a/video/decode/d3d.c b/video/decode/d3d.c new file mode 100644 index 0000000000..35d1af9243 --- /dev/null +++ b/video/decode/d3d.c @@ -0,0 +1,241 @@ +/* + * 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 . + */ + +#include + +#include "lavc.h" +#include "common/common.h" +#include "common/av_common.h" +#include "video/fmt-conversion.h" +#include "video/mp_image.h" +#include "osdep/windows_utils.h" + +#include "d3d.h" + +// define all the GUIDs used directly here, to avoid problems with inconsistent +// dxva2api.h versions in mingw-w64 and different MSVC version +#include +DEFINE_GUID(DXVA2_ModeMPEG2_VLD, 0xee27417f, 0x5e28, 0x4e65, 0xbe, 0xea, 0x1d, 0x26, 0xb5, 0x08, 0xad, 0xc9); +DEFINE_GUID(DXVA2_ModeMPEG2and1_VLD, 0x86695f12, 0x340e, 0x4f04, 0x9f, 0xd3, 0x92, 0x53, 0xdd, 0x32, 0x74, 0x60); + +DEFINE_GUID(DXVA2_ModeH264_E, 0x1b81be68, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5); +DEFINE_GUID(DXVA2_ModeH264_F, 0x1b81be69, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5); +DEFINE_GUID(DXVA_ModeH264_VLD_WithFMOASO_NoFGT, 0xd5f04ff9, 0x3418, 0x45d8, 0x95, 0x61, 0x32, 0xa7, 0x6a, 0xae, 0x2d, 0xdd); +DEFINE_GUID(DXVA_Intel_H264_NoFGT_ClearVideo, 0x604F8E68, 0x4951, 0x4c54, 0x88, 0xFE, 0xAB, 0xD2, 0x5C, 0x15, 0xB3, 0xD6); +DEFINE_GUID(DXVA_ModeH264_VLD_NoFGT_Flash, 0x4245F676, 0x2BBC, 0x4166, 0xa0, 0xBB, 0x54, 0xE7, 0xB8, 0x49, 0xC3, 0x80); + +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); // August 2010 update + +DEFINE_GUID(DXVA2_ModeHEVC_VLD_Main, 0x5b11d51b, 0x2f4c, 0x4452, 0xbc, 0xc3, 0x09, 0xf2, 0xa1, 0x16, 0x0c, 0xc0); +DEFINE_GUID(DXVA2_ModeHEVC_VLD_Main10, 0x107af0e0, 0xef1a, 0x4d19, 0xab, 0xa8, 0x67, 0xa1, 0x63, 0x07, 0x3d, 0x13); + +DEFINE_GUID(DXVA2_ModeVP9_VLD_Profile0, 0x463707f8, 0xa1d0, 0x4585, 0x87, 0x6d, 0x83, 0xaa, 0x6d, 0x60, 0xb8, 0x9e); + +DEFINE_GUID(DXVA2_NoEncrypt, 0x1b81beD0, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5); + +static const int PROF_MPEG2_SIMPLE[] = {FF_PROFILE_MPEG2_SIMPLE, 0}; +static const int PROF_MPEG2_MAIN[] = {FF_PROFILE_MPEG2_SIMPLE, + FF_PROFILE_MPEG2_MAIN, 0}; +static const int PROF_H264_HIGH[] = {FF_PROFILE_H264_CONSTRAINED_BASELINE, + FF_PROFILE_H264_MAIN, + FF_PROFILE_H264_HIGH, 0}; +static const int PROF_HEVC_MAIN[] = {FF_PROFILE_HEVC_MAIN, 0}; +static const int PROF_HEVC_MAIN10[] = {FF_PROFILE_HEVC_MAIN, + FF_PROFILE_HEVC_MAIN_10, 0}; + +struct d3dva_mode { + const GUID *guid; + const char *name; + enum AVCodecID codec; + const int *profiles; // NULL or ends with 0 +}; + +#define MODE2(id) &MP_CONCAT(DXVA2_Mode, id), # id +#define MODE(id) &MP_CONCAT(DXVA_, id), # id +// Prefered modes must come first +static const struct d3dva_mode d3dva_modes[] = { + // MPEG-1/2 + {MODE2(MPEG2_VLD), AV_CODEC_ID_MPEG2VIDEO, PROF_MPEG2_SIMPLE}, + {MODE2(MPEG2and1_VLD), AV_CODEC_ID_MPEG2VIDEO, PROF_MPEG2_MAIN}, + {MODE2(MPEG2and1_VLD), AV_CODEC_ID_MPEG1VIDEO}, + + // H.264 + {MODE2(H264_F), AV_CODEC_ID_H264, PROF_H264_HIGH}, + {MODE (Intel_H264_NoFGT_ClearVideo), AV_CODEC_ID_H264, PROF_H264_HIGH}, + {MODE2(H264_E), AV_CODEC_ID_H264, PROF_H264_HIGH}, + {MODE (ModeH264_VLD_WithFMOASO_NoFGT), AV_CODEC_ID_H264, PROF_H264_HIGH}, + {MODE (ModeH264_VLD_NoFGT_Flash), AV_CODEC_ID_H264, PROF_H264_HIGH}, + + // VC-1 / WMV3 + {MODE2(VC1_D), AV_CODEC_ID_VC1}, + {MODE2(VC1_D), AV_CODEC_ID_WMV3}, + {MODE2(VC1_D2010), AV_CODEC_ID_VC1}, + {MODE2(VC1_D2010), AV_CODEC_ID_WMV3}, + + // HEVC + {MODE2(HEVC_VLD_Main), AV_CODEC_ID_HEVC, PROF_HEVC_MAIN}, + {MODE2(HEVC_VLD_Main10), AV_CODEC_ID_HEVC, PROF_HEVC_MAIN10}, + + // VP9 + {MODE2(VP9_VLD_Profile0), AV_CODEC_ID_VP9}, +}; +#undef MODE +#undef MODE2 + +int d3d_probe_codec(const char *codec) +{ + enum AVCodecID codecid = mp_codec_to_av_codec_id(codec); + for (int i = 0; i < MP_ARRAY_SIZE(d3dva_modes); i++) { + const struct d3dva_mode *mode = &d3dva_modes[i]; + if (mode->codec == codecid) + return 0; + } + return HWDEC_ERR_NO_CODEC; +} + +static bool profile_compatible(const struct d3dva_mode *mode, int profile) +{ + if (!mode->profiles) + return true; + + for (int i = 0; mode->profiles[i]; i++){ + if(mode->profiles[i] == profile) + return true; + } + return false; +} + +static bool mode_supported(const struct d3dva_mode *mode, + const GUID *device_modes, UINT n_modes) +{ + for (int i = 0; i < n_modes; i++) { + if (IsEqualGUID(mode->guid, &device_modes[i])) + return true; + } + return false; +} + +struct d3d_decoder_fmt d3d_select_decoder_mode( + struct lavc_ctx *s, const GUID *device_guids, UINT n_guids, + DWORD (*get_dxfmt_cb)(struct lavc_ctx *s, const GUID *guid, int depth)) +{ + struct d3d_decoder_fmt fmt = { + .guid = &GUID_NULL, + .mpfmt_decoded = IMGFMT_NONE, + .dxfmt_decoded = 0, + }; + + // this has the right bit-depth, but is unfortunately not the native format + int sw_img_fmt = pixfmt2imgfmt(s->avctx->sw_pix_fmt); + if (sw_img_fmt == IMGFMT_NONE) + return fmt; + + int depth = IMGFMT_RGB_DEPTH(sw_img_fmt); + int p010 = mp_imgfmt_find(1, 1, 2, 10, MP_IMGFLAG_YUV_NV); + int mpfmt_decoded = depth <= 8 ? IMGFMT_NV12 : p010; + + for (int i = 0; i < MP_ARRAY_SIZE(d3dva_modes); i++) { + const struct d3dva_mode *mode = &d3dva_modes[i]; + if (mode->codec == s->avctx->codec_id && + profile_compatible(mode, s->avctx->profile) && + mode_supported(mode, device_guids, n_guids)) { + + DWORD dxfmt_decoded = get_dxfmt_cb(s, mode->guid, depth); + if (dxfmt_decoded) { + fmt.guid = mode->guid; + fmt.mpfmt_decoded = mpfmt_decoded; + fmt.dxfmt_decoded = dxfmt_decoded; + return fmt; + } + } + } + return fmt; +} + +char *d3d_decoder_guid_to_desc_buf(char *buf, size_t buf_size, + const GUID *mode_guid) +{ + const char *name = ""; + for (int i = 0; i < MP_ARRAY_SIZE(d3dva_modes); i++) { + const struct d3dva_mode *mode = &d3dva_modes[i]; + if (IsEqualGUID(mode->guid, mode_guid)) { + name = mode->name; + break; + } + } + snprintf(buf, buf_size, "%s %s", mp_GUID_to_str(mode_guid), name); + return buf; +} + +void d3d_surface_align(struct lavc_ctx *s, int *w, int *h) +{ + int alignment = 16; + switch (s->avctx->codec_id) { + // decoding MPEG-2 requires additional alignment on some Intel GPUs, but it + // causes issues for H.264 on certain AMD GPUs..... + case AV_CODEC_ID_MPEG2VIDEO: + alignment = 32; + break; + // the HEVC DXVA2 spec asks for 128 pixel aligned surfaces to ensure + // all coding features have enough room to work with + case AV_CODEC_ID_HEVC: + alignment = 128; + break; + } + *w = FFALIGN(*w, alignment); + *h = FFALIGN(*h, alignment); +} + +unsigned d3d_decoder_config_score(struct lavc_ctx *s, + GUID *guidConfigBitstreamEncryption, + UINT ConfigBitstreamRaw) +{ + unsigned score = 0; + if (ConfigBitstreamRaw == 1) { + score = 1; + } else if (s->avctx->codec_id == AV_CODEC_ID_H264 + && ConfigBitstreamRaw == 2) { + score = 2; + } else { + return 0; + } + + if (IsEqualGUID(guidConfigBitstreamEncryption, &DXVA2_NoEncrypt)) + score += 16; + + return score; +} + +BOOL is_clearvideo(const GUID *mode_guid) +{ + return IsEqualGUID(mode_guid, &DXVA_Intel_H264_NoFGT_ClearVideo); +} + +void copy_nv12(struct mp_image *dest, uint8_t *src_bits, + unsigned src_pitch, unsigned surf_height) +{ + struct mp_image buf = {0}; + mp_image_setfmt(&buf, dest->imgfmt); + mp_image_set_size(&buf, dest->w, dest->h); + + buf.planes[0] = src_bits; + buf.stride[0] = src_pitch; + buf.planes[1] = src_bits + src_pitch * surf_height; + buf.stride[1] = src_pitch; + mp_image_copy_gpu(dest, &buf); +} diff --git a/video/decode/d3d.h b/video/decode/d3d.h new file mode 100644 index 0000000000..bbd6bdfd2a --- /dev/null +++ b/video/decode/d3d.h @@ -0,0 +1,50 @@ +/* + * 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 . + */ + +#ifndef MPV_DECODE_D3D_H +#define MPV_DECODE_D3D_H + +#include +#include + +struct mp_image; +struct lavc_ctx; + +struct d3d_decoder_fmt { + const GUID *guid; + int mpfmt_decoded; + DWORD dxfmt_decoded; // D3DFORMAT or DXGI_FORMAT +}; + +int d3d_probe_codec(const char *codec); +struct d3d_decoder_fmt d3d_select_decoder_mode( + struct lavc_ctx *s, const GUID *device_guids, UINT n_guids, + DWORD (*get_dxfmt_cb)(struct lavc_ctx *s, const GUID *guid, int depth)); + +char *d3d_decoder_guid_to_desc_buf(char *buf, size_t buf_size, + const GUID *mode_guid); +#define d3d_decoder_guid_to_desc(guid) d3d_decoder_guid_to_desc_buf((char[256]){0}, 256, (guid)) + +void d3d_surface_align(struct lavc_ctx *s, int *w, int *h); +unsigned d3d_decoder_config_score(struct lavc_ctx *s, + GUID *guidConfigBitstreamEncryption, + UINT ConfigBitstreamRaw); +BOOL is_clearvideo(const GUID *mode_guid); +void copy_nv12(struct mp_image *dest, uint8_t *src_bits, + unsigned src_pitch, unsigned surf_height); + +#endif diff --git a/video/decode/d3d11va.c b/video/decode/d3d11va.c new file mode 100644 index 0000000000..622a28981d --- /dev/null +++ b/video/decode/d3d11va.c @@ -0,0 +1,499 @@ +/* + * 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 . + */ + +#include + +#include "lavc.h" +#include "common/common.h" +#include "common/av_common.h" +#include "osdep/windows_utils.h" +#include "video/fmt-conversion.h" +#include "video/mp_image_pool.h" +#include "video/hwdec.h" + +#include "video/d3d11va.h" +#include "d3d.h" + +#define ADDITIONAL_SURFACES (4 + HWDEC_DELAY_QUEUE_COUNT) + +struct d3d11va_decoder { + ID3D11VideoDecoder *decoder; + struct mp_image_pool *pool; + ID3D11Texture2D *staging; + int mpfmt_decoded; +}; + +struct priv { + struct mp_log *log; + + HMODULE d3d11_dll; + ID3D11Device *device; + ID3D11DeviceContext *device_ctx; + ID3D11VideoDevice *video_dev; + ID3D11VideoContext *video_ctx; + + struct d3d11va_decoder *decoder; + struct mp_image_pool *sw_pool; +}; + +static struct mp_image *d3d11va_allocate_image(struct lavc_ctx *s, int w, int h) +{ + struct priv *p = s->hwdec_priv; + struct mp_image *img = mp_image_pool_get_no_alloc(p->decoder->pool, + IMGFMT_D3D11VA, w, h); + if (!img) + MP_ERR(p, "Failed to get free D3D11VA surface\n"); + return img; +} + +static struct mp_image *d3d11va_retrieve_image(struct lavc_ctx *s, + struct mp_image *img) +{ + HRESULT hr; + struct priv *p = s->hwdec_priv; + ID3D11Texture2D *staging = p->decoder->staging; + ID3D11Texture2D *texture = d3d11_texture_in_mp_image(img); + ID3D11VideoDecoderOutputView *surface = d3d11_surface_in_mp_image(img); + + if (!texture || !surface) { + MP_ERR(p, "Failed to get Direct3D texture and surface from mp_image\n"); + return img; + } + + D3D11_TEXTURE2D_DESC texture_desc; + ID3D11Texture2D_GetDesc(texture, &texture_desc); + if (texture_desc.Width < img->w || texture_desc.Height < img->h) { + MP_ERR(p, "Direct3D11 texture smaller than mp_image dimensions\n"); + return img; + } + + // copy to the staging texture + D3D11_VIDEO_DECODER_OUTPUT_VIEW_DESC surface_desc; + ID3D11VideoDecoderOutputView_GetDesc(surface, &surface_desc); + ID3D11DeviceContext_CopySubresourceRegion( + p->device_ctx, + (ID3D11Resource *)staging, 0, 0, 0, 0, + (ID3D11Resource *)texture, surface_desc.Texture2D.ArraySlice, NULL); + + struct mp_image *sw_img = mp_image_pool_get(p->sw_pool, + p->decoder->mpfmt_decoded, + texture_desc.Width, + texture_desc.Height); + if (!sw_img) { + MP_ERR(p, "Failed to get %s surface from CPU pool\n", + mp_imgfmt_to_name(p->decoder->mpfmt_decoded)); + return img; + } + + // copy staging texture to the cpu mp_image + D3D11_MAPPED_SUBRESOURCE lock; + hr = ID3D11DeviceContext_Map(p->device_ctx, (ID3D11Resource *)staging, + 0, D3D11_MAP_READ, 0, &lock); + if (FAILED(hr)) { + MP_ERR(p, "Failed to map D3D11 surface: %s\n", mp_HRESULT_to_str(hr)); + talloc_free(sw_img); + return img; + } + copy_nv12(sw_img, lock.pData, lock.RowPitch, texture_desc.Height); + ID3D11DeviceContext_Unmap(p->device_ctx, (ID3D11Resource *)staging, 0); + + mp_image_set_size(sw_img, img->w, img->h); + mp_image_copy_attributes(sw_img, img); + talloc_free(img); + return sw_img; +} + +struct d3d11_format { + DXGI_FORMAT format; + const char *name; + int depth; +}; + +#define DFMT(name) MP_CONCAT(DXGI_FORMAT_, name), # name +static const struct d3d11_format d3d11_formats[] = { + {DFMT(NV12), 8}, + {DFMT(P010), 10}, + {DFMT(P016), 16}, +}; +#undef DFMT + +static BOOL d3d11_format_supported(struct lavc_ctx *s, const GUID *guid, + const struct d3d11_format *format) +{ + struct priv *p = s->hwdec_priv; + BOOL is_supported = FALSE; + HRESULT hr = ID3D11VideoDevice_CheckVideoDecoderFormat( + p->video_dev, guid, format->format, &is_supported); + if (FAILED(hr)) { + MP_ERR(p, "Check decoder output format %s for decoder %s: %s\n", + format->name, d3d_decoder_guid_to_desc(guid), + mp_HRESULT_to_str(hr)); + } + return is_supported; +} + +static void dump_decoder_info(struct lavc_ctx *s, const GUID *guid) +{ + struct priv *p = s->hwdec_priv; + char fmts[256] = {0}; + for (int i = 0; i < MP_ARRAY_SIZE(d3d11_formats); i++) { + const struct d3d11_format *format = &d3d11_formats[i]; + if (d3d11_format_supported(s, guid, format)) + mp_snprintf_cat(fmts, sizeof(fmts), " %s", format->name); + } + MP_VERBOSE(p, "%s %s\n", d3d_decoder_guid_to_desc(guid), fmts); +} + +static DWORD get_dxfmt_cb(struct lavc_ctx *s, const GUID *guid, int depth) +{ + for (int i = 0; i < MP_ARRAY_SIZE(d3d11_formats); i++) { + const struct d3d11_format *format = &d3d11_formats[i]; + if (depth <= format->depth && + d3d11_format_supported(s, guid, format)) { + return format->format; + } + } + return 0; +} + +static void d3d11va_destroy_decoder(void *arg) +{ + struct d3d11va_decoder *decoder = arg; + + if (decoder->decoder) + ID3D11VideoDecoder_Release(decoder->decoder); + + if (decoder->staging) + ID3D11Texture2D_Release(decoder->staging); +} + +static int d3d11va_init_decoder(struct lavc_ctx *s, int w, int h) +{ + HRESULT hr; + int ret = -1; + struct priv *p = s->hwdec_priv; + TA_FREEP(&p->decoder); + + void *tmp = talloc_new(NULL); + + UINT n_guids = ID3D11VideoDevice_GetVideoDecoderProfileCount(p->video_dev); + GUID *device_guids = talloc_array(tmp, GUID, n_guids); + for (UINT i = 0; i < n_guids; i++) { + GUID *guid = &device_guids[i]; + hr = ID3D11VideoDevice_GetVideoDecoderProfile(p->video_dev, i, guid); + if (FAILED(hr)) { + MP_ERR(p, "Failed to get VideoDecoderProfile %d: %s\n", + i, mp_HRESULT_to_str(hr)); + goto done; + } + dump_decoder_info(s, guid); + } + + struct d3d_decoder_fmt fmt = + d3d_select_decoder_mode(s, device_guids, n_guids, get_dxfmt_cb); + if (fmt.mpfmt_decoded == IMGFMT_NONE) { + MP_ERR(p, "Failed to find a suitable decoder\n"); + goto done; + } + + struct d3d11va_decoder *decoder = talloc_zero(tmp, struct d3d11va_decoder); + talloc_set_destructor(decoder, d3d11va_destroy_decoder); + decoder->mpfmt_decoded = fmt.mpfmt_decoded; + + int n_surfaces = hwdec_get_max_refs(s) + ADDITIONAL_SURFACES; + int w_align = w, h_align = h; + d3d_surface_align(s, &w_align, &h_align); + + ID3D11Texture2D *texture = NULL; + D3D11_TEXTURE2D_DESC tex_desc = { + .Width = w_align, + .Height = h_align, + .MipLevels = 1, + .Format = fmt.dxfmt_decoded, + .SampleDesc.Count = 1, + .MiscFlags = 0, + .ArraySize = n_surfaces, + .Usage = D3D11_USAGE_DEFAULT, + .BindFlags = D3D11_BIND_DECODER, + .CPUAccessFlags = 0, + }; + hr = ID3D11Device_CreateTexture2D(p->device, &tex_desc, NULL, &texture); + if (FAILED(hr)) { + MP_ERR(p, "Failed to create Direct3D11 texture with %d surfaces: %s\n", + n_surfaces, mp_HRESULT_to_str(hr)); + goto done; + } + + if (s->hwdec->type == HWDEC_D3D11VA_COPY) { + // create staging texture shared with the CPU with mostly the same + // parameters as the above decoder-bound texture + ID3D11Texture2D_GetDesc(texture, &tex_desc); + tex_desc.MipLevels = 1; + tex_desc.MiscFlags = 0; + tex_desc.ArraySize = 1; + tex_desc.Usage = D3D11_USAGE_STAGING; + tex_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + tex_desc.BindFlags = 0; + hr = ID3D11Device_CreateTexture2D(p->device, &tex_desc, NULL, + &decoder->staging); + if (FAILED(hr)) { + MP_ERR(p, "Failed to create staging texture: %s\n", + mp_HRESULT_to_str(hr)); + goto done; + } + } + + // pool to hold the mp_image wrapped surfaces + decoder->pool = talloc_steal(decoder, mp_image_pool_new(n_surfaces)); + // array of the same surfaces (needed by ffmpeg) + ID3D11VideoDecoderOutputView **surfaces = + talloc_array_ptrtype(decoder->pool, surfaces, n_surfaces); + + D3D11_VIDEO_DECODER_OUTPUT_VIEW_DESC view_desc = { + .DecodeProfile = *fmt.guid, + .ViewDimension = D3D11_VDOV_DIMENSION_TEXTURE2D, + }; + for (int i = 0; i < n_surfaces; i++) { + ID3D11VideoDecoderOutputView **surface = &surfaces[i]; + view_desc.Texture2D.ArraySlice = i; + hr = ID3D11VideoDevice_CreateVideoDecoderOutputView( + p->video_dev, (ID3D11Resource *)texture, &view_desc, surface); + if (FAILED(hr)) { + MP_ERR(p, "Failed getting decoder output view %d: %s\n", + i, mp_HRESULT_to_str(hr)); + goto done; + } + struct mp_image *img = d3d11va_new_ref(*surface, w, h); + ID3D11VideoDecoderOutputView_Release(*surface); // transferred to img + if (!img) { + MP_ERR(p, "Failed to create D3D11VA image %d\n", i); + goto done; + } + mp_image_pool_add(decoder->pool, img); // transferred to pool + } + + D3D11_VIDEO_DECODER_DESC decoder_desc = { + .Guid = *fmt.guid, + .SampleWidth = w, + .SampleHeight = h, + .OutputFormat = fmt.dxfmt_decoded, + }; + UINT n_cfg; + hr = ID3D11VideoDevice_GetVideoDecoderConfigCount(p->video_dev, + &decoder_desc, &n_cfg); + if (FAILED(hr)) { + MP_ERR(p, "Failed to get number of decoder configurations: %s)", + mp_HRESULT_to_str(hr)); + goto done; + } + + // pick the config with the highest score + D3D11_VIDEO_DECODER_CONFIG *decoder_config = + talloc_zero(decoder, D3D11_VIDEO_DECODER_CONFIG); + unsigned max_score = 0; + for (UINT i = 0; i < n_cfg; i++) { + D3D11_VIDEO_DECODER_CONFIG cfg; + hr = ID3D11VideoDevice_GetVideoDecoderConfig(p->video_dev, + &decoder_desc, + i, &cfg); + if (FAILED(hr)) { + MP_ERR(p, "Failed to get decoder config %d: %s\n", + i, mp_HRESULT_to_str(hr)); + goto done; + } + unsigned score = d3d_decoder_config_score( + s, &cfg.guidConfigBitstreamEncryption, cfg.ConfigBitstreamRaw); + if (score > max_score) { + max_score = score; + *decoder_config = cfg; + } + } + if (!max_score) { + MP_ERR(p, "Failed to find a suitable decoder configuration\n"); + goto done; + } + + hr = ID3D11VideoDevice_CreateVideoDecoder(p->video_dev, &decoder_desc, + decoder_config, + &decoder->decoder); + if (FAILED(hr)) { + MP_ERR(p, "Failed to create video decoder: %s\n", + mp_HRESULT_to_str(hr)); + goto done; + } + + struct AVD3D11VAContext *avd3d11va_ctx = s->avctx->hwaccel_context; + avd3d11va_ctx->decoder = decoder->decoder; + avd3d11va_ctx->video_context = p->video_ctx; + avd3d11va_ctx->cfg = decoder_config; + avd3d11va_ctx->surface_count = n_surfaces; + avd3d11va_ctx->surface = surfaces; + avd3d11va_ctx->workaround = is_clearvideo(fmt.guid) ? + FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO : 0; + + p->decoder = talloc_steal(NULL, decoder); + ret = 0; +done: + // still referenced by pool images / surfaces + if (texture) + ID3D11Texture2D_Release(texture); + + talloc_free(tmp); + return ret; +} + +static void destroy_device(struct lavc_ctx *s) +{ + struct priv *p = s->hwdec_priv; + + if (p->device) + ID3D11Device_Release(p->device); + + if (p->device_ctx) + ID3D11DeviceContext_Release(p->device_ctx); + + if (p->d3d11_dll) + FreeLibrary(p->d3d11_dll); +} + +static bool create_device(struct lavc_ctx *s, BOOL thread_safe) +{ + HRESULT hr; + struct priv *p = s->hwdec_priv; + + p->d3d11_dll = LoadLibrary(L"d3d11.dll"); + if (!p->d3d11_dll) { + MP_ERR(p, "Failed to load D3D11 library\n"); + return false; + } + + PFN_D3D11_CREATE_DEVICE CreateDevice = + (void *)GetProcAddress(p->d3d11_dll, "D3D11CreateDevice"); + if (!CreateDevice) { + MP_ERR(p, "Failed to get D3D11CreateDevice symbol from DLL: %s\n", + mp_LastError_to_str()); + return false; + } + + hr = CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, + D3D11_CREATE_DEVICE_VIDEO_SUPPORT, NULL, 0, + D3D11_SDK_VERSION, &p->device, NULL, &p->device_ctx); + if (FAILED(hr)) { + MP_ERR(p, "Failed to create D3D11 Device: %s\n", + mp_HRESULT_to_str(hr)); + return false; + } + + ID3D10Multithread *multithread; + hr = ID3D11Device_QueryInterface(p->device, &IID_ID3D10Multithread, + (void **)&multithread); + if (FAILED(hr)) { + MP_ERR(p, "Failed to get Multithread interface: %s\n", + mp_HRESULT_to_str(hr)); + return false; + } + ID3D10Multithread_SetMultithreadProtected(multithread, thread_safe); + ID3D10Multithread_Release(multithread); + return true; +} + +static void d3d11va_uninit(struct lavc_ctx *s) +{ + struct priv *p = s->hwdec_priv; + if (!p) + return; + + talloc_free(p->decoder); + av_freep(&s->avctx->hwaccel_context); + + if (p->video_dev) + ID3D11VideoDevice_Release(p->video_dev); + + if (p->video_ctx) + ID3D11VideoContext_Release(p->video_ctx); + + destroy_device(s); + + TA_FREEP(&s->hwdec_priv); +} + +static int d3d11va_init(struct lavc_ctx *s) +{ + HRESULT hr; + struct priv *p = talloc_zero(NULL, struct priv); + if (!p) + return -1; + + s->hwdec_priv = p; + p->log = mp_log_new(s, s->log, "d3d11va"); + if (s->hwdec->type == HWDEC_D3D11VA_COPY) { + mp_check_gpu_memcpy(p->log, NULL); + p->sw_pool = talloc_steal(p, mp_image_pool_new(17)); + } + + if (!create_device(s, FALSE)) + goto fail; + + hr = ID3D11DeviceContext_QueryInterface(p->device_ctx, + &IID_ID3D11VideoContext, + (void **)&p->video_ctx); + if (FAILED(hr)) { + MP_ERR(p, "Failed to get VideoContext interface: %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + hr = ID3D11Device_QueryInterface(p->device, + &IID_ID3D11VideoDevice, + (void **)&p->video_dev); + if (FAILED(hr)) { + MP_ERR(p, "Failed to get VideoDevice interface. %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + s->avctx->hwaccel_context = av_d3d11va_alloc_context(); + if (!s->avctx->hwaccel_context) { + MP_ERR(p, "Failed to allocate hwaccel_context\n"); + goto fail; + } + + return 0; +fail: + d3d11va_uninit(s); + return -1; +} + +static int d3d11va_probe(struct vd_lavc_hwdec *hwdec, + struct mp_hwdec_info *info, + const char *codec) +{ + hwdec_request_api(info, "d3d11va"); + return d3d_probe_codec(codec); +} + +const struct vd_lavc_hwdec mp_vd_lavc_d3d11va_copy = { + .type = HWDEC_D3D11VA_COPY, + .image_format = IMGFMT_D3D11VA, + .probe = d3d11va_probe, + .init = d3d11va_init, + .uninit = d3d11va_uninit, + .init_decoder = d3d11va_init_decoder, + .allocate_image = d3d11va_allocate_image, + .process_image = d3d11va_retrieve_image, + .delay_queue = HWDEC_DELAY_QUEUE_COUNT, +}; diff --git a/video/decode/dec_video.c b/video/decode/dec_video.c index e8a57749ab..2a6a3ec213 100644 --- a/video/decode/dec_video.c +++ b/video/decode/dec_video.c @@ -364,16 +364,16 @@ void video_work(struct dec_video *d_video) if (d_video->header->attached_picture) { if (d_video->current_state == DATA_AGAIN && !d_video->cover_art_mpi) { - d_video->cover_art_mpi = - decode_packet(d_video, d_video->header->attached_picture, 0); + struct demux_packet *packet = + demux_copy_packet(d_video->header->attached_picture); + d_video->cover_art_mpi = decode_packet(d_video, packet, 0); // Might need flush. if (!d_video->cover_art_mpi) d_video->cover_art_mpi = decode_packet(d_video, NULL, 0); - d_video->current_state = DATA_OK; + talloc_free(packet); } - if (d_video->current_state == DATA_OK) + if (d_video->current_state != DATA_EOF) d_video->current_mpi = mp_image_new_ref(d_video->cover_art_mpi); - // (DATA_OK is returned the first time, when current_mpi is sill set) d_video->current_state = DATA_EOF; return; } diff --git a/video/decode/dxva2.c b/video/decode/dxva2.c index d4ce0c72b3..c90fa76885 100644 --- a/video/decode/dxva2.c +++ b/video/decode/dxva2.c @@ -19,14 +19,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include - #define DXVA2API_USE_BITFIELDS - -#include - -#include - #include #include "lavc.h" @@ -34,315 +27,449 @@ #include "common/av_common.h" #include "osdep/windows_utils.h" #include "video/fmt-conversion.h" -#include "video/dxva2.h" #include "video/mp_image_pool.h" #include "video/hwdec.h" + #include "video/d3d.h" +#include "video/dxva2.h" +#include "d3d.h" #define ADDITIONAL_SURFACES (4 + HWDEC_DELAY_QUEUE_COUNT) -// A minor evil. -#ifndef FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO -#define FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO 2 -#endif - -/* define all the GUIDs used directly here, - to avoid problems with inconsistent dxva2api.h versions in mingw-w64 and different MSVC version */ -#include -DEFINE_GUID(IID_IDirectXVideoDecoderService, 0xfc51a551,0xd5e7,0x11d9,0xaf,0x55,0x00,0x05,0x4e,0x43,0xff,0x02); - -DEFINE_GUID(DXVA2_ModeMPEG2_VLD, 0xee27417f, 0x5e28,0x4e65,0xbe,0xea,0x1d,0x26,0xb5,0x08,0xad,0xc9); -DEFINE_GUID(DXVA2_ModeMPEG2and1_VLD, 0x86695f12, 0x340e,0x4f04,0x9f,0xd3,0x92,0x53,0xdd,0x32,0x74,0x60); -DEFINE_GUID(DXVA2_ModeH264_E, 0x1b81be68, 0xa0c7,0x11d3,0xb9,0x84,0x00,0xc0,0x4f,0x2e,0x73,0xc5); -DEFINE_GUID(DXVA2_ModeH264_F, 0x1b81be69, 0xa0c7,0x11d3,0xb9,0x84,0x00,0xc0,0x4f,0x2e,0x73,0xc5); -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_ModeHEVC_VLD_Main10,0x107af0e0, 0xef1a,0x4d19,0xab,0xa8,0x67,0xa1,0x63,0x07,0x3d,0x13); -DEFINE_GUID(DXVA2_NoEncrypt, 0x1b81beD0, 0xa0c7,0x11d3,0xb9,0x84,0x00,0xc0,0x4f,0x2e,0x73,0xc5); - -typedef IDirect3D9* WINAPI pDirect3DCreate9(UINT); -typedef HRESULT WINAPI pCreateDeviceManager9(UINT *, IDirect3DDeviceManager9 **); - -typedef struct dxva2_mode { - const GUID *guid; - const char *name; - enum AVCodecID codec; - int depth; // defaults to 8 -} dxva2_mode; - -#define MODE(id) &MP_CONCAT(DXVA2_Mode, id), # id - -static const dxva2_mode dxva2_modes[] = { - /* MPEG-2 */ - { MODE(MPEG2_VLD), AV_CODEC_ID_MPEG2VIDEO }, - { MODE(MPEG2and1_VLD), AV_CODEC_ID_MPEG2VIDEO }, - - /* H.264 */ - { MODE(H264_F), AV_CODEC_ID_H264 }, - { MODE(H264_E), AV_CODEC_ID_H264 }, - /* Intel specific H.264 mode */ - { &DXVADDI_Intel_ModeH264_E, "Intel_ModeH264_E", AV_CODEC_ID_H264 }, - - /* VC-1 / WMV3 */ - { MODE(VC1_D2010), AV_CODEC_ID_VC1 }, - { MODE(VC1_D2010), AV_CODEC_ID_WMV3 }, - { MODE(VC1_D), AV_CODEC_ID_VC1 }, - { MODE(VC1_D), AV_CODEC_ID_WMV3 }, - - { MODE(HEVC_VLD_Main), AV_CODEC_ID_HEVC }, - { MODE(HEVC_VLD_Main10),AV_CODEC_ID_HEVC, .depth = 10}, - - { NULL, 0 }, -}; - -#undef MODE - -struct dxva2_decoder { - DXVA2_ConfigPictureDecode config; - IDirectXVideoDecoder *decoder; - LPDIRECT3DSURFACE9 *surfaces; - int num_surfaces; - struct mp_image_pool *pool; -}; - -typedef struct DXVA2Context { +struct priv { struct mp_log *log; - HMODULE d3dlib; - HMODULE dxva2lib; - - HANDLE deviceHandle; - + HMODULE d3d9_dll; + HMODULE dxva2_dll; IDirect3D9 *d3d9; - IDirect3DDevice9 *d3d9device; - IDirect3DDeviceManager9 *d3d9devmgr; + IDirect3DDevice9 *device; + HANDLE device_handle; + IDirect3DDeviceManager9 *device_manager; IDirectXVideoDecoderService *decoder_service; - struct dxva2_decoder *decoder; + struct mp_image_pool *decoder_pool; struct mp_image_pool *sw_pool; - int mp_format; -} DXVA2Context; - -static void dxva2_uninit(struct lavc_ctx *s) -{ - DXVA2Context *ctx = s->hwdec_priv; - if (!ctx) - return; - - talloc_free(ctx->decoder); - - if (ctx->decoder_service) - IDirectXVideoDecoderService_Release(ctx->decoder_service); - - if (ctx->d3d9devmgr && ctx->deviceHandle != INVALID_HANDLE_VALUE) - IDirect3DDeviceManager9_CloseDeviceHandle(ctx->d3d9devmgr, ctx->deviceHandle); - - if (ctx->d3d9devmgr) - IDirect3DDeviceManager9_Release(ctx->d3d9devmgr); - - if (ctx->d3d9device) - IDirect3DDevice9_Release(ctx->d3d9device); - - if (ctx->d3d9) - IDirect3D9_Release(ctx->d3d9); - - if (ctx->d3dlib) - FreeLibrary(ctx->d3dlib); - - if (ctx->dxva2lib) - FreeLibrary(ctx->dxva2lib); - - av_freep(&s->avctx->hwaccel_context); - talloc_free(ctx); - s->hwdec_priv = NULL; -} + int mpfmt_decoded; +}; static struct mp_image *dxva2_allocate_image(struct lavc_ctx *s, int w, int h) { - DXVA2Context *ctx = s->hwdec_priv; - - struct mp_image *img = mp_image_pool_get_no_alloc(ctx->decoder->pool, + struct priv *p = s->hwdec_priv; + struct mp_image *img = mp_image_pool_get_no_alloc(p->decoder_pool, IMGFMT_DXVA2, w, h); if (!img) - MP_ERR(ctx, "Failed to allocate additional DXVA2 surface.\n"); + MP_ERR(p, "Failed to allocate additional DXVA2 surface.\n"); return img; } -static void copy_nv12(struct mp_image *dest, uint8_t *src_bits, - unsigned src_pitch, unsigned surf_height) -{ - struct mp_image buf = {0}; - mp_image_setfmt(&buf, dest->imgfmt); - mp_image_set_size(&buf, dest->w, dest->h); - - buf.planes[0] = src_bits; - buf.stride[0] = src_pitch; - buf.planes[1] = src_bits + src_pitch * surf_height; - buf.stride[1] = src_pitch; - mp_image_copy_gpu(dest, &buf); -} - static struct mp_image *dxva2_retrieve_image(struct lavc_ctx *s, struct mp_image *img) { - DXVA2Context *ctx = s->hwdec_priv; - LPDIRECT3DSURFACE9 surface = d3d9_surface_in_mp_image(img); - D3DSURFACE_DESC surfaceDesc; - D3DLOCKED_RECT LockedRect; - HRESULT hr; + HRESULT hr; + struct priv *p = s->hwdec_priv; + IDirect3DSurface9 *surface = d3d9_surface_in_mp_image(img); - IDirect3DSurface9_GetDesc(surface, &surfaceDesc); - - if (surfaceDesc.Width < img->w || surfaceDesc.Height < img->h) + if (!surface) { + MP_ERR(p, "Failed to get Direct3D surface from mp_image\n"); return img; + } - struct mp_image *sw_img = mp_image_pool_get(ctx->sw_pool, ctx->mp_format, - surfaceDesc.Width, - surfaceDesc.Height); - - if (!sw_img) + D3DSURFACE_DESC surface_desc; + IDirect3DSurface9_GetDesc(surface, &surface_desc); + if (surface_desc.Width < img->w || surface_desc.Height < img->h) { + MP_ERR(p, "Direct3D11 texture smaller than mp_image dimensions\n"); return img; + } - hr = IDirect3DSurface9_LockRect(surface, &LockedRect, NULL, D3DLOCK_READONLY); + struct mp_image *sw_img = mp_image_pool_get(p->sw_pool, + p->mpfmt_decoded, + surface_desc.Width, + surface_desc.Height); + if (!sw_img) { + MP_ERR(p, "Failed to get %s surface from CPU pool\n", + mp_imgfmt_to_name(p->mpfmt_decoded)); + return img; + } + + D3DLOCKED_RECT lock; + hr = IDirect3DSurface9_LockRect(surface, &lock, NULL, D3DLOCK_READONLY); if (FAILED(hr)) { - MP_ERR(ctx, "Unable to lock DXVA2 surface: %s\n", + MP_ERR(p, "Unable to lock DXVA2 surface: %s\n", mp_HRESULT_to_str(hr)); talloc_free(sw_img); return img; } - - 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); - + copy_nv12(sw_img, lock.pBits, lock.Pitch, surface_desc.Height); IDirect3DSurface9_UnlockRect(surface); + mp_image_set_size(sw_img, img->w, img->h); + mp_image_copy_attributes(sw_img, img); talloc_free(img); return sw_img; } -static int create_device(struct lavc_ctx *s) -{ - DXVA2Context *ctx = s->hwdec_priv; - pDirect3DCreate9 *createD3D = NULL; - HRESULT hr; - D3DPRESENT_PARAMETERS d3dpp = {0}; - D3DDISPLAYMODE d3ddm; - UINT adapter = D3DADAPTER_DEFAULT; +struct d3d9_format { + D3DFORMAT format; + int depth; +}; - 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; +static const struct d3d9_format d3d9_formats[] = { + {MKTAG('N','V','1','2'), 8}, + {MKTAG('P','0','1','0'), 10}, + {MKTAG('P','0','1','6'), 16}, +}; + +static void dump_decoder_info(struct lavc_ctx *s, + GUID *device_guids, UINT n_guids) +{ + struct priv *p = s->hwdec_priv; + MP_VERBOSE(p, "%u decoder devices:\n", (unsigned)n_guids); + for (UINT i = 0; i < n_guids; i++) { + GUID *guid = &device_guids[i]; + char *description = d3d_decoder_guid_to_desc(guid); + + D3DFORMAT *formats = NULL; + UINT n_formats = 0; + HRESULT hr = IDirectXVideoDecoderService_GetDecoderRenderTargets( + p->decoder_service, guid, &n_formats, &formats); + if (FAILED(hr)) { + MP_ERR(p, "Failed to get render targets for decoder %s:%s", + description, mp_HRESULT_to_str(hr)); + } + + char fmts[256] = {0}; + for (UINT j = 0; j < n_formats; j++) { + mp_snprintf_cat(fmts, sizeof(fmts), + " %s", mp_tag_str(formats[j])); + } + CoTaskMemFree(formats); + + MP_VERBOSE(p, "%s %s\n", description, fmts); + } +} + +static DWORD get_dxfmt_cb(struct lavc_ctx *s, const GUID *guid, int depth) +{ + DWORD ret = 0; + struct priv *p = s->hwdec_priv; + D3DFORMAT *formats = NULL; + UINT n_formats = 0; + HRESULT hr = IDirectXVideoDecoderService_GetDecoderRenderTargets( + p->decoder_service, guid, &n_formats, &formats); + if (FAILED(hr)) { + MP_ERR(p, "Callback failed to get render targets for decoder %s: %s", + d3d_decoder_guid_to_desc(guid), mp_HRESULT_to_str(hr)); + return 0; + } + + for (int i = 0; i < MP_ARRAY_SIZE(d3d9_formats); i++) { + const struct d3d9_format *d3d9_fmt = &d3d9_formats[i]; + if (d3d9_fmt->depth < depth) + continue; + + for (UINT j = 0; j < n_formats; j++) { + if (formats[i] == d3d9_fmt->format) { + ret = formats[i]; + goto done; + } } } +done: + CoTaskMemFree(formats); + return ret; +} - ctx->d3dlib = LoadLibrary(L"d3d9.dll"); - if (!ctx->d3dlib) { - MP_ERR(ctx, "Failed to load D3D9 library\n"); - goto fail; - } +static int dxva2_init_decoder(struct lavc_ctx *s, int w, int h) +{ + HRESULT hr; + int ret = -1; + struct priv *p = s->hwdec_priv; + TA_FREEP(&p->decoder_pool); - createD3D = (pDirect3DCreate9 *)GetProcAddress(ctx->d3dlib, "Direct3DCreate9"); - if (!createD3D) { - MP_ERR(ctx, "Failed to locate Direct3DCreate9\n"); - goto fail; - } + int n_surfaces = hwdec_get_max_refs(s) + ADDITIONAL_SURFACES; + IDirect3DSurface9 **surfaces = NULL; + IDirectXVideoDecoder *decoder = NULL; + void *tmp = talloc_new(NULL); - ctx->d3d9 = createD3D(D3D_SDK_VERSION); - if (!ctx->d3d9) { - MP_ERR(ctx, "Failed to create IDirect3D object\n"); - goto fail; - } - - IDirect3D9_GetAdapterDisplayMode(ctx->d3d9, adapter, &d3ddm); - d3dpp.Windowed = TRUE; - d3dpp.BackBufferWidth = 640; - d3dpp.BackBufferHeight = 480; - d3dpp.BackBufferCount = 0; - d3dpp.BackBufferFormat = d3ddm.Format; - d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; - d3dpp.Flags = D3DPRESENTFLAG_VIDEO; - - hr = IDirect3D9_CreateDevice(ctx->d3d9, adapter, D3DDEVTYPE_HAL, GetShellWindow(), - D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE, - &d3dpp, &ctx->d3d9device); + UINT n_guids; + GUID *device_guids; + hr = IDirectXVideoDecoderService_GetDecoderDeviceGuids( + p->decoder_service, &n_guids, &device_guids); if (FAILED(hr)) { - MP_ERR(ctx, "Failed to create Direct3D device: %s\n", + MP_ERR(p, "Failed to retrieve decoder device GUIDs: %s\n", mp_HRESULT_to_str(hr)); - goto fail; + goto done; } - return 0; + dump_decoder_info(s, device_guids, n_guids); -fail: - return -1; + struct d3d_decoder_fmt fmt = + d3d_select_decoder_mode(s, device_guids, n_guids, get_dxfmt_cb); + CoTaskMemFree(device_guids); + if (fmt.mpfmt_decoded == IMGFMT_NONE) { + MP_ERR(p, "Failed to find a suitable decoder\n"); + goto done; + } + + p->mpfmt_decoded = fmt.mpfmt_decoded; + struct mp_image_pool *decoder_pool = + talloc_steal(tmp, mp_image_pool_new(n_surfaces)); + DXVA2_ConfigPictureDecode *decoder_config = + talloc_zero(decoder_pool, DXVA2_ConfigPictureDecode); + + int w_align = w, h_align = h; + d3d_surface_align(s, &w_align, &h_align); + DXVA2_VideoDesc video_desc ={ + .SampleWidth = w, + .SampleHeight = h, + .Format = fmt.dxfmt_decoded, + }; + UINT n_configs = 0; + DXVA2_ConfigPictureDecode *configs = NULL; + hr = IDirectXVideoDecoderService_GetDecoderConfigurations( + p->decoder_service, fmt.guid, &video_desc, NULL, + &n_configs, &configs); + if (FAILED(hr)) { + MP_ERR(p, "Unable to retrieve decoder configurations: %s\n", + mp_HRESULT_to_str(hr)); + goto done; + } + + unsigned max_score = 0; + for (UINT i = 0; i < n_configs; i++) { + unsigned score = d3d_decoder_config_score( + s, &configs[i].guidConfigBitstreamEncryption, + configs[i].ConfigBitstreamRaw); + if (score > max_score) { + max_score = score; + *decoder_config = configs[i]; + } + } + CoTaskMemFree(configs); + if (!max_score) { + MP_ERR(p, "Failed to find a suitable decoder configuration\n"); + goto done; + } + + surfaces = talloc_zero_array(decoder_pool, IDirect3DSurface9*, n_surfaces); + hr = IDirectXVideoDecoderService_CreateSurface( + p->decoder_service, + w_align, h_align, + n_surfaces - 1, fmt.dxfmt_decoded, D3DPOOL_DEFAULT, 0, + DXVA2_VideoDecoderRenderTarget, surfaces, NULL); + if (FAILED(hr)) { + MP_ERR(p, "Failed to create %d video surfaces: %s\n", + n_surfaces, mp_HRESULT_to_str(hr)); + goto done; + } + + hr = IDirectXVideoDecoderService_CreateVideoDecoder( + p->decoder_service, fmt.guid, &video_desc, decoder_config, + surfaces, n_surfaces, &decoder); + if (FAILED(hr)) { + MP_ERR(p, "Failed to create DXVA2 video decoder: %s\n", + mp_HRESULT_to_str(hr)); + goto done; + } + + for (int i = 0; i < n_surfaces; i++) { + struct mp_image *img = dxva2_new_ref(decoder, surfaces[i], w, h); + if (!img) { + MP_ERR(p, "Failed to create DXVA2 image\n"); + goto done; + } + mp_image_pool_add(decoder_pool, img); // transferred to pool + } + + // Pass required information on to ffmpeg. + struct dxva_context *dxva_ctx = s->avctx->hwaccel_context; + dxva_ctx->cfg = decoder_config; + dxva_ctx->decoder = decoder; + dxva_ctx->surface_count = n_surfaces; + dxva_ctx->surface = surfaces; + dxva_ctx->workaround = is_clearvideo(fmt.guid) ? + FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO : 0; + + p->decoder_pool = talloc_steal(NULL, decoder_pool); + ret = 0; +done: + // On success, `p->decoder_pool` mp_images still hold refs to `surfaces` and + // `decoder`, so the pointers in the ffmpeg `dxva_context` strcture remain + // valid for the lifetime of the pool. + if (surfaces) { + for (int i = 0; i < n_surfaces; i++) + IDirect3DSurface9_Release(surfaces[i]); + } + if (decoder) + IDirectXVideoDecoder_Release(decoder); + + talloc_free(tmp); + return ret; +} + +static void destroy_device(struct lavc_ctx *s) +{ + struct priv *p = s->hwdec_priv; + + if (p->device) + IDirect3DDevice9_Release(p->device); + + if (p->d3d9) + IDirect3D9_Release(p->d3d9); + + if (p->d3d9_dll) + FreeLibrary(p->d3d9_dll); + + if (p->dxva2_dll) + FreeLibrary(p->dxva2_dll); +} + +static bool create_device(struct lavc_ctx *s) +{ + struct priv *p = s->hwdec_priv; + p->d3d9_dll = LoadLibrary(L"d3d9.dll"); + if (!p->d3d9_dll) { + MP_ERR(p, "Failed to load D3D9 library\n"); + return false; + } + + IDirect3D9* (WINAPI *Direct3DCreate9)(UINT) = + (void *)GetProcAddress(p->d3d9_dll, "Direct3DCreate9"); + if (!Direct3DCreate9) { + MP_ERR(p, "Failed to locate Direct3DCreate9\n"); + return false; + } + + p->d3d9 = Direct3DCreate9(D3D_SDK_VERSION); + if (!p->d3d9) { + MP_ERR(p, "Failed to create IDirect3D object\n"); + return false; + } + + UINT adapter = D3DADAPTER_DEFAULT; + D3DDISPLAYMODE display_mode; + IDirect3D9_GetAdapterDisplayMode(p->d3d9, adapter, &display_mode); + D3DPRESENT_PARAMETERS present_params = { + .Windowed = TRUE, + .BackBufferWidth = 640, + .BackBufferHeight = 480, + .BackBufferCount = 0, + .BackBufferFormat = display_mode.Format, + .SwapEffect = D3DSWAPEFFECT_DISCARD, + .Flags = D3DPRESENTFLAG_VIDEO, + }; + HRESULT hr = IDirect3D9_CreateDevice(p->d3d9, adapter, + D3DDEVTYPE_HAL, + GetShellWindow(), + D3DCREATE_SOFTWARE_VERTEXPROCESSING | + D3DCREATE_MULTITHREADED | + D3DCREATE_FPU_PRESERVE, + &present_params, + &p->device); + if (FAILED(hr)) { + MP_ERR(p, "Failed to create Direct3D device: %s\n", + mp_HRESULT_to_str(hr)); + return false; + } + return true; +} + +static void dxva2_uninit(struct lavc_ctx *s) +{ + struct priv *p = s->hwdec_priv; + if (!p) + return; + + av_freep(&s->avctx->hwaccel_context); + talloc_free(p->decoder_pool); + + if (p->decoder_service) + IDirectXVideoDecoderService_Release(p->decoder_service); + + if (p->device_manager && p->device_handle != INVALID_HANDLE_VALUE) + IDirect3DDeviceManager9_CloseDeviceHandle(p->device_manager, p->device_handle); + + if (p->device_manager) + IDirect3DDeviceManager9_Release(p->device_manager); + + destroy_device(s); + + TA_FREEP(&s->hwdec_priv); } 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) + struct priv *p = talloc_zero(NULL, struct priv); + if (!p) return -1; - s->hwdec_priv = ctx; - ctx->log = mp_log_new(s, s->log, "dxva2"); + s->hwdec_priv = p; + p->device_handle = INVALID_HANDLE_VALUE; + p->log = mp_log_new(s, s->log, "dxva2"); if (s->hwdec->type == HWDEC_DXVA2_COPY) { - mp_check_gpu_memcpy(ctx->log, NULL); - ctx->sw_pool = talloc_steal(ctx, mp_image_pool_new(17)); + mp_check_gpu_memcpy(p->log, NULL); + p->sw_pool = talloc_steal(p, mp_image_pool_new(17)); } - ctx->deviceHandle = INVALID_HANDLE_VALUE; + if (s->hwdec_info && s->hwdec_info->hwctx && s->hwdec_info->hwctx->d3d_ctx) + p->device = s->hwdec_info->hwctx->d3d_ctx->d3d9_device; - ctx->dxva2lib = LoadLibrary(L"dxva2.dll"); - if (!ctx->dxva2lib) { - MP_ERR(ctx, "Failed to load DXVA2 library\n"); + if (p->device) { + IDirect3D9_AddRef(p->device); + MP_VERBOSE(p, "Using VO-supplied device %p.\n", p->device); + } else if (s->hwdec->type == HWDEC_DXVA2) { + MP_ERR(p, "No Direct3D device provided for native dxva2 decoding\n"); + goto fail; + } else { + if (!create_device(s)) + goto fail; + } + + p->dxva2_dll = LoadLibrary(L"dxva2.dll"); + if (!p->dxva2_dll) { + MP_ERR(p, "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"); + HRESULT (WINAPI *CreateDeviceManager9)(UINT *, IDirect3DDeviceManager9 **) = + (void *)GetProcAddress(p->dxva2_dll, + "DXVA2CreateDirect3DDeviceManager9"); + if (!CreateDeviceManager9) { + MP_ERR(p, "Failed to locate DXVA2CreateDirect3DDeviceManager9\n"); goto fail; } - hr = createDeviceManager(&resetToken, &ctx->d3d9devmgr); + unsigned reset_token = 0; + hr = CreateDeviceManager9(&reset_token, &p->device_manager); if (FAILED(hr)) { - MP_ERR(ctx, "Failed to create Direct3D device manager: %s\n", + MP_ERR(p, "Failed to create Direct3D device manager: %s\n", mp_HRESULT_to_str(hr)); goto fail; } - hr = IDirect3DDeviceManager9_ResetDevice(ctx->d3d9devmgr, ctx->d3d9device, resetToken); + hr = IDirect3DDeviceManager9_ResetDevice(p->device_manager, + p->device, reset_token); if (FAILED(hr)) { - MP_ERR(ctx, "Failed to bind Direct3D device to device manager: %s\n", + MP_ERR(p, "Failed to bind Direct3D device to device manager: %s\n", mp_HRESULT_to_str(hr)); goto fail; } - hr = IDirect3DDeviceManager9_OpenDeviceHandle(ctx->d3d9devmgr, &ctx->deviceHandle); + hr = IDirect3DDeviceManager9_OpenDeviceHandle(p->device_manager, + &p->device_handle); if (FAILED(hr)) { - MP_ERR(ctx, "Failed to open device handle: %s\n", + MP_ERR(p, "Failed to open device handle: %s\n", mp_HRESULT_to_str(hr)); goto fail; } - hr = IDirect3DDeviceManager9_GetVideoService(ctx->d3d9devmgr, ctx->deviceHandle, &IID_IDirectXVideoDecoderService, (void **)&ctx->decoder_service); + hr = IDirect3DDeviceManager9_GetVideoService( + p->device_manager, p->device_handle, &IID_IDirectXVideoDecoderService, + (void **)&p->decoder_service); if (FAILED(hr)) { - MP_ERR(ctx, "Failed to create IDirectXVideoDecoderService: %s\n", + MP_ERR(p, "Failed to create IDirectXVideoDecoderService: %s\n", mp_HRESULT_to_str(hr)); goto fail; } @@ -357,306 +484,37 @@ fail: return -1; } -static int dxva2_get_decoder_configuration(struct lavc_ctx *s, - enum AVCodecID codec_id, - const GUID *device_guid, - const DXVA2_VideoDesc *desc, - DXVA2_ConfigPictureDecode *config) -{ - DXVA2Context *ctx = s->hwdec_priv; - unsigned cfg_count = 0, best_score = 0; - DXVA2_ConfigPictureDecode *cfg_list = NULL; - DXVA2_ConfigPictureDecode best_cfg = {{0}}; - HRESULT hr; - int i; - - hr = IDirectXVideoDecoderService_GetDecoderConfigurations(ctx->decoder_service, device_guid, desc, NULL, &cfg_count, &cfg_list); - if (FAILED(hr)) { - MP_ERR(ctx, "Unable to retrieve decoder configurations: %s\n", - mp_HRESULT_to_str(hr)); - return -1; - } - - for (i = 0; i < cfg_count; i++) { - DXVA2_ConfigPictureDecode *cfg = &cfg_list[i]; - - unsigned score; - if (cfg->ConfigBitstreamRaw == 1) - score = 1; - else if (codec_id == AV_CODEC_ID_H264 && cfg->ConfigBitstreamRaw == 2) - score = 2; - else - continue; - if (IsEqualGUID(&cfg->guidConfigBitstreamEncryption, &DXVA2_NoEncrypt)) - score += 16; - if (score > best_score) { - best_score = score; - best_cfg = *cfg; - } - } - CoTaskMemFree(cfg_list); - - if (!best_score) { - MP_ERR(ctx, "No valid decoder configuration available\n"); - return -1; - } - - *config = best_cfg; - return 0; -} - -static void dxva2_destroy_decoder(void *arg) -{ - struct dxva2_decoder *decoder = arg; - if (decoder->decoder) - IDirectXVideoDecoder_Release(decoder->decoder); - - if (decoder->surfaces) { - for (int i = 0; i < decoder->num_surfaces; i++) - IDirect3DSurface9_Release(decoder->surfaces[i]); - } -} - -static int dxva2_create_decoder(struct lavc_ctx *s, int w, int h, - enum AVCodecID codec_id, int profile) -{ - DXVA2Context *ctx = s->hwdec_priv; - struct dxva_context *dxva_ctx = s->avctx->hwaccel_context; - void *tmp = talloc_new(NULL); - GUID *guid_list = NULL; - unsigned guid_count = 0, i, j; - GUID device_guid = GUID_NULL; - D3DFORMAT target_format = 0; - DXVA2_VideoDesc desc = { 0 }; - HRESULT hr; - struct dxva2_decoder *decoder; - int surface_alignment; - int ret = -1; - - hr = IDirectXVideoDecoderService_GetDecoderDeviceGuids(ctx->decoder_service, &guid_count, &guid_list); - if (FAILED(hr)) { - MP_ERR(ctx, "Failed to retrieve decoder device GUIDs: %s\n", - mp_HRESULT_to_str(hr)); - goto fail; - } - - // dump all decoder info - MP_VERBOSE(ctx, "%d decoder devices:\n", (int)guid_count); - for (j = 0; j < guid_count; j++) { - GUID *guid = &guid_list[j]; - - const char *name = ""; - for (i = 0; dxva2_modes[i].guid; i++) { - if (IsEqualGUID(dxva2_modes[i].guid, guid)) - name = dxva2_modes[i].name; - } - - D3DFORMAT *target_list = NULL; - unsigned target_count = 0; - hr = IDirectXVideoDecoderService_GetDecoderRenderTargets(ctx->decoder_service, guid, &target_count, &target_list); - if (FAILED(hr)) - continue; - char fmts[256] = {0}; - for (i = 0; i < target_count; i++) - mp_snprintf_cat(fmts, sizeof(fmts), " %s", mp_tag_str(target_list[i])); - CoTaskMemFree(target_list); - MP_VERBOSE(ctx, "%s %s %s\n", mp_GUID_to_str(guid), name, fmts); - } - - // find a suitable decoder - for (i = 0; dxva2_modes[i].guid; i++) { - D3DFORMAT *target_list = NULL; - unsigned target_count = 0; - const dxva2_mode *mode = &dxva2_modes[i]; - int depth = mode->depth; - if (mode->codec != codec_id) - continue; - - for (j = 0; j < guid_count; j++) { - if (IsEqualGUID(mode->guid, &guid_list[j])) - break; - } - if (j == guid_count) - continue; - - if (codec_id == AV_CODEC_ID_HEVC) { - if ((mode->depth > 8) != (s->avctx->profile == FF_PROFILE_HEVC_MAIN_10)) - continue; - } - - hr = IDirectXVideoDecoderService_GetDecoderRenderTargets(ctx->decoder_service, mode->guid, &target_count, &target_list); - if (FAILED(hr)) { - continue; - } - for (j = 0; j < target_count; j++) { - const D3DFORMAT format = target_list[j]; - if (depth <= 8) { - if (format == MKTAG('N','V','1','2')) { - ctx->mp_format = IMGFMT_NV12; - target_format = format; - } - } else { - int p010 = mp_imgfmt_find(1, 1, 2, 10, MP_IMGFLAG_YUV_NV); - if (p010 && (format == MKTAG('P','0','1','0') || - format == MKTAG('P','0','1','6'))) - { - // There is no FFmpeg format that is like NV12 and supports - // 16 bit per component, but vo_opengl will use the lower - // bits in P010 anyway. - ctx->mp_format = p010; - target_format = format; - } - } - if (target_format) - break; - } - CoTaskMemFree(target_list); - if (target_format && ctx->mp_format) { - device_guid = *mode->guid; - break; - } - } - CoTaskMemFree(guid_list); - - if (IsEqualGUID(&device_guid, &GUID_NULL)) { - MP_ERR(ctx, "No decoder device for codec found\n"); - goto fail; - } - - desc.SampleWidth = w; - desc.SampleHeight = h; - desc.Format = target_format; - - decoder = talloc_zero(tmp, struct dxva2_decoder); - talloc_set_destructor(decoder, dxva2_destroy_decoder); - if (dxva2_get_decoder_configuration(s, codec_id, &device_guid, &desc, - &decoder->config) < 0) { - goto fail; - } - - /* decoding MPEG-2 requires additional alignment on some Intel GPUs, - 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; - - decoder->num_surfaces = hwdec_get_max_refs(s) + ADDITIONAL_SURFACES; - - decoder->surfaces = talloc_array(decoder, LPDIRECT3DSURFACE9, decoder->num_surfaces); - hr = IDirectXVideoDecoderService_CreateSurface( - ctx->decoder_service, - FFALIGN(w, surface_alignment), FFALIGN(h, surface_alignment), - decoder->num_surfaces - 1, target_format, D3DPOOL_DEFAULT, 0, - DXVA2_VideoDecoderRenderTarget, decoder->surfaces, NULL); - if (FAILED(hr)) { - MP_ERR(ctx, "Failed to create %d video surfaces: %s\n", - decoder->num_surfaces, mp_HRESULT_to_str(hr)); - goto fail; - } - - hr = IDirectXVideoDecoderService_CreateVideoDecoder( - ctx->decoder_service, &device_guid, &desc, &decoder->config, - decoder->surfaces, decoder->num_surfaces, &decoder->decoder); - if (FAILED(hr)) { - MP_ERR(ctx, "Failed to create DXVA2 video decoder: %s\n", - mp_HRESULT_to_str(hr)); - goto fail; - } - decoder->pool = talloc_steal(decoder, mp_image_pool_new(decoder->num_surfaces)); - for (i = 0; i < decoder->num_surfaces; i++) { - struct mp_image *img = dxva2_new_ref(decoder->decoder, decoder->surfaces[i], w, h); - if (!img) { - MP_ERR(ctx, "Failed to create DXVA2 image\n"); - goto fail; - } - mp_image_pool_add(decoder->pool, img); - } - - // Pass required information on to ffmpeg. - dxva_ctx->cfg = &decoder->config; - dxva_ctx->decoder = decoder->decoder; - dxva_ctx->surface = decoder->surfaces; - dxva_ctx->surface_count = decoder->num_surfaces; - - if (IsEqualGUID(&device_guid, &DXVADDI_Intel_ModeH264_E)) - dxva_ctx->workaround |= FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO; - - ctx->decoder = talloc_steal(NULL, decoder); - ret = 0; -fail: - talloc_free(tmp); - return ret; -} - -static int dxva2_init_decoder(struct lavc_ctx *s, int w, int h) -{ - DXVA2Context *ctx = s->hwdec_priv; - - enum AVCodecID codec = s->avctx->codec_id; - int profile = s->avctx->profile; - if (codec == AV_CODEC_ID_H264 && - (profile & ~FF_PROFILE_H264_CONSTRAINED) > FF_PROFILE_H264_HIGH) - { - MP_ERR(ctx, "Unsupported H.264 profile for DXVA2 HWAccel: %d\n", profile); - return -1; - } - if (codec == AV_CODEC_ID_HEVC && profile != FF_PROFILE_HEVC_MAIN && - profile != FF_PROFILE_HEVC_MAIN_10) - { - MP_ERR(ctx, "Unsupported H.265 profile for DXVA2 HWAccel: %d\n", profile); - return -1; - } - - talloc_free(ctx->decoder); - ctx->decoder = NULL; - - if (dxva2_create_decoder(s, w, h, codec, profile) < 0) { - MP_ERR(ctx, "Error creating the DXVA2 decoder\n"); - return -1; - } - - return 0; -} - -static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, - const char *decoder) +static int dxva2_probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, + const char *codec) { hwdec_request_api(info, "dxva2"); // dxva2-copy can do without external context; dxva2 requires it. if (hwdec->type != HWDEC_DXVA2_COPY) { - if (!info || !info->hwctx || !info->hwctx->d3d_ctx) + if (!info || !info->hwctx || !info->hwctx->d3d_ctx || + info->hwctx->type == HWDEC_DXVA2_COPY) return HWDEC_ERR_NO_CTX; } - 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) - return 0; - } - return HWDEC_ERR_NO_CODEC; + return d3d_probe_codec(codec); } const struct vd_lavc_hwdec mp_vd_lavc_dxva2 = { - .type = HWDEC_DXVA2, - .image_format = IMGFMT_DXVA2, - .probe = probe, - .init = dxva2_init, - .uninit = dxva2_uninit, - .init_decoder = dxva2_init_decoder, + .type = HWDEC_DXVA2, + .image_format = IMGFMT_DXVA2, + .probe = dxva2_probe, + .init = dxva2_init, + .uninit = dxva2_uninit, + .init_decoder = dxva2_init_decoder, .allocate_image = dxva2_allocate_image, }; const struct vd_lavc_hwdec mp_vd_lavc_dxva2_copy = { - .type = HWDEC_DXVA2_COPY, - .image_format = IMGFMT_DXVA2, - .probe = probe, - .init = dxva2_init, - .uninit = dxva2_uninit, - .init_decoder = dxva2_init_decoder, + .type = HWDEC_DXVA2_COPY, + .image_format = IMGFMT_DXVA2, + .probe = dxva2_probe, + .init = dxva2_init, + .uninit = dxva2_uninit, + .init_decoder = dxva2_init_decoder, .allocate_image = dxva2_allocate_image, - .process_image = dxva2_retrieve_image, + .process_image = dxva2_retrieve_image, + .delay_queue = HWDEC_DELAY_QUEUE_COUNT, }; diff --git a/video/decode/lavc.h b/video/decode/lavc.h index 9ef74668d8..826edbff83 100644 --- a/video/decode/lavc.h +++ b/video/decode/lavc.h @@ -49,8 +49,13 @@ struct vd_lavc_hwdec { // If not-0: the IMGFMT_ format that should be accepted in the libavcodec // get_format callback. int image_format; + // Setting this will queue the given number of frames before calling + // process_image() or returning them to the renderer. This can increase + // efficiency by not blocking on the hardware pipeline by reading back + // immediately after decoding. + int delay_queue; int (*probe)(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, - const char *decoder); + const char *codec); int (*init)(struct lavc_ctx *ctx); int (*init_decoder)(struct lavc_ctx *ctx, int w, int h); void (*uninit)(struct lavc_ctx *ctx); @@ -80,7 +85,7 @@ struct hwdec_profile_entry { const struct hwdec_profile_entry *hwdec_find_profile( struct lavc_ctx *ctx, const struct hwdec_profile_entry *table); -bool hwdec_check_codec_support(const char *decoder, +bool hwdec_check_codec_support(const char *codec, const struct hwdec_profile_entry *table); int hwdec_get_max_refs(struct lavc_ctx *ctx); diff --git a/video/decode/mediacodec.c b/video/decode/mediacodec.c new file mode 100644 index 0000000000..37ce1b8a2f --- /dev/null +++ b/video/decode/mediacodec.c @@ -0,0 +1,68 @@ +/* + * 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 . + */ + +#include "lavc.h" +#include "common/common.h" + +static const char *const codecs[][2] = { + {"h264", "h264_mediacodec"}, + {0} +}; + +static const char *map_codec(const char *c) +{ + for (int n = 0; codecs[n][0]; n++) { + if (c && strcmp(codecs[n][0], c) == 0) + return codecs[n][1]; + } + return NULL; +} + +static int init_decoder(struct lavc_ctx *ctx, int w, int h) +{ + return 0; +} + +static void uninit(struct lavc_ctx *ctx) +{ +} + +static int init(struct lavc_ctx *ctx) +{ + return 0; +} + +static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, + const char *decoder) +{ + return map_codec(decoder) ? 0 : HWDEC_ERR_NO_CODEC; +} + +static const char *get_codec(struct lavc_ctx *ctx, const char *codec) +{ + return map_codec(codec); +} + +const struct vd_lavc_hwdec mp_vd_lavc_mediacodec = { + .type = HWDEC_MEDIACODEC, + .image_format = IMGFMT_NV12, + .probe = probe, + .init = init, + .uninit = uninit, + .init_decoder = init_decoder, + .get_codec = get_codec, +}; diff --git a/video/decode/rpi.c b/video/decode/rpi.c index b72dc34faa..f2ed6d2549 100644 --- a/video/decode/rpi.c +++ b/video/decode/rpi.c @@ -50,9 +50,9 @@ static int init(struct lavc_ctx *ctx) } static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, - const char *decoder) + const char *codec) { - return map_codec(decoder) ? 0 : HWDEC_ERR_NO_CODEC; + return map_codec(codec) ? 0 : HWDEC_ERR_NO_CODEC; } static const char *get_codec(struct lavc_ctx *ctx, const char *codec) diff --git a/video/decode/vaapi.c b/video/decode/vaapi.c index d7aa2694aa..2682225876 100644 --- a/video/decode/vaapi.c +++ b/video/decode/vaapi.c @@ -431,12 +431,12 @@ static int init(struct lavc_ctx *ctx) } static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, - const char *decoder) + const char *codec) { hwdec_request_api(info, "vaapi"); if (!info || !info->hwctx || !info->hwctx->vaapi_ctx) return HWDEC_ERR_NO_CTX; - if (!hwdec_check_codec_support(decoder, profiles)) + if (!hwdec_check_codec_support(codec, profiles)) return HWDEC_ERR_NO_CODEC; if (va_guess_if_emulated(info->hwctx->vaapi_ctx)) return HWDEC_ERR_EMULATED; @@ -444,14 +444,14 @@ static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, } static int probe_copy(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, - const char *decoder) + const char *codec) { struct priv dummy = {mp_null_log}; if (!create_va_dummy_ctx(&dummy)) return HWDEC_ERR_NO_CTX; bool emulated = va_guess_if_emulated(dummy.ctx); destroy_va_dummy_ctx(&dummy); - if (!hwdec_check_codec_support(decoder, profiles)) + if (!hwdec_check_codec_support(codec, profiles)) return HWDEC_ERR_NO_CODEC; if (emulated) return HWDEC_ERR_EMULATED; @@ -508,4 +508,5 @@ const struct vd_lavc_hwdec mp_vd_lavc_vaapi_copy = { .init_decoder = init_decoder, .allocate_image = allocate_image, .process_image = copy_image, + .delay_queue = HWDEC_DELAY_QUEUE_COUNT, }; diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c index 0520c3a4fd..a444f88c10 100644 --- a/video/decode/vd_lavc.c +++ b/video/decode/vd_lavc.c @@ -126,7 +126,9 @@ extern const struct vd_lavc_hwdec mp_vd_lavc_vaapi; extern const struct vd_lavc_hwdec mp_vd_lavc_vaapi_copy; extern const struct vd_lavc_hwdec mp_vd_lavc_dxva2; extern const struct vd_lavc_hwdec mp_vd_lavc_dxva2_copy; +extern const struct vd_lavc_hwdec mp_vd_lavc_d3d11va_copy; extern const struct vd_lavc_hwdec mp_vd_lavc_rpi; +extern const struct vd_lavc_hwdec mp_vd_lavc_mediacodec; static const struct vd_lavc_hwdec *const hwdec_list[] = { #if HAVE_RPI @@ -145,6 +147,12 @@ static const struct vd_lavc_hwdec *const hwdec_list[] = { #if HAVE_DXVA2_HWACCEL &mp_vd_lavc_dxva2, &mp_vd_lavc_dxva2_copy, +#endif +#if HAVE_D3D11VA_HWACCEL + &mp_vd_lavc_d3d11va_copy, +#endif +#if HAVE_ANDROID + &mp_vd_lavc_mediacodec, #endif NULL }; @@ -206,12 +214,12 @@ const struct hwdec_profile_entry *hwdec_find_profile( } // Check codec support, without checking the profile. -bool hwdec_check_codec_support(const char *decoder, +bool hwdec_check_codec_support(const char *codec, const struct hwdec_profile_entry *table) { - enum AVCodecID codec = mp_codec_to_av_codec_id(decoder); + enum AVCodecID codecid = mp_codec_to_av_codec_id(codec); for (int n = 0; table[n].av_codec; n++) { - if (table[n].av_codec == codec) + if (table[n].av_codec == codecid) return true; } return false; @@ -232,17 +240,17 @@ void hwdec_request_api(struct mp_hwdec_info *info, const char *api_name) } static int hwdec_probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, - const char *decoder) + const char *codec) { int r = 0; if (hwdec->probe) - r = hwdec->probe(hwdec, info, decoder); + r = hwdec->probe(hwdec, info, codec); return r; } static struct vd_lavc_hwdec *probe_hwdec(struct dec_video *vd, bool autoprobe, enum hwdec_type api, - const char *decoder) + const char *codec) { MP_VERBOSE(vd, "Probing '%s'...\n", m_opt_choice_str(mp_hwdec_names, api)); struct vd_lavc_hwdec *hwdec = find_hwcodec(api); @@ -250,7 +258,7 @@ static struct vd_lavc_hwdec *probe_hwdec(struct dec_video *vd, bool autoprobe, MP_VERBOSE(vd, "Requested hardware decoder not compiled.\n"); return NULL; } - int r = hwdec_probe(hwdec, vd->hwdec_info, decoder); + int r = hwdec_probe(hwdec, vd->hwdec_info, codec); if (r == HWDEC_ERR_EMULATED) { if (autoprobe) return NULL; @@ -261,8 +269,8 @@ 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 for '%s' with the given API not found " + "in libavcodec.\n", codec); } else if (r == HWDEC_ERR_NO_CTX && !autoprobe) { MP_WARN(vd, "VO does not support requested hardware decoder, or " "loading it failed.\n"); @@ -293,25 +301,26 @@ static void reinit(struct dec_video *vd) { vd_ffmpeg_ctx *ctx = vd->priv; const char *decoder = ctx->decoder; + const char *codec = vd->codec->codec; uninit_avctx(vd); struct vd_lavc_hwdec *hwdec = NULL; - if (hwdec_codec_allowed(vd, decoder)) { + if (hwdec_codec_allowed(vd, codec)) { if (vd->opts->hwdec_api == HWDEC_AUTO) { for (int n = 0; hwdec_list[n]; n++) { - hwdec = probe_hwdec(vd, true, hwdec_list[n]->type, decoder); + hwdec = probe_hwdec(vd, true, hwdec_list[n]->type, codec); if (hwdec) break; } } else if (vd->opts->hwdec_api != HWDEC_NONE) { - hwdec = probe_hwdec(vd, false, vd->opts->hwdec_api, decoder); + hwdec = probe_hwdec(vd, false, vd->opts->hwdec_api, codec); } } else { MP_VERBOSE(vd, "Not trying to use hardware decoding: codec %s is not " "on whitelist, or does not support hardware acceleration.\n", - decoder); + codec); } if (hwdec) { @@ -335,17 +344,6 @@ static int init(struct dec_video *vd, const char *decoder) ctx->opts = vd->opts; ctx->decoder = talloc_strdup(ctx, decoder); - if (bstr_endswith0(bstr0(decoder), "_vdpau")) { - MP_WARN(vd, "VDPAU decoder '%s' was requested. " - "This way of enabling hardware\ndecoding is not supported " - "anymore. Use --hwdec=vdpau instead.\nThe --hwdec-codec=... " - "option can be used to restrict which codecs are\nenabled, " - "otherwise all hardware decoding is tried for all codecs.\n", - decoder); - uninit(vd); - return 0; - } - reinit(vd); if (!ctx->avctx) { @@ -377,7 +375,7 @@ static void init_avctx(struct dec_video *vd, const char *decoder, ctx->hwdec_info = vd->hwdec_info; ctx->codec_timebase = (AVRational){0}; - if (strstr(decoder, "_mmal")) + if (strstr(decoder, "_mmal") || strstr(decoder, "_mediacodec")) ctx->codec_timebase = (AVRational){1, 1000000}; ctx->pix_fmt = AV_PIX_FMT_NONE; @@ -403,10 +401,7 @@ static void init_avctx(struct dec_video *vd, const char *decoder, avctx->get_buffer2 = get_buffer2_hwdec; if (ctx->hwdec->init(ctx) < 0) goto error; - // This can increase efficiency by not blocking on the hardware - // pipeline by reading back immediately after decoding. - if (ctx->hwdec->process_image) - ctx->max_delay_queue = HWDEC_DELAY_QUEUE_COUNT; + ctx->max_delay_queue = ctx->hwdec->delay_queue; } else { mp_set_avcodec_threads(vd->log, avctx, lavc_param->threads); } @@ -447,8 +442,7 @@ static void init_avctx(struct dec_video *vd, const char *decoder, mp_imgfmt_to_name(c->codec_tag)); } - if (c->lav_headers) - mp_copy_lav_codec_headers(avctx, c->lav_headers); + mp_set_lav_codec_headers(avctx, c); /* open it */ if (avcodec_open2(avctx, lavc_codec, NULL) < 0) @@ -657,7 +651,7 @@ static void decode(struct dec_video *vd, struct demux_packet *packet, vd_ffmpeg_ctx *ctx = vd->priv; AVCodecContext *avctx = ctx->avctx; struct vd_lavc_params *opts = ctx->opts->vd_lavc_params; - AVRational *tb = ctx->codec_timebase.num ? &ctx->codec_timebase : NULL; + bool consumed = false; AVPacket pkt; if (!avctx) @@ -671,7 +665,7 @@ static void decode(struct dec_video *vd, struct demux_packet *packet, avctx->skip_frame = ctx->skip_frame; } - mp_set_av_packet(&pkt, packet, tb); + mp_set_av_packet(&pkt, packet, &ctx->codec_timebase); ctx->flushing |= !pkt.data; // Reset decoder if hw state got reset, or new data comes during flushing. @@ -679,7 +673,23 @@ static void decode(struct dec_video *vd, struct demux_packet *packet, reset_avctx(vd); hwdec_lock(ctx); +#if HAVE_AVCODEC_NEW_CODEC_API + ret = avcodec_send_packet(avctx, packet ? &pkt : NULL); + if (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + if (ret >= 0) + consumed = true; + ret = avcodec_receive_frame(avctx, ctx->pic); + if (ret >= 0) + got_picture = 1; + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + ret = 0; + } else { + consumed = true; + } +#else ret = avcodec_decode_video2(avctx, ctx->pic, &got_picture, &pkt); + consumed = true; +#endif hwdec_unlock(ctx); // Reset decoder if it was fully flushed. Caller might send more flush @@ -694,6 +704,8 @@ static void decode(struct dec_video *vd, struct demux_packet *packet, if (ctx->hwdec_fail_count >= opts->software_fallback) ctx->hwdec_failed = true; } + if (!ctx->hwdec_failed && packet) + packet->len = 0; // skip failed packet return; } @@ -702,10 +714,8 @@ static void decode(struct dec_video *vd, struct demux_packet *packet, return; } - if (packet) { - // always fully consumed + if (packet && consumed) packet->len = 0; - } // Skipped frame, or delayed output due to multithreaded decoding. if (!got_picture) { @@ -732,8 +742,8 @@ static void decode(struct dec_video *vd, struct demux_packet *packet, return; } assert(mpi->planes[0] || mpi->planes[3]); - mpi->pts = mp_pts_from_av(ctx->pic->pkt_pts, tb); - mpi->dts = mp_pts_from_av(ctx->pic->pkt_dts, tb); + mpi->pts = mp_pts_from_av(ctx->pic->pkt_pts, &ctx->codec_timebase); + mpi->dts = mp_pts_from_av(ctx->pic->pkt_dts, &ctx->codec_timebase); struct mp_image_params params; update_image_params(vd, ctx->pic, ¶ms); diff --git a/video/decode/vdpau.c b/video/decode/vdpau.c index c69465094b..313fabff76 100644 --- a/video/decode/vdpau.c +++ b/video/decode/vdpau.c @@ -84,7 +84,7 @@ static int init(struct lavc_ctx *ctx) } static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, - const char *decoder) + const char *codec) { hwdec_request_api(info, "vdpau"); if (!info || !info->hwctx || !info->hwctx->vdpau_ctx) diff --git a/video/decode/videotoolbox.c b/video/decode/videotoolbox.c index 61e3005bd5..2d2f5f735c 100644 --- a/video/decode/videotoolbox.c +++ b/video/decode/videotoolbox.c @@ -28,12 +28,12 @@ static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, - const char *decoder) + const char *codec) { hwdec_request_api(info, "videotoolbox"); - if (!info || !info->hwctx || info->hwctx->type != HWDEC_VIDEOTOOLBOX) + if (!info || !info->hwctx || !info->hwctx->get_vt_fmt) return HWDEC_ERR_NO_CTX; - switch (mp_codec_to_av_codec_id(decoder)) { + switch (mp_codec_to_av_codec_id(codec)) { case AV_CODEC_ID_H264: case AV_CODEC_ID_H263: case AV_CODEC_ID_MPEG1VIDEO: @@ -88,9 +88,11 @@ 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 = (uintptr_t)ctx->hwdec_info->hwctx->priv; - int err = av_videotoolbox_default_init2(ctx->avctx, vtctx); + struct mp_hwdec_ctx *hwctx = ctx->hwdec_info->hwctx; + vtctx->cv_pix_fmt_type = hwctx->get_vt_fmt(hwctx); + + 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; @@ -105,6 +107,15 @@ static void uninit(struct lavc_ctx *ctx) av_videotoolbox_default_free(ctx->avctx); } +static struct mp_image *process_image(struct lavc_ctx *ctx, struct mp_image *img) +{ + if (img->imgfmt == IMGFMT_VIDEOTOOLBOX) { + CVPixelBufferRef pbuf = (CVPixelBufferRef)img->planes[3]; + img->params.hw_subfmt = CVPixelBufferGetPixelFormatType(pbuf); + } + return img; +} + const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox = { .type = HWDEC_VIDEOTOOLBOX, .image_format = IMGFMT_VIDEOTOOLBOX, @@ -112,4 +123,5 @@ const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox = { .init = init, .uninit = uninit, .init_decoder = init_decoder, + .process_image = process_image, }; diff --git a/video/dxva2.c b/video/dxva2.c index e47f84e8a3..d6635ce0c6 100644 --- a/video/dxva2.c +++ b/video/dxva2.c @@ -15,8 +15,6 @@ * License along with mpv. If not, see . */ -#include - #include "common/av_common.h" #include "dxva2.h" #include "mp_image.h" @@ -28,13 +26,13 @@ struct dxva2_surface { HMODULE dxva2lib; IDirectXVideoDecoder *decoder; - LPDIRECT3DSURFACE9 surface; + IDirect3DSurface9 *surface; }; -LPDIRECT3DSURFACE9 d3d9_surface_in_mp_image(struct mp_image *mpi) +IDirect3DSurface9 *d3d9_surface_in_mp_image(struct mp_image *mpi) { return mpi && mpi->imgfmt == IMGFMT_DXVA2 ? - (LPDIRECT3DSURFACE9)mpi->planes[3] : NULL; + (IDirect3DSurface9 *)mpi->planes[3] : NULL; } static void dxva2_release_img(void *arg) @@ -56,7 +54,7 @@ static void dxva2_release_img(void *arg) } struct mp_image *dxva2_new_ref(IDirectXVideoDecoder *decoder, - LPDIRECT3DSURFACE9 d3d9_surface, int w, int h) + IDirect3DSurface9 *d3d9_surface, int w, int h) { if (!decoder || !d3d9_surface) return NULL; @@ -77,7 +75,7 @@ struct mp_image *dxva2_new_ref(IDirectXVideoDecoder *decoder, struct mp_image *mpi = mp_image_new_custom_ref(&(struct mp_image){0}, surface, dxva2_release_img); if (!mpi) - goto fail; + abort(); mp_image_setfmt(mpi, IMGFMT_DXVA2); mp_image_set_size(mpi, w, h); diff --git a/video/dxva2.h b/video/dxva2.h index b52767da44..1f2e4a4071 100644 --- a/video/dxva2.h +++ b/video/dxva2.h @@ -24,9 +24,9 @@ struct mp_image; struct mp_image_pool; -LPDIRECT3DSURFACE9 d3d9_surface_in_mp_image(struct mp_image *mpi); +IDirect3DSurface9 *d3d9_surface_in_mp_image(struct mp_image *mpi); struct mp_image *dxva2_new_ref(IDirectXVideoDecoder *decoder, - LPDIRECT3DSURFACE9 d3d9_surface, int w, int h); + IDirect3DSurface9 *d3d9_surface, int w, int h); #endif diff --git a/video/fmt-conversion.c b/video/fmt-conversion.c index c23b7bb232..53342060b1 100644 --- a/video/fmt-conversion.c +++ b/video/fmt-conversion.c @@ -109,6 +109,9 @@ static const struct { #endif {IMGFMT_VAAPI, AV_PIX_FMT_VAAPI_VLD}, {IMGFMT_DXVA2, AV_PIX_FMT_DXVA2_VLD}, +#if HAVE_D3D11VA_HWACCEL + {IMGFMT_D3D11VA, AV_PIX_FMT_D3D11VA_VLD}, +#endif #if HAVE_AV_PIX_FMT_MMAL {IMGFMT_MMAL, AV_PIX_FMT_MMAL}, #endif diff --git a/video/hwdec.h b/video/hwdec.h index 1588901442..898b035361 100644 --- a/video/hwdec.h +++ b/video/hwdec.h @@ -15,7 +15,9 @@ enum hwdec_type { HWDEC_VAAPI_COPY = 5, HWDEC_DXVA2 = 6, HWDEC_DXVA2_COPY = 7, - HWDEC_RPI = 8, + HWDEC_D3D11VA_COPY = 8, + HWDEC_RPI = 9, + HWDEC_MEDIACODEC = 10, }; // hwdec_type names (options.c) @@ -30,6 +32,7 @@ struct mp_hwdec_ctx { struct mp_vdpau_ctx *vdpau_ctx; struct mp_vaapi_ctx *vaapi_ctx; struct mp_d3d_ctx *d3d_ctx; + uint32_t (*get_vt_fmt)(struct mp_hwdec_ctx *ctx); // Optional. // Allocates a software image from the pool, downloads the hw image from diff --git a/video/img_format.c b/video/img_format.c index 82136b5192..fe2ca14bf4 100644 --- a/video/img_format.c +++ b/video/img_format.c @@ -171,6 +171,7 @@ struct mp_imgfmt_desc mp_imgfmt_get_desc(int mpfmt) shift = d.shift; if (shift != d.shift) shift = -1; + desc.components[d.plane] += 1; } for (int p = 0; p < 4; p++) { diff --git a/video/img_format.h b/video/img_format.h index b18a6f5d3f..605dc920bd 100644 --- a/video/img_format.h +++ b/video/img_format.h @@ -93,6 +93,7 @@ struct mp_imgfmt_desc { int8_t component_bits; // number of bits per component (0 if uneven) int8_t component_full_bits; // number of bits per component including // internal padding (0 if uneven) + int8_t components[MP_MAX_PLANES]; // number of components for each plane // chroma shifts per plane (provided for convenience with planar formats) int8_t xs[MP_MAX_PLANES]; int8_t ys[MP_MAX_PLANES]; @@ -194,7 +195,8 @@ enum mp_imgfmt { IMGFMT_VDPAU, // VdpVideoSurface IMGFMT_VDPAU_OUTPUT, // VdpOutputSurface IMGFMT_VAAPI, - IMGFMT_DXVA2, // IDirect3DSurface9 (NV12) + IMGFMT_D3D11VA, // ID3D11VideoDecoderOutputView (NV12/P010/P016) + IMGFMT_DXVA2, // IDirect3DSurface9 (NV12/P010/P016) IMGFMT_MMAL, // MMAL_BUFFER_HEADER_T IMGFMT_VIDEOTOOLBOX, // CVPixelBufferRef diff --git a/video/mp_image.c b/video/mp_image.c index ff81dd7666..565de18ca6 100644 --- a/video/mp_image.c +++ b/video/mp_image.c @@ -507,6 +507,8 @@ char *mp_image_params_to_str_buf(char *b, size_t bs, if (p->p_w != p->p_h || !p->p_w) mp_snprintf_cat(b, bs, " [%d:%d]", p->p_w, p->p_h); mp_snprintf_cat(b, bs, " %s", mp_imgfmt_to_name(p->imgfmt)); + if (p->hw_subfmt) + mp_snprintf_cat(b, bs, "[%llu]", (unsigned long long)(p->hw_subfmt)); mp_snprintf_cat(b, bs, " %s/%s", m_opt_choice_str(mp_csp_names, p->colorspace), m_opt_choice_str(mp_csp_levels_names, p->colorlevels)); @@ -547,6 +549,9 @@ bool mp_image_params_valid(const struct mp_image_params *p) if (!desc.id) return false; + if (p->hw_subfmt && !(desc.flags & MP_IMGFLAG_HWACCEL)) + return false; + return true; } @@ -554,6 +559,7 @@ bool mp_image_params_equal(const struct mp_image_params *p1, const struct mp_image_params *p2) { return p1->imgfmt == p2->imgfmt && + p1->hw_subfmt == p2->hw_subfmt && p1->w == p2->w && p1->h == p2->h && p1->p_w == p2->p_w && p1->p_h == p2->p_h && p1->colorspace == p2->colorspace && diff --git a/video/mp_image.h b/video/mp_image.h index af602721f3..c00c78a459 100644 --- a/video/mp_image.h +++ b/video/mp_image.h @@ -39,6 +39,8 @@ // usually copy the whole struct, so that fields added later will be preserved. struct mp_image_params { enum mp_imgfmt imgfmt; // pixel format + uint64_t hw_subfmt; // underlying format for some hwaccel pixfmts + // (will use the HW API's format identifiers) int w, h; // image dimensions int p_w, p_h; // define pixel aspect ratio (never 0/0) enum mp_csp colorspace; diff --git a/video/out/aspect.c b/video/out/aspect.c index 2d25bbdfd5..205ee3b89f 100644 --- a/video/out/aspect.c +++ b/video/out/aspect.c @@ -17,6 +17,7 @@ /* Stuff for correct aspect scaling. */ #include "aspect.h" +#include "math.h" #include "vo.h" #include "common/msg.h" #include "options/options.h" @@ -25,38 +26,36 @@ #include "vo.h" #include "sub/osd.h" -static void aspect_calc_panscan(struct mp_log *log, struct mp_vo_opts *opts, - int w, int h, int d_w, int d_h, +static void aspect_calc_panscan(struct mp_vo_opts *opts, + int w, int h, int d_w, int d_h, bool unscaled, int window_w, int window_h, double monitor_par, int *out_w, int *out_h) { - mp_dbg(log, "aspect(0) fitin: %dx%d monitor_par: %.2f\n", - window_w, window_h, monitor_par); int fwidth = window_w; int fheight = (float)window_w / d_w * d_h / monitor_par; - mp_dbg(log, "aspect(1) wh: %dx%d (org: %dx%d)\n", - fwidth, fheight, d_w, d_h); if (fheight > window_h || fheight < h) { int tmpw = (float)window_h / d_h * d_w * monitor_par; if (tmpw <= window_w) { fheight = window_h; fwidth = tmpw; - } else if (fheight > window_h) { - mp_warn(log, "No suitable new aspect found!\n"); } } - mp_dbg(log, "aspect(2) wh: %dx%d (org: %dx%d)\n", - fwidth, fheight, d_w, d_h); int vo_panscan_area = window_h - fheight; double f_w = fwidth / (double)fheight; double f_h = 1; - if (!vo_panscan_area) { + if (vo_panscan_area == 0) { vo_panscan_area = window_w - fwidth; f_w = 1; f_h = fheight / (double)fwidth; } + if (unscaled) { + fwidth = w * monitor_par; + fheight = h; + vo_panscan_area = 0; + } + *out_w = fwidth + vo_panscan_area * opts->panscan * f_w; *out_h = fheight + vo_panscan_area * opts->panscan * f_h; } @@ -73,18 +72,13 @@ static void clamp_size(int size, int *start, int *end) } static void src_dst_split_scaling(int src_size, int dst_size, - int scaled_src_size, bool unscaled, + int scaled_src_size, float zoom, float align, float pan, int *src_start, int *src_end, int *dst_start, int *dst_end, int *osd_margin_a, int *osd_margin_b) { - if (unscaled) { - scaled_src_size = src_size; - zoom = 0.0; - } - - scaled_src_size += zoom * scaled_src_size; + scaled_src_size *= powf(2, zoom); align = (align + 1) / 2; *src_start = 0; @@ -110,17 +104,6 @@ static void src_dst_split_scaling(int src_size, int dst_size, *dst_end = dst_size; } - if (unscaled) { - // Force unscaled by reducing the range for src or dst - int src_s = *src_end - *src_start; - int dst_s = *dst_end - *dst_start; - if (src_s > dst_s) { - *src_end = *src_start + dst_s; - } else if (src_s < dst_s) { - *dst_end = *dst_start + src_s; - } - } - // For sanity: avoid bothering VOs with corner cases clamp_size(src_size, src_start, src_end); clamp_size(dst_size, dst_start, dst_end); @@ -152,14 +135,14 @@ void mp_get_src_dst_rects(struct mp_log *log, struct mp_vo_opts *opts, }; if (opts->keepaspect) { int scaled_width, scaled_height; - aspect_calc_panscan(log, opts, src_w, src_h, src_dw, src_dh, + aspect_calc_panscan(opts, src_w, src_h, src_dw, src_dh, opts->unscaled, window_w, window_h, monitor_par, &scaled_width, &scaled_height); - src_dst_split_scaling(src_w, window_w, scaled_width, opts->unscaled, + src_dst_split_scaling(src_w, window_w, scaled_width, opts->zoom, opts->align_x, opts->pan_x, &src.x0, &src.x1, &dst.x0, &dst.x1, &osd.ml, &osd.mr); - src_dst_split_scaling(src_h, window_h, scaled_height, opts->unscaled, + src_dst_split_scaling(src_h, window_h, scaled_height, opts->zoom, opts->align_y, opts->pan_y, &src.y0, &src.y1, &dst.y0, &dst.y1, &osd.mt, &osd.mb); diff --git a/video/out/opengl/common.c b/video/out/opengl/common.c index f08fd4bfd9..46cbc2fc8c 100644 --- a/video/out/opengl/common.c +++ b/video/out/opengl/common.c @@ -369,8 +369,10 @@ void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n), int major = 0, minor = 0; const char *version_string = gl->GetString(GL_VERSION); - if (!version_string) + if (!version_string) { + mp_fatal(log, "glGetString(GL_VERSION) returned NULL.\n"); goto error; + } mp_verbose(log, "GL_VERSION='%s'\n", version_string); if (strncmp(version_string, "OpenGL ES ", 10) == 0) { version_string += 10; diff --git a/video/out/opengl/common.h b/video/out/opengl/common.h index 9236ce1d6f..f790dcb166 100644 --- a/video/out/opengl/common.h +++ b/video/out/opengl/common.h @@ -89,7 +89,6 @@ struct GL { int mpgl_caps; // Bitfield of MPGL_CAP_* constants bool debug_context; // use of e.g. GLX_CONTEXT_DEBUG_BIT_ARB int fb_r, fb_g, fb_b; // frame buffer bit depth (0 if unknown) - bool fb_premultiplied; // assumption about FB alpha compositor usage void (GLAPIENTRY *Viewport)(GLint, GLint, GLsizei, GLsizei); void (GLAPIENTRY *Clear)(GLbitfield); diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c index aada9df973..77e9709426 100644 --- a/video/out/opengl/context.c +++ b/video/out/opengl/context.c @@ -78,6 +78,17 @@ static const struct mpgl_driver *const backends[] = { #endif }; +// 0-terminated list of desktop GL versions a backend should try to +// initialize. The first entry is the most preferred version. +const int mpgl_preferred_gl_versions[] = { + 330, + 320, + 310, + 300, + 210, + 0 +}; + int mpgl_find_backend(const char *name) { if (name == NULL || strcmp(name, "auto") == 0) diff --git a/video/out/opengl/context.h b/video/out/opengl/context.h index cd12bb9f32..df842bc8a1 100644 --- a/video/out/opengl/context.h +++ b/video/out/opengl/context.h @@ -34,6 +34,8 @@ enum { VOFLAG_SW = 1 << 4, // Hint to accept a software GL renderer }; +extern const int mpgl_preferred_gl_versions[]; + struct MPGLContext; // A windowing backend (like X11, win32, ...), which provides OpenGL rendering. diff --git a/video/out/opengl/context_cocoa.c b/video/out/opengl/context_cocoa.c index e48472ff46..271bdb7444 100644 --- a/video/out/opengl/context_cocoa.c +++ b/video/out/opengl/context_cocoa.c @@ -124,7 +124,6 @@ static bool create_gl_context(struct MPGLContext *ctx, int vo_flags) mpgl_load_functions(ctx->gl, (void *)cocoa_glgetaddr, NULL, ctx->vo->log); ctx->gl->fb_r = ctx->gl->fb_g = ctx->gl->fb_b = cgl_color_size(ctx); - ctx->gl->fb_premultiplied = true; CGLReleasePixelFormat(p->pix); diff --git a/video/out/opengl/context_dxinterop.c b/video/out/opengl/context_dxinterop.c index d3287f1f28..4dfc3c2108 100644 --- a/video/out/opengl/context_dxinterop.c +++ b/video/out/opengl/context_dxinterop.c @@ -543,6 +543,20 @@ static int GLAPIENTRY dxinterop_swap_interval(int interval) return 1; } +static void * GLAPIENTRY dxinterop_get_native_display(const char *name) +{ + if (!current_ctx || !name) + return NULL; + struct priv *p = current_ctx->priv; + + if (p->device && strcmp("IDirect3DDevice9Ex", name) == 0) { + return p->device; + } else if (p->device_h && strcmp("dxinterop_device_HANDLE", name) == 0) { + return p->device_h; + } + return NULL; +} + static int dxinterop_init(struct MPGLContext *ctx, int flags) { struct priv *p = ctx->priv; @@ -566,6 +580,8 @@ static int dxinterop_init(struct MPGLContext *ctx, int flags) gl->SwapInterval = dxinterop_swap_interval; + gl->MPGetNativeDisplay = dxinterop_get_native_display; + if (d3d_create(ctx) < 0) goto fail; if (d3d_size_dependent_create(ctx) < 0) @@ -581,9 +597,6 @@ static int dxinterop_init(struct MPGLContext *ctx, int flags) DwmEnableMMCSS(TRUE); - ctx->native_display_type = "IDirect3DDevice9Ex"; - ctx->native_display = p->device; - return 0; fail: dxinterop_uninit(ctx); diff --git a/video/out/opengl/context_wayland.c b/video/out/opengl/context_wayland.c index 63a14539fe..a100073780 100644 --- a/video/out/opengl/context_wayland.c +++ b/video/out/opengl/context_wayland.c @@ -183,7 +183,8 @@ static void waylandgl_uninit(MPGLContext *ctx) if (wl->egl_context.egl.ctx) { eglReleaseThread(); - wl_egl_window_destroy(wl->egl_context.egl_window); + if (wl->egl_context.egl_window) + wl_egl_window_destroy(wl->egl_context.egl_window); eglDestroySurface(wl->egl_context.egl.dpy, wl->egl_context.egl_surface); eglMakeCurrent(wl->egl_context.egl.dpy, NULL, NULL, EGL_NO_CONTEXT); eglDestroyContext(wl->egl_context.egl.dpy, wl->egl_context.egl.ctx); diff --git a/video/out/opengl/context_x11.c b/video/out/opengl/context_x11.c index 6a6441cd23..d9a584e87c 100644 --- a/video/out/opengl/context_x11.c +++ b/video/out/opengl/context_x11.c @@ -125,10 +125,8 @@ static bool create_context_x11_gl3(struct MPGLContext *ctx, int vo_flags, glx_ctx->fbc, 0, True, context_attribs); vo_x11_silence_xlib(-1); - if (!context) { - MP_VERBOSE(vo, "Could not create GL3 context. Retrying with legacy context.\n"); + if (!context) return false; - } // set context if (!glXMakeCurrent(vo->x11->display, vo->x11->window, context)) { @@ -253,9 +251,18 @@ static int glx_init(struct MPGLContext *ctx, int flags) bool success = false; if (!(flags & VOFLAG_GLES)) { - success = create_context_x11_gl3(ctx, flags, 300, false); - if (!success) - success = create_context_x11_old(ctx); + for (int n = 0; mpgl_preferred_gl_versions[n]; n++) { + int version = mpgl_preferred_gl_versions[n]; + MP_VERBOSE(vo, "Creating OpenGL %d.%d context...\n", + MPGL_VER_P(version)); + if (version >= 300) { + success = create_context_x11_gl3(ctx, flags, version, false); + } else { + success = create_context_x11_old(ctx); + } + if (success) + break; + } } if (!success) // try ES success = create_context_x11_gl3(ctx, flags, 200, true); @@ -267,7 +274,6 @@ static int glx_init(struct MPGLContext *ctx, int flags) glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_RED_SIZE, &ctx->gl->fb_r); glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_GREEN_SIZE, &ctx->gl->fb_g); glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_BLUE_SIZE, &ctx->gl->fb_b); - ctx->gl->fb_premultiplied = true; return 0; diff --git a/video/out/opengl/hwdec.c b/video/out/opengl/hwdec.c index 300197ac00..b58af9bae9 100644 --- a/video/out/opengl/hwdec.c +++ b/video/out/opengl/hwdec.c @@ -28,6 +28,7 @@ extern const struct gl_hwdec_driver gl_hwdec_vaegl; extern const struct gl_hwdec_driver gl_hwdec_vaglx; 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_dxva2egl; extern const struct gl_hwdec_driver gl_hwdec_dxva2gldx; extern const struct gl_hwdec_driver gl_hwdec_dxva2; @@ -45,6 +46,9 @@ static const struct gl_hwdec_driver *const mpgl_hwdec_drivers[] = { &gl_hwdec_videotoolbox, #endif #if HAVE_DXVA2_HWACCEL +#if HAVE_EGL_ANGLE + &gl_hwdec_dxva2egl, +#endif #if HAVE_GL_DXINTEROP &gl_hwdec_dxva2gldx, #endif diff --git a/video/out/opengl/hwdec.h b/video/out/opengl/hwdec.h index c04962dd76..5126d7f0fa 100644 --- a/video/out/opengl/hwdec.h +++ b/video/out/opengl/hwdec.h @@ -39,6 +39,7 @@ struct gl_hwdec_driver { // Prepare for rendering video. (E.g. create textures.) // Called on initialization, and every time the video size changes. // *params must be set to the format the hw textures return. + // This also can update hw->converted_imgfmt. int (*reinit)(struct gl_hwdec *hw, struct mp_image_params *params); // Return textures that contain a copy or reference of the given hw_image. int (*map_image)(struct gl_hwdec *hw, struct mp_image *hw_image, diff --git a/video/out/opengl/hwdec_dxva2egl.c b/video/out/opengl/hwdec_dxva2egl.c new file mode 100644 index 0000000000..eed942618c --- /dev/null +++ b/video/out/opengl/hwdec_dxva2egl.c @@ -0,0 +1,344 @@ +/* + * 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 . + */ + +#include +#include +#include +#include + +#include "common/common.h" +#include "osdep/timer.h" +#include "osdep/windows_utils.h" +#include "hwdec.h" +#include "video/dxva2.h" +#include "video/d3d.h" +#include "video/hwdec.h" + +struct priv { + struct mp_d3d_ctx ctx; + + HMODULE d3d9_dll; + IDirect3D9Ex *d3d9ex; + IDirect3DDevice9Ex *device9ex; + IDirect3DQuery9 *query9; + IDirect3DTexture9 *texture9; + IDirect3DSurface9 *surface9; + + EGLDisplay egl_display; + EGLConfig egl_config; + EGLint alpha; + EGLSurface egl_surface; + + GLuint gl_texture; +}; + +static void destroy_textures(struct gl_hwdec *hw) +{ + struct priv *p = hw->priv; + GL *gl = hw->gl; + + gl->DeleteTextures(1, &p->gl_texture); + p->gl_texture = 0; + + if (p->egl_display && p->egl_surface) { + eglReleaseTexImage(p->egl_display, p->egl_surface, EGL_BACK_BUFFER); + eglDestroySurface(p->egl_display, p->egl_surface); + p->egl_surface = NULL; + } + + if (p->surface9) { + IDirect3DSurface9_Release(p->surface9); + p->surface9 = NULL; + } + + if (p->texture9) { + IDirect3DTexture9_Release(p->texture9); + p->texture9 = NULL; + } +} + +static void destroy(struct gl_hwdec *hw) +{ + struct priv *p = hw->priv; + + destroy_textures(hw); + + if (p->query9) + IDirect3DQuery9_Release(p->query9); + + if (p->device9ex) + IDirect3DDevice9Ex_Release(p->device9ex); + + if (p->d3d9ex) + IDirect3D9Ex_Release(p->d3d9ex); + + if (p->d3d9_dll) + FreeLibrary(p->d3d9_dll); +} + +static int create(struct gl_hwdec *hw) +{ + if (hw->hwctx) + return -1; + + EGLDisplay egl_display = eglGetCurrentDisplay(); + if (!egl_display) + return -1; + + const char *exts = eglQueryString(egl_display, EGL_EXTENSIONS); + if (!exts || + !strstr(exts, "EGL_ANGLE_d3d_share_handle_client_buffer")) { + return -1; + } + + HRESULT hr; + struct priv *p = talloc_zero(hw, struct priv); + hw->priv = p; + + p->egl_display = egl_display; + + p->d3d9_dll = LoadLibraryW(L"d3d9.dll"); + if (!p->d3d9_dll) { + MP_FATAL(hw, "Failed to load \"d3d9.dll\": %s\n", + mp_LastError_to_str()); + goto fail; + } + + HRESULT (WINAPI *Direct3DCreate9Ex)(UINT SDKVersion, IDirect3D9Ex **ppD3D); + Direct3DCreate9Ex = (void *)GetProcAddress(p->d3d9_dll, "Direct3DCreate9Ex"); + if (!Direct3DCreate9Ex) { + MP_FATAL(hw, "Direct3D 9Ex not supported\n"); + goto fail; + } + + hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &p->d3d9ex); + if (FAILED(hr)) { + MP_FATAL(hw, "Couldn't create Direct3D9Ex: %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + // We must create our own Direct3D9Ex device. ANGLE can give us the device + // it's using, but that's probably a ID3D11Device. + // (copied from chromium dxva_video_decode_accelerator_win.cc) + D3DPRESENT_PARAMETERS present_params = { + .BackBufferWidth = 1, + .BackBufferHeight = 1, + .BackBufferFormat = D3DFMT_UNKNOWN, + .BackBufferCount = 1, + .SwapEffect = D3DSWAPEFFECT_DISCARD, + .hDeviceWindow = NULL, + .Windowed = TRUE, + .Flags = D3DPRESENTFLAG_VIDEO, + .FullScreen_RefreshRateInHz = 0, + .PresentationInterval = 0, + }; + hr = IDirect3D9Ex_CreateDeviceEx(p->d3d9ex, + D3DADAPTER_DEFAULT, + D3DDEVTYPE_HAL, + NULL, + D3DCREATE_FPU_PRESERVE | + D3DCREATE_HARDWARE_VERTEXPROCESSING | + D3DCREATE_DISABLE_PSGP_THREADING | + D3DCREATE_MULTITHREADED, + &present_params, + NULL, + &p->device9ex); + if (FAILED(hr)) { + MP_FATAL(hw, "Failed to create Direct3D9Ex device: %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + hr = IDirect3DDevice9_CreateQuery(p->device9ex, D3DQUERYTYPE_EVENT, + &p->query9); + if (FAILED(hr)) { + MP_FATAL(hw, "Failed to create Direct3D query interface: %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + // Test the query API + hr = IDirect3DQuery9_Issue(p->query9, D3DISSUE_END); + if (FAILED(hr)) { + MP_FATAL(hw, "Failed to issue Direct3D END test query: %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + EGLint attrs[] = { + EGL_BUFFER_SIZE, 32, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_ALPHA_SIZE, 0, + EGL_NONE + }; + EGLint count; + if (!eglChooseConfig(p->egl_display, attrs, &p->egl_config, 1, &count) || + !count) { + MP_ERR(hw, "Failed to get EGL surface configuration\n"); + goto fail; + } + + if (!eglGetConfigAttrib(p->egl_display, p->egl_config, + EGL_BIND_TO_TEXTURE_RGBA, &p->alpha)) { + MP_FATAL(hw, "Failed to query EGL surface alpha\n"); + goto fail; + } + + hw->converted_imgfmt = IMGFMT_RGB0; + + p->ctx.d3d9_device = (IDirect3DDevice9 *)p->device9ex; + p->ctx.hwctx.type = HWDEC_DXVA2; + p->ctx.hwctx.d3d_ctx = &p->ctx; + + hw->hwctx = &p->ctx.hwctx; + return 0; +fail: + destroy(hw); + return -1; +} + +static int reinit(struct gl_hwdec *hw, struct mp_image_params *params) +{ + struct priv *p = hw->priv; + GL *gl = hw->gl; + HRESULT hr; + + destroy_textures(hw); + + assert(params->imgfmt == hw->driver->imgfmt); + + HANDLE share_handle = NULL; + hr = IDirect3DDevice9Ex_CreateTexture(p->device9ex, + params->w, params->h, + 1, D3DUSAGE_RENDERTARGET, + p->alpha ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8, + D3DPOOL_DEFAULT, + &p->texture9, + &share_handle); + if (FAILED(hr)) { + MP_ERR(hw, "Failed to create Direct3D9 texture: %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + hr = IDirect3DTexture9_GetSurfaceLevel(p->texture9, 0, &p->surface9); + if (FAILED(hr)) { + MP_ERR(hw, "Failed to get Direct3D9 surface from texture: %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + EGLint attrib_list[] = { + EGL_WIDTH, params->w, + EGL_HEIGHT, params->h, + EGL_TEXTURE_FORMAT, p->alpha ? EGL_TEXTURE_RGBA : EGL_TEXTURE_RGB, + EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, + EGL_NONE + }; + p->egl_surface = eglCreatePbufferFromClientBuffer( + p->egl_display, EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE, + share_handle, p->egl_config, attrib_list); + if (p->egl_surface == EGL_NO_SURFACE) { + MP_ERR(hw, "Failed to create EGL surface\n"); + goto fail; + } + + gl->GenTextures(1, &p->gl_texture); + gl->BindTexture(GL_TEXTURE_2D, p->gl_texture); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + 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); + gl->BindTexture(GL_TEXTURE_2D, 0); + + return 0; +fail: + destroy_textures(hw); + return -1; +} + +static int map_image(struct gl_hwdec *hw, struct mp_image *hw_image, + GLuint *out_textures) +{ + struct priv *p = hw->priv; + GL *gl = hw->gl; + if (!p->surface9 || !p->egl_surface || !p->gl_texture) + return -1; + + HRESULT hr; + RECT rc = {0, 0, hw_image->w, hw_image->h}; + IDirect3DSurface9* hw_surface = d3d9_surface_in_mp_image(hw_image); + hr = IDirect3DDevice9Ex_StretchRect(p->device9ex, + hw_surface, &rc, + p->surface9, &rc, + D3DTEXF_NONE); + if (FAILED(hr)) { + MP_ERR(hw, "Direct3D RGB conversion failed: %s\n", + mp_HRESULT_to_str(hr)); + return -1; + } + + hr = IDirect3DQuery9_Issue(p->query9, D3DISSUE_END); + if (FAILED(hr)) { + MP_ERR(hw, "Failed to issue Direct3D END query\n"); + return -1; + } + + // There doesn't appear to be an efficient way to do a blocking flush + // of the above StretchRect. Timeout of 8ms is required to reliably + // render 4k on Intel Haswell, Ivybridge and Cherry Trail Atom. + const int max_retries = 8; + const int64_t wait_us = 1000; + int retries = 0; + while (true) { + hr = IDirect3DQuery9_GetData(p->query9, NULL, 0, D3DGETDATA_FLUSH); + if (FAILED(hr)) { + MP_ERR(hw, "Failed to query Direct3D flush state\n"); + return -1; + } else if (hr == S_FALSE) { + if (++retries > max_retries) { + MP_VERBOSE(hw, "Failed to flush frame after %lld ms\n", + (long long)(wait_us * max_retries) / 1000); + break; + } + mp_sleep_us(wait_us); + } else { + break; + } + } + + gl->BindTexture(GL_TEXTURE_2D, p->gl_texture); + eglBindTexImage(p->egl_display, p->egl_surface, EGL_BACK_BUFFER); + gl->BindTexture(GL_TEXTURE_2D, 0); + + out_textures[0] = p->gl_texture; + return 0; +} + +const struct gl_hwdec_driver gl_hwdec_dxva2egl = { + .name = "dxva2-egl", + .api = HWDEC_DXVA2, + .imgfmt = IMGFMT_DXVA2, + .create = create, + .reinit = reinit, + .map_image = map_image, + .destroy = destroy, +}; diff --git a/video/out/opengl/hwdec_dxva2gldx.c b/video/out/opengl/hwdec_dxva2gldx.c index 9e193fc3a5..69be0ccd18 100644 --- a/video/out/opengl/hwdec_dxva2gldx.c +++ b/video/out/opengl/hwdec_dxva2gldx.c @@ -72,13 +72,8 @@ static void destroy_objects(struct gl_hwdec *hw) static void destroy(struct gl_hwdec *hw) { struct priv *p = hw->priv; - GL *gl = hw->gl; destroy_objects(hw); - if (!gl->DXCloseDeviceNV(p->device_h)) - MP_ERR(hw, "Failed to close Direct3D device in OpenGL %s\n", - mp_LastError_to_str()); - if (p->device) IDirect3DDevice9Ex_Release(p->device); } @@ -94,28 +89,25 @@ static int create(struct gl_hwdec *hw) struct priv *p = talloc_zero(hw, struct priv); hw->priv = p; + // AMD drivers won't open multiple dxinterop HANDLES on the same D3D device, + // so we request the one already in use by context_dxinterop + p->device_h = gl->MPGetNativeDisplay("dxinterop_device_HANDLE"); + if (!p->device_h) + return -1; + + // But we also still need the actual D3D device p->device = gl->MPGetNativeDisplay("IDirect3DDevice9Ex"); if (!p->device) return -1; IDirect3DDevice9Ex_AddRef(p->device); p->ctx.d3d9_device = (IDirect3DDevice9 *)p->device; - p->device_h = gl->DXOpenDeviceNV(p->device); - if (!p->device_h) { - MP_ERR(hw, "Failed to open Direct3D device in OpenGL: %s\n", - mp_LastError_to_str()); - goto fail; - } - p->ctx.hwctx.type = HWDEC_DXVA2; p->ctx.hwctx.d3d_ctx = &p->ctx; hw->hwctx = &p->ctx.hwctx; hw->converted_imgfmt = SHARED_SURFACE_MPFMT; return 0; -fail: - destroy(hw); - return -1; } static int reinit(struct gl_hwdec *hw, struct mp_image_params *params) diff --git a/video/out/opengl/hwdec_osx.c b/video/out/opengl/hwdec_osx.c index 78d01b3894..addc16f404 100644 --- a/video/out/opengl/hwdec_osx.c +++ b/video/out/opengl/hwdec_osx.c @@ -101,16 +101,15 @@ static struct mp_image *download_image(struct mp_hwdec_ctx *ctx, if (hw_image->imgfmt != IMGFMT_VIDEOTOOLBOX) return NULL; + struct mp_image *image = NULL; CVPixelBufferRef pbuf = (CVPixelBufferRef)hw_image->planes[3]; - CVPixelBufferLockBaseAddress(pbuf, 0); + CVPixelBufferLockBaseAddress(pbuf, kCVPixelBufferLock_ReadOnly); size_t width = CVPixelBufferGetWidth(pbuf); size_t height = CVPixelBufferGetHeight(pbuf); uint32_t cvpixfmt = CVPixelBufferGetPixelFormatType(pbuf); struct vt_format *f = vt_get_gl_format(cvpixfmt); - if (!f) { - CVPixelBufferUnlockBaseAddress(pbuf, 0); - return NULL; - } + if (!f) + goto unlock; struct mp_image img = {0}; mp_image_setfmt(&img, f->imgfmt); @@ -125,8 +124,10 @@ static struct mp_image *download_image(struct mp_hwdec_ctx *ctx, mp_image_copy_attributes(&img, hw_image); - struct mp_image *image = mp_image_pool_new_copy(swpool, &img); - CVPixelBufferUnlockBaseAddress(pbuf, 0); + image = mp_image_pool_new_copy(swpool, &img); + +unlock: + CVPixelBufferUnlockBaseAddress(pbuf, kCVPixelBufferLock_ReadOnly); return image; } @@ -146,14 +147,21 @@ static bool check_hwdec(struct gl_hwdec *hw) return true; } +static uint32_t get_vt_fmt(struct mp_hwdec_ctx *ctx) +{ + struct gl_hwdec *hw = ctx->priv; + struct vt_format *f = + vt_get_gl_format_from_imgfmt(hw->global->opts->videotoolbox_format); + return f ? f->cvpixfmt : (uint32_t)-1; +} + static int create(struct gl_hwdec *hw) { if (!check_hwdec(hw)) return -1; struct priv *p = talloc_zero(hw, struct priv); - struct vt_format *f = - vt_get_gl_format_from_imgfmt(hw->global->opts->videotoolbox_format); + struct vt_format *f = vt_get_gl_format_from_imgfmt(IMGFMT_NV12); if (!f) return -1; @@ -162,17 +170,26 @@ static int create(struct gl_hwdec *hw) hw->hwctx = &p->hwctx; hw->hwctx->download_image = download_image; hw->hwctx->type = HWDEC_VIDEOTOOLBOX; - hw->hwctx->priv = (void *)(uintptr_t)f->cvpixfmt; + hw->hwctx->get_vt_fmt = get_vt_fmt; hw->gl_texture_target = GL_TEXTURE_RECTANGLE; hw->gl->GenTextures(MP_MAX_PLANES, p->gl_planes); + hw->hwctx->priv = hw; return 0; } static int reinit(struct gl_hwdec *hw, struct mp_image_params *params) { assert(params->imgfmt == hw->driver->imgfmt); + + struct vt_format *f = vt_get_gl_format(params->hw_subfmt); + if (!f) { + MP_ERR(hw, "Unsupported CVPixelBuffer format.\n"); + return -1; + } + + hw->converted_imgfmt = f->imgfmt; return 0; } diff --git a/video/out/opengl/lcms.c b/video/out/opengl/lcms.c index c956127a75..7db8da6f70 100644 --- a/video/out/opengl/lcms.c +++ b/video/out/opengl/lcms.c @@ -44,6 +44,8 @@ struct gl_lcms { size_t icc_size; char *icc_path; bool changed; + enum mp_csp_prim prev_prim; + enum mp_csp_trc prev_trc; struct mp_log *log; struct mpv_global *global; @@ -166,16 +168,99 @@ void gl_lcms_set_memory_profile(struct gl_lcms *p, bstr *profile) p->icc_size = profile->len; } -// Return and _reset_ whether the lookul table has changed since the last call. -// If it has changed, gl_lcms_get_lut3d() should be called. -bool gl_lcms_has_changed(struct gl_lcms *p) +// Return and _reset_ whether the profile or config has changed since the last +// call. If it has changed, gl_lcms_get_lut3d() should be called. +bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, + enum mp_csp_trc trc) { - bool change = p->changed; + bool change = p->changed || p->prev_prim != prim || p->prev_trc != trc; p->changed = false; + p->prev_prim = prim; + p->prev_trc = trc; return change; } -bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d) +static cmsHPROFILE get_vid_profile(cmsContext cms, cmsHPROFILE disp_profile, + enum mp_csp_prim prim, enum mp_csp_trc trc) +{ + // The input profile for the transformation is dependent on the video + // primaries and transfer characteristics + struct mp_csp_primaries csp = mp_get_csp_primaries(prim); + cmsCIExyY wp_xyY = {csp.white.x, csp.white.y, 1.0}; + cmsCIExyYTRIPLE prim_xyY = { + .Red = {csp.red.x, csp.red.y, 1.0}, + .Green = {csp.green.x, csp.green.y, 1.0}, + .Blue = {csp.blue.x, csp.blue.y, 1.0}, + }; + + cmsToneCurve *tonecurve[3] = {0}; + switch (trc) { + case MP_CSP_TRC_LINEAR: tonecurve[0] = cmsBuildGamma(cms, 1.0); break; + case MP_CSP_TRC_GAMMA18: tonecurve[0] = cmsBuildGamma(cms, 1.8); break; + case MP_CSP_TRC_GAMMA22: tonecurve[0] = cmsBuildGamma(cms, 2.2); break; + case MP_CSP_TRC_GAMMA28: tonecurve[0] = cmsBuildGamma(cms, 2.8); break; + + case MP_CSP_TRC_SRGB: + // Values copied from Little-CMS + tonecurve[0] = cmsBuildParametricToneCurve(cms, 4, + (double[5]){2.40, 1/1.055, 0.055/1.055, 1/12.92, 0.04045}); + break; + + case MP_CSP_TRC_PRO_PHOTO: + tonecurve[0] = cmsBuildParametricToneCurve(cms, 4, + (double[5]){1.8, 1.0, 0.0, 1/16.0, 0.03125}); + break; + + case MP_CSP_TRC_BT_1886: { + // To build an appropriate BT.1886 transformation we need access to + // the display's black point, so we use the reverse mappings + cmsToneCurve *linear = cmsBuildGamma(cms, 1.0); + cmsHPROFILE rev_profile = cmsCreateRGBProfileTHR(cms, &wp_xyY, &prim_xyY, + (cmsToneCurve*[3]){linear, linear, linear}); + cmsHTRANSFORM disp2src = cmsCreateTransformTHR(cms, + disp_profile, TYPE_RGB_16, rev_profile, TYPE_RGB_DBL, + INTENT_RELATIVE_COLORIMETRIC, 0); + cmsFreeToneCurve(linear); + cmsCloseProfile(rev_profile); + if (!disp2src) + return false; + + uint64_t disp_black[3] = {0}; + double src_black[3]; + cmsDoTransform(disp2src, disp_black, src_black, 1); + + // Build the parametric BT.1886 transfer curve, one per channel + for (int i = 0; i < 3; i++) { + const double gamma = 2.40; + double binv = pow(src_black[i], 1.0/gamma); + tonecurve[i] = cmsBuildParametricToneCurve(cms, 6, + (double[4]){gamma, 1.0 - binv, binv, 0.0}); + } + break; + } + + default: + abort(); + } + + if (!tonecurve[0]) + return false; + + if (!tonecurve[1]) tonecurve[1] = tonecurve[0]; + if (!tonecurve[2]) tonecurve[2] = tonecurve[0]; + + cmsHPROFILE *vid_profile = cmsCreateRGBProfileTHR(cms, &wp_xyY, &prim_xyY, + tonecurve); + + if (tonecurve[2] != tonecurve[0]) cmsFreeToneCurve(tonecurve[2]); + if (tonecurve[1] != tonecurve[0]) cmsFreeToneCurve(tonecurve[1]); + cmsFreeToneCurve(tonecurve[0]); + + return vid_profile; +} + +bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, + enum mp_csp_prim prim, enum mp_csp_trc trc) { int s_r, s_g, s_b; bool result = false; @@ -197,8 +282,8 @@ bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d) // because we may change the parameter in the future or make it // customizable, same for the primaries. 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); + "ver=1.3, intent=%d, size=%dx%dx%d, prim=%d, trc=%d\n", + p->opts.intent, s_r, s_g, s_b, prim, trc); uint8_t hash[32]; struct AVSHA *sha = av_sha_alloc(); @@ -242,23 +327,12 @@ bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d) if (!profile) goto error_exit; - // We always generate the 3DLUT against BT.2020, and transform into this - // space inside the shader if the source differs. - struct mp_csp_primaries csp = mp_get_csp_primaries(MP_CSP_PRIM_BT_2020); + cmsHPROFILE vid_profile = get_vid_profile(cms, profile, prim, trc); + if (!vid_profile) { + cmsCloseProfile(profile); + goto error_exit; + } - cmsCIExyY wp = {csp.white.x, csp.white.y, 1.0}; - cmsCIExyYTRIPLE prim = { - .Red = {csp.red.x, csp.red.y, 1.0}, - .Green = {csp.green.x, csp.green.y, 1.0}, - .Blue = {csp.blue.x, csp.blue.y, 1.0}, - }; - - // 2.4 is arbitrarily used as a gamma compression factor for the 3DLUT, - // reducing artifacts due to rounding errors on wide gamut profiles - cmsToneCurve *tonecurve = cmsBuildGamma(cms, 2.4); - cmsHPROFILE vid_profile = cmsCreateRGBProfileTHR(cms, &wp, &prim, - (cmsToneCurve*[3]){tonecurve, tonecurve, tonecurve}); - cmsFreeToneCurve(tonecurve); cmsHTRANSFORM trafo = cmsCreateTransformTHR(cms, vid_profile, TYPE_RGB_16, profile, TYPE_RGB_16, p->opts.intent, @@ -333,7 +407,17 @@ struct gl_lcms *gl_lcms_init(void *talloc_ctx, struct mp_log *log, void gl_lcms_set_options(struct gl_lcms *p, struct mp_icc_opts *opts) { } void gl_lcms_set_memory_profile(struct gl_lcms *p, bstr *profile) { } -bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **x) { return false; } -bool gl_lcms_has_changed(struct gl_lcms *p) { return false; } + +bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, + enum mp_csp_trc trc) +{ + return false; +} + +bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, + enum mp_csp_prim prim, enum mp_csp_trc trc) +{ + return false; +} #endif diff --git a/video/out/opengl/lcms.h b/video/out/opengl/lcms.h index 5ad08b7d64..ee2a48b59c 100644 --- a/video/out/opengl/lcms.h +++ b/video/out/opengl/lcms.h @@ -24,7 +24,9 @@ struct gl_lcms *gl_lcms_init(void *talloc_ctx, struct mp_log *log, struct mpv_global *global); void gl_lcms_set_options(struct gl_lcms *p, struct mp_icc_opts *opts); void gl_lcms_set_memory_profile(struct gl_lcms *p, bstr *profile); -bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **); -bool gl_lcms_has_changed(struct gl_lcms *p); +bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **, + enum mp_csp_prim prim, enum mp_csp_trc trc); +bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, + enum mp_csp_trc trc); #endif diff --git a/video/out/opengl/nnedi3.c b/video/out/opengl/nnedi3.c index c07731611a..3c12fcc623 100644 --- a/video/out/opengl/nnedi3.c +++ b/video/out/opengl/nnedi3.c @@ -98,7 +98,7 @@ const float* get_nnedi3_weights(const struct nnedi3_opts *conf, int *size) void pass_nnedi3(GL *gl, struct gl_shader_cache *sc, int planes, int tex_num, int step, float tex_mul, const struct nnedi3_opts *conf, - struct gl_transform *transform) + struct gl_transform *transform, GLenum tex_target) { assert(0 <= step && step < 2); @@ -112,8 +112,8 @@ void pass_nnedi3(GL *gl, struct gl_shader_cache *sc, int planes, int tex_num, const int offset = nnedi3_weight_offsets[conf->window * 4 + conf->neurons]; const uint32_t *weights = (const int*)(nnedi3_weights + offset * 4); - GLSLF("// nnedi3 (tex %d, step %d, neurons %d, window %dx%d, mode %d)\n", - tex_num, step + 1, neurons, width, height, conf->upload); + GLSLF("// nnedi3 (step %d, neurons %d, window %dx%d, mode %d)\n", + step, neurons, width, height, conf->upload); // This is required since each row will be encoded into vec4s assert(width % 4 == 0); @@ -131,7 +131,7 @@ void pass_nnedi3(GL *gl, struct gl_shader_cache *sc, int planes, int tex_num, GLSLH(#pragma optionNV(fastprecision on)) } - GLSLHF("float nnedi3(sampler2D tex, vec2 pos, vec2 tex_size, vec2 pixel_size, int plane, float tex_mul) {\n"); + GLSLHF("float nnedi3(%s tex, vec2 pos, vec2 tex_size, vec2 pixel_size, int plane, float tex_mul) {\n", mp_sampler_type(tex_target)); if (step == 0) { *transform = (struct gl_transform){{{1.0,0.0}, {0.0,2.0}}, {0.0,-0.5}}; @@ -246,7 +246,7 @@ const float* get_nnedi3_weights(const struct nnedi3_opts *conf, int *size) void pass_nnedi3(GL *gl, struct gl_shader_cache *sc, int planes, int tex_num, int step, float tex_mul, const struct nnedi3_opts *conf, - struct gl_transform *transform) + struct gl_transform *transform, GLenum tex_target) { } diff --git a/video/out/opengl/nnedi3.h b/video/out/opengl/nnedi3.h index b75094a7e4..c3895a0773 100644 --- a/video/out/opengl/nnedi3.h +++ b/video/out/opengl/nnedi3.h @@ -40,6 +40,6 @@ const float* get_nnedi3_weights(const struct nnedi3_opts *conf, int *size); void pass_nnedi3(GL *gl, struct gl_shader_cache *sc, int planes, int tex_num, int step, float tex_mul, const struct nnedi3_opts *conf, - struct gl_transform *transform); + struct gl_transform *transform, GLenum tex_target); #endif diff --git a/video/out/opengl/osd.c b/video/out/opengl/osd.c index 215c83cbe7..c554425e0f 100644 --- a/video/out/opengl/osd.c +++ b/video/out/opengl/osd.c @@ -92,7 +92,7 @@ struct mpgl_osd { int64_t change_counter; // temporary int stereo_mode; - int display_size[2]; + struct mp_osd_res osd_res; void *scratch; }; @@ -382,8 +382,8 @@ void mpgl_osd_draw_part(struct mpgl_osd *ctx, int vp_w, int vp_h, int index) struct gl_transform t; gl_transform_ortho(&t, 0, vp_w, 0, vp_h); - float a_x = ctx->display_size[0] * x; - float a_y = ctx->display_size[1] * y; + float a_x = ctx->osd_res.w * x; + float a_y = ctx->osd_res.h * y; t.t[0] += a_x * t.m[0][0] + a_y * t.m[1][0]; t.t[1] += a_x * t.m[0][1] + a_y * t.m[1][1]; @@ -403,20 +403,25 @@ struct gl_vao *mpgl_osd_get_vao(struct mpgl_osd *ctx) return &ctx->vao; } +static void set_res(struct mpgl_osd *ctx, struct mp_osd_res res, int stereo_mode) +{ + int div[2]; + get_3d_side_by_side(stereo_mode, div); + + res.w /= div[0]; + res.h /= div[1]; + ctx->osd_res = res; +} + void mpgl_osd_generate(struct mpgl_osd *ctx, struct mp_osd_res res, double pts, int stereo_mode, int draw_flags) { for (int n = 0; n < MAX_OSD_PARTS; n++) ctx->parts[n]->num_subparts = 0; - int div[2]; - get_3d_side_by_side(stereo_mode, div); + set_res(ctx, res, stereo_mode); - struct mp_osd_res s_res = res; - ctx->display_size[0] = s_res.w = s_res.w / div[0]; - ctx->display_size[1] = s_res.h = s_res.h / div[1]; - - osd_draw(ctx->osd, s_res, pts, draw_flags, ctx->formats, gen_osd_cb, ctx); + osd_draw(ctx->osd, ctx->osd_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 @@ -429,6 +434,13 @@ void mpgl_osd_generate(struct mpgl_osd *ctx, struct mp_osd_res res, double pts, } } +// See osd_resize() for remarks. This function is an optional optimization too. +void mpgl_osd_resize(struct mpgl_osd *ctx, struct mp_osd_res res, int stereo_mode) +{ + set_res(ctx, res, stereo_mode); + osd_resize(ctx->osd, ctx->osd_res); +} + int64_t mpgl_get_change_counter(struct mpgl_osd *ctx) { return ctx->change_counter; diff --git a/video/out/opengl/osd.h b/video/out/opengl/osd.h index bd47f71764..34cbf2b383 100644 --- a/video/out/opengl/osd.h +++ b/video/out/opengl/osd.h @@ -14,6 +14,7 @@ void mpgl_osd_set_options(struct mpgl_osd *ctx, bool pbo); void mpgl_osd_generate(struct mpgl_osd *ctx, struct mp_osd_res res, double pts, int stereo_mode, int draw_flags); +void mpgl_osd_resize(struct mpgl_osd *ctx, struct mp_osd_res res, int stereo_mode); 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); diff --git a/video/out/opengl/superxbr.c b/video/out/opengl/superxbr.c index 8039e6e01d..323ed18513 100644 --- a/video/out/opengl/superxbr.c +++ b/video/out/opengl/superxbr.c @@ -31,7 +31,7 @@ struct superxbr_opts { const struct superxbr_opts superxbr_opts_def = { .sharpness = 1.0f, - .edge_strength = 1.0f, + .edge_strength = 0.6f, }; #define OPT_BASE_STRUCT struct superxbr_opts @@ -71,73 +71,144 @@ const struct m_sub_options superxbr_conf = { */ -void pass_superxbr(struct gl_shader_cache *sc, int planes, int tex_num, - int step, float tex_mul, const struct superxbr_opts *conf, +struct step_params { + const float dstr, ostr; // sharpness strength modifiers + const int d1[3][3]; // 1-distance diagonal mask + const int d2[2][2]; // 2-distance diagonal mask + const int o1[3]; // 1-distance orthogonal mask + const int o2[3]; // 2-distance orthogonal mask +}; + +const struct step_params params[3] = { + { .dstr = 0.129633, + .ostr = 0.175068, + .d1 = {{0, 1, 0}, + {1, 2, 1}, + {0, 1, 0}}, + .d2 = {{-1, 0}, + { 0, -1}}, + + .o1 = {1, 2, 1}, + .o2 = { 0, 0}, + }, { + .dstr = 0.175068, + .ostr = 0.129633, + .d1 = {{0, 1, 0}, + {1, 4, 1}, + {0, 1, 0}}, + .d2 = {{ 0, 0}, + { 0, 0}}, + + .o1 = {1, 4, 1}, + .o2 = { 0, 0}, + } +}; + +// Compute a single step of the superxbr process, assuming the input can be +// sampled using i(x,y). Dumps its output into 'res' +static void superxbr_step_h(struct gl_shader_cache *sc, + const struct superxbr_opts *conf, + const struct step_params *mask) +{ + GLSLHF("{ // step\n"); + + // Convolute along the diagonal and orthogonal lines + GLSLH(vec4 d1 = vec4( i(0,0), i(1,1), i(2,2), i(3,3) );) + GLSLH(vec4 d2 = vec4( i(0,3), i(1,2), i(2,1), i(3,0) );) + GLSLH(vec4 h1 = vec4( i(0,1), i(1,1), i(2,1), i(3,1) );) + GLSLH(vec4 h2 = vec4( i(0,2), i(1,2), i(2,2), i(3,2) );) + GLSLH(vec4 v1 = vec4( i(1,0), i(1,1), i(1,2), i(1,3) );) + GLSLH(vec4 v2 = vec4( i(2,0), i(2,1), i(2,2), i(2,3) );) + + GLSLHF("float dw = %f;\n", conf->sharpness * mask->dstr); + GLSLHF("float ow = %f;\n", conf->sharpness * mask->ostr); + GLSLH(vec4 dk = vec4(-dw, dw+0.5, dw+0.5, -dw);) // diagonal kernel + GLSLH(vec4 ok = vec4(-ow, ow+0.5, ow+0.5, -ow);) // ortho kernel + + // Convoluted results + GLSLH(float d1c = dot(d1, dk);) + GLSLH(float d2c = dot(d2, dk);) + GLSLH(float vc = dot(v1+v2, ok)/2.0;) + GLSLH(float hc = dot(h1+h2, ok)/2.0;) + + // Compute diagonal edge strength using diagonal mask + GLSLH(float d_edge = 0;) + for (int x = 0; x < 3; x++) { + for (int y = 0; y < 3; y++) { + if (mask->d1[x][y]) { + // 1-distance diagonal neighbours + GLSLHF("d_edge += %d * abs(i(%d,%d) - i(%d,%d));\n", + mask->d1[x][y], x+1, y, x, y+1); + GLSLHF("d_edge -= %d * abs(i(%d,%d) - i(%d,%d));\n", + mask->d1[x][y], 3-y, x+1, 3-(y+1), x); // rotated + } + if (x < 2 && y < 2 && mask->d2[x][y]) { + // 2-distance diagonal neighbours + GLSLHF("d_edge += %d * abs(i(%d,%d) - i(%d,%d));\n", + mask->d2[x][y], x+2, y, x, y+2); + GLSLHF("d_edge -= %d * abs(i(%d,%d) - i(%d,%d));\n", + mask->d2[x][y], 3-y, x+2, 3-(y+2), x); // rotated + } + } + } + + // Compute orthogonal edge strength using orthogonal mask + GLSLH(float o_edge = 0;) + for (int x = 1; x < 3; x++) { + for (int y = 0; y < 3; y++) { + if (mask->o1[y]) { + // 1-distance neighbours + GLSLHF("o_edge += %d * abs(i(%d,%d) - i(%d,%d));\n", + mask->o1[y], x, y, x, y+1); // vertical + GLSLHF("o_edge -= %d * abs(i(%d,%d) - i(%d,%d));\n", + mask->o1[y], y, x, y+1, x); // horizontal + } + if (y < 2 && mask->o2[y]) { + // 2-distance neighbours + GLSLHF("o_edge += %d * abs(i(%d,%d) - i(%d,%d));\n", + mask->o2[y], x, y, x, y+2); // vertical + GLSLHF("o_edge -= %d * abs(i(%d,%d) - i(%d,%d));\n", + mask->o2[x], y, x, y+2, x); // horizontal + } + } + } + + // Pick the two best directions and mix them together + GLSLHF("float str = smoothstep(0.0, %f + 1e-6, abs(tex_mul*d_edge));\n", + conf->edge_strength); + GLSLH(res = mix(mix(d2c, d1c, step(0.0, d_edge)), \ + mix(hc, vc, step(0.0, o_edge)), 1.0 - str);) + + // Anti-ringing using center square + GLSLH(float lo = min(min( i(1,1), i(2,1) ), min( i(1,2), i(2,2) ));) + GLSLH(float hi = max(max( i(1,1), i(2,1) ), max( i(1,2), i(2,2) ));) + GLSLH(res = clamp(res, lo, hi);) + + GLSLHF("} // step\n"); +} + +void pass_superxbr(struct gl_shader_cache *sc, int id, int step, float tex_mul, + const struct superxbr_opts *conf, struct gl_transform *transform) { - assert(0 <= step && step < 2); - GLSLF("// superxbr (tex %d, step %d)\n", tex_num, step + 1); - if (!conf) conf = &superxbr_opts_def; + assert(0 <= step && step < 2); + GLSLF("// superxbr (step %d)\n", step); + GLSLHF("#define tex texture%d\n", id); + GLSLHF("#define tex_size texture_size%d\n", id); + GLSLHF("#define tex_mul %f\n", tex_mul); + GLSLHF("#define pt pixel_size%d\n", id); + + // We use a sub-function in the header so we can return early + GLSLHF("float superxbr(vec2 pos) {\n"); + GLSLH(float i[4*4];) + GLSLH(float res;) + GLSLH(#define i(x,y) i[(x)*4+(y)]) + if (step == 0) { *transform = (struct gl_transform){{{2.0,0.0}, {0.0,2.0}}, {-0.5,-0.5}}; - - GLSLH(#define wp1 2.0) - GLSLH(#define wp2 1.0) - GLSLH(#define wp3 -1.0) - GLSLH(#define wp4 4.0) - GLSLH(#define wp5 -1.0) - GLSLH(#define wp6 1.0) - - GLSLHF("#define weight1 (%f*1.29633/10.0)\n", conf->sharpness); - GLSLHF("#define weight2 (%f*1.75068/10.0/2.0)\n", conf->sharpness); - - GLSLH(#define Get(x, y) (texture(tex, pos + (vec2(x, y) - vec2(0.25, 0.25)) * pixel_size)[plane] * tex_mul)) - } else { - *transform = (struct gl_transform){{{1.0,0.0}, {0.0,1.0}}, {0.0,0.0}}; - - GLSLH(#define wp1 2.0) - GLSLH(#define wp2 0.0) - GLSLH(#define wp3 0.0) - GLSLH(#define wp4 0.0) - GLSLH(#define wp5 0.0) - GLSLH(#define wp6 0.0) - - GLSLHF("#define weight1 (%f*1.75068/10.0)\n", conf->sharpness); - GLSLHF("#define weight2 (%f*1.29633/10.0/2.0)\n", conf->sharpness); - - GLSLH(#define Get(x, y) (texture(tex, pos + (vec2((x) + (y) - 1, (y) - (x))) * pixel_size)[plane] * tex_mul)) - } - GLSLH(float df(float A, float B) - { - return abs(A-B); - }) - - GLSLH(float d_wd(float b0, float b1, float c0, float c1, float c2, - float d0, float d1, float d2, float d3, float e1, - float e2, float e3, float f2, float f3) - { - return (wp1*(df(c1,c2) + df(c1,c0) + df(e2,e1) + df(e2,e3)) + - wp2*(df(d2,d3) + df(d0,d1)) + - wp3*(df(d1,d3) + df(d0,d2)) + - wp4*df(d1,d2) + - wp5*(df(c0,c2) + df(e1,e3)) + - wp6*(df(b0,b1) + df(f2,f3))); - }) - - GLSLH(float hv_wd(float i1, float i2, float i3, float i4, - float e1, float e2, float e3, float e4) - { - return (wp4*(df(i1,i2)+df(i3,i4)) + - wp1*(df(i1,e1)+df(i2,e2)+df(i3,e3)+df(i4,e4)) + - wp3*(df(i1,e2)+df(i3,e4)+df(e1,i2)+df(e3,i4))); - }) - - GLSLHF("float superxbr(sampler2D tex, vec2 pos, vec2 tex_size, vec2 pixel_size, int plane, float tex_mul) {\n"); - - if (step == 0) { GLSLH(vec2 dir = fract(pos * tex_size) - 0.5;) // Optimization: Discard (skip drawing) unused pixels, except those @@ -147,83 +218,27 @@ void pass_superxbr(struct gl_shader_cache *sc, int planes, int tex_num, return 0.0;) GLSLH(if (dir.x < 0.0 || dir.y < 0.0 || dist.x < 1.0 || dist.y < 1.0) - return texture(tex, pos - dir * pixel_size)[plane] * tex_mul;) + return texture(tex, pos - pt * dir).x;) + + // Load the input samples + GLSLH(for (int x = 0; x < 4; x++)) + GLSLH(for (int y = 0; y < 4; y++)) + GLSLH(i(x,y) = texture(tex, pos + pt * vec2(x-1.25, y-1.25)).x;) } else { + *transform = (struct gl_transform){{{1.0,0.0}, {0.0,1.0}}, {0.0,0.0}}; + GLSLH(vec2 dir = fract(pos * tex_size / 2.0) - 0.5;) GLSLH(if (dir.x * dir.y > 0.0) - return texture(tex, pos)[plane] * tex_mul;) + return texture(tex, pos).x;) + + GLSLH(for (int x = 0; x < 4; x++)) + GLSLH(for (int y = 0; y < 4; y++)) + GLSLH(i(x,y) = texture(tex, pos + pt * vec2(x+y-3, y-x)).x;) } - GLSLH(float P0 = Get(-1,-1); - float P1 = Get( 2,-1); - float P2 = Get(-1, 2); - float P3 = Get( 2, 2); + superxbr_step_h(sc, conf, ¶ms[step]); + GLSLH(return res;) + GLSLHF("}\n"); - float B = Get( 0,-1); - float C = Get( 1,-1); - float D = Get(-1, 0); - float E = Get( 0, 0); - float F = Get( 1, 0); - float G = Get(-1, 1); - float H = Get( 0, 1); - float I = Get( 1, 1); - - float F4 = Get(2, 0); - float I4 = Get(2, 1); - float H5 = Get(0, 2); - float I5 = Get(1, 2);) - -/* - P1 - |P0|B |C |P1| C F4 |a0|b1|c2|d3| - |D |E |F |F4| B F I4 |b0|c1|d2|e3| |e1|i1|i2|e2| - |G |H |I |I4| P0 E A I P3 |c0|d1|e2|f3| |e3|i3|i4|e4| - |P2|H5|I5|P3| D H I5 |d0|e1|f2|g3| - G H5 - P2 -*/ - - /* Calc edgeness in diagonal directions. */ - GLSLH(float d_edge = (d_wd( D, B, G, E, C, P2, H, F, P1, H5, I, F4, I5, I4 ) - - d_wd( C, F4, B, F, I4, P0, E, I, P3, D, H, I5, G, H5 ));) - - /* Calc edgeness in horizontal/vertical directions. */ - GLSLH(float hv_edge = (hv_wd(F, I, E, H, C, I5, B, H5) - - hv_wd(E, F, H, I, D, F4, G, I4));) - - /* Filter weights. Two taps only. */ - GLSLH(vec4 w1 = vec4(-weight1, weight1+0.5, weight1+0.5, -weight1); - vec4 w2 = vec4(-weight2, weight2+0.25, weight2+0.25, -weight2);) - - /* Filtering and normalization in four direction generating four colors. */ - GLSLH(float c1 = dot(vec4(P2, H, F, P1), w1); - float c2 = dot(vec4(P0, E, I, P3), w1); - float c3 = dot(vec4( D+G, E+H, F+I, F4+I4), w2); - float c4 = dot(vec4( C+B, F+E, I+H, I5+H5), w2);) - - GLSLHF("float limits = %f + 0.000001;\n", conf->edge_strength); - GLSLH(float edge_strength = smoothstep(0.0, limits, abs(d_edge));) - - /* Smoothly blends the two strongest directions(one in diagonal and the - * other in vert/horiz direction). */ - GLSLHF("float color = mix(mix(c1, c2, step(0.0, d_edge))," - "mix(c3, c4, step(0.0, hv_edge)), 1.0 - %f);\n", - conf->edge_strength); - /* Anti-ringing code. */ - GLSLH(float min_sample = min(min(E, F), min(H, I)); - float max_sample = max(max(E, F), max(H, I)); - float aux = color; - color = clamp(color, min_sample, max_sample);) - GLSLHF("color = mix(aux, color, 1.0-2.0*abs(%f-0.5));\n", conf->edge_strength); - - GLSLH(return color;) - - GLSLHF("}"); // superxbr() - - GLSL(color = vec4(1.0);) - - for (int i = 0; i < planes; i++) { - GLSLF("color[%d] = superxbr(texture%d, texcoord%d, texture_size%d, pixel_size%d, %d, %f);\n", - i, tex_num, tex_num, tex_num, tex_num, i, tex_mul); - } + GLSLF("color.x = tex_mul * superxbr(texcoord%d);\n", id); } diff --git a/video/out/opengl/superxbr.h b/video/out/opengl/superxbr.h index 38a10ffedd..7aa46eff7c 100644 --- a/video/out/opengl/superxbr.h +++ b/video/out/opengl/superxbr.h @@ -24,8 +24,8 @@ extern const struct superxbr_opts superxbr_opts_def; extern const struct m_sub_options superxbr_conf; -void pass_superxbr(struct gl_shader_cache *sc, int planes, int tex_num, - int step, float tex_mul, const struct superxbr_opts *conf, +void pass_superxbr(struct gl_shader_cache *sc, int id, int step, float tex_mul, + const struct superxbr_opts *conf, struct gl_transform *transform); #endif diff --git a/video/out/opengl/utils.c b/video/out/opengl/utils.c index 7329240593..40f1beba5f 100644 --- a/video/out/opengl/utils.c +++ b/video/out/opengl/utils.c @@ -355,13 +355,18 @@ 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->w) - cw = fbo->w; - if ((flags & FBOTEX_FUZZY_H) && ch < fbo->h) - ch = fbo->h; + if ((flags & FBOTEX_FUZZY_W) && cw < fbo->rw) + cw = fbo->rw; + if ((flags & FBOTEX_FUZZY_H) && ch < fbo->rh) + ch = fbo->rh; - if (fbo->w == cw && fbo->h == ch && fbo->iformat == iformat) + if (fbo->rw == cw && fbo->rh == ch && fbo->iformat == iformat) { + fbo->lw = w; + fbo->lh = h; return true; + } + + int lw = w, lh = h; if (flags & FBOTEX_FUZZY_W) w = MP_ALIGN_UP(w, 256); @@ -384,12 +389,15 @@ bool fbotex_change(struct fbotex *fbo, GL *gl, struct mp_log *log, int w, int h, *fbo = (struct fbotex) { .gl = gl, - .w = w, - .h = h, + .rw = w, + .rh = h, + .lw = lw, + .lh = lh, .iformat = iformat, }; - mp_verbose(log, "Create FBO: %dx%d\n", fbo->w, fbo->h); + mp_verbose(log, "Create FBO: %dx%d -> %dx%d\n", fbo->lw, fbo->lh, + fbo->rw, fbo->rh); if (!(gl->mpgl_caps & MPGL_CAP_FB)) return false; @@ -397,7 +405,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, format.internal_format, fbo->w, fbo->h, 0, + gl->TexImage2D(GL_TEXTURE_2D, 0, format.internal_format, fbo->rw, fbo->rh, 0, format.format, format.type, 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); @@ -472,11 +480,11 @@ void gl_transform_ortho(struct gl_transform *t, float x0, float x1, // process. In other words: post-composes t onto x void gl_transform_trans(struct gl_transform t, struct gl_transform *x) { - float x00 = x->m[0][0], x01 = x->m[0][1], x10 = x->m[1][0], x11 = x->m[1][1]; - x->m[0][0] = t.m[0][0] * x00 + t.m[0][1] * x10; - x->m[1][0] = t.m[0][0] * x01 + t.m[0][1] * x11; - x->m[0][1] = t.m[1][0] * x00 + t.m[1][1] * x10; - x->m[1][1] = t.m[1][0] * x01 + t.m[1][1] * x11; + struct gl_transform xt = *x; + x->m[0][0] = t.m[0][0] * xt.m[0][0] + t.m[0][1] * xt.m[1][0]; + x->m[1][0] = t.m[1][0] * xt.m[0][0] + t.m[1][1] * xt.m[1][0]; + x->m[0][1] = t.m[0][0] * xt.m[0][1] + t.m[0][1] * xt.m[1][1]; + x->m[1][1] = t.m[1][0] * xt.m[0][1] + t.m[1][1] * xt.m[1][1]; gl_transform_vec(t, &x->t[0], &x->t[1]); } @@ -498,13 +506,8 @@ static void GLAPIENTRY gl_debug_cb(GLenum source, GLenum type, GLuint id, void gl_set_debug_logger(GL *gl, struct mp_log *log) { - if (gl->DebugMessageCallback) { - if (log) { - gl->DebugMessageCallback(gl_debug_cb, log); - } else { - gl->DebugMessageCallback(NULL, NULL); - } - } + if (gl->DebugMessageCallback) + gl->DebugMessageCallback(log ? gl_debug_cb : NULL, log); } #define SC_ENTRIES 32 @@ -518,26 +521,30 @@ enum uniform_type { UT_buffer, }; +union uniform_val { + GLfloat f[9]; + GLint i[4]; + struct { + char* text; + GLint binding; + } buffer; +}; + struct sc_uniform { char *name; enum uniform_type type; const char *glsl_type; int size; GLint loc; - union { - GLfloat f[9]; - GLint i[4]; - struct { - char* text; - GLint binding; - } buffer; - } v; + union uniform_val v; }; struct sc_entry { GLuint gl_shader; - // the following fields define the shader's contents - char *key; // vertex+frag shader (mangled) + GLint uniform_locs[SC_UNIFORM_ENTRIES]; + union uniform_val cached_v[SC_UNIFORM_ENTRIES]; + bstr frag; + bstr vert; struct gl_vao *vao; }; @@ -546,9 +553,9 @@ struct gl_shader_cache { struct mp_log *log; // this is modified during use (gl_sc_add() etc.) - char *prelude_text; - char *header_text; - char *text; + bstr prelude_text; + bstr header_text; + bstr text; struct gl_vao *vao; struct sc_entry entries[SC_ENTRIES]; @@ -556,6 +563,9 @@ struct gl_shader_cache { struct sc_uniform uniforms[SC_UNIFORM_ENTRIES]; int num_uniforms; + + // temporary buffers (avoids frequent reallocations) + bstr tmp[5]; }; struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log) @@ -564,18 +574,15 @@ struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log) *sc = (struct gl_shader_cache){ .gl = gl, .log = log, - .prelude_text = talloc_strdup(sc, ""), - .header_text = talloc_strdup(sc, ""), - .text = talloc_strdup(sc, ""), }; return sc; } void gl_sc_reset(struct gl_shader_cache *sc) { - sc->prelude_text[0] = '\0'; - sc->header_text[0] = '\0'; - sc->text[0] = '\0'; + sc->prelude_text.len = 0; + sc->header_text.len = 0; + sc->text.len = 0; for (int n = 0; n < sc->num_uniforms; n++) { talloc_free(sc->uniforms[n].name); if (sc->uniforms[n].type == UT_buffer) @@ -589,7 +596,8 @@ static void sc_flush_cache(struct gl_shader_cache *sc) for (int n = 0; n < sc->num_entries; n++) { struct sc_entry *e = &sc->entries[n]; sc->gl->DeleteProgram(e->gl_shader); - talloc_free(e->key); + talloc_free(e->vert.start); + talloc_free(e->frag.start); } sc->num_entries = 0; } @@ -605,33 +613,34 @@ void gl_sc_destroy(struct gl_shader_cache *sc) void gl_sc_enable_extension(struct gl_shader_cache *sc, char *name) { - sc->prelude_text = talloc_asprintf_append(sc->prelude_text, - "#extension %s : enable\n", name); + bstr_xappend_asprintf(sc, &sc->prelude_text, "#extension %s : enable\n", name); } +#define bstr_xappend0(sc, b, s) bstr_xappend(sc, b, bstr0(s)) + void gl_sc_add(struct gl_shader_cache *sc, const char *text) { - sc->text = talloc_strdup_append(sc->text, text); + bstr_xappend0(sc, &sc->text, text); } void gl_sc_addf(struct gl_shader_cache *sc, const char *textf, ...) { va_list ap; va_start(ap, textf); - ta_xvasprintf_append(&sc->text, textf, ap); + bstr_xappend_vasprintf(sc, &sc->text, textf, ap); 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); + bstr_xappend0(sc, &sc->header_text, text); } void gl_sc_haddf(struct gl_shader_cache *sc, const char *textf, ...) { va_list ap; va_start(ap, textf); - ta_xvasprintf_append(&sc->header_text, textf, ap); + bstr_xappend_vasprintf(sc, &sc->header_text, textf, ap); va_end(ap); } @@ -789,35 +798,45 @@ static const char *vao_glsl_type(const struct gl_vao_entry *e) } // Assumes program is current (gl->UseProgram(program)). -static void update_uniform(GL *gl, GLuint program, struct sc_uniform *u) +static void update_uniform(GL *gl, struct sc_entry *e, struct sc_uniform *u, int n) { if (u->type == UT_buffer) { - GLuint idx = gl->GetUniformBlockIndex(program, u->name); - gl->UniformBlockBinding(program, idx, u->v.buffer.binding); + GLuint idx = gl->GetUniformBlockIndex(e->gl_shader, u->name); + gl->UniformBlockBinding(e->gl_shader, idx, u->v.buffer.binding); return; } - GLint loc = gl->GetUniformLocation(program, u->name); + GLint loc = e->uniform_locs[n]; if (loc < 0) return; switch (u->type) { case UT_i: assert(u->size == 1); - gl->Uniform1i(loc, u->v.i[0]); + if (memcmp(e->cached_v[n].i, u->v.i, sizeof(u->v.i)) != 0) { + memcpy(e->cached_v[n].i, u->v.i, sizeof(u->v.i)); + gl->Uniform1i(loc, u->v.i[0]); + } break; case UT_f: - switch (u->size) { - case 1: gl->Uniform1f(loc, u->v.f[0]); break; - case 2: gl->Uniform2f(loc, u->v.f[0], u->v.f[1]); break; - case 3: gl->Uniform3f(loc, u->v.f[0], u->v.f[1], u->v.f[2]); break; - case 4: gl->Uniform4f(loc, u->v.f[0], u->v.f[1], u->v.f[2], u->v.f[3]); break; - default: abort(); + if (memcmp(e->cached_v[n].f, u->v.f, sizeof(u->v.f)) != 0) { + memcpy(e->cached_v[n].f, u->v.f, sizeof(u->v.f)); + switch (u->size) { + case 1: gl->Uniform1f(loc, u->v.f[0]); break; + case 2: gl->Uniform2f(loc, u->v.f[0], u->v.f[1]); break; + case 3: gl->Uniform3f(loc, u->v.f[0], u->v.f[1], u->v.f[2]); break; + case 4: gl->Uniform4f(loc, u->v.f[0], u->v.f[1], u->v.f[2], + u->v.f[3]); break; + default: abort(); + } } break; case UT_m: - switch (u->size) { - case 2: gl->UniformMatrix2fv(loc, 1, GL_FALSE, &u->v.f[0]); break; - case 3: gl->UniformMatrix3fv(loc, 1, GL_FALSE, &u->v.f[0]); break; - default: abort(); + if (memcmp(e->cached_v[n].f, u->v.f, sizeof(u->v.f)) != 0) { + memcpy(e->cached_v[n].f, u->v.f, sizeof(u->v.f)); + switch (u->size) { + case 2: gl->UniformMatrix2fv(loc, 1, GL_FALSE, &u->v.f[0]); break; + case 3: gl->UniformMatrix3fv(loc, 1, GL_FALSE, &u->v.f[0]); break; + default: abort(); + } } break; default: @@ -879,12 +898,13 @@ 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]) { + if (sc->header_text.len) { MP_VERBOSE(sc, "header:\n"); - mp_log_source(sc->log, MSGL_V, sc->header_text); + mp_log_source(sc->log, MSGL_V, sc->header_text.start); MP_VERBOSE(sc, "body:\n"); } - mp_log_source(sc->log, MSGL_V, sc->text); + if (sc->text.len) + mp_log_source(sc->log, MSGL_V, sc->text.start); GLuint prog = gl->CreateProgram(); compile_attach_shader(sc, prog, GL_VERTEX_SHADER, vertex); compile_attach_shader(sc, prog, GL_FRAGMENT_SHADER, frag); @@ -897,7 +917,8 @@ static GLuint create_program(struct gl_shader_cache *sc, const char *vertex, return prog; } -#define ADD(x, ...) (x) = talloc_asprintf_append(x, __VA_ARGS__) +#define ADD(x, ...) bstr_xappend_asprintf(sc, (x), __VA_ARGS__) +#define ADD_BSTR(x, s) bstr_xappend(sc, (x), (s)) // 1. Generate vertex and fragment shaders from the fragment shader text added // with gl_sc_add(). The generated shader program is cached (based on the @@ -909,25 +930,29 @@ static GLuint create_program(struct gl_shader_cache *sc, const char *vertex, void gl_sc_gen_shader_and_reset(struct gl_shader_cache *sc) { GL *gl = sc->gl; - void *tmp = talloc_new(NULL); assert(sc->vao); + for (int n = 0; n < MP_ARRAY_SIZE(sc->tmp); n++) + sc->tmp[n].len = 0; + // set up shader text (header + uniforms + body) - char *header = talloc_asprintf(tmp, "#version %d%s\n", gl->glsl_version, - gl->es >= 300 ? " es" : ""); + bstr *header = &sc->tmp[0]; + ADD(header, "#version %d%s\n", gl->glsl_version, gl->es >= 300 ? " es" : ""); if (gl->es) ADD(header, "precision mediump float;\n"); - ADD(header, "%s", sc->prelude_text); + ADD_BSTR(header, sc->prelude_text); char *vert_in = gl->glsl_version >= 130 ? "in" : "attribute"; char *vert_out = gl->glsl_version >= 130 ? "out" : "varying"; char *frag_in = gl->glsl_version >= 130 ? "in" : "varying"; // vertex shader: we don't use the vertex shader, so just setup a dummy, // which passes through the vertex array attributes. - char *vert_head = talloc_strdup(tmp, header); - char *vert_body = talloc_strdup(tmp, "void main() {\n"); - char *frag_vaos = talloc_strdup(tmp, ""); + bstr *vert_head = &sc->tmp[1]; + ADD_BSTR(vert_head, *header); + bstr *vert_body = &sc->tmp[2]; + ADD(vert_body, "void main() {\n"); + bstr *frag_vaos = &sc->tmp[3]; for (int n = 0; sc->vao->entries[n].name; n++) { const struct gl_vao_entry *e = &sc->vao->entries[n]; const char *glsl_type = vao_glsl_type(e); @@ -944,10 +969,12 @@ void gl_sc_gen_shader_and_reset(struct gl_shader_cache *sc) } } ADD(vert_body, "}\n"); - char *vert = talloc_asprintf(tmp, "%s%s", vert_head, vert_body); + bstr *vert = vert_head; + ADD_BSTR(vert, *vert_body); // fragment shader; still requires adding used uniforms and VAO elements - char *frag = talloc_strdup(tmp, header); + bstr *frag = &sc->tmp[4]; + ADD_BSTR(frag, *header); ADD(frag, "#define RG %s\n", gl->mpgl_caps & MPGL_CAP_TEX_RG ? "rg" : "ra"); if (gl->glsl_version >= 130) { ADD(frag, "#define texture1D texture\n"); @@ -956,13 +983,14 @@ void gl_sc_gen_shader_and_reset(struct gl_shader_cache *sc) } else { ADD(frag, "#define texture texture2D\n"); } - ADD(frag, "%s", frag_vaos); + ADD_BSTR(frag, *frag_vaos); for (int n = 0; n < sc->num_uniforms; n++) { struct sc_uniform *u = &sc->uniforms[n]; - if (u->type == UT_buffer) + if (u->type == UT_buffer) { ADD(frag, "uniform %s { %s };\n", u->name, u->v.buffer.text); - else + } else { ADD(frag, "uniform %s %s;\n", u->glsl_type, u->name); + } } // Additional helpers. @@ -970,15 +998,15 @@ void gl_sc_gen_shader_and_reset(struct gl_shader_cache *sc) " mix(0.5 / (lut_size), 1.0 - 0.5 / (lut_size), (x))\n"); // custom shader header - if (sc->header_text[0]) { + if (sc->header_text.len) { ADD(frag, "// header\n"); - ADD(frag, "%s\n", sc->header_text); + ADD_BSTR(frag, sc->header_text); ADD(frag, "// body\n"); } ADD(frag, "void main() {\n"); // we require _all_ frag shaders to write to a "vec4 color" - ADD(frag, "vec4 color;\n"); - ADD(frag, "%s", sc->text); + ADD(frag, "vec4 color = vec4(0.0, 0.0, 0.0, 1.0);\n"); + ADD_BSTR(frag, sc->text); if (gl->glsl_version >= 130) { ADD(frag, "out_color = color;\n"); } else { @@ -986,11 +1014,11 @@ void gl_sc_gen_shader_and_reset(struct gl_shader_cache *sc) } ADD(frag, "}\n"); - char *key = talloc_asprintf(tmp, "%s%s", vert, frag); struct sc_entry *entry = NULL; for (int n = 0; n < sc->num_entries; n++) { - if (strcmp(key, sc->entries[n].key) == 0) { - entry = &sc->entries[n]; + struct sc_entry *cur = &sc->entries[n]; + if (bstr_equals(cur->frag, *frag) && bstr_equals(cur->vert, *vert)) { + entry = cur; break; } } @@ -998,20 +1026,24 @@ void gl_sc_gen_shader_and_reset(struct gl_shader_cache *sc) if (sc->num_entries == SC_ENTRIES) sc_flush_cache(sc); entry = &sc->entries[sc->num_entries++]; - *entry = (struct sc_entry){.key = talloc_strdup(NULL, key)}; + *entry = (struct sc_entry){ + .vert = bstrdup(NULL, *vert), + .frag = bstrdup(NULL, *frag), + }; + } + // build vertex shader from vao and cache the locations of the uniform variables + if (!entry->gl_shader) { + entry->gl_shader = create_program(sc, vert->start, frag->start); + for (int n = 0; n < sc->num_uniforms; n++) { + entry->uniform_locs[n] = gl->GetUniformLocation(entry->gl_shader, + sc->uniforms[n].name); + } } - // build vertex shader from vao - if (!entry->gl_shader) - entry->gl_shader = create_program(sc, vert, frag); gl->UseProgram(entry->gl_shader); - // For now we set the uniforms every time. This is probably bad, and we - // should switch to caching them. for (int n = 0; n < sc->num_uniforms; n++) - update_uniform(gl, entry->gl_shader, &sc->uniforms[n]); - - talloc_free(tmp); + update_uniform(gl, entry, &sc->uniforms[n], n); gl_sc_reset(sc); } diff --git a/video/out/opengl/utils.h b/video/out/opengl/utils.h index 3ec6077bf5..170e24d71a 100644 --- a/video/out/opengl/utils.h +++ b/video/out/opengl/utils.h @@ -71,7 +71,8 @@ struct fbotex { GLuint texture; GLenum iformat; GLenum tex_filter; - int w, h; // size of .texture + int rw, rh; // real (texture) size + int lw, lh; // logical (configured) size }; bool fbotex_init(struct fbotex *fbo, GL *gl, struct mp_log *log, int w, int h, @@ -86,10 +87,18 @@ void fbotex_set_filter(struct fbotex *fbo, GLenum gl_filter); // A 3x2 matrix, with the translation part separate. struct gl_transform { + // row-major, e.g. in mathematical notation: + // | m[0][0] m[0][1] | + // | m[1][0] m[1][1] | float m[2][2]; float t[2]; }; +static const struct gl_transform identity_trans = { + .m = {{1.0, 0.0}, {0.0, 1.0}}, + .t = {0.0, 0.0}, +}; + void gl_transform_ortho(struct gl_transform *t, float x0, float x1, float y0, float y1); @@ -98,8 +107,8 @@ void gl_transform_ortho(struct gl_transform *t, float x0, float x1, static inline void gl_transform_vec(struct gl_transform t, float *x, float *y) { float vx = *x, vy = *y; - *x = vx * t.m[0][0] + vy * t.m[1][0] + t.t[0]; - *y = vx * t.m[0][1] + vy * t.m[1][1] + t.t[1]; + *x = vx * t.m[0][0] + vy * t.m[0][1] + t.t[0]; + *y = vx * t.m[1][0] + vy * t.m[1][1] + t.t[1]; } struct mp_rect_f { @@ -112,6 +121,18 @@ static inline void gl_transform_rect(struct gl_transform t, struct mp_rect_f *r) gl_transform_vec(t, &r->x1, &r->y1); } +static inline bool gl_transform_eq(struct gl_transform a, struct gl_transform b) +{ + for (int x = 0; x < 2; x++) { + for (int y = 0; y < 2; y++) { + if (a.m[x][y] != b.m[x][y]) + return false; + } + } + + return a.t[0] == b.t[0] && a.t[1] == b.t[1]; +} + void gl_transform_trans(struct gl_transform t, struct gl_transform *x); void gl_set_debug_logger(GL *gl, struct mp_log *log); diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c index c10e16fe41..8807b65005 100644 --- a/video/out/opengl/video.c +++ b/video/out/opengl/video.c @@ -106,6 +106,31 @@ struct video_image { struct mp_image *mpi; // original input image }; +enum plane_type { + PLANE_NONE = 0, + PLANE_RGB, + PLANE_LUMA, + PLANE_CHROMA, + PLANE_ALPHA, + PLANE_XYZ, +}; + +// A self-contained description of a source image which can be bound to a +// texture unit and sampled from. Contains metadata about how it's to be used +struct img_tex { + enum plane_type type; // must be set to something non-zero + int components; // number of relevant coordinates + float multiplier; // multiplier to be used when sampling + GLuint gl_tex; + GLenum gl_target; + bool use_integer; + int tex_w, tex_h; // source texture size + int w, h; // logical size (with pre_transform applied) + struct gl_transform pre_transform; // source texture space + struct gl_transform transform; // rendering transformation + bool texture_la; // it's a GL_LUMINANCE_ALPHA texture (access with .ra not .rg) +}; + struct fbosurface { struct fbotex fbotex; double pts; @@ -113,14 +138,6 @@ struct fbosurface { #define FBOSURFACES_MAX 10 -struct src_tex { - GLuint gl_tex; - GLenum gl_target; - bool use_integer; - int w, h; - struct mp_rect_f src; -}; - struct cached_file { char *path; char *body; @@ -132,6 +149,7 @@ struct gl_video { struct mpv_global *global; struct mp_log *log; struct gl_video_opts opts; + struct gl_lcms *cms; bool gl_debug; int texture_16bit_depth; // actual bits available in 16 bit textures @@ -169,15 +187,15 @@ struct gl_video { bool dumb_mode; bool forced_dumb_mode; - struct fbotex chroma_merge_fbo; - struct fbotex chroma_deband_fbo; + struct fbotex merge_fbo[4]; + struct fbotex deband_fbo[4]; + struct fbotex scale_fbo[4]; + struct fbotex integer_fbo[4]; struct fbotex indirect_fbo; struct fbotex blend_subs_fbo; struct fbotex unsharp_fbo; struct fbotex output_fbo; - struct fbotex deband_fbo; struct fbosurface surfaces[FBOSURFACES_MAX]; - struct fbotex integer_conv_fbo[TEXUNIT_VIDEO_NUM]; // these are duplicated so we can keep rendering back and forth between // them to support an unlimited number of shader passes per step @@ -192,8 +210,8 @@ struct gl_video { bool is_interpolated; bool output_fbo_valid; - // state for luma (0), luma-down(1), chroma (2) and temporal (3) scalers - struct scaler scaler[4]; + // state for configured scalers + struct scaler scaler[SCALER_COUNT]; struct mp_csp_equalizer video_eq; @@ -203,11 +221,12 @@ struct gl_video { int vp_w, vp_h; // temporary during rendering - struct src_tex pass_tex[TEXUNIT_VIDEO_NUM]; + struct img_tex pass_tex[TEXUNIT_VIDEO_NUM]; + int pass_tex_num; int texture_w, texture_h; struct gl_transform texture_offset; // texture transform without rotation + int components; bool use_linear; - bool use_normalized_range; float user_gamma; int frames_uploaded; @@ -418,10 +437,10 @@ const struct m_sub_options gl_video_conf = { 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("pbo", pbo, 0), - SCALER_OPTS("scale", 0), - SCALER_OPTS("dscale", 1), - SCALER_OPTS("cscale", 2), - SCALER_OPTS("tscale", 3), + SCALER_OPTS("scale", SCALER_SCALE), + SCALER_OPTS("dscale", SCALER_DSCALE), + SCALER_OPTS("cscale", SCALER_CSCALE), + SCALER_OPTS("tscale", SCALER_TSCALE), OPT_INTRANGE("scaler-lut-size", scaler_lut_size, 0, 4, 10), OPT_FLAG("scaler-resizes-only", scaler_resizes_only, 0), OPT_FLAG("linear-scaling", linear_scaling, 0), @@ -470,7 +489,7 @@ const struct m_sub_options gl_video_conf = { OPT_FLAG("deband", deband, 0), OPT_SUBSTRUCT("deband", deband_opts, deband_conf, 0), OPT_FLOAT("sharpen", unsharp, 0), - OPT_CHOICE("prescale", prescale, 0, + OPT_CHOICE("prescale-luma", prescale_luma, 0, ({"none", 0}, {"superxbr", 1} #if HAVE_NNEDI @@ -505,6 +524,7 @@ const struct m_sub_options gl_video_conf = { OPT_REPLACED("smoothmotion-threshold", "tscale-param1"), OPT_REPLACED("scale-down", "dscale"), OPT_REPLACED("fancy-downscaling", "correct-downscaling"), + OPT_REPLACED("prescale", "prescale-luma"), {0} }, @@ -518,7 +538,7 @@ 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, struct mp_image *mpi); static void assign_options(struct gl_video_opts *dst, struct gl_video_opts *src); -static void get_scale_factors(struct gl_video *p, double xy[2]); +static void get_scale_factors(struct gl_video *p, bool transpose_rot, double xy[2]); #define GLSL(x) gl_sc_add(p->sc, #x "\n"); #define GLSLF(...) gl_sc_addf(p->sc, __VA_ARGS__) @@ -639,7 +659,7 @@ static void uninit_rendering(struct gl_video *p) { GL *gl = p->gl; - for (int n = 0; n < 4; n++) + for (int n = 0; n < SCALER_COUNT; n++) uninit_scaler(p, &p->scaler[n]); gl->DeleteTextures(1, &p->dither_texture); @@ -648,15 +668,16 @@ static void uninit_rendering(struct gl_video *p) gl->DeleteBuffers(1, &p->nnedi3_weights_buffer); p->nnedi3_weights_buffer = 0; - fbotex_uninit(&p->chroma_merge_fbo); - fbotex_uninit(&p->chroma_deband_fbo); + for (int n = 0; n < 4; n++) { + fbotex_uninit(&p->merge_fbo[n]); + fbotex_uninit(&p->deband_fbo[n]); + fbotex_uninit(&p->scale_fbo[n]); + fbotex_uninit(&p->integer_fbo[n]); + } + fbotex_uninit(&p->indirect_fbo); fbotex_uninit(&p->blend_subs_fbo); fbotex_uninit(&p->unsharp_fbo); - fbotex_uninit(&p->deband_fbo); - - for (int n = 0; n < 4; n++) - fbotex_uninit(&p->integer_conv_fbo[n]); for (int n = 0; n < 2; n++) { fbotex_uninit(&p->pre_fbo[n]); @@ -674,21 +695,31 @@ static void uninit_rendering(struct gl_video *p) gl_video_reset_surfaces(p); } -void gl_video_set_lut3d(struct gl_video *p, struct lut3d *lut3d) +void gl_video_update_profile(struct gl_video *p) +{ + if (p->use_lut_3d) + return; + + p->use_lut_3d = true; + check_gl_features(p); + + reinit_rendering(p); +} + +static bool gl_video_get_lut3d(struct gl_video *p, enum mp_csp_prim prim, + enum mp_csp_trc trc) { GL *gl = p->gl; - if (!lut3d) { - if (p->use_lut_3d) { - p->use_lut_3d = false; - reinit_rendering(p); - } - return; - } + if (!p->cms || !p->use_lut_3d) + return false; - if (!(gl->mpgl_caps & MPGL_CAP_3D_TEX) || gl->es) { - MP_ERR(p, "16 bit fixed point 3D textures not available.\n"); - return; + if (!gl_lcms_has_changed(p->cms, prim, trc)) + return true; + + struct lut3d *lut3d = NULL; + if (!gl_lcms_get_lut3d(p->cms, &lut3d, prim, trc) || !lut3d) { + return false; } if (!p->lut_3d_texture) @@ -705,33 +736,76 @@ void gl_video_set_lut3d(struct gl_video *p, struct lut3d *lut3d) gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); gl->ActiveTexture(GL_TEXTURE0); - p->use_lut_3d = true; - check_gl_features(p); - debug_check_gl(p, "after 3d lut creation"); - reinit_rendering(p); + return true; } -static void pass_load_fbotex(struct gl_video *p, struct fbotex *src_fbo, - int w, int h, int id) +// Fill an img_tex struct from an FBO + some metadata +static struct img_tex img_tex_fbo(struct fbotex *fbo, struct gl_transform t, + enum plane_type type, int components) { - p->pass_tex[id] = (struct src_tex){ - .gl_tex = src_fbo->texture, + assert(type != PLANE_NONE); + return (struct img_tex){ + .type = type, + .gl_tex = fbo->texture, .gl_target = GL_TEXTURE_2D, - .w = src_fbo->w, - .h = src_fbo->h, - .src = {0, 0, w, h}, + .multiplier = 1.0, + .use_integer = false, + .tex_w = fbo->rw, + .tex_h = fbo->rh, + .w = fbo->lw, + .h = fbo->lh, + .pre_transform = identity_trans, + .transform = t, + .components = components, }; } -static void pass_set_image_textures(struct gl_video *p, struct video_image *vimg, - struct gl_transform *chroma) +// Bind an img_tex to a free texture unit and return its ID. At most +// TEXUNIT_VIDEO_NUM texture units can be bound at once +static int pass_bind(struct gl_video *p, struct img_tex tex) { - *chroma = (struct gl_transform){{{0}}}; + assert(p->pass_tex_num < TEXUNIT_VIDEO_NUM); + p->pass_tex[p->pass_tex_num] = tex; + return p->pass_tex_num++; +} +// Rotation by 90° and flipping. +static void get_plane_source_transform(struct gl_video *p, int w, int h, + struct gl_transform *out_tr) +{ + struct gl_transform tr = identity_trans; + int a = p->image_params.rotate % 90 ? 0 : p->image_params.rotate / 90; + int sin90[4] = {0, 1, 0, -1}; // just to avoid rounding issues etc. + int cos90[4] = {1, 0, -1, 0}; + struct gl_transform rot = {{{cos90[a], sin90[a]}, {-sin90[a], cos90[a]}}}; + gl_transform_trans(rot, &tr); + + // basically, recenter to keep the whole image in view + float b[2] = {1, 1}; + gl_transform_vec(rot, &b[0], &b[1]); + tr.t[0] += b[0] < 0 ? w : 0; + tr.t[1] += b[1] < 0 ? h : 0; + + if (p->image.image_flipped) { + struct gl_transform flip = {{{1, 0}, {0, -1}}, {0, h}}; + gl_transform_trans(flip, &tr); + } + + *out_tr = tr; +} + +// Places a video_image's image textures + associated metadata into tex[]. The +// number of textures is equal to p->plane_count. +static void pass_get_img_tex(struct gl_video *p, struct video_image *vimg, + struct img_tex tex[4]) +{ assert(vimg->mpi); + // Determine the chroma offset + struct gl_transform chroma = (struct gl_transform){{{0}}}; + float ls_w = 1.0 / (1 << p->image_desc.chroma_xs); float ls_h = 1.0 / (1 << p->image_desc.chroma_ys); @@ -743,26 +817,56 @@ static void pass_set_image_textures(struct gl_video *p, struct video_image *vimg // so that the luma and chroma sample line up exactly. // For 4:4:4, setting chroma location should have no effect at all. // luma sample size (in chroma coord. space) - chroma->t[0] = ls_w < 1 ? ls_w * -cx / 2 : 0; - chroma->t[1] = ls_h < 1 ? ls_h * -cy / 2 : 0; + chroma.t[0] = ls_w < 1 ? ls_w * -cx / 2 : 0; + chroma.t[1] = ls_h < 1 ? ls_h * -cy / 2 : 0; } // 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].w / vimg->planes[1].w; - chroma->m[1][1] = ls_h * (float)vimg->planes[0].h / vimg->planes[1].h; + 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; + // The existing code assumes we just have a single tex multiplier for + // all of the planes. This may change in the future + float tex_mul = 1.0 / mp_get_csp_mul(p->image_params.colorspace, + p->image_desc.component_bits, + p->image_desc.component_full_bits); + + memset(tex, 0, 4 * sizeof(tex[0])); for (int n = 0; n < p->plane_count; n++) { struct texplane *t = &vimg->planes[n]; - p->pass_tex[n] = (struct src_tex){ + + enum plane_type type; + if (n >= 3) { + type = PLANE_ALPHA; + } else if (p->image_desc.flags & MP_IMGFLAG_RGB) { + type = PLANE_RGB; + } else if (p->image_desc.flags & MP_IMGFLAG_YUV) { + type = n == 0 ? PLANE_LUMA : PLANE_CHROMA; + } else if (p->image_desc.flags & MP_IMGFLAG_XYZ) { + type = PLANE_XYZ; + } else { + abort(); + } + + tex[n] = (struct img_tex){ + .type = type, .gl_tex = t->gl_texture, .gl_target = t->gl_target, + .multiplier = tex_mul, .use_integer = t->use_integer, + .tex_w = t->w, + .tex_h = t->h, .w = t->w, .h = t->h, - .src = {0, 0, t->w, t->h}, + .transform = type == PLANE_CHROMA ? chroma : identity_trans, + .components = p->image_desc.components[n], + .texture_la = t->gl_format == GL_LUMINANCE_ALPHA, }; + get_plane_source_transform(p, t->w, t->h, &tex[n].pre_transform); + if (p->image_params.rotate % 180 == 90) + MPSWAP(int, tex[n].w, tex[n].h); } } @@ -864,8 +968,8 @@ static void pass_prepare_src_tex(struct gl_video *p) GL *gl = p->gl; struct gl_shader_cache *sc = p->sc; - for (int n = 0; n < TEXUNIT_VIDEO_NUM; n++) { - struct src_tex *s = &p->pass_tex[n]; + for (int n = 0; n < p->pass_tex_num; n++) { + struct img_tex *s = &p->pass_tex[n]; if (!s->gl_tex) continue; @@ -883,8 +987,8 @@ static void pass_prepare_src_tex(struct gl_video *p) } float f[2] = {1, 1}; if (s->gl_target != GL_TEXTURE_RECTANGLE) { - f[0] = s->w; - f[1] = s->h; + f[0] = s->tex_w; + f[1] = s->tex_h; } gl_sc_uniform_vec2(sc, texture_size, f); gl_sc_uniform_vec2(sc, pixel_size, (GLfloat[]){1.0f / f[0], @@ -896,11 +1000,10 @@ static void pass_prepare_src_tex(struct gl_video *p) gl->ActiveTexture(GL_TEXTURE0); } -// flags = bits 0-1: rotate, bit 2: flip vertically static void render_pass_quad(struct gl_video *p, int vp_w, int vp_h, - const struct mp_rect *dst, int flags) + const struct mp_rect *dst) { - struct vertex va[4]; + struct vertex va[4] = {0}; struct gl_transform t; gl_transform_ortho(&t, 0, vp_w, 0, vp_h); @@ -914,30 +1017,21 @@ static void render_pass_quad(struct gl_video *p, int vp_w, int vp_h, struct vertex *v = &va[n]; v->position.x = x[n / 2]; v->position.y = y[n % 2]; - for (int i = 0; i < TEXUNIT_VIDEO_NUM; i++) { - struct src_tex *s = &p->pass_tex[i]; - if (s->gl_tex) { - float tx[2] = {s->src.x0, s->src.x1}; - float ty[2] = {s->src.y0, s->src.y1}; - 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->w); - v->texcoord[i].y = ty[n % 2] / (rect ? 1 : s->h); - } + for (int i = 0; i < p->pass_tex_num; i++) { + struct img_tex *s = &p->pass_tex[i]; + if (!s->gl_tex) + continue; + struct gl_transform tr = s->transform; + gl_transform_trans(s->pre_transform, &tr); + float tx = (n / 2) * s->w; + float ty = (n % 2) * s->h; + gl_transform_vec(tr, &tx, &ty); + bool rect = s->gl_target == GL_TEXTURE_RECTANGLE; + v->texcoord[i].x = tx / (rect ? 1 : s->tex_w); + v->texcoord[i].y = ty / (rect ? 1 : s->tex_h); } } - int rot = flags & 3; - while (rot--) { - static const int perm[4] = {1, 3, 0, 2}; - struct vertex vb[4]; - memcpy(vb, va, sizeof(vb)); - for (int n = 0; n < 4; n++) - memcpy(va[n].texcoord, vb[perm[n]].texcoord, - sizeof(struct vertex_pt[TEXUNIT_VIDEO_NUM])); - } - p->gl->Viewport(0, 0, vp_w, abs(vp_h)); gl_vao_draw_data(&p->vao, GL_TRIANGLE_STRIP, va, 4); @@ -946,32 +1040,37 @@ static void render_pass_quad(struct gl_video *p, int vp_w, int vp_h, // flags: see render_pass_quad static void finish_pass_direct(struct gl_video *p, GLint fbo, int vp_w, int vp_h, - const struct mp_rect *dst, int flags) + const struct mp_rect *dst) { GL *gl = p->gl; pass_prepare_src_tex(p); gl->BindFramebuffer(GL_FRAMEBUFFER, fbo); gl_sc_gen_shader_and_reset(p->sc); - render_pass_quad(p, vp_w, vp_h, dst, flags); + render_pass_quad(p, vp_w, vp_h, dst); gl->BindFramebuffer(GL_FRAMEBUFFER, 0); memset(&p->pass_tex, 0, sizeof(p->pass_tex)); + p->pass_tex_num = 0; } // dst_fbo: this will be used for rendering; possibly reallocating the whole // FBO, if the required parameters have changed // w, h: required FBO target dimension, and also defines the target rectangle // used for rasterization -// tex: the texture unit to load the result back into // flags: 0 or combination of FBOTEX_FUZZY_W/FBOTEX_FUZZY_H (setting the fuzzy // flags allows the FBO to be larger than the w/h parameters) static void finish_pass_fbo(struct gl_video *p, struct fbotex *dst_fbo, - int w, int h, int tex, int flags) + int w, int h, int flags) { fbotex_change(dst_fbo, p->gl, p->log, w, h, p->opts.fbo_format, flags); - 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, w, h, tex); + finish_pass_direct(p, dst_fbo->fbo, dst_fbo->rw, dst_fbo->rh, + &(struct mp_rect){0, 0, w, h}); +} + +static void skip_unused(struct gl_video *p, int num_components) +{ + for (int i = num_components; i < 4; i++) + GLSLF("color.%c = %f;\n", "rgba"[i], i < 3 ? 0.0 : 1.0); } static void uninit_scaler(struct gl_video *p, struct scaler *scaler) @@ -1008,8 +1107,8 @@ static const char *get_custom_shader_fn(struct gl_video *p, const char *body) // 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) +static bool apply_shaders(struct gl_video *p, char **shaders, int w, int h, + struct fbotex textures[2]) { if (!shaders) return false; @@ -1019,13 +1118,15 @@ static bool apply_shaders(struct gl_video *p, char **shaders, const char *body = load_cached_file(p, shaders[n]); if (!body) continue; - finish_pass_fbo(p, &textures[tex], w, h, tex_num, 0); - GLSLHF("#define pixel_size pixel_size%d\n", tex_num); + finish_pass_fbo(p, &textures[tex], w, h, 0); + int id = pass_bind(p, img_tex_fbo(&textures[tex], identity_trans, + PLANE_RGB, p->components)); + GLSLHF("#define pixel_size pixel_size%d\n", id); load_shader(p, body); const char *fn_name = get_custom_shader_fn(p, body); GLSLF("// custom shader\n"); GLSLF("color = %s(texture%d, texcoord%d, texture_size%d);\n", - fn_name, tex_num, tex_num, tex_num); + fn_name, id, id, id); tex = (tex+1) % 2; success = true; } @@ -1165,46 +1266,52 @@ static void reinit_scaler(struct gl_video *p, struct scaler *scaler, } // Special helper for sampling from two separated stages -static void pass_sample_separated(struct gl_video *p, int src_tex, - struct scaler *scaler, int w, int h, - struct gl_transform transform) +static void pass_sample_separated(struct gl_video *p, struct img_tex src, + struct scaler *scaler, int w, int h) { - // Keep the x components untouched for the first pass - struct mp_rect_f src_new = p->pass_tex[src_tex].src; - gl_transform_rect(transform, &src_new); + // Separate the transformation into x and y components, per pass + struct gl_transform t_x = { + .m = {{src.transform.m[0][0], 0.0}, {src.transform.m[1][0], 1.0}}, + .t = {src.transform.t[0], 0.0}, + }; + struct gl_transform t_y = { + .m = {{1.0, src.transform.m[0][1]}, {0.0, src.transform.m[1][1]}}, + .t = {0.0, src.transform.t[1]}, + }; + + // First pass (scale only in the y dir) + src.transform = t_y; + sampler_prelude(p->sc, pass_bind(p, src)); GLSLF("// pass 1\n"); - p->pass_tex[src_tex].src.y0 = src_new.y0; - p->pass_tex[src_tex].src.y1 = src_new.y1; pass_sample_separated_gen(p->sc, scaler, 0, 1); - int src_w = p->pass_tex[src_tex].src.x1 - p->pass_tex[src_tex].src.x0; - finish_pass_fbo(p, &scaler->sep_fbo, src_w, h, src_tex, FBOTEX_FUZZY_H); - // Restore the sample source for the second pass - sampler_prelude(p->sc, src_tex); + GLSLF("color *= %f;\n", src.multiplier); + finish_pass_fbo(p, &scaler->sep_fbo, src.w, h, FBOTEX_FUZZY_H); + + // Second pass (scale only in the x dir) + src = img_tex_fbo(&scaler->sep_fbo, t_x, src.type, src.components); + sampler_prelude(p->sc, pass_bind(p, src)); GLSLF("// pass 2\n"); - p->pass_tex[src_tex].src.x0 = src_new.x0; - p->pass_tex[src_tex].src.x1 = src_new.x1; pass_sample_separated_gen(p->sc, scaler, 1, 0); } -// Sample. This samples from the texture ID given by src_tex. It's hardcoded to -// use all variables and values associated with it (which includes textureN, -// texcoordN and texture_sizeN). -// The src rectangle is implicit in p->pass_tex + transform. +// Sample from img_tex, with the src rectangle given by it. // The dst rectangle is implicit by what the caller will do next, but w and h // must still be what is going to be used (to dimension FBOs correctly). // This will write the scaled contents to the vec4 "color". // The scaler unit is initialized by this function; in order to avoid cache // thrashing, the scaler unit should usually use the same parameters. -static void pass_sample(struct gl_video *p, int src_tex, struct scaler *scaler, - const struct scaler_config *conf, double scale_factor, - int w, int h, struct gl_transform transform) +static void pass_sample(struct gl_video *p, struct img_tex tex, + struct scaler *scaler, const struct scaler_config *conf, + double scale_factor, int w, int h) { reinit_scaler(p, scaler, conf, scale_factor, filter_sizes); - sampler_prelude(p->sc, src_tex); - // Set up the transformation for everything other than separated scaling - if (!scaler->kernel || scaler->kernel->polar) - gl_transform_rect(transform, &p->pass_tex[src_tex].src); + bool is_separated = scaler->kernel && !scaler->kernel->polar; + + // Set up the transformation+prelude and bind the texture, for everything + // other than separated scaling (which does this in the subfunction) + if (!is_separated) + sampler_prelude(p->sc, pass_bind(p, tex)); // Dispatch the scaler. They're all wildly different. const char *name = scaler->conf.kernel.name; @@ -1227,28 +1334,42 @@ static void pass_sample(struct gl_video *p, int src_tex, struct scaler *scaler, } else if (scaler->kernel && scaler->kernel->polar) { pass_sample_polar(p->sc, scaler); } else if (scaler->kernel) { - pass_sample_separated(p, src_tex, scaler, w, h, transform); + pass_sample_separated(p, tex, scaler, w, h); } else { // Should never happen abort(); } + // Apply any required multipliers. Separated scaling already does this in + // its first stage + if (!is_separated) + GLSLF("color *= %f;\n", tex.multiplier); + // Micro-optimization: Avoid scaling unneeded channels - if (!p->has_alpha || p->opts.alpha_mode != 1) - GLSL(color.a = 1.0;) + skip_unused(p, tex.components); } // Get the number of passes for prescaler, with given display size. -static int get_prescale_passes(struct gl_video *p) +static int get_prescale_passes(struct gl_video *p, struct img_tex tex[4]) { - if (!p->opts.prescale) + if (!p->opts.prescale_luma) return 0; + + // Return 0 if no luma planes exist + for (int n = 0; ; n++) { + if (n > 4) + return 0; + + if (tex[n].type == PLANE_LUMA) + break; + } + // The downscaling threshold check is turned off. if (p->opts.prescale_downscaling_threshold < 1.0f) return p->opts.prescale_passes; double scale_factors[2]; - get_scale_factors(p, scale_factors); + get_scale_factors(p, true, scale_factors); int passes = 0; for (; passes < p->opts.prescale_passes; passes ++) { @@ -1265,283 +1386,303 @@ static int get_prescale_passes(struct gl_video *p) return passes; } -// apply pre-scalers -static void pass_prescale(struct gl_video *p, int src_tex_num, int dst_tex_num, - int planes, int w, int h, int passes, - float tex_mul, struct gl_transform *offset) +// Upload the NNEDI3 UBO weights only if needed +static void upload_nnedi3_weights(struct gl_video *p) { - *offset = (struct gl_transform){{{1.0,0.0}, {0.0,1.0}}, {0.0,0.0}}; + GL *gl = p->gl; - int tex_num = src_tex_num; - - // Happens to be the same for superxbr and nnedi3. - const int steps_per_pass = 2; - - for (int pass = 0; pass < passes; pass++) { - for (int step = 0; step < steps_per_pass; step++) { - struct gl_transform transform = {{{0}}}; - - switch(p->opts.prescale) { - case 1: - pass_superxbr(p->sc, planes, tex_num, step, - tex_mul, p->opts.superxbr_opts, &transform); - break; - case 2: - pass_nnedi3(p->gl, p->sc, planes, tex_num, step, - tex_mul, p->opts.nnedi3_opts, &transform); - break; - default: - abort(); - } - - tex_mul = 1.0; - - gl_transform_trans(transform, offset); - - w *= (int)transform.m[0][0]; - h *= (int)transform.m[1][1]; - - finish_pass_fbo(p, &p->prescale_fbo[pass][step], - w, h, dst_tex_num, 0); - tex_num = dst_tex_num; - } - } -} - -// Prescale the planes from the main textures. -static bool pass_prescale_luma(struct gl_video *p, float tex_mul, - struct gl_transform *chromafix, - struct gl_transform *transform, - struct src_tex *prescaled_tex, - int *prescaled_planes) -{ - if (p->opts.prescale == 2 && - p->opts.nnedi3_opts->upload == NNEDI3_UPLOAD_UBO) + if (p->opts.nnedi3_opts->upload == NNEDI3_UPLOAD_UBO && + !p->nnedi3_weights_buffer) { - // nnedi3 are configured to use uniform buffer objects. - if (!p->nnedi3_weights_buffer) { - p->gl->GenBuffers(1, &p->nnedi3_weights_buffer); - p->gl->BindBufferBase(GL_UNIFORM_BUFFER, 0, - p->nnedi3_weights_buffer); - int weights_size; - const float *weights = - get_nnedi3_weights(p->opts.nnedi3_opts, &weights_size); + gl->GenBuffers(1, &p->nnedi3_weights_buffer); + gl->BindBufferBase(GL_UNIFORM_BUFFER, 0, p->nnedi3_weights_buffer); - MP_VERBOSE(p, "Uploading NNEDI3 weights via uniform buffer (size=%d)\n", - weights_size); + int size; + const float *weights = get_nnedi3_weights(p->opts.nnedi3_opts, &size); - // We don't know the endianness of GPU, just assume it's little - // endian. - p->gl->BufferData(GL_UNIFORM_BUFFER, weights_size, weights, - GL_STATIC_DRAW); - } + MP_VERBOSE(p, "Uploading NNEDI3 weights via UBO (size=%d)\n", size); + + // We don't know the endianness of GPU, just assume it's LE + gl->BufferData(GL_UNIFORM_BUFFER, size, weights, GL_STATIC_DRAW); } - // number of passes to apply prescaler, can be zero. - int prescale_passes = get_prescale_passes(p); - - if (prescale_passes == 0) - return false; - - p->use_normalized_range = true; - - // estimate a safe upperbound of planes being prescaled on texture0. - *prescaled_planes = p->is_yuv ? 1 : - (!p->color_swizzle[0] || p->color_swizzle[3] == 'a') ? 3 : 4; - - struct src_tex tex_backup[4]; - for (int i = 0; i < 4; i++) - tex_backup[i] = p->pass_tex[i]; - - if (p->opts.deband) { - // apply debanding before upscaling. - pass_sample_deband(p->sc, p->opts.deband_opts, 0, p->pass_tex[0].gl_target, - tex_mul, &p->lfg); - finish_pass_fbo(p, &p->deband_fbo, p->texture_w, - p->texture_h, 0, 0); - tex_backup[0] = p->pass_tex[0]; - } - - // process texture0 and store the result in texture4. - pass_prescale(p, 0, 4, *prescaled_planes, p->texture_w, p->texture_h, - prescale_passes, p->opts.deband ? 1.0 : tex_mul, transform); - - // correct the chromafix under new transform. - chromafix->t[0] -= transform->t[0] / transform->m[0][0]; - chromafix->t[1] -= transform->t[1] / transform->m[1][1]; - - // restore the first four texture. - for (int i = 0; i < 4; i++) - p->pass_tex[i] = tex_backup[i]; - - // backup texture4 for later use. - *prescaled_tex = p->pass_tex[4]; - - return true; } -// The input textures are in an integer format (non-fixed-point), like R16UI. -// Convert it to float in an extra pass. -static void pass_integer_conversion(struct gl_video *p, bool *chroma_merging) +// Applies a single pass of the prescaler, and accumulates the offset in +// pass_transform. +static void pass_prescale_luma(struct gl_video *p, struct img_tex *tex, + struct gl_transform *pass_transform, + struct fbotex fbo[MAX_PRESCALE_STEPS]) { - double tex_mul = 1 / mp_get_csp_mul(p->image_params.colorspace, - p->image_desc.component_bits, - p->image_desc.component_full_bits); - uint64_t tex_max = 1ull << p->image_desc.component_full_bits; - tex_mul *= 1.0 / (tex_max - 1); + // Happens to be the same for superxbr and nnedi3. + const int num_steps = 2; - struct src_tex pass_tex[TEXUNIT_VIDEO_NUM]; - assert(sizeof(pass_tex) == sizeof(p->pass_tex)); - memcpy(pass_tex, p->pass_tex, sizeof(pass_tex)); + for (int step = 0; step < num_steps; step++) { + struct gl_transform step_transform = {{{0}}}; + int id = pass_bind(p, *tex); + int planes = tex->components; - *chroma_merging = p->plane_count == 3; - - for (int n = 0; n < TEXUNIT_VIDEO_NUM; n++) { - if (!p->pass_tex[n].gl_tex) - continue; - if (*chroma_merging && n == 2) - continue; - GLSLF("// integer conversion plane %d\n", n); - GLSLF("uvec4 icolor = texture(texture%d, texcoord%d);\n", n, n); - GLSLF("color = vec4(icolor) * tex_mul;\n"); - if (*chroma_merging && n == 1) { - GLSLF("uvec4 icolor2 = texture(texture2, texcoord2);\n"); - GLSLF("color.g = vec4(icolor2).r * tex_mul;\n"); + switch(p->opts.prescale_luma) { + case 1: + assert(planes == 1); + pass_superxbr(p->sc, id, step, tex->multiplier, + p->opts.superxbr_opts, &step_transform); + break; + case 2: + upload_nnedi3_weights(p); + pass_nnedi3(p->gl, p->sc, planes, id, step, tex->multiplier, + p->opts.nnedi3_opts, &step_transform, tex->gl_target); + break; + default: + abort(); } - gl_sc_uniform_f(p->sc, "tex_mul", tex_mul); - int c_w = p->pass_tex[n].src.x1 - p->pass_tex[n].src.x0; - int c_h = p->pass_tex[n].src.y1 - p->pass_tex[n].src.y0; - finish_pass_fbo(p, &p->integer_conv_fbo[n], c_w, c_h, n, 0); - pass_tex[n] = p->pass_tex[n]; - memcpy(p->pass_tex, pass_tex, sizeof(p->pass_tex)); + + int new_w = tex->w * (int)step_transform.m[0][0], + new_h = tex->h * (int)step_transform.m[1][1]; + + skip_unused(p, planes); + finish_pass_fbo(p, &fbo[step], new_w, new_h, 0); + *tex = img_tex_fbo(&fbo[step], identity_trans, tex->type, tex->components); + + // Accumulate the local transform + gl_transform_trans(step_transform, pass_transform); + } +} + +// Copy a texture to the vec4 color, while increasing offset. Also applies +// the texture multiplier to the sampled color +static void copy_img_tex(struct gl_video *p, int *offset, struct img_tex img) +{ + int count = img.components; + assert(*offset + count <= 4); + + int id = pass_bind(p, img); + char src[5] = {0}; + char dst[5] = {0}; + const char *tex_fmt = img.texture_la ? "ragg" : "rgba"; + const char *dst_fmt = "rgba"; + for (int i = 0; i < count; i++) { + src[i] = tex_fmt[i]; + dst[i] = dst_fmt[*offset + i]; } - p->use_normalized_range = true; + if (img.use_integer) { + uint64_t tex_max = 1ull << p->image_desc.component_full_bits; + img.multiplier *= 1.0 / (tex_max - 1); + } + + GLSLF("color.%s = %f * vec4(texture(texture%d, texcoord%d)).%s;\n", + dst, img.multiplier, id, id, src); + + *offset += count; } // sample from video textures, set "color" variable to yuv value static void pass_read_video(struct gl_video *p) { - p->use_normalized_range = false; + struct img_tex tex[4]; + pass_get_img_tex(p, &p->image, tex); - struct gl_transform chromafix; - pass_set_image_textures(p, &p->image, &chromafix); - - bool chroma_merged = false; - - if (p->use_integer_conversion) - pass_integer_conversion(p, &chroma_merged); - - float tex_mul = 1 / mp_get_csp_mul(p->image_params.colorspace, - p->image_desc.component_bits, - p->image_desc.component_full_bits); - if (p->use_normalized_range) - tex_mul = 1.0; - - struct src_tex prescaled_tex; - struct gl_transform offset = {{{0}}}; - int prescaled_planes; - - bool prescaled = pass_prescale_luma(p, tex_mul, &chromafix, &offset, - &prescaled_tex, &prescaled_planes); - - const int scale_factor_x = prescaled ? (int)offset.m[0][0] : 1; - const int scale_factor_y = prescaled ? (int)offset.m[1][1] : 1; - - if (p->plane_count > 1) { - // Chroma processing (merging -> debanding -> 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->plane_count > 2 && !chroma_merged) { - // For simplicity and performance, we merge the chroma planes - // into a single texture before scaling or debanding, so the shader - // doesn't need to run multiple times. - GLSLF("// chroma merging\n"); - GLSL(color = vec4(texture(texture1, texcoord1).x, - texture(texture2, texcoord2).x, - 0.0, 1.0);) - // We also pull up to the full dynamic range of the texture to avoid - // heavy clipping when using low-bit-depth FBOs - GLSLF("color.xy *= %f;\n", tex_mul); - 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); - p->use_normalized_range = true; - } - - if (p->opts.deband) { - pass_sample_deband(p->sc, p->opts.deband_opts, 1, p->pass_tex[1].gl_target, - p->use_normalized_range ? 1.0 : tex_mul, &p->lfg); - GLSL(color.zw = vec2(0.0, 1.0);) // skip unused - finish_pass_fbo(p, &p->chroma_deband_fbo, c_w, c_h, 1, 0); - p->use_normalized_range = true; - } - - // Sample either directly or by upscaling - if ((p->image_desc.flags & MP_IMGFLAG_SUBSAMPLED) || prescaled) { - GLSLF("// chroma scaling\n"); - pass_sample(p, 1, &p->scaler[2], cscale, 1.0, - p->texture_w * scale_factor_x, - p->texture_h * scale_factor_y, chromafix); - GLSL(vec2 chroma = color.xy;) - } else { - GLSL(vec2 chroma = texture(texture1, texcoord1).xy;) - } - - p->pass_tex[0] = luma; // Restore the luma and alpha planes - p->pass_tex[3] = alpha; + // Most of the steps here don't actually apply image transformations yet, + // save for the actual upscaling - so as a code convenience we store them + // separately + struct gl_transform transforms[4]; + struct gl_transform tex_trans = identity_trans; + for (int i = 0; i < 4; i++) { + transforms[i] = tex[i].transform; + tex[i].transform = identity_trans; } - // Sample the main (luma/RGB) plane. - if (!prescaled && p->opts.deband) { - pass_sample_deband(p->sc, p->opts.deband_opts, 0, p->pass_tex[0].gl_target, - tex_mul, &p->lfg); - p->use_normalized_range = true; - } else { - if (!prescaled) { - GLSL(color = texture(texture0, texcoord0);) - } else { - // just use bilinear for non-essential planes. - GLSLF("color = texture(texture0, " - "texcoord0 + vec2(%f,%f) * pixel_size0);\n", - -offset.t[0] / scale_factor_x, - -offset.t[1] / scale_factor_y); + int prescale_passes = get_prescale_passes(p, tex); + + int dst_w = p->texture_w << prescale_passes, + dst_h = p->texture_h << prescale_passes; + + bool needs_deband[4]; + int scaler_id[4]; // ID if needed, -1 otherwise + int needs_prescale[4]; // number of prescaling passes left + + // Determine what needs to be done for which plane + for (int i=0; i < 4; i++) { + enum plane_type type = tex[i].type; + if (type == PLANE_NONE) { + needs_deband[i] = false; + needs_prescale[i] = 0; + scaler_id[i] = -1; + continue; } - if (p->use_normalized_range) - GLSLF("color *= %f;\n", tex_mul); - } - // Set up the right combination of planes - if (prescaled) { - // Restore texture4 and merge it into the main texture. - p->pass_tex[4] = prescaled_tex; + needs_deband[i] = type != PLANE_ALPHA ? p->opts.deband : false; + needs_prescale[i] = type == PLANE_LUMA ? prescale_passes : 0; - const char* planes_to_copy = "abgr" + 4 - prescaled_planes; - GLSLF("color.%s = texture(texture4, texcoord4).%s;\n", - planes_to_copy, planes_to_copy); + scaler_id[i] = -1; + switch (type) { + case PLANE_RGB: + case PLANE_LUMA: + case PLANE_XYZ: + scaler_id[i] = SCALER_SCALE; + break; - p->texture_w *= scale_factor_x; - p->texture_h *= scale_factor_y; - gl_transform_trans(offset, &p->texture_offset); - } - if (p->plane_count > 1) - GLSL(color.yz = chroma;) - if (p->has_alpha && p->plane_count >= 4) { - if (!prescaled) { - GLSL(color.a = texture(texture3, texcoord3).r;) - } else { - GLSLF("color.a = texture(texture3, " - "texcoord3 + vec2(%f,%f) * pixel_size3).r;", - -offset.t[0] / scale_factor_x, - -offset.t[1] / scale_factor_y); + case PLANE_CHROMA: + scaler_id[i] = SCALER_CSCALE; + break; + + case PLANE_ALPHA: // always use bilinear for alpha + default: + continue; } - if (p->use_normalized_range) - GLSLF("color.a *= %f;\n", tex_mul); + + // We can skip scaling if the texture is already at the required size + if (tex[i].w == dst_w && tex[i].h == dst_h) + scaler_id[i] = -1; } + // Process all the planes that need some action performed + while (true) { + // Find next plane to operate on + int n = -1; + for (int i = 0; i < 4; i++) { + if (tex[i].type != PLANE_NONE && + (scaler_id[i] >= 0 || needs_deband[i] || needs_prescale[i])) + { + n = i; + break; + } + } + + if (n == -1) // no textures left + break; + + // Figure out if it needs to be merged with anything else first + int o = -1; + for (int i = n+1; i < 4; i++) { + if (tex[i].type == tex[n].type + && tex[i].w == tex[n].w + && tex[i].h == tex[n].h + && gl_transform_eq(transforms[i], transforms[n])) + { + o = i; + break; + } + } + + // Multiple planes share the same dimensions and type, merge them for + // upscaling/debanding efficiency + if (o != -1) { + GLSLF("// merging plane %d into %d\n", o, n); + + int num = 0; + copy_img_tex(p, &num, tex[n]); + copy_img_tex(p, &num, tex[o]); + finish_pass_fbo(p, &p->merge_fbo[n], tex[n].w, tex[n].h, 0); + tex[n] = img_tex_fbo(&p->merge_fbo[n], identity_trans, + tex[n].type, num); + + memset(&tex[o], 0, sizeof(tex[o])); + continue; + } + + // The steps after this point (debanding, upscaling) can't handle + // integer textures, so the plane is still in that format by this point + // we need to ensure it gets converted + if (tex[n].use_integer) { + GLSLF("// use_integer fix for plane %d\n", n); + + copy_img_tex(p, &(int){0}, tex[n]); + finish_pass_fbo(p, &p->integer_fbo[n], tex[n].w, tex[n].h, 0); + tex[n] = img_tex_fbo(&p->integer_fbo[n], identity_trans, + tex[n].type, tex[n].components); + continue; + } + + // Plane is not yet debanded + if (needs_deband[n]) { + GLSLF("// debanding plane %d\n", n); + + int id = pass_bind(p, tex[n]); + pass_sample_deband(p->sc, p->opts.deband_opts, id, tex[n].multiplier, + tex[n].gl_target, &p->lfg); + skip_unused(p, tex[n].components); + finish_pass_fbo(p, &p->deband_fbo[n], tex[n].w, tex[n].h, 0); + tex[n] = img_tex_fbo(&p->deband_fbo[n], identity_trans, + tex[n].type, tex[n].components); + + needs_deband[n] = false; + continue; + } + + // Plane still needs prescaling passes + if (needs_prescale[n]) { + GLSLF("// prescaling plane %d (%d left)\n", n, needs_prescale[n]); + pass_prescale_luma(p, &tex[n], &tex_trans, + p->prescale_fbo[needs_prescale[n]-1]); + needs_prescale[n]--; + + // We can skip scaling if we arrived at our target res + if (tex[n].w == dst_w && tex[n].h == dst_h) + scaler_id[n] = -1; + + // If we're done prescaling, we need to adjust all of the + // other transforms to make sure the planes still align + if (needs_prescale[n] == 0) { + for (int i = 0; i < 4; i++) { + if (n == i) + continue; + + transforms[i].t[0] -= tex_trans.t[0] / tex_trans.m[0][0]; + transforms[i].t[1] -= tex_trans.t[1] / tex_trans.m[1][1]; + } + } + continue; + } + + // Plane is not yet upscaled + if (scaler_id[n] >= 0) { + const struct scaler_config *conf = &p->opts.scaler[scaler_id[n]]; + struct scaler *scaler = &p->scaler[scaler_id[n]]; + + // This is the only step that actually uses the transform + tex[n].transform = transforms[n]; + + // Bilinear scaling is a no-op due to GPU sampling + if (strcmp(conf->kernel.name, "bilinear") != 0) { + GLSLF("// upscaling plane %d\n", n); + pass_sample(p, tex[n], scaler, conf, 1.0, dst_w, dst_h); + finish_pass_fbo(p, &p->scale_fbo[n], dst_w, dst_h, FBOTEX_FUZZY); + tex[n] = img_tex_fbo(&p->scale_fbo[n], identity_trans, + tex[n].type, tex[n].components); + transforms[n] = identity_trans; + } + + scaler_id[n] = -1; + continue; + } + + // Execution should never reach this point + abort(); + } + + // All planes are of the same size and properly aligned at this point + GLSLF("// combining planes\n"); + int coord = 0; + for (int i = 0; i < 4; i++) { + if (tex[i].type != PLANE_NONE) + copy_img_tex(p, &coord, tex[i]); + } + + p->texture_w = dst_w; + p->texture_h = dst_h; + p->texture_offset = tex_trans; + p->components = coord; +} + +// Utility function that simply binds an FBO and reads from it, without any +// transformations. Returns the ID of the texture unit it was bound to +static int pass_read_fbo(struct gl_video *p, struct fbotex *fbo) +{ + struct img_tex tex = img_tex_fbo(fbo, identity_trans, PLANE_RGB, p->components); + copy_img_tex(p, &(int){0}, tex); + + return pass_bind(p, tex); } // yuv conversion, and any other conversions before main up/down-scaling @@ -1566,9 +1707,8 @@ static void pass_convert_yuv(struct gl_video *p) if (cparams.colorspace == MP_CSP_XYZ) GLSL(color.rgb = pow(color.rgb, vec3(2.6));) // linear light - // Something already took care of expansion - disable it. - if (p->use_normalized_range) - cparams.input_bits = cparams.texture_bits = 0; + // We always explicitly normalize the range in pass_read_video + cparams.input_bits = cparams.texture_bits = 0; // Conversion to RGB. For RGB itself, this still applies e.g. brightness // and contrast controls, or expansion of e.g. LSB-packed 10 bit data. @@ -1579,14 +1719,6 @@ static void pass_convert_yuv(struct gl_video *p) GLSL(color.rgb = mat3(colormatrix) * color.rgb + colormatrix_c;) - if (!p->use_normalized_range && p->has_alpha) { - float tex_mul = 1 / mp_get_csp_mul(p->image_params.colorspace, - p->image_desc.component_bits, - p->image_desc.component_full_bits); - gl_sc_uniform_f(p->sc, "tex_mul_alpha", tex_mul); - GLSL(color.a *= tex_mul_alpha;) - } - if (p->image_params.colorspace == MP_CSP_BT_2020_C) { // Conversion for C'rcY'cC'bc via the BT.2020 CL system: // C'bc = (B'-Y'c) / 1.9404 | C'bc <= 0 @@ -1619,52 +1751,38 @@ static void pass_convert_yuv(struct gl_video *p) lessThanEqual(vec3(0.0181), color.rgb));) } + p->components = 3; if (!p->has_alpha || p->opts.alpha_mode == 0) { // none GLSL(color.a = 1.0;) } else if (p->opts.alpha_mode == 2) { // blend against black GLSL(color = vec4(color.rgb * color.a, 1.0);) - } else if (p->opts.alpha_mode == 3) { // blend against tiles - GLSL(bvec2 tile = lessThan(fract(gl_FragCoord.xy / 32.0), vec2(0.5));) - GLSL(vec3 background = vec3(tile.x == tile.y ? 1.0 : 0.75);) - GLSL(color.rgb = color.rgb * color.a + background * (1.0 - color.a);) - } else if (p->gl->fb_premultiplied) { + } else { // alpha present in image + p->components = 4; GLSL(color = vec4(color.rgb * color.a, color.a);) } } -static void get_scale_factors(struct gl_video *p, double xy[2]) +static void get_scale_factors(struct gl_video *p, bool transpose_rot, double xy[2]) { - xy[0] = (p->dst_rect.x1 - p->dst_rect.x0) / - (double)(p->src_rect.x1 - p->src_rect.x0); - xy[1] = (p->dst_rect.y1 - p->dst_rect.y0) / - (double)(p->src_rect.y1 - p->src_rect.y0); + double target_w = p->src_rect.x1 - p->src_rect.x0; + double target_h = p->src_rect.y1 - p->src_rect.y0; + if (transpose_rot && p->image_params.rotate % 180 == 90) + MPSWAP(double, target_w, target_h); + xy[0] = (p->dst_rect.x1 - p->dst_rect.x0) / target_w; + xy[1] = (p->dst_rect.y1 - p->dst_rect.y0) / target_h; } -// Compute the cropped and rotated transformation of the video source rectangle. -// vp_w and vp_h are set to the _destination_ video size. -static void compute_src_transform(struct gl_video *p, struct gl_transform *tr, - int *vp_w, int *vp_h) +// Cropping. +static void compute_src_transform(struct gl_video *p, struct gl_transform *tr) { float sx = (p->src_rect.x1 - p->src_rect.x0) / (float)p->texture_w, sy = (p->src_rect.y1 - p->src_rect.y0) / (float)p->texture_h, ox = p->src_rect.x0, oy = p->src_rect.y0; - struct gl_transform transform = {{{sx,0.0}, {0.0,sy}}, {ox,oy}}; + struct gl_transform transform = {{{sx, 0}, {0, sy}}, {ox, oy}}; gl_transform_trans(p->texture_offset, &transform); - int xc = 0, yc = 1; - *vp_w = p->dst_rect.x1 - p->dst_rect.x0, - *vp_h = p->dst_rect.y1 - p->dst_rect.y0; - - if ((p->image_params.rotate % 180) == 90) { - MPSWAP(float, transform.m[0][xc], transform.m[0][yc]); - MPSWAP(float, transform.m[1][xc], transform.m[1][yc]); - MPSWAP(float, transform.t[0], transform.t[1]); - MPSWAP(int, xc, yc); - MPSWAP(int, *vp_w, *vp_h); - } - *tr = transform; } @@ -1673,7 +1791,7 @@ static void pass_scale_main(struct gl_video *p) { // Figure out the main scaler. double xy[2]; - get_scale_factors(p, xy); + get_scale_factors(p, true, xy); // actual scale factor should be divided by the scale factor of prescaling. xy[0] /= p->texture_offset.m[0][0]; @@ -1683,17 +1801,17 @@ static void pass_scale_main(struct gl_video *p) bool upscaling = !downscaling && (xy[0] > 1.0 || xy[1] > 1.0); double scale_factor = 1.0; - struct scaler *scaler = &p->scaler[0]; - struct scaler_config scaler_conf = p->opts.scaler[0]; + struct scaler *scaler = &p->scaler[SCALER_SCALE]; + struct scaler_config scaler_conf = p->opts.scaler[SCALER_SCALE]; if (p->opts.scaler_resizes_only && !downscaling && !upscaling) { scaler_conf.kernel.name = "bilinear"; // bilinear is going to be used, just remove all sub-pixel offsets. p->texture_offset.t[0] = (int)p->texture_offset.t[0]; p->texture_offset.t[1] = (int)p->texture_offset.t[1]; } - if (downscaling && p->opts.scaler[1].kernel.name) { - scaler_conf = p->opts.scaler[1]; - scaler = &p->scaler[1]; + if (downscaling && p->opts.scaler[SCALER_DSCALE].kernel.name) { + scaler_conf = p->opts.scaler[SCALER_DSCALE]; + scaler = &p->scaler[SCALER_DSCALE]; } // When requesting correct-downscaling and the clip is anamorphic, and @@ -1726,14 +1844,16 @@ static void pass_scale_main(struct gl_video *p) sig_center, sig_scale, sig_offset, sig_slope); } + int vp_w = p->dst_rect.x1 - p->dst_rect.x0; + int vp_h = p->dst_rect.y1 - p->dst_rect.y0; struct gl_transform transform; - int vp_w, vp_h; - compute_src_transform(p, &transform, &vp_w, &vp_h); + compute_src_transform(p, &transform); GLSLF("// main scaling\n"); - finish_pass_fbo(p, &p->indirect_fbo, p->texture_w, p->texture_h, 0, 0); - pass_sample(p, 0, scaler, &scaler_conf, scale_factor, vp_w, vp_h, - transform); + finish_pass_fbo(p, &p->indirect_fbo, p->texture_w, p->texture_h, 0); + struct img_tex src = img_tex_fbo(&p->indirect_fbo, transform, + PLANE_RGB, p->components); + pass_sample(p, src, scaler, &scaler_conf, scale_factor, vp_w, vp_h); // Changes the texture size to display size after main scaler. p->texture_w = vp_w; @@ -1757,10 +1877,16 @@ static void pass_colormanage(struct gl_video *p, enum mp_csp_prim prim_src, enum mp_csp_prim prim_dst = p->opts.target_prim; if (p->use_lut_3d) { - // The 3DLUT is hard-coded against BT.2020's gamut during creation, and - // we never want to adjust its output (so treat it as linear) - prim_dst = MP_CSP_PRIM_BT_2020; - trc_dst = MP_CSP_TRC_LINEAR; + // The 3DLUT is always generated against the original source space + enum mp_csp_prim prim_orig = p->image_params.primaries; + enum mp_csp_trc trc_orig = p->image_params.gamma; + + if (gl_video_get_lut3d(p, prim_orig, trc_orig)) { + prim_dst = prim_orig; + trc_dst = trc_orig; + } else { + p->use_lut_3d = false; + } } if (prim_dst == MP_CSP_PRIM_AUTO) @@ -1774,10 +1900,10 @@ static void pass_colormanage(struct gl_video *p, enum mp_csp_prim prim_src, trc_dst = MP_CSP_TRC_GAMMA22; } - bool need_cms = prim_src != prim_dst || p->use_lut_3d; - bool need_gamma = trc_src != trc_dst || need_cms; + bool need_gamma = trc_src != trc_dst || prim_src != prim_dst; if (need_gamma) pass_linearize(p->sc, trc_src); + // Adapt to the right colorspace if necessary if (prim_src != prim_dst) { struct mp_csp_primaries csp_src = mp_get_csp_primaries(prim_src), @@ -1787,16 +1913,14 @@ static void pass_colormanage(struct gl_video *p, enum mp_csp_prim prim_src, gl_sc_uniform_mat3(p->sc, "cms_matrix", true, &m[0][0]); GLSL(color.rgb = cms_matrix * color.rgb;) } - if (p->use_lut_3d) { - gl_sc_uniform_sampler(p->sc, "lut_3d", GL_TEXTURE_3D, TEXUNIT_3DLUT); - // For the 3DLUT we are arbitrarily using 2.4 as input gamma to reduce - // the severity of quantization errors. - GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);) - GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));) - GLSL(color.rgb = texture3D(lut_3d, color.rgb).rgb;) - } + if (need_gamma) pass_delinearize(p->sc, trc_dst); + + if (p->use_lut_3d) { + gl_sc_uniform_sampler(p->sc, "lut_3d", GL_TEXTURE_3D, TEXUNIT_3DLUT); + GLSL(color.rgb = texture3D(lut_3d, color.rgb).rgb;) + } } static void pass_dither(struct gl_video *p) @@ -1948,35 +2072,23 @@ static void pass_render_frame_dumb(struct gl_video *p, int fbo) { p->gl->BindFramebuffer(GL_FRAMEBUFFER, fbo); - struct gl_transform chromafix; - pass_set_image_textures(p, &p->image, &chromafix); + struct img_tex tex[4]; + pass_get_img_tex(p, &p->image, tex); struct gl_transform transform; - int vp_w, vp_h; - compute_src_transform(p, &transform, &vp_w, &vp_h); + compute_src_transform(p, &transform); struct gl_transform tchroma = transform; tchroma.t[0] /= 1 << p->image_desc.chroma_xs; tchroma.t[1] /= 1 << p->image_desc.chroma_ys; - gl_transform_rect(transform, &p->pass_tex[0].src); - for (int n = 1; n < 3; n++) { - gl_transform_rect(chromafix, &p->pass_tex[n].src); - gl_transform_rect(tchroma, &p->pass_tex[n].src); + int index = 0; + for (int i = 0; i < p->plane_count; i++) { + gl_transform_trans(tex[i].type == PLANE_CHROMA ? tchroma : transform, + &tex[i].transform); + copy_img_tex(p, &index, tex[i]); } - gl_transform_rect(transform, &p->pass_tex[3].src); - GLSL(color = texture(texture0, texcoord0);) - if (p->image_desc.flags & MP_IMGFLAG_YUV_NV) { - GLSL(color.gb = texture(texture1, texcoord1).RG;) - } else if (p->plane_count >= 3) { - GLSL(color.g = texture(texture1, texcoord1).r;) - GLSL(color.b = texture(texture2, texcoord2).r;) - } - if (p->plane_count >= 4) - GLSL(color.a = texture(texture3, texcoord3).r;); - - p->use_normalized_range = false; pass_convert_yuv(p); } @@ -1987,7 +2099,11 @@ static void pass_render_frame(struct gl_video *p) // initialize the texture parameters p->texture_w = p->image_params.w; p->texture_h = p->image_params.h; - p->texture_offset = (struct gl_transform){{{1.0,0.0}, {0.0,1.0}}, {0.0,0.0}}; + p->texture_offset = identity_trans; + p->components = 0; + + if (p->image_params.rotate % 180 == 90) + MPSWAP(int, p->texture_w, p->texture_h); if (p->dumb_mode) return; @@ -2003,24 +2119,24 @@ static void pass_render_frame(struct gl_video *p) if (p->osd && p->opts.blend_subs == 2) { double scale[2]; - get_scale_factors(p, scale); + get_scale_factors(p, false, scale); struct mp_osd_res rect = { .w = p->texture_w, .h = p->texture_h, .display_par = scale[1] / scale[0], // counter compensate scaling }; - finish_pass_fbo(p, &p->blend_subs_fbo, - p->texture_w, p->texture_h, 0, 0); + finish_pass_fbo(p, &p->blend_subs_fbo, rect.w, rect.h, 0); pass_draw_osd(p, OSD_DRAW_SUB_ONLY, vpts, rect, - p->texture_w, p->texture_h, p->blend_subs_fbo.fbo, false); + rect.w, rect.h, p->blend_subs_fbo.fbo, false); GLSL(color = texture(texture0, texcoord0);) + pass_read_fbo(p, &p->blend_subs_fbo); } - apply_shaders(p, p->opts.pre_shaders, &p->pre_fbo[0], 0, - p->texture_w, p->texture_h); + apply_shaders(p, p->opts.pre_shaders, p->texture_w, p->texture_h, p->pre_fbo); if (p->opts.unsharp != 0.0) { - finish_pass_fbo(p, &p->unsharp_fbo, p->texture_w, p->texture_h, 0, 0); - pass_sample_unsharp(p->sc, p->opts.unsharp); + finish_pass_fbo(p, &p->unsharp_fbo, p->texture_w, p->texture_h, 0); + int id = pass_read_fbo(p, &p->unsharp_fbo); + pass_sample_unsharp(p->sc, id, p->opts.unsharp); } pass_scale_main(p); @@ -2037,23 +2153,22 @@ static void pass_render_frame(struct gl_video *p) }; // Adjust margins for scale double scale[2]; - get_scale_factors(p, scale); + get_scale_factors(p, true, scale); rect.ml *= scale[0]; rect.mr *= scale[0]; rect.mt *= scale[1]; rect.mb *= scale[1]; // We should always blend subtitles in non-linear light if (p->use_linear) pass_delinearize(p->sc, p->image_params.gamma); - finish_pass_fbo(p, &p->blend_subs_fbo, p->texture_w, p->texture_h, 0, + finish_pass_fbo(p, &p->blend_subs_fbo, p->texture_w, p->texture_h, FBOTEX_FUZZY); pass_draw_osd(p, OSD_DRAW_SUB_ONLY, vpts, rect, p->texture_w, p->texture_h, p->blend_subs_fbo.fbo, false); - GLSL(color = texture(texture0, texcoord0);) + pass_read_fbo(p, &p->blend_subs_fbo); if (p->use_linear) pass_linearize(p->sc, p->image_params.gamma); } - apply_shaders(p, p->opts.post_shaders, &p->post_fbo[0], 0, - p->texture_w, p->texture_h); + apply_shaders(p, p->opts.post_shaders, p->texture_w, p->texture_h, p->post_fbo); } static void pass_draw_to_screen(struct gl_video *p, int fbo) @@ -2067,12 +2182,20 @@ static void pass_draw_to_screen(struct gl_video *p, int fbo) GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);) GLSL(color.rgb = pow(color.rgb, vec3(user_gamma));) } + pass_colormanage(p, p->image_params.primaries, p->use_linear ? MP_CSP_TRC_LINEAR : p->image_params.gamma); + + // Draw checkerboard pattern to indicate transparency + if (p->has_alpha && p->opts.alpha_mode == 3) { + GLSLF("// transparency checkerboard\n"); + GLSL(bvec2 tile = lessThan(fract(gl_FragCoord.xy / 32.0), vec2(0.5));) + GLSL(vec3 background = vec3(tile.x == tile.y ? 1.0 : 0.75);) + GLSL(color.rgb = mix(background, color.rgb, color.a);) + } + pass_dither(p); - int flags = (p->image_params.rotate % 90 ? 0 : p->image_params.rotate / 90) - | (p->image.image_flipped ? 4 : 0); - finish_pass_direct(p, fbo, p->vp_w, p->vp_h, &p->dst_rect, flags); + finish_pass_direct(p, fbo, p->vp_w, p->vp_h, &p->dst_rect); } // Draws an interpolate frame to fbo, based on the frame timing in t @@ -2094,7 +2217,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t, 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); + vp_w, vp_h, FBOTEX_FUZZY); p->surfaces[p->surface_now].pts = p->image.mpi->pts; p->surface_idx = p->surface_now; } @@ -2115,8 +2238,8 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t, // look like this: _ A [B] C D _ // A is surface_bse, B is surface_now, C is surface_now+1 and D is // surface_end. - struct scaler *tscale = &p->scaler[3]; - reinit_scaler(p, tscale, &p->opts.scaler[3], 1, tscale_sizes); + struct scaler *tscale = &p->scaler[SCALER_TSCALE]; + reinit_scaler(p, tscale, &p->opts.scaler[SCALER_TSCALE], 1, tscale_sizes); bool oversample = strcmp(tscale->conf.kernel.name, "oversample") == 0; int size; @@ -2153,7 +2276,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t, 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); + vp_w, vp_h, FBOTEX_FUZZY); p->surfaces[surface_dst].pts = f->pts; p->surface_idx = surface_dst; surface_dst = fbosurface_wrap(surface_dst+1); @@ -2184,8 +2307,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t, // Finally, draw the right mix of frames to the screen. 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, vp_w, vp_h, 0); - GLSL(color = texture(texture0, texcoord0);) + pass_read_fbo(p, &p->surfaces[surface_now].fbotex); p->is_interpolated = false; } else { double mix = t->vsync_offset / t->ideal_frame_duration; @@ -2225,8 +2347,14 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t, // Load all the required frames for (int i = 0; i < size; i++) { - pass_load_fbotex(p, &p->surfaces[fbosurface_wrap(surface_bse+i)].fbotex, - vp_w, vp_h, i); + struct img_tex img = + img_tex_fbo(&p->surfaces[fbosurface_wrap(surface_bse+i)].fbotex, + identity_trans, PLANE_RGB, p->components); + // Since the code in pass_sample_separated currently assumes + // the textures are bound in-order and starting at 0, we just + // assert to make sure this is the case (which it should always be) + int id = pass_bind(p, img); + assert(id == i); } MP_DBG(p, "inter frame dur: %f vsync: %f, mix: %f\n", @@ -2344,6 +2472,9 @@ void gl_video_resize(struct gl_video *p, int vp_w, int vp_h, p->vp_h = vp_h; gl_video_reset_surfaces(p); + + if (p->osd) + mpgl_osd_resize(p->osd, p->osd_rect, p->image_params.stereo_out); } static bool unmap_image(struct gl_video *p, struct mp_image *mpi) @@ -2472,13 +2603,15 @@ static bool check_dumb_mode(struct gl_video *p) return true; if (o->target_prim || o->target_trc || o->linear_scaling || o->correct_downscaling || o->sigmoid_upscaling || o->interpolation || - o->blend_subs || o->deband || o->unsharp || o->prescale) + o->blend_subs || o->deband || o->unsharp || o->prescale_luma) return false; - // check scale, dscale, cscale (tscale is already implicitly excluded above) - for (int i = 0; i < 3; i++) { - const char *name = o->scaler[i].kernel.name; - if (name && strcmp(name, "bilinear") != 0) - return false; + // check remaining scalers (tscale is already implicitly excluded above) + for (int i = 0; i < SCALER_COUNT; i++) { + if (i != SCALER_TSCALE) { + const char *name = o->scaler[i].kernel.name; + if (name && strcmp(name, "bilinear") != 0) + return false; + } } if (o->pre_shaders && o->pre_shaders[0]) return false; @@ -2534,13 +2667,9 @@ static void check_gl_features(struct gl_video *p) .use_rectangle = p->opts.use_rectangle, .background = p->opts.background, .dither_algo = -1, - .scaler = { - gl_video_opts_def.scaler[0], - gl_video_opts_def.scaler[1], - gl_video_opts_def.scaler[2], - gl_video_opts_def.scaler[3], - }, }; + for (int n = 0; n < SCALER_COUNT; n++) + new_opts.scaler[n] = gl_video_opts_def.scaler[n]; assign_options(&p->opts, &new_opts); p->opts.deband_opts = m_config_alloc_struct(NULL, &deband_conf); return; @@ -2551,7 +2680,7 @@ static void check_gl_features(struct gl_video *p) // because they will be slow (not critically slow, but still slower). // Without FP textures, we must always disable them. // I don't know if luminance alpha float textures exist, so disregard them. - for (int n = 0; n < 4; n++) { + for (int n = 0; n < SCALER_COUNT; n++) { const struct filter_kernel *kernel = mp_find_filter_kernel(p->opts.scaler[n].kernel.name); if (kernel) { @@ -2567,7 +2696,7 @@ static void check_gl_features(struct gl_video *p) // GLES3 doesn't provide filtered 16 bit integer textures // GLES2 doesn't even provide 3D textures - if (p->use_lut_3d && !(have_3d_tex && have_float_tex)) { + if (p->use_lut_3d && (!have_3d_tex || gl->es)) { p->use_lut_3d = false; MP_WARN(p, "Disabling color management (GLES unsupported).\n"); } @@ -2592,13 +2721,13 @@ static void check_gl_features(struct gl_video *p) MP_WARN(p, "Disabling debanding (GLSL version too old).\n"); } - if (p->opts.prescale == 2) { + if (p->opts.prescale_luma == 2) { if (p->opts.nnedi3_opts->upload == NNEDI3_UPLOAD_UBO) { // Check features for uniform buffer objects. if (!gl->BindBufferBase || !gl->GetUniformBlockIndex) { MP_WARN(p, "Disabling NNEDI3 (%s required).\n", gl->es ? "OpenGL ES 3.0" : "OpenGL 3.1"); - p->opts.prescale = 0; + p->opts.prescale_luma = 0; } } else if (p->opts.nnedi3_opts->upload == NNEDI3_UPLOAD_SHADER) { // Check features for hard coding approach. @@ -2607,7 +2736,7 @@ static void check_gl_features(struct gl_video *p) { MP_WARN(p, "Disabling NNEDI3 (%s required).\n", gl->es ? "OpenGL ES 3.0" : "OpenGL 3.3"); - p->opts.prescale = 0; + p->opts.prescale_luma = 0; } } } @@ -2887,7 +3016,8 @@ 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 mpv_global *g) +struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct mpv_global *g, + struct gl_lcms *cms) { if (gl->version < 210 && gl->es < 200) { mp_err(log, "At least OpenGL 2.1 or OpenGL ES 2.0 required.\n"); @@ -2899,12 +3029,14 @@ struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct mpv_global *g) .gl = gl, .global = g, .log = log, + .cms = cms, .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), }; + for (int n = 0; n < SCALER_COUNT; n++) + p->scaler[n] = (struct scaler){.index = n}; gl_video_set_debug(p, true); init_gl(p); recreate_osd(p); @@ -2967,9 +3099,10 @@ static void assign_options(struct gl_video_opts *dst, struct gl_video_opts *src) src->nnedi3_opts); } - for (int n = 0; n < 4; n++) { + for (int n = 0; n < SCALER_COUNT; n++) { dst->scaler[n].kernel.name = - (char *)handle_scaler_opt(dst->scaler[n].kernel.name, n == 3); + (char *)handle_scaler_opt(dst->scaler[n].kernel.name, + n == SCALER_TSCALE); } dst->scale_shader = talloc_strdup(NULL, dst->scale_shader); @@ -3001,10 +3134,10 @@ void gl_video_configure_queue(struct gl_video *p, struct vo *vo) // 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); + mp_find_filter_kernel(p->opts.scaler[SCALER_TSCALE].kernel.name); if (kernel) { double radius = kernel->f.radius; - radius = radius > 0 ? radius : p->opts.scaler[3].radius; + radius = radius > 0 ? radius : p->opts.scaler[SCALER_TSCALE].radius; queue_size += 1 + ceil(radius); } else { // Oversample case diff --git a/video/out/opengl/video.h b/video/out/opengl/video.h index a1918bef39..4702f8cc79 100644 --- a/video/out/opengl/video.h +++ b/video/out/opengl/video.h @@ -24,6 +24,7 @@ #include "sub/osd.h" #include "common.h" #include "utils.h" +#include "lcms.h" #include "video/out/filter_kernels.h" // Texture units 0-5 are used by the video, and for free use by the passes @@ -31,7 +32,7 @@ // Other texture units are reserved for specific purposes #define TEXUNIT_SCALERS TEXUNIT_VIDEO_NUM -#define TEXUNIT_3DLUT (TEXUNIT_SCALERS+4) +#define TEXUNIT_3DLUT (TEXUNIT_SCALERS+SCALER_COUNT) #define TEXUNIT_DITHER (TEXUNIT_3DLUT+1) struct lut3d { @@ -69,6 +70,14 @@ struct scaler { struct filter_kernel kernel_storage; }; +enum scaler_unit { + SCALER_SCALE, // luma/video + SCALER_DSCALE, // luma-video downscaling + SCALER_CSCALE, // chroma upscaling + SCALER_TSCALE, // temporal scaling (interpolation) + SCALER_COUNT +}; + struct gl_video_opts { int dumb_mode; struct scaler_config scaler[4]; @@ -102,7 +111,7 @@ struct gl_video_opts { int deband; struct deband_opts *deband_opts; float unsharp; - int prescale; + int prescale_luma; int prescale_passes; float prescale_downscaling_threshold; struct superxbr_opts *superxbr_opts; @@ -116,14 +125,15 @@ 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 mpv_global *g); +struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct mpv_global *g, + struct gl_lcms *cms); 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); 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_update_profile(struct gl_video *p); 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, diff --git a/video/out/opengl/video_shaders.c b/video/out/opengl/video_shaders.c index cd83ef5c97..bea1bbf325 100644 --- a/video/out/opengl/video_shaders.c +++ b/video/out/opengl/video_shaders.c @@ -250,7 +250,8 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) lessThan(vec3(0.04045), color.rgb));) break; case MP_CSP_TRC_BT_1886: - GLSL(color.rgb = pow(color.rgb, vec3(1.961));) + // We don't have an actual black point, so we assume a perfect display + GLSL(color.rgb = pow(color.rgb, vec3(2.4));) break; case MP_CSP_TRC_GAMMA18: GLSL(color.rgb = pow(color.rgb, vec3(1.8));) @@ -284,7 +285,7 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) lessThanEqual(vec3(0.0031308), color.rgb));) break; case MP_CSP_TRC_BT_1886: - GLSL(color.rgb = pow(color.rgb, vec3(1.0/1.961));) + GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));) break; case MP_CSP_TRC_GAMMA18: GLSL(color.rgb = pow(color.rgb, vec3(1.0/1.8));) @@ -348,10 +349,9 @@ const struct m_sub_options deband_conf = { // Stochastically sample a debanded result from a given texture void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts, - int tex_num, GLenum tex_target, float tex_mul, AVLFG *lfg) + int tex_num, float tex_mul, GLenum tex_target, AVLFG *lfg) { // Set up common variables and initialize the PRNG - GLSLF("// debanding (tex %d)\n", tex_num); GLSLF("{\n"); sampler_prelude(sc, tex_num); prng_init(sc, lfg); @@ -399,10 +399,10 @@ void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts, GLSLF("}\n"); } -void pass_sample_unsharp(struct gl_shader_cache *sc, float param) +void pass_sample_unsharp(struct gl_shader_cache *sc, int tex_num, float param) { GLSLF("// unsharp\n"); - sampler_prelude(sc, 0); + sampler_prelude(sc, tex_num); GLSLF("{\n"); GLSL(vec2 st1 = pt * 1.2;) diff --git a/video/out/opengl/video_shaders.h b/video/out/opengl/video_shaders.h index 27a021662a..e010fdba55 100644 --- a/video/out/opengl/video_shaders.h +++ b/video/out/opengl/video_shaders.h @@ -39,8 +39,8 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc); void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc); void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts, - int tex_num, GLenum tex_target, float tex_mul, AVLFG *lfg); + int tex_num, float tex_mul, GLenum tex_target, AVLFG *lfg); -void pass_sample_unsharp(struct gl_shader_cache *sc, float param); +void pass_sample_unsharp(struct gl_shader_cache *sc, int tex_num, float param); #endif diff --git a/video/out/vo.c b/video/out/vo.c index 5c56f62690..3e7999a698 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -150,6 +150,7 @@ struct vo_internal { double estimated_vsync_interval; double estimated_vsync_jitter; bool expecting_vsync; + int64_t num_successive_vsyncs; int64_t flip_queue_offset; // queue flip events at most this much in advance @@ -247,6 +248,7 @@ static struct vo *vo_create(bool probing, struct mpv_global *global, *vo->in = (struct vo_internal) { .dispatch = mp_dispatch_create(vo), .req_frames = 1, + .estimated_vsync_jitter = -1, }; mp_make_wakeup_pipe(vo->in->wakeup_pipe); mp_dispatch_set_wakeup_fn(vo->in->dispatch, dispatch_wakeup_cb, vo); @@ -321,13 +323,10 @@ void vo_destroy(struct vo *vo) static void reset_vsync_timings(struct vo *vo) { struct vo_internal *in = vo->in; - in->num_vsync_samples = 0; - in->num_total_vsync_samples = 0; in->drop_point = 0; - in->estimated_vsync_interval = 0; - in->estimated_vsync_jitter = -1; in->base_vsync = 0; in->expecting_vsync = false; + in->num_successive_vsyncs = 0; } static double vsync_stddef(struct vo *vo, int64_t ref_vsync) @@ -419,17 +418,23 @@ static void update_vsync_timing_after_swap(struct vo *vo) struct vo_internal *in = vo->in; int64_t now = mp_time_us(); + int64_t prev_vsync = in->prev_vsync; + + in->prev_vsync = now; if (!in->expecting_vsync) { - in->prev_vsync = now; // for normal system-time framedrop reset_vsync_timings(vo); return; } + in->num_successive_vsyncs++; + if (in->num_successive_vsyncs <= 2) + return; + if (in->num_vsync_samples >= MAX_VSYNC_SAMPLES) in->num_vsync_samples -= 1; MP_TARRAY_INSERT_AT(in, in->vsync_samples, in->num_vsync_samples, 0, - now - in->prev_vsync); + now - prev_vsync); in->drop_point = MPMIN(in->drop_point + 1, in->num_vsync_samples); in->num_total_vsync_samples += 1; if (in->base_vsync) { @@ -437,7 +442,6 @@ static void update_vsync_timing_after_swap(struct vo *vo) } else { in->base_vsync = now; } - in->prev_vsync = now; double avg = 0; for (int n = 0; n < in->num_vsync_samples; n++) @@ -788,9 +792,10 @@ static bool render_frame(struct vo *vo) if (in->current_frame->num_vsyncs > 0) in->current_frame->num_vsyncs -= 1; - in->expecting_vsync = in->current_frame->display_synced && !in->paused; - if (in->expecting_vsync && !in->num_vsync_samples) // first DS frame in a row + bool use_vsync = in->current_frame->display_synced && !in->paused; + if (use_vsync && !in->expecting_vsync) // first DS frame in a row in->prev_vsync = now; + in->expecting_vsync = use_vsync; if (in->dropped_frame) { in->drop_count += 1; diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c index 7f4f13f882..dfef6ec500 100644 --- a/video/out/vo_opengl.c +++ b/video/out/vo_opengl.c @@ -217,7 +217,7 @@ static void call_request_hwdec_api(struct mp_hwdec_info *info, vo_control(vo, VOCTRL_LOAD_HWDEC_API, (void *)api_name); } -static bool get_and_update_icc_profile(struct gl_priv *p, int *events) +static void get_and_update_icc_profile(struct gl_priv *p, int *events) { bool has_profile = p->icc_opts->profile && p->icc_opts->profile[0]; if (p->icc_opts->profile_auto && !has_profile) { @@ -233,17 +233,12 @@ static bool get_and_update_icc_profile(struct gl_priv *p, int *events) } gl_lcms_set_memory_profile(p->cms, &icc); + has_profile = true; } } - struct lut3d *lut3d = NULL; - if (!gl_lcms_has_changed(p->cms)) - return true; - if (gl_lcms_get_lut3d(p->cms, &lut3d) && !lut3d) - return false; - gl_video_set_lut3d(p->renderer, lut3d); - talloc_free(lut3d); - return true; + if (has_profile) + gl_video_update_profile(p->renderer); } static void get_and_update_ambient_lighting(struct gl_priv *p, int *events) @@ -416,19 +411,18 @@ static int preinit(struct vo *vo) MP_VERBOSE(vo, "swap_control extension missing.\n"); } - p->renderer = gl_video_init(p->gl, vo->log, vo->global); + p->cms = gl_lcms_init(p, vo->log, vo->global); + if (!p->cms) + goto err_out; + p->renderer = gl_video_init(p->gl, vo->log, vo->global, p->cms); if (!p->renderer) goto err_out; gl_video_set_osd_source(p->renderer, vo->osd); 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) - goto err_out; gl_lcms_set_options(p->cms, p->icc_opts); - if (!get_and_update_icc_profile(p, &(int){0})) - goto err_out; + get_and_update_icc_profile(p, &(int){0}); p->hwdec_info.load_api = call_request_hwdec_api; p->hwdec_info.load_api_ctx = vo; diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c index 7accfc1a92..40930fbcae 100644 --- a/video/out/vo_opengl_cb.c +++ b/video/out/vo_opengl_cb.c @@ -176,7 +176,7 @@ 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->global); + ctx->renderer = gl_video_init(ctx->gl, ctx->log, ctx->global, NULL); if (!ctx->renderer) return MPV_ERROR_UNSUPPORTED; diff --git a/video/out/vo_x11.c b/video/out/vo_x11.c index 28262231ac..7676155ead 100644 --- a/video/out/vo_x11.c +++ b/video/out/vo_x11.c @@ -407,6 +407,8 @@ static int preinit(struct vo *vo) goto error; p->gc = XCreateGC(x11->display, x11->window, 0, NULL); + MP_WARN(vo, "Warning: this legacy VO has bad performance. Consider fixing" + "your graphic drivers, or not forcing the x11 VO.\n"); return 0; error: diff --git a/video/out/vo_xv.c b/video/out/vo_xv.c index ad40fd8376..e02ea2d046 100644 --- a/video/out/vo_xv.c +++ b/video/out/vo_xv.c @@ -843,6 +843,9 @@ static int preinit(struct vo *vo) ctx->fo = XvListImageFormats(x11->display, ctx->xv_port, (int *) &ctx->formats); + MP_WARN(vo, "Warning: this legacy VO has bad quality and performance, " + "and will in particular result in blurry OSD and subtitles. " + "You should fix your graphic drivers, or not force the xv VO.\n"); return 0; error: diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index 73ef5ed570..b9dac9049c 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -141,11 +141,6 @@ static void ssurface_handle_configure(void *data, { struct vo_wayland_state *wl = data; schedule_resize(wl, edges, width, height); - - // check for every resize if the window is in fullscreen mode and reset the - // mode accordingly, this prevents gnome from resizing mpvs fullscreen - // window to a smaller size - vo_wayland_fullscreen(wl->vo); } static void ssurface_handle_popup_done(void *data, @@ -1056,6 +1051,7 @@ static void vo_wayland_ontop (struct vo *vo) MP_DBG(wl, "going ontop\n"); vo->opts->ontop = 1; window_set_toplevel(wl); + schedule_resize(wl, 0, wl->window.width, wl->window.height); } static void vo_wayland_fullscreen (struct vo *vo) @@ -1080,6 +1076,7 @@ static void vo_wayland_fullscreen (struct vo *vo) MP_DBG(wl, "leaving fullscreen\n"); wl->window.is_fullscreen = false; window_set_toplevel(wl); + schedule_resize(wl, 0, wl->window.p_width, wl->window.p_height); } } @@ -1227,6 +1224,9 @@ static void vo_wayland_update_screeninfo(struct vo *vo, struct mp_rect *screenrc screenrc->y1 = wl->display.current_output->height; } } + + wl->window.fs_width = screenrc->x1; + wl->window.fs_height = screenrc->y1; } int vo_wayland_control (struct vo *vo, int *events, int request, void *arg) @@ -1301,7 +1301,6 @@ bool vo_wayland_config (struct vo *vo) wl->window.width = vo->dwidth; wl->window.height = vo->dheight; - vo_wayland_fullscreen(vo); return true; diff --git a/video/out/x11_common.c b/video/out/x11_common.c index 5033a0f691..034f785cbc 100644 --- a/video/out/x11_common.c +++ b/video/out/x11_common.c @@ -146,6 +146,22 @@ static void vo_x11_move_resize(struct vo *vo, bool move, bool resize, #define RC_W(rc) ((rc).x1 - (rc).x0) #define RC_H(rc) ((rc).y1 - (rc).y0) +static char *x11_atom_name_buf(struct vo_x11_state *x11, Atom atom, + char *buf, size_t buf_size) +{ + buf[0] = '\0'; + + char *new_name = XGetAtomName(x11->display, atom); + if (new_name) { + snprintf(buf, buf_size, "%s", new_name); + XFree(new_name); + } + + return buf; +} + +#define x11_atom_name(x11, atom) x11_atom_name_buf(x11, atom, (char[80]){0}, 80) + // format = 8 (unsigned char), 16 (short), 32 (long, even on LP64 systems) // *out_nitems = returned number of items of requested format static void *x11_get_property(struct vo_x11_state *x11, Window w, Atom property, @@ -518,6 +534,7 @@ int vo_x11_init(struct vo *vo) struct vo_x11_state *x11 = talloc_ptrtype(NULL, x11); *x11 = (struct vo_x11_state){ .log = mp_log_new(x11, vo->log, "x11"), + .input_ctx = vo->input_ctx, .screensaver_enabled = true, .xrandr_event = -1, }; @@ -709,7 +726,7 @@ void vo_x11_uninit(struct vo *vo) if (!x11) return; - mp_input_put_key(vo->input_ctx, MP_INPUT_RELEASE_ALL); + mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); set_screensaver(x11, true); @@ -756,13 +773,43 @@ static void vo_x11_dnd_init_window(struct vo *vo) 32, PropModeReplace, (unsigned char *)&version, 1); } +// The Atom does not always map to a mime type, but often. +static char *x11_dnd_mime_type_buf(struct vo_x11_state *x11, Atom atom, + char *buf, size_t buf_size) +{ + if (atom == XInternAtom(x11->display, "UTF8_STRING", False)) + return "text"; + return x11_atom_name_buf(x11, atom, buf, buf_size); +} + +#define x11_dnd_mime_type(x11, atom) \ + x11_dnd_mime_type_buf(x11, atom, (char[80]){0}, 80) + +static bool dnd_format_is_better(struct vo_x11_state *x11, Atom cur, Atom new) +{ + int new_score = mp_event_get_mime_type_score(x11->input_ctx, + x11_dnd_mime_type(x11, new)); + int cur_score = -1; + if (cur) { + cur_score = mp_event_get_mime_type_score(x11->input_ctx, + x11_dnd_mime_type(x11, cur)); + } + return new_score >= 0 && new_score > cur_score; +} + static void dnd_select_format(struct vo_x11_state *x11, Atom *args, int items) { + x11->dnd_requested_format = 0; + for (int n = 0; n < items; n++) { + MP_VERBOSE(x11, "DnD type: '%s'\n", x11_atom_name(x11, args[n])); // There are other types; possibly not worth supporting. - if (args[n] == XInternAtom(x11->display, "text/uri-list", False)) + if (dnd_format_is_better(x11, x11->dnd_requested_format, args[n])) x11->dnd_requested_format = args[n]; } + + MP_VERBOSE(x11, "Selected DnD type: %s\n", x11->dnd_requested_format ? + x11_atom_name(x11, x11->dnd_requested_format) : "(none)"); } static void dnd_reset(struct vo *vo) @@ -848,8 +895,12 @@ static void vo_x11_dnd_handle_selection(struct vo *vo, XSelectionEvent *se) x11->dnd_requested_action == XA(x11, XdndActionCopy) ? DND_REPLACE : DND_APPEND; + char *mime_type = x11_dnd_mime_type(x11, x11->dnd_requested_format); + MP_VERBOSE(x11, "Dropping type: %s (%s)\n", + x11_atom_name(x11, x11->dnd_requested_format), mime_type); + // 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", + success = mp_event_drop_mime_data(x11->input_ctx, mime_type, (bstr){prop, nitems}, action) > 0; XFree(prop); } @@ -976,17 +1027,17 @@ int vo_x11_check_events(struct vo *vo) sizeof(buf), &keySym, &status); int mpkey = vo_x11_lookupkey(keySym); if (mpkey) { - mp_input_put_key(vo->input_ctx, mpkey | modifiers); + mp_input_put_key(x11->input_ctx, mpkey | modifiers); } else if (status == XLookupChars || status == XLookupBoth) { struct bstr t = { buf, len }; - mp_input_put_key_utf8(vo->input_ctx, modifiers, t); + mp_input_put_key_utf8(x11->input_ctx, modifiers, t); } } else { XLookupString(&Event.xkey, buf, sizeof(buf), &keySym, &x11->compose_status); int mpkey = vo_x11_lookupkey(keySym); if (mpkey) - mp_input_put_key(vo->input_ctx, mpkey | modifiers); + mp_input_put_key(x11->input_ctx, mpkey | modifiers); } break; } @@ -996,16 +1047,16 @@ int vo_x11_check_events(struct vo *vo) case KeyRelease: { if (x11->no_autorepeat) - mp_input_put_key(vo->input_ctx, MP_INPUT_RELEASE_ALL); + mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); x11->win_drag_button1_down = false; break; } case MotionNotify: if (x11->win_drag_button1_down && !x11->fs && - !mp_input_test_dragging(vo->input_ctx, Event.xmotion.x, - Event.xmotion.y)) + !mp_input_test_dragging(x11->input_ctx, Event.xmotion.x, + Event.xmotion.y)) { - mp_input_put_key(vo->input_ctx, MP_INPUT_RELEASE_ALL); + mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); XUngrabPointer(x11->display, CurrentTime); long params[5] = { @@ -1016,8 +1067,8 @@ int vo_x11_check_events(struct vo *vo) }; x11_send_ewmh_msg(x11, "_NET_WM_MOVERESIZE", params); } else { - mp_input_set_mouse_pos(vo->input_ctx, Event.xmotion.x, - Event.xmotion.y); + mp_input_set_mouse_pos(x11->input_ctx, Event.xmotion.x, + Event.xmotion.y); } x11->win_drag_button1_down = false; break; @@ -1025,17 +1076,17 @@ int vo_x11_check_events(struct vo *vo) if (Event.xcrossing.mode != NotifyNormal) break; x11->win_drag_button1_down = false; - mp_input_put_key(vo->input_ctx, MP_KEY_MOUSE_LEAVE); + mp_input_put_key(x11->input_ctx, MP_KEY_MOUSE_LEAVE); break; case EnterNotify: if (Event.xcrossing.mode != NotifyNormal) break; - mp_input_put_key(vo->input_ctx, MP_KEY_MOUSE_ENTER); + mp_input_put_key(x11->input_ctx, MP_KEY_MOUSE_ENTER); break; case ButtonPress: if (Event.xbutton.button == 1) x11->win_drag_button1_down = true; - mp_input_put_key(vo->input_ctx, + mp_input_put_key(x11->input_ctx, (MP_MOUSE_BTN0 + Event.xbutton.button - 1) | get_mods(Event.xbutton.state) | MP_KEY_STATE_DOWN); long msg[4] = {XEMBED_REQUEST_FOCUS}; @@ -1044,7 +1095,7 @@ int vo_x11_check_events(struct vo *vo) case ButtonRelease: if (Event.xbutton.button == 1) x11->win_drag_button1_down = false; - mp_input_put_key(vo->input_ctx, + mp_input_put_key(x11->input_ctx, (MP_MOUSE_BTN0 + Event.xbutton.button - 1) | get_mods(Event.xbutton.state) | MP_KEY_STATE_UP); break; @@ -1055,13 +1106,13 @@ int vo_x11_check_events(struct vo *vo) break; case DestroyNotify: MP_WARN(x11, "Our window was destroyed, exiting\n"); - mp_input_put_key(vo->input_ctx, MP_KEY_CLOSE_WIN); + mp_input_put_key(x11->input_ctx, MP_KEY_CLOSE_WIN); x11->window = 0; break; case ClientMessage: if (Event.xclient.message_type == XA(x11, WM_PROTOCOLS) && Event.xclient.data.l[0] == XA(x11, WM_DELETE_WINDOW)) - mp_input_put_key(vo->input_ctx, MP_KEY_CLOSE_WIN); + mp_input_put_key(x11->input_ctx, MP_KEY_CLOSE_WIN); vo_x11_dnd_handle_message(vo, &Event.xclient); vo_x11_xembed_handle_message(vo, &Event.xclient); break; @@ -1437,9 +1488,9 @@ static void vo_x11_map_window(struct vo *vo, struct mp_rect rc) // map window int events = StructureNotifyMask | ExposureMask | PropertyChangeMask | LeaveWindowMask | EnterWindowMask; - if (mp_input_mouse_enabled(vo->input_ctx)) + if (mp_input_mouse_enabled(x11->input_ctx)) events |= PointerMotionMask | ButtonPressMask | ButtonReleaseMask; - if (mp_input_vo_keyboard_enabled(vo->input_ctx)) + if (mp_input_vo_keyboard_enabled(x11->input_ctx)) events |= KeyPressMask | KeyReleaseMask; vo_x11_selectinput_witherr(vo, x11->display, x11->window, events); XMapWindow(x11->display, x11->window); diff --git a/video/out/x11_common.h b/video/out/x11_common.h index 4b122e8535..812c309afe 100644 --- a/video/out/x11_common.h +++ b/video/out/x11_common.h @@ -43,6 +43,7 @@ struct xrandr_display { struct vo_x11_state { struct mp_log *log; + struct input_ctx *input_ctx; Display *display; Window window; Window rootwin; diff --git a/waftools/fragments/dvb.c b/waftools/fragments/dvb.c index 9deaa1eaab..4ec9e37311 100644 --- a/waftools/fragments/dvb.c +++ b/waftools/fragments/dvb.c @@ -7,4 +7,6 @@ #include #include #include +#include +fe_bandwidth_t t; int main(void) {return 0;} diff --git a/wscript b/wscript index 13d739e221..f4fe38ed59 100644 --- a/wscript +++ b/wscript @@ -325,6 +325,7 @@ iconv support use --disable-iconv.", } , { 'name' : '--encoding', 'desc' : 'Encoding', + 'default': 'disable', 'func': check_true, }, { 'name': '--libbluray', @@ -484,6 +485,18 @@ FFmpeg/Libav libraries. You need at least {0}. Aborting.".format(libav_versions_ 'func': check_statement('libavcodec/avcodec.h', 'avcodec_profile_name(0,0)', use='libav'), + }, { + 'name': 'avcodec-new-codec-api', + 'desc': 'libavcodec decode/encode API', + 'func': check_statement('libavcodec/avcodec.h', + 'avcodec_send_packet(0,0)', + use='libav'), + }, { + 'name': 'avcodec-has-codecpar', + 'desc': 'libavcodec AVCodecParameters API', + 'func': check_statement('libavformat/avformat.h', + '(void)offsetof(AVStream, codecpar)', + use='libav'), }, ] @@ -771,8 +784,8 @@ video_output_features = [ check_statement('GL/gl.h', '(void)GL_LUMINANCE16') # arbitrary OpenGL legacy-only symbol ), }, { - 'name': '--desktop-gl', - 'desc': 'Desktop OpengGL support', + 'name': '--standard-gl', + 'desc': 'Desktop standard OpengGL support', 'func': compose_checks( 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 @@ -785,7 +798,7 @@ video_output_features = [ } , { 'name': '--any-gl', 'desc': 'Any OpenGL (ES) support', - 'deps_any': ['desktop-gl', 'android-gl'], + 'deps_any': ['standard-gl', 'android-gl', 'cocoa'], 'func': check_true } , { 'name': '--plain-gl', @@ -839,10 +852,20 @@ hwaccel_features = [ 'desc': 'libavcodec DXVA2 hwaccel', 'deps': [ 'win32' ], 'func': check_headers('libavcodec/dxva2.h', use='libav'), + }, { + 'name': '--d3d11va-hwaccel', + 'desc': 'libavcodec D3D11VA hwaccel', + 'deps': [ 'win32' ], + 'func': check_headers('libavcodec/d3d11va.h', use='libav'), + }, { + 'name': 'd3d-hwaccel', + 'desc': 'Direct3D hwaccel', + 'deps_any': [ 'dxva2-hwaccel', 'd3d11va-hwaccel' ], + 'func': check_true }, { 'name': 'sse4-intrinsics', 'desc': 'GCC SSE4 intrinsics for GPU memcpy', - 'deps_any': [ 'dxva2-hwaccel', 'vaapi-hwaccel' ], + 'deps_any': [ 'd3d-hwaccel', 'vaapi-hwaccel' ], 'func': check_cc(fragment=load_fragment('sse.c')), } ] diff --git a/wscript_build.py b/wscript_build.py index 4cc35cad9e..f996a6e57b 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -175,6 +175,7 @@ def build(ctx): ( "demux/demux_mf.c" ), ( "demux/demux_mkv.c" ), ( "demux/demux_mkv_timeline.c" ), + ( "demux/demux_null.c" ), ( "demux/demux_playlist.c" ), ( "demux/demux_raw.c" ), ( "demux/demux_rar.c" ), @@ -189,7 +190,9 @@ def build(ctx): ( "input/cmd_parse.c" ), ( "input/event.c" ), ( "input/input.c" ), - ( "input/ipc.c", "!mingw" ), + ( "input/ipc.c" ), + ( "input/ipc-unix.c", "!mingw" ), + ( "input/ipc-win.c", "mingw" ), ( "input/keycodes.c" ), ( "input/pipe-win32.c", "mingw" ), @@ -283,16 +286,20 @@ def build(ctx): ( "video/mp_image_pool.c" ), ( "video/sws_utils.c" ), ( "video/dxva2.c", "dxva2-hwaccel" ), + ( "video/d3d11va.c", "d3d11va-hwaccel" ), ( "video/vaapi.c", "vaapi" ), ( "video/vdpau.c", "vdpau" ), ( "video/vdpau_mixer.c", "vdpau" ), ( "video/decode/dec_video.c"), ( "video/decode/dxva2.c", "dxva2-hwaccel" ), + ( "video/decode/d3d11va.c", "d3d11va-hwaccel" ), + ( "video/decode/d3d.c", "d3d-hwaccel" ), ( "video/decode/rpi.c", "rpi" ), ( "video/decode/vaapi.c", "vaapi-hwaccel" ), ( "video/decode/vd_lavc.c" ), ( "video/decode/videotoolbox.c", "videotoolbox-hwaccel" ), ( "video/decode/vdpau.c", "vdpau-hwaccel" ), + ( "video/decode/mediacodec.c", "android" ), ( "video/filter/vf.c" ), ( "video/filter/vf_buffer.c" ), ( "video/filter/vf_crop.c" ), @@ -339,6 +346,7 @@ def build(ctx): ( "video/out/opengl/hwdec.c", "gl" ), ( "video/out/opengl/hwdec_dxva2.c", "gl-win32" ), ( "video/out/opengl/hwdec_dxva2gldx.c", "gl-dxinterop" ), + ( "video/out/opengl/hwdec_dxva2egl.c", "egl-angle" ), ( "video/out/opengl/hwdec_vaegl.c", "vaapi-egl" ), ( "video/out/opengl/hwdec_vaglx.c", "vaapi-glx" ), ( "video/out/opengl/hwdec_osx.c", "videotoolbox-gl" ), @@ -484,9 +492,9 @@ def build(ctx): if build_shared: waftoolsdir = os.path.join(os.path.dirname(__file__), "waftools") ctx.load("syms", tooldir=waftoolsdir) - vre = '^#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION\((.*), (.*)\)$' + vre = '#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION\((.*), (.*)\)' libmpv_header = ctx.path.find_node("libmpv/client.h").read() - major, minor = re.search(vre, libmpv_header, re.M).groups() + major, minor = re.search(vre, libmpv_header).groups() libversion = major + '.' + minor + '.0' def _build_libmpv(shared): @@ -585,3 +593,7 @@ def build(ctx): ctx.install_as( ctx.env.DATADIR + '/icons/hicolor/scalable/apps/mpv.svg', 'etc/mpv-gradient.svg') + + ctx.install_files( + ctx.env.DATADIR + '/icons/hicolor/symbolic/apps', + ['etc/mpv-symbolic.svg'])