Merge branch 'master' into release/current

This commit is contained in:
Martin Herkt committed 2016-04-11 17:42:55 +02:00
commit 0803f4ad21
165 files changed
+5426 -4648

No files matched your search

-14
View File
@@ -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.
-114
View File
@@ -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.
@@ -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 <mpv/client.h>
#import <mpv/opengl_cb.h>
#import <stdio.h>
#import <stdlib.h>
#import <OpenGL/gl.h>
#import <Cocoa/Cocoa.h>
static inline void check_error(int status)
{
if (status < 0) {
printf("mpv API error: %s\n", mpv_error_string(status));
exit(1);
}
}
static void *get_proc_address(void *ctx, const char *name)
{
CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault, name, kCFStringEncodingASCII);
void *addr = CFBundleGetFunctionPointerForName(CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")), symbolName);
CFRelease(symbolName);
return addr;
}
static void glupdate(void *ctx);
@interface MpvClientOGLView : NSOpenGLView
@property mpv_opengl_cb_context *mpvGL;
- (instancetype)initWithFrame:(NSRect)frame;
- (void)drawRect;
- (void)fillBlack;
@end
@implementation MpvClientOGLView
- (instancetype)initWithFrame:(NSRect)frame
{
// make sure the pixel format is double buffered so we can use
// [[self openGLContext] flushBuffer].
NSOpenGLPixelFormatAttribute attributes[] = {
NSOpenGLPFADoubleBuffer,
0
};
self = [super initWithFrame:frame
pixelFormat:[[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]];
if (self) {
[self setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
// swap on vsyncs
GLint swapInt = 1;
[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
[[self openGLContext] makeCurrentContext];
self.mpvGL = nil;
}
return self;
}
- (void)fillBlack
{
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
}
- (void)drawRect
{
if (self.mpvGL)
mpv_opengl_cb_draw(self.mpvGL, 0, self.bounds.size.width, -self.bounds.size.height);
else
[self fillBlack];
[[self openGLContext] flushBuffer];
}
- (void)drawRect:(NSRect)dirtyRect
{
[self drawRect];
}
@end
@interface CocoaWindow : NSWindow
@property(retain, readonly) MpvClientOGLView *glView;
@property(retain, readonly) NSButton *pauseButton;
@end
@implementation CocoaWindow
- (BOOL)canBecomeMainWindow { return YES; }
- (BOOL)canBecomeKeyWindow { return YES; }
- (void)initOGLView {
NSRect bounds = [[self contentView] bounds];
// window coordinate origin is bottom left
NSRect glFrame = NSMakeRect(bounds.origin.x, bounds.origin.y + 30, bounds.size.width, bounds.size.height - 30);
_glView = [[MpvClientOGLView alloc] initWithFrame:glFrame];
[self.contentView addSubview:_glView];
NSRect buttonFrame = NSMakeRect(bounds.origin.x, bounds.origin.y, 60, 30);
_pauseButton = [[NSButton alloc] initWithFrame:buttonFrame];
_pauseButton.buttonType = NSToggleButton;
// button target has to be the delegate (it holds the mpv context
// pointer), so that's set later.
_pauseButton.action = @selector(togglePause:);
_pauseButton.title = @"Pause";
_pauseButton.alternateTitle = @"Play";
[self.contentView addSubview:_pauseButton];
}
@end
@interface AppDelegate : NSObject <NSApplicationDelegate>
{
mpv_handle *mpv;
dispatch_queue_t queue;
CocoaWindow *window;
}
@end
static void wakeup(void *);
@implementation AppDelegate
- (void)createWindow {
int mask = NSTitledWindowMask|NSClosableWindowMask|
NSMiniaturizableWindowMask|NSResizableWindowMask;
window = [[CocoaWindow alloc]
initWithContentRect:NSMakeRect(0, 0, 1280, 720)
styleMask:mask
backing:NSBackingStoreBuffered
defer:NO];
// force a minimum size to stop opengl from exploding.
[window setMinSize:NSMakeSize(200, 200)];
[window initOGLView];
[window setTitle:@"cocoa-openglcb example"];
[window makeMainWindow];
[window makeKeyAndOrderFront:nil];
NSMenu *m = [[NSMenu alloc] initWithTitle:@"AMainMenu"];
NSMenuItem *item = [m addItemWithTitle:@"Apple" action:nil keyEquivalent:@""];
NSMenu *sm = [[NSMenu alloc] initWithTitle:@"Apple"];
[m setSubmenu:sm forItem:item];
[sm addItemWithTitle: @"quit" action:@selector(terminate:) keyEquivalent:@"q"];
[NSApp setMenu:m];
[NSApp activateIgnoringOtherApps:YES];
}
- (void) applicationDidFinishLaunching:(NSNotification *)notification {
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
atexit_b(^{
// Because activation policy has just been set to behave like a real
// application, that policy must be reset on exit to prevent, among
// other things, the menubar created here from remaining on screen.
[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
});
// Read filename
NSArray *args = [NSProcessInfo processInfo].arguments;
if (args.count < 2) {
NSLog(@"Expected filename on command line");
exit(1);
}
NSString *filename = args[1];
[self createWindow];
window.pauseButton.target = self;
mpv = mpv_create();
if (!mpv) {
printf("failed creating context\n");
exit(1);
}
check_error(mpv_set_option_string(mpv, "input-media-keys", "yes"));
// request important errors
check_error(mpv_request_log_messages(mpv, "warn"));
check_error(mpv_initialize(mpv));
check_error(mpv_set_option_string(mpv, "vo", "opengl-cb"));
mpv_opengl_cb_context *mpvGL = mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
if (!mpvGL) {
puts("libmpv does not have the opengl-cb sub-API.");
exit(1);
}
// pass the mpvGL context to our view
window.glView.mpvGL = mpvGL;
int r = mpv_opengl_cb_init_gl(mpvGL, NULL, get_proc_address, NULL);
if (r < 0) {
puts("gl init has failed.");
exit(1);
}
mpv_opengl_cb_set_update_callback(mpvGL, glupdate, (__bridge void *)window.glView);
// Deal with MPV in the background.
queue = dispatch_queue_create("mpv", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// Register to be woken up whenever mpv generates new events.
mpv_set_wakeup_callback(mpv, wakeup, (__bridge void *)self);
// Load the indicated file
const char *cmd[] = {"loadfile", filename.UTF8String, NULL};
check_error(mpv_command(mpv, cmd));
});
}
static void glupdate(void *ctx)
{
MpvClientOGLView *glView = (__bridge MpvClientOGLView *)ctx;
// I'm still not sure what the best way to handle this is, but this
// works.
dispatch_async(dispatch_get_main_queue(), ^{
[glView drawRect];
});
}
- (void) handleEvent:(mpv_event *)event
{
switch (event->event_id) {
case MPV_EVENT_SHUTDOWN: {
mpv_detach_destroy(mpv);
mpv_opengl_cb_uninit_gl(window.glView.mpvGL);
mpv = NULL;
printf("event: shutdown\n");
break;
}
case MPV_EVENT_LOG_MESSAGE: {
struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
printf("[%s] %s: %s", msg->prefix, msg->level, msg->text);
}
default:
printf("event: %s\n", mpv_event_name(event->event_id));
}
}
- (void)togglePause:(NSButton *)button
{
if (mpv) {
switch (button.state) {
case NSOffState:
{
int pause = 0;
mpv_set_property(mpv, "pause", MPV_FORMAT_FLAG, &pause);
}
break;
case NSOnState:
{
int pause = 1;
mpv_set_property(mpv, "pause", MPV_FORMAT_FLAG, &pause);
}
break;
default:
NSLog(@"This should never happen.");
}
}
}
- (void) readEvents
{
dispatch_async(queue, ^{
while (mpv) {
mpv_event *event = mpv_wait_event(mpv, 0);
if (event->event_id == MPV_EVENT_NONE)
break;
[self handleEvent:event];
}
});
}
static void wakeup(void *context)
{
AppDelegate *a = (__bridge AppDelegate *) context;
[a readEvents];
}
// quit when the window is closed.
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app
{
return YES;
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
NSLog(@"Terminating.");
const char *args[] = {"quit", NULL};
mpv_command(mpv, args);
[window.glView clearGLContext];
return NSTerminateNow;
}
@end
// Delete this if you already have a main.m.
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSApplication *app = [NSApplication sharedApplication];
AppDelegate *delegate = [AppDelegate new];
app.delegate = delegate;
[app run];
}
return 0;
}
-209
View File
@@ -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 <mpv/client.h>
#include <stdio.h>
#include <stdlib.h>
static inline void check_error(int status)
{
if (status < 0) {
printf("mpv API error: %s\n", mpv_error_string(status));
exit(1);
}
}
#import <Cocoa/Cocoa.h>
@interface CocoaWindow : NSWindow
@end
@implementation CocoaWindow
- (BOOL)canBecomeMainWindow { return YES; }
- (BOOL)canBecomeKeyWindow { return YES; }
@end
@interface AppDelegate : NSObject <NSApplicationDelegate>
{
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;
}
-136
View File
@@ -1,136 +0,0 @@
#include "main.h"
#include <stdexcept>
#include <clocale>
#include <QObject>
#include <QtGlobal>
#include <QOpenGLContext>
#include <QGuiApplication>
#include <QtGui/QOpenGLFramebufferObject>
#include <QtQuick/QQuickWindow>
#include <QtQuick/QQuickView>
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<MpvObject>("mpvtest", 1, 0, "MpvObject");
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setSource(QUrl("qrc:///mpvtest/main.qml"));
view.show();
return app.exec();
}
-36
View File
@@ -1,36 +0,0 @@
#ifndef MPVRENDERER_H_
#define MPVRENDERER_H_
#include <QtQuick/QQuickFramebufferObject>
#include <mpv/client.h>
#include <mpv/opengl_cb.h>
#include <mpv/qthelper.hpp>
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
-71
View File
@@ -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)
}
}
}
}
-12
View File
@@ -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
-5
View File
@@ -1,5 +0,0 @@
<RCC>
<qresource prefix="/mpvtest">
<file>main.qml</file>
</qresource>
</RCC>
@@ -1,159 +0,0 @@
#include "main.h"
#include <stdexcept>
#include <clocale>
#include <QObject>
#include <QtGlobal>
#include <QOpenGLContext>
#include <QGuiApplication>
#include <QtQuick/QQuickWindow>
#include <QtQuick/QQuickView>
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<MpvObject>("mpvtest", 1, 0, "MpvObject");
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setSource(QUrl("qrc:///mpvtest/main.qml"));
view.show();
return app.exec();
}
@@ -1,51 +0,0 @@
#ifndef MPVRENDERER_H_
#define MPVRENDERER_H_
#include <QtQuick/QQuickItem>
#include <mpv/client.h>
#include <mpv/opengl_cb.h>
#include <mpv/qthelper.hpp>
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
@@ -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"
}
}
}
@@ -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
@@ -1,5 +0,0 @@
<RCC>
<qresource prefix="/mpvtest">
<file>main.qml</file>
</qresource>
</RCC>
-225
View File
@@ -1,225 +0,0 @@
// This example can be built with: qmake && make
#include <clocale>
#include <sstream>
#include <stdexcept>
#include <QtGlobal>
#include <QFileDialog>
#include <QStatusBar>
#include <QMenuBar>
#include <QMenu>
#include <QGridLayout>
#include <QApplication>
#include <QTextEdit>
#if QT_VERSION >= 0x050000
#include <QJsonDocument>
#endif
#include <mpv/qthelper.hpp>
#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();
}
-37
View File
@@ -1,37 +0,0 @@
#ifndef QTEXAMPLE_H
#define QTEXAMPLE_H
#include <QMainWindow>
#include <mpv/client.h>
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
-13
View File
@@ -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
@@ -1,13 +0,0 @@
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Qt sets the locale in the QApplication constructor, but libmpv requires
// the LC_NUMERIC category to be set to "C", so change it back.
setlocale(LC_NUMERIC, "C");
MainWindow w;
w.show();
return a.exec();
}
@@ -1,52 +0,0 @@
#include "mainwindow.h"
#include "mpvwidget.h"
#include <QPushButton>
#include <QSlider>
#include <QLayout>
#include <QFileDialog>
MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
{
m_mpv = new MpvWidget(this);
m_slider = new QSlider();
m_slider->setOrientation(Qt::Horizontal);
m_openBtn = new QPushButton("Open");
m_playBtn = new QPushButton("Pause");
QHBoxLayout *hb = new QHBoxLayout();
hb->addWidget(m_openBtn);
hb->addWidget(m_playBtn);
QVBoxLayout *vl = new QVBoxLayout();
vl->addWidget(m_mpv);
vl->addWidget(m_slider);
vl->addLayout(hb);
setLayout(vl);
connect(m_slider, SIGNAL(sliderMoved(int)), SLOT(seek(int)));
connect(m_openBtn, SIGNAL(clicked()), SLOT(openMedia()));
connect(m_playBtn, SIGNAL(clicked()), SLOT(pauseResume()));
connect(m_mpv, SIGNAL(positionChanged(int)), m_slider, SLOT(setValue(int)));
connect(m_mpv, SIGNAL(durationChanged(int)), this, SLOT(setSliderRange(int)));
}
void MainWindow::openMedia()
{
QString file = QFileDialog::getOpenFileName(0, "Open a video");
if (file.isEmpty())
return;
m_mpv->command(QStringList() << "loadfile" << file);
}
void MainWindow::seek(int pos)
{
m_mpv->command(QVariantList() << "seek" << pos << "absolute");
}
void MainWindow::pauseResume()
{
const bool paused = m_mpv->getProperty("pause").toBool();
m_mpv->setProperty("pause", !paused);
}
void MainWindow::setSliderRange(int duration)
{
m_slider->setRange(0, duration);
}
@@ -1,27 +0,0 @@
#ifndef MainWindow_H
#define MainWindow_H
#include <QtWidgets/QWidget>
class MpvWidget;
class QSlider;
class QPushButton;
class MainWindow : public QWidget
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
public Q_SLOTS:
void openMedia();
void seek(int pos);
void pauseResume();
private Q_SLOTS:
void setSliderRange(int duration);
private:
MpvWidget *m_mpv;
QSlider *m_slider;
QPushButton *m_openBtn;
QPushButton *m_playBtn;
};
#endif // MainWindow_H
@@ -1,129 +0,0 @@
#include "mpvwidget.h"
#include <stdexcept>
#include <QtGui/QOpenGLContext>
#include <QtCore/QMetaObject>
static void wakeup(void *ctx)
{
QMetaObject::invokeMethod((MpvWidget*)ctx, "on_mpv_events", Qt::QueuedConnection);
}
static void *get_proc_address(void *ctx, const char *name) {
Q_UNUSED(ctx);
QOpenGLContext *glctx = QOpenGLContext::currentContext();
if (!glctx)
return NULL;
return (void *)glctx->getProcAddress(QByteArray(name));
}
MpvWidget::MpvWidget(QWidget *parent, Qt::WindowFlags f)
: QOpenGLWidget(parent, f)
{
mpv = mpv::qt::Handle::FromRawHandle(mpv_create());
if (!mpv)
throw std::runtime_error("could not create mpv context");
mpv_set_option_string(mpv, "terminal", "yes");
mpv_set_option_string(mpv, "msg-level", "all=v");
if (mpv_initialize(mpv) < 0)
throw std::runtime_error("could not initialize mpv context");
// Make use of the MPV_SUB_API_OPENGL_CB API.
mpv::qt::set_option_variant(mpv, "vo", "opengl-cb");
// Request hw decoding, just for testing.
mpv::qt::set_option_variant(mpv, "hwdec", "auto");
mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
if (!mpv_gl)
throw std::runtime_error("OpenGL not compiled in");
mpv_opengl_cb_set_update_callback(mpv_gl, MpvWidget::on_update, (void *)this);
connect(this, SIGNAL(frameSwapped()), SLOT(swapped()));
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
mpv_set_wakeup_callback(mpv, wakeup, this);
}
MpvWidget::~MpvWidget()
{
makeCurrent();
if (mpv_gl)
mpv_opengl_cb_set_update_callback(mpv_gl, NULL, NULL);
// Until this call is done, we need to make sure the player remains
// alive. This is done implicitly with the mpv::qt::Handle instance
// in this class.
mpv_opengl_cb_uninit_gl(mpv_gl);
}
void MpvWidget::command(const QVariant& params)
{
mpv::qt::command_variant(mpv, params);
}
void MpvWidget::setProperty(const QString& name, const QVariant& value)
{
mpv::qt::set_property_variant(mpv, name, value);
}
QVariant MpvWidget::getProperty(const QString &name) const
{
return mpv::qt::get_property_variant(mpv, name);
}
void MpvWidget::initializeGL()
{
int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL);
if (r < 0)
throw std::runtime_error("could not initialize OpenGL");
}
void MpvWidget::paintGL()
{
mpv_opengl_cb_draw(mpv_gl, defaultFramebufferObject(), width(), -height());
}
void MpvWidget::swapped()
{
mpv_opengl_cb_report_flip(mpv_gl, 0);
}
void MpvWidget::on_mpv_events()
{
// Process all events, until the event queue is empty.
while (mpv) {
mpv_event *event = mpv_wait_event(mpv, 0);
if (event->event_id == MPV_EVENT_NONE) {
break;
}
handle_mpv_event(event);
}
}
void MpvWidget::handle_mpv_event(mpv_event *event)
{
switch (event->event_id) {
case MPV_EVENT_PROPERTY_CHANGE: {
mpv_event_property *prop = (mpv_event_property *)event->data;
if (strcmp(prop->name, "time-pos") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double time = *(double *)prop->data;
Q_EMIT positionChanged(time);
}
} else if (strcmp(prop->name, "duration") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double time = *(double *)prop->data;
Q_EMIT durationChanged(time);
}
}
break;
}
default: ;
// Ignore uninteresting or unknown events.
}
}
void MpvWidget::on_update(void *ctx)
{
QMetaObject::invokeMethod((MpvWidget*)ctx, "update");
}
@@ -1,38 +0,0 @@
#ifndef PLAYERWINDOW_H
#define PLAYERWINDOW_H
#include <QtWidgets/QOpenGLWidget>
#include <mpv/client.h>
#include <mpv/opengl_cb.h>
#include <mpv/qthelper.hpp>
class MpvWidget Q_DECL_FINAL: public QOpenGLWidget
{
Q_OBJECT
public:
MpvWidget(QWidget *parent = 0, Qt::WindowFlags f = 0);
~MpvWidget();
void command(const QVariant& params);
void setProperty(const QString& name, const QVariant& value);
QVariant getProperty(const QString& name) const;
QSize sizeHint() const { return QSize(480, 270);}
Q_SIGNALS:
void durationChanged(int value);
void positionChanged(int value);
protected:
void initializeGL() Q_DECL_OVERRIDE;
void paintGL() Q_DECL_OVERRIDE;
private Q_SLOTS:
void swapped();
void on_mpv_events();
private:
void handle_mpv_event(mpv_event *event);
static void on_update(void *ctx);
mpv::qt::Handle mpv;
mpv_opengl_cb_context *mpv_gl;
};
#endif // PLAYERWINDOW_H
@@ -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
-158
View File
@@ -1,158 +0,0 @@
// Build with: gcc -o main main.c `pkg-config --libs --cflags mpv sdl2`
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#include <mpv/client.h>
#include <mpv/opengl_cb.h>
static Uint32 wakeup_on_mpv_redraw, wakeup_on_mpv_events;
static void die(const char *msg)
{
fprintf(stderr, "%s\n", msg);
exit(1);
}
static void *get_proc_address_mpv(void *fn_ctx, const char *name)
{
return SDL_GL_GetProcAddress(name);
}
static void on_mpv_events(void *ctx)
{
SDL_Event event = {.type = wakeup_on_mpv_events};
SDL_PushEvent(&event);
}
static void on_mpv_redraw(void *ctx)
{
SDL_Event event = {.type = wakeup_on_mpv_redraw};
SDL_PushEvent(&event);
}
int main(int argc, char *argv[])
{
if (argc != 2)
die("pass a single media file as argument");
mpv_handle *mpv = mpv_create();
if (!mpv)
die("context init failed");
// Some minor options can only be set before mpv_initialize().
if (mpv_initialize(mpv) < 0)
die("mpv init failed");
if (SDL_Init(SDL_INIT_VIDEO) < 0)
die("SDL init failed");
SDL_Window *window =
SDL_CreateWindow("hi", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
1000, 500, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN |
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;
}
-54
View File
@@ -1,54 +0,0 @@
// Build with: gcc -o simple simple.c `pkg-config --libs --cflags mpv`
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <mpv/client.h>
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;
}
+38 -13
View File
@@ -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
+11
View File
@@ -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)
+14
View File
@@ -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
+88 -9
View File
@@ -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/<name>`` (RW)
Read-only access to value of option ``--<name>``. Most options can be
changed at runtime by writing to this property. Note that many options
+32 -7
View File
@@ -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
+4 -9
View File
@@ -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,
+5 -7
View File
@@ -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
+52 -24
View File
@@ -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=<value>``
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=<yes|no>``
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=<filename>``
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=<filename>``
``--input-ipc-server=<filename>``
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\<name>``). 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=<yes|no>``
(OS X only)
@@ -2613,6 +2622,9 @@ OSD
``--osd-bold=<yes|no>``, ``--sub-text-bold=<yes|no>``
Format text on bold.
``--osd-italic=<yes|no>``, ``--sub-text-italic=<yes|no>``
Format text on italic.
``--osd-border-color=<color>``, ``--sub-text-border-color=<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.
+8 -11
View File
@@ -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=<filter>``
This option provides non-convolution-based filters for upscaling. These
filters resize the video to multiple of the original size (all currently
supported prescalers can only perform image doubling in a single pass).
Generally another convolution based filter (the main scaler) will be
applied after prescaler to match the target display size.
``prescale-luma=<filter>``
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=<value>``
Specifies the transfer characteristics (gamma) of the display. Video
+19 -11
View File
@@ -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.
+7 -1
View File
@@ -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.)
-74
View File
@@ -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)
+8 -2
View File
@@ -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
+20 -4
View File
@@ -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)
+4
View File
@@ -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;
+31
View File
@@ -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)
+1
View File
@@ -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);
+1 -1
View File
@@ -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");
+5
View File
@@ -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.
+3 -11
View File
@@ -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;
}
+37 -1
View File
@@ -25,9 +25,12 @@
#include <libavutil/cpu.h>
#include <libavcodec/avcodec.h>
#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);
}
+3
View File
@@ -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);
+7 -2
View File
@@ -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);
+33 -27
View File
@@ -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];
+3 -5
View File
@@ -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]);
+67 -76
View File
@@ -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)
+2 -3
View File
@@ -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);
+20 -24
View File
@@ -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;
+8 -5
View File
@@ -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:
+34
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#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,
};
+5 -7
View File
@@ -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;
+2
View File
@@ -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;
+1 -1
View File
@@ -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.
##
+17 -2
View File
@@ -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;
}
+9
View File
@@ -14,6 +14,8 @@
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#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
+1 -1
View File
@@ -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) {
+9
View File
@@ -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 */
+422
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#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);
}
+502
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <windows.h>
#include <sddl.h>
#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);
}
+39 -427
View File
@@ -15,58 +15,16 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#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;
}
+2 -1
View File
@@ -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
* ----------
+2 -2
View File
@@ -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;
}
+9 -3
View File
@@ -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;
}
+5 -2
View File
@@ -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,
-21
View File
@@ -18,7 +18,6 @@
*/
#include <winver.h>
#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"
+4
View File
@@ -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:
+138 -45
View File
@@ -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;
}
}
-4
View File
@@ -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.
+1
View File
@@ -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)
+1 -1
View File
@@ -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;
}
+16 -31
View File
@@ -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),
+5 -4
View File
@@ -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
+30 -9
View File
@@ -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)
+1 -1
View File
@@ -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
-2
View File
@@ -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)
+8 -11
View File
@@ -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)
+7 -7
View File
@@ -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);
}
+3 -2
View File
@@ -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);
+31 -12
View File
@@ -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});
}
+15 -9
View File
@@ -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, &params) < 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, &params);
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, &params) < 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, &params) < 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");
}
}
+61 -36
View File
@@ -42,6 +42,7 @@
#include <assert.h>
#include <pthread.h>
#include <time.h>
#include <math.h>
#include <sys/time.h>
#include <libavutil/common.h>
@@ -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);
+2
View File
@@ -18,6 +18,8 @@
#include <inttypes.h>
#include <linux/dvb/dmx.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/video.h>
#include <linux/dvb/audio.h>
#include <linux/dvb/version.h>
#undef DVB_ATSC
+9 -4
View File
@@ -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;
+4 -4
View File
@@ -22,6 +22,7 @@
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <assert.h>
#include <ass/ass.h>
#include <ass/ass_types.h>
@@ -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[] = {
+1 -1
View File
@@ -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 */
+32 -17
View File
@@ -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);
+9 -4
View File
@@ -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);
+4 -4
View File
@@ -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)
+40 -64
View File
@@ -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;
}
+11 -26
View File
@@ -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 */
+2 -4
View File
@@ -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;
}
+130 -79
View File
@@ -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;
}
+33 -6
View File
@@ -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
+5 -1
View File
@@ -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 {
+30 -10
View File
@@ -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);
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff. Show more