Compare commits

..
1 Commits
Author SHA1 Message Date
Alessandro Ghedini 03bfc2b3fd Release 0.6.0 2014-09-30 20:52:47 +02:00
244 changed files with 5103 additions and 12620 deletions

No files matched your search

+4 -26
View File
@@ -4,23 +4,17 @@ os:
- linux
- osx
env:
matrix:
- LIBAV=libav-stable
- LIBAV=libav-git
- LIBAV=ffmpeg-stable
- LIBAV=ffmpeg-git
global:
# Coverity token
- secure: "H21mSRlMhk4BKS0xHZvCFGJxteCP0hRVUxTuNfM2Z9HBsyutuLEYMtViLO86VtM+Tqla3xXPzUdS4ozLwI72Ax/5ZUDXACROj73yW6QhFB5D6rLut12+FjqC7M33Qv2hl0xwgNBmR5dsm1ToP37+Wn+ecJQNvN8fkTXF+HVzOEw="
- LIBAV=libav-stable
- LIBAV=libav-git
- LIBAV=ffmpeg-stable
- LIBAV=ffmpeg-git
compiler:
- clang
- gcc
branches:
only:
- master
- ci
- coverity_scan
matrix:
exclude:
@@ -30,12 +24,6 @@ matrix:
env: LIBAV=libav-git
- os: osx
env: LIBAV=ffmpeg-git
- os: osx
compiler: gcc
- os: linux
env: LIBAV=ffmpeg-stable
- os: linux
compiler: clang
before_install: ./travis-deps libass-stable $LIBAV
script:
@@ -50,13 +38,3 @@ notifications:
- "irc.freenode.org#mpv-player-dev"
on_success: change
on_failure: always
addons:
coverity_scan:
project:
name: "mpv-player/mpv"
description: "Build submitted via Travis CI"
notification_email: mpv-team@googlegroups.com
build_command_prepend: "./bootstrap.py && ./waf configure"
build_command: "./waf build"
branch_pattern: coverity_scan
-25
View File
@@ -25,36 +25,11 @@ API changes
::
--- mpv 0.7.0 is released ---
1.10 - deprecate/disable everything directly related to script_dispatch
(most likely affects nobody)
1.9 - add enum mpv_end_file_reason for mpv_event_end_file.reason
- add MPV_END_FILE_REASON_ERROR and the mpv_event_end_file.error field
for slightly better error reporting on playback failure
- add --stop-playback-on-init-failure option, and make it the default
behavior for libmpv only
- add qthelper.hpp set_option_variant()
- mark the following events as deprecated:
MPV_EVENT_TRACKS_CHANGED
MPV_EVENT_TRACK_SWITCHED
MPV_EVENT_PAUSE
MPV_EVENT_UNPAUSE
MPV_EVENT_METADATA_UPDATE
MPV_EVENT_CHAPTER_CHANGE
They are handled better with mpv_observe_property() as mentioned in
the documentation comments. They are not removed and still work.
1.8 - add qthelper.hpp
1.7 - add mpv_command_node(), mpv_command_node_async()
1.6 - modify "core-idle" property behavior
- MPV_EVENT_LOG_MESSAGE now always sends complete lines
- introduce numeric log levels (mpv_log_level)
--- mpv 0.6.0 is released ---
1.5 - change in X11 and "--wid" behavior again. The previous change didn't
work as expected, and now the behavior can be explicitly controlled
with the "input-x11-keyboard" option. This is only a temporary
measure until XEmbed is implemented and confirmed working.
Note: in 1.6, "input-x11-keyboard" was renamed to "input-vo-keyboard",
although the old option name still works.
1.4 - subtle change in X11 and "--wid" behavior
(this change was added to 0.5.2, and broke some things, see #1090)
--- mpv 0.5.0 is released ---
+13 -77
View File
@@ -8,19 +8,10 @@
#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;
}
@end
@@ -28,39 +19,7 @@ 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];
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: @"Shutdown mpv" action:@selector(shutdown) keyEquivalent:@"s"];
[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) {
@@ -69,8 +28,6 @@ static void wakeup(void *);
}
NSString *filename = args[1];
[self createWindow];
// Deal with MPV in the background.
queue = dispatch_queue_create("mpv", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
@@ -81,21 +38,10 @@ static void wakeup(void *);
exit(1);
}
int64_t wid = (intptr_t) [self->w contentView];
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.
@@ -110,27 +56,17 @@ static void wakeup(void *);
- (void) handleEvent:(mpv_event *)event
{
switch (event->event_id) {
case MPV_EVENT_SHUTDOWN:
// Clean up and shut down.
mpv_terminate_destroy(mpv);
mpv = NULL;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSApplication sharedApplication] terminate:nil];
});
break;
case MPV_EVENT_SHUTDOWN:
// Clean up and shut down.
mpv_terminate_destroy(mpv);
mpv = NULL;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSApplication sharedApplication] terminate:nil];
});
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(), ^{
[self->w selectNextKeyView:nil];
});
default:
printf("event: %s\n", mpv_event_name(event->event_id));
default:
printf("event: %s\n", mpv_event_name(event->event_id));
}
}
@@ -155,8 +91,6 @@ static void wakeup(void *context) {
- (BOOL) windowShouldClose:(id)sender
{
[self shutdown];
if (self->w)
[self->w release];
return YES;
}
@@ -169,10 +103,12 @@ static void wakeup(void *context) {
}
@end
// Delete this if you already have a main.m.
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSApplication *app = [NSApplication sharedApplication];
NSApplication *app = [NSApplication new];
AppDelegate *delegate = [AppDelegate new];
app.delegate = delegate;
[app run];
+4 -64
View File
@@ -2,23 +2,14 @@
// This example can be built with: qmake && make
#include <clocale>
#include <sstream>
#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"
@@ -35,9 +26,6 @@ static void wakeup(void *ctx)
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);
@@ -47,23 +35,15 @@ MainWindow::MainWindow(QWidget *parent) :
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 "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
// pass the window ID to the mpv wid option. This doesn't work on OSX,
// because Cocoa doesn't support this form of embedding.
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();
@@ -74,20 +54,13 @@ MainWindow::MainWindow(QWidget *parent) :
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");
// --input-x11-keyboard on the manpage.
mpv_set_option_string(mpv, "input-x11-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 Qt QEvent mechanism to relay
// the wakeup in a thread-safe way.
@@ -113,19 +86,6 @@ void MainWindow::handle_mpv_event(mpv_event *event)
// 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;
}
@@ -147,13 +107,6 @@ void MainWindow::handle_mpv_event(mpv_event *event)
}
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;
@@ -190,14 +143,6 @@ void MainWindow::on_file_open()
}
}
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)
@@ -207,11 +152,6 @@ MainWindow::~MainWindow()
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();
-5
View File
@@ -5,8 +5,6 @@
#include <mpv/client.h>
class QTextEdit;
class MainWindow : public QMainWindow
{
Q_OBJECT
@@ -24,9 +22,6 @@ private slots:
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);
+1 -1
View File
@@ -5,7 +5,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = qtexample
TEMPLATE = app
CONFIG += link_pkgconfig debug
CONFIG += link_pkgconfig
PKGCONFIG = mpv
SOURCES += qtexample.cpp
+4 -4
View File
@@ -27,13 +27,13 @@ The general simplified syntax is::
<filename>,<start in seconds>,<length in seconds>
If the start time is omitted, 0 is used. If the length is omitted, the
estimated remaining duration of the source file is used.
estimated duration of the source file is used.
Note::
mpv can't use ordered chapter files in EDL entries. Usage of relative or
absolute paths as well as any protocol prefixes is prevented for security
reasons.
mpv can't use ordered chapter files or libquvi-resolved URLs in EDL
entries. Usage of relative or absolute paths as well as any protocol
prefixes is prevented for security reasons.
Syntax of mpv EDL files
+11 -4
View File
@@ -114,18 +114,25 @@ Available audio output drivers are:
with fixed setups.
``coreaudio`` (Mac OS X only)
Native Mac OS X audio output driver using AudioUnits and the CoreAudio
Native Mac OS X audio output driver using the AudioUnits and the CoreAudio
sound server.
Automatically redirects to ``coreaudio_exclusive`` when playing compressed
formats.
``device_id=<id>``
ID of output device to use (0 = default device)
``help``
List all available output devices with their IDs.
``coreaudio_exclusive`` (Mac OS X only)
``coreaudio_device`` (Mac OS X only)
Native Mac OS X audio output driver using direct device access and
exclusive mode (bypasses the sound server).
Supports only compressed formats (AC3 and DTS).
``device_id=<id>``
ID of output device to use (0 = default device)
``help``
List all available output devices with their IDs.
``openal``
Experimental OpenAL audio output driver
+92 -147
View File
@@ -10,159 +10,104 @@ should generally be treated as a completely different program.
.. note::
These lists are incomplete.
General Changes from MPlayer to mpv
-----------------------------------
General Changes for MPlayer to mpv
----------------------------------
This listing is about changes introduced by mplayer2 and mpv relatively to
MPlayer.
Player
~~~~~~
* New name for the binary (``mpv``). New location for config files (either
``~/.config/mpv/mpv.conf``, or if you want, ``~/.mpv/config``).
* Encoding functionality (replacement for MEncoder, see the `ENCODING`_ section).
* Support for Lua scripting (see the `LUA SCRIPTING`_ section).
* Better pause handling (e.g. do not unpause on a command).
* Precise seeking support.
* Improvements in audio/video sync handling.
* Do not lose settings when playing a new file in the same player instance.
* Slave mode compatibility broken (see below).
* Re-enable screensaver while the player is paused.
* Allow resuming playback at a later point with ``Shift+q``, also see the
``quit_watch_later`` input command.
* Switch to GPLv2+. (Technically speaking, MPlayer as a whole seems to be
GPLv2-only, while mplayer2 is GPLv3+ - see ``Copyright`` file for details.)
* Removal of the internal GUI, MEncoder, OSD menu, video kernel drivers for
Linux 2.4 (including VIDIX)
* Large internal cleanups
* Removal of support for dead platforms
* New build system
* No embedded copy of FFmpeg and other libraries
* Better pause handling (do not unpause on a command)
* Better MKV support (such as ordered chapters)
* vo_vdpau improvements
* Precise seeking support
* Native OpenGL backend for OS X
* General OS X improvements
* Improvements in audio/video sync handling
* Cleaned up terminal output
* Gapless audio support (``--gapless-audio``)
* Improved responsiveness on user input
* Support for modifier keys (alt, shift, ctrl) in input.conf
* OSS4 volume control
* More correct color reproduction (color matrix generation)
* Use libass for subtitle rendering by default (better quality)
* Generally preferring FFmpeg/Libav over internal demuxers and decoders
* Improvements when playing multiple files (``--fixed-vo``)
* Screenshot improvements (instant screenshots without 1-frame delay, allow
taking screenshots even with hardware decoding)
* Improved support for PulseAudio
* Generally improved MS Windows support (dealing with Unicode file names,
improved ``--vo=direct3d``, improved window handling)
* Better OSD rendering (using libass). This has full Unicode support, and
languages like Arabic should be better supported.
* Cleaned up terminal output (nicer status line, less useless noise)
* Support for playing URLs of popular streaming sites directly
(e.g. ``mpv https://www.youtube.com/watch?v=...``)
* Improved OpenGL output (``--vo=opengl-hq``)
* Make ``--softvol`` default (**mpv** is not a mixer control panel)
* Improved support for .cue files
* Screenshot improvements (can save screenshots as JPEG or PNG, configurable
file names, support for taking screenshots with or without subtitles - the
``screenshot`` video filter is not needed anymore, and should not be put
into the mpv config file)
* Removal of teletext support
* Removal of most built-in demuxers, using libavformat instead
* Removal of built-in network support, using libavformat instead (also,
support https via libavformat)
* Replace image VOs (``--vo=jpeg`` etc.) with ``--vo=image``
* Do not lose settings when playing a new file in the same player instance
* New location for config files, new name for the binary.
* Slave mode compatibility broken (see below)
* Encoding functionality (replacement for MEncoder, see ``DOCS/encoding.rst``)
* Remove ``--vo=gif89a``, ``--vo=md5sum``, ``--vo=yuv4mpeg``, as encoding can
handle these use cases. For yuv4mpeg, for example, use:
``mpv input.mkv -o output.y4m --no-audio --oautofps --oneverdrop``.
* Image subtitles (DVDs etc.) are rendered in color and use more correct
positioning (color can be disabled with ``--sub-gray``)
* Wayland support
* Support for precise scrolling which scales the parameter of commands. If the
input doesn't support precise scrolling the scale factor stays 1.
* OS X: Cocoa event loop is independent from MPlayer's event loop, so user
actions like accessing menus and live resizing do not block the playback.
* OS X: Apple Remote support.
* OS X: Media Keys support.
* Windows: Added WASAPI audio output.
* New OSD bar with chapter marks and not positioned in the middle of the video
(though this can be customized with the ``--osd-bar-align-y`` option).
* Allow customizing whether a key binding for seeking shows the video time, the
OSD bar, or nothing (see section `Input Command Prefixes`_).
* Display list of chapters and audio/subtitle tracks on OSD (see section
`Properties`_).
* ``--keep-open`` option to stop the player from closing the window and
exiting after playback ends.
* A client API, that allows embedding **mpv** into applications
(see ``libmpv/client.h`` in the sources).
Input
~~~~~
* Improved default keybindings. MPlayer bindings are also available (see
``etc/mplayer-input.conf`` in the source tree).
* Improved responsiveness on user input.
* Support for modifier keys (alt, shift, ctrl) in input.conf.
* Allow customizing whether a key binding for seeking shows the video time, the
OSD bar, or nothing (see the `Input Command Prefixes`_ section).
* Support mapping multiple commands to one key.
Audio
~~~~~
* Support for gapless audio (see the ``--gapless-audio`` option).
* Support for OSS4 volume control.
* Improved support for PulseAudio.
* Make ``--softvol`` default (**mpv** is not a mixer control panel).
* By default, do pitch correction if playback speed is increased.
* Improved downmixing and output of surround audio:
- Instead of using hardcoded pan filters to do remixing, libavresample is used
- Channel maps are used to identify the channel layout, so e.g. ``3.0`` and
``2.1`` audio can be distinguished.
Video
~~~~~
* Wayland support.
* Native support for VAAPI and VDA. Improved VDPAU video output.
* Improved OpenGL output (see the ``opengl-hq`` video output).
* Make hardware decoding work with the ``opengl`` video output.
* Re-enable screensaver while the player is paused.
* Matroska edition switching at runtime.
* Support for libavfilter (for video->video and audio->audio). This allows
using most of FFmpeg's filters, which improve greatly on the old MPlayer
filters in features, performance, and correctness.
* More correct color reproduction (color matrix generation).
* Better subtitle rendering using libass by default.
* Improvements when playing multiple files (``--fixed-vo`` is default, do not
reset settings by default when playing a new file).
* Replace image video outputs (``--vo=jpeg`` etc.) with ``--vo=image``.
* Removal of ``--vo=gif89a``, ``--vo=md5sum``, ``--vo=yuv4mpeg``, as encoding
can handle these use cases. For yuv4mpeg, for example, use::
mpv input.mkv -o output.y4m --no-audio --oautofps --oneverdrop
* Image subtitles (DVDs etc.) are rendered in color and use more correct
positioning (color for image subs can be disabled with ``--sub-gray``).
OSD and terminal
~~~~~~~~~~~~~~~~
* Cleaned up terminal output: nicer status line, less useless noise.
* Improved OSD rendering using libass, with full Unicode support.
* New OSD bar with chapter marks. Not positioned in the middle of the video
(this can be customized with the ``--osd-bar-align-y`` option).
* Display list of chapters and audio/subtitle tracks on OSD (see the
`Properties`_ section).
Screenshots
~~~~~~~~~~~
* Instant screenshots without 1-frame delay.
* Support for taking screenshots even with hardware decoding.
* Support for saving screenshots as JPEG or PNG.
* Support for configurable file names.
* Support for taking screenshots with or without subtitles.
Note that the ``screenshot`` video filter is not needed anymore, and should not
be put into the mpv config file.
Miscellaneous
~~~~~~~~~~~~~
* Better MKV support (e.g. ordered chapters, 3D metadata).
* Matroska edition switching at runtime.
* Support for playing URLs of popular streaming sites directly.
(e.g. ``mpv https://www.youtube.com/watch?v=...``).
Requires a recent version of ``youtube-dl`` to be installed, and
``ytdl=yes`` in the mpv config file.
* Support for precise scrolling which scales the parameter of commands. If the
input doesn't support precise scrolling the scale factor stays 1.
* Improved downmixing and output of surround audio. Instead of using hardcoded
pan filters to do remixing, use libavresample. Channel maps are used to
identify the channel layout, so e.g. ``3.0`` and ``2.1`` audio can be
distinguished.
* Allow resuming playback at a later point with ``Shift+q``, also see
``quit_watch_later`` input command.
* Support mapping multiple commands to one key.
* Allow changing/adjusting video filters at runtime. (This is also used to make
the ``D`` key insert vf_yadif if deinterlacing is not supported otherwise).
* Improved support for .cue files.
Mac OS X
~~~~~~~~
* Native OpenGL backend.
* Cocoa event loop is independent from MPlayer's event loop, so user
actions like accessing menus and live resizing do not block the playback.
* Apple Remote support.
* Media Keys support.
* VDA support using libavcodec hwaccel API instead of FFmpeg's decoder with up
the ``D`` key insert vf_yadif if deinterlacing is not supported otherwise.)
* Native VAAPI support
* OS X: VDA support using libavcodec hwaccel API instead of FFmpeg's decoder. Up
to 2-2.5x reduction in CPU usage.
Windows
~~~~~~~
* Improved support for Unicode file names.
* Improved window handling.
* Do not block playback when moving the window.
* Improved Direct3D video output.
* Added WASAPI audio output.
Internal changes
~~~~~~~~~~~~~~~~
* Switch to GPLv2+ (see ``Copyright`` file for details).
* Removal of lots of cruft:
- Internal GUI (replaced by the OSC, see the `ON SCREEN CONTROLLER`_ section).
- MEncoder (replaced by native encoding, see the `ENCODING`_ section).
- OSD menu.
- Kernel video drivers for Linux 2.4 (including VIDIX).
- Teletext support.
- Support for dead platforms.
- Most built-in demuxers have been replaced by their libavformat counterparts.
- Built-in network support has been replaced by libavformat's (which also
supports https URLs).
- Embedded copies of libraries (such as FFmpeg).
* General code cleanups (including refactoring or rewrites of many parts).
* New build system.
* Many bug fixes and removal of long-standing issues.
* Generally preferring FFmpeg/Libav over internal demuxers, decoders, and
filters.
* Make hardware decoding in general work with the ``opengl`` video output.
* Lua scripting (see `LUA SCRIPTING`_)
* A client API, that allows embedding **mpv** into applications
(see ``libmpv/client.h`` in the sources)
* General bug fixes and removal of long-standing issues
* General code cleanups (including refactoring or rewrites of many parts)
* Many more changes
Detailed Listing of User-visible Changes
----------------------------------------
@@ -389,8 +334,8 @@ Slave mode
(The option was readded in 0.5.1 and sets exactly these options.)
* A JSON RPC protocol giving access to the client API is also supported. See
`JSON IPC`_ for more information.
* A JSON RPC protocol giving access to the client API is planned, but nothing
has emerged yet.
* **mpv** also provides a client API, which can be used to embed the player
by loading it as shared library. (See ``libmpv/client.h`` in the sources.)
+21 -223
View File
@@ -11,9 +11,8 @@ value (upper case if combined with ``Shift``), or a name for special keys. For
example, ``a`` maps to the ``a`` key without shift, and ``A`` maps to ``a``
with shift.
The file is located in the mpv configuration directory (normally at
``~/.config/mpv/input.conf`` depending on platform). The default bindings are
defined here::
The file is normally located in ``~/.config/mpv/input.conf`` (some platforms
may use different paths). The default bindings are defined here::
https://github.com/mpv-player/mpv/blob/master/etc/input.conf
@@ -193,7 +192,7 @@ List of Input Commands
force
Terminate playback if the first file is being played.
``loadfile "<file>" [replace|append|append-play [options]]``
``loadfile "<file>" [replace|append [options]]``
Load the given file and play it.
Second argument:
@@ -204,8 +203,6 @@ List of Input Commands
Append the file to the playlist.
<append-play>
Append the file, and if nothing is currently playing, start playback.
(Always starts with the added file, even if the playlist was not empty
before running this command.)
The third argument is a list of options and values which should be set
while the file is playing. It is of the form ``opt1=value1,opt2=value2,..``.
@@ -262,33 +259,10 @@ List of Input Commands
will seek to the previous position on start. The (optional) argument is
exactly as in the ``quit`` command.
``sub_add "<file>" [<flags> [<title> [<lang>]]]``
``sub_add "<file>"``
Load the given subtitle file. It is selected as current subtitle after
loading.
The ``flags`` args is one of the following values:
<select>
Select the subtitle immediately.
<auto>
Don't select the subtitle. (Or in some special situations, let the
default stream selection mechanism decide.)
<cached>
Select the subtitle. If a subtitle with the same filename was already
added, that one is selected, instead of loading a duplicate entry.
(In this case, title/language are ignored, and if the was changed since
it was loaded, these changes won't be reflected.)
The ``title`` argument sets the track title in the UI.
The ``lang`` argument sets the track language, and can also influence
stream selection with ``flags`` set to ``auto``.
``sub_remove [<id>]``
Remove the given subtitle track. If the ``id`` argument is missing, remove
the current track. (Works on external subtitle files only.)
@@ -428,7 +402,7 @@ Input Commands that are Possibly Subject to Change
The special argument ``!reverse`` can be used to cycle the value list in
reverse. Compared with a command that just lists the value in reverse, this
command will actually share the internal counter with the forward-cycling
key binding (as long as the rest of the arguments are the same).
key binding.
Note that there is a static limit of (as of this writing) 10 arguments
(this limit could be raised on demand).
@@ -482,16 +456,6 @@ Input Commands that are Possibly Subject to Change
random points. Instead, call ``overlay_add`` again (preferably with a
different memory region to prevent tearing).
It is also possible to pass a raw memory address for use as bitmap memory
by passing a memory address as integer prefixed with a ``&`` character.
Passing the wrong thing here will crash the player. The ``offset`` parameter
is not used and must be 0. This mode might be useful for use with libmpv.
On Windows, currently only raw memory addresses work. File mapping is not
implemented because a ``mmap`` compatibility layer is missing, and because
this kind of shared memory method would perhaps not be overly useful on
Windows.
``offset`` is the offset of the first pixel in the source file. It is
passed directly to ``mmap`` and is subject to certain restrictions
(see ``man mmap`` for details). In particular, this value has to be a
@@ -541,46 +505,12 @@ Input Commands that are Possibly Subject to Change
``<target>``. Each client (scripts etc.) has a unique name. For example,
Lua scripts can get their name via ``mp.get_script_name()``.
``script_binding "<name>"``
Invoke a script-provided key binding. This can be used to remap key
bindings provided by external Lua scripts.
(Scripts use this internally to dispatch key bindings, and this can also
be used in input.conf to reassign such bindings.)
The argument is the name of the binding.
It can optionally be prefixed with the name of the script, using ``/`` as
separator, e.g. ``script_binding scriptname/bindingname``.
For completeness, here is how this command works internally. The details
could change any time. On any matching key event, ``script_message_to``
or ``script_message`` is called (depending on whether the script name is
included), where the first argument is the string ``key-binding``, the
second argument is the name of the binding, and the third argument is the
key state as string. The key state consists of a number of letters. The
first letter is one of ``d`` (key was pressed down), ``u`` (was released),
``r`` (key is still down, and was repeated; only if key repeat is enabled
for this binding), ``p`` (key was pressed; happens if up/down can't be
tracked). The second letter whether the event originates from the mouse,
either ``m`` (mouse button) or ``-`` (something else).
``ab_loop``
Cycle through A-B loop states. The first command will set the ``A`` point
(the ``ab-loop-a`` property); the second the ``B`` point, and the third
will clear both points.
``vo_cmdline "<args>"``
Reset the sub-option of the current VO. Currently works with ``opengl``
(including ``opengl-hq``). The argument is the sub-option string usually
passed to the VO on the command line. Not all sub-options can be set, but
those which can will be reset even if they don't appear in the argument.
This command might be changed or removed in the future.
``drop_buffers``
Drop audio/video/demuxer buffers, and restart from fresh. Might help with
unseekable streams that are going out of sync.
This command might be changed or removed in the future.
Undocumented commands: ``tv_last_channel`` (TV/DVB only), ``get_property`` (?),
``ao_reload`` (experimental/internal).
``vo_cmdline`` (experimental).
Input Command Prefixes
----------------------
@@ -605,9 +535,7 @@ prefixes can be specified. They are separated by whitespace.
Do not expand properties in string arguments. (Like ``"${property-name}"``.)
``expand-properties`` (default)
All string arguments are expanded as described in `Property Expansion`_.
``repeatable``
For some commands, keeping a key pressed doesn't run the command repeatedly.
This prefix forces enabling key repeat in any case.
All of the osd prefixes are still overridden by the global ``--osd-level``
settings.
@@ -690,7 +618,10 @@ Property list
Full path of the currently played file.
``media-title``
If the currently played file has a ``title`` tag, use that.
If libquvi is used and libquvi returns a page title for the currently
played URL, return the page title.
Otherwise, if the currently played file has a ``title`` tag, use that.
Otherwise, if the media type is DVD, return the volume ID of DVD.
@@ -724,10 +655,9 @@ Property list
disabled.
``drop-frame-count``
Video frames dropped by decoder, because video is too far behind audio (when
using ``--framedrop=decoder``). Sometimes, this may be incremented in other
situations, e.g. when video packets are damaged, or the decoder doesn't
follow the usual rules. Unavailable if video is disabled.
Frames dropped because they arrived too late. Doesn't necessarily indicate
actual frame-drops, just the number of times the decoder was asked to drop.
Unavailable if video is disabled
``vo-drop-frame-count``
Frames dropped by VO (when using ``--framedrop=vo``).
@@ -738,6 +668,9 @@ Property list
estimating the playback position from the byte position, if the file
duration is not known.
``ratio-pos`` (RW)
Position in current file (0.0-1.0). higher precision that ``percent-pos``.
``time-pos`` (RW)
Position in current file in seconds.
@@ -814,10 +747,6 @@ Property list
"title" MPV_FORMAT_STRING
"default" MPV_FORMAT_FLAG
``ab-loop-a``, ``ab-loop-b`` (TW)
Set/get A-B loop points. See corresponding options and ``ab_loop`` command.
The special value ``no`` on either of these properties disables looping.
``angle`` (RW)
Current DVD angle.
@@ -891,10 +820,6 @@ Property list
be different ``pause`` in special situations, such as when the player
pauses itself due to low network cache.
This also returns ``yes`` if playback is restarting or if nothing is
playing at all. In other words, it's only ``no`` if there's actually
video playing. (Behavior since mpv 0.7.0.)
``cache``
Network cache fill state (0-100.0).
@@ -932,10 +857,6 @@ Property list
``paused-for-cache``
Returns ``yes`` when playback is paused because of waiting for the cache.
``cache-buffering-state``
Return the percentage (0-100) of the cache fill status until the player
will unpause (related to ``paused-for-cache``).
``eof-reached``
Returns ``yes`` if end of playback was reached, ``no`` otherwise. Note
that this is usually interesting only if ``--keep-open`` is enabled,
@@ -997,9 +918,6 @@ Property list
``deinterlace`` (RW)
See ``--deinterlace``.
``field-dominance`` (RW)
See ``--field-dominance``
``colormatrix`` (RW)
See ``--colormatrix``.
@@ -1155,13 +1073,6 @@ Property list
(or to be exact, the size the video filters output). ``2`` will set the
double size, ``0.5`` halves the size.
``window-minimized``
Return whether the video window is minimized or not.
``display-names``
Names of the displays that the mpv window covers. On X11, these
are the xrandr names (LVDS1, HDMI1, DP1, VGA1, etc.).
``video-aspect`` (RW)
Video aspect, see ``--video-aspect``.
@@ -1228,8 +1139,8 @@ Property list
See ``--ass-style-override``.
``stream-capture`` (RW)
A filename, see ``--stream-capture``. Setting this will start capture using
the given filename. Setting it to an empty string will stop it.
A filename, see ``--capture``. Setting this will start capture using the
given filename. Setting it to an empty string will stop it.
``tv-brightness``, ``tv-contrast``, ``tv-saturation``, ``tv-hue`` (RW)
TV stuff.
@@ -1254,13 +1165,6 @@ Property list
``playlist/N/filename``
Filename of the Nth entry.
``playlist/N/current``, ``playlist/N/playing``
``yes`` if this entry is currently playing (or being loaded).
Unavailable or ``no`` otherwise. When changing files, ``current`` and
``playing`` can be different, because the currently playing file hasn't
been unloaded yet; in this case, ``current`` refers to the new
selection. (Since mpv 0.7.0.)
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:
@@ -1270,8 +1174,6 @@ Property list
MPV_FORMAT_NODE_ARRAY
MPV_FORMAT_NODE_MAP (for each playlist entry)
"filename" MPV_FORMAT_STRING
"current" MPV_FORMAT_FLAG (might be missing; since mpv 0.7.0)
"playing" MPV_FORMAT_FLAG (same)
``track-list``
List of audio/video/sub tracks, current entry marked. Currently, the raw
@@ -1391,21 +1293,9 @@ Property list
It's also possible to write the property using this format.
``video-rotate`` (RW)
See ``--video-rotate`` option.
``seekable``
Return whether it's generally possible to seek in the current file.
``playback-abort``
Return whether playback is stopped or is to be stopped. (Useful in obscure
situations like during ``on_load`` hook processing, when the user can
stop playback, but the script has to explicitly end processing.)
``cursor-autohide`` (RW)
See ``--cursor-autohide``. Setting this to a new value will always update
the cursor, and reset the internal timer.
``osd-sym-cc``
Inserts the current OSD symbol as opaque OSD control code (cc). This makes
sense only with the ``show_text`` command or options which set OSD messages.
@@ -1428,104 +1318,12 @@ Property list
Note that you need to escape the ``\`` character, because the string is
processed for C escape sequences before passing it to the OSD code.
``vo-configured``
Return whether the VO is configured right now. Usually this corresponds to
whether the video window is visible. If the ``--force-window`` option is
used, this is usually always returns ``yes``.
``audio-device-list``
Return the list of discovered audio devices. This is mostly for use with
the client API, and reflects what ``--audio-device=help`` with the command
line player returns.
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 device entry)
"name" MPV_FORMAT_STRING
"description" MPV_FORMAT_STRING
The ``name`` is what is to be passed to the ``--audio-device`` option (and
often a rather cryptic audio API-specific ID), while ``description`` is
human readable free form text. The description is an empty string if none
was received.
``audio-device`` (RW)
Set the audio device. This directly reads/writes the ``--audio-device``
option, but on write accesses, the audio output will be scheduled for
reloading.
Writing this property while no audio output is active will not automatically
enable audio. (This is also true in the case when audio was disabled due to
reinitialization failure after a previous write access to ``audio-device``.)
This property also doesn't tell you which audio device is actually in use.
How these details are handled may change in the future.
``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.
``mpv-configuration``
Return the configuration arguments which were passed to the build system
(typically the way ``./waf configure ...`` was invoked).
``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
require reloading the file for changes to take effect. If there is an
equivalent property, prefer setting the property instead.
``file-local-options/<name>``
Similar to ``options/<name>``, but when setting an option through this
property, the option is reset to its old value once the current file has
stopped playing. Trying to write an option while no file is playing (or
is being loaded) results in an error.
(Note that if an option is marked as file-local, even ``options/`` will
access the local value, and the ``old`` value, which will be restored on
end of playback, can not be read or written until end of playback.)
``option-info/<name>``
Additional per-option information.
This has a number of sub-properties. Replace ``<name>`` with the name of
a top-level option. No guarantee of stability is given to any of these
sub-properties - they may change radically in the feature.
``option-info/<name>/name``
Returns the name of the option.
``option-info/<name>/type``
Return the name of the option type, like ``String`` or ``Integer``.
For many complex types, this isn't very accurate.
``option-info/<name>/set-from-commandline``
Return ``yes`` if the option was set from the mpv command line,
``no`` otherwise. What this is set to if the option is e.g. changed
at runtime is left undefined (meaning it could change in the future).
``option-info/<name>/default-value``
The default value of the option. May not always be available.
``option-info/<name>/min``, ``option-info/<name>/max``
Integer minimum and maximum values allowed for the option. Only
available if the options are numeric, and the minimum/maximum has been
set internally. It's also possible that only one of these is set.
``option-info/<name>/choices``
If the option is a choice option, the possible choices. Choices that
are integers may or may not be included (they can be implied by ``min``
and ``max``). Note that options which behave like choice options, but
are not actual choice options internally, may not have this info
available.
``property-list``
Return the list of top-level properties.
-183
View File
@@ -1,183 +0,0 @@
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. You can use the ``socat`` tool to send
commands (and receive reply) from the shell.
.. warning::
This is not intended to be a secure network protocol. It is explicitly
insecure: there is no authentication, no encryption, and the commands
themselves are insecure too. For example, the ``run`` command is exposed,
which can run arbitrary system commands. The use-case is controlling the
player locally. This is not different from the MPlayer slave protocol.
Protocol
--------
Clients can execute commands on the player by sending JSON messages of the
following form:
::
{ "command": ["command_name", "param1", "param2", ...] }
where ``command_name`` is the name of the command to be executed, followed by a
list of parameters. Parameters must be formatted as native JSON values
(integers, strings, booleans, ...). Every message **must** be terminated with
``\n``. Additionally, ``\n`` must not appear anywhere inside the message. In
practice this means that messages should be minified before being sent to mpv.
mpv will then send back a reply indicating whether the command was run
correctly, and an additional field holding the command-specific return data (it
can also be null).
::
{ "error": "success", "data": null }
mpv will also send events to clients with JSON messages of the following form:
::
{ "event": "event_name" }
where ``event_name`` is the name of the event. Additional event-specific fields
can also be present. See `List of events`_ for a list of all supported events.
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
with ``#`` and empty lines are ignored.
Currently, embedded 0 bytes terminate the current line, but you should not
rely on this.
Commands
--------
Additionally to the commands described in `List of Input Commands`_, a few
extra commands can also be used as part of the protocol:
``client_name``
Return the name of the client as string. This is the string ``ipc-N`` with
N being an integer number.
``get_time_us``
Return the current mpv internal time in microseconds as a number. This is
basically the system time, with an arbitrary offset.
``get_property``
Return the value of the given property. The value will be sent in the data
field of the replay message.
Example:
::
{ "command": ["get_property", "volume"] }
{ "data": 50.0, "error": "success" }
``get_property_string``
Like ``get_property``, but the resulting data will always be a string.
Example:
::
{ "command": ["get_property_string", "volume"] }
{ "data": "50.000000", "error": "success" }
``set_property``
Set the given property to the given value. See `Properties`_ for more
information about properties.
Example:
::
{ "command": ["set_property", "pause", true] }
{ "error": "success" }
``set_property_string``
Like ``set_property``, but the argument value must be passed as string.
Example:
::
{ "command": ["set_property_string", "pause", "yes"] }
{ "error": "success" }
``observe_property``
Watch a property for changes. If the given property is changed, then an
event of type ``property-change`` will be generated
Example:
::
{ "command": ["observe_property", 1, "volume"] }
{ "error": "success" }
{ "event": "property-change", "id": 1, "data": 52.0, "name": "volume" }
``observe_property_string``
Like ``observe_property``, but the resulting data will always be a string.
Example:
::
{ "command": ["observe_property_string", 1, "volume"] }
{ "error": "success" }
{ "event": "property-change", "id": 1, "data": "52.000000", "name": "volume" }
``unobserve_property``
Undo ``observe_property`` or ``observe_property_string``. This requires the
numeric id passed to the observe command as argument.
Example:
::
{ "command": ["unobserve_property", 1] }
{ "error": "success" }
``request_log_messages``
Enable output of mpv log messages. They will be received as events. The
parameter to this command is the log-level (see ``mpv_request_log_messages``
C API function).
Log message output is meant for humans only (mostly for debugging).
Attempting to retrieve information by parsing these messages will just
lead to breakages with future mpv releases. Instead, make a feature request,
and ask for a proper event that returns the information you need.
``suspend``
Suspend the mpv main loop. There is a long-winded explanation of this in
the C API function ``mpv_suspend()``. In short, this prevents the player
from displaying the next video frame, so that you don't get blocked when
trying to access the player.
``resume``
Undo one ``suspend`` call. ``suspend`` increments an internal counter, and
``resume`` decrements it. When 0 is reached, the player is actually resumed.
``get_version``
Returns the client API version the C API of the remote mpv instance
provides. (Also see ``DOCS/client-api-changes.rst``.)
UTF-8
-----
Normally, all strings are in UTF-8. Sometimes it can happen that strings are
in some broken encoding (often happens with file tags and such, and filenames
on many Unixes are not required to be in UTF-8 either). This means that mpv
sometimes sends invalid JSON. If that is a problem for the client application's
parser, it should filter the raw data for invalid UTF-8 sequences and perform
the desired replacement, before feeding the data to its JSON parser.
mpv will not attempt to construct invalid UTF-8 with broken escape sequences.
+37 -127
View File
@@ -1,12 +1,11 @@
LUA SCRIPTING
=============
mpv can load Lua scripts. Scripts passed to the ``--lua`` option, or found in
the ``lua`` subdirectory of the mpv configuration directory (usually
``~/.config/mpv/lua/``) will be loaded on program start. mpv also appends the
``lua`` subdirectory to the end of Lua's path so you can import scripts from
there too. Since it's added to the end, don't name scripts you want to import
the same as Lua libraries because they will be overshadowed by them.
mpv can load Lua scripts. Scripts in ``~/.mpv/lua/`` will be loaded on program
start, or if passed to ``--lua``. mpv appends ``~/.mpv/lua`` to the end of
lua's path so you can import them too. Since it's added to the end, don't name
scripts you want to import the same as lua libraries because they will be
overshadowed by them.
mpv provides the built-in module ``mp``, which provides functions to send
commands to the mpv core and to retrieve information about playback state, user
@@ -33,13 +32,12 @@ allow users implement features which are not going to be added to the mpv core.
Mode of operation
-----------------
Your script will be loaded by the player at program start from the ``lua``
configuration subdirectory, from a path specified with the ``--lua`` option, or
in some cases, internally (like ``--osc``). Each script runs in its own
thread. Your script is first run "as is", and once that is done, the event loop
is entered. This event loop will dispatch events received by mpv and call your
own event handlers which you have registered with ``mp.register_event``, or
timers added with ``mp.add_timeout`` or similar.
Your script will be loaded by the player at program start from ``~/.mpv/lua/``,
or ``--lua``, or in some cases, internally (like ``--osc``). Each script runs
in its own thread. Your script is first run "as is", and once that is done,
the event loop is entered. This event loop will dispatch events received by mpv
and call your own event handlers which you have registered with
``mp.register_event``, or timers added with ``mp.add_timeout`` or similar.
When the player quits, all scripts will be asked to terminate. This happens via
a ``shutdown`` event, which by default will make the event loop return. If your
@@ -94,15 +92,6 @@ The ``mp`` module is preloaded, although it can be loaded manually with
the ``expand-properties`` prefix, or the ``mp.get_property`` family of
functions.
``mp.command_native(table [,def])``
Similar to ``mp.commandv``, but pass the argument list as table. This has
the advantage that in at least some cases, arguments can be passed as
native types.
Returns a result table on success (usually empty), or ``def, error`` on
error. ``def`` is the second parameter provided to the function, and is
nil if it's missing.
``mp.get_property(name [,def])``
Return the value of the given property as string. These are the same
properties as used in input.conf. See `Properties`_ for a list of
@@ -181,7 +170,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
Return the current mpv internal time in seconds as a number. This is
basically the system time, with an arbitrary offset.
``mp.add_key_binding(key, name|fn [,fn [,flags]])``
``mp.add_key_binding(key, name|fn [,fn])``
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
@@ -198,23 +187,8 @@ The ``mp`` module is preloaded, although it can be loaded manually with
overwritten. You can omit the name, in which case a random name is generated
internally.
The last argument is used for optional flags. This is a table, which can
have the following entries:
``repeatable``
If set to ``true``, enables key repeat for this specific binding.
``complex``
If set to ``true``, then ``fn`` is called on both key up and down
events (as well as key repeat, if enabled), with the first
argument being a table. This table has an ``event`` entry, which
is set to one of the strings ``down``, ``repeat``, ``up`` or
``press`` (the latter if key up/down can't be tracked). It further
has an ``is_mouse`` entry, which tells whether the event was caused
by a mouse button.
Internally, key bindings are dispatched via the ``script_message_to`` or
``script_binding`` input commands and ``mp.register_script_message``.
Internally, key bindings are dispatched via the ``script_message_to`` input
command and ``mp.register_script_message``.
Trying to map multiple commands to a key will essentially prefer a random
binding, while the other bindings are not called. It is guaranteed that
@@ -237,7 +211,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
::
y script_binding something
y script_message something
This will print the message when the key ``y`` is pressed. (``x`` will
@@ -248,7 +222,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
::
y script_binding fooscript.something
y script_message_to fooscript something
``mp.add_forced_key_binding(...)``
This works almost the same as ``mp.add_key_binding``, but registers the
@@ -526,9 +500,6 @@ This built-in module provides generic helper functions for Lua, and have
strictly speaking nothing to do with mpv or video/audio playback. They are
provided for convenience. Most compensate for Lua's scarce standard library.
Be warned that any of these functions might disappear any time. They are not
strictly part of the guaranteed API.
``utils.getcwd()``
Returns the directory that mpv was launched from. On error, ``nil, error``
is returned.
@@ -569,60 +540,6 @@ strictly part of the guaranteed API.
Return the concatenation of the 2 paths. Tries to be clever. For example,
if ```p2`` is an absolute path, p2 is returned without change.
``utils.subprocess(t)``
Runs an external process and waits until it exits. Returns process status
and the captured output.
The paramater ``t`` is a table. The function reads the following entries:
``args``
Array of strings. The first array entry is the executable. This
can be either an absolute path, or a filename with no path
components, in which case the ``PATH`` environment variable is
used to resolve the executable. The other array elements are
passed as command line arguments.
``cancellable``
Optional. If set to ``true`` (default), then if the user stops
playback or goes to the next file while the process is running,
the process will be killed.
``max_size``
Optional. The maximum size in bytes of the data that can be captured
from stdout. (Default: 16 MB.)
The function returns a table as result with the following entries:
``status``
The raw exit status of the process. It will be negative on error.
``stdout``
Captured output stream as string, limited to ``max_size``.
``error``
``nil`` on success. The string ``killed`` if the process was
terminated in an unusual way. The string ``init`` if the process
could not be started.
On Windows, ``killed`` is only returned when the process has been
killed by mpv as a result of ``cancellable`` being set to ``true``.
In all cases, ``mp.resume_all()`` is implicitly called.
``utils.parse_json(str [, trail])``
Parses the given string argument as JSON, and returns it as a Lua table. On
error, returns ``nil, error``. (Currently, ``error`` is just a string
reading ``error``, because there is no fine-grained error reporting of any
kind.)
The returned value uses similar conventions as ``mp.get_property_native()``
to distinguish empty objects and arrays.
If the ``trail`` parameter is ``true`` (or any value equal to ``true``),
then trailing non-whitespace text is tolerated by the function, and the
trailing text is returned as 3rd return value. (The 3rd return value is
always there, but with ``trail`` set, no error is raised.)
Events
------
@@ -664,11 +581,28 @@ List of events
``playback-restart``
Start of playback after seek or after file was loaded.
``tracks-changed``
The list of video/audio/sub tracks was updated. (This happens on playback
start, and very rarely during playback.)
``track-switched``
A video/audio/subtitle track was switched on or off. This usually happens
when the user (or a script) changes the subtitle track and so on.
``idle``
Idle mode is entered. This happens when playback ended, and the player was
started with ``--idle`` or ``--force-window``. This mode is implicitly ended
when the ``start-file`` or ``shutdown`` events happen.
``pause``
Playback was paused. This also happens when for example the player is
paused on low network cache. Then the event type indicates the pause state
(like the property "pause" as opposed to the "core-idle" property), and you
might receive multiple ``pause`` events in a row.
``unpause``
Playback was unpaused. See above for details.
``tick``
Called after a video frame was displayed. This is a hack, and you should
avoid using it. Use timers instead and maybe watch pausing/unpausing events
@@ -728,32 +662,8 @@ List of events
``audio-reconfig``
Happens on audio output or filter reconfig.
The following events also happen, but are deprecated: ``tracks-changed``,
``track-switched``, ``pause``, ``unpause``, ``metadata-update``,
``chapter-change``. Use ``mp.observe_property()`` instead.
``metadata-update``
Metadata (like file tags) was updated.
Extras
------
This documents experimental features, or features that are "too special" and
we don't guarantee a stable interface to it.
``mp.add_hook(type, priority, fn)``
Add a hook callback for ``type`` (a string identifying a certain kind of
hook). These hooks allow the player to call script functions and wait for
their result (normally, the Lua scripting interface is asynchronous from
the point of view of the player core). ``priority`` is an arbitrary integer
that allows ordering among hooks of the same kind. Using the value 50 is
recommended as neutral default value. ``fn`` is the function that will be
called during execution of the hook.
Currently existing hooks:
``on_load``
Called when a file is to be opened, before anything is actually done.
For example, you could read and write the ``stream-open-filename``
property to redirect an URL to something else (consider support for
streaming sites which rarely give the user a direct media URL), or
you could set per-file options with by setting the property
``file-local-options/<option name>``. The player will wait until all
hooks are run.
``chapter-change``
The current chapter possibly changed.
+15 -64
View File
@@ -12,7 +12,8 @@ a media player
SYNOPSIS
========
| **mpv** [options] [file|URL|PLAYLIST|-]
| **mpv** [options] [file|URL|-]
| **mpv** [options] --playlist=PLAYLIST
| **mpv** [options] files
DESCRIPTION
@@ -46,10 +47,6 @@ UP and DOWN
Seek forward/backward 1 minute. Shift+arrow does a 5 second exact seek (see
``--hr-seek``).
Ctrl+LEFT and Ctrl+RIGHT
Seek to the previous/next subtitle. Subject to some restrictions and
might not work always; see ``sub_seek`` command.
[ and ]
Decrease/increase current playback speed by 10%.
@@ -63,7 +60,7 @@ BACKSPACE
Go backward/forward in the playlist.
ENTER
Go forward in the playlist.
Go forward in the playlist, even over the end.
p / SPACE
Pause (pressing again unpauses).
@@ -129,12 +126,6 @@ j and J
x and z
Adjust subtitle delay by +/- 0.1 seconds.
l
Set/clear A-B loop points. See ``ab_loop`` command for details.
Ctrl + and Ctrl -
Adjust audio delay by +/- 0.1 seconds.
V
Toggle subtitle VSFilter aspect compatibility mode. See
``--ass-vsfilter-aspect-compat`` for more info.
@@ -271,7 +262,7 @@ need to escape special characters. To work this around, the path can be
additionally wrapped in the ``%n%string_of_length_n`` syntax (see above).
Some mpv options interpret paths starting with ``~``. Currently, the prefix
``~~/`` expands to the mpv configuration directory (usually ``~/.config/mpv/``).
``~~/`` expands to the mpv configuration directory (usually ``~/.mpv/``).
``~/`` expands to the user's home directory. (The trailing ``/`` is always
required.)
@@ -361,11 +352,11 @@ File-specific Configuration Files
You can also write file-specific configuration files. If you wish to have a
configuration file for a file called 'video.avi', create a file named
'video.avi.conf' with the file-specific options in it and put it in
``~/.config/mpv/``. You can also put the configuration file in the same directory
as the file to be played. Both require you to set the ``--use-filedir-conf``
``~/.mpv/``. You can also put the configuration file in the same directory
as the file to be played, as long as you give the ``--use-filedir-conf``
option (either on the command line or in your global config file). If a
file-specific configuration file is found in the same directory, no
file-specific configuration is loaded from ``~/.config/mpv``. In addition, the
file-specific configuration is loaded from ``~/.mpv``. In addition, the
``--use-filedir-conf`` option enables directory-specific configuration files.
For this, mpv first tries to load a mpv.conf from the same directory
as the file played and then tries to load any file-specific configuration.
@@ -451,15 +442,14 @@ listed.
if there is audio "missing", or not enough frames can be dropped. Usually
this will indicate a problem. (``total-avsync-change`` property.)
- Encoding state in ``{...}``, only shown in encoding mode.
- Dropped frames, e.g. ``Dropped: 4``. Shows up only if the count is not 0. Can
grow if the video framerate is higher than that of the display, or if video
rendering is too slow. Also can be incremented on "hiccups" and when the video
frame couldn't be displayed on time. (``vo-drop-frame-count`` property.)
If the decoder drops frames, the number of decoder-dropped frames is appended
to the display as well, e.g.: ``Dropped: 4/34``. This should almost never
happen, unless decoder-framedropping is enabled with one of the
``--framedrop`` options, the stream contains errors, or a weird codec is in
use. (``drop-frame-count`` property.)
- Decoder-dropped video frames, e.g. ``SD: 2``. Shows up only if the count is
not 0. Normally should never show up, unless ``--framedrop`` is set to enable
this mode, and your CPU is too slow. (``drop-frame-count`` property.)
- VO-dropped video frames, e.g. ``D: 4``. Shows up only if the count is not 0.
Can grow if the video framerate is higher than that of the display, or if
video rendering is too slow. Also can be incremented on "hiccups" and when
the video frame couldn't be displayed on time. (``vo-drop-frame-count``
property.)
- Cache state, e.g. ``Cache: 2s+134KB``. Visible if the stream cache is enabled.
The first value shows the amount of video buffered in the demuxer in seconds,
the second value shows *additional* data buffered in the stream cache in
@@ -474,10 +464,6 @@ PROTOCOLS
be specified. mpv will never attempt to guess whether a filename is
actually a network address. A protocol prefix is always required.
Note that not all prefixes are documented here. Undocumented prefixes are
either aliases to documented protocols, or are just redirections to
protocols implemented and documented in FFmpeg.
``-``
Play data from stdin.
@@ -567,8 +553,6 @@ PROTOCOLS
.. include:: lua.rst
.. include:: ipc.rst
.. include:: changes.rst
ENVIRONMENT VARIABLES
@@ -724,39 +708,6 @@ override the standard directory ``~/.config/mpv/``.
Also, the old config location at ``~/.mpv/`` is still read, and if the XDG
variant does not exist, will still be preferred.
FILES ON WINDOWS
================
On win32 (if compiled with MinGW, but not Cygwin), the default config file
locations are different:
``%APPDATA%/mpv/mpv.conf``
Preferred mpv config file. This maps to a system and user-specific path,
for example ``C:\users\USERNAME\Application Data\mpv\mpv.conf``. You can
find the exact path by running ``echo %APPDATA%\mpv\mpv.conf`` in cmd.exe.
``%APPDATA%/mpv/input.conf``
key bindings (see `INPUT.CONF`_ section)
``%APPDATA%/mpv/lua/``
equivalent of ``~/.config/mpv/lua/`` on Unix.
The environment variable ``$MPV_HOME`` completely overrides these, like on
UNIX.
Config files located in the same directory as ``mpv.exe`` are loaded with
lower priority. Some config files are loaded only once, which means that
e.g. of 2 ``input.conf`` files located in two config directories, only the
one from the directory with higher priority will be loaded.
A third config directory with lowest priority is the directory named ``mpv``
in the same directory as ``mpv.exe``. This used to be the directory with
highest priority, but is now discouraged to use and might be removed in the
future.
Note that mpv likes to mix ``/`` and ``\`` path separators for simplicity.
kernel32.dll accepts this, but cmd.exe does not.
EXAMPLES OF MPV USAGE
=====================
+92 -225
View File
@@ -45,15 +45,6 @@ Track Selection
``--vid=<ID|auto|no>``
Select video channel. ``auto`` selects the default, ``no`` disables video.
``--ff-aid=<ID|auto|no>``, ``--ff-sid=<ID|auto|no>``, ``--ff-vid=<ID|auto|no>``
Select audio/subtitle/video streams by the FFmpeg stream index. The FFmpeg
stream index is relatively arbitrary, but useful when interacting with
other software using FFmpeg (consider ``ffprobe``).
Note that with external tracks (added with ``--sub-file`` and similar
options) will have duplicate IDs. In that case, the first stream in order
is selected.
``--edition=<ID|auto>``
(Matroska files only)
Specify the edition (set of chapters) to use, where 0 is the first. If set
@@ -104,9 +95,6 @@ Playback Control
``--speed=<0.01-100>``
Slow down or speed up playback by the factor given as parameter.
If ``--audio-pitch-correction`` is used, playing with a speed higher than
normal automatically inserts the ``scaletempo`` audio filter.
``--loop=<N|inf|no>``
Loops playback ``N`` times. A value of ``1`` plays it one time (default),
``2`` two times, etc. ``inf`` means forever. ``no`` is the same as ``1`` and
@@ -128,23 +116,21 @@ Playback Control
no format is detected, t will be treated as list of files, separated by
newline characters. Note that XML playlist formats are not supported.)
You can play playlists directly and without this option, however, this
option disables any security mechanisms that might be in place. You may
also need this option to load plaintext files as playlist.
.. warning::
The way mpv uses playlist files via ``--playlist`` is not safe against
maliciously constructed files. Such files may trigger harmful actions.
The way mpv uses playlist files is not safe against maliciously
constructed files. Such files may trigger harmful actions.
This has been the case for all mpv and MPlayer versions, but
unfortunately this fact was not well documented earlier, and some people
have even misguidedly recommended use of ``--playlist`` with untrusted
sources. Do NOT use ``--playlist`` with random internet sources or files
you do not trust!
Playlist can contain entries using other protocols, such as local files,
or (most severely), special protocols like ``avdevice://``, which are
inherently unsafe.
The main problem is that playlists can point to arbitrary network
addresses (including local addresses inside of your LAN), and thus
can't be considered secure. Playlists also can contain entries using
other protocols, such as local files, or (most severely), special
protocols like ``avdevice://``, which are inherently unsafe.
``--chapter-merge-threshold=<number>``
Threshold for merging almost consecutive ordered chapter parts in
@@ -229,12 +215,6 @@ Playback Control
between the two option is that this option performs a seek on loop, instead
of reloading the file.
``--ab-loop-a=<time>``, ``--ab-loop-b=<time>``
Set loop points. If playback passes the ``b`` timestamp, it will seek to
the ``a`` timestamp. Seeking past the ``b`` point doesn't loop (this is
intentional). The loop-points can be adjusted at runtime with the
corresponding properties. See also ``ab_loop`` command.
``--ordered-chapters``, ``--no-ordered-chapters``
Enabled by default.
Disable support for Matroska ordered chapters. mpv will not load or
@@ -253,10 +233,6 @@ Playback Control
Note: a playlist can be as simple as a text file containing filenames
separated by newlines.
``--chapters-file=<filename>``
Load chapters from this file, instead of using the chapter metadata found
in the main file.
``--sstep=<sec>``
Skip <sec> seconds after every frame.
@@ -264,12 +240,6 @@ Playback Control
Without ``--hr-seek``, skipping will snap to keyframes.
``--stop-playback-on-init-failure=<yes|no>``
Stop playback if either audio or video fails to initialize. Currently,
the default behavior is ``no`` for the command line player, but ``yes``
for libmpv. With ``no``, playback will continue in video-only or audio-only
mode if one of them fails. This doesn't affect playback of audio-only or
video-only files.
Program Behavior
----------------
@@ -285,8 +255,9 @@ Program Behavior
Print version string and exit.
``--no-config``
Do not load default configuration files. This prevents loading of both the
user-level and system-wide ``mpv.conf`` and ``input.conf`` files. Other
Do not load default configuration files. This prevents loading of
``~/.config/mpv/mpv.conf`` and ``~/.config/mpv/input.conf``, as well as
loading the same files from system wide configuration directories. Other
configuration files are blocked as well, such as resume playback files.
.. note::
@@ -342,8 +313,7 @@ Program Behavior
Specify configuration file to be parsed after the default ones.
``--load-scripts=<yes|no>``
If set to ``no``, don't auto-load scripts from the ``lua`` configuration
subdirectory (usually ``~/.config/mpv/lua/``).
If set to ``no``, don't auto-load scripts from ``~/.mpv/lua/``.
(Default: ``yes``)
``--lua=<filename>``
@@ -358,15 +328,14 @@ Program Behavior
``--merge-files``
Pretend that all files passed to mpv are concatenated into a single, big
file. This uses timeline/EDL support internally. Note that this won't work
for ordered chapter files.
for ordered chapter files or quvi-resolved URLs (such as YouTube links).
This option is interpreted at program start, and doesn't affect for
example files or playlists loaded with the ``loadfile`` or ``loadlist``
commands.
``--no-resume-playback``
Do not restore playback position from the ``watch_later`` configuration
subdirectory (usually ``~/.config/mpv/watch_later/``).
Do not restore playback position from ``~/.mpv/watch_later/``.
See ``quit_watch_later`` input command.
``--profile=<profile1,profile2,...>``
@@ -422,24 +391,6 @@ Program Behavior
May be dangerous if playing from untrusted media.
``--ytdl``, ``--no-ytdl``
Enable the youtube-dl hook-script. It will look at the input URL, and will
play the video located on the website. This works with many streaming sites,
not just the one the scripts are named after. This requires a recent version
of youtube-dl to be installed on the system.
If the script can't do anything with an URL, it will do nothing.
Currently disabled by default, because youtube-dl's generic extractor can
get stuck on some URL, preventing playback.
(Note: this is the replacement for the now removed libquvi support.)
``--ytdl-format=<best|worst|mp4|webm|...>``
Video format/quality that is directly passed to youtube-dl. The possible
values are specific to the website and the video, for a given url the
available formats can be found with the command
``youtube-dl --list-formats URL``.
Video
-----
@@ -528,18 +479,15 @@ Video
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux with Intel GPUs only)
:vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
:vda: requires ``--vo=opengl`` (OS X only)
:dxva2-copy: copies video back to system RAM (Windows only) (experimental)
``auto`` tries to automatically enable hardware decoding using the first
available method. This still depends what VO you are using. For example,
if you are not using ``--vo=vdpau`` or ``--vo=opengl``, vdpau decoding will
never be enabled. Also note that if the first found method doesn't actually
work, it will always fall back to software decoding, instead of trying the
next method (might matter on some Linux systems).
if you are not using ``--vo=vdpau``, vdpau decoding will never be enabled.
Also note that if the first found method doesn't actually work, it will
always fall back to software decoding, instead of trying the next method.
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.
The ``vaapi-copy`` function allows you to use vaapi with any VO. Because
this copies the decoded video back to system RAM, it's quite inefficient.
.. note::
@@ -714,6 +662,26 @@ Video
``mpv --hwdec=vdpau --vo=vdpau --hwdec-codecs=h264,mpeg2video``
Enable vdpau decoding for h264 and mpeg2 only.
``--quvi-format=<best|default|...>``
Video format/quality that is directly passed to libquvi (default: ``best``).
This is used when opening links to streaming sites like YouTube. The
interpretation of this value is highly specific to the streaming site and
the video.
libquvi 0.4.x:
The only well-defined values that work on all sites are ``best``
(best quality/highest bandwidth, default), and ``default`` (lowest
quality).
The quvi command line tool can be used to find out which formats are
supported for a given URL: ``quvi --query-formats URL``.
libquvi 0.9.x:
The following explanations are relevant:
`<http://quvi.sourceforge.net/r/api/0.9/glossary_termino.html#m_stream_id>`_
``--vd-lavc-check-hw-profile=<yes|no>``
Check hardware decoder profile (default: yes). If ``no`` is set, the
highest profile of the hardware decoder is unconditionally selected, and
@@ -787,30 +755,6 @@ Video
Audio
-----
``--audio-pitch-correction=<yes|no>``
If this is enabled (default), playing with a speed higher than normal
automatically inserts the ``scaletempo`` audio filter. For details, see
audio filter section.
``--audio-device=<name>``
Use the given audio device. This consists of the audio output name, e.g.
``alsa``, followed by ``/``, followed by the audio output specific device
name.
You can list audio devices with ``--audio-device=help``. This outputs the
device name in quotes, followed by a description. The device name is what
you have to pass to the ``--audio-device`` option.
The default value for this option is ``auto``, which tries every audio
output in preference order with the default device.
Note that many AOs have a ``device`` sub-option, which overrides the
device selection of this option (but not the audio output selection).
Likewise, forcing an AO with ``--ao`` will override the audio output
selection of ``--audio-device`` (but not the device selecton).
Currently not implemented for most AOs.
``--ao=<driver1[:suboption1[=value]:...],driver2,...[,]>``
Specify a priority list of audio output drivers to be used. For
interactive use one would normally specify a single one to use, but in
@@ -939,9 +883,8 @@ Audio
lists speaker names, which can be used to express arbitrary channel
layouts (e.g. ``fl-fr-lfe`` is 2.1).
You can use ``--audio-channels=auto`` to disable this. In this case, the AO
use the channel layout as the audio filter chain indicates. (``empty`` is
an accepted obsolete for ``auto``.)
You can use ``--audio-channels=empty`` to disable this. In this case, the AO
use the channel layout as the audio filter chain indicates.
This will also request the channel layout from the decoder. If the decoder
does not support the layout, it will fall back to its native channel layout.
@@ -978,7 +921,7 @@ Audio
of the current media, the lavrresample audio filter will be inserted into
the audio filter layer to compensate for the difference.
``--gapless-audio=<no|yes|weak>``
``--gapless-audio=<no|yes|weak``
Try to play consecutive audio files with no silence or disruption at the
point of file change. Default: ``weak``.
@@ -1033,11 +976,6 @@ Audio
maximum amplification, i.e. amplify by 200%. The default volume (no
change in volume) will be ``50`` in this case.
``--audio-client-name=<name>``
The application name the player reports to the audio API. Can be useful
if you want to force a different audio profile (e.g. with PulseAudio),
or to set your own application name when using libmpv.
``--volume-restore-data=<string>``
Used internally for use by playback resume (e.g. with ``quit_watch_later``).
Restoring value has to be done carefully, because different AOs as well as
@@ -1122,7 +1060,7 @@ Subtitles
This affects ASS subtitles as well, and may lead to incorrect subtitle
rendering. Use with care, or use ``--sub-text-font-size`` instead.
``--sub-scale-with-window=<yes|no>``
``--sub-scale-with-window=yes|no``
Make the subtitle font size relative to the window, instead of the video.
This is useful if you always want the same font size, even if the video
doesn't covert the window fully, e.g. because screen aspect and window
@@ -1165,7 +1103,7 @@ Subtitles
Using this option may lead to incorrect subtitle rendering.
``--ass-hinting=<none|light|normal|native>``
``--ass-hinting=none|light|normal|native``
Set font hinting type. <type> can be:
:none: no hinting (default)
@@ -1183,7 +1121,7 @@ Subtitles
``--ass-line-spacing=<value>``
Set line spacing value for SSA/ASS renderer.
``--ass-shaper=<simple|complex>``
``--ass-shaper=simple|complex``
Set the text layout engine used by libass.
:simple: uses Fribidi only, fast, doesn't render some languages correctly
@@ -1207,7 +1145,6 @@ Subtitles
:yes: Apply all the ``--ass-*`` style override options. Changing the default
for any of these options can lead to incorrect subtitle rendering
(default).
:signfs: like ``yes``, but apply ``--sub-scale`` only to signs
:no: Render subtitles as forced by subtitle scripts.
:force: Try to force the font style as defined by the ``--sub-text-*``
options. Requires a modified libass, can break rendering easily.
@@ -1268,6 +1205,15 @@ Subtitles
a subtitle script with another video file. The ``--ass-style-override``
option doesn't affect how this option is interpreted.
``--quvi-fetch-subtitles=<yes|no>``
Toggles fetching of subtitles from streaming sites with libquvi. Disabled
by default, because it's unreliable and slow. Note that when enabled,
subtitles will always be fetched, even if subtitles are explicitly
disabled with ``--no-sub`` (because you might want to enable subtitles
at runtime).
Supported when using libquvi 0.9.x.
``--stretch-dvd-subs=<yes|no>``
Stretch DVD subtitles when playing anamorphic videos for better looking
fonts on badly mastered DVDs. This switch has no effect when the
@@ -1416,7 +1362,7 @@ Subtitles
- ``/path/to/video/sub/``
- ``/path/to/video/subtitles/``
- ``/tmp/subs/``
- the ``sub`` configuration subdirectory (usually ``~/.config/mpv/sub/``)
- ``~/.mpv/sub/``
``--sub-visibility``, ``--no-sub-visibility``
Can be used to disable display of subtitles, but still select and decode
@@ -1663,6 +1609,13 @@ Window
there is a change in video parameters, video stream or file. This used to
be the default behavior. Currently only affects X11 VOs.
``--fs-missioncontrol``
(OS X only)
Use OS X Mission Control's fullscreen feature instead of the custom one
provided by mpv. This can potentially break a lot of stuff like
``--geometry`` and is disabled by default. On the other hand it provides
a more 'OS X-like' user experience.
``--heartbeat-cmd=<command>``
Command that is executed every 30 seconds during playback via *system()* -
i.e. using the shell. The time between the commands can be customized with
@@ -1704,13 +1657,6 @@ Window
disable the window manager hints that force the window aspect ratio.
(Ignored in fullscreen mode.)
``--no-keepaspect-window``, ``--keepaspect-window``
``--keepaspect-window`` (the default) will lock the window size to the
video aspect. ``--no-keepaspect-window`` disables this behavior, and will
instead add black bars if window aspect and video aspect mismatch. Whether
this actually works depends on the VO backend.
(Ignored in fullscreen mode.)
``--monitoraspect=<ratio>``
Set the aspect ratio of your monitor or TV screen. A value of 0 disables a
previous setting (e.g. in the config file). Overrides the
@@ -1738,25 +1684,14 @@ Window
to to work this around using ``--heartbeat-cmd`` instead.
``--wid=<ID>``
This tells mpv to attach to an existing window. If a VO is selected that
supports this option, it will use that window for video output. mpv will
scale the video to the size of this window, and will add black bars to
compensate if the aspect ratio of the video is different.
(X11 and Windows only)
This tells mpv to attach to an existing window. The ID is interpreted as
"Window" on X11, and as HWND on Windows. If a VO is selected that supports
this option, a new window will be created and the given window will be set
as parent. The window will always be resized to cover the parent window
fully, and will add black bars to compensate for the video aspect ratio.
On X11, the ID is interpreted as a ``Window`` on X11. Unlike
MPlayer/mplayer2, mpv always creates its own window, and sets the wid
window as parent. The window will always be resized to cover the parent
window fully. The value ``0`` is interpreted specially, and mpv will
draw directly on the root window.
On win32, the ID is interpreted as ``HWND``. Pass it as value cast to
``intptr_t``. mpv will create its own window, and set the wid window as
parent, like with X11.
On OSX/Cocoa. the ID is interpreted as ``NSView*``. Pass it as value cast
to ``intptr_t``. mpv will creates its own sub-view. Because OSX does not
support window embedding of foreign processes, this works only with libmpv,
and will crash when used from the command line.
See also ``--slave-broken``.
``--no-window-dragging``
Don't move the window when clicking on it and moving the mouse pointer.
@@ -1764,26 +1699,16 @@ Window
``--x11-name``
Set the window class name for X11-based video output methods.
``--x11-netwm=<yes|no|auto>``
``--x11-netwm=no``
(X11 only)
Control the use of NetWM protocol features.
Disable use of the NetWM protocol when switching to or from fullscreen.
This may or may not help with broken window managers. This provides some
functionality that was implemented by the now removed ``--fstype`` option.
Actually, it is not known to the developers to which degree this option
was needed, so feedback is welcome.
Specifically, ``yes`` will force use of NetWM fullscreen support, even if
not advertised by the WM. This can be useful for WMs that are broken on
purpose, like XMonad. (XMonad supposedly doesn't advertise fullscreen
support, because Flash uses it. Apparently, applications which want to
use fullscreen anyway are supposed to either ignore the NetWM support hints,
or provide a workaround. Shame on XMonad for deliberately breaking X
protocols (as if X isn't bad enough already).
By default, NetWM support is autodetected (``auto``).
This option might be removed in the future.
By default, NetWM support is autodetected, and using this option forces
autodetection to fail.
Disc Devices
@@ -1843,7 +1768,7 @@ Disc Devices
Add ``<value>`` sectors to the values reported when addressing tracks.
May be negative.
``--cdda-skip=<yes|no>``
``--cdda-skip=<es|no``
(Never) accept imperfect data reconstruction.
``--dvd-speed=<speed>``
@@ -2062,22 +1987,6 @@ Demuxer
and hr-seeks, and this option changes behavior with relative or imprecise
seeks only.
You can use the ``--demuxer-mkv-subtitle-preroll-secs`` option to specify
how mach data the demuxer should pre-read at most in order to find subtitle
packets that may overlap. Setting this to 0 will effectively disable this
preroll mechanism. Setting a very large value can make seeking very slow,
and an extremely large value would completely reread the entire file from
start to seek target on every seek - seeking can become slower towards the
end of the file. The details are messy, and the value is actually rounded
down to the cluster with the previous video keyframe.
Some files, especially files muxed with newer mkvmerge versions, have
information embedded that can be used to determine what subtitle packets
overlap with a seek target. In these cases, mpv will reduce the amount
of data read to a minimum. (Although it will still read *all* data between
the cluster that contains the first wanted subtitle packet, and the seek
target.)
See also ``--hr-seek-demuxer-offset`` option. This option can achieve a
similar effect, but only if hr-seek is active. It works with any demuxer,
but makes seeking much slower, as it has to decode audio and video data
@@ -2085,16 +1994,6 @@ Demuxer
``--mkv-subtitle-preroll`` is a deprecated alias.
``--demuxer-mkv-subtitle-preroll-secs=<value>``
See ``--demuxer-mkv-subtitle-preroll``.
``--demuxer-mkv-probe-video-duration``
When opening the file, seek to the end of it, and check what timestamp the
last video packet has, and report that as file duration. This is strictly
for compatibility with Haali only. In this mode, it's possible that opening
will be slower (especially when playing over http), or that behavior with
broken files is much worse. So don't use this option.
``--demuxer-rawaudio-channels=<value>``
Number of channels (or channel layout) if ``--demuxer=rawaudio`` is used
(default: stereo).
@@ -2141,7 +2040,7 @@ Demuxer
playback, but on the other hand can add delays to seeking or track
switching.
``--demuxer-readahead-secs=<seconds>``
``--demuxer-readahead-secs=N``
If ``--demuxer-thread`` is enabled, this controls how much the demuxer
should buffer ahead in seconds (default: 0.2). As long as no packet has
a timestamp difference higher than the readahead amount relative to the
@@ -2150,7 +2049,7 @@ Demuxer
(This value tends to be fuzzy, because many file formats don't store linear
timestamps.)
``--demuxer-readahead-packets=<packets>``
``--demuxer-readahead-packets=N``
If ``--demuxer-thread`` is enabled, this controls how much the demuxer
should buffer ahead. As long as the number of packets in the packet queue
doesn't exceed ``--demuxer-readahead-packets``, and the total number of
@@ -2162,7 +2061,7 @@ Demuxer
See ``--list-options`` for defaults and value range.
``--demuxer-readahead-bytes=<bytes>``
``--demuxer-readahead-bytes=N``
See ``--demuxer-readahead-packets``.
@@ -2182,8 +2081,8 @@ Input
Number of key presses to generate per second on autorepeat.
``--input-conf=<filename>``
Specify input configuration file other than the default location in the mpv
configuration directory (usually ``~/.config/mpv/input.conf``).
Specify input configuration file other than the default
``~/.config/mpv/input.conf``.
``--no-input-default-bindings``
Disable mpv default (built-in) key bindings.
@@ -2213,11 +2112,7 @@ Input
like any other binding). See `INPUT.CONF`_.
``--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
bi-directional. On MS Windows, JSON commands are not available.
Read commands from the given file. Mostly useful with a FIFO.
See also ``--slave-broken``.
.. note::
@@ -2234,16 +2129,8 @@ Input
or intend to read from stdin later on via the loadfile or loadlist slave
commands.
``--input-unix-socket=<filename>``
Enable the IPC support and create the listening socket at the given path.
See `JSON IPC`_ for details.
Not available on MS Windows.
``--input-appleremote=<yes|no>``
(OS X only)
Enable/disable Apple Remote support. Enabled by default (except for libmpv).
``--input-appleremote``, ``--no-input-appleremote``
Enable/disable AppleIR remote support. Enabled by default.
``--input-cursor``, ``--no-input-cursor``
Permit mpv to receive pointer events reported by the video output
@@ -2263,18 +2150,16 @@ Input
(LIRC only)
Specifies a configuration file for LIRC (default: ``~/.lircrc``).
``--input-media-keys=<yes|no>``
(OS X only)
Enable/disable media keys support. Enabled by default (except for libmpv).
``--input-media-keys``, ``--no-input-media-keys``
OS X only: Enabled by default. Enables/disable media keys support.
``--input-right-alt-gr``, ``--no-input-right-alt-gr``
(Cocoa and Windows only)
Use the right Alt key as Alt Gr to produce special characters. If disabled,
count the right Alt as an Alt modifier key. Enabled by default.
``--input-vo-keyboard=<yes|no>``
Disable all keyboard input on for VOs which can't participate in proper
keyboard input dispatching. This currently affects X11. Generally useful for
``--input-x11-keyboard=<yes|no>``
Disable all keyboard input on the X11 VO window. Generally useful for
embedding only.
On X11, a sub-window with input enabled grabs all keyboard input as long
@@ -2292,13 +2177,6 @@ Input
As a workaround, this option is disabled by default in libmpv. (Note that
``input-default-bindings`` is disabled by default in libmpv as well.)
(This option was renamed from ``--input-x11-keyboard``.)
``--input-app-events=<yes|no>``
(OS X only)
Enable/disable application wide keyboard events so that keyboard shortcuts
can be processed without a window. Enabled by default (except for libmpv).
OSD
---
@@ -2337,7 +2215,7 @@ OSD
height: if the window height is larger or smaller than 720, the actual size
of the text increases or decreases as well.
Default: 55.
Default: 45.
``--osd-msg1=<string>``
Show this string as message on OSD with OSD level 1 (visible by default).
@@ -2411,7 +2289,7 @@ OSD
Size of the OSD/sub font border in scaled pixels (see ``--osd-font-size``
for details). A value of 0 disables borders.
Default: 3.
Default: 2.5.
``--osd-color=<color>``, ``--sub-text-color=<color>``
Specify the color used for OSD/unstyled text subtitles.
@@ -2469,12 +2347,12 @@ OSD
for unstyled text subtitles. If you just want to raise the vertical
subtitle position, use ``--sub-pos``.
Default: 22.
Default: 10.
``--osd-scale=<factor>``
OSD font size multiplier, multiplied with ``--osd-font-size`` value.
``--osd-scale-by-window=<yes|no>``
``--osd-scale-by-window=yes|no``
Whether to scale the OSD with the window size (default: yes). If this is
disabled, ``--osd-font-size`` and other OSD options that use scaled pixels
are always in actual pixels. The effect is that changing the window size
@@ -2496,16 +2374,6 @@ OSD
Default: 0.
``--use-text-osd=<yes|no>``
Disable text OSD rendering completely. (This includes the complete OSC as
well.) This is mostly useful for avoiding loading fontconfig in situations
where fontconfig does not behave well, and OSD is unused - this could for
example allow GUI programs using libmpv to workaround fontconfig issues.
Note that selecting subtitles of any kind still initializes fontconfig.
Default: ``no``.
Screenshot
----------
@@ -3095,10 +2963,9 @@ DVB
Specifies using card number 1-4 (default: 1).
``--dvbin-file=<filename>``
Instructs mpv to read the channels list from ``<filename>``. The default is
in the mpv configuration directory (usually ``~/.config/mpv``) with the
filename ``channels.conf.{sat,ter,cbl,atsc}`` (based on your card type) or
``channels.conf`` as a last resort.
Instructs mpv to read the channels list from ``<filename>``. Default is
``~/.mpv/channels.conf.{sat,ter,cbl,atsc}`` (based on your card type) or
``~/.mpv/channels.conf`` as a last resort.
``--dvbin-timeout=<1-30>``
Maximum number of seconds to wait when trying to tune a frequency before
+1 -5
View File
@@ -161,7 +161,7 @@ To avoid collisions with other scripts, all options need to be prefixed with
Example::
--lua-opts=osc-optionA=value1,osc-optionB=value2
--lua-opts=osc-optionA=value1:osc-optionB=value2
Configurable Options
@@ -229,10 +229,6 @@ Configurable Options
| Default: yes
| Display a tooltip over the seekbar indicating time at mouse position.
``seekbarstyle``
| Default: slider
| Sets the style of the seekbar, slider (diamond marker) or bar (fill)
Script Commands
~~~~~~~~~~~~~~~
-25
View File
@@ -910,31 +910,6 @@ Available filters are:
making it higher than the number of cores can actually make it
slower.
``vapoursynth-lazy``
The same as ``vapoursynth``, but doesn't load Python scripts. Instead, a
custom backend using Lua and the raw VapourSynth API is used. The syntax
is completely different, and absolutely no conveniencve features are
provided. There's no type checking either, and you can trigger crashes.
.. admonition:: Example:
::
video_out = invoke("morpho", "Open", {clip = video_in})
The special variable ``video_in`` is the mpv video source, while the
special variable ``video_out`` is used to read video from. The 1st argument
is the plugin (queried with ``getPluginByNs``), the 2nd is the filter name,
and the 3rd argument is a table with the arguments. Positional arguments
are not supported. The types must match exactly. Since Lua is terrible and
can't distinguish integers and floats, integer arguments must be prefixed
with ``i_``, in which case the prefix is removed and the argument is cast
to an integer. Should the argument's name start with ``i_``, you're out of
luck.
Clips (VSNodeRef) are passed as light userdata, so trying to pass any
other userdata type will result in hard crashes.
``vavpp``
VA-AP-API video post processing. Works with ``--vo=vaapi`` and ``--vo=opengl``
only. Currently deinterlaces. This filter is automatically inserted if
+11 -27
View File
@@ -233,29 +233,18 @@ Available video output drivers are:
Always force textures to power of 2, even if the device reports
non-power-of-2 texture sizes as supported.
``texture-memory=<mode>``
``texture-memory=N``
Only affects operation with shaders/texturing enabled, and (E)OSD.
Possible values:
Values for N:
``default`` (default)
Use ``D3DPOOL_DEFAULT``, with a ``D3DPOOL_SYSTEMMEM`` texture for
locking. If the driver supports ``D3DDEVCAPS_TEXTURESYSTEMMEMORY``,
``D3DPOOL_SYSTEMMEM`` is used directly.
``default-pool``
Use ``D3DPOOL_DEFAULT``. (Like ``default``, but never use a
shadow-texture.)
``default-pool-shadow``
Use ``D3DPOOL_DEFAULT``, with a ``D3DPOOL_SYSTEMMEM`` texture for
locking. (Like ``default``, but always force the shadow-texture.)
``managed``
Use ``D3DPOOL_MANAGED``.
``scratch``
Use ``D3DPOOL_SCRATCH``, with a ``D3DPOOL_SYSTEMMEM`` texture for
locking.
0
default, will often use an additional shadow texture + copy
1
use ``D3DPOOL_MANAGED``
2
use ``D3DPOOL_DEFAULT``
3
use ``D3DPOOL_SYSTEMMEM``, but without shadow texture
``swap-discard``
Use ``D3DSWAPEFFECT_DISCARD``, which might be faster.
@@ -449,17 +438,12 @@ Available video output drivers are:
this options will make rendering a single operation.
Note that chroma scalers are always done as 1-pass filters.
``cscale=<filter>``
``cscale=<n>``
As ``lscale``, but for chroma (2x slower with little visible effect).
Note that with some scaling filters, upscaling is always done in
RGB. If chroma is not subsampled, this option is ignored, and the
luma scaler is used instead. Setting this option is often useless.
``lscale-down=<filter>``, ``cscale-down=<filter>``
Like ``lscale`` and ``cscale``, but apply these filters on downscaling
instead. If these options are unset, the filter implied by ``lscale``
(and ``cscale``, respectively) will be applied.
``cparam1``, ``cparam2``, ``cradius``
Set filter parameters and radius for ``cscale``.
+8 -10
View File
@@ -3,8 +3,6 @@ mpv
[![Build Status](https://api.travis-ci.org/mpv-player/mpv.png)](https://travis-ci.org/mpv-player/mpv)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/1232/badge.svg)](https://scan.coverity.com/projects/1232)
Overview
--------
@@ -50,7 +48,7 @@ Essential dependencies (incomplete list):
- FFmpeg libraries (libavutil libavcodec libavformat libswscale libavresample)
At least FFmpeg 2.1.4 or Libav 10 is required.
- libjpeg (for screenshots)
- Lua (strictly speaking optional, but required for the OSC pseudo-GUI)
- libquvi if you want to play Youtube videos directly
Libass dependencies:
@@ -119,17 +117,17 @@ make code review easier for both parties later on.
Contact
-------
Most activity happens on the IRC channel and the github issue tracker. The
mailing lists are mostly unused.
These forms of contact are meant to ask questions about mpv usage, give
feedback on mpv and discuss it's development.
- **Github issue tracker**: [issue tracker][issue-tracker] (report bugs here)
- **User IRC Channel**: `#mpv-player` on `irc.freenode.net`
- **Developer IRC Channel**: `#mpv-player-dev` on `irc.freenode.net`
If possible, please avoid posting bugs here and use the [issue tracker][issue-tracker]
instead.
- **Users IRC Channel**: `#mpv-player` on `irc.freenode.net`
- **Users Mailing List**: `mpv-users@googlegroups.com` ([Archive / Subscribe][mpv-users]).
- **Devel Mailing List**: `mpv-devel@googlegroups.com` ([Archive / Subscribe][mpv-devel])
To contact the `mpv` team in private write to `mpv-team@googlegroups.com`. Use
only if discretion is required.
To contact the `mpv` team in private write to `mpv-team@googlegroups.com`.
[changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/man/changes.rst
[mpv-build]: https://github.com/mpv-player/mpv-build
+56 -55
View File
@@ -1,67 +1,68 @@
Release 0.7.0
Release 0.6.0
=============
Changes
-------
- Buffer partial log messages in the client API (the client API will now only
pass full log messages to clients).
- Remove ncurses/terminfo/termcap support (it was disabled by default and
replaced by new code since v0.6.0).
- Enable cdda:// support by default again (it was disabled since v0.6.0).
- Cascade-load input.conf (if there are several input.confs in the set of valid
config paths, load them all).
- Draw the OSD twice in 3D mode (this fixes subtitles display in 3D mode).
- Make wasapi the default AO on Windows again since many of its problems have
been solved.
- Use "site-functions" subdir to install the zsh completion script instead of
the Debian-specific "vendor-completions" (also provide the --zshdir waf
configure option for changing this value).
- Improve synchronization between the Cocoa GUI and the player (this fixes some
long standing deadlock issues on Mac OS X).
- Remove --fs-missioncontrol option (only relevant to Mac OS X).
- Map ESC to exiting fullscreen.
- Run the vo inside its own thread.
- Enable the demuxer thread by default.
- Prefer the opengl vo over the vdpau one.
- Complete profile names in the zsh completion script.
- Raise dsound ao buffer size to 200ms and make it configurable.
- Bind ctrl+c to quit, so that it works both in the terminal and the player
window.
- Automatically load playlists without needing --load-unsafe-playlists or
--playlist. Unsafe URLs are still rejected (e.g. remote playlists can't load
local files).
- Remove quvi-format property (which never really worked).
- Wait until all Lua scripts are loaded on start.
- Use separate threads for input sources (terminal, lirc, joystick) instead of
using a central I/O loop.
- Complete values for options that take file names in the zsh completion script.
- Use libwaio for pipe input on Windows (if you want --input-file to work on
Windows you need libwaio as an additional dependency).
- Remove the --volstep option (users should change the argument to the "add"
function in input.conf instead).
- Always show the OSD message when changing volume, gamma, panscan, etc along
with the OSD bar.
- When changing a property always show it on the OSD (except for "pause" and
"fullscreen").
- Prefer the libavcodec decoder over libmpg123 for mp3 decoding (ffmpeg/libav
support for gapless audio works better for mpv's use-case).
- Allow passing number of loops to --loop-file.
- Change --keep-open option semantics to not terminate the player when playing
or seeking past the end of the file and there's no next file to be played.
- Disable the sndio ao by default and prefer other outputs even if it's enabled.
New features
------------
- Enable pitch correction by default when playing at higher speeds (this can be
controlled with the --audio-pitch-correction option).
- Open stream and demuxer asynchronously (this should avoid having the player
get blocked on network streams).
- Add cache-buffering-state property for querying the cache fill status until
the player unpauses.
- Add support for listing and selecting the audio device (note that it is not
implemented for all AOs, see the --audio-device option for more information).
- Add support for a JSON-based IPC mechanism (note that this is not currently
supported on Windows, see the JSON IPC section in the manpage for more
information).
- Add Lua utility function for starting processes (see utils.subprocess() in the
manpage).
- Add Lua utility function for parsing JSON (see utils.parse_json() in the
manpage).
- Add field-dominance property (see --field-dominance option).
- Add video-rotate property (see --video-rotate option).
- Add playback-abort property for querying whether playback is stopped or is to
be stopped.
- Add cursor-autohide property (see --cursor-autohide option).
- Add vo-configured property for querying whether a window is created.
- Add support for dxva2 hardware acceleration on Windows.
- Drop libquvi support (this has been replaced by a built-in Lua script that
invokes the youtube-dl tool, which needs to be installed, see the --ytdl
option).
- Add support for loading chapters from an external file (see the
--chapters-file option).
- Add window-minimized property for querying whether the window is minimized
(works for X11 only).
- Make it possible to configure the OSC seekbar style (see the "seekbarstyle"
OSC option).
- Add support for libmpv on Mac OS X (it used to be broken, now it's fully
functional and also provides support for embedding the mpv window inside a
Cocoa/Qt application).
- Try to use the audio channel map reported by ALSA in the alsa AO.
- Add option to disable text OSD rendering completely (useful for working around
certain fontconfig issues, see the --use-text-osd option).
- Add new framedrop mode that limits the frequency of new video frames displayed
based on the display framerate (this is enabled by default, the old
decoder-based mode can be enabled with --framedrop=decoder).
- Add --demuxer-readahead-secs option to control the readahead buffer using a
duration value.
- Add estimated-frame-count and estimated-frame-number properties.
- Add --cache-secs option which overrides the value of --demuxer-readahead-secs
if the stream cache is active.
- Add demuxer-cache-duration and demuxer-cache-idle properties.
- Make --cache-file create a temporary file if the string "TMP" is passed
instead of a file name.
- Add support for Matroska 3D (if a file is marked as 3D using the StereoMode
element, an automatic conversion filter is added).
- Add --osd-playing-msg option to show a message on the OSD when the playback
starts.
- Add --media-title option to force the content of the media-title property.
- Add --osd-msg1, --osd-msg2 and --osd-msg3 options to override the OSD message
for the corresponding OSD level (before only level 3 could be overridden with
--osd-status-message).
- Add support for digital passthrough in the pulse ao.
- Add support for opening DVD and Bluray directories directly.
- Add --input-x11-keyboard option to control whether keyboard input should be
enabled on the X11 window ot not (this is disabled by default when using the
libmpv library).
This listing is not complete. There are many more bug fixes and changes. The
complete change log can be viewed by running ``git log 7759c18..9479daa`` in
complete change log can be viewed by running ``git log a1be3cf..61ebe61`` in
the git repository.
+5 -1
View File
@@ -35,7 +35,11 @@ judge()
bff=0
progressive=0
undetermined=0
while read -r _ _ _ _ _ _ tff1 _ bff1 _ progressive1 _ undetermined1 _; do
while IFS= read -r out; do
tff1=${out##* TFF:}; tff1=${tff1%% *}
bff1=${out##* BFF:}; bff1=${bff1%% *}
progressive1=${out##* Progressive:}; progressive1=${progressive1%% *}
undetermined1=${out##* Undetermined:}; undetermined1=${undetermined1%% *}
case "$tff1$bff1$progressive1$undetermined1" in
*[!0-9]*)
printf >&2 'ERROR: Unrecognized idet output: %s\n' "$out"
-5
View File
@@ -232,7 +232,6 @@ sub define_matroska {
elem('Block', 'a1', 'binary'),
elem('BlockDuration', '9b', 'uint'),
elem('ReferenceBlock*', 'fb', 'sint'),
elem('DiscardPadding', '75A2', 'sint'),
}),
elem('SimpleBlock*', 'a3', 'binary'),
}),
@@ -257,8 +256,6 @@ sub define_matroska {
elem('CodecPrivate', '63a2', 'binary'),
elem('CodecName', '258688', 'str'),
elem('CodecDecodeAll', 'aa', 'uint'),
elem('CodecDelay', '56AA', 'uint'),
elem('SeekPreRoll', '56BB', 'uint'),
elem('Video', 'e0', {
elem('FlagInterlaced', '9a', 'uint'),
elem('PixelWidth', 'b0', 'uint'),
@@ -296,8 +293,6 @@ sub define_matroska {
elem('CueTrackPositions*', 'b7', {
elem('CueTrack', 'f7', 'uint'),
elem('CueClusterPosition', 'f1', 'uint'),
elem('CueRelativePosition','f0', 'uint'),
elem('CueDuration', 'b2', 'uint'),
}),
}),
}),
+9 -2
View File
@@ -8,5 +8,12 @@ adding the option
to mpv's command line.
Where appropriate, they may also be placed in ~/.config/mpv/lua from where
they will be automatically loaded when mpv starts.
Unless otherwise specified, they are also suitable for inclusion in
the `~/.mpv/lua` directory where they will be loaded every time mpv
starts, obviating the need to load them with the above `--lua=...`
argument. This is acceptable as they do only basic argument parsing
and key-binding registration, until those bound keys are actually
pressed. They should therefore not interfere with normal playback
(unless you have a conflicting user-defined key-binding, in which
case, you may want to modify either the `mp.add_key_binding()` calls
in the scripts, or your keybinding).
+18 -18
View File
@@ -42,11 +42,11 @@ function del_filter_if_present(label)
-- error if the filter doesn't exist
local vfs = mp.get_property_native("vf")
for i,vf in pairs(vfs) do
if vf["label"] == label then
table.remove(vfs, i)
mp.set_property_native("vf", vfs)
return true
end
if vf["label"] == label then
table.remove(vfs, i)
mp.set_property_native("vf", vfs)
return true
end
end
return false
end
@@ -65,10 +65,10 @@ function autocrop_start()
-- insert the cropdetect filter
ret=mp.command(
string.format(
'vf add @%s:lavfi=graph="cropdetect=limit=24:round=2:reset=0"',
cropdetect_label
)
string.format(
'vf add @%s:lavfi=graph="cropdetect=limit=24:round=2:reset=0"',
cropdetect_label
)
)
-- wait to gather data
timer=mp.add_timeout(detect_seconds, do_crop)
@@ -77,15 +77,15 @@ end
function do_crop()
-- get the metadata
local cropdetect_metadata = mp.get_property_native(
string.format("vf-metadata/%s", cropdetect_label)
string.format("vf-metadata/%s", cropdetect_label)
)
-- use it to crop if its valid
if cropdetect_metadata then
if cropdetect_metadata["lavfi.cropdetect.w"]
and cropdetect_metadata["lavfi.cropdetect.h"]
and cropdetect_metadata["lavfi.cropdetect.x"]
and cropdetect_metadata["lavfi.cropdetect.y"]
then
if cropdetect_metadata["lavfi.cropdetect.w"]
and cropdetect_metadata["lavfi.cropdetect.h"]
and cropdetect_metadata["lavfi.cropdetect.x"]
and cropdetect_metadata["lavfi.cropdetect.y"]
then
mp.command(string.format("vf add @%s:crop=%s:%s:%s:%s",
crop_label,
cropdetect_metadata["lavfi.cropdetect.w"],
@@ -94,9 +94,9 @@ function do_crop()
cropdetect_metadata["lavfi.cropdetect.y"]))
else
mp.msg.error(
"Got empty crop data. You might need to increase detect_seconds."
)
end
"Got empty crop data. You might need to increase detect_seconds."
)
end
else
mp.msg.error(
"No crop data. Was the cropdetect filter successfully inserted?"
-164
View File
@@ -1,164 +0,0 @@
-- This script uses the lavfi idet filter to automatically insert the
-- appropriate deinterlacing filter based on a short section of the
-- currently playing video.
--
-- It registers the key-binding ctrl+d, which when pressed, inserts
-- the filter vf=lavfi=idet. After 4 second, it examines the results
-- to determine whether the content progressive or interlaced and the
-- interlacing field dominance. It immediately sets the latter within
-- mpv.
--
-- If the content is judged not to be progressive, it may be either
-- interlaced or telecined. To determine which one, the video is
-- seeked back to the detection starting point with the "pullup"
-- filter inserted. After another four seconds, if idet thinks that
-- the pulled up viedo is "progressive", then pullup is the correct
-- filter and this script leaves it in places while removing the idet
-- filter. Otherwise, the content plain interlaced and mpv's deinterlace
-- property is set. This will usually insert the yadif deinterlacing filter.
--
-- The default detection time may be overridden by adding
--
-- --lua-opts=autodeint.detect_seconds=<number of seconds>
--
-- to mpv's arguments. This may be desirable to allow idet more
-- time to collect data.
--
-- To see counts of the various types of frames for each detection phase,
-- the verbosity can be increased with
--
-- --msg-level autodeint=v
--
-- This script requires a recent version of ffmpeg for which the idet
-- filter adds the required metadata.
require "mp.msg"
script_name = mp.get_script_name()
detect_label = string.format("%s-detect", script_name)
pullup_label = string.format("%s", script_name)
-- number of seconds to gather cropdetect data
detect_seconds = tonumber(mp.get_opt(string.format("%s.detect_seconds", script_name)))
if not detect_seconds then
detect_seconds = 4
end
function del_filter_if_present(label)
-- necessary because mp.command('vf del @label:filter') raises an
-- error if the filter doesn't exist
local vfs = mp.get_property_native("vf")
for i,vf in pairs(vfs) do
if vf["label"] == label then
table.remove(vfs, i)
mp.set_property_native("vf", vfs)
return true
end
end
return false
end
function start_detect()
-- exit if detection is already in progress
if timer then
mp.msg.warn("already detecting!")
return
end
mp.set_property("deinterlace","no")
del_filter_if_present(pullup_label)
percent_pos = mp.get_property("percent-pos")
-- insert the detection filter
local cmd = string.format('vf add @%s:lavfi=graph="idet"', detect_label)
if not mp.command(cmd) then
mp.msg.error("failed to insert detection filter")
return
end
-- wait to gather data
timer = mp.add_timeout(detect_seconds, judge_field_dominance)
end
function stop_detect()
del_filter_if_present(detect_label)
timer = nil
mp.set_property("playback-pos", percent_pos)
end
progressive, interlaced_tff, interlaced_bff, interlaced = 0, 1, 2, 3, 4
function judge()
-- get the metadata
local result = mp.get_property_native(string.format("vf-metadata/%s", detect_label))
num_tff = tonumber(result["lavfi.idet.multiple.tff"])
num_bff = tonumber(result["lavfi.idet.multiple.bff"])
num_progressive = tonumber(result["lavfi.idet.multiple.progressive"])
num_undetermined = tonumber(result["lavfi.idet.multiple.undetermined"])
num_interlaced = num_tff + num_bff
num_determined = num_interlaced + num_progressive
mp.msg.verbose("progressive = "..num_progressive)
mp.msg.verbose("interlaced-tff = "..num_tff)
mp.msg.verbose("interlaced-bff = "..num_bff)
mp.msg.verbose("undetermined = "..num_undetermined)
if num_determined < num_undetermined then
mp.msg.warn("majority undetermined frames")
end
if num_progressive > 20*num_interlaced then
return progressive
elseif num_tff > 10*num_bff then
return interlaced_tff
elseif num_bff > 10*num_tff then
return interlaced_bff
else
return interlaced
end
end
function judge_field_dominance()
verdict = judge()
if verdict == progressive then
stop_detect()
mp.msg.info("progressive: doing nothing")
return
elseif verdict == interlaced_tff then
mp.set_property("field-dominance", "top")
elseif verdict == interlaced_bff then
mp.set_property("field-dominance", "bottom")
elseif verdict == interlaced then
mp.set_property("field-dominance", "auto")
end
local cmd = string.format("vf pre @%s:pullup", pullup_label)
if not mp.command(cmd) then
mp.msg.error("failed to insert pullup filter")
stop_detect()
return
end
-- redo the detection with the pullup filter to see if it's telecined
mp.set_property("percent-pos", percent_pos)
timer = mp.add_timeout(detect_seconds, judge_ivtc)
end
function judge_ivtc()
verdict = judge()
if verdict == progressive then
mp.msg.info(string.format("telecinied with %s field dominance: using pullup", mp.get_property("field-dominance")))
stop_detect()
else
mp.msg.info(string.format("interlaced with %s field dominance: setting deinterlace property", mp.get_property("field-dominance")))
del_filter_if_present(pullup_label)
mp.set_property("deinterlace","yes")
stop_detect()
end
return
end
mp.add_key_binding("ctrl+d", script_name, start_detect)
+7 -11
View File
@@ -26,9 +26,6 @@ function find_and_add_entries()
end
local files = mputils.readdir(dir, "files")
table.sort(files)
if dir == "." then
dir = ""
end
local pl = mp.get_property_native("playlist", {})
local pl_current = mp.get_property_number("playlist-pos", 0) + 1
-- Find the current pl entry (dir+"/"+filename) in the sorted dir list
@@ -43,28 +40,27 @@ function find_and_add_entries()
return
end
local append = {[-1] = {}, [1] = {}}
for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
for dir = -1, 1, 2 do -- 2 iterations, with dir = -1 and +1
for i = 1, MAXENTRIES do
local file = files[current + i * direction]
local pl_e = pl[pl_current + i * direction]
local file = files[current + i * dir]
local pl_e = pl[pl_current + i * dir]
if file == nil or file[1] == "." then
break
end
local filepath = dir .. file
if pl_e then
-- If there's a playlist entry, and it's the same file, stop.
if pl_e.filename == filepath then
if pl_e.filename == file then
break
end
end
if direction == -1 then
if dir == -1 then
if pl_current == 1 then -- never add additional entries in the middle
mp.msg.info("Prepending " .. file)
table.insert(append[-1], 1, filepath)
table.insert(append[-1], 1, file)
end
else
mp.msg.info("Adding " .. file)
table.insert(append[1], filepath)
table.insert(append[1], file)
end
end
end
+3 -3
View File
@@ -20,9 +20,9 @@ pullup_label = string.format("%s-pullup", script_name)
function pullup_on()
for i,vf in pairs(mp.get_property_native('vf')) do
if vf['label'] == pullup_label then
return "yes"
end
if vf['label'] == pullup_label then
return "yes"
end
end
return "no"
end
+29 -29
View File
@@ -21,30 +21,30 @@ script_name = mp.get_script_name()
function print_state(params)
if params then
mp.osd_message(script_name..":\n"
.."method = "..params["method"].."\n"
.."target = "..params["target"])
mp.osd_message(script_name..":\n"
.."method = "..params["method"].."\n"
.."target = "..params["target"])
else
mp.osd_message(script_name..":\noff")
mp.osd_message(script_name..":\noff")
end
end
function get_index_of_drc(afs)
for i,af in pairs(afs) do
if af["label"] == script_name then
return i
end
if af["label"] == script_name then
return i
end
end
end
function append_drc(afs)
afs[#afs+1] = {
name = "drc",
label = script_name,
params = {
method = "1",
target = "0.25"
}
name = "drc",
label = script_name,
params = {
method = "1",
target = "0.25"
}
}
print_state(afs[#afs]["params"])
end
@@ -53,34 +53,34 @@ function modify_or_create_af(fun)
afs = mp.get_property_native("af")
i = get_index_of_drc(afs)
if not i then
append_drc(afs)
append_drc(afs)
else
fun(afs, i)
fun(afs, i)
end
mp.set_property_native("af", afs)
end
function drc_toggle_method_handler()
modify_or_create_af(
function (afs, i)
new_method=(afs[i]["params"]["method"]+1)%3
if new_method == 0 then
table.remove(afs, i)
print_state(nil)
else
afs[i]["params"]["method"] = tostring((afs[i]["params"]["method"])%2+1)
print_state(afs[i]["params"])
end
end
function (afs, i)
new_method=(afs[i]["params"]["method"]+1)%3
if new_method == 0 then
table.remove(afs, i)
print_state(nil)
else
afs[i]["params"]["method"] = tostring((afs[i]["params"]["method"])%2+1)
print_state(afs[i]["params"])
end
end
)
end
function drc_scale_target(factor)
modify_or_create_af(
function (afs)
afs[i]["params"]["target"] = tostring(afs[i]["params"]["target"]*factor)
print_state(afs[i]["params"])
end
function (afs)
afs[i]["params"]["target"] = tostring(afs[i]["params"]["target"]*factor)
print_state(afs[i]["params"])
end
)
end
+23 -3
View File
@@ -21,13 +21,21 @@ script after that will start a new mpv instance.
Note that you can control the mpv instance by writing to the command fifo:
echo "cycle fullscreen" > ~/.umpv-fifo
echo "cycle fullscreen" > /tmp/umpv-fifo-*
Note: you can supply custom mpv path and options with the MPV environment
variable. The environment variable will be split on whitespace, and the
first item is used as path to mpv binary and the rest is passed as options
_if_ the script starts mpv. If mpv is not started by the script (i.e. mpv
is already running), this will be ignored.
Warning:
The script attempts to make sure the FIFO is safely created (i.e. not world-
writable), and checks that it's really a FIFO. This is important for security,
because the FIFO allows anyone with write access to run arbitrary commands
in mpv's context using the "run" input command. If you are worried about
security, you should verify that the code handles these concerns correctly.
"""
import sys
@@ -57,7 +65,7 @@ def make_abs(filename):
return filename
files = [make_abs(f) for f in files]
FIFO = os.path.join(os.getenv("HOME"), ".umpv_fifo")
FIFO = "/tmp/umpv-fifo-" + os.getenv("USER")
fifo_fd = -1
try:
@@ -70,6 +78,15 @@ except OSError as e:
else:
raise e
if fifo_fd >= 0:
st = os.fstat(fifo_fd)
if (((st.st_mode & (stat.S_IRWXG | stat.S_IRWXO)) != 0) or
(not stat.S_ISFIFO(st.st_mode)) or
(st.st_uid != os.getuid())):
sys.stderr.write("error: command FIFO is not a FIFO or has bogus "
"permissions\n")
sys.exit(1)
if fifo_fd >= 0:
# Unhandled race condition: what if mpv is terminating right now?
fcntl.fcntl(fifo_fd, fcntl.F_SETFL, 0) # set blocking mode
@@ -86,7 +103,10 @@ else:
try:
os.unlink(FIFO)
except OSError as e:
pass
if e.errno == errno.ENOENT:
pass
else:
raise e
os.mkfifo(FIFO, 0o600)
opts = (os.getenv("MPV") or "mpv").split()
+1
View File
@@ -11,6 +11,7 @@ esac
: ${MPV:=mpv}
: ${ILDETECT_MPV:=$MPV}
: ${ILDETECT_MPV:=$MPV}
: ${ILDETECT_MPVFLAGS:=--start=35% --length=35}
: ${ILDETECT_DRY_RUN:=}
: ${ILDETECT_QUIET:=}
+1 -1
View File
@@ -1 +1 @@
0.7.0
0.6.0
+35 -165
View File
@@ -20,13 +20,10 @@
#include <stdlib.h>
#include <assert.h>
#include <libavutil/buffer.h>
#include <libavutil/frame.h>
#include <libavutil/version.h>
#include <libavutil/mem.h>
#include "talloc.h"
#include "common/common.h"
#include "fmt-conversion.h"
#include "audio.h"
static void update_redundant_info(struct mp_audio *mpa)
@@ -91,12 +88,18 @@ bool mp_audio_config_valid(const struct mp_audio *mpa)
&& mpa->rate >= 1 && mpa->rate < 10000000;
}
char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa)
char *mp_audio_fmt_to_str(int srate, const struct mp_chmap *chmap, int format)
{
snprintf(buf, buf_sz, "%dHz %s %dch %s", mpa->rate,
mp_chmap_to_str(&mpa->channels), mpa->channels.num,
af_fmt_to_str(mpa->format));
return buf;
char *chstr = mp_chmap_to_str(chmap);
char *res = talloc_asprintf(NULL, "%dHz %s %dch %s", srate, chstr,
chmap->num, af_fmt_to_str(format));
talloc_free(chstr);
return res;
}
char *mp_audio_config_to_str(struct mp_audio *mpa)
{
return mp_audio_fmt_to_str(mpa->rate, &mpa->channels, mpa->format);
}
void mp_audio_force_interleaved_format(struct mp_audio *mpa)
@@ -113,27 +116,19 @@ int mp_audio_psize(struct mp_audio *mpa)
void mp_audio_set_null_data(struct mp_audio *mpa)
{
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
for (int n = 0; n < MP_NUM_CHANNELS; n++)
mpa->planes[n] = NULL;
mpa->allocated[n] = NULL;
}
mpa->samples = 0;
}
static int get_plane_size(const struct mp_audio *mpa, int samples)
{
if (samples < 0 || !mp_audio_config_valid(mpa))
return -1;
if (samples >= INT_MAX / mpa->sstride)
return -1;
return MPMAX(samples * mpa->sstride, 1);
}
static void mp_audio_destructor(void *ptr)
{
struct mp_audio *mpa = ptr;
for (int n = 0; n < MP_NUM_CHANNELS; n++)
av_buffer_unref(&mpa->allocated[n]);
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
// Note: don't free if not allocated by mp_audio_realloc
if (mpa->allocated[n])
av_free(mpa->planes[n]);
}
}
/* Reallocate the data stored in mpa->planes[n] so that enough samples are
@@ -152,19 +147,27 @@ static void mp_audio_destructor(void *ptr)
*/
void mp_audio_realloc(struct mp_audio *mpa, int samples)
{
int size = get_plane_size(mpa, samples);
if (size < 0)
abort(); // oom or invalid parameters
assert(samples >= 0);
if (samples >= INT_MAX / mpa->sstride)
abort(); // oom
int size = MPMAX(samples * mpa->sstride, 1);
for (int n = 0; n < mpa->num_planes; n++) {
if (!mpa->allocated[n] || size != mpa->allocated[n]->size) {
if (av_buffer_realloc(&mpa->allocated[n], size) < 0)
abort(); // OOM
mpa->planes[n] = mpa->allocated[n]->data;
if (size != mpa->allocated[n]) {
// Note: av_realloc() can't be used (see libavutil doxygen)
void *new = av_malloc(size);
if (!new)
abort();
if (mpa->allocated[n])
memcpy(new, mpa->planes[n], MPMIN(mpa->allocated[n], size));
av_free(mpa->planes[n]);
mpa->planes[n] = new;
mpa->allocated[n] = size;
}
}
for (int n = mpa->num_planes; n < MP_NUM_CHANNELS; n++) {
av_buffer_unref(&mpa->allocated[n]);
av_free(mpa->planes[n]);
mpa->planes[n] = NULL;
mpa->allocated[n] = 0;
}
talloc_set_destructor(mpa, mp_audio_destructor);
}
@@ -191,7 +194,7 @@ int mp_audio_get_allocated_size(struct mp_audio *mpa)
{
int size = 0;
for (int n = 0; n < mpa->num_planes; n++) {
int s = mpa->allocated[n] ? mpa->allocated[n]->size / mpa->sstride : 0;
int s = mpa->allocated[n] / mpa->sstride;
size = n == 0 ? s : MPMIN(size, s);
}
return size;
@@ -237,136 +240,3 @@ void mp_audio_skip_samples(struct mp_audio *data, int samples)
data->samples -= samples;
}
// Make sure the frame owns the audio data, and if not, copy the data.
// Return negative value on failure (which means it can't be made writeable).
// Non-refcounted frames are always considered writeable.
int mp_audio_make_writeable(struct mp_audio *data)
{
bool ok = true;
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
if (data->allocated[n])
ok &= av_buffer_is_writable(data->allocated[n]);
}
if (!ok) {
struct mp_audio *new = talloc(NULL, struct mp_audio);
*new = *data;
mp_audio_set_null_data(new); // use format only
mp_audio_realloc(new, data->samples);
new->samples = data->samples;
mp_audio_copy(new, 0, data, 0, data->samples);
// "Transfer" the reference.
mp_audio_destructor(data);
*data = *new;
talloc_set_destructor(new, NULL);
talloc_free(new);
}
return 0;
}
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe)
{
AVFrame *tmp = NULL;
struct mp_audio *new = talloc_zero(NULL, struct mp_audio);
talloc_set_destructor(new, mp_audio_destructor);
mp_audio_set_format(new, af_from_avformat(avframe->format));
struct mp_chmap lavc_chmap;
mp_chmap_from_lavc(&lavc_chmap, avframe->channel_layout);
#if LIBAVUTIL_VERSION_MICRO >= 100
// FFmpeg being special again
if (lavc_chmap.num != avframe->channels)
mp_chmap_from_channels(&lavc_chmap, avframe->channels);
#endif
new->rate = avframe->sample_rate;
mp_audio_set_channels(new, &lavc_chmap);
// Force refcounted frame.
if (!avframe->buf[0]) {
tmp = av_frame_alloc();
if (!tmp)
goto fail;
if (av_frame_ref(tmp, avframe) < 0)
goto fail;
avframe = tmp;
}
// If we can't handle the format (e.g. too many channels), bail out.
if (!mp_audio_config_valid(new) || avframe->nb_extended_buf)
goto fail;
for (int n = 0; n < AV_NUM_DATA_POINTERS; n++) {
if (!avframe->buf[n])
break;
if (n >= MP_NUM_CHANNELS)
goto fail;
new->allocated[n] = av_buffer_ref(avframe->buf[n]);
if (!new->allocated[n])
goto fail;
}
for (int n = 0; n < new->num_planes; n++)
new->planes[n] = avframe->data[n];
new->samples = avframe->nb_samples;
return new;
fail:
talloc_free(new);
av_frame_free(&tmp);
return NULL;
}
struct mp_audio_pool {
AVBufferPool *avpool;
int element_size;
};
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent)
{
return talloc_zero(ta_parent, struct mp_audio_pool);
}
static void mp_audio_pool_destructor(void *p)
{
struct mp_audio_pool *pool = p;
av_buffer_pool_uninit(&pool->avpool);
}
// Allocate data using the given format and number of samples.
// Returns NULL on error.
struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
const struct mp_audio *fmt, int samples)
{
int size = get_plane_size(fmt, samples);
if (size < 0)
return NULL;
if (!pool->avpool || size > pool->element_size) {
size_t alloc = ta_calc_prealloc_elems(size);
if (alloc >= INT_MAX)
return NULL;
av_buffer_pool_uninit(&pool->avpool);
pool->avpool = av_buffer_pool_init(alloc, NULL);
if (!pool->avpool)
return NULL;
talloc_set_destructor(pool, mp_audio_pool_destructor);
}
struct mp_audio *new = talloc_ptrtype(NULL, new);
talloc_set_destructor(new, mp_audio_destructor);
*new = *fmt;
mp_audio_set_null_data(new);
new->samples = samples;
for (int n = 0; n < new->num_planes; n++) {
new->allocated[n] = av_buffer_pool_get(pool->avpool);
if (!new->allocated[n]) {
talloc_free(new);
return NULL;
}
new->planes[n] = new->allocated[n]->data;
}
return new;
}
+3 -12
View File
@@ -38,7 +38,7 @@ struct mp_audio {
int bps; // size of sub-samples (af_fmt2bps(format))
// private
struct AVBufferRef *allocated[MP_NUM_CHANNELS];
int allocated[MP_NUM_CHANNELS]; // use mp_audio_get_allocated_size()
};
void mp_audio_set_format(struct mp_audio *mpa, int format);
@@ -49,8 +49,8 @@ void mp_audio_copy_config(struct mp_audio *dst, const struct mp_audio *src);
bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b);
bool mp_audio_config_valid(const struct mp_audio *mpa);
char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa);
#define mp_audio_config_to_str(m) mp_audio_config_to_str_buf((char[64]){0}, 64, (m))
char *mp_audio_fmt_to_str(int srate, const struct mp_chmap *chmap, int format);
char *mp_audio_config_to_str(struct mp_audio *mpa);
void mp_audio_force_interleaved_format(struct mp_audio *mpa);
@@ -68,13 +68,4 @@ void mp_audio_copy(struct mp_audio *dst, int dst_offset,
struct mp_audio *src, int src_offset, int length);
void mp_audio_skip_samples(struct mp_audio *data, int samples);
int mp_audio_make_writeable(struct mp_audio *data);
struct AVFrame;
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe);
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent);
struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
const struct mp_audio *fmt, int samples);
#endif
+12 -15
View File
@@ -18,7 +18,6 @@
#include <stdlib.h>
#include <assert.h>
#include "common/common.h"
#include "common/msg.h"
#include "chmap.h"
@@ -87,7 +86,6 @@ static const char *const std_layout_names[][2] = {
{"7.1(wide-side)", "fl-fr-fc-lfe-flc-frc-sl-sr"},
{"octagonal", "fl-fr-fc-bl-br-bc-sl-sr"},
{"downmix", "dl-dr"},
{"auto", ""}, // not in lavc
{0}
};
@@ -377,35 +375,34 @@ void mp_chmap_get_reorder(int dst[MP_NUM_CHANNELS], const struct mp_chmap *from,
// Returns something like "fl-fr-fc". If there's a standard layout in lavc
// order, return that, e.g. "3.0" instead of "fl-fr-fc".
// Unassigned but valid speakers get names like "sp28".
char *mp_chmap_to_str_buf(char *buf, size_t buf_size, const struct mp_chmap *src)
char *mp_chmap_to_str(const struct mp_chmap *src)
{
buf[0] = '\0';
char *res = talloc_strdup(NULL, "");
if (mp_chmap_is_unknown(src)) {
snprintf(buf, buf_size, "unknown%d", src->num);
return buf;
}
if (mp_chmap_is_unknown(src))
return talloc_asprintf_append_buffer(res, "unknown%d", src->num);
for (int n = 0; n < src->num; n++) {
int sp = src->speaker[n];
const char *s = sp < MP_SPEAKER_ID_COUNT ? speaker_names[sp][0] : NULL;
char sp_buf[10];
char buf[10];
if (!s) {
snprintf(sp_buf, sizeof(sp_buf), "sp%d", sp);
s = sp_buf;
snprintf(buf, sizeof(buf), "sp%d", sp);
s = buf;
}
mp_snprintf_cat(buf, buf_size, "%s%s", n > 0 ? "-" : "", s);
res = talloc_asprintf_append_buffer(res, "%s%s", n > 0 ? "-" : "", s);
}
// To standard layout name
for (int n = 0; std_layout_names[n][0]; n++) {
if (strcmp(buf, std_layout_names[n][1]) == 0) {
snprintf(buf, buf_size, "%s", std_layout_names[n][0]);
if (res && strcmp(res, std_layout_names[n][1]) == 0) {
talloc_free(res);
res = talloc_strdup(NULL, std_layout_names[n][0]);
break;
}
}
return buf;
return res;
}
// If src can be parsed as channel map (as produced by mp_chmap_to_str()),
+2 -5
View File
@@ -61,8 +61,7 @@ enum mp_speaker_id {
MP_SPEAKER_ID_UNKNOWN0 = 64,
MP_SPEAKER_ID_UNKNOWN_LAST = MP_SPEAKER_ID_UNKNOWN0 + MP_NUM_CHANNELS - 1,
// Including the unassigned IDs in between. This is not a valid ID anymore,
// but is still within uint8_t.
// Including the unassigned IDs in between. This is not a valid ID anymore.
MP_SPEAKER_ID_COUNT,
};
@@ -120,9 +119,7 @@ void mp_chmap_reorder_to_lavc(struct mp_chmap *map);
void mp_chmap_get_reorder(int dst[MP_NUM_CHANNELS], const struct mp_chmap *from,
const struct mp_chmap *to);
char *mp_chmap_to_str_buf(char *buf, size_t buf_size, const struct mp_chmap *src);
#define mp_chmap_to_str(m) mp_chmap_to_str_buf((char[64]){0}, 64, (m))
char *mp_chmap_to_str(const struct mp_chmap *src);
bool mp_chmap_from_str(struct mp_chmap *dst, bstr src);
struct mp_log;
+1 -1
View File
@@ -36,7 +36,7 @@ struct ad_functions {
int (*init)(struct dec_audio *da, const char *decoder);
void (*uninit)(struct dec_audio *da);
int (*control)(struct dec_audio *da, int cmd, void *arg);
int (*decode_packet)(struct dec_audio *da, struct mp_audio **out);
int (*decode_packet)(struct dec_audio *da);
};
enum ad_ctrl {
+38 -46
View File
@@ -25,7 +25,6 @@
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/common.h>
#include <libavutil/intreadwrite.h>
#include "talloc.h"
@@ -44,7 +43,6 @@ struct priv {
struct mp_audio frame;
bool force_channel_map;
struct demux_packet *packet;
uint32_t skip_samples;
};
static void uninit(struct dec_audio *da);
@@ -73,6 +71,37 @@ const struct m_sub_options ad_lavc_conf = {
},
};
static void set_data_from_avframe(struct dec_audio *da)
{
struct priv *priv = da->priv;
AVCodecContext *lavc_context = priv->avctx;
// Note: invalid parameters are rejected by dec_audio.c
int fmt = lavc_context->sample_fmt;
mp_audio_set_format(&da->decoded, af_from_avformat(fmt));
if (!da->decoded.format)
MP_FATAL(da, "unsupported lavc format %s", av_get_sample_fmt_name(fmt));
da->decoded.rate = lavc_context->sample_rate;
struct mp_chmap lavc_chmap;
mp_chmap_from_lavc(&lavc_chmap, lavc_context->channel_layout);
// No channel layout or layout disagrees with channel count
if (lavc_chmap.num != lavc_context->channels)
mp_chmap_from_channels(&lavc_chmap, lavc_context->channels);
if (priv->force_channel_map) {
struct sh_audio *sh_audio = da->header->audio;
if (lavc_chmap.num == sh_audio->channels.num)
lavc_chmap = sh_audio->channels;
}
mp_audio_set_channels(&da->decoded, &lavc_chmap);
da->decoded.samples = priv->avframe->nb_samples;
for (int n = 0; n < da->decoded.num_planes; n++)
da->decoded.planes[n] = priv->avframe->data[n];
}
static int init(struct dec_audio *da, const char *decoder)
{
struct MPOpts *mpopts = da->opts;
@@ -110,11 +139,6 @@ static int init(struct dec_audio *da, const char *decoder)
av_opt_set_double(lavc_context, "drc_scale", opts->ac3drc,
AV_OPT_SEARCH_CHILDREN);
#if HAVE_AVFRAME_SKIP_SAMPLES
// Let decoder add AV_FRAME_DATA_SKIP_SAMPLES.
av_opt_set(lavc_context, "flags2", "+skip_manual", AV_OPT_SEARCH_CHILDREN);
#endif
mp_set_avopts(da->log, lavc_context, opts->avopts);
lavc_context->codec_tag = sh->format;
@@ -173,19 +197,21 @@ static int control(struct dec_audio *da, int cmd, void *arg)
switch (cmd) {
case ADCTRL_RESET:
avcodec_flush_buffers(ctx->avctx);
mp_audio_set_null_data(&da->decoded);
talloc_free(ctx->packet);
ctx->packet = NULL;
ctx->skip_samples = 0;
return CONTROL_TRUE;
}
return CONTROL_UNKNOWN;
}
static int decode_packet(struct dec_audio *da, struct mp_audio **out)
static int decode_packet(struct dec_audio *da)
{
struct priv *priv = da->priv;
AVCodecContext *avctx = priv->avctx;
mp_audio_set_null_data(&da->decoded);
struct demux_packet *mpkt = priv->packet;
if (!mpkt) {
if (demux_read_packet_async(da->header, &mpkt) == 0)
@@ -232,49 +258,15 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
if (!got_frame)
return mpkt ? AD_OK : AD_EOF;
set_data_from_avframe(da);
double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, NULL);
if (out_pts != MP_NOPTS_VALUE) {
da->pts = out_pts;
da->pts_offset = 0;
}
struct mp_audio *mpframe = mp_audio_from_avframe(priv->avframe);
if (!mpframe)
return AD_ERR;
struct mp_chmap lavc_chmap = mpframe->channels;
if (lavc_chmap.num != avctx->channels)
mp_chmap_from_channels(&lavc_chmap, avctx->channels);
if (priv->force_channel_map) {
struct sh_audio *sh_audio = da->header->audio;
if (lavc_chmap.num == sh_audio->channels.num)
lavc_chmap = sh_audio->channels;
}
mp_audio_set_channels(mpframe, &lavc_chmap);
#if HAVE_AVFRAME_SKIP_SAMPLES
AVFrameSideData *sd =
av_frame_get_side_data(priv->avframe, AV_FRAME_DATA_SKIP_SAMPLES);
if (sd && sd->size >= 10) {
char *d = sd->data;
priv->skip_samples += AV_RL32(d + 0);
uint32_t pad = AV_RL32(d + 4);
uint32_t skip = MPMIN(priv->skip_samples, mpframe->samples);
if (skip) {
mp_audio_skip_samples(mpframe, skip);
da->pts_offset += skip;
priv->skip_samples -= skip;
}
if (pad <= mpframe->samples)
mpframe->samples -= pad;
}
#endif
*out = mpframe;
av_frame_unref(priv->avframe);
MP_DBG(da, "Decoded %d -> %d samples\n", in_len, mpframe->samples);
MP_DBG(da, "Decoded %d -> %d samples\n", in_len, da->decoded.samples);
return 0;
}
+9 -10
View File
@@ -43,7 +43,6 @@ struct ad_mpg123_context {
short delay;
/* If the stream is actually VBR. */
char vbr;
struct mp_audio frame;
};
static void uninit(struct dec_audio *da)
@@ -198,25 +197,27 @@ static int set_format(struct dec_audio *da)
int encoding;
ret = mpg123_getformat(con->handle, &rate, &channels, &encoding);
if (ret == MPG123_OK) {
mp_audio_set_num_channels(&con->frame, channels);
con->frame.rate = rate;
mp_audio_set_num_channels(&da->decoded, channels);
da->decoded.rate = rate;
int af = mpg123_format_to_af(encoding);
if (!af) {
/* This means we got a funny custom build of libmpg123 that only supports an unknown format. */
MP_ERR(da, "Bad encoding from mpg123: %i.\n", encoding);
return MPG123_ERR;
}
mp_audio_set_format(&con->frame, af);
mp_audio_set_format(&da->decoded, af);
con->sample_size = channels * af_fmt2bps(af);
}
return ret;
}
static int decode_packet(struct dec_audio *da, struct mp_audio **out)
static int decode_packet(struct dec_audio *da)
{
struct ad_mpg123_context *con = da->priv;
int ret;
mp_audio_set_null_data(&da->decoded);
struct demux_packet *pkt;
if (demux_read_packet_async(da->header, &pkt) == 0)
return AD_WAIT;
@@ -256,11 +257,8 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
}
int got_samples = bytes / con->sample_size;
*out = mp_audio_pool_get(da->pool, &con->frame, got_samples);
if (!*out)
return AD_ERR;
memcpy((*out)->planes[0], audio, bytes);
da->decoded.planes[0] = audio;
da->decoded.samples = got_samples;
update_info(da);
return 0;
@@ -276,6 +274,7 @@ static int control(struct dec_audio *da, int cmd, void *arg)
switch (cmd) {
case ADCTRL_RESET:
mp_audio_set_null_data(&da->decoded);
mpg123_close(con->handle);
if (mpg123_open_feed(con->handle) != MPG123_OK) {
+8 -11
View File
@@ -40,7 +40,6 @@ struct spdifContext {
int out_buffer_len;
uint8_t out_buffer[OUTBUF_SIZE];
bool need_close;
struct mp_audio fmt;
};
static int write_packet(void *p, uint8_t *buf, int buf_size)
@@ -163,9 +162,9 @@ static int init(struct dec_audio *da, const char *decoder)
default:
abort();
}
mp_audio_set_num_channels(&spdif_ctx->fmt, num_channels);
mp_audio_set_format(&spdif_ctx->fmt, sample_format);
spdif_ctx->fmt.rate = samplerate;
mp_audio_set_num_channels(&da->decoded, num_channels);
mp_audio_set_format(&da->decoded, sample_format);
da->decoded.rate = samplerate;
if (avformat_write_header(lavf_ctx, &format_opts) < 0) {
MP_FATAL(da, "libavformat spdif initialization failed.\n");
@@ -183,11 +182,13 @@ fail:
return 0;
}
static int decode_packet(struct dec_audio *da, struct mp_audio **out)
static int decode_packet(struct dec_audio *da)
{
struct spdifContext *spdif_ctx = da->priv;
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
mp_audio_set_null_data(&da->decoded);
spdif_ctx->out_buffer_len = 0;
struct demux_packet *mpkt;
@@ -211,12 +212,8 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
if (ret < 0)
return AD_ERR;
int samples = spdif_ctx->out_buffer_len / spdif_ctx->fmt.sstride;
*out = mp_audio_pool_get(da->pool, &spdif_ctx->fmt, samples);
if (!*out)
return AD_ERR;
memcpy((*out)->planes[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len);
da->decoded.planes[0] = spdif_ctx->out_buffer;
da->decoded.samples = spdif_ctx->out_buffer_len / da->decoded.sstride;
return 0;
}
+166 -46
View File
@@ -56,6 +56,19 @@ static const struct ad_functions * const ad_drivers[] = {
NULL
};
// Drop audio buffer and reinit it (after format change)
// Returns whether the format was valid at all.
static bool reinit_audio_buffer(struct dec_audio *da)
{
if (!mp_audio_config_valid(&da->decoded)) {
MP_ERR(da, "Audio decoder did not specify audio "
"format, or requested an unsupported configuration!\n");
return false;
}
mp_audio_buffer_reinit(da->decode_buffer, &da->decoded);
return true;
}
static void uninit_decoder(struct dec_audio *d_audio)
{
if (d_audio->ad_driver) {
@@ -76,6 +89,7 @@ static int init_audio_codec(struct dec_audio *d_audio, const char *decoder)
return 0;
}
d_audio->decode_buffer = mp_audio_buffer_create(NULL);
return 1;
}
@@ -150,26 +164,136 @@ void audio_uninit(struct dec_audio *d_audio)
{
if (!d_audio)
return;
MP_VERBOSE(d_audio, "Uninit audio filters...\n");
af_destroy(d_audio->afilter);
if (d_audio->afilter) {
MP_VERBOSE(d_audio, "Uninit audio filters...\n");
af_destroy(d_audio->afilter);
d_audio->afilter = NULL;
}
uninit_decoder(d_audio);
talloc_free(d_audio->decode_buffer);
talloc_free(d_audio);
}
int audio_init_filters(struct dec_audio *d_audio, int in_samplerate,
int *out_samplerate, struct mp_chmap *out_channels,
int *out_format)
{
if (!d_audio->afilter)
d_audio->afilter = af_new(d_audio->global);
struct af_stream *afs = d_audio->afilter;
// input format: same as codec's output format:
mp_audio_buffer_get_format(d_audio->decode_buffer, &afs->input);
// Sample rate can be different when adjusting playback speed
afs->input.rate = in_samplerate;
// output format: same as ao driver's input format (if missing, fallback to input)
afs->output.rate = *out_samplerate;
mp_audio_set_channels(&afs->output, out_channels);
mp_audio_set_format(&afs->output, *out_format);
afs->replaygain_data = d_audio->replaygain_data;
char *s_from = mp_audio_config_to_str(&afs->input);
char *s_to = mp_audio_config_to_str(&afs->output);
MP_VERBOSE(d_audio, "Building audio filter chain for %s -> %s...\n", s_from, s_to);
talloc_free(s_from);
talloc_free(s_to);
// let's autoprobe it!
if (af_init(afs) != 0) {
af_destroy(afs);
d_audio->afilter = NULL;
return 0; // failed :(
}
*out_samplerate = afs->output.rate;
*out_channels = afs->output.channels;
*out_format = afs->output.format;
return 1;
}
/* Decode packets until we know the audio format. Then reinit the buffer.
* Returns AD_OK on success, negative AD_* code otherwise.
* Also returns AD_OK if already initialized (and does nothing).
*/
int initial_audio_decode(struct dec_audio *da)
{
while (!da->waiting) {
int ret = da->ad_driver->decode_packet(da, &da->waiting);
while (!mp_audio_config_valid(&da->decoded)) {
if (da->decoded.samples > 0)
return AD_ERR; // invalid format, rather than uninitialized
int ret = da->ad_driver->decode_packet(da);
if (ret < 0)
return ret;
}
talloc_steal(da, da->waiting);
da->decode_format = *da->waiting;
return mp_audio_config_valid(da->waiting) ? AD_OK : AD_ERR;
if (mp_audio_buffer_samples(da->decode_buffer) > 0) // avoid accidental flush
return AD_OK;
return reinit_audio_buffer(da) ? AD_OK : AD_ERR;
}
// Filter len bytes of input, put result into outbuf.
static int filter_n_bytes(struct dec_audio *da, struct mp_audio_buffer *outbuf,
int len)
{
bool format_change = false;
int error = 0;
assert(len > 0); // would break EOF logic below
while (mp_audio_buffer_samples(da->decode_buffer) < len) {
// Check for a format change
struct mp_audio config;
mp_audio_buffer_get_format(da->decode_buffer, &config);
format_change = !mp_audio_config_equals(&da->decoded, &config);
if (format_change) {
error = AD_EOF; // drain remaining data left in the current buffer
break;
}
if (da->decoded.samples > 0) {
int copy = MPMIN(da->decoded.samples, len);
struct mp_audio append = da->decoded;
append.samples = copy;
mp_audio_buffer_append(da->decode_buffer, &append);
mp_audio_skip_samples(&da->decoded, copy);
da->pts_offset += copy;
continue;
}
error = da->ad_driver->decode_packet(da);
if (error < 0)
break;
}
if (error == AD_WAIT)
return error;
// Filter
struct mp_audio filter_data;
mp_audio_buffer_peek(da->decode_buffer, &filter_data);
filter_data.rate = da->afilter->input.rate; // due to playback speed change
len = MPMIN(filter_data.samples, len);
filter_data.samples = len;
bool eof = error == AD_EOF && filter_data.samples == 0;
if (af_filter(da->afilter, &filter_data, eof ? AF_FILTER_FLAG_EOF : 0) < 0)
return AD_ERR;
mp_audio_buffer_append(outbuf, &filter_data);
if (error == AD_EOF && filter_data.samples > 0)
error = 0; // don't end playback yet
// remove processed data from decoder buffer:
mp_audio_buffer_skip(da->decode_buffer, len);
// if format was changed, and all data was drained, execute the format change
if (format_change && eof) {
error = AD_NEW_FMT;
if (!reinit_audio_buffer(da))
error = AD_ERR; // switch to invalid format
}
return error;
}
/* Try to get at least minsamples decoded+filtered samples in outbuf
@@ -177,55 +301,52 @@ int initial_audio_decode(struct dec_audio *da)
* Return 0 on success, or negative AD_* error code.
* In the former case outbuf has at least minsamples buffered on return.
* In case of EOF/error it might or might not be. */
int audio_decode(struct dec_audio *da, struct mp_audio_buffer *outbuf,
int audio_decode(struct dec_audio *d_audio, struct mp_audio_buffer *outbuf,
int minsamples)
{
struct af_stream *afs = da->afilter;
if (afs->initialized < 1)
if (!d_audio->afilter)
return AD_ERR;
MP_STATS(da, "start audio");
// Indicates that a filter seems to be buffering large amounts of data
int huge_filter_buffer = 0;
/* Filter output size will be about filter_multiplier times input size.
* If some filter buffers audio in big blocks this might only hold
* as average over time. */
double filter_multiplier = af_calc_filter_multiplier(d_audio->afilter);
int prev_buffered = -1;
int res = 0;
MP_STATS(d_audio, "start audio");
while (res >= 0 && minsamples >= 0) {
int buffered = mp_audio_buffer_samples(outbuf);
if (minsamples < buffered)
if (minsamples < buffered || buffered == prev_buffered)
break;
prev_buffered = buffered;
res = 0;
int decsamples = (minsamples - buffered) / filter_multiplier;
// + some extra for possible filter buffering, and avoid 0
decsamples += 512;
struct mp_audio *mpa = da->waiting;
if (!mpa)
res = da->ad_driver->decode_packet(da, &mpa);
if (res != AD_EOF) {
if (res < 0)
break;
if (!mpa )
continue;
if (huge_filter_buffer) {
/* Some filter must be doing significant buffering if the estimated
* input length didn't produce enough output from filters.
* Feed the filters 250 samples at a time until we have enough
* output. Very small amounts could make filtering inefficient while
* large amounts can make mpv demux the file unnecessarily far ahead
* to get audio data and buffer video frames in memory while doing
* so. However the performance impact of either is probably not too
* significant as long as the value is not completely insane. */
decsamples = 250;
}
if (mpa) {
da->pts_offset += mpa->samples;
da->decode_format = *mpa;
mp_audio_set_null_data(&da->decode_format);
// On format change, make sure to drain the filter chain.
if (!mp_audio_config_equals(&afs->input, mpa)) {
res = AD_NEW_FMT;
da->waiting = talloc_steal(da, mpa);
mpa = NULL;
}
}
/* if this iteration does not fill buffer, we must have lots
* of buffering in filters */
huge_filter_buffer = 1;
if (mpa)
da->waiting = NULL;
if (af_filter(afs, mpa, outbuf) < 0)
return AD_ERR;
res = filter_n_bytes(d_audio, outbuf, decsamples);
}
MP_STATS(da, "end audio");
MP_STATS(d_audio, "end audio");
return res;
}
@@ -233,11 +354,10 @@ void audio_reset_decoding(struct dec_audio *d_audio)
{
if (d_audio->ad_driver)
d_audio->ad_driver->control(d_audio, ADCTRL_RESET, NULL);
af_control_all(d_audio->afilter, AF_CONTROL_RESET, NULL);
if (d_audio->afilter)
af_control_all(d_audio->afilter, AF_CONTROL_RESET, NULL);
d_audio->pts = MP_NOPTS_VALUE;
d_audio->pts_offset = 0;
if (d_audio->waiting) {
talloc_free(d_audio->waiting);
d_audio->waiting = NULL;
}
if (d_audio->decode_buffer)
mp_audio_buffer_clear(d_audio->decode_buffer);
}
+7 -3
View File
@@ -33,13 +33,13 @@ struct dec_audio {
struct mpv_global *global;
const struct ad_functions *ad_driver;
struct sh_stream *header;
struct mp_audio_buffer *decode_buffer;
struct af_stream *afilter;
char *decoder_desc;
struct replaygain_data *replaygain_data;
int init_retries;
struct mp_audio_pool *pool;
struct mp_audio decode_format;
struct mp_audio *waiting; // used on format-change
// set by decoder
struct mp_audio decoded; // decoded audio set by last decode_packet() call
int bitrate; // input bitrate, can change with VBR sources
// last known pts value in output from decoder
double pts;
@@ -65,4 +65,8 @@ int initial_audio_decode(struct dec_audio *d_audio);
void audio_reset_decoding(struct dec_audio *d_audio);
void audio_uninit(struct dec_audio *d_audio);
int audio_init_filters(struct dec_audio *d_audio, int in_samplerate,
int *out_samplerate, struct mp_chmap *out_channels,
int *out_format);
#endif /* MPLAYER_DEC_AUDIO_H */
+41 -94
View File
@@ -28,7 +28,6 @@
#include "options/m_option.h"
#include "options/m_config.h"
#include "audio/audio_buffer.h"
#include "af.h"
// Static list of filters
@@ -54,7 +53,6 @@ extern const struct af_info af_info_center;
extern const struct af_info af_info_sinesuppress;
extern const struct af_info af_info_karaoke;
extern const struct af_info af_info_scaletempo;
extern const struct af_info af_info_forcespeed;
extern const struct af_info af_info_bs2b;
extern const struct af_info af_info_lavfi;
extern const struct af_info af_info_convert24;
@@ -85,7 +83,6 @@ static const struct af_info *const filter_list[] = {
&af_info_center,
&af_info_sinesuppress,
&af_info_karaoke,
&af_info_forcespeed,
&af_info_scaletempo,
#if HAVE_LIBBS2B
&af_info_bs2b,
@@ -124,6 +121,13 @@ const struct m_obj_list af_obj_list = {
},
};
static bool af_config_equals(struct mp_audio *a, struct mp_audio *b)
{
return a->format == b->format
&& mp_chmap_equals(&a->channels, &b->channels)
&& a->rate == b->rate;
}
static void af_copy_unset_fields(struct mp_audio *dst, struct mp_audio *src)
{
if (dst->format == AF_FORMAT_UNKNOWN)
@@ -158,7 +162,7 @@ static int output_control(struct af_instance* af, int cmd, void* arg)
*filter_output = *output;
af_copy_unset_fields(filter_output, in);
*in = *filter_output;
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
return af_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
}
}
return AF_UNKNOWN;
@@ -308,8 +312,11 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
while (af) {
char b[128] = {0};
mp_snprintf_cat(b, sizeof(b), " [%s] ", af->info->name);
if (af->data)
mp_snprintf_cat(b, sizeof(b), "%s", mp_audio_config_to_str(af->data));
if (af->data) {
char *info = mp_audio_config_to_str(af->data);
mp_snprintf_cat(b, sizeof(b), "%s", info);
talloc_free(info);
}
if (af == at)
mp_snprintf_cat(b, sizeof(b), " <-");
MP_MSG(s, msg_level, "%s\n", b);
@@ -317,7 +324,9 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
af = af->next;
}
MP_MSG(s, msg_level, " [ao] %s\n", mp_audio_config_to_str(&s->output));
char *info = mp_audio_config_to_str(&s->output);
MP_MSG(s, msg_level, " [ao] %s\n", info);
talloc_free(info);
}
static int af_count_filters(struct af_stream *s)
@@ -514,16 +523,11 @@ static int af_reinit(struct af_stream *s)
// Reset just in case...
mp_audio_set_null_data(&in);
if (!mp_audio_config_valid(&in))
goto error;
int rv = af->control(af, AF_CONTROL_REINIT, &in);
if (rv == AF_OK && !mp_audio_config_equals(&in, af->prev->data))
rv = AF_FALSE; // conversion filter needed
switch (rv) {
case AF_OK:
if (!mp_audio_config_valid(af->data))
goto error;
af = af->next;
break;
case AF_FALSE: { // Configuration filter is needed
@@ -554,27 +558,22 @@ static int af_reinit(struct af_stream *s)
MP_ERR(s, "Reinitialization did not work, "
"audio filter '%s' returned error code %i\n",
af->info->name, rv);
goto error;
af_print_filter_chain(s, af, MSGL_ERR);
return AF_ERROR;
}
}
af_print_filter_chain(s, NULL, MSGL_V);
/* Set previously unset fields in s->output to those of the filter chain
* output. This is used to make the output format fixed, and even if you
* insert new filters or change the input format, the output format won't
* change. (Audio outputs generally can't change format at runtime.) */
af_copy_unset_fields(&s->output, &s->filter_output);
if (mp_audio_config_equals(&s->output, &s->filter_output)) {
s->initialized = 1;
af_print_filter_chain(s, NULL, MSGL_V);
return AF_OK;
}
goto error;
return af_config_equals(&s->output, &s->filter_output) ? AF_OK : AF_ERROR;
negotiate_error:
MP_ERR(s, "Unable to convert audio input format to output format.\n");
error:
s->initialized = -1;
af_print_filter_chain(s, af, MSGL_ERR);
return AF_ERROR;
}
@@ -584,7 +583,6 @@ void af_uninit(struct af_stream *s)
{
while (s->first->next && s->first->next != s->last)
af_remove(s, s->first->next);
s->initialized = 0;
}
struct af_stream *af_new(struct mpv_global *global)
@@ -634,6 +632,10 @@ void af_destroy(struct af_stream *s)
The return value is 0 if success and -1 if failure */
int af_init(struct af_stream *s)
{
// Sanity check
if (!s)
return -1;
// Precaution in case caller is misbehaving
mp_audio_set_null_data(&s->input);
mp_audio_set_null_data(&s->output);
@@ -643,20 +645,17 @@ int af_init(struct af_stream *s)
// Add all filters in the list (if there are any)
struct m_obj_settings *list = s->opts->af_settings;
for (int i = 0; list && list[i].name; i++) {
struct af_instance *af =
af_prepend(s, s->last, list[i].name, list[i].attribs);
if (!af) {
if (!af_prepend(s, s->last, list[i].name, list[i].attribs)) {
af_uninit(s);
s->initialized = -1;
return -1;
}
af->label = talloc_strdup(af, list[i].label);
}
}
if (af_reinit(s) != AF_OK) {
// Something is stuffed audio out will not work
MP_ERR(s, "Could not create audio filter chain.\n");
af_uninit(s);
return -1;
}
return 0;
@@ -669,6 +668,9 @@ int af_init(struct af_stream *s)
struct af_instance *af_add(struct af_stream *s, char *name, char **args)
{
struct af_instance *new;
// Sanity check
if (!s || !s->first || !name)
return NULL;
// Insert the filter somewhere nice
if (af_is_conversion_filter(s->first->next))
new = af_append(s, s->first->next, name, args);
@@ -679,85 +681,30 @@ struct af_instance *af_add(struct af_stream *s, char *name, char **args)
// Reinitalize the filter list
if (af_reinit(s) != AF_OK) {
af_remove(s, new);
if (af_reinit(s) != AF_OK) {
af_uninit(s);
af_init(s);
}
af_uninit(s);
af_init(s);
return NULL;
}
return new;
}
struct af_instance *af_find_by_label(struct af_stream *s, char *label)
{
for (struct af_instance *af = s->first; af; af = af->next) {
if (af->label && strcmp(af->label, label) == 0)
return af;
}
return NULL;
}
/* Remove the first filter that matches this name. Return number of filters
* removed (0, 1), or a negative error code if reinit after removing failed.
/* Filter data chunk through the filters in the list.
* On success, *data is set to the filtered data/format.
* Warning: input audio data will be overwritten.
*/
int af_remove_by_label(struct af_stream *s, char *label)
{
struct af_instance *af = af_find_by_label(s, label);
if (!af)
return 0;
af_remove(s, af);
if (af_reinit(s) != AF_OK) {
af_uninit(s);
af_init(s);
return -1;
}
return 1;
}
/* Feed "data" to the chain, and write results to output. "data" needs to be
* a refcounted frame, although refcounting is not used yet.
* data==NULL means EOF.
*/
int af_filter(struct af_stream *s, struct mp_audio *data,
struct mp_audio_buffer *output)
int af_filter(struct af_stream *s, struct mp_audio *data, int flags)
{
struct af_instance *af = s->first;
assert(s->initialized > 0);
int flags = 0;
int r = 0;
struct mp_audio tmp;
char dummy[MP_NUM_CHANNELS];
if (data) {
assert(mp_audio_config_equals(af->data, data));
r = mp_audio_make_writeable(data);
} else {
data = &tmp;
*data = *(af->data);
mp_audio_set_null_data(data);
flags = AF_FILTER_FLAG_EOF;
for (int n = 0; n < MP_NUM_CHANNELS; n++)
data->planes[n] = &dummy[n];
}
if (r < 0)
goto done;
struct mp_audio frame = *data;
for (int n = 0; n < MP_NUM_CHANNELS; n++)
frame.allocated[n] = NULL;
assert(mp_audio_config_equals(af->data, data));
// Iterate through all filters
while (af) {
r = af->filter(af, &frame, flags);
int r = af->filter(af, data, flags);
if (r < 0)
goto done;
assert(mp_audio_config_equals(af->data, &frame));
return r;
assert(mp_audio_config_equals(af->data, data));
af = af->next;
}
mp_audio_buffer_append(output, &frame);
done:
if (data != &tmp)
talloc_free(data);
return r;
return 0;
}
// Calculate average ratio of filter output samples to input samples.
+1 -9
View File
@@ -76,13 +76,10 @@ struct af_instance {
* the number of samples passed though. (Ratio of input
* and output, e.g. mul=4 => 1 sample becomes 4 samples) .*/
bool auto_inserted; // inserted by af.c, such as conversion filters
char *label;
};
// Current audio stream
struct af_stream {
int initialized; // 0: no, 1: yes, -1: attempted to, but failed
// The first and last filter in the list
struct af_instance *first;
struct af_instance *last;
@@ -120,7 +117,6 @@ enum af_control {
AF_CONTROL_SET_PAN_BALANCE,
AF_CONTROL_GET_PAN_BALANCE,
AF_CONTROL_SET_PLAYBACK_SPEED,
AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE,
};
// Argument for AF_CONTROL_SET_PAN_LEVEL
@@ -134,11 +130,7 @@ void af_destroy(struct af_stream *s);
int af_init(struct af_stream *s);
void af_uninit(struct af_stream *s);
struct af_instance *af_add(struct af_stream *s, char *name, char **args);
int af_remove_by_label(struct af_stream *s, char *label);
struct af_instance *af_find_by_label(struct af_stream *s, char *label);
struct mp_audio_buffer;
int af_filter(struct af_stream *s, struct mp_audio *data,
struct mp_audio_buffer *output);
int af_filter(struct af_stream *s, struct mp_audio *data, int flags);
struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg);
void af_control_all(struct af_stream *s, int cmd, void *arg);
+5 -6
View File
@@ -78,7 +78,8 @@ static int control(struct af_instance* af, int cmd, void* arg)
int mapsize;
// Free previous buffers
free(s->buf[0]);
if (s->buf)
free(s->buf[0]);
// unmap previous area
if(s->mmap_area)
@@ -93,10 +94,8 @@ static int control(struct af_instance* af, int cmd, void* arg)
// Allocate new buffers (as one continuous block)
s->buf[0] = calloc(s->sz*af->data->nch, af->data->bps);
if(NULL == s->buf[0]) {
if(NULL == s->buf[0])
MP_FATAL(af, "Out of memory\n");
return AF_ERROR;
}
for(i = 1; i < af->data->nch; i++)
s->buf[i] = (uint8_t *)s->buf[0] + i*s->sz*af->data->bps;
@@ -148,8 +147,8 @@ static int control(struct af_instance* af, int cmd, void* arg)
static void uninit( struct af_instance* af )
{
af_export_t* s = af->priv;
free(s->buf[0]);
if (s->buf)
free(s->buf[0]);
// Free mmaped area
if(s->mmap_area)
-69
View File
@@ -1,69 +0,0 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include "af.h"
struct priv {
double speed;
};
static int control(struct af_instance *af, int cmd, void *arg)
{
struct priv *priv = af->priv;
switch (cmd) {
case AF_CONTROL_REINIT: {
struct mp_audio *in = arg;
struct mp_audio orig_in = *in;
struct mp_audio *out = af->data;
mp_audio_copy_config(out, in);
out->rate = in->rate * priv->speed;
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
}
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
priv->speed = *(double *)arg;
return AF_OK;
}
}
return AF_UNKNOWN;
}
static int filter(struct af_instance *af, struct mp_audio *data, int flags)
{
mp_audio_copy_config(data, af->data);
return 0;
}
static int af_open(struct af_instance *af)
{
struct priv *priv = af->priv;
af->control = control;
af->filter = filter;
priv->speed = 1.0;
return AF_OK;
}
#define OPT_BASE_STRUCT struct priv
const struct af_info af_info_forcespeed = {
.info = "Force audio speed",
.name = "forcespeed",
.open = af_open,
.priv_size = sizeof(struct priv),
};
+2 -2
View File
@@ -80,12 +80,12 @@ static int control(struct af_instance *af, int cmd, void *arg)
force_out_params(af, out);
if (in->nch != out->nch || in->bps != out->bps) {
MP_ERR(af, "Forced input/output formats are incompatible.\n");
MP_ERR(af, "[af_format] Forced input/output formats are incompatible.\n");
return AF_ERROR;
}
if (priv->fail) {
MP_ERR(af, "Failing on purpose.\n");
MP_ERR(af, "[af_format] Failing on purpose.\n");
return AF_ERROR;
}
+2 -2
View File
@@ -232,8 +232,8 @@ static int filter(struct af_instance* af, struct mp_audio* audio, int flags)
AV_WL16(hdr, 0xF872); // iec 61937 syncword 1
AV_WL16(hdr + 2, 0x4E1F); // iec 61937 syncword 2
hdr[5] = bsmod; // bsmod
hdr[4] = 0x01; // data-type ac3
hdr[4] = bsmod; // bsmod
hdr[5] = 0x01; // data-type ac3
AV_WL16(hdr + 6, len << 3); // number of bits in payload
}
+1 -1
View File
@@ -271,7 +271,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
switch (cmd) {
case AF_CONTROL_REINIT: {
struct mp_audio *data = (struct mp_audio *)arg;
float srate = data->rate / 1000.0;
float srate = data->rate / 1000;
int nch = data->nch;
int use_int = 0;
+34 -175
View File
@@ -29,7 +29,6 @@
#include "audio/format.h"
#include "audio/audio.h"
#include "input/input.h"
#include "options/options.h"
#include "options/m_config.h"
#include "common/msg.h"
@@ -64,12 +63,12 @@ static const struct ao_driver * const audio_out_drivers[] = {
#if HAVE_ALSA
&audio_out_alsa,
#endif
#if HAVE_WASAPI
&audio_out_wasapi,
#endif
#if HAVE_DSOUND
&audio_out_dsound,
#endif
#if HAVE_WASAPI
&audio_out_wasapi,
#endif
#if HAVE_OSS_AUDIO
&audio_out_oss,
#endif
@@ -88,9 +87,6 @@ static const struct ao_driver * const audio_out_drivers[] = {
#endif
#if HAVE_SNDIO
&audio_out_sndio,
#endif
#if HAVE_COREAUDIO
&audio_out_coreaudio_exclusive,
#endif
&audio_out_null,
// should not be auto-selected:
@@ -98,6 +94,9 @@ static const struct ao_driver * const audio_out_drivers[] = {
#if HAVE_ENCODING
&audio_out_lavc,
#endif
#if HAVE_COREAUDIO
&audio_out_coreaudio_exclusive,
#endif
#if HAVE_RSOUND
&audio_out_rsound,
#endif
@@ -129,34 +128,12 @@ const struct m_obj_list ao_obj_list = {
.allow_trailer = true,
};
// Return true if the ao with the given name matches the opt (--audio-device
// contents). If opt is empty, matching always succeeds. If out_device is not
// NULL, it will be set to the implied audio device (or NULL).
static bool match_ao_driver(const char *ao_name, char *opt, char **out_device)
static struct ao *ao_create(bool probing, struct mpv_global *global,
struct input_ctx *input_ctx,
struct encode_lavc_context *encode_lavc_ctx,
int samplerate, int format, struct mp_chmap channels,
char *name, char **args)
{
bstr ao_nameb = bstr0(ao_name);
bstr optb = bstr0(opt);
char *dummy;
if (!out_device)
out_device = &dummy;
*out_device = NULL;
if (!optb.len || bstr_equals0(optb, "auto"))
return true;
if (!bstr_startswith(optb, ao_nameb))
return false;
if (optb.len > ao_nameb.len && optb.start[ao_nameb.len] != '/')
return false;
char *split = strchr(opt, '/');
*out_device = split ? split + 1 : NULL;
return true;
}
static struct ao *ao_alloc(bool probing, struct mpv_global *global,
struct input_ctx *input_ctx, char *name, char **args)
{
struct MPOpts *opts = global->opts;
struct mp_log *log = mp_log_new(NULL, global->log, "ao");
struct m_obj_desc desc;
if (!m_obj_list_find(&desc, &ao_obj_list, bstr0(name))) {
@@ -169,62 +146,34 @@ static struct ao *ao_alloc(bool probing, struct mpv_global *global,
*ao = (struct ao) {
.driver = desc.p,
.probing = probing,
.encode_lavc_ctx = encode_lavc_ctx,
.input_ctx = input_ctx,
.samplerate = samplerate,
.channels = channels,
.format = format,
.log = mp_log_new(ao, log, name),
.def_buffer = opts->audio_buffer,
.def_buffer = global->opts->audio_buffer,
};
if (ao->driver->encode != !!ao->encode_lavc_ctx)
goto error;
struct m_config *config = m_config_from_obj_desc(ao, ao->log, &desc);
if (m_config_apply_defaults(config, name, opts->ao_defs) < 0)
if (m_config_apply_defaults(config, name, global->opts->ao_defs) < 0)
goto error;
if (m_config_set_obj_params(config, args) < 0)
goto error;
ao->priv = config->optstruct;
return ao;
error:
talloc_free(ao);
return NULL;
}
static struct ao *ao_alloc_pb(bool probing, struct mpv_global *global,
struct input_ctx *input_ctx,
struct encode_lavc_context *encode_lavc_ctx,
int samplerate, int format, struct mp_chmap channels,
char *name, char **args)
{
struct MPOpts *opts = global->opts;
struct ao *ao = ao_alloc(probing, global, input_ctx, name, args);
if (!ao)
return NULL;
ao->samplerate = samplerate;
ao->channels = channels;
ao->format = format;
ao->encode_lavc_ctx = encode_lavc_ctx;
if (ao->driver->encode != !!ao->encode_lavc_ctx) {
talloc_free(ao);
return NULL;
}
match_ao_driver(ao->driver->name, opts->audio_device, &ao->device);
ao->device = talloc_strdup(ao, ao->device);
ao->client_name = talloc_strdup(ao, opts->audio_client_name);
return ao;
}
static int ao_init(struct ao *ao)
{
char *chmap = mp_chmap_to_str(&ao->channels);
MP_VERBOSE(ao, "requested format: %d Hz, %s channels, %s\n",
ao->samplerate, mp_chmap_to_str(&ao->channels),
af_fmt_to_str(ao->format));
ao->samplerate, chmap, af_fmt_to_str(ao->format));
talloc_free(chmap);
ao->api = ao->driver->play ? &ao_api_push : &ao_api_pull;
ao->api_priv = talloc_zero_size(ao, ao->api->priv_size);
assert(!ao->api->priv_defaults && !ao->api->options);
int r = ao->driver->init(ao);
if (r < 0)
return r;
if (ao->driver->init(ao) < 0)
goto error;
ao->sstride = af_fmt2bps(ao->format);
ao->num_planes = 1;
@@ -235,40 +184,18 @@ static int ao_init(struct ao *ao)
}
ao->bps = ao->samplerate * ao->sstride;
if (!ao->device_buffer && ao->driver->get_space)
if (!ao->device_buffer && ao->driver->get_space) {
ao->device_buffer = ao->driver->get_space(ao);
if (ao->device_buffer)
MP_VERBOSE(ao, "device buffer: %d samples.\n", ao->device_buffer);
}
ao->buffer = MPMAX(ao->device_buffer, ao->def_buffer * ao->samplerate);
MP_VERBOSE(ao, "using soft-buffer of %d samples.\n", ao->buffer);
return ao->api->init(ao);
}
if (ao->api->init(ao) < 0)
goto error;
static struct ao *ao_create(bool probing, struct mpv_global *global,
struct input_ctx *input_ctx,
struct encode_lavc_context *encode_lavc_ctx,
int samplerate, int format, struct mp_chmap channels,
char *name, char **args)
{
struct ao *ao = ao_alloc_pb(probing, global, input_ctx, encode_lavc_ctx,
samplerate, format, channels, name, args);
if (ao && ao_init(ao) >= 0)
return ao;
// Silly exception for coreaudio spdif redirection
if (ao && ao->redirect) {
char redirect[80], device[80];
snprintf(redirect, sizeof(redirect), "%s", ao->redirect);
snprintf(device, sizeof(device), "%s", ao->device ? ao->device : "");
talloc_free(ao);
ao = ao_alloc_pb(probing, global, input_ctx, encode_lavc_ctx,
samplerate, format, channels, redirect, args);
if (ao) {
ao->device = talloc_strdup(ao, device);
if (ao_init(ao) >= 0)
return ao;
}
}
return ao;
error:
talloc_free(ao);
return NULL;
}
@@ -278,10 +205,9 @@ struct ao *ao_init_best(struct mpv_global *global,
struct encode_lavc_context *encode_lavc_ctx,
int samplerate, int format, struct mp_chmap channels)
{
struct MPOpts *opts = global->opts;
struct mp_log *log = mp_log_new(NULL, global->log, "ao");
struct ao *ao = NULL;
struct m_obj_settings *ao_list = opts->audio_driver_list;
struct m_obj_settings *ao_list = global->opts->audio_driver_list;
if (ao_list && ao_list[0].name) {
for (int n = 0; ao_list[n].name; n++) {
if (strlen(ao_list[n].name) == 0)
@@ -298,21 +224,15 @@ struct ao *ao_init_best(struct mpv_global *global,
}
goto done;
}
autoprobe: ;
autoprobe:
// now try the rest...
bool matched_dev = false;
for (int i = 0; audio_out_drivers[i]; i++) {
char *name = (char *)audio_out_drivers[i]->name;
if (!match_ao_driver(name, opts->audio_device, NULL))
continue;
matched_dev = true;
ao = ao_create(true, global, input_ctx, encode_lavc_ctx,
samplerate, format, channels, name, NULL);
samplerate, format, channels,
(char *)audio_out_drivers[i]->name, NULL);
if (ao)
goto done;
}
if (!matched_dev)
mp_err(log, "--audio-device option refers to missing output driver.\n");
done:
talloc_free(log);
return ao;
@@ -396,22 +316,6 @@ bool ao_eof_reached(struct ao *ao)
return ao->api->get_eof ? ao->api->get_eof(ao) : true;
}
// Query the AO_EVENT_*s as requested by the events parameter, and return them.
int ao_query_and_reset_events(struct ao *ao, int events)
{
int actual_events = 0;
if (atomic_load(&ao->request_reload)) // don't need to reset it
actual_events |= AO_EVENT_RELOAD;
return actual_events & events;
}
// Request that the player core destroys and recreates the AO.
void ao_request_reload(struct ao *ao)
{
atomic_store(&ao->request_reload, true);
mp_input_wakeup(ao->input_ctx);
}
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
struct mp_chmap *map)
{
@@ -448,48 +352,3 @@ bool ao_untimed(struct ao *ao)
{
return ao->untimed;
}
struct ao_device_list *ao_get_device_list(struct mpv_global *global)
{
struct ao_device_list *list = talloc_zero(NULL, struct ao_device_list);
MP_TARRAY_APPEND(list, list->devices, list->num_devices,
(struct ao_device_desc){"auto", "Autoselect device"});
for (int n = 0; audio_out_drivers[n]; n++) {
const struct ao_driver *d = audio_out_drivers[n];
if (d == &audio_out_null)
break; // don't add unsafe/special entries
struct ao *ao = ao_alloc(true, global, NULL, (char *)d->name, NULL);
if (!ao)
continue;
int num = list->num_devices;
if (d->list_devs)
d->list_devs(ao, list);
// Add at least a default entry
if (list->num_devices == num)
ao_device_list_add(list, ao, &(struct ao_device_desc){"", "Default"});
talloc_free(ao);
}
return list;
}
void ao_device_list_add(struct ao_device_list *list, struct ao *ao,
struct ao_device_desc *e)
{
struct ao_device_desc c = *e;
const char *dname = ao->driver->name;
c.name = c.name[0] ? talloc_asprintf(list, "%s/%s", dname, c.name)
: talloc_strdup(list, dname);
c.desc = talloc_strdup(list, c.desc);
MP_TARRAY_APPEND(list, list->devices, list->num_devices, c);
}
void ao_print_devices(struct mpv_global *global, struct mp_log *log)
{
struct ao_device_list *list = ao_get_device_list(global);
mp_info(log, "List of detected audio devices:\n");
for (int n = 0; n < list->num_devices; n++) {
struct ao_device_desc *desc = &list->devices[n];
mp_info(log, " '%s' (%s)\n", desc->name, desc->desc);
}
talloc_free(list);
}
-19
View File
@@ -46,25 +46,11 @@ enum aocontrol {
// data might be written again, instead of closing the AO.
#define AOPLAY_FINAL_CHUNK 1
enum {
AO_EVENT_RELOAD = 1,
};
typedef struct ao_control_vol {
float left;
float right;
} ao_control_vol_t;
struct ao_device_desc {
const char *name; // symbolic name; will be set on ao->device
const char *desc; // verbose human readable name
};
struct ao_device_list {
struct ao_device_desc *devices;
int num_devices;
};
struct ao;
struct mpv_global;
struct input_ctx;
@@ -89,10 +75,5 @@ void ao_pause(struct ao *ao);
void ao_resume(struct ao *ao);
void ao_drain(struct ao *ao);
bool ao_eof_reached(struct ao *ao);
int ao_query_and_reset_events(struct ao *ao, int events);
void ao_request_reload(struct ao *ao);
struct ao_device_list *ao_get_device_list(struct mpv_global *global);
void ao_print_devices(struct mpv_global *global, struct mp_log *log);
#endif /* MPLAYER_AUDIO_OUT_H */
+135 -298
View File
@@ -40,10 +40,10 @@
#include "common/msg.h"
#include "osdep/endian.h"
#include <alsa/asoundlib.h>
#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API
#define HAVE_CHMAP_API \
(defined(SND_CHMAP_API_VERSION) && SND_CHMAP_API_VERSION >= (1 << 16))
#include <alsa/asoundlib.h>
#include "ao.h"
#include "internal.h"
@@ -54,7 +54,7 @@ struct priv {
snd_pcm_format_t alsa_fmt;
int can_pause;
snd_pcm_sframes_t prepause_frames;
double delay_before_pause;
float delay_before_pause;
int buffersize; // in frames
int outburst; // in frames
@@ -78,12 +78,10 @@ struct priv {
} \
} while (0)
#define CHECK_ALSA_WARN(message) \
do { \
if (err < 0) \
MP_WARN(ao, "%s: %s\n", (message), snd_strerror(err)); \
} while (0)
static float get_delay(struct ao *ao);
static void uninit(struct ao *ao);
/* to set/get/query special features/parameters */
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
{
struct priv *p = ao->priv;
@@ -105,8 +103,10 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
if (AF_FORMAT_IS_SPECIAL(ao->format))
return CONTROL_FALSE;
//allocate simple id
snd_mixer_selem_id_alloca(&sid);
//sets simple-mixer index and name
snd_mixer_selem_id_set_index(sid, p->cfg_mixer_index);
snd_mixer_selem_id_set_name(sid, p->cfg_mixer_name);
@@ -138,6 +138,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
ao_control_vol_t *vol = arg;
set_vol = vol->left / f_multi + pmin + 0.5;
//setting channels
err = snd_mixer_selem_set_playback_volume
(elem, SND_MIXER_SCHN_FRONT_LEFT, set_vol);
CHECK_ALSA_ERROR("Error setting left channel");
@@ -149,7 +150,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
(elem, SND_MIXER_SCHN_FRONT_RIGHT, set_vol);
CHECK_ALSA_ERROR("Error setting right channel");
MP_DBG(ao, "right=%li, pmin=%li, pmax=%li, mult=%f\n",
set_vol, pmin, pmax, f_multi);
set_vol, pmin, pmax,
f_multi);
break;
}
case AOCONTROL_GET_VOLUME: {
@@ -229,98 +231,6 @@ static int find_alsa_format(int af_format)
return SND_PCM_FORMAT_UNKNOWN;
}
#if HAVE_CHMAP_API
static const int alsa_to_mp_channels[][2] = {
{SND_CHMAP_FL, MP_SP(FL)},
{SND_CHMAP_FR, MP_SP(FR)},
{SND_CHMAP_RL, MP_SP(BL)},
{SND_CHMAP_RR, MP_SP(BR)},
{SND_CHMAP_FC, MP_SP(FC)},
{SND_CHMAP_LFE, MP_SP(LFE)},
{SND_CHMAP_SL, MP_SP(SL)},
{SND_CHMAP_SR, MP_SP(SR)},
{SND_CHMAP_RC, MP_SP(BC)},
{SND_CHMAP_FLC, MP_SP(FLC)},
{SND_CHMAP_FRC, MP_SP(FRC)},
{SND_CHMAP_FLW, MP_SP(WL)},
{SND_CHMAP_FRW, MP_SP(WR)},
{SND_CHMAP_TC, MP_SP(TC)},
{SND_CHMAP_TFL, MP_SP(TFL)},
{SND_CHMAP_TFR, MP_SP(TFR)},
{SND_CHMAP_TFC, MP_SP(TFC)},
{SND_CHMAP_TRL, MP_SP(TBL)},
{SND_CHMAP_TRR, MP_SP(TBR)},
{SND_CHMAP_TRC, MP_SP(TBC)},
{SND_CHMAP_MONO, MP_SP(FC)},
{SND_CHMAP_LAST, MP_SPEAKER_ID_COUNT}
};
static int find_mp_channel(int alsa_channel)
{
for (int i = 0; alsa_to_mp_channels[i][1] != MP_SPEAKER_ID_COUNT; i++) {
if (alsa_to_mp_channels[i][0] == alsa_channel)
return alsa_to_mp_channels[i][1];
}
return MP_SPEAKER_ID_COUNT;
}
static int find_alsa_channel(int mp_channel)
{
for (int i = 0; alsa_to_mp_channels[i][1] != MP_SPEAKER_ID_COUNT; i++) {
if (alsa_to_mp_channels[i][1] == mp_channel)
return alsa_to_mp_channels[i][0];
}
return SND_CHMAP_UNKNOWN;
}
static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
{
struct priv *p = ao->priv;
struct mp_chmap_sel chmap_sel = {0};
snd_pcm_chmap_query_t **maps = snd_pcm_query_chmaps(p->alsa);
if (!maps)
return false;
for (int i = 0; maps[i] != NULL; i++) {
if (maps[i]->map.channels > MP_NUM_CHANNELS) {
MP_VERBOSE(ao, "skipping ALSA channel map with too many channels.\n");
continue;
}
struct mp_chmap entry = {.num = maps[i]->map.channels};
for (int c = 0; c < entry.num; c++)
entry.speaker[c] = find_mp_channel(maps[i]->map.pos[c]);
if (mp_chmap_is_valid(&entry)) {
MP_VERBOSE(ao, "Got supported channel map: %s (type %s)\n",
mp_chmap_to_str(&entry),
snd_pcm_chmap_type_name(maps[i]->type));
mp_chmap_sel_add_map(&chmap_sel, &entry);
} else {
char tmp[128];
if (snd_pcm_chmap_print(&maps[i]->map, sizeof(tmp), tmp) > 0)
MP_VERBOSE(ao, "skipping unknown ALSA channel map: %s\n", tmp);
}
}
snd_pcm_free_chmaps(maps);
return ao_chmap_sel_adjust(ao, &chmap_sel, chmap);
}
#else /* HAVE_CHMAP_API */
static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
{
return false;
}
#endif /* else HAVE_CHMAP_API */
// Lists device names and their implied channel map.
// The second item must be resolvable with mp_chmap_from_str().
// Source: http://www.alsa-project.org/main/index.php/DeviceNames
@@ -338,9 +248,11 @@ static const char *const device_channel_layouts[][2] = {
{"surround71", "fl-fr-bl-br-fc-lfe-sl-sr"},
};
#define NUM_ALSA_CHMAPS MP_ARRAY_SIZE(device_channel_layouts)
#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0]))
static const char *select_chmap(struct ao *ao, struct mp_chmap *chmap)
#define NUM_ALSA_CHMAPS ARRAY_LEN(device_channel_layouts)
static const char *select_chmap(struct ao *ao)
{
struct mp_chmap_sel sel = {0};
struct mp_chmap maps[NUM_ALSA_CHMAPS];
@@ -349,16 +261,18 @@ static const char *select_chmap(struct ao *ao, struct mp_chmap *chmap)
mp_chmap_sel_add_map(&sel, &maps[n]);
};
if (!ao_chmap_sel_adjust(ao, &sel, chmap))
return "default";
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
return NULL;
for (int n = 0; n < NUM_ALSA_CHMAPS; n++) {
if (mp_chmap_equals(chmap, &maps[n]))
if (mp_chmap_equals(&ao->channels, &maps[n]))
return device_channel_layouts[n][0];
}
char *name = mp_chmap_to_str(&ao->channels);
MP_ERR(ao, "channel layout %s (%d ch) not supported.\n",
mp_chmap_to_str(chmap), chmap->num);
name, ao->channels.num);
talloc_free(name);
return "default";
}
@@ -379,44 +293,34 @@ static int map_iec958_srate(int srate)
}
}
// ALSA device strings can have parameters. They are usually appended to the
// device name. Since there can be various forms, and we (sometimes) want to
// append them to unknown device strings, which possibly already include params.
static char *append_params(void *ta_parent, const char *device, const char *p)
{
if (!p || !p[0])
return talloc_strdup(ta_parent, device);
int len = strlen(device);
char *end = strchr(device, ':');
if (!end) {
/* no existing parameters: add it behind device name */
return talloc_asprintf(ta_parent, "%s:%s", device, p);
} else if (end[1] == '\0') {
/* ":" but no parameters */
return talloc_asprintf(ta_parent, "%s%s", device, p);
} else if (end[1] == '{' && device[len - 1] == '}') {
/* parameters in config syntax: add it inside the { } block */
return talloc_asprintf(ta_parent, "%.*s %s}", len - 1, device, p);
} else {
/* a simple list of parameters: add it at the end of the list */
return talloc_asprintf(ta_parent, "%s,%s", device, p);
}
abort();
}
static int try_open_device(struct ao *ao, const char *device, int open_mode)
{
struct priv *p = ao->priv;
if (AF_FORMAT_IS_IEC61937(ao->format)) {
void *tmp = talloc_new(NULL);
/* to set the non-audio bit, use AES0=6 */
char *params = talloc_asprintf(tmp,
"AES0=%d,AES1=%d,AES2=0,AES3=%d",
IEC958_AES0_NONAUDIO | IEC958_AES0_PRO_EMPHASIS_NONE,
IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER,
map_iec958_srate(ao->samplerate));
const char *ac3_device = append_params(tmp, device, params);
const char *ac3_device = device;
int len = strlen(device);
char *end = strchr(device, ':');
if (!end) {
/* no existing parameters: add it behind device name */
ac3_device = talloc_asprintf(tmp, "%s:%s", device, params);
} else if (end[1] == '\0') {
/* ":" but no parameters */
ac3_device = talloc_asprintf(tmp, "%s%s", device, params);
} else if (end[1] == '{' && device[len - 1] == '}') {
/* parameters in config syntax: add it inside the { } block */
ac3_device = talloc_asprintf(tmp, "%.*s %s}", len - 1, device, params);
} else {
/* a simple list of parameters: add it at the end of the list */
ac3_device = talloc_asprintf(tmp, "%s,%s", device, params);
}
int err = snd_pcm_open
(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, open_mode);
talloc_free(tmp);
@@ -427,49 +331,49 @@ static int try_open_device(struct ao *ao, const char *device, int open_mode)
return snd_pcm_open(&p->alsa, device, SND_PCM_STREAM_PLAYBACK, open_mode);
}
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;
if (p->alsa) {
int err;
err = snd_pcm_close(p->alsa);
CHECK_ALSA_ERROR("pcm close error");
}
alsa_error: ;
}
/*
open & setup audio device
return: 0=success -1=fail
*/
static int init(struct ao *ao)
{
struct priv *p = ao->priv;
int err;
snd_pcm_uframes_t chunk_size;
snd_pcm_uframes_t bufsize;
snd_pcm_uframes_t boundary;
struct priv *p = ao->priv;
if (!p->cfg_ni)
ao->format = af_fmt_from_planar(ao->format);
struct mp_chmap implied_chmap = ao->channels;
/* switch for spdif
* sets opening sequence for SPDIF
* sets also the playback and other switches 'on the fly'
* while opening the abstract alias for the spdif subdevice
* 'iec958'
*/
const char *device;
if (AF_FORMAT_IS_IEC61937(ao->format)) {
device = "iec958";
MP_VERBOSE(ao, "playing AC3/iec61937/iec958, %i channels\n",
ao->channels.num);
} else {
device = select_chmap(ao, &implied_chmap);
// Not-default likely means a hw device - enable software conversions.
if (strcmp(device, "default") != 0)
device = select_chmap(ao);
if (strcmp(device, "default") != 0 && (ao->format & AF_FORMAT_F)) {
// hack - use the converter plugin (why the heck?)
device = talloc_asprintf(ao, "plug:%s", device);
}
}
if (ao->device)
device = ao->device;
if (p->cfg_device && p->cfg_device[0])
device = p->cfg_device;
MP_VERBOSE(ao, "using device: %s\n", device);
MP_VERBOSE(ao, "using ALSA version: %s\n", snd_asoundlib_version());
err = try_open_device(ao, device, p->cfg_block ? 0 : SND_PCM_NONBLOCK);
int open_mode = p->cfg_block ? 0 : SND_PCM_NONBLOCK;
//modes = 0, SND_PCM_NONBLOCK, SND_PCM_ASYNC
err = try_open_device(ao, device, open_mode);
if (err < 0) {
if (err != -EBUSY && !p->cfg_block) {
MP_WARN(ao, "Open in nonblock-mode "
@@ -492,6 +396,7 @@ static int init(struct ao *ao)
snd_pcm_hw_params_alloca(&alsa_hwparams);
snd_pcm_sw_params_alloca(&alsa_swparams);
// setting hw-parameters
err = snd_pcm_hw_params_any(p->alsa, alsa_hwparams);
CHECK_ALSA_ERROR("Unable to get initial parameters");
@@ -533,24 +438,11 @@ static int init(struct ao *ao)
}
CHECK_ALSA_ERROR("Unable to set access type");
struct mp_chmap dev_chmap = ao->channels;
if (query_chmaps(ao, &dev_chmap)) {
ao->channels = dev_chmap;
} else {
dev_chmap.num = 0;
ao->channels = implied_chmap;
}
int num_channels = ao->channels.num;
err = snd_pcm_hw_params_set_channels_near
(p->alsa, alsa_hwparams, &num_channels);
CHECK_ALSA_ERROR("Unable to set channels");
if (num_channels > MP_NUM_CHANNELS) {
MP_FATAL(ao, "Too many audio channels (%d).\n", num_channels);
goto alsa_error;
}
if (num_channels != ao->channels.num) {
MP_ERR(ao, "Couldn't get requested number of channels.\n");
mp_chmap_from_channels_alsa(&ao->channels, num_channels);
@@ -569,79 +461,25 @@ static int init(struct ao *ao)
err = snd_pcm_hw_params_set_buffer_time_near
(p->alsa, alsa_hwparams, &(unsigned int){BUFFER_TIME}, NULL);
CHECK_ALSA_WARN("Unable to set buffer time near");
CHECK_ALSA_ERROR("Unable to set buffer time near");
err = snd_pcm_hw_params_set_periods_near
(p->alsa, alsa_hwparams, &(unsigned int){FRAGCOUNT}, NULL);
CHECK_ALSA_WARN("Unable to set periods");
CHECK_ALSA_ERROR("Unable to set periods");
/* finally install hardware parameters */
err = snd_pcm_hw_params(p->alsa, alsa_hwparams);
CHECK_ALSA_ERROR("Unable to set hw-parameters");
/* end setting hw-params */
// end setting hw-params
#if HAVE_CHMAP_API
if (mp_chmap_is_valid(&dev_chmap)) {
snd_pcm_chmap_t *alsa_chmap =
calloc(1, sizeof(*alsa_chmap) +
sizeof(alsa_chmap->pos[0]) * dev_chmap.num);
if (!alsa_chmap)
goto alsa_error;
alsa_chmap->channels = dev_chmap.num;
for (int c = 0; c < dev_chmap.num; c++)
alsa_chmap->pos[c] = find_alsa_channel(dev_chmap.speaker[c]);
char tmp[128];
if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0)
MP_VERBOSE(ao, "trying to set ALSA channel map: %s\n", tmp);
err = snd_pcm_set_chmap(p->alsa, alsa_chmap);
if (err == -ENXIO) {
MP_WARN(ao, "Device does not support requested channel map\n");
} else {
CHECK_ALSA_WARN("Channel map setup failed");
}
}
snd_pcm_chmap_t *alsa_chmap = snd_pcm_get_chmap(p->alsa);
if (alsa_chmap) {
char tmp[128];
if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0)
MP_VERBOSE(ao, "channel map reported by ALSA: %s\n", tmp);
struct mp_chmap chmap = {.num = alsa_chmap->channels};
for (int c = 0; c < chmap.num; c++)
chmap.speaker[c] = find_mp_channel(alsa_chmap->pos[c]);
MP_VERBOSE(ao, "which we understand as: %s\n", mp_chmap_to_str(&chmap));
if (mp_chmap_is_valid(&chmap)) {
if (mp_chmap_equals(&chmap, &ao->channels)) {
MP_VERBOSE(ao, "which is what we requested.\n");
} else if (chmap.num == ao->channels.num) {
MP_VERBOSE(ao, "using the ALSA channel map.\n");
ao->channels = chmap;
} else {
MP_WARN(ao, "ALSA channel map conflicts with channel count!\n");
}
} else {
MP_WARN(ao, "Got unknown channel map from ALSA.\n");
}
free(alsa_chmap);
}
#endif
snd_pcm_uframes_t bufsize;
// gets buffersize for control
err = snd_pcm_hw_params_get_buffer_size(alsa_hwparams, &bufsize);
CHECK_ALSA_ERROR("Unable to get buffersize");
p->buffersize = bufsize;
MP_VERBOSE(ao, "got buffersize=%i samples\n", p->buffersize);
snd_pcm_uframes_t chunk_size;
err = snd_pcm_hw_params_get_period_size(alsa_hwparams, &chunk_size, NULL);
CHECK_ALSA_ERROR("Unable to get period size");
@@ -652,7 +490,6 @@ static int init(struct ao *ao)
err = snd_pcm_sw_params_current(p->alsa, alsa_swparams);
CHECK_ALSA_ERROR("Unable to get sw-parameters");
snd_pcm_uframes_t boundary;
err = snd_pcm_sw_params_get_boundary(alsa_swparams, &boundary);
CHECK_ALSA_ERROR("Unable to get boundary");
@@ -672,17 +509,40 @@ static int init(struct ao *ao)
CHECK_ALSA_ERROR("Unable to set silence size");
err = snd_pcm_sw_params(p->alsa, alsa_swparams);
CHECK_ALSA_ERROR("Unable to set sw-parameters");
CHECK_ALSA_ERROR("Unable to get sw-parameters");
/* end setting sw-params */
p->can_pause = snd_pcm_hw_params_can_pause(alsa_hwparams);
MP_VERBOSE(ao, "opened: %d Hz/%d channels/%d bps/%d samples buffer/%s\n",
ao->samplerate, ao->channels.num, af_fmt2bits(ao->format),
p->buffersize, snd_pcm_format_description(p->alsa_fmt));
return 0;
alsa_error:
uninit(ao);
return -1;
} // end init
/* close audio device */
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;
if (p->alsa) {
int err;
err = snd_pcm_close(p->alsa);
CHECK_ALSA_ERROR("pcm close error");
MP_VERBOSE(ao, "uninit: pcm closed\n");
}
alsa_error:
p->alsa = NULL;
}
static void drain(struct ao *ao)
@@ -691,46 +551,6 @@ static void drain(struct ao *ao)
snd_pcm_drain(p->alsa);
}
static int get_space(struct ao *ao)
{
struct priv *p = ao->priv;
snd_pcm_status_t *status;
int err;
snd_pcm_status_alloca(&status);
err = snd_pcm_status(p->alsa, status);
CHECK_ALSA_ERROR("cannot get pcm status");
unsigned space = snd_pcm_status_get_avail(status);
if (space > p->buffersize) // Buffer underrun?
space = p->buffersize;
return space / p->outburst * p->outburst;
alsa_error:
return 0;
}
/* delay in seconds between first and last sample in buffer */
static double get_delay(struct ao *ao)
{
struct priv *p = ao->priv;
snd_pcm_sframes_t delay;
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED)
return p->delay_before_pause;
if (snd_pcm_delay(p->alsa, &delay) < 0)
return 0;
if (delay < 0) {
/* underrun - move the application pointer forward to catch up */
snd_pcm_forward(p->alsa, -delay);
delay = 0;
}
return delay / (double)ao->samplerate;
}
static void audio_pause(struct ao *ao)
{
struct priv *p = ao->priv;
@@ -747,7 +567,7 @@ static void audio_pause(struct ao *ao)
if (snd_pcm_delay(p->alsa, &p->prepause_frames) < 0
|| p->prepause_frames < 0)
p->prepause_frames = 0;
p->delay_before_pause = p->prepause_frames / (double)ao->samplerate;
p->delay_before_pause = p->prepause_frames / (float)ao->samplerate;
err = snd_pcm_drop(p->alsa);
CHECK_ALSA_ERROR("pcm drop error");
@@ -784,6 +604,7 @@ static void audio_resume(struct ao *ao)
alsa_error: ;
}
/* stop playing and empty buffers (for seeking/pause) */
static void reset(struct ao *ao)
{
struct priv *p = ao->priv;
@@ -816,7 +637,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
res = snd_pcm_writei(p->alsa, data[0], samples);
}
if (res == -EINTR || res == -EAGAIN) { /* retry */
if (res == -EINTR) {
/* nothing to do */
res = 0;
} else if (res == -ESTRPIPE) { /* suspend */
audio_resume(ao);
@@ -835,6 +657,46 @@ alsa_error:
return -1;
}
static int get_space(struct ao *ao)
{
struct priv *p = ao->priv;
snd_pcm_status_t *status;
int err;
snd_pcm_status_alloca(&status);
err = snd_pcm_status(p->alsa, status);
CHECK_ALSA_ERROR("cannot get pcm status");
unsigned space = snd_pcm_status_get_avail(status);
if (space > p->buffersize) // Buffer underrun?
space = p->buffersize;
return space / p->outburst * p->outburst;
alsa_error:
return 0;
}
/* delay in seconds between first and last sample in buffer */
static float get_delay(struct ao *ao)
{
struct priv *p = ao->priv;
snd_pcm_sframes_t delay;
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED)
return p->delay_before_pause;
if (snd_pcm_delay(p->alsa, &delay) < 0)
return 0;
if (delay < 0) {
/* underrun - move the application pointer forward to catch up */
snd_pcm_forward(p->alsa, -delay);
delay = 0;
}
return (float)delay / (float)ao->samplerate;
}
#define MAX_POLL_FDS 20
static int audio_wait(struct ao *ao, pthread_mutex_t *lock)
{
@@ -869,34 +731,10 @@ alsa_error:
return -1;
}
static void list_devs(struct ao *ao, struct ao_device_list *list)
{
void **hints;
if (snd_device_name_hint(-1, "pcm", &hints) < 0)
return;
for (int n = 0; hints[n]; n++) {
char *name = snd_device_name_get_hint(hints[n], "NAME");
char *desc = snd_device_name_get_hint(hints[n], "DESC");
char *io = snd_device_name_get_hint(hints[n], "IOID");
if (io && strcmp(io, "Output") != 0)
continue;
char desc2[1024];
snprintf(desc2, sizeof(desc2), "%s", desc ? desc : "");
for (int i = 0; desc2[i]; i++) {
if (desc2[i] == '\n')
desc2[i] = '/';
}
ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc2});
}
snd_device_name_free_hint(hints);
}
#define OPT_BASE_STRUCT struct priv
const struct ao_driver audio_out_alsa = {
.description = "ALSA audio output",
.description = "ALSA-0.9.x-1.x audio output",
.name = "alsa",
.init = init,
.uninit = uninit,
@@ -910,7 +748,6 @@ const struct ao_driver audio_out_alsa = {
.drain = drain,
.wait = audio_wait,
.wakeup = ao_wakeup_poll,
.list_devs = list_devs,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.cfg_block = 1,
+15 -11
View File
@@ -33,6 +33,10 @@ struct priv {
AudioUnit audio_unit;
uint64_t hw_latency_us;
// options
int opt_device_id;
int opt_list;
};
bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
@@ -145,15 +149,11 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd);
static int init(struct ao *ao)
{
if (AF_FORMAT_IS_IEC61937(ao->format)) {
MP_WARN(ao, "detected IEC61937, redirecting to coreaudio_exclusive\n");
ao->redirect = "coreaudio_exclusive";
return CONTROL_ERROR;
}
struct priv *p = ao->priv;
OSStatus err = ca_select_device(ao, ao->device, &p->device);
if (p->opt_list) ca_print_device_list(ao);
OSStatus err = ca_select_device(ao, p->opt_device_id, &p->device);
CHECK_CA_ERROR("failed to select device");
if (!init_chmap(ao))
@@ -221,9 +221,9 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
AudioComponentDescription desc = (AudioComponentDescription) {
.componentType = kAudioUnitType_Output,
.componentSubType = (ao->device) ?
kAudioUnitSubType_HALOutput :
kAudioUnitSubType_DefaultOutput,
.componentSubType = (p->opt_device_id < 0) ?
kAudioUnitSubType_DefaultOutput :
kAudioUnitSubType_HALOutput,
.componentManufacturer = kAudioUnitManufacturer_Apple,
.componentFlags = 0,
.componentFlagsMask = 0,
@@ -440,6 +440,10 @@ const struct ao_driver audio_out_coreaudio = {
.control = control,
.pause = stop,
.resume = start,
.list_devs = ca_get_device_list,
.priv_size = sizeof(struct priv),
.options = (const struct m_option[]) {
OPT_INT("device_id", opt_device_id, 0, OPTDEF_INT(-1)),
OPT_FLAG("list", opt_list, 0),
{0}
},
};
@@ -333,6 +333,10 @@ struct priv {
bool changed_mixing;
int stream_asbd_changed;
bool muted;
// options
int opt_device_id;
int opt_list;
};
static int get_ring_size(struct ao *ao)
@@ -398,7 +402,9 @@ static int init(struct ao *ao)
{
struct priv *p = ao->priv;
OSStatus err = ca_select_device(ao, ao->device, &p->device);
if (p->opt_list) ca_print_device_list(ao);
OSStatus err = ca_select_device(ao, p->opt_device_id, &p->device);
CHECK_CA_ERROR("failed to select device");
ao->format = af_fmt_from_planar(ao->format);
@@ -581,12 +587,12 @@ static int get_space(struct ao *ao)
return mp_ring_available(p->buffer) / ao->sstride;
}
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
// FIXME: should also report the delay of coreaudio itself (hardware +
// internal buffers)
struct priv *p = ao->priv;
return mp_ring_buffered(p->buffer) / (double)ao->bps;
return mp_ring_buffered(p->buffer) / (float)ao->bps;
}
static void uninit(struct ao *ao)
@@ -654,7 +660,6 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
.reset = reset,
.pause = audio_pause,
.resume = audio_resume,
.list_devs = ca_get_device_list,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv){
.muted = false,
@@ -664,4 +669,9 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
.stream_idx = -1,
.changed_mixing = false,
},
.options = (const struct m_option[]) {
OPT_INT("device_id", opt_device_id, 0, OPTDEF_INT(-1)),
OPT_FLAG("list", opt_list, 0),
{0}
},
};
+24 -30
View File
@@ -28,48 +28,42 @@
#include "osdep/endian.h"
#include "audio/format.h"
static bool ca_is_output_device(struct ao *ao, AudioDeviceID dev)
void ca_print_device_list(struct ao *ao)
{
size_t n_buffers;
AudioBufferList *buffers;
const ca_scope scope = kAudioDevicePropertyStreamConfiguration;
CA_GET_ARY_O(dev, scope, &buffers, &n_buffers);
talloc_free(buffers);
return n_buffers > 0;
}
char *help = talloc_strdup(NULL, "Available output devices:\n");
void ca_get_device_list(struct ao *ao, struct ao_device_list *list)
{
AudioDeviceID *devs;
size_t n_devs;
OSStatus err =
CA_GET_ARY(kAudioObjectSystemObject, kAudioHardwarePropertyDevices,
&devs, &n_devs);
CHECK_CA_ERROR("Failed to get list of output devices.");
for (int i = 0; i < n_devs; i++) {
if (!ca_is_output_device(ao, devs[i]))
continue;
char name[32];
char *desc;
sprintf(name, "%d", devs[i]);
err = CA_GET_STR(devs[i], kAudioObjectPropertyName, &desc);
if (err != noErr)
desc = "Unknown";
ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc});
char *name;
err = CA_GET_STR(devs[i], kAudioObjectPropertyName, &name);
if (err == noErr)
talloc_steal(devs, name);
else
name = "Unknown";
help = talloc_asprintf_append(
help, " * %s (id: %" PRIu32 ")\n", name, devs[i]);
}
talloc_free(devs);
coreaudio_error:
return;
MP_INFO(ao, "%s", help);
talloc_free(help);
}
OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
OSStatus ca_select_device(struct ao *ao, int selection, AudioDeviceID *device)
{
OSStatus err = noErr;
int selection = name ? strtol(name, (char **)NULL, 10) : -1;
if (errno == EINVAL || errno == ERANGE) {
selection = -1;
MP_WARN(ao, "device identifier '%s' is invalid\n", name);
}
*device = 0;
if (selection < 0) {
// device not set by user, get the default one
@@ -82,14 +76,14 @@ OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
}
if (mp_msg_test(ao->log, MSGL_V)) {
char *desc;
err = CA_GET_STR(*device, kAudioObjectPropertyName, &desc);
char *name;
err = CA_GET_STR(*device, kAudioObjectPropertyName, &name);
CHECK_CA_ERROR("could not get selected audio device name");
MP_VERBOSE(ao, "selected audio output device: %s (%" PRIu32 ")\n",
desc, *device);
name, *device);
talloc_free(desc);
talloc_free(name);
}
coreaudio_error:
+2 -2
View File
@@ -46,8 +46,8 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message);
if (err != noErr) goto label; \
} while (0)
void ca_get_device_list(struct ao *ao, struct ao_device_list *list);
OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device);
void ca_print_device_list(struct ao *ao);
OSStatus ca_select_device(struct ao *ao, int selection, AudioDeviceID *device);
void ca_fill_asbd(struct ao *ao, AudioStreamBasicDescription *asbd);
void ca_print_asbd(struct ao *ao, const char *description,
+24 -88
View File
@@ -41,7 +41,6 @@
#include "internal.h"
#include "common/msg.h"
#include "osdep/timer.h"
#include "osdep/io.h"
#include "options/m_option.h"
/**
@@ -100,10 +99,10 @@ struct priv {
int cfg_device;
int cfg_buffersize;
struct ao_device_list *listing; ///temporary during list_devs()
};
static float get_delay(struct ao *ao);
/***************************************************************************************/
/**
@@ -173,84 +172,11 @@ static BOOL CALLBACK DirectSoundEnum(LPGUID guid, LPCSTR desc, LPCSTR module,
if (guid)
memcpy(&p->device, guid, sizeof(GUID));
}
char *guidstr = talloc_strdup(NULL, "");
if (guid) {
wchar_t guidwstr[80] = {0};
StringFromGUID2(guid, guidwstr, MP_ARRAY_SIZE(guidwstr));
char *nstr = mp_to_utf8(NULL, guidwstr);
if (nstr) {
talloc_free(guidstr);
guidstr = nstr;
}
}
if (p->device_num < 0 && ao->device) {
if (strcmp(ao->device, guidstr) == 0) {
MP_VERBOSE(ao, "<--");
p->device_num = p->device_index;
if (guid)
memcpy(&p->device, guid, sizeof(GUID));
}
}
if (p->listing) {
struct ao_device_desc e = {guidstr, desc};
ao_device_list_add(p->listing, ao, &e);
}
talloc_free(guidstr);
MP_VERBOSE(ao, "\n");
p->device_index++;
return TRUE;
}
static void EnumDevs(struct ao *ao)
{
struct priv *p = ao->priv;
p->device_index = 0;
p->device_num = p->cfg_device;
HRESULT (WINAPI *OurDirectSoundEnumerate)(LPDSENUMCALLBACKA, LPVOID);
OurDirectSoundEnumerate = (void *)GetProcAddress(p->hdsound_dll,
"DirectSoundEnumerateA");
if (OurDirectSoundEnumerate == NULL) {
MP_ERR(ao, "GetProcAddress FAILED\n");
return;
}
// Enumerate all directsound p->devices
MP_VERBOSE(ao, "Output Devices:\n");
OurDirectSoundEnumerate(DirectSoundEnum, ao);
}
static int LoadDirectSound(struct ao *ao)
{
struct priv *p = ao->priv;
// initialize directsound
p->hdsound_dll = LoadLibrary("DSOUND.DLL");
if (p->hdsound_dll == NULL) {
MP_ERR(ao, "cannot load DSOUND.DLL\n");
return 0;
}
return 1;
}
static void list_devs(struct ao *ao, struct ao_device_list *list)
{
struct priv *p = ao->priv;
bool need_init = !p->hdsound_dll;
if (need_init && !LoadDirectSound(ao))
return;
p->listing = list;
EnumDevs(ao);
p->listing = NULL;
if (need_init)
UninitDirectSound(ao);
}
/**
\brief initilize direct sound
@@ -262,23 +188,34 @@ static int InitDirectSound(struct ao *ao)
DSCAPS dscaps;
if (!LoadDirectSound(ao))
return 0;
// initialize directsound
HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
OurDirectSoundCreate =
(void *)GetProcAddress(p->hdsound_dll, "DirectSoundCreate");
HRESULT (WINAPI *OurDirectSoundEnumerate)(LPDSENUMCALLBACKA, LPVOID);
p->device_index = 0;
p->device_num = p->cfg_device;
if (OurDirectSoundCreate == NULL) {
p->hdsound_dll = LoadLibrary("DSOUND.DLL");
if (p->hdsound_dll == NULL) {
MP_ERR(ao, "cannot load DSOUND.DLL\n");
return 0;
}
OurDirectSoundCreate = (void *)GetProcAddress(p->hdsound_dll,
"DirectSoundCreate");
OurDirectSoundEnumerate = (void *)GetProcAddress(p->hdsound_dll,
"DirectSoundEnumerateA");
if (OurDirectSoundCreate == NULL || OurDirectSoundEnumerate == NULL) {
MP_ERR(ao, "GetProcAddress FAILED\n");
FreeLibrary(p->hdsound_dll);
return 0;
}
EnumDevs(ao);
// Enumerate all directsound p->devices
MP_VERBOSE(ao, "Output Devices:\n");
OurDirectSoundEnumerate(DirectSoundEnum, ao);
// Create the direct sound object
if (FAILED(OurDirectSoundCreate((p->device_num > 0) ? &p->device : NULL,
if (FAILED(OurDirectSoundCreate((p->device_num) ? &p->device : NULL,
&p->hds, NULL)))
{
MP_ERR(ao, "cannot create a DirectSound device\n");
@@ -676,12 +613,12 @@ static int play(struct ao *ao, void **data, int samples, int flags)
\brief get the delay between the first and last sample in the buffer
\return delay in seconds
*/
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
struct priv *p = ao->priv;
int space = check_free_buffer_size(ao);
return (p->buffer_size - space) / (double)ao->bps;
return (float)(p->buffer_size - space) / (float)ao->bps;
}
#define OPT_BASE_STRUCT struct priv
@@ -698,10 +635,9 @@ const struct ao_driver audio_out_dsound = {
.pause = audio_pause,
.resume = audio_resume,
.reset = reset,
.list_devs = list_devs,
.priv_size = sizeof(struct priv),
.options = (const struct m_option[]) {
OPT_INT("device", cfg_device, 0, OPTDEF_INT(-1)),
OPT_INT("device", cfg_device, 0),
OPT_INTRANGE("buffersize", cfg_buffersize, 0, 1, 10000, OPTDEF_INT(200)),
{0}
},
+4 -13
View File
@@ -56,8 +56,6 @@ struct priv {
AVRational worst_time_base;
int worst_time_base_is_stream;
bool shutdown;
};
static void select_format(struct ao *ao, AVCodec *codec)
@@ -176,7 +174,6 @@ static int init(struct ao *ao)
fail:
pthread_mutex_unlock(&ao->encode_lavc_ctx->lock);
ac->shutdown = true;
return -1;
}
@@ -187,9 +184,6 @@ static void uninit(struct ao *ao)
struct priv *ac = ao->priv;
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
if (!ac || ac->shutdown)
return;
pthread_mutex_lock(&ectx->lock);
if (!encode_lavc_start(ectx)) {
@@ -207,8 +201,6 @@ static void uninit(struct ao *ao)
}
pthread_mutex_unlock(&ectx->lock);
ac->shutdown = true;
}
// return: how many bytes can be played without blocking
@@ -242,9 +234,8 @@ static int encode(struct ao *ao, double apts, void **data)
frame->format = af_to_avformat(ao->format);
frame->nb_samples = ac->aframesize;
size_t num_planes = af_fmt_is_planar(ao->format) ? ao->channels.num : 1;
assert(num_planes <= AV_NUM_DATA_POINTERS);
for (int n = 0; n < num_planes; n++)
assert(ao->channels.num <= AV_NUM_DATA_POINTERS);
for (int n = 0; n < ao->channels.num; n++)
frame->extended_data[n] = data[n];
frame->linesize[0] = frame->nb_samples * ao->sstride;
@@ -354,12 +345,12 @@ static int play(struct ao *ao, void **data, int samples, int flags)
size_t num_planes = af_fmt_is_planar(ao->format) ? ao->channels.num : 1;
void *tempdata = NULL;
void *padded[MP_NUM_CHANNELS];
if ((flags & AOPLAY_FINAL_CHUNK) && (samples % ac->aframesize)) {
tempdata = talloc_new(NULL);
size_t bytelen = samples * ao->sstride;
size_t extralen = (ac->aframesize - 1) * ao->sstride;
void *padded[MP_NUM_CHANNELS];
for (int n = 0; n < num_planes; n++) {
padded[n] = talloc_size(tempdata, bytelen + extralen);
memcpy(padded[n], data[n], bytelen);
@@ -439,7 +430,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
outpts += encode_lavc_getoffset(ectx, ac->stream);
while (samples - bufpos >= ac->aframesize) {
void *start[MP_NUM_CHANNELS] = {0};
void *start[MP_NUM_CHANNELS];
for (int n = 0; n < num_planes; n++)
start[n] = (char *)data[n] + bufpos * ao->sstride;
encode(ao, outpts + bufpos / (double) ao->samplerate, start);
+1 -1
View File
@@ -176,7 +176,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
return accepted;
}
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
struct priv *priv = ao->priv;
+3 -18
View File
@@ -101,17 +101,6 @@ static int validate_device_opt(struct mp_log *log, const m_option_t *opt,
return 0;
}
static void list_devs(struct ao *ao, struct ao_device_list *list)
{
if (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") != AL_TRUE)
return;
const char *devs = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
while (devs && *devs) {
ao_device_list_add(list, ao, &(struct ao_device_desc){devs, devs});
devs = devs + strlen(devs) + 1;
}
}
struct speaker {
int id;
float pos[3];
@@ -162,10 +151,7 @@ static int init(struct ao *ao)
goto err_out;
}
}
char *dev_name = p->cfg_device;
if (!dev_name || !dev_name[0])
dev_name = ao->device;
dev = alcOpenDevice(dev_name && dev_name[0] ? dev_name : NULL);
dev = alcOpenDevice(p->cfg_device && p->cfg_device[0] ? p->cfg_device : NULL);
if (!dev) {
MP_FATAL(ao, "could not open device\n");
goto err_out;
@@ -293,12 +279,12 @@ static int play(struct ao *ao, void **data, int samples, int flags)
return num * CHUNK_SAMPLES;
}
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
ALint queued;
unqueue_buffers();
alGetSourcei(sources[0], AL_BUFFERS_QUEUED, &queued);
return queued * CHUNK_SAMPLES / (double)ao->samplerate;
return queued * CHUNK_SAMPLES / (float)ao->samplerate;
}
#define OPT_BASE_STRUCT struct priv
@@ -316,7 +302,6 @@ const struct ao_driver audio_out_openal = {
.resume = audio_resume,
.reset = reset,
.drain = drain,
.list_devs = list_devs,
.priv_size = sizeof(struct priv),
.options = (const struct m_option[]) {
OPT_STRING_VALIDATE("device", cfg_device, 0, validate_device_opt),
+6 -21
View File
@@ -322,8 +322,7 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
#endif
if (AF_FORMAT_IS_IEC61937(format)) {
if (ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &samplerate) == -1)
goto fail;
ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &samplerate);
// Probably could be fixed by setting number of channels; needs testing.
if (channels.num != 2) {
MP_ERR(ao, "Format %s not implemented.\n", af_fmt_to_str(format));
@@ -375,8 +374,7 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
MP_VERBOSE(ao, "using %d channels (requested: %d)\n",
channels.num, reqchannels);
// set rate
if (ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &samplerate) == -1)
goto fail;
ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &samplerate);
MP_VERBOSE(ao, "using %d Hz samplerate\n", samplerate);
}
@@ -548,7 +546,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
}
// return: delay in seconds between first and last sample in buffer
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
struct priv *p = ao->priv;
if (p->audio_fd < 0) {
@@ -562,18 +560,18 @@ static double get_delay(struct ao *ao)
#ifdef SNDCTL_DSP_GETODELAY
int r = 0;
if (ioctl(p->audio_fd, SNDCTL_DSP_GETODELAY, &r) != -1)
return r / (double)ao->bps;
return ((float)r) / (float)ao->bps;
#endif
p->audio_delay_method = 1; // fallback if not supported
}
if (p->audio_delay_method == 1) {
audio_buf_info zz = {0};
if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &zz) != -1) {
return (p->buffersize - zz.bytes) / (double)ao->bps;
return ((float)(p->buffersize - zz.bytes)) / (float)ao->bps;
}
p->audio_delay_method = 0; // fallback if not supported
}
return p->buffersize / (double)ao->bps;
return ((float)p->buffersize) / (float)ao->bps;
}
@@ -618,17 +616,6 @@ static void audio_resume(struct ao *ao)
ao_play_silence(ao, p->prepause_samples);
}
static int audio_wait(struct ao *ao, pthread_mutex_t *lock)
{
struct priv *p = ao->priv;
struct pollfd fd = {.fd = p->audio_fd, .events = POLLOUT};
int r = ao_wait_poll(ao, &fd, 1, lock);
if (fd.revents & (POLLERR | POLLNVAL))
return -1;
return r;
}
#define OPT_BASE_STRUCT struct priv
const struct ao_driver audio_out_oss = {
@@ -644,8 +631,6 @@ const struct ao_driver audio_out_oss = {
.resume = audio_resume,
.reset = reset,
.drain = drain,
.wait = audio_wait,
.wakeup = ao_wakeup_poll,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.audio_fd = -1,
+17 -13
View File
@@ -72,35 +72,39 @@ static void fput32le(uint32_t val, FILE *fp)
static void write_wave_header(struct ao *ao, FILE *fp, uint64_t data_length)
{
bool use_waveex = true;
uint16_t fmt = ao->format == AF_FORMAT_FLOAT ? WAV_ID_FLOAT_PCM : WAV_ID_PCM;
uint32_t fmt_chunk_size = use_waveex ? 40 : 16;
int bits = af_fmt2bits(ao->format);
// Master RIFF chunk
fput32le(WAV_ID_RIFF, fp);
// RIFF chunk size: 'WAVE' + 'fmt ' + 4 + 40 +
// RIFF chunk size: 'WAVE' + 'fmt ' + 4 + fmt_chunk_size +
// data chunk hdr (8) + data length
fput32le(12 + 40 + 8 + data_length, fp);
fput32le(12 + fmt_chunk_size + 8 + data_length, fp);
fput32le(WAV_ID_WAVE, fp);
// Format chunk
fput32le(WAV_ID_FMT, fp);
fput32le(40, fp);
fput16le(WAV_ID_FORMAT_EXTENSIBLE, fp);
fput32le(fmt_chunk_size, fp);
fput16le(use_waveex ? WAV_ID_FORMAT_EXTENSIBLE : fmt, fp);
fput16le(ao->channels.num, fp);
fput32le(ao->samplerate, fp);
fput32le(ao->bps, fp);
fput16le(ao->channels.num * (bits / 8), fp);
fput16le(bits, fp);
// Extension chunk
fput16le(22, fp);
fput16le(bits, fp);
fput32le(mp_chmap_to_waveext(&ao->channels), fp);
// 2 bytes format + 14 bytes guid
fput32le(fmt, fp);
fput32le(0x00100000, fp);
fput32le(0xAA000080, fp);
fput32le(0x719B3800, fp);
if (use_waveex) {
// Extension chunk
fput16le(22, fp);
fput16le(bits, fp);
fput32le(mp_chmap_to_waveext(&ao->channels), fp);
// 2 bytes format + 14 bytes guid
fput32le(fmt, fp);
fput32le(0x00100000, fp);
fput32le(0xAA000080, fp);
fput32le(0x719B3800, fp);
}
// Data chunk
fput32le(WAV_ID_DATA, fp);
+2 -24
View File
@@ -125,24 +125,6 @@ static int validate_device_opt(struct mp_log *log, const m_option_t *opt,
return 0;
}
static void list_devs(struct ao *ao, struct ao_device_list *list)
{
if (Pa_Initialize() < 0)
return;
int count = Pa_GetDeviceCount(); // negative on error
for (int n = 0; n < count; n++) {
const PaDeviceInfo* info = Pa_GetDeviceInfo(n);
char desc[1024];
snprintf(desc, sizeof(desc), "%s, %d channels, latency: %.2f "
"ms, sample rate: %.0f", info->name,
info->maxOutputChannels,
info->defaultHighOutputLatency * 1000,
info->defaultSampleRate);
ao_device_list_add(list, ao, &(struct ao_device_desc){info->name, desc});
}
Pa_Terminate();
}
static int stream_callback(const void *input,
void *output_v,
unsigned long frameCount,
@@ -185,11 +167,8 @@ static int init(struct ao *ao)
return -1;
int pa_device = Pa_GetDefaultOutputDevice();
char *dev_name = priv->cfg_device;
if (!dev_name || !dev_name[0])
dev_name = ao->device;
if (dev_name && dev_name[0])
pa_device = find_device(ao->log, dev_name);
if (priv->cfg_device && priv->cfg_device[0])
pa_device = find_device(ao->log, priv->cfg_device);
if (pa_device == paNoDevice)
goto error_exit;
@@ -267,7 +246,6 @@ const struct ao_driver audio_out_portaudio = {
.uninit = uninit,
.reset = reset,
.resume = resume,
.list_devs = list_devs,
.priv_size = sizeof(struct priv),
.options = (const struct m_option[]) {
OPT_STRING_VALIDATE("device", cfg_device, 0, validate_device_opt),
+41 -100
View File
@@ -35,6 +35,8 @@
#include "ao.h"
#include "internal.h"
#define PULSE_CLIENT_NAME "mpv"
#define VOL_PA2MP(v) ((v) * 100 / PA_VOLUME_NORM)
#define VOL_MP2PA(v) ((v) * PA_VOLUME_NORM / 100)
@@ -285,11 +287,14 @@ static void uninit(struct ao *ao)
pthread_mutex_destroy(&priv->wakeup_lock);
}
static int pa_init_boilerplate(struct ao *ao)
static int init(struct ao *ao)
{
struct pa_channel_map map;
pa_proplist *proplist = NULL;
pa_format_info *format = NULL;
struct priv *priv = ao->priv;
char *host = priv->cfg_host && priv->cfg_host[0] ? priv->cfg_host : NULL;
bool locked = false;
char *sink = priv->cfg_sink && priv->cfg_sink[0] ? priv->cfg_sink : NULL;
pthread_mutex_init(&priv->wakeup_lock, NULL);
pthread_cond_init(&priv->wakeup, NULL);
@@ -299,15 +304,8 @@ static int pa_init_boilerplate(struct ao *ao)
goto fail;
}
if (pa_threaded_mainloop_start(priv->mainloop) < 0)
goto fail;
pa_threaded_mainloop_lock(priv->mainloop);
locked = true;
if (!(priv->context = pa_context_new(pa_threaded_mainloop_get_api(
priv->mainloop), ao->client_name)))
{
priv->mainloop), PULSE_CLIENT_NAME))) {
MP_ERR(ao, "Failed to allocate context\n");
goto fail;
}
@@ -323,47 +321,17 @@ static int pa_init_boilerplate(struct ao *ao)
if (pa_context_connect(priv->context, host, 0, NULL) < 0)
goto fail;
/* Wait until the context is ready */
while (1) {
int state = pa_context_get_state(priv->context);
if (state == PA_CONTEXT_READY)
break;
if (!PA_CONTEXT_IS_GOOD(state))
goto fail;
pa_threaded_mainloop_wait(priv->mainloop);
}
pa_threaded_mainloop_unlock(priv->mainloop);
return 0;
fail:
if (locked)
pa_threaded_mainloop_unlock(priv->mainloop);
if (priv->context) {
pa_threaded_mainloop_lock(priv->mainloop);
if (!(pa_context_errno(priv->context) == PA_ERR_CONNECTIONREFUSED
&& ao->probing))
GENERIC_ERR_MSG("Init failed");
pa_threaded_mainloop_unlock(priv->mainloop);
}
uninit(ao);
return -1;
}
static int init(struct ao *ao)
{
struct pa_channel_map map;
pa_proplist *proplist = NULL;
pa_format_info *format = NULL;
struct priv *priv = ao->priv;
char *sink = priv->cfg_sink && priv->cfg_sink[0] ? priv->cfg_sink : ao->device;
if (pa_init_boilerplate(ao) < 0)
return -1;
pa_threaded_mainloop_lock(priv->mainloop);
if (pa_threaded_mainloop_start(priv->mainloop) < 0)
goto unlock_and_fail;
/* Wait until the context is ready */
pa_threaded_mainloop_wait(priv->mainloop);
if (pa_context_get_state(priv->context) != PA_CONTEXT_READY)
goto unlock_and_fail;
if (!(format = pa_format_info_new()))
goto unlock_and_fail;
@@ -394,7 +362,8 @@ static int init(struct ao *ao)
goto unlock_and_fail;
}
(void)pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "video");
(void)pa_proplist_sets(proplist, PA_PROP_MEDIA_ICON_NAME, ao->client_name);
(void)pa_proplist_sets(proplist, PA_PROP_MEDIA_ICON_NAME,
PULSE_CLIENT_NAME);
pa_format_info_set_rate(format, ao->samplerate);
pa_format_info_set_channels(format, ao->channels.num);
@@ -412,15 +381,20 @@ static int init(struct ao *ao)
pa_proplist_free(proplist);
proplist = NULL;
pa_sample_spec ss;
pa_channel_map map2;
if (pa_format_info_to_sample_spec(format, &ss, &map2) < 0)
goto unlock_and_fail;
pa_stream_set_state_callback(priv->stream, stream_state_cb, ao);
pa_stream_set_write_callback(priv->stream, stream_request_cb, ao);
pa_stream_set_latency_update_callback(priv->stream,
stream_latency_update_cb, ao);
int buf_size = af_fmt_seconds_to_bytes(ao->format, priv->cfg_buffer / 1000.0,
ao->channels.num, ao->samplerate);
pa_buffer_attr bufattr = {
.maxlength = -1,
.tlength = buf_size > 0 ? buf_size : (uint32_t)-1,
.tlength = priv->cfg_buffer > 0 ?
pa_usec_to_bytes(priv->cfg_buffer * 1000, &ss) : (uint32_t)-1,
.prebuf = -1,
.minreq = -1,
.fragsize = -1,
@@ -444,19 +418,23 @@ static int init(struct ao *ao)
pa_threaded_mainloop_wait(priv->mainloop);
}
if (pa_stream_is_suspended(priv->stream)) {
MP_ERR(ao, "The stream is suspended. Bailing out.\n");
goto unlock_and_fail;
}
pa_threaded_mainloop_unlock(priv->mainloop);
return 0;
unlock_and_fail:
pa_threaded_mainloop_unlock(priv->mainloop);
if (priv->mainloop)
pa_threaded_mainloop_unlock(priv->mainloop);
fail:
if (format)
pa_format_info_free(format);
if (priv->context) {
if (!(pa_context_errno(priv->context) == PA_ERR_CONNECTIONREFUSED
&& ao->probing))
GENERIC_ERR_MSG("Init failed");
}
if (proplist)
pa_proplist_free(proplist);
@@ -530,7 +508,7 @@ static int get_space(struct ao *ao)
return space / ao->sstride;
}
static double get_delay_hackfixed(struct ao *ao)
static float get_delay_hackfixed(struct ao *ao)
{
/* This code basically does what pa_stream_get_latency() _should_
* do, but doesn't due to multiple known bugs in PulseAudio (at
@@ -585,7 +563,7 @@ static double get_delay_hackfixed(struct ao *ao)
return latency / 1e6;
}
static double get_delay_pulse(struct ao *ao)
static float get_delay_pulse(struct ao *ao)
{
struct priv *priv = ao->priv;
pa_usec_t latency = (pa_usec_t) -1;
@@ -603,7 +581,7 @@ static double get_delay_pulse(struct ao *ao)
}
// Return the current latency in seconds
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
struct priv *priv = ao->priv;
if (priv->cfg_latency_hacks) {
@@ -725,42 +703,6 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
}
}
struct sink_cb_ctx {
struct ao *ao;
struct ao_device_list *list;
};
static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *ud)
{
struct sink_cb_ctx *ctx = ud;
struct priv *priv = ctx->ao->priv;
if (eol) {
pa_threaded_mainloop_signal(priv->mainloop, 0); // wakeup waitop()
return;
}
struct ao_device_desc entry = {.name = i->name, .desc = i->description};
ao_device_list_add(ctx->list, ctx->ao, &entry);
}
static void list_devs(struct ao *ao, struct ao_device_list *list)
{
struct priv *priv = ao->priv;
bool need_uninit = !priv->mainloop;
struct sink_cb_ctx ctx = {ao, list};
if (need_uninit && pa_init_boilerplate(ao) < 0)
return;
pa_threaded_mainloop_lock(priv->mainloop);
waitop(priv, pa_context_get_sink_info_list(priv->context, sink_info_cb, &ctx));
if (need_uninit)
uninit(ao);
}
#define OPT_BASE_STRUCT struct priv
const struct ao_driver audio_out_pulse = {
@@ -778,7 +720,6 @@ const struct ao_driver audio_out_pulse = {
.drain = drain,
.wait = wait_audio,
.wakeup = wakeup,
.list_devs = list_devs,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.cfg_buffer = 250,
@@ -787,7 +728,7 @@ const struct ao_driver audio_out_pulse = {
.options = (const struct m_option[]) {
OPT_STRING("host", cfg_host, 0),
OPT_STRING("sink", cfg_sink, 0),
OPT_CHOICE_OR_INT("buffer", cfg_buffer, 0, 1, 2000, ({"native", 0})),
OPT_CHOICE_OR_INT("buffer", cfg_buffer, 0, 1, 2000, ({"native", -1})),
OPT_FLAG("latency-hacks", cfg_latency_hacks, 0),
{0}
},
+1 -1
View File
@@ -153,7 +153,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
return rsd_write(priv->rd, data[0], samples * ao->sstride) / ao->sstride;
}
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
struct priv *priv = ao->priv;
return rsd_delay_ms(priv->rd) / 1000.0;
+1 -1
View File
@@ -38,7 +38,7 @@ struct priv
float buflen;
};
static const int fmtmap[][2] = {
static int fmtmap[][2] = {
{AF_FORMAT_U8, AUDIO_U8},
{AF_FORMAT_S8, AUDIO_S8},
{AF_FORMAT_U16, AUDIO_U16SYS},
+3 -5
View File
@@ -111,8 +111,7 @@ static int init(struct ao *ao)
struct af_to_par {
int format, bits, sig;
};
static const struct af_to_par af_to_par[] = {
} static const af_to_par[] = {
{AF_FORMAT_U8, 8, 0},
{AF_FORMAT_S8, 8, 1},
{AF_FORMAT_U16, 16, 0},
@@ -121,8 +120,7 @@ static int init(struct ao *ao)
{AF_FORMAT_S24, 24, 1},
{AF_FORMAT_U32, 32, 0},
{AF_FORMAT_S32, 32, 1},
};
const struct af_to_par *ap;
}, *ap;
int i;
p->hdl = sio_open(p->dev, SIO_PLAY, 1);
@@ -282,7 +280,7 @@ static int get_space(struct ao *ao)
/*
* return: delay in seconds between first and last sample in buffer
*/
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
struct priv *p = ao->priv;
+46 -63
View File
@@ -36,6 +36,11 @@
#include "osdep/timer.h"
#include "osdep/io.h"
#define EXIT_ON_ERROR(hres) \
do { if (FAILED(hres)) { goto exit_label; } } while(0)
#define SAFE_RELEASE(unk, release) \
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
static double get_device_delay(struct wasapi_state *state) {
UINT64 sample_count = atomic_load(&state->sample_count);
UINT64 position, qpc_position;
@@ -45,8 +50,7 @@ static double get_device_delay(struct wasapi_state *state) {
case S_OK: case S_FALSE:
break;
default:
MP_ERR(state, "IAudioClock::GetPosition returned %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
MP_ERR(state, "IAudioClock::GetPosition returned %s\n", wasapi_explain_err(hr));
}
LARGE_INTEGER qpc_count;
@@ -61,7 +65,7 @@ static double get_device_delay(struct wasapi_state *state) {
double diff = sample_count - position;
double delay = diff / state->format.Format.nSamplesPerSec;
MP_TRACE(state, "Device delay: %g samples (%g ms)\n", diff, delay * 1000);
MP_TRACE(state, "device delay: %g samples (%g ms)\n", diff, delay * 1000);
return delay;
}
@@ -79,7 +83,6 @@ static void thread_feed(struct ao *ao)
EXIT_ON_ERROR(hr);
frame_count -= padding;
MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n", frame_count, padding);
}
BYTE *pData;
@@ -100,10 +103,7 @@ static void thread_feed(struct ao *ao)
return;
exit_label:
MP_ERR(state, "Error feeding audio: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
MP_VERBOSE(ao, "Requesting ao reload\n");
ao_request_reload(ao);
MP_ERR(state, "thread_feed fails with %"PRIx32"!\n", (uint32_t)hr);
return;
}
@@ -113,27 +113,20 @@ static DWORD __stdcall ThreadLoop(void *lpParameter)
if (!ao || !ao->priv)
return -1;
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
int thread_ret;
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
state->init_ret = wasapi_thread_init(ao);
SetEvent(state->init_done);
if (state->init_ret != S_OK) {
thread_ret = -1;
if (wasapi_thread_init(ao))
goto exit_label;
}
DWORD waitstatus;
MSG msg;
DWORD waitstatus = WAIT_FAILED;
HANDLE playcontrol[] =
{state->hUninit, state->hFeed, state->hForceFeed, NULL};
MP_DBG(ao, "Entering dispatch loop\n");
MP_VERBOSE(ao, "Entering dispatch loop!\n");
while (1) { /* watch events */
waitstatus = MsgWaitForMultipleObjects(3, playcontrol, FALSE, INFINITE,
QS_POSTMESSAGE | QS_SENDMESSAGE);
switch (waitstatus) {
case WAIT_OBJECT_0: /*shutdown*/
thread_ret = 0;
wasapi_thread_uninit(state);
goto exit_label;
case (WAIT_OBJECT_0 + 1): /* feed */
thread_feed(ao);
@@ -143,52 +136,51 @@ static DWORD __stdcall ThreadLoop(void *lpParameter)
SetEvent(state->hFeedDone);
break;
case (WAIT_OBJECT_0 + 3): /* messages to dispatch (COM marshalling) */
wasapi_dispatch();
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
DispatchMessage(&msg);
}
break;
default:
MP_ERR(ao, "Unhandled case in thread loop");
thread_ret = -1;
goto exit_label;
case WAIT_FAILED: /* ??? */
return -1;
}
}
exit_label:
wasapi_thread_uninit(ao);
CoUninitialize();
return thread_ret;
/* dispatch any possible pending messages */
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
DispatchMessage(&msg);
}
return state->init_ret;
}
static void closehandles(struct ao *ao)
{
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
if (state->init_done) CloseHandle(state->init_done);
if (state->hUninit) CloseHandle(state->hUninit);
if (state->hFeed) CloseHandle(state->hFeed);
if (state->hForceFeed) CloseHandle(state->hForceFeed);
if (state->hFeedDone) CloseHandle(state->hFeedDone);
if (state->threadLoop) CloseHandle(state->threadLoop);
if (state->init_done)
CloseHandle(state->init_done);
if (state->hUninit)
CloseHandle(state->hUninit);
if (state->hFeed)
CloseHandle(state->hFeed);
}
static void uninit(struct ao *ao)
{
MP_DBG(ao, "Uninit wasapi\n");
MP_VERBOSE(ao, "uninit!\n");
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
wasapi_release_proxies(state);
SetEvent(state->hUninit);
/* wait up to 10 seconds */
if (WaitForSingleObject(state->threadLoop, 10000) == WAIT_TIMEOUT){
MP_ERR(ao, "Audio loop thread refuses to abort");
return;
}
if (WaitForSingleObject(state->threadLoop, 10000) == WAIT_TIMEOUT)
MP_ERR(ao, "audio loop thread refuses to abort!");
if (state->VistaBlob.hAvrt)
FreeLibrary(state->VistaBlob.hAvrt);
closehandles(ao);
MP_DBG(ao, "Uninit wasapi done\n");
MP_VERBOSE(ao, "uninit END!\n");
}
static int init(struct ao *ao)
{
MP_DBG(ao, "Init wasapi\n");
MP_VERBOSE(ao, "init!\n");
ao->format = af_fmt_from_planar(ao->format);
struct mp_chmap_sel sel = {0};
mp_chmap_sel_add_waveext(&sel);
@@ -199,7 +191,7 @@ static int init(struct ao *ao)
wasapi_fill_VistaBlob(state);
if (state->opt_list) {
wasapi_enumerate_devices(state->log, NULL, NULL);
wasapi_enumerate_devices(state->log);
}
if (state->opt_exclusive) {
@@ -220,26 +212,23 @@ static int init(struct ao *ao)
/* failed to init events */
return -1;
}
state->init_ret = E_FAIL;
state->init_ret = -1;
state->threadLoop = (HANDLE)CreateThread(NULL, 0, &ThreadLoop, ao, 0, NULL);
if (!state->threadLoop) {
/* failed to init thread */
MP_ERR(ao, "Failed to create thread\n");
MP_ERR(ao, "fail to create thread!\n");
return -1;
}
WaitForSingleObject(state->init_done, INFINITE); /* wait on init complete */
if (state->init_ret != S_OK) {
if (!ao->probing)
MP_ERR(ao, "Received failure from audio thread\n");
uninit(ao);
return -1;
}
if (state->init_ret) {
if (!ao->probing) {
MP_ERR(ao, "thread_init failed!\n");
}
} else
MP_VERBOSE(ao, "Init Done!\n");
wasapi_setup_proxies(state);
MP_DBG(ao, "Init wasapi done\n");
return 0;
return state->init_ret;
}
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
@@ -260,7 +249,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
/* check to see if user manually changed volume through mixer;
this information is used in exclusive mode for restoring the mixer volume on uninit */
if (state->audio_volume != state->previous_volume) {
MP_VERBOSE(state, "Mixer difference: %.2g now, expected %.2g\n",
MP_VERBOSE(state, "mixer difference: %.2g now, expected %.2g\n",
state->audio_volume, state->previous_volume);
state->initial_volume = state->audio_volume;
}
@@ -340,11 +329,6 @@ static void audio_resume(struct ao *ao)
IAudioClient_Start(state->pAudioClientProxy);
}
static void list_devs(struct ao *ao, struct ao_device_list *list)
{
wasapi_enumerate_devices(mp_null_log, ao, list);
}
#define OPT_BASE_STRUCT struct wasapi_state
const struct ao_driver audio_out_wasapi = {
@@ -353,9 +337,8 @@ const struct ao_driver audio_out_wasapi = {
.init = init,
.uninit = uninit,
.control = control,
.reset = audio_reset,
//.reset = audio_reset, <- doesn't wait for audio callback to return
.resume = audio_resume,
.list_devs = list_devs,
.priv_size = sizeof(wasapi_state),
.options = (const struct m_option[]) {
OPT_FLAG("exclusive", opt_exclusive, 0),
+5 -19
View File
@@ -30,26 +30,12 @@
#include "osdep/atomics.h"
typedef struct change_notify {
IMMNotificationClient client; /* this must be first in the structure! */
LPWSTR monitored; /* Monitored device */
struct ao *ao;
} change_notify;
HRESULT wasapi_change_init(struct ao* ao);
void wasapi_change_uninit(struct ao* ao);
#define EXIT_ON_ERROR(hres) \
do { if (FAILED(hres)) { goto exit_label; } } while(0)
#define SAFE_RELEASE(unk, release) \
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
typedef struct wasapi_state {
struct mp_log *log;
HANDLE threadLoop;
/* Init phase */
HRESULT init_ret;
int init_ret;
HANDLE init_done;
int share_mode;
@@ -63,6 +49,10 @@ typedef struct wasapi_state {
/* Buffers */
size_t buffer_block_size; /* Size of each block in bytes */
REFERENCE_TIME
minRequestedDuration; /* minimum wasapi buffer block size, in 100-nanosecond units */
REFERENCE_TIME
defaultRequestedDuration; /* default wasapi default block size, in 100-nanosecond units */
UINT32 bufferFrameCount; /* wasapi buffer block size, number of frames, frame size at format.nBlockAlign */
/* WASAPI handles, owned by other thread */
@@ -72,8 +62,6 @@ typedef struct wasapi_state {
ISimpleAudioVolume *pAudioVolume;
IAudioEndpointVolume *pEndpointVolume;
IAudioSessionControl *pSessionControl;
IMMDeviceEnumerator *pEnumerator;
HANDLE hFeed; /* wasapi event */
HANDLE hForceFeed; /* forces writing a buffer (e.g. before audio_resume) */
HANDLE hFeedDone; /* set only after a hForceFeed */
@@ -112,8 +100,6 @@ typedef struct wasapi_state {
HANDLE (WINAPI *pAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
WINBOOL (WINAPI *pAvRevertMmThreadCharacteristics)(HANDLE);
} VistaBlob;
change_notify change;
} wasapi_state;
#endif
-284
View File
@@ -1,284 +0,0 @@
/*
* This file is part of mpv.
*
* Original author: Jonathan Yong <10walls@gmail.com>
*
* mpv is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#define COBJMACROS 1
#define _WIN32_WINNT 0x600
#include <initguid.h>
#include <audioclient.h>
#include <endpointvolume.h>
#include <mmdeviceapi.h>
#include <wchar.h>
#include <stdlib.h>
#include "ao_wasapi.h"
#include "ao_wasapi_utils.h"
static int GUID_compare(const GUID *l, const GUID *r)
{
unsigned int i;
if (l->Data1 != r->Data1) return 1;
if (l->Data2 != r->Data2) return 1;
if (l->Data3 != r->Data3) return 1;
for (i = 0; i < 8; i++) {
if (l->Data4[i] != r->Data4[i]) return 1;
}
return 0;
}
static int PKEY_compare(const PROPERTYKEY *l, const PROPERTYKEY *r)
{
if (GUID_compare(&l->fmtid, &r->fmtid)) return 1;
if (l->pid != r->pid) return 1;
return 0;
}
static char *GUID_to_str_buf(char *buf, size_t buf_size, const GUID *guid)
{
snprintf(buf, buf_size,
"{%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x}",
(unsigned) guid->Data1, guid->Data2, guid->Data3,
guid->Data4[0], guid->Data4[1],
guid->Data4[2], guid->Data4[3],
guid->Data4[4], guid->Data4[5],
guid->Data4[6], guid->Data4[7]);
return buf;
}
static char *PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey)
{
buf = GUID_to_str_buf(buf, buf_size, &pkey->fmtid);
size_t guid_len = strnlen(buf, buf_size);
snprintf(buf + guid_len, buf_size - guid_len, ",%"PRIu32, (uint32_t)pkey->pid );
return buf;
}
#define PKEY_to_str(pkey) PKEY_to_str_buf((char[42]){0}, 42, (pkey))
static char* ERole_to_str(ERole role)
{
switch(role){
case eConsole: return "console";
case eMultimedia: return "multimedia";
case eCommunications: return "communications";
default: return "<Unknown>";
}
}
static char* EDataFlow_to_str(EDataFlow flow)
{
switch(flow){
case eRender: return "render";
case eCapture: return "capture";
case eAll: return "all";
default: return "<Unknown>";
}
}
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_QueryInterface(
IMMNotificationClient* This, REFIID riid, void **ppvObject)
{
/* Compatible with IMMNotificationClient and IUnknown */
if (!GUID_compare(&IID_IMMNotificationClient, riid) ||
!GUID_compare(&IID_IUnknown, riid)) {
*ppvObject = (void *)This;
return S_OK;
} else {
*ppvObject = NULL;
return E_NOINTERFACE;
}
}
/* these are required, but not actually used */
static ULONG STDMETHODCALLTYPE sIMMNotificationClient_AddRef(
IMMNotificationClient *This)
{
return 1;
}
/* MSDN says it should free itself, but we're static */
static ULONG STDMETHODCALLTYPE sIMMNotificationClient_Release(
IMMNotificationClient *This)
{
return 1;
}
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceStateChanged(
IMMNotificationClient *This,
LPCWSTR pwstrDeviceId,
DWORD dwNewState)
{
change_notify *change = (change_notify *)This;
struct ao *ao = change->ao;
if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)){
switch (dwNewState) {
case DEVICE_STATE_DISABLED:
case DEVICE_STATE_NOTPRESENT:
case DEVICE_STATE_UNPLUGGED:
MP_VERBOSE(ao,
"OnDeviceStateChange triggered - requesting ao reload\n");
ao_request_reload(ao);
case DEVICE_STATE_ACTIVE:
default:
return S_OK;
}
}
return S_OK;
}
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceAdded(
IMMNotificationClient *This,
LPCWSTR pwstrDeviceId)
{
change_notify *change = (change_notify *)This;
struct ao *ao = change->ao;
MP_VERBOSE(ao, "OnDeviceAdded triggered\n");
if(pwstrDeviceId)
MP_VERBOSE(ao, "New device %S\n",pwstrDeviceId);
return S_OK;
}
/* maybe MPV can go over to the prefered device once it is plugged in? */
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceRemoved(
IMMNotificationClient *This,
LPCWSTR pwstrDeviceId)
{
change_notify *change = (change_notify *)This;
struct ao *ao = change->ao;
if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)) {
MP_VERBOSE(ao, "OnDeviceRemoved triggered - requesting ao reload\n");
ao_request_reload(ao);
}
return S_OK;
}
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDefaultDeviceChanged(
IMMNotificationClient *This,
EDataFlow flow,
ERole role,
LPCWSTR pwstrDeviceId)
{
change_notify *change = (change_notify *)This;
struct ao *ao = change->ao;
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered for role:%s flow:%s\n",
ERole_to_str(role), EDataFlow_to_str(flow));
if(pwstrDeviceId)
MP_VERBOSE(ao, "New default device %S\n", pwstrDeviceId);
/* don't care about "eCapture" or non-"eMultimedia" roles */
if ( flow == eCapture ||
role != eMultimedia ) return S_OK;
/* stay on the device the user specified */
if (state->opt_device) {
MP_VERBOSE(ao, "Staying on specified device \"%s\"", state->opt_device);
return S_OK;
}
/* don't reload if already on the new default */
if ( pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId) ){
MP_VERBOSE(ao, "Already using default device, no reload required\n");
return S_OK;
}
/* if we got here, we need to reload */
ao_request_reload(ao);
MP_VERBOSE(ao, "Requesting ao reload\n");
return S_OK;
}
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnPropertyValueChanged(
IMMNotificationClient *This,
LPCWSTR pwstrDeviceId,
const PROPERTYKEY key)
{
change_notify *change = (change_notify *)This;
struct ao *ao = change->ao;
if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)) {
MP_VERBOSE(ao, "OnPropertyValueChanged triggered\n");
MP_VERBOSE(ao, "Changed property: ");
if (!PKEY_compare(&PKEY_AudioEngine_DeviceFormat, &key)) {
MP_VERBOSE(change->ao,
"PKEY_AudioEngine_DeviceFormat - requesting ao reload\n");
ao_request_reload(change->ao);
} else {
MP_VERBOSE(ao, "%s\n", PKEY_to_str(&key));
}
}
return S_OK;
}
static CONST_VTBL IMMNotificationClientVtbl sIMMDeviceEnumeratorVtbl_vtbl = {
.QueryInterface = sIMMNotificationClient_QueryInterface,
.AddRef = sIMMNotificationClient_AddRef,
.Release = sIMMNotificationClient_Release,
.OnDeviceStateChanged = sIMMNotificationClient_OnDeviceStateChanged,
.OnDeviceAdded = sIMMNotificationClient_OnDeviceAdded,
.OnDeviceRemoved = sIMMNotificationClient_OnDeviceRemoved,
.OnDefaultDeviceChanged = sIMMNotificationClient_OnDefaultDeviceChanged,
.OnPropertyValueChanged = sIMMNotificationClient_OnPropertyValueChanged,
};
HRESULT wasapi_change_init(struct ao *ao)
{
MP_DBG(ao, "Setting up monitoring on playback device\n");
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
struct change_notify *change = &state->change;
/* COM voodoo to emulate c++ class */
change->client.lpVtbl = &sIMMDeviceEnumeratorVtbl_vtbl;
/* so the callbacks can access the ao */
change->ao = ao;
/* get the device string to compare with the pwstrDeviceId argument in callbacks */
HRESULT hr = IMMDevice_GetId(state->pDevice, &change->monitored);
EXIT_ON_ERROR(hr);
/* register the change notification client */
hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(
state->pEnumerator, (IMMNotificationClient *)change);
EXIT_ON_ERROR(hr);
MP_VERBOSE(state, "Monitoring changes in device: %S\n", state->change.monitored);
return hr;
exit_label:
MP_ERR(state, "Error setting up device change monitoring: %s\n",
wasapi_explain_err(hr));
wasapi_change_uninit(ao);
return hr;
}
void wasapi_change_uninit(struct ao *ao)
{
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
struct change_notify *change = &state->change;
if( state->pEnumerator && change->client.lpVtbl )
IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(
state->pEnumerator, (IMMNotificationClient *)change);
if (change->monitored) CoTaskMemFree(change->monitored);
}
+126 -232
View File
@@ -20,7 +20,6 @@
#define COBJMACROS 1
#define _WIN32_WINNT 0x600
#include <math.h>
#include <initguid.h>
#include <audioclient.h>
#include <endpointvolume.h>
@@ -31,10 +30,14 @@
#include "audio/format.h"
#include "osdep/io.h"
#include "osdep/timer.h"
#define MIXER_DEFAULT_LABEL L"mpv - video player"
#define EXIT_ON_ERROR(hres) \
do { if (FAILED(hres)) { goto exit_label; } } while(0)
#define SAFE_RELEASE(unk, release) \
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
#ifndef PKEY_Device_FriendlyName
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName,
0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20,
@@ -82,10 +85,6 @@ const char *wasapi_explain_err(const HRESULT hr)
#define E(x) case x : return # x ;
switch (hr) {
E(S_OK)
E(E_FAIL)
E(E_OUTOFMEMORY)
E(E_POINTER)
E(E_INVALIDARG)
E(AUDCLNT_E_NOT_INITIALIZED)
E(AUDCLNT_E_ALREADY_INITIALIZED)
E(AUDCLNT_E_WRONG_ENDPOINT_TYPE)
@@ -161,7 +160,7 @@ static int set_ao_format(struct wasapi_state *state,
WAVEFORMATEXTENSIBLE wformat)
{
if (wformat.SubFormat.Data1 != 1 && wformat.SubFormat.Data1 != 3) {
MP_ERR(ao, "Unknown SubFormat %"PRIu32"\n",
MP_ERR(ao, "unknown SubFormat %"PRIu32"\n",
(uint32_t)wformat.SubFormat.Data1);
return 0;
}
@@ -197,7 +196,7 @@ static int try_format(struct wasapi_state *state,
if (!af_format)
return 0;
MP_VERBOSE(ao, "Trying %dch %s @ %dhz\n",
MP_VERBOSE(ao, "trying %dch %s @ %dhz\n",
channels.num, af_fmt_to_str(af_format), samplerate);
union WAVEFMT u;
@@ -221,7 +220,7 @@ static int try_format(struct wasapi_state *state,
if (hr == S_FALSE) {
if (set_ao_format(state, ao, wformat)) {
MP_VERBOSE(ao, "Accepted as %dch %s @ %dhz\n",
MP_VERBOSE(ao, "accepted as %dch %s @ %dhz\n",
ao->channels.num, af_fmt_to_str(ao->format), ao->samplerate);
return 1;
@@ -284,7 +283,7 @@ static int try_passthrough(struct wasapi_state *state,
union WAVEFMT u;
u.extensible = &wformat;
MP_VERBOSE(ao, "Trying passthrough for %s...\n", af_fmt_to_str(ao->format));
MP_VERBOSE(ao, "trying passthrough for %s...\n", af_fmt_to_str(ao->format));
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
state->share_mode,
@@ -305,7 +304,7 @@ static int find_formats(struct ao *const ao)
if (try_passthrough(state, ao))
return 0;
MP_ERR(ao, "Couldn't use passthrough");
MP_ERR(ao, "couldn't use passthrough!");
if (!state->opt_exclusive)
MP_ERR(ao, " (try exclusive mode)");
MP_ERR(ao, "\n");
@@ -327,7 +326,7 @@ static int find_formats(struct ao *const ao)
return 0;
}
MP_WARN(ao, "Couldn't use default mix format\n");
MP_WARN(ao, "couldn't use default mix format!\n");
}
/* Exclusive mode, we have to guess. */
@@ -398,13 +397,13 @@ static int find_formats(struct ao *const ao)
bits = start_bits;
mp_chmap_from_channels(&ao->channels, 2);
} else {
MP_ERR(ao, "Couldn't find acceptable audio format\n");
MP_ERR(ao, "couldn't find acceptable audio format!\n");
return -1;
}
}
}
static HRESULT init_clock(struct wasapi_state *state) {
static int init_clock(struct wasapi_state *state) {
HRESULT hr;
hr = IAudioClient_GetService(state->pAudioClient,
@@ -418,17 +417,16 @@ static HRESULT init_clock(struct wasapi_state *state) {
atomic_store(&state->sample_count, 0);
MP_VERBOSE(state, "IAudioClock::GetFrequency gave a frequency of %"PRIu64".\n",
(uint64_t) state->clock_frequency);
MP_VERBOSE(state, "IAudioClock::GetFrequency gave a frequency of %"PRIu64".\n", (uint64_t) state->clock_frequency);
return S_OK;
return 0;
exit_label:
MP_ERR(state, "Error obtaining the audio device's timing: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
return hr;
MP_ERR(state, "init_clock failed with %s, unable to obtain the audio device's timing!\n",
wasapi_explain_err(hr));
return 1;
}
static HRESULT init_session_display(struct wasapi_state *state) {
static int init_session_display(struct wasapi_state *state) {
HRESULT hr;
wchar_t path[MAX_PATH+12] = {0};
@@ -445,59 +443,44 @@ static HRESULT init_session_display(struct wasapi_state *state) {
hr = IAudioSessionControl_SetIconPath(state->pSessionControl, path, NULL);
EXIT_ON_ERROR(hr);
return S_OK;
return 0;
exit_label:
MP_ERR(state, "Error setting audio session display name: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
return hr;
MP_ERR(state, "init_session_display failed with %s.\n",
wasapi_explain_err(hr));
return 1;
}
static HRESULT fix_format(struct ao *ao)
static int fix_format(struct wasapi_state *state)
{
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
HRESULT hr;
double offset = 0.5;
REFERENCE_TIME devicePeriod, bufferDuration, bufferPeriod;
MP_DBG(state, "IAudioClient::GetDevicePeriod\n");
hr = IAudioClient_GetDevicePeriod(state->pAudioClient,&devicePeriod, NULL);
MP_VERBOSE(state, "Device period: %.2g ms\n", (double) devicePeriod / 10000.0 );
/* integer multiple of device period close to 50ms */
bufferPeriod = bufferDuration = ceil( 50.0 * 10000.0 / devicePeriod ) * devicePeriod;
if (state->share_mode == AUDCLNT_SHAREMODE_SHARED)
bufferPeriod = 0;
/* handle unsupported buffer size */
/* hopefully this shouldn't happen because of the above integer device period */
/* http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875%28v=vs.85%29.aspx */
int retries=0;
/* cargo cult code to negotiate buffer block size, affected by hardware/drivers combinations,
gradually grow it to 10s, by 0.5s, consider failure if it still doesn't work
*/
hr = IAudioClient_GetDevicePeriod(state->pAudioClient,
&state->defaultRequestedDuration,
&state->minRequestedDuration);
reinit:
MP_DBG(state, "IAudioClient::Initialize\n");
hr = IAudioClient_Initialize(state->pAudioClient,
state->share_mode,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
bufferDuration,
bufferPeriod,
state->defaultRequestedDuration,
state->defaultRequestedDuration,
&(state->format.Format),
NULL);
/* something about buffer sizes on Win7 */
/* something about buffer sizes on Win7, fixme might loop forever */
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
MP_VERBOSE(state, "IAudioClient::Initialize negotiation failed with %s (0x%"PRIx32"), used %lld * 100ns\n",
wasapi_explain_err(hr), (uint32_t)hr, bufferDuration);
if (retries > 0) {
hr = E_FAIL;
EXIT_ON_ERROR(hr);
} else {
retries ++;
}
MP_VERBOSE(state, "IAudioClient::Initialize negotiation failed with %s, used %lld * 100ns\n",
wasapi_explain_err(hr), state->defaultRequestedDuration);
if (offset > 10.0)
goto exit_label; /* is 10 enough to break out of the loop?*/
IAudioClient_GetBufferSize(state->pAudioClient, &state->bufferFrameCount);
bufferPeriod = bufferDuration =
state->defaultRequestedDuration =
(REFERENCE_TIME)((10000.0 * 1000 / state->format.Format.nSamplesPerSec *
state->bufferFrameCount) + 0.5);
if (state->share_mode == AUDCLNT_SHAREMODE_SHARED)
bufferPeriod = 0;
state->bufferFrameCount) + offset);
offset += 0.5;
IAudioClient_Release(state->pAudioClient);
state->pAudioClient = NULL;
hr = IMMDeviceActivator_Activate(state->pDevice,
@@ -506,54 +489,40 @@ reinit:
goto reinit;
}
EXIT_ON_ERROR(hr);
MP_DBG(state, "IAudioClient::Initialize pRenderClient\n");
hr = IAudioClient_GetService(state->pAudioClient,
&IID_IAudioRenderClient,
(void **)&state->pRenderClient);
EXIT_ON_ERROR(hr);
MP_DBG(state, "IAudioClient::Initialize pAudioVolume\n");
hr = IAudioClient_GetService(state->pAudioClient,
&IID_ISimpleAudioVolume,
(void **) &state->pAudioVolume);
EXIT_ON_ERROR(hr);
MP_DBG(state, "IAudioClient::Initialize IAudioClient_SetEventHandle\n");
if (!state->hFeed)
goto exit_label;
hr = IAudioClient_SetEventHandle(state->pAudioClient, state->hFeed);
EXIT_ON_ERROR(hr);
MP_DBG(state, "IAudioClient::Initialize IAudioClient_GetBufferSize\n");
hr = IAudioClient_GetBufferSize(state->pAudioClient,
&state->bufferFrameCount);
EXIT_ON_ERROR(hr);
ao->device_buffer = state->bufferFrameCount;
state->buffer_block_size = state->format.Format.nChannels *
state->format.Format.wBitsPerSample / 8 *
state->bufferFrameCount;
bufferDuration =
(REFERENCE_TIME)((10000.0 * 1000 / state->format.Format.nSamplesPerSec *
state->bufferFrameCount) + 0.5);
MP_VERBOSE(state, "Buffer frame count: %"PRIu32" (%.2g ms)\n",
state->bufferFrameCount, (double) bufferDuration / 10000.0 );
hr = init_clock(state);
EXIT_ON_ERROR(hr);
hr = init_session_display(state);
EXIT_ON_ERROR(hr);
if (init_clock(state))
return 1;
if (init_session_display(state))
return 1;
state->hTask =
state->VistaBlob.pAvSetMmThreadCharacteristicsW(L"Pro Audio", &state->taskIndex);
MP_VERBOSE(state, "Format fixed. Using %lld byte buffer block size\n",
MP_VERBOSE(state, "fix_format OK, using %lld byte buffer block size!\n",
(long long) state->buffer_block_size);
return S_OK;
return 0;
exit_label:
MP_ERR(state, "Error initializing device: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
return hr;
MP_ERR(state, "fix_format fails with %s, failed to determine buffer block size!\n",
wasapi_explain_err(hr));
return 1;
}
static char* get_device_id(IMMDevice *pDevice) {
@@ -650,10 +619,8 @@ end:
return found;
}
// Warning: ao and list are NULL in the "--ao=wasapi:device=help" path!
static HRESULT enumerate_with_state(struct mp_log *log, struct ao *ao,
struct ao_device_list *list,
char *header, int status, int with_id)
static HRESULT enumerate_with_state(struct mp_log *log, char *header,
int status, int with_id)
{
HRESULT hr;
IMMDeviceEnumerator *pEnumerator = NULL;
@@ -666,7 +633,7 @@ static HRESULT enumerate_with_state(struct mp_log *log, struct ao *ao,
EXIT_ON_ERROR(hr);
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator,
eRender, eMultimedia,
eRender, eConsole,
&pDevice);
EXIT_ON_ERROR(hr);
@@ -701,12 +668,6 @@ static HRESULT enumerate_with_state(struct mp_log *log, struct ao *ao,
mp_info(log, "%s, ID: %s%s\n", name, id, mark);
}
if (ao) {
char *desc = talloc_asprintf(NULL, "%s, ID: %s%s", name, id, mark);
struct ao_device_desc e = {id, desc};
ao_device_list_add(list, ao, &e);
}
talloc_free(name);
talloc_free(id);
SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice));
@@ -714,8 +675,8 @@ static HRESULT enumerate_with_state(struct mp_log *log, struct ao *ao,
talloc_free(defid);
SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices));
SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator));
return hr;
return S_OK;
exit_label:
talloc_free(defid);
SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice));
@@ -724,50 +685,29 @@ exit_label:
return hr;
}
int wasapi_enumerate_devices(struct mp_log *log, struct ao *ao,
struct ao_device_list *list)
int wasapi_enumerate_devices(struct mp_log *log)
{
HRESULT hr;
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
hr = enumerate_with_state(log, ao, list, "Active devices:",
DEVICE_STATE_ACTIVE, 1);
hr = enumerate_with_state(log, "Active devices:", DEVICE_STATE_ACTIVE, 1);
EXIT_ON_ERROR(hr);
hr = enumerate_with_state(log, ao, list, "Unplugged devices:",
DEVICE_STATE_UNPLUGGED, 0);
hr = enumerate_with_state(log, "Unplugged devices:", DEVICE_STATE_UNPLUGGED, 0);
EXIT_ON_ERROR(hr);
CoUninitialize();
return 0;
exit_label:
mp_err(log, "Error enumerating devices: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
mp_err(log, "Error enumerating devices: HRESULT %08"PRIx32" \"%s\"\n",
(uint32_t)hr, wasapi_explain_err(hr));
CoUninitialize();
return 1;
}
static HRESULT load_default_device(struct ao *ao, IMMDeviceEnumerator* pEnumerator,
IMMDevice **ppDevice)
{
HRESULT hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator,
eRender, eMultimedia,
ppDevice);
EXIT_ON_ERROR(hr);
char *id = get_device_id(*ppDevice);
MP_VERBOSE(ao, "Default device ID: %s\n", id);
talloc_free(id);
return S_OK;
exit_label:
MP_ERR(ao , "Error loading default device: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
return hr;
}
static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumerator,
IMMDevice **ppDevice, char *search)
static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice,
char *search)
{
HRESULT hr;
IMMDeviceEnumerator *pEnumerator = NULL;
IMMDeviceCollection *pDevices = NULL;
IMMDevice *pTempDevice = NULL;
LPWSTR deviceID = NULL;
@@ -780,6 +720,10 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumera
devid = search;
}
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
&IID_IMMDeviceEnumerator, (void**)&pEnumerator);
EXIT_ON_ERROR(hr);
int search_err = 0;
if (devid == NULL) {
@@ -791,16 +735,16 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumera
IMMDeviceCollection_GetCount(pDevices, &count);
if (devno >= count) {
MP_ERR(ao, "No device #%d\n", devno);
MP_ERR(ao, "no device #%d!\n", devno);
} else {
MP_VERBOSE(ao, "Finding device #%d\n", devno);
MP_VERBOSE(ao, "finding device #%d\n", devno);
hr = IMMDeviceCollection_Item(pDevices, devno, &pTempDevice);
EXIT_ON_ERROR(hr);
hr = IMMDevice_GetId(pTempDevice, &deviceID);
EXIT_ON_ERROR(hr);
MP_VERBOSE(ao, "Found device #%d\n", devno);
MP_VERBOSE(ao, "found device #%d\n", devno);
}
} else {
hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eRender,
@@ -811,7 +755,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumera
int count;
IMMDeviceCollection_GetCount(pDevices, &count);
MP_VERBOSE(ao, "Finding device %s\n", devid);
MP_VERBOSE(ao, "finding device %s\n", devid);
IMMDevice *prevDevice = NULL;
@@ -829,7 +773,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumera
if (deviceID) {
char *name;
if (!search_err) {
MP_ERR(ao, "Multiple matching devices found\n");
MP_ERR(ao, "multiple matching devices found!\n");
name = get_device_name(prevDevice);
MP_ERR(ao, "%s\n", name);
talloc_free(name);
@@ -848,7 +792,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumera
}
if (deviceID == NULL) {
MP_ERR(ao, "Could not find device %s\n", devid);
MP_ERR(ao, "could not find device %s!\n", devid);
}
}
@@ -858,20 +802,19 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumera
if (deviceID == NULL || search_err) {
hr = E_NOTFOUND;
} else {
MP_VERBOSE(ao, "Loading device %S\n", deviceID);
MP_VERBOSE(ao, "loading device %S\n", deviceID);
hr = IMMDeviceEnumerator_GetDevice(pEnumerator, deviceID, ppDevice);
if (FAILED(hr)) {
MP_ERR(ao, "Could not load requested device\n");
MP_ERR(ao, "could not load requested device!\n");
}
}
exit_label:
SAFE_RELEASE(pTempDevice, IMMDevice_Release(pTempDevice));
SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices));
CoTaskMemFree(deviceID);
SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator));
return hr;
}
@@ -879,11 +822,11 @@ int wasapi_validate_device(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param)
{
if (bstr_equals0(param, "help")) {
wasapi_enumerate_devices(log, NULL, NULL);
wasapi_enumerate_devices(log);
return M_OPT_EXIT;
}
mp_dbg(log, "Validating device=%s\n", param.start);
mp_dbg(log, "validating device=%s\n", param.start);
char *end;
int devno = (int) strtol(param.start, &end, 10);
@@ -916,8 +859,8 @@ HRESULT wasapi_setup_proxies(struct wasapi_state *state) {
exit_label:
if (hr != S_OK) {
MP_ERR(state, "Error reading COM proxy: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
MP_ERR(state, "error reading COM proxy: %08x %s\n", (unsigned int)hr,
wasapi_explain_err(hr));
}
return hr;
}
@@ -952,129 +895,80 @@ exit_label:
return hr;
}
static void destroy_proxies(struct wasapi_state *state) {
SAFE_RELEASE(state->sAudioClient, IUnknown_Release(state->sAudioClient));
SAFE_RELEASE(state->sAudioVolume, IUnknown_Release(state->sAudioVolume));
SAFE_RELEASE(state->sEndpointVolume, IUnknown_Release(state->sEndpointVolume));
SAFE_RELEASE(state->sSessionControl, IUnknown_Release(state->sSessionControl));
}
void wasapi_dispatch(void)
{
/* dispatch any possible pending messages */
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
DispatchMessage(&msg);
}
HRESULT wasapi_thread_init(struct ao *ao)
int wasapi_thread_init(struct ao *ao)
{
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
HRESULT hr;
MP_DBG(ao, "Init wasapi thread\n");
int64_t retry_wait = 1;
retry:
state->initial_volume = -1.0;
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
&IID_IMMDeviceEnumerator, (void**)&state->pEnumerator);
EXIT_ON_ERROR(hr);
if (!state->opt_device) {
IMMDeviceEnumerator *pEnumerator;
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
&IID_IMMDeviceEnumerator, (void**)&pEnumerator);
EXIT_ON_ERROR(hr);
char *device = state->opt_device;
if (!device || !device[0])
device = ao->device;
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator,
eRender, eConsole,
&state->pDevice);
SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator));
if (!device || !device[0])
hr = load_default_device(ao, state->pEnumerator, &state->pDevice);
else
hr = find_and_load_device(ao, state->pEnumerator, &state->pDevice, device);
char *id = get_device_id(state->pDevice);
MP_VERBOSE(ao, "default device ID: %s\n", id);
talloc_free(id);
} else {
hr = find_and_load_device(ao, &state->pDevice, state->opt_device);
}
EXIT_ON_ERROR(hr);
char *name = get_device_name(state->pDevice);
MP_VERBOSE(ao, "Device loaded: %s\n", name);
MP_VERBOSE(ao, "device loaded: %s\n", name);
talloc_free(name);
MP_DBG(ao, "Activating pAudioClient interface\n");
hr = IMMDeviceActivator_Activate(state->pDevice, &IID_IAudioClient,
CLSCTX_ALL, NULL, (void **)&state->pAudioClient);
EXIT_ON_ERROR(hr);
MP_DBG(ao, "Activating pEndpointVolume interface\n");
hr = IMMDeviceActivator_Activate(state->pDevice, &IID_IAudioEndpointVolume,
CLSCTX_ALL, NULL,
(void **)&state->pEndpointVolume);
EXIT_ON_ERROR(hr);
IAudioEndpointVolume_QueryHardwareSupport(state->pEndpointVolume,
&state->vol_hw_support);
MP_DBG(ao, "Query hardware volume support\n");
hr = IAudioEndpointVolume_QueryHardwareSupport(state->pEndpointVolume,
&state->vol_hw_support);
if ( hr != S_OK )
MP_WARN(ao, "Query hardware volume control: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
state->init_ret = find_formats(ao); /* Probe support formats */
if (state->init_ret)
goto exit_label;
if (!fix_format(state)) { /* now that we're sure what format to use */
EXIT_ON_ERROR(create_proxies(state));
MP_DBG(ao, "Probing formats\n");
if (find_formats(ao)){
hr = E_FAIL;
EXIT_ON_ERROR(hr);
if (state->opt_exclusive)
IAudioEndpointVolume_GetMasterVolumeLevelScalar(state->pEndpointVolume,
&state->initial_volume);
else
ISimpleAudioVolume_GetMasterVolume(state->pAudioVolume,
&state->initial_volume);
state->previous_volume = state->initial_volume;
MP_VERBOSE(ao, "thread_init OK!\n");
SetEvent(state->init_done);
return state->init_ret;
}
MP_DBG(ao, "Fixing format\n");
hr = fix_format(ao);
if ( (hr == AUDCLNT_E_DEVICE_IN_USE ||
hr == AUDCLNT_E_DEVICE_INVALIDATED) &&
retry_wait <= 8 ) {
wasapi_thread_uninit(ao);
MP_WARN(ao, "Retrying in %"PRId64" us\n", retry_wait);
mp_sleep_us(retry_wait);
retry_wait *= 2;
goto retry;
}
EXIT_ON_ERROR(hr);
MP_DBG(ao, "Creating proxies\n");
hr = create_proxies(state);
EXIT_ON_ERROR(hr);
MP_DBG(ao, "Read volume levels\n");
if (state->opt_exclusive)
IAudioEndpointVolume_GetMasterVolumeLevelScalar(state->pEndpointVolume,
&state->initial_volume);
else
ISimpleAudioVolume_GetMasterVolume(state->pAudioVolume,
&state->initial_volume);
state->previous_volume = state->initial_volume;
wasapi_change_init(ao);
MP_DBG(ao, "Init wasapi thread done\n");
return S_OK;
exit_label:
MP_ERR(state, "Error setting up audio thread: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
return hr;
state->init_ret = -1;
SetEvent(state->init_done);
return -1;
}
void wasapi_thread_uninit(struct ao *ao)
void wasapi_thread_uninit(wasapi_state *state)
{
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
wasapi_dispatch();
if (state->pAudioClient)
IAudioClient_Stop(state->pAudioClient);
wasapi_change_uninit(ao);
if (state->opt_exclusive &&
state->pEndpointVolume &&
state->initial_volume > 0 )
if (state->opt_exclusive)
IAudioEndpointVolume_SetMasterVolumeLevelScalar(state->pEndpointVolume,
state->initial_volume, NULL);
destroy_proxies(state);
SAFE_RELEASE(state->pRenderClient, IAudioRenderClient_Release(state->pRenderClient));
SAFE_RELEASE(state->pAudioClock, IAudioClock_Release(state->pAudioClock));
SAFE_RELEASE(state->pAudioVolume, ISimpleAudioVolume_Release(state->pAudioVolume));
@@ -1082,9 +976,9 @@ void wasapi_thread_uninit(struct ao *ao)
SAFE_RELEASE(state->pSessionControl, IAudioSessionControl_Release(state->pSessionControl));
SAFE_RELEASE(state->pAudioClient, IAudioClient_Release(state->pAudioClient));
SAFE_RELEASE(state->pDevice, IMMDevice_Release(state->pDevice));
SAFE_RELEASE(state->pEnumerator, IMMDeviceEnumerator_Release(state->pEnumerator));
if (state->hTask)
state->VistaBlob.pAvRevertMmThreadCharacteristics(state->hTask);
MP_DBG(ao, "Thread uninit done\n");
CoUninitialize();
ExitThread(0);
}
+3 -6
View File
@@ -31,16 +31,13 @@ int wasapi_fill_VistaBlob(wasapi_state *state);
const char *wasapi_explain_err(const HRESULT hr);
int wasapi_enumerate_devices(struct mp_log *log, struct ao *ao,
struct ao_device_list *list);
int wasapi_enumerate_devices(struct mp_log *log);
int wasapi_validate_device(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param);
void wasapi_dispatch(void);
HRESULT wasapi_thread_init(struct ao *ao);
void wasapi_thread_uninit(struct ao *ao);
int wasapi_thread_init(struct ao *ao);
void wasapi_thread_uninit(wasapi_state *state);
HRESULT wasapi_setup_proxies(wasapi_state *state);
void wasapi_release_proxies(wasapi_state *state);
+8 -35
View File
@@ -22,8 +22,13 @@
#include <stdbool.h>
#include <pthread.h>
#include "osdep/atomics.h"
#include "audio/out/ao.h"
#include "audio/chmap.h"
#include "audio/chmap_sel.h"
// If ao_get_delay() reaches this value after ao_play() was called with the
// AOPLAY_FINAL_CHUNK flag set, the playback core expects that the audio has
// all been played.
#define AO_EOF_DELAY 0.05
/* global data used by ao.c and ao drivers */
struct ao {
@@ -45,19 +50,6 @@ struct ao {
struct input_ctx *input_ctx;
struct mp_log *log; // Using e.g. "[ao/coreaudio]" as prefix
// The device as selected by the user, usually using ao_device_desc.name
// from an entry from the list returned by driver->list_devices. If the
// default device should be used, this is set to NULL.
char *device;
// Application name to report to the audio API.
char *client_name;
// Used during init: if init fails, redirect to this ao
char *redirect;
atomic_bool request_reload;
int buffer;
double def_buffer;
void *api_priv;
@@ -135,7 +127,7 @@ struct ao_driver {
// push based: see ao_play()
int (*play)(struct ao *ao, void **data, int samples, int flags);
// push based: see ao_get_delay()
double (*get_delay)(struct ao *ao);
float (*get_delay)(struct ao *ao);
// push based: block until all queued audio is played (optional)
void (*drain)(struct ao *ao);
// Optional. Return true if audio has stopped in any way.
@@ -155,20 +147,6 @@ struct ao_driver {
// In combination with wait(). Lock may or may not be held.
void (*wakeup)(struct ao *ao);
// Return the list of devices currently available in the system. Use
// ao_device_list_add() to add entries. The selected device will be set as
// ao->device (using ao_device_desc.name).
// Warning: the ao struct passed doesn't necessarily have ao_driver->init()
// called on it - in this case, ->uninit() won't be called either
// after this function. The idea is that list_devs can be called
// both when no audio or when audio is active. the latter can
// happen if the audio config change at runtime, and in this case
// we don't want to force a new connection to the audio server
// just to update the device list. For runtime updates, ->init()
// will have been called. In both cases, ao->priv is properly
// allocated. (Runtime updates are not used/supported yet.)
void (*list_devs)(struct ao *ao, struct ao_device_list *list);
// For option parsing (see vo.h)
int priv_size;
const void *priv_defaults;
@@ -189,9 +167,4 @@ bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
struct mp_chmap *map, int num);
// Add a deep copy of e to the list.
// Call from ao_driver->list_devs callback only.
void ao_device_list_add(struct ao_device_list *list, struct ao *ao,
struct ao_device_desc *e);
#endif
+27 -41
View File
@@ -43,11 +43,8 @@ enum {
// finished, but device is open)
AO_STATE_WAIT, // wait for callback to go into AO_STATE_NONE state
AO_STATE_PLAY, // play the buffer
AO_STATE_BUSY, // like AO_STATE_PLAY, but ao_read_data() is being called
};
#define IS_PLAYING(st) ((st) == AO_STATE_PLAY || (st) == AO_STATE_BUSY)
struct ao_pull_state {
// Be very careful with the order when accessing planes.
struct mp_ring *buffers[MP_NUM_CHANNELS];
@@ -59,21 +56,6 @@ struct ao_pull_state {
atomic_llong end_time_us;
};
static void set_state(struct ao *ao, int new_state)
{
struct ao_pull_state *p = ao->api_priv;
while (1) {
int old = atomic_load(&p->state);
if (old == AO_STATE_BUSY) {
// A spinlock, because some audio APIs don't want us to use mutexes.
mp_sleep_us(1);
continue;
}
if (atomic_compare_exchange_strong(&p->state, &old, new_state))
break;
}
}
static int get_space(struct ao *ao)
{
struct ao_pull_state *p = ao->api_priv;
@@ -97,10 +79,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
int r = mp_ring_write(p->buffers[n], data[n], write_bytes);
assert(r == write_bytes);
}
int state = atomic_load(&p->state);
if (!IS_PLAYING(state)) {
set_state(ao, AO_STATE_PLAY);
if (atomic_load(&p->state) != AO_STATE_PLAY) {
atomic_store(&p->state, AO_STATE_PLAY);
ao->driver->resume(ao);
}
@@ -121,13 +101,14 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
struct ao_pull_state *p = ao->api_priv;
int full_bytes = samples * ao->sstride;
bool need_wakeup = false;
int state = atomic_load(&p->state);
int bytes = 0;
// Play silence in states other than AO_STATE_PLAY.
if (!atomic_compare_exchange_strong(&p->state, &(int){AO_STATE_PLAY},
AO_STATE_BUSY))
if (state != AO_STATE_PLAY) {
if (state == AO_STATE_WAIT)
atomic_store(&p->state, AO_STATE_NONE);
goto end;
}
// Since the writer will write the first plane last, its buffered amount
// of data is the minimum amount across all planes.
@@ -143,16 +124,10 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
}
// Half of the buffer played -> request more.
need_wakeup = buffered_bytes - bytes <= mp_ring_size(p->buffers[0]) / 2;
// Should never fail.
atomic_compare_exchange_strong(&p->state, &(int){AO_STATE_BUSY}, AO_STATE_PLAY);
end:
if (need_wakeup)
if (buffered_bytes - bytes <= mp_ring_size(p->buffers[0]) / 2)
mp_input_wakeup_nolock(ao->input_ctx);
end:
// pad with silence (underflow/paused/eof)
for (int n = 0; n < ao->num_planes; n++)
af_fill_silence(data[n], full_bytes - bytes, ao->format);
@@ -172,7 +147,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
// it takes until the last sample in the buffer reaches the speakers. This is
// used for audio/video synchronization, so it's very important to implement
// this correctly.
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
struct ao_pull_state *p = ao->api_priv;
@@ -185,9 +160,19 @@ static double get_delay(struct ao *ao)
static void reset(struct ao *ao)
{
struct ao_pull_state *p = ao->api_priv;
if (ao->driver->reset)
if (ao->driver->reset) {
ao->driver->reset(ao); // assumes the audio callback thread is stopped
set_state(ao, AO_STATE_NONE);
atomic_store(&p->state, AO_STATE_NONE);
} else {
// The thread keeps running. Wait until the audio callback gets into
// a defined state where it won't touch the ringbuffer. We must do
// this, because emptying the ringbuffer is not an atomic operation.
if (atomic_load(&p->state) != AO_STATE_NONE) {
atomic_store(&p->state, AO_STATE_WAIT);
while (atomic_load(&p->state) != AO_STATE_NONE)
mp_sleep_us(1);
}
}
for (int n = 0; n < ao->num_planes; n++)
mp_ring_reset(p->buffers[n]);
atomic_store(&p->end_time_us, 0);
@@ -195,22 +180,23 @@ static void reset(struct ao *ao)
static void pause(struct ao *ao)
{
struct ao_pull_state *p = ao->api_priv;
if (ao->driver->reset)
ao->driver->reset(ao);
set_state(ao, AO_STATE_NONE);
atomic_store(&p->state, AO_STATE_NONE);
}
static void resume(struct ao *ao)
{
set_state(ao, AO_STATE_PLAY);
struct ao_pull_state *p = ao->api_priv;
atomic_store(&p->state, AO_STATE_PLAY);
ao->driver->resume(ao);
}
static void drain(struct ao *ao)
{
struct ao_pull_state *p = ao->api_priv;
int state = atomic_load(&p->state);
if (IS_PLAYING(state))
if (atomic_load(&p->state) == AO_STATE_PLAY)
mp_sleep_us(get_delay(ao) * 1000000);
reset(ao);
}
+44 -42
View File
@@ -44,12 +44,14 @@ struct ao_push_state {
pthread_t thread;
pthread_mutex_t lock;
pthread_cond_t wakeup;
pthread_cond_t wakeup_drain;
// --- protected by lock
struct mp_audio_buffer *buffer;
bool terminate;
bool drain;
bool wait_on_ao;
bool still_playing;
bool need_wakeup;
@@ -90,14 +92,22 @@ static double unlocked_get_delay(struct ao *ao)
double driver_delay = 0;
if (ao->driver->get_delay)
driver_delay = ao->driver->get_delay(ao);
return driver_delay + mp_audio_buffer_seconds(p->buffer);
double delay = driver_delay + mp_audio_buffer_seconds(p->buffer);
if (delay >= AO_EOF_DELAY && p->expected_end_time) {
if (mp_time_sec() > p->expected_end_time) {
MP_ERR(ao, "Audio device EOF reporting is broken!\n");
MP_ERR(ao, "Please report this problem.\n");
delay = 0;
}
}
return delay;
}
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
struct ao_push_state *p = ao->api_priv;
pthread_mutex_lock(&p->lock);
double delay = unlocked_get_delay(ao);
float delay = unlocked_get_delay(ao);
pthread_mutex_unlock(&p->lock);
return delay;
}
@@ -143,27 +153,20 @@ static void drain(struct ao *ao)
{
struct ao_push_state *p = ao->api_priv;
MP_VERBOSE(ao, "draining...\n");
pthread_mutex_lock(&p->lock);
if (p->paused)
goto done;
p->final_chunk = true;
wakeup_playthread(ao);
while (p->still_playing && mp_audio_buffer_samples(p->buffer) > 0)
pthread_cond_wait(&p->wakeup, &p->lock);
if (ao->driver->drain) {
ao->driver->drain(ao);
} else {
double time = unlocked_get_delay(ao);
mp_sleep_us(MPMIN(time, ao->buffer / (double)ao->samplerate + 1) * 1e6);
if (p->paused) {
pthread_mutex_unlock(&p->lock);
return;
}
done:
p->final_chunk = true;
p->drain = true;
wakeup_playthread(ao);
while (p->drain)
pthread_cond_wait(&p->wakeup_drain, &p->lock);
pthread_mutex_unlock(&p->lock);
if (!ao->driver->drain)
mp_sleep_us(get_delay(ao) * 1000000);
reset(ao);
}
@@ -232,12 +235,10 @@ static int play(struct ao *ao, void **data, int samples, int flags)
bool got_data = write_samples > 0 || p->paused || p->final_chunk != is_final;
p->expected_end_time = 0;
p->final_chunk = is_final;
p->paused = false;
if (got_data) {
p->still_playing = true;
p->expected_end_time = 0;
}
p->still_playing |= write_samples > 0;
// If we don't have new data, the decoder thread basically promises it
// will send new data as soon as it's available.
@@ -278,8 +279,11 @@ static void ao_play_data(struct ao *ao)
r = max;
}
mp_audio_buffer_skip(p->buffer, r);
if (r > 0)
p->expected_end_time = 0;
if (p->final_chunk && mp_audio_buffer_samples(p->buffer) == 0) {
p->expected_end_time = mp_time_sec() + AO_EOF_DELAY + 0.25; // + margin
if (ao->driver->get_delay)
p->expected_end_time += ao->driver->get_delay(ao);
}
// Nothing written, but more input data than space - this must mean the
// AO's get_space() doesn't do period alignment correctly.
bool stuck = r == 0 && max >= space && space > 0;
@@ -303,12 +307,18 @@ static void *playthread(void *arg)
{
struct ao *ao = arg;
struct ao_push_state *p = ao->api_priv;
mpthread_set_name("ao");
pthread_mutex_lock(&p->lock);
while (!p->terminate) {
if (!p->paused)
ao_play_data(ao);
if (p->drain && (!p->wait_on_ao || p->paused)) {
if (ao->driver->drain)
ao->driver->drain(ao);
p->drain = false;
pthread_cond_signal(&p->wakeup_drain);
}
if (!p->need_wakeup) {
MP_STATS(ao, "start audio wait");
if (!p->wait_on_ao || p->paused) {
@@ -322,22 +332,14 @@ static void *playthread(void *arg)
bool was_playing = p->still_playing;
double timeout = -1;
if (p->still_playing && !p->paused && p->final_chunk &&
!mp_audio_buffer_samples(p->buffer))
{
double now = mp_time_sec();
if (!p->expected_end_time)
p->expected_end_time = now + unlocked_get_delay(ao);
if (p->expected_end_time < now) {
if (p->still_playing && !p->paused) {
timeout = unlocked_get_delay(ao);
if (timeout < AO_EOF_DELAY)
p->still_playing = false;
} else {
timeout = p->expected_end_time - now;
}
}
if (was_playing && !p->still_playing)
mp_input_wakeup(ao->input_ctx);
pthread_cond_signal(&p->wakeup); // for draining
if (p->still_playing && timeout > 0) {
mpthread_cond_timedwait_rel(&p->wakeup, &p->lock, timeout);
@@ -345,15 +347,13 @@ static void *playthread(void *arg)
pthread_cond_wait(&p->wakeup, &p->lock);
}
} else {
// Wait until the device wants us to write more data to it.
if (!ao->driver->wait || ao->driver->wait(ao, &p->lock) < 0) {
// Fallback to guessing.
double timeout = 0;
if (ao->driver->get_delay)
timeout = ao->driver->get_delay(ao);
timeout *= 0.25; // wake up if 25% played
if (!p->need_wakeup)
mpthread_cond_timedwait_rel(&p->wakeup, &p->lock, timeout);
mpthread_cond_timedwait_rel(&p->wakeup, &p->lock, timeout);
}
}
MP_STATS(ao, "end audio wait");
@@ -374,6 +374,7 @@ static void destroy_no_thread(struct ao *ao)
close(p->wakeup_pipe[n]);
pthread_cond_destroy(&p->wakeup);
pthread_cond_destroy(&p->wakeup_drain);
pthread_mutex_destroy(&p->lock);
}
@@ -397,6 +398,7 @@ static int init(struct ao *ao)
pthread_mutex_init(&p->lock, NULL);
pthread_cond_init(&p->wakeup, NULL);
pthread_cond_init(&p->wakeup_drain, NULL);
mp_make_wakeup_pipe(p->wakeup_pipe);
if (ao->device_buffer <= 0) {
@@ -464,7 +466,7 @@ int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds,
assert(ao->api == &ao_api_push);
assert(&p->lock == lock);
if (num_fds >= MAX_POLL_FDS || p->wakeup_pipe[0] < 0)
if (num_fds > MAX_POLL_FDS || p->wakeup_pipe[0] < 0)
return -1;
struct pollfd p_fds[MAX_POLL_FDS];
+2 -4
View File
@@ -5,9 +5,9 @@
from __future__ import print_function
import os, sys, stat, hashlib, subprocess
WAFRELEASE = "waf-1.8.1"
WAFRELEASE = "waf-1.7.16"
WAFURL = "http://ftp.waf.io/pub/release/" + WAFRELEASE
SHA256HASH = "ec658116ba0b96629d91fde0b32321849e866e0819f1e835c4c2c7f7ffe1a21d"
SHA256HASH = "b64dc26c882572415fd450b745006107965f3fe17b357e3eb43d6676c9635a61"
if os.path.exists("waf"):
wafver = subprocess.check_output(['./waf', '--version']).decode()
@@ -31,8 +31,6 @@ if SHA256HASH == hashlib.sha256(waf).hexdigest():
print("Checksum verified.")
else:
print("The checksum of the downloaded file does not match!")
print(" - got: {}".format(hashlib.sha256(waf).hexdigest()))
print(" - expected: {}".format(SHA256HASH))
print("Please download and verify the file manually.")
sys.exit(1)
+1 -1
View File
@@ -171,7 +171,7 @@ static bool mp_parse_escape(void *talloc_ctx, bstr *dst, bstr *code)
if (code->start[0] == 'x' && code->len >= 3) {
bstr num = bstr_splice(*code, 1, 3);
char c = bstrtoll(num, &num, 16);
if (num.len)
if (!num.len)
return false;
bstr_xappend(talloc_ctx, dst, (bstr){&c, 1});
*code = bstr_cut(*code, 3);
+2 -2
View File
@@ -60,8 +60,8 @@ enum stream_type {
STREAM_TYPE_COUNT,
};
extern const char *const mpv_version;
extern const char *const mpv_builddate;
extern const char *mpv_version;
extern const char *mpv_builddate;
char *mp_format_time(double time, bool fractions);
char *mp_format_time_fmt(const char *fmt, double time);
-1
View File
@@ -1145,7 +1145,6 @@ void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...)
va_list va;
va_start(va, format);
mp_msg_va(ctx->log, MSGL_ERR, format, va);
va_end(va);
if (ctx->failed)
return;
ctx->failed = true;
+64 -92
View File
@@ -37,8 +37,6 @@
#include "osdep/io.h"
#include "osdep/timer.h"
#include "libmpv/client.h"
#include "msg.h"
#include "msg_control.h"
@@ -53,6 +51,7 @@ struct mp_log_root {
bool module;
bool show_time;
bool termosd; // use terminal control codes for status line
bool header; // indicate that message header should be printed
int blank_lines; // number of lines useable by status
int status_lines; // number of current status lines
bool color;
@@ -68,8 +67,6 @@ struct mp_log_root {
* (This is perhaps better than maintaining a globally accessible and
* synchronized mp_log tree.) */
atomic_ulong reload_counter;
// --- protected by mp_msg_lock
char buffer[MSGSIZE_MAX];
};
struct mp_log {
@@ -158,9 +155,6 @@ static void prepare_status_line(struct mp_log_root *root, char *new_status)
}
size_t old_lines = root->status_lines;
if (!new_status[0] && old_lines == 0)
return; // nothing to clear
size_t clear_lines = MPMIN(MPMAX(new_lines, old_lines), root->blank_lines);
// clear the status line itself
@@ -235,42 +229,70 @@ static void pretty_print_module(FILE* stream, const char *prefix, bool use_color
set_msg_color(stream, lev);
}
static bool test_terminal_level(struct mp_log *log, int lev)
static void print_msg_on_terminal(struct mp_log *log, int lev, char *text)
{
return lev <= log->terminal_level &&
!(lev == MSGL_STATUS && terminal_in_background());
}
static void print_terminal_line(struct mp_log *log, int lev, char *text)
{
if (!test_terminal_level(log, lev))
return;
struct mp_log_root *root = log->root;
FILE *stream = (root->force_stderr || lev == MSGL_STATUS) ? stderr : stdout;
if (lev != MSGL_STATUS)
if (!(lev <= log->terminal_level))
return;
bool header = root->header;
const char *prefix = log->prefix;
char *terminate = NULL;
if ((lev >= MSGL_V) || root->verbose || root->module)
prefix = log->verbose_prefix;
if (lev == MSGL_STATUS) {
// skip status line output if stderr is a tty but in background
if (terminal_in_background())
return;
// don't clear if we don't have to
if (!text[0] && !root->status_lines)
return;
if (root->termosd) {
prepare_status_line(root, text);
terminate = "\r";
} else {
terminate = "\n";
}
root->header = true;
} else {
flush_status_line(root);
size_t len = strlen(text);
root->header = len && text[len - 1] == '\n';
if (lev == MSGL_STATS)
terminate = "\n";
}
if (root->color)
set_msg_color(stream, lev);
if (root->show_time)
fprintf(stream, "[%" PRId64 "] ", mp_time_us());
do {
if (header) {
if (root->show_time)
fprintf(stream, "[%" PRId64 "] ", mp_time_us());
const char *prefix = log->prefix;
if ((lev >= MSGL_V) || root->verbose || root->module)
prefix = log->verbose_prefix;
if (prefix) {
if (root->module) {
pretty_print_module(stream, prefix, root->color, lev);
} else {
fprintf(stream, "[%s] ", prefix);
if (prefix) {
if (root->module) {
pretty_print_module(stream, prefix, root->color, lev);
} else {
fprintf(stream, "[%s] ", prefix);
}
}
}
}
fprintf(stream, "%s", text);
char *next = strchr(text, '\n');
int len = next ? next - text + 1 : strlen(text);
fprintf(stream, "%.*s", len, text);
text = text + len;
header = true;
} while (text[0]);
if (terminate)
fprintf(stream, "%s", terminate);
if (root->color)
set_term_color(stream, -1);
@@ -282,7 +304,7 @@ static void write_msg_to_buffers(struct mp_log *log, int lev, char *text)
struct mp_log_root *root = log->root;
for (int n = 0; n < root->num_buffers; n++) {
struct mp_log_buffer *buffer = root->buffers[n];
if (lev <= buffer->level && lev != MSGL_STATUS) {
if (lev <= buffer->level) {
// Assuming a single writer (serialized by msg lock)
int avail = mp_ring_available(buffer->ring) / sizeof(void *);
if (avail < 1)
@@ -299,7 +321,7 @@ static void write_msg_to_buffers(struct mp_log *log, int lev, char *text)
*entry = (struct mp_log_buffer_entry) {
.prefix = "overflow",
.level = MSGL_FATAL,
.text = "log message buffer overflow\n",
.text = "",
};
}
mp_ring_write(buffer->ring, (unsigned char *)&entry, sizeof(entry));
@@ -326,54 +348,15 @@ 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;
if (vsnprintf(tmp, MSGSIZE_MAX, format, va) < 0)
snprintf(tmp, MSGSIZE_MAX, "[fprintf error]\n");
tmp[MSGSIZE_MAX - 2] = '\n';
tmp[MSGSIZE_MAX - 1] = 0;
char *text = tmp;
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;
if (lev == MSGL_STATS) {
dump_stats(log, lev, text);
} else if (lev == MSGL_STATUS && !test_terminal_level(log, lev)) {
/* discard */
} else {
if (lev == MSGL_STATUS && root->termosd)
prepare_status_line(root, text);
// Split away each line. Normally we require full lines; buffer partial
// lines if they happen.
while (1) {
char *end = strchr(text, '\n');
if (!end)
break;
char *next = &end[1];
char saved = next[0];
next[0] = '\0';
print_terminal_line(log, lev, text);
write_msg_to_buffers(log, lev, text);
next[0] = saved;
text = next;
}
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);
}
}
print_msg_on_terminal(log, lev, text);
write_msg_to_buffers(log, lev, text);
dump_stats(log, lev, text);
pthread_mutex_unlock(&mp_msg_lock);
}
@@ -427,6 +410,7 @@ void mp_msg_init(struct mpv_global *global)
struct mp_log_root *root = talloc_zero(NULL, struct mp_log_root);
*root = (struct mp_log_root){
.global = global,
.header = true,
.reload_counter = ATOMIC_VAR_INIT(1),
};
@@ -604,18 +588,6 @@ const char *const mp_log_levels[MSGL_MAX + 1] = {
[MSGL_STATS] = "stats",
};
const int mp_mpv_log_levels[MSGL_MAX + 1] = {
[MSGL_FATAL] = MPV_LOG_LEVEL_FATAL,
[MSGL_ERR] = MPV_LOG_LEVEL_ERROR,
[MSGL_WARN] = MPV_LOG_LEVEL_WARN,
[MSGL_INFO] = MPV_LOG_LEVEL_INFO,
[MSGL_STATUS] = 0, // never used
[MSGL_V] = MPV_LOG_LEVEL_V,
[MSGL_DEBUG] = MPV_LOG_LEVEL_DEBUG,
[MSGL_TRACE] = MPV_LOG_LEVEL_TRACE,
[MSGL_STATS] = 0, // never used
};
int mp_msg_split_msglevel(struct bstr *s, struct bstr *out_mod, int *out_level)
{
if (s->len == 0)
-1
View File
@@ -32,6 +32,5 @@ struct bstr;
int mp_msg_split_msglevel(struct bstr *s, struct bstr *out_mod, int *out_level);
extern const char *const mp_log_levels[MSGL_MAX + 1];
extern const int mp_mpv_log_levels[MSGL_MAX + 1];
#endif
-1
View File
@@ -16,7 +16,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "common.h"
#include "version.h"
#ifdef NO_BUILD_TIMESTAMPS
#undef BUILDDATE
+2 -2
View File
@@ -352,8 +352,8 @@ static const char *lookup_tag(const struct mp_codec_tag *mp_table,
return id == AV_CODEC_ID_NONE ? NULL : mp_codec_from_av_codec_id(id);
}
static const char *const pcm_le[] = {"pcm_u8", "pcm_s16le", "pcm_s24le", "pcm_s32le"};
static const char *const pcm_be[] = {"pcm_s8", "pcm_s16be", "pcm_s24be", "pcm_s32be"};
static const char *pcm_le[] = {"pcm_u8", "pcm_s16le", "pcm_s24le", "pcm_s32le"};
static const char *pcm_be[] = {"pcm_s8", "pcm_s16be", "pcm_s24be", "pcm_s32be"};
static const char *map_audio_pcm_tag(uint32_t tag, int bits)
{
+44 -115
View File
@@ -33,7 +33,6 @@
#include "talloc.h"
#include "common/msg.h"
#include "common/global.h"
#include "osdep/threads.h"
#include "stream/stream.h"
#include "demux.h"
@@ -123,15 +122,12 @@ struct demux_internal {
double seek_pts;
// Cached state.
bool force_cache_update;
double time_length;
struct mp_tags *stream_metadata;
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;
};
struct demux_stream {
@@ -149,11 +145,9 @@ struct demux_stream {
struct demux_packet *tail;
};
// Return "a", or if that is NOPTS, return "def".
#define PTS_OR_DEF(a, def) ((a) == MP_NOPTS_VALUE ? (def) : (a))
// If one of the values is NOPTS, always pick the other one.
#define MP_PTS_MIN(a, b) MPMIN(PTS_OR_DEF(a, b), PTS_OR_DEF(b, a))
#define MP_PTS_MAX(a, b) MPMAX(PTS_OR_DEF(a, b), PTS_OR_DEF(b, a))
#define MP_PTS_MIN(a, b) ((a) == MP_NOPTS_VALUE || ((a) > (b)) ? (b) : (a))
#define MP_PTS_MAX(a, b) ((a) == MP_NOPTS_VALUE || ((a) < (b)) ? (b) : (a))
static void demuxer_sort_chapters(demuxer_t *demuxer);
static void *demux_thread(void *pctx);
@@ -195,7 +189,6 @@ struct sh_stream *new_sh_stream(demuxer_t *demuxer, enum stream_type type)
*sh = (struct sh_stream) {
.type = type,
.index = demuxer->num_streams,
.ff_index = demuxer->num_streams,
.demuxer_id = demuxer_id, // may be overwritten by demuxer
.ds = talloc(sh, struct demux_stream),
};
@@ -390,9 +383,10 @@ static bool read_packet(struct demux_internal *in)
pthread_mutex_unlock(&in->lock);
struct demuxer *demux = in->d_thread;
bool eof = !demux->desc->fill_buffer || demux->desc->fill_buffer(demux) <= 0;
update_cache(in);
pthread_mutex_lock(&in->lock);
update_cache(in);
if (eof) {
for (int n = 0; n < in->d_buffer->num_streams; n++) {
struct demux_stream *ds = in->d_buffer->streams[n]->ds;
@@ -461,7 +455,6 @@ static void execute_seek(struct demux_internal *in)
static void *demux_thread(void *pctx)
{
struct demux_internal *in = pctx;
mpthread_set_name("demux");
pthread_mutex_lock(&in->lock);
while (!in->thread_terminate) {
in->thread_paused = in->thread_request_pause > 0;
@@ -482,13 +475,7 @@ static void *demux_thread(void *pctx)
if (read_packet(in))
continue; // read_packet unlocked, so recheck conditions
}
if (in->force_cache_update) {
pthread_mutex_unlock(&in->lock);
update_cache(in);
pthread_mutex_lock(&in->lock);
in->force_cache_update = false;
continue;
}
update_cache(in);
pthread_cond_signal(&in->wakeup);
pthread_cond_wait(&in->wakeup, &in->lock);
}
@@ -766,10 +753,10 @@ void demux_changed(demuxer_t *demuxer, int events)
demuxer->events |= events;
update_cache(in);
pthread_mutex_lock(&in->lock);
update_cache(in);
if (demuxer->events & DEMUX_EVENT_INIT)
demuxer_sort_chapters(demuxer);
if (demuxer->events & (DEMUX_EVENT_METADATA | DEMUX_EVENT_STREAMS))
@@ -787,26 +774,15 @@ void demux_update(demuxer_t *demuxer)
assert(demuxer == demuxer->in->d_user);
struct demux_internal *in = demuxer->in;
pthread_mutex_lock(&in->lock);
if (!in->threading)
update_cache(in);
pthread_mutex_lock(&in->lock);
demux_copy(demuxer, in->d_buffer);
if (in->stream_metadata && (demuxer->events & DEMUX_EVENT_METADATA))
mp_tags_merge(demuxer->metadata, in->stream_metadata);
pthread_mutex_unlock(&in->lock);
}
static void demux_init_cache(struct demuxer *demuxer)
{
struct demux_internal *in = demuxer->in;
struct stream *stream = demuxer->stream;
char *base = NULL;
stream_control(stream, STREAM_CTRL_GET_BASE_FILENAME, &base);
in->stream_base_filename = talloc_steal(demuxer, base);
}
static struct demuxer *open_given_type(struct mpv_global *global,
struct mp_log *log,
const struct demuxer_desc *desc,
@@ -854,18 +830,13 @@ static struct demuxer *open_given_type(struct mpv_global *global,
in->d_user->metadata = talloc_zero(in->d_user, struct mp_tags);
in->d_buffer->metadata = talloc_zero(in->d_buffer, struct mp_tags);
int64_t start_pos = stream_tell(stream);
mp_verbose(log, "Trying demuxer: %s (force-level: %s)\n",
desc->name, d_level(check));
in->d_thread->params = params; // temporary during open()
if (stream->seekable) // not for DVD/BD/DVB in particular
stream_seek(stream, 0);
// Peek this much data to avoid that stream_read() run by some demuxers
// or stream filters will flush previous peeked data.
stream_peek(stream, STREAM_BUFFER_SIZE);
int ret = demuxer->desc->open(in->d_thread, check);
if (ret >= 0) {
in->d_thread->params = NULL;
@@ -874,20 +845,19 @@ static struct demuxer *open_given_type(struct mpv_global *global,
in->d_thread->filetype, desc->desc);
else
mp_verbose(log, "Detected file format: %s\n", desc->desc);
if (!in->d_thread->seekable)
mp_warn(log, "Stream is not seekable.\n");
// Pretend we can seek if we can't seek, but there's a cache.
if (!in->d_thread->seekable && stream->uncached_stream) {
mp_warn(log, "Enabling seeking because stream cache is active.\n");
mp_warn(log,
"File is not seekable, but there's a cache: enabling seeking.\n");
in->d_thread->seekable = true;
}
demux_init_cache(demuxer);
demux_changed(in->d_thread, DEMUX_EVENT_ALL);
demux_update(demuxer);
return demuxer;
}
free_demuxer(demuxer);
stream_seek(stream, start_pos);
return NULL;
}
@@ -923,6 +893,10 @@ struct demuxer *demux_open(struct stream *stream, char *force_format,
}
}
// Peek this much data to avoid that stream_read() run by some demuxers
// or stream filters will flush previous peeked data.
stream_peek(stream, STREAM_BUFFER_SIZE);
// Test demuxers from first to last, one pass for each check_levels[] entry
for (int pass = 0; check_levels[pass] != -1; pass++) {
enum demux_check level = check_levels[pass];
@@ -963,7 +937,7 @@ void demux_flush(demuxer_t *demuxer)
pthread_mutex_unlock(&demuxer->in->lock);
}
int demux_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
int demux_seek(demuxer_t *demuxer, float rel_seek_secs, int flags)
{
struct demux_internal *in = demuxer->in;
assert(demuxer == in->d_user);
@@ -973,22 +947,9 @@ int demux_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
return 0;
}
if ((flags & SEEK_FACTOR) && !(flags & SEEK_ABSOLUTE)) {
MP_WARN(demuxer, "Invalid seek flags.\n");
return 0;
}
if (rel_seek_secs == MP_NOPTS_VALUE && (flags & SEEK_ABSOLUTE))
return 0;
if (!(flags & (SEEK_BACKWARD | SEEK_FORWARD))) {
if (flags & SEEK_ABSOLUTE || rel_seek_secs < 0) {
flags |= SEEK_BACKWARD;
} else {
flags |= SEEK_FORWARD;
}
}
pthread_mutex_lock(&in->lock);
flush_locked(demuxer);
@@ -1086,9 +1047,9 @@ static int chapter_compare(const void *p1, const void *p2)
struct demux_chapter *c1 = (void *)p1;
struct demux_chapter *c2 = (void *)p2;
if (c1->pts > c2->pts)
if (c1->start > c2->start)
return 1;
else if (c1->pts < c2->pts)
else if (c1->start < c2->start)
return -1;
return c1->original_index > c2->original_index ? 1 :-1; // never equal
}
@@ -1100,11 +1061,12 @@ static void demuxer_sort_chapters(demuxer_t *demuxer)
}
int demuxer_add_chapter(demuxer_t *demuxer, struct bstr name,
double pts, uint64_t demuxer_id)
uint64_t start, uint64_t end, uint64_t demuxer_id)
{
struct demux_chapter new = {
.original_index = demuxer->num_chapters,
.pts = pts,
.start = start,
.end = end,
.name = name.len ? bstrdup0(demuxer, name) : NULL,
.metadata = talloc_zero(demuxer, struct mp_tags),
.demuxer_id = demuxer_id,
@@ -1122,53 +1084,42 @@ double demuxer_get_time_length(struct demuxer *demuxer)
return -1;
}
// must be called not locked
// must be called locked
static void update_cache(struct demux_internal *in)
{
struct demuxer *demuxer = in->d_thread;
struct stream *stream = demuxer->stream;
// Don't lock while querying the stream.
double time_length = -1;
struct mp_tags *stream_metadata = NULL;
int64_t stream_size = -1;
int64_t stream_cache_size = -1;
int64_t stream_cache_fill = -1;
int stream_cache_idle = -1;
in->time_length = -1;
if (demuxer->desc->control) {
demuxer->desc->control(demuxer, DEMUXER_CTRL_GET_TIME_LENGTH,
&time_length);
&in->time_length);
}
stream_control(stream, STREAM_CTRL_GET_METADATA, &stream_metadata);
stream_control(stream, STREAM_CTRL_GET_SIZE, &stream_size);
stream_control(stream, STREAM_CTRL_GET_CACHE_SIZE, &stream_cache_size);
stream_control(stream, STREAM_CTRL_GET_CACHE_FILL, &stream_cache_fill);
stream_control(stream, STREAM_CTRL_GET_CACHE_IDLE, &stream_cache_idle);
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;
if (stream_metadata) {
struct mp_tags *s_meta = NULL;
stream_control(stream, STREAM_CTRL_GET_METADATA, &s_meta);
if (s_meta) {
talloc_free(in->stream_metadata);
in->stream_metadata = talloc_steal(in, stream_metadata);
in->stream_metadata = talloc_steal(in, s_meta);
in->d_buffer->events |= DEMUX_EVENT_METADATA;
}
pthread_mutex_unlock(&in->lock);
in->stream_size = -1;
stream_control(stream, STREAM_CTRL_GET_SIZE, &in->stream_size);
in->stream_cache_size = -1;
stream_control(stream, STREAM_CTRL_GET_CACHE_SIZE, &in->stream_cache_size);
in->stream_cache_fill = -1;
stream_control(stream, STREAM_CTRL_GET_CACHE_FILL, &in->stream_cache_fill);
in->stream_cache_idle = -1;
stream_control(stream, STREAM_CTRL_GET_CACHE_IDLE, &in->stream_cache_idle);
}
// must be called locked
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) {
in->force_cache_update = true;
if (in->stream_cache_size >= 0)
pthread_cond_signal(&in->wakeup);
}
switch (cmd) {
case STREAM_CTRL_GET_CACHE_SIZE:
@@ -1191,11 +1142,6 @@ static int cached_stream_control(struct demux_internal *in, int cmd, void *arg)
return STREAM_UNSUPPORTED;
*(int64_t *)arg = in->stream_size;
return STREAM_OK;
case STREAM_CTRL_GET_BASE_FILENAME:
if (!in->stream_base_filename)
return STREAM_UNSUPPORTED;
*(char **)arg = talloc_strdup(NULL, in->stream_base_filename);
return STREAM_OK;
}
return STREAM_ERROR;
}
@@ -1228,24 +1174,18 @@ static int cached_demux_control(struct demux_internal *in, int cmd, void *arg)
.ts_range = {MP_NOPTS_VALUE, MP_NOPTS_VALUE},
.ts_duration = -1,
};
int num_packets = 0;
for (int n = 0; n < in->d_user->num_streams; n++) {
struct demux_stream *ds = in->d_user->streams[n]->ds;
if (ds->active) {
r->underrun |= !ds->head && !ds->eof;
if (!ds->eof) {
r->ts_range[0] = MP_PTS_MAX(r->ts_range[0], ds->base_ts);
r->ts_range[1] = MP_PTS_MIN(r->ts_range[1], ds->last_ts);
}
num_packets += ds->packs;
r->underrun |= !ds->head;
r->ts_range[0] = MP_PTS_MAX(r->ts_range[0], ds->base_ts);
r->ts_range[1] = MP_PTS_MIN(r->ts_range[1], ds->last_ts);
}
}
r->idle = (in->idle && !r->underrun) || r->eof;
r->underrun &= !r->idle;
if (r->ts_range[0] != MP_NOPTS_VALUE && r->ts_range[1] != MP_NOPTS_VALUE)
r->ts_duration = MPMAX(0, r->ts_range[1] - r->ts_range[0]);
if (!num_packets || in->seeking)
r->ts_duration = 0;
r->ts_duration = r->ts_range[1] - r->ts_range[0];
return DEMUXER_CTRL_OK;
}
}
@@ -1326,14 +1266,3 @@ void demux_unpause(demuxer_t *demuxer)
pthread_cond_signal(&in->wakeup);
pthread_mutex_unlock(&in->lock);
}
struct demux_chapter *demux_copy_chapter_data(struct demux_chapter *c, int num)
{
struct demux_chapter *new = talloc_array(NULL, struct demux_chapter, num);
for (int n = 0; n < num; n++) {
new[n] = c[n];
new[n].name = talloc_strdup(new, new[n].name);
new[n].metadata = mp_tags_dup(new, new[n].metadata);
}
return new;
}
+4 -6
View File
@@ -123,7 +123,7 @@ typedef struct demuxer_desc {
typedef struct demux_chapter
{
int original_index;
double pts;
uint64_t start, end;
char *name;
struct mp_tags *metadata;
uint64_t demuxer_id; // for mapping to internal demuxer data structures
@@ -254,7 +254,7 @@ void demux_stop_thread(struct demuxer *demuxer);
void demux_set_wakeup_cb(struct demuxer *demuxer, void (*cb)(void *ctx), void *ctx);
void demux_flush(struct demuxer *demuxer);
int demux_seek(struct demuxer *demuxer, double rel_seek_secs, int flags);
int demux_seek(struct demuxer *demuxer, float rel_seek_secs, int flags);
int demux_control(struct demuxer *demuxer, int cmd, void *arg);
@@ -268,8 +268,8 @@ void demuxer_help(struct mp_log *log);
int demuxer_add_attachment(struct demuxer *demuxer, struct bstr name,
struct bstr type, struct bstr data);
int demuxer_add_chapter(demuxer_t *demuxer, struct bstr name,
double pts, uint64_t demuxer_id);
int demuxer_add_chapter(struct demuxer *demuxer, struct bstr name,
uint64_t start, uint64_t end, uint64_t demuxer_id);
double demuxer_get_time_length(struct demuxer *demuxer);
@@ -284,8 +284,6 @@ void demux_update(demuxer_t *demuxer);
struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
enum stream_type t, int id);
struct demux_chapter *demux_copy_chapter_data(struct demux_chapter *c, int num);
bool demux_matroska_uid_cmp(struct matroska_segment_uid *a,
struct matroska_segment_uid *b);
+6 -2
View File
@@ -35,9 +35,13 @@ static int try_open_file(struct demuxer *demuxer, enum demux_check check)
{
struct stream *s = demuxer->stream;
if (check >= DEMUX_CHECK_UNSAFE) {
bstr d = stream_peek(s, PROBE_SIZE);
if (d.len < 1 || !mp_probe_cue(d))
char buf[PROBE_SIZE];
int len = stream_read(s, buf, sizeof(buf));
if (len <= 0)
return -1;
if (!mp_probe_cue((struct bstr) { buf, len }))
return -1;
stream_seek(s, 0);
}
demuxer->file_contents = stream_read_complete(s, demuxer, 1000000);
if (demuxer->file_contents.start == NULL)
+1 -2
View File
@@ -143,7 +143,6 @@ static void add_streams(demuxer_t *demuxer)
sh->codec = talloc_strdup(sh, src->codec);
sh->format = src->format;
sh->lav_headers = src->lav_headers;
sh->demuxer_id = src->demuxer_id;
if (src->video) {
double ar;
if (stream_control(demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar)
@@ -275,7 +274,7 @@ static void add_stream_chapters(struct demuxer *demuxer)
double p = n;
if (stream_control(demuxer->stream, STREAM_CTRL_GET_CHAPTER_TIME, &p) < 1)
continue;
demuxer_add_chapter(demuxer, bstr0(""), p, 0);
demuxer_add_chapter(demuxer, bstr0(""), p * 1e9, 0, 0);
}
}
+10 -24
View File
@@ -101,7 +101,6 @@ typedef struct lavf_priv {
AVFormatContext *avfc;
AVIOContext *pb;
int64_t last_pts;
bool init_pts;
struct sh_stream **streams; // NULL for unknown streams
int num_streams;
int cur_program;
@@ -132,7 +131,6 @@ static const char *const format_blacklist[] = {
// Image demuxers, disabled in favor of demux_mf:
"image2", "image2pipe",
"bmp_pipe", "dpx_pipe", "exr_pipe", "j2k_pipe", "png_pipe", "tiff_pipe",
"jpeg_pipe",
0
};
@@ -190,11 +188,8 @@ static int64_t mp_read_seek(void *opaque, int stream_idx, int64_t ts, int flags)
.flags = flags,
};
if (stream_control(stream, STREAM_CTRL_AVSEEK, &cmd) == STREAM_OK) {
stream_drop_buffers(stream);
return 0;
}
return AVERROR(ENOSYS);
int ret = stream_control(stream, STREAM_CTRL_AVSEEK, &cmd);
return ret < 1 ? AVERROR(ENOSYS) : 0;
}
static void list_formats(struct demuxer *demuxer)
@@ -591,7 +586,6 @@ static void handle_stream(demuxer_t *demuxer, int i)
MP_TARRAY_APPEND(priv, priv->streams, priv->num_streams, sh);
if (sh) {
sh->ff_index = st->index;
sh->codec = mp_codec_from_av_codec_id(codec->codec_id);
sh->lav_headers = codec;
@@ -711,13 +705,9 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
"analyzeduration to %f\n", analyze_duration);
}
AVDictionary *dopts = NULL;
if ((priv->avif->flags & AVFMT_NOFILE) ||
demuxer->stream->type == STREAMTYPE_AVDEVICE ||
matches_avinputformat_name(priv, "hls"))
demuxer->stream->type == STREAMTYPE_AVDEVICE)
{
mp_setup_av_network_options(&dopts, demuxer->global, demuxer->log, opts);
// This might be incorrect.
demuxer->seekable = true;
} else {
@@ -733,10 +723,10 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
priv->pb->read_seek = mp_read_seek;
priv->pb->seekable = demuxer->seekable ? AVIO_SEEKABLE_NORMAL : 0;
avfc->pb = priv->pb;
if (stream_control(demuxer->stream, STREAM_CTRL_HAS_AVSEEK, NULL) > 0)
demuxer->seekable = true;
}
AVDictionary *dopts = NULL;
if (matches_avinputformat_name(priv, "rtsp")) {
const char *transport = NULL;
switch (opts->network_rtsp_transport) {
@@ -770,9 +760,13 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
for (i = 0; i < avfc->nb_chapters; i++) {
AVChapter *c = avfc->chapters[i];
uint64_t start = av_rescale_q(c->start, c->time_base,
(AVRational){1, 1000000000});
uint64_t end = av_rescale_q(c->end, c->time_base,
(AVRational){1, 1000000000});
t = av_dict_get(c->metadata, "title", NULL, 0);
int index = demuxer_add_chapter(demuxer, t ? bstr0(t->value) : bstr0(""),
c->start * av_q2d(c->time_base), i);
start, end, i);
mp_tags_copy_from_av_dictionary(demuxer->chapters[index].metadata, c->metadata);
}
@@ -833,12 +827,6 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
return 1;
}
if (!priv->init_pts && (priv->avfc->flags & AVFMT_NOTIMESTAMPS)) {
if (pkt->pts == AV_NOPTS_VALUE && pkt->dts == AV_NOPTS_VALUE)
pkt->dts = 0;
priv->init_pts = true;
}
if (pkt->pts != AV_NOPTS_VALUE)
dp->pts = pkt->pts * av_q2d(st->time_base);
if (pkt->dts != AV_NOPTS_VALUE)
@@ -863,8 +851,6 @@ static void demux_seek_lavf(demuxer_t *demuxer, double rel_seek_secs, int flags)
lavf_priv_t *priv = demuxer->priv;
int avsflags = 0;
priv->init_pts = false;
if (flags & SEEK_ABSOLUTE)
priv->last_pts = 0;
else if (rel_seek_secs < 0)
+71 -200
View File
@@ -112,7 +112,6 @@ typedef struct mkv_track {
float a_osfreq;
double default_duration;
double codec_delay;
int default_track;
@@ -131,13 +130,13 @@ typedef struct mkv_track {
double ra_pts; /* previous audio timestamp */
/** realaudio descrambling */
uint32_t sub_packet_size; ///< sub packet size, per stream
uint32_t sub_packet_h; ///< number of coded frames per block
uint16_t sub_packet_size; ///< sub packet size, per stream
uint16_t sub_packet_h; ///< number of coded frames per block
uint32_t coded_framesize; ///< coded frame size, per stream
uint32_t audiopk_size; ///< audio packet size
uint16_t audiopk_size; ///< audio packet size
unsigned char *audio_buf; ///< place to store reordered audio data
double *audio_timestamp; ///< timestamp for each audio packet
uint32_t sub_packet_cnt; ///< number of subpacket already received
uint16_t sub_packet_cnt; ///< number of subpacket already received
int audio_filepos; ///< file position of first audio packet in block
/* generic content encoding support */
@@ -150,8 +149,7 @@ typedef struct mkv_track {
typedef struct mkv_index {
int tnum;
uint64_t timecode, duration;
uint64_t filepos; // position of the cluster which contains the packet
uint64_t timecode, filepos;
} mkv_index_t;
typedef struct mkv_demuxer {
@@ -184,10 +182,7 @@ typedef struct mkv_demuxer {
uint64_t skip_to_timecode;
int v_skip_to_keyframe, a_skip_to_keyframe;
int a_skip_preroll;
int subtitle_preroll;
bool index_has_durations;
} mkv_demuxer_t;
#define REALHEADER_SIZE 16
@@ -199,8 +194,6 @@ typedef struct mkv_demuxer {
// (Subtitle packets added before first A/V keyframe packet is found with seek.)
#define NUM_SUB_PREROLL_PACKETS 500
static void probe_last_timestamp(struct demuxer *demuxer);
#define AAC_SYNC_EXTENSION_TYPE 0x02b7
static int aac_get_sample_rate_index(uint32_t sample_rate)
{
@@ -633,9 +626,6 @@ static void parse_trackentry(struct demuxer *demuxer,
if (entry->n_content_encodings)
parse_trackencodings(demuxer, track, &entry->content_encodings);
if (entry->n_codec_delay)
track->codec_delay = entry->codec_delay / 1e9;
mkv_d->tracks[mkv_d->num_tracks++] = track;
}
@@ -662,40 +652,34 @@ static int demux_mkv_read_tracks(demuxer_t *demuxer)
}
static void cue_index_add(demuxer_t *demuxer, int track_id, uint64_t filepos,
uint64_t timecode, uint64_t duration)
uint64_t timecode)
{
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
MP_TARRAY_GROW(mkv_d, mkv_d->indexes, mkv_d->num_indexes);
mkv_d->indexes[mkv_d->num_indexes] = (mkv_index_t) {
.tnum = track_id,
.filepos = filepos,
.timecode = timecode,
.duration = duration,
};
mkv_d->indexes[mkv_d->num_indexes].tnum = track_id;
mkv_d->indexes[mkv_d->num_indexes].timecode = timecode;
mkv_d->indexes[mkv_d->num_indexes].filepos = filepos;
mkv_d->num_indexes++;
}
static void add_block_position(demuxer_t *demuxer, struct mkv_track *track,
uint64_t filepos,
uint64_t timecode, uint64_t duration)
uint64_t filepos, uint64_t timecode)
{
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
if (mkv_d->index_complete || !track)
return;
mkv_d->index_has_durations = true;
if (track->last_index_entry != (size_t)-1) {
mkv_index_t *index = &mkv_d->indexes[track->last_index_entry];
// Never add blocks which are already covered by the index.
if (index->timecode >= timecode)
// filepos is always the cluster position, which can contain multiple
// blocks with different timecodes - one is enough.
// Also, never add block which are already covered by the index.
if (index->filepos == filepos || index->timecode >= timecode)
return;
}
cue_index_add(demuxer, track->tnum, filepos, timecode, duration);
cue_index_add(demuxer, track->tnum, filepos, timecode);
track->last_index_entry = mkv_d->num_indexes - 1;
}
@@ -719,7 +703,6 @@ static int demux_mkv_read_cues(demuxer_t *demuxer)
return -1;
mkv_d->num_indexes = 0;
mkv_d->index_has_durations = false;
for (int i = 0; i < cues.n_cue_point; i++) {
struct ebml_cue_point *cuepoint = &cues.cue_point[i];
@@ -732,14 +715,10 @@ static int demux_mkv_read_cues(demuxer_t *demuxer)
struct ebml_cue_track_positions *trackpos =
&cuepoint->cue_track_positions[c];
uint64_t pos = mkv_d->segment_start + trackpos->cue_cluster_position;
cue_index_add(demuxer, trackpos->cue_track, pos,
time, trackpos->cue_duration);
mkv_d->index_has_durations |= trackpos->n_cue_duration > 0;
cue_index_add(demuxer, trackpos->cue_track, pos, time);
MP_DBG(demuxer, "|+ found cue point for track %" PRIu64
": timecode %" PRIu64 ", filepos: %" PRIu64
" offset %" PRIu64 ", duration %" PRIu64 "\n",
trackpos->cue_track, time, pos,
trackpos->cue_relative_position, trackpos->cue_duration);
": timecode %" PRIu64 ", filepos: %" PRIu64 "\n",
trackpos->cue_track, time, pos);
}
}
@@ -900,7 +879,7 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
BSTR_P(name));
if (idx == selected_edition) {
demuxer_add_chapter(demuxer, name, chapter.start / 1e9,
demuxer_add_chapter(demuxer, name, chapter.start, chapter.end,
ca->chapter_uid);
}
if (m_chapters) {
@@ -1170,7 +1149,7 @@ static const videocodec_info_t vinfo[] = {
static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
{
unsigned char *extradata = NULL;
unsigned char *extradata;
unsigned int extradata_size = 0;
bool raw = false;
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_VIDEO);
@@ -1471,7 +1450,7 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
/* Common initialization for all RealAudio codecs */
unsigned char *src = track->private_data;
int codecdata_length, version;
unsigned int flavor;
int flavor;
sh_a->bitrate = 0; /* FIXME !? */
@@ -1507,20 +1486,14 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
switch (track->a_formattag) {
case MP_FOURCC('a', 't', 'r', 'c'):
if (flavor >= MP_ARRAY_SIZE(atrc_fl2bps))
goto error;
sh_a->bitrate = atrc_fl2bps[flavor] * 8;
sh_a->block_align = track->sub_packet_size;
goto audiobuf;
case MP_FOURCC('c', 'o', 'o', 'k'):
if (flavor >= MP_ARRAY_SIZE(cook_fl2bps))
goto error;
sh_a->bitrate = cook_fl2bps[flavor] * 8;
sh_a->block_align = track->sub_packet_size;
goto audiobuf;
case MP_FOURCC('s', 'i', 'p', 'r'):
if (flavor >= MP_ARRAY_SIZE(sipr_fl2bps))
goto error;
sh_a->bitrate = sipr_fl2bps[flavor] * 8;
sh_a->block_align = track->coded_framesize;
goto audiobuf;
@@ -1765,13 +1738,6 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
int64_t start_pos;
int64_t end_pos;
bstr start = stream_peek(s, 4);
uint32_t start_id = 0;
for (int n = 0; n < start.len; n++)
start_id = (start_id << 8) | start.start[n];
if (start_id != EBML_ID_EBML)
return -1;
if (!read_ebml_header(demuxer))
return -1;
MP_VERBOSE(demuxer, "Found the head...\n");
@@ -1784,7 +1750,6 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
mkv_d->tc_scale = 1000000;
mkv_d->segment_start = stream_tell(s);
mkv_d->segment_end = end_pos;
mkv_d->a_skip_preroll = 1;
if (demuxer->params && demuxer->params->matroska_was_valid)
*demuxer->params->matroska_was_valid = true;
@@ -1853,9 +1818,6 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
process_tags(demuxer);
display_create_tracks(demuxer);
if (demuxer->opts->mkv_probe_duration)
probe_last_timestamp(demuxer);
return 0;
}
@@ -2013,11 +1975,11 @@ static void handle_realaudio(demuxer_t *demuxer, mkv_track_t *track,
bstr data, bool keyframe)
{
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
uint32_t sps = track->sub_packet_size;
uint32_t sph = track->sub_packet_h;
uint16_t sps = track->sub_packet_size;
uint16_t sph = track->sub_packet_h;
uint32_t cfs = track->coded_framesize; // restricted to [1,0x40000000]
uint32_t w = track->audiopk_size;
uint32_t spc = track->sub_packet_cnt;
uint16_t w = track->audiopk_size;
uint16_t spc = track->sub_packet_cnt;
uint8_t *buffer = data.start;
uint32_t size = data.len;
demux_packet_t *dp;
@@ -2296,7 +2258,7 @@ static bool mkv_parse_packet(mkv_track_t *track, bstr *raw, bstr *out)
}
struct block_info {
uint64_t duration, discardpadding;
uint64_t duration;
bool simple, keyframe;
uint64_t timecode;
mkv_track_t *track;
@@ -2317,8 +2279,7 @@ static void index_block(demuxer_t *demuxer, struct block_info *block)
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
if (block->keyframe) {
add_block_position(demuxer, block->track, mkv_d->cluster_start,
block->timecode / mkv_d->tc_scale,
block->duration / mkv_d->tc_scale);
block->timecode / mkv_d->tc_scale);
}
}
@@ -2397,7 +2358,7 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
return 0;
}
current_pts = tc / 1e9 - track->codec_delay;
current_pts = tc / 1e9;
if (track->type == MATROSKA_TRACK_AUDIO) {
if (mkv_d->a_skip_to_keyframe)
@@ -2446,7 +2407,6 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
if (!dp)
break;
dp->keyframe = keyframe;
dp->pos = mkv_d->last_filepos;
/* If default_duration is 0, assume no pts value is known
* for packets after the first one (rather than all pts
* values being the same). Also, don't use it for extra
@@ -2457,13 +2417,6 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
MPSWAP(double, dp->pts, dp->dts);
if (p == 0)
dp->duration = block_duration / 1e9;
if (stream->type == STREAM_AUDIO) {
unsigned int srate = track->a_sfreq;
demux_packet_set_padding(dp,
mkv_d->a_skip_preroll ? track->codec_delay * srate : 0,
block_info->discardpadding / 1e9 * srate);
mkv_d->a_skip_preroll = 0;
}
demux_add_packet(stream, dp);
p++;
}
@@ -2502,12 +2455,6 @@ static int read_block_group(demuxer_t *demuxer, int64_t end,
block->duration *= mkv_d->tc_scale;
break;
case MATROSKA_ID_DISCARDPADDING:
block->discardpadding = ebml_read_uint(s);
if (block->discardpadding == EBML_UINT_INVALID)
goto error;
break;
case MATROSKA_ID_BLOCK:
if (read_block(demuxer, end, block) < 0)
goto error;
@@ -2733,39 +2680,14 @@ static struct mkv_index *seek_with_cues(struct demuxer *demuxer, int seek_id,
if (index) { /* We've found an entry. */
uint64_t seek_pos = index->filepos;
if (flags & SEEK_SUBPREROLL) {
// Find the cluster with the highest filepos, that has a timestamp
// still lower than min_tc.
double secs = demuxer->opts->mkv_subtitle_preroll_secs;
uint64_t pre = MPMIN(INT64_MAX, secs * 1e9 / mkv_d->tc_scale);
uint64_t min_tc = pre < index->timecode ? index->timecode - pre : 0;
uint64_t prev_target = 0;
uint64_t prev_tc = 0;
for (size_t i = 0; i < mkv_d->num_indexes; i++) {
if (seek_id < 0 || mkv_d->indexes[i].tnum == seek_id) {
struct mkv_index *cur = &mkv_d->indexes[i];
if (cur->timecode <= min_tc && cur->timecode >= prev_tc) {
prev_tc = cur->timecode;
prev_target = cur->filepos;
}
uint64_t index_pos = mkv_d->indexes[i].filepos;
if (index_pos > prev_target && index_pos < seek_pos)
prev_target = index_pos;
}
}
if (mkv_d->index_has_durations) {
// Find the earliest cluster that is not before prev_target,
// but contains subtitle packets overlapping with the cluster
// at seek_pos.
uint64_t target = seek_pos;
for (size_t i = 0; i < mkv_d->num_indexes; i++) {
struct mkv_index *cur = &mkv_d->indexes[i];
if (cur->timecode <= index->timecode &&
cur->timecode + cur->duration > index->timecode &&
cur->filepos >= prev_target &&
cur->filepos < target)
{
target = cur->filepos;
}
}
prev_target = target;
}
if (prev_target)
seek_pos = prev_target;
}
@@ -2797,7 +2719,13 @@ static void demux_mkv_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
mkv_d->subtitle_preroll = NUM_SUB_PREROLL_PACKETS;
if (!st_active[STREAM_SUB] || !st_active[STREAM_VIDEO])
flags &= ~SEEK_SUBPREROLL;
if (!(flags & (SEEK_BACKWARD | SEEK_FORWARD))) {
if (flags & SEEK_ABSOLUTE || rel_seek_secs < 0) {
flags |= SEEK_BACKWARD;
} else {
flags |= SEEK_FORWARD;
}
}
// Adjust the target a little bit to catch cases where the target position
// specifies a keyframe with high, but not perfect, precision.
rel_seek_secs += flags & SEEK_FORWARD ? -0.005 : 0.005;
@@ -2820,115 +2748,58 @@ static void demux_mkv_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
if (!index)
stream_seek(demuxer->stream, old_pos);
mkv_d->v_skip_to_keyframe = st_active[STREAM_VIDEO];
mkv_d->a_skip_to_keyframe = st_active[STREAM_AUDIO];
if (flags & SEEK_FORWARD) {
mkv_d->skip_to_timecode = target_timecode;
} else {
mkv_d->skip_to_timecode = index ? index->timecode * mkv_d->tc_scale
: 0;
}
} else {
demux_mkv_fill_buffer(demuxer);
} else if (!(flags & SEEK_ABSOLUTE))
MP_VERBOSE(demuxer, "seek unsupported flags\n");
else {
stream_t *s = demuxer->stream;
uint64_t target_filepos;
mkv_index_t *index = NULL;
read_deferred_cues(demuxer);
if (!mkv_d->index_complete) { /* not implemented without index */
MP_VERBOSE(demuxer, "seek unsupported flags\n");
stream_seek(s, old_pos);
return;
}
int64_t size = 0;
stream_control(s, STREAM_CTRL_GET_SIZE, &size);
int64_t target_filepos = size * MPCLAMP(rel_seek_secs, 0, 1);
target_filepos = (uint64_t) (size * rel_seek_secs);
for (size_t i = 0; i < mkv_d->num_indexes; i++)
if (mkv_d->indexes[i].tnum == v_tnum)
if ((index == NULL)
|| ((mkv_d->indexes[i].filepos >= target_filepos)
&& ((index->filepos < target_filepos)
|| (mkv_d->indexes[i].filepos < index->filepos))))
index = &mkv_d->indexes[i];
mkv_index_t *index = NULL;
if (mkv_d->index_complete) {
for (size_t i = 0; i < mkv_d->num_indexes; i++) {
if (mkv_d->indexes[i].tnum == v_tnum) {
if ((index == NULL)
|| ((mkv_d->indexes[i].filepos >= target_filepos)
&& ((index->filepos < target_filepos)
|| (mkv_d->indexes[i].filepos < index->filepos))))
index = &mkv_d->indexes[i];
}
}
if (!index) {
stream_seek(s, old_pos);
return;
}
mkv_d->cluster_end = 0;
stream_seek(s, index->filepos);
if (index) {
stream_seek(s, index->filepos);
mkv_d->skip_to_timecode = index->timecode * mkv_d->tc_scale;
} else {
stream_seek(s, target_filepos);
if (ebml_resync_cluster(mp_null_log, s) < 0) {
// Assume EOF
mkv_d->cluster_end = size;
}
}
mkv_d->v_skip_to_keyframe = st_active[STREAM_VIDEO];
mkv_d->a_skip_to_keyframe = st_active[STREAM_AUDIO];
mkv_d->skip_to_timecode = index->timecode * mkv_d->tc_scale;
demux_mkv_fill_buffer(demuxer);
}
mkv_d->v_skip_to_keyframe = st_active[STREAM_VIDEO];
mkv_d->a_skip_to_keyframe = st_active[STREAM_AUDIO];
mkv_d->a_skip_preroll = mkv_d->a_skip_to_keyframe;
demux_mkv_fill_buffer(demuxer);
}
static void probe_last_timestamp(struct demuxer *demuxer)
{
mkv_demuxer_t *mkv_d = demuxer->priv;
int64_t old_pos = stream_tell(demuxer->stream);
if (!demuxer->seekable)
return;
// Pick some arbitrary video track
int v_tnum = -1;
for (int n = 0; n < mkv_d->num_tracks; n++) {
if (mkv_d->tracks[n]->type == MATROSKA_TRACK_VIDEO) {
v_tnum = mkv_d->tracks[n]->tnum;
break;
}
}
if (v_tnum < 0)
return;
read_deferred_cues(demuxer);
if (!mkv_d->index_complete)
return;
// Find last cluster that still has video packets
int64_t target = 0;
for (size_t i = 0; i < mkv_d->num_indexes; i++) {
struct mkv_index *cur = &mkv_d->indexes[i];
if (cur->tnum == v_tnum)
target = MPMAX(target, cur->filepos);
}
if (!target)
return;
if (!stream_seek(demuxer->stream, target))
return;
int64_t last_ts[STREAM_TYPE_COUNT] = {0};
while (1) {
struct block_info block;
int res = read_next_block(demuxer, &block);
if (res < 0)
break;
if (res > 0) {
if (block.track && block.track->stream) {
enum stream_type type = block.track->stream->type;
uint64_t endtime = block.timecode + block.duration;
if (last_ts[type] < endtime)
last_ts[type] = endtime;
}
free_block(&block);
}
}
if (last_ts[STREAM_VIDEO])
mkv_d->duration = last_ts[STREAM_VIDEO] / 1e9;
stream_seek(demuxer->stream, old_pos);
mkv_d->cluster_start = mkv_d->cluster_end = 0;
}
static int demux_mkv_control(demuxer_t *demuxer, int cmd, void *arg)
+14 -30
View File
@@ -27,17 +27,6 @@
#define PROBE_SIZE (8 * 1024)
static bool check_mimetype(struct stream *s, const char *const *list)
{
if (s->mime_type) {
for (int n = 0; list && list[n]; n++) {
if (strcmp(s->mime_type, list[n]) == 0)
return true;
}
}
return false;
}
struct pl_parser {
struct mp_log *log;
struct stream *s;
@@ -99,22 +88,6 @@ static int parse_ref_init(struct pl_parser *p)
bstr line = bstr_strip(pl_get_line(p));
if (!bstr_equals0(line, "[Reference]"))
return -1;
// ASF http streaming redirection - this is needed because ffmpeg http://
// and mmsh:// can not automatically switch automatically between each
// others. Both protocols use http - MMSH requires special http headers
// to "activate" it, and will in other cases return this playlist.
static const char *const mmsh_types[] = {"audio/x-ms-wax",
"audio/x-ms-wma", "video/x-ms-asf", "video/x-ms-afs", "video/x-ms-wmv",
"video/x-ms-wma", "application/x-mms-framed",
"application/vnd.ms.wms-hdr.asfv1", NULL};
bstr burl = bstr0(p->s->url);
if (bstr_eatstart0(&burl, "http://") && check_mimetype(p->s, mmsh_types)) {
MP_INFO(p, "Redirectiong to mmsh://\n");
playlist_add_file(p->pl, talloc_asprintf(p, "mmsh://%.*s", BSTR_P(burl)));
return 0;
}
while (!pl_eof(p)) {
line = bstr_strip(pl_get_line(p));
if (bstr_case_startswith(line, bstr0("Ref"))) {
@@ -180,15 +153,15 @@ static int parse_txt(struct pl_parser *p)
return 0;
}
#define MIME_TYPES(...) \
.mime_types = (const char*const[]){__VA_ARGS__, NULL}
struct pl_format {
const char *name;
int (*parse)(struct pl_parser *p);
const char *const *mime_types;
};
#define MIME_TYPES(...) \
.mime_types = (const char*const[]){__VA_ARGS__, NULL}
static const struct pl_format formats[] = {
{"m3u", parse_m3u,
MIME_TYPES("audio/mpegurl", "audio/x-mpegurl", "application/x-mpegurl")},
@@ -199,6 +172,17 @@ static const struct pl_format formats[] = {
{"txt", parse_txt},
};
static bool check_mimetype(struct stream *s, const char *const *list)
{
if (s->mime_type) {
for (int n = 0; list && list[n]; n++) {
if (strcmp(s->mime_type, list[n]) == 0)
return true;
}
}
return false;
}
static const struct pl_format *probe_pl(struct pl_parser *p)
{
int64_t start = stream_tell(p->s);
+4 -4
View File
@@ -173,7 +173,7 @@ uint64_t ebml_read_uint(stream_t *s)
*/
int64_t ebml_read_int(stream_t *s)
{
uint64_t value = 0;
int64_t value = 0;
uint64_t len;
int l;
@@ -189,7 +189,7 @@ int64_t ebml_read_int(stream_t *s)
while (len--)
value = (value << 8) | stream_read_char(s);
return (int64_t)value; // assume complement of 2
return value;
}
/*
@@ -328,12 +328,12 @@ static uint64_t ebml_parse_uint(uint8_t *data, int length)
static int64_t ebml_parse_sint(uint8_t *data, int length)
{
assert(length >=1 && length <= 8);
uint64_t r = 0;
int64_t r = 0;
if (*data & 0x80)
r = -1;
while (length--)
r = (r << 8) | *data++;
return (int64_t)r; // assume complement of 2
return r;
}
static double ebml_parse_float(uint8_t *data, int length)
-20
View File
@@ -20,9 +20,6 @@
#include <assert.h>
#include <libavcodec/avcodec.h>
#include <libavutil/intreadwrite.h>
#include "config.h"
#include "common/av_common.h"
#include "common/common.h"
@@ -116,20 +113,3 @@ struct demux_packet *demux_copy_packet(struct demux_packet *dp)
new->duration = dp->duration;
return new;
}
int demux_packet_set_padding(struct demux_packet *dp, int start, int end)
{
#if HAVE_AVFRAME_SKIP_SAMPLES
if (!start && !end)
return 0;
if (!dp->avpacket)
return -1;
uint8_t *p = av_packet_new_side_data(dp->avpacket, AV_PKT_DATA_SKIP_SAMPLES, 10);
if (!p)
return -1;
AV_WL32(p + 0, start);
AV_WL32(p + 4, end);
#endif
return 0;
}
-2
View File
@@ -44,6 +44,4 @@ void demux_packet_shorten(struct demux_packet *dp, size_t len);
void free_demux_packet(struct demux_packet *dp);
struct demux_packet *demux_copy_packet(struct demux_packet *dp);
int demux_packet_set_padding(struct demux_packet *dp, int start, int end);
#endif /* MPLAYER_DEMUX_PACKET_H */
+2 -3
View File
@@ -36,8 +36,6 @@ struct sh_stream {
// Demuxer/format specific ID. Corresponds to the stream IDs as encoded in
// some file formats (e.g. MPEG), or an index chosen by demux.c.
int demuxer_id;
// FFmpeg stream index (AVFormatContext.streams[index]), or equivalent.
int ff_index;
// One of these is non-NULL, the others are NULL, depending on the stream
// type.
struct sh_audio *audio;
@@ -50,7 +48,8 @@ struct sh_stream {
// Usually a FourCC, exact meaning depends on codec.
unsigned int format;
// Codec specific header data (set by demux_lavf.c only)
// Codec specific header data (set by demux_lavf.c)
// Other demuxers use sh_audio->wf and sh_video->bih instead.
struct AVCodecContext *lav_headers;
char *title;
+1 -5
View File
@@ -55,9 +55,6 @@
#Shift+LEFT no-osd seek -1 - exact
#Shift+UP no-osd seek 5 - exact
#Shift+DOWN no-osd seek -5 - exact
# Skip to previous/next subtitle (subject to some restrictions; see manpage)
#Ctrl+LEFT no-osd sub_seek -1
#Ctrl+RIGHT no-osd sub_seek 1
#PGUP add chapter 1 # skip to next chapter
#PGDWN add chapter -1 # skip to previous chapter
#Shift+PGUP seek 600
@@ -77,7 +74,7 @@
#, frame_back_step # go back by one frame and pause
#SPACE cycle pause
#> playlist_next # skip to next file
#ENTER playlist_next # skip to next file
#ENTER playlist_next force # skip to next file or quit
#< playlist_prev # skip to previous file
#O osd # cycle through OSD mode
#o show_progress
@@ -135,7 +132,6 @@
#CLOSE_WIN quit
#CLOSE_WIN {encode} quit
#E cycle edition # next edition
#l ab_loop # Set/clear A-B loop points
#ctrl+c quit
# Apple Remote section
+1 -1
View File
@@ -28,7 +28,7 @@ ENTER playlist_next force # skip to next file
p cycle pause
. frame_step # advance one frame and pause
SPACE cycle pause
HOME set playlist-pos 0 # not the same as MPlayer
HOME set playlist-pos 1 # not the same as MPlayer
#END pt_up_step -1
> playlist_next # skip to next file
< playlist_prev # previous
-35
View File
@@ -1,35 +0,0 @@
# This file contains all bindings that were removed after a certain release.
# If you want MPlayer bindings, use mplayer-input.conf
# Pick the bindings you want back and add them to your own input.conf. Append
# this file to your input.conf if you want them all back:
#
# cat restore-old-bindings.conf >> ~/.config/mpv/input.conf
#
# Older installations use ~/.mpv/input.conf instead.
# changed in mpv 0.7.0
ENTER playlist_next force
# changed in mpv 0.6.0
ESC quit
# changed in mpv 0.5.0
PGUP seek 600
PGDWN seek -600
RIGHT seek 10
LEFT seek -10
+ add audio-delay 0.100
- add audio-delay -0.100
( add balance -0.1
) add balance 0.1
F cycle sub-forced-only
TAB cycle program
A cycle angle
U stop
o osd
I show_text "${filename}"
+4 -19
View File
@@ -51,7 +51,6 @@
#define OARG_DOUBLE(def) OPT_DOUBLE(ARG(d), 0, OPTDEF_DOUBLE(def))
#define OARG_INT(def) OPT_INT(ARG(i), 0, OPTDEF_INT(def))
#define OARG_CHOICE(def, c) OPT_CHOICE(ARG(i), 0, c, OPTDEF_INT(def))
#define OARG_STRING(def) OPT_STRING(ARG(s), 0, OPTDEF_STR(def))
#define OARG_CYCLEDIR(def) OPT_CYCLEDIR(ARG(d), 0, OPTDEF_DOUBLE(def))
@@ -73,8 +72,7 @@ const struct mp_cmd_def mp_cmds[] = {
{ MP_CMD_QUIT, "quit", { OARG_INT(0) } },
{ MP_CMD_QUIT_WATCH_LATER, "quit_watch_later", { OARG_INT(0) } },
{ MP_CMD_STOP, "stop", },
{ MP_CMD_FRAME_STEP, "frame_step", .allow_auto_repeat = true,
.on_updown = true },
{ MP_CMD_FRAME_STEP, "frame_step", .allow_auto_repeat = true },
{ MP_CMD_FRAME_BACK_STEP, "frame_back_step", .allow_auto_repeat = true },
{ MP_CMD_PLAYLIST_NEXT, "playlist_next", {
OARG_CHOICE(0, ({"weak", 0}, {"0", 0},
@@ -91,9 +89,7 @@ const struct mp_cmd_def mp_cmds[] = {
{ MP_CMD_SHOW_TEXT, "show_text", { ARG_STRING, OARG_INT(-1), OARG_INT(0) },
.allow_auto_repeat = true},
{ MP_CMD_SHOW_PROGRESS, "show_progress", .allow_auto_repeat = true},
{ MP_CMD_SUB_ADD, "sub_add", { ARG_STRING,
OARG_CHOICE(0, ({"select", 0}, {"auto", 1}, {"cached", 2})),
OARG_STRING(""), OARG_STRING("") } },
{ MP_CMD_SUB_ADD, "sub_add", { ARG_STRING } },
{ MP_CMD_SUB_REMOVE, "sub_remove", { OARG_INT(-1) } },
{ MP_CMD_SUB_RELOAD, "sub_reload", { OARG_INT(-1) } },
@@ -156,20 +152,13 @@ const struct mp_cmd_def mp_cmds[] = {
{ MP_CMD_DISCNAV, "discnav", { ARG_STRING } },
{ MP_CMD_AB_LOOP, "ab_loop", },
{ MP_CMD_DROP_BUFFERS, "drop_buffers", },
{ MP_CMD_AF, "af", { ARG_STRING, ARG_STRING } },
{ MP_CMD_AO_RELOAD, "ao_reload", },
{ MP_CMD_VF, "vf", { ARG_STRING, ARG_STRING } },
{ MP_CMD_VO_CMDLINE, "vo_cmdline", { ARG_STRING } },
{ MP_CMD_SCRIPT_BINDING, "script_binding", { ARG_STRING },
.allow_auto_repeat = true, .on_updown = true},
{ MP_CMD_SCRIPT_DISPATCH, "script_dispatch", { ARG_STRING, ARG_INT } },
{ MP_CMD_SCRIPT_MESSAGE, "script_message", { ARG_STRING }, .vararg = true },
{ MP_CMD_SCRIPT_MESSAGE_TO, "script_message_to", { ARG_STRING, ARG_STRING },
.vararg = true },
@@ -181,9 +170,6 @@ const struct mp_cmd_def mp_cmds[] = {
{ MP_CMD_WRITE_WATCH_LATER_CONFIG, "write_watch_later_config", },
{ MP_CMD_HOOK_ADD, "hook_add", { ARG_STRING, ARG_INT, ARG_INT } },
{ MP_CMD_HOOK_ACK, "hook_ack", { ARG_STRING } },
{0}
};
@@ -300,8 +286,7 @@ bool mp_input_is_abort_cmd(struct mp_cmd *cmd)
bool mp_input_is_repeatable_cmd(struct mp_cmd *cmd)
{
return (cmd->def && cmd->def->allow_auto_repeat) ||
cmd->id == MP_CMD_COMMAND_LIST ||
(cmd->flags & MP_ALLOW_REPEAT);
cmd->id == MP_CMD_COMMAND_LIST;
}
void mp_print_cmd_list(struct mp_log *out)
+1 -10
View File
@@ -31,7 +31,6 @@ struct mp_cmd_def {
const char *name; // user-visible name (as used in input.conf)
const struct m_option args[MP_CMD_MAX_ARGS];
bool allow_auto_repeat; // react to repeated key events
bool on_updown; // always emit it on both up and down key events
bool vararg; // last argument can be given 0 to multiple times
};
@@ -79,13 +78,8 @@ enum mp_command_type {
MP_CMD_DISCNAV,
MP_CMD_AB_LOOP,
MP_CMD_DROP_BUFFERS,
/// Audio Filter commands
MP_CMD_AF,
MP_CMD_AO_RELOAD,
/// Video filter commands
MP_CMD_VF,
@@ -94,7 +88,7 @@ enum mp_command_type {
MP_CMD_VO_CMDLINE,
/// Internal for Lua scripts
MP_CMD_SCRIPT_BINDING,
MP_CMD_SCRIPT_DISPATCH,
MP_CMD_SCRIPT_MESSAGE,
MP_CMD_SCRIPT_MESSAGE_TO,
@@ -103,9 +97,6 @@ enum mp_command_type {
MP_CMD_WRITE_WATCH_LATER_CONFIG,
MP_CMD_HOOK_ADD,
MP_CMD_HOOK_ACK,
// Internal
MP_CMD_COMMAND_LIST, // list of sub-commands in args[0].v.p
};
+191 -265
View File
@@ -27,8 +27,6 @@
#include "cmd_list.h"
#include "input.h"
#include "libmpv/client.h"
static void destroy_cmd(void *ptr)
{
struct mp_cmd *cmd = ptr;
@@ -36,6 +34,74 @@ static void destroy_cmd(void *ptr)
m_option_free(cmd->args[n].type, &cmd->args[n].v);
}
static bool read_token(bstr str, bstr *out_rest, bstr *out_token)
{
bstr t = bstr_lstrip(str);
char nextc = t.len > 0 ? t.start[0] : 0;
if (nextc == '#' || nextc == ';')
return false; // comment or command separator
int next = bstrcspn(t, WHITESPACE);
if (!next)
return false;
*out_token = bstr_splice(t, 0, next);
*out_rest = bstr_cut(t, next);
return true;
}
// Somewhat awkward; the main purpose is supporting both strings and
// pre-split string arrays as input.
struct parse_ctx {
struct mp_log *log;
const char *loc;
void *tmp;
bool array_input; // false: use start/str, true: use num_strs/strs
bstr start, str;
bstr *strs;
int num_strs;
};
static int pctx_read_token(struct parse_ctx *ctx, bstr *out)
{
*out = (bstr){0};
if (ctx->array_input) {
if (!ctx->num_strs)
return 0;
*out = ctx->strs[0];
ctx->strs++;
ctx->num_strs--;
return 1;
} else {
ctx->str = bstr_lstrip(ctx->str);
bstr start = ctx->str;
if (bstr_eatstart0(&ctx->str, "\"")) {
if (!mp_append_escaped_string_noalloc(ctx->tmp, out, &ctx->str)) {
MP_ERR(ctx, "Broken string escapes: ...>%.*s<.\n", BSTR_P(start));
return -1;
}
if (!bstr_eatstart0(&ctx->str, "\"")) {
MP_ERR(ctx, "Unterminated quotes: ...>%.*s<.\n", BSTR_P(start));
return -1;
}
return 1;
}
return read_token(ctx->str, &ctx->str, out) ? 1 : 0;
}
}
static bstr pctx_get_trailing(struct parse_ctx *ctx)
{
if (ctx->array_input) {
if (ctx->num_strs == 0)
return (bstr){0};
return ctx->strs[0]; // mentioning the first trailing arg is enough?
} else {
bstr dummy;
if (!read_token(ctx->str, &dummy, &dummy))
return (bstr){0};
return ctx->str;
}
}
struct flag {
const char *name;
unsigned int remove, add;
@@ -47,246 +113,106 @@ static const struct flag cmd_flags[] = {
{"osd-msg", MP_ON_OSD_FLAGS, MP_ON_OSD_MSG},
{"osd-msg-bar", MP_ON_OSD_FLAGS, MP_ON_OSD_MSG | MP_ON_OSD_BAR},
{"osd-auto", MP_ON_OSD_FLAGS, MP_ON_OSD_AUTO},
{"expand-properties", 0, MP_EXPAND_PROPERTIES},
{"expand-properties", 0, MP_EXPAND_PROPERTIES},
{"raw", MP_EXPAND_PROPERTIES, 0},
{"repeatable", 0, MP_ALLOW_REPEAT},
{0}
};
static bool apply_flag(struct mp_cmd *cmd, bstr str)
static struct mp_cmd *parse_cmd(struct parse_ctx *ctx, int def_flags)
{
for (int n = 0; cmd_flags[n].name; n++) {
if (bstr_equals0(str, cmd_flags[n].name)) {
cmd->flags = (cmd->flags & ~cmd_flags[n].remove) | cmd_flags[n].add;
return true;
struct mp_cmd *cmd = NULL;
int r;
if (!ctx->array_input) {
ctx->str = bstr_lstrip(ctx->str);
bstr old = ctx->str;
if (mp_replace_legacy_cmd(ctx->tmp, &ctx->str)) {
MP_WARN(ctx, "Warning: command '%.*s' is deprecated, "
"replaced with '%.*s' at %s.\n",
BSTR_P(old), BSTR_P(ctx->str), ctx->loc);
ctx->start = ctx->str;
}
}
return false;
}
static bool find_cmd(struct mp_log *log, struct mp_cmd *cmd, bstr name)
{
if (name.len == 0) {
mp_err(log, "Command name missing.\n");
return false;
}
for (int n = 0; mp_cmds[n].name; n++) {
if (bstr_equals0(name, mp_cmds[n].name)) {
cmd->def = &mp_cmds[n];
cmd->name = (char *)cmd->def->name;
cmd->id = cmd->def->id;
return true;
}
}
mp_err(log, "Command '%.*s' not found.\n", BSTR_P(name));
return false;
}
static bool is_vararg(const struct mp_cmd_def *m, int i)
{
return m->vararg && (i + 1 >= MP_CMD_MAX_ARGS || !m->args[i + 1].type);
}
static const struct m_option *get_arg_type(const struct mp_cmd_def *cmd, int i)
{
if (i >= MP_CMD_MAX_ARGS)
return NULL;
const struct m_option *opt = &cmd->args[i];
if (!opt->type && is_vararg(cmd, i)) {
// The last arg in a vararg command sets all vararg types.
for (int n = i; n >= 0; n--) {
if (cmd->args[n].type) {
opt = &cmd->args[n];
break;
}
}
}
return opt->type ? opt : NULL;
}
// Set correct arg count, and fill in missing optional args.
static bool finish_cmd(struct mp_log *log, struct mp_cmd *cmd)
{
cmd->nargs = 0;
for (int i = 0; i < MP_CMD_MAX_ARGS; i++) {
if (!cmd->args[i].type) {
const struct m_option *opt = get_arg_type(cmd->def, i);
if (!opt || is_vararg(cmd->def, i))
break;
if (!opt->defval && !(opt->flags & MP_CMD_OPT_ARG)) {
mp_err(log, "Command %s: more than %d arguments required.\n",
cmd->name, cmd->nargs);
return false;
}
cmd->args[i].type = opt;
if (opt->defval)
m_option_copy(opt, &cmd->args[i].v, opt->defval);
}
if (cmd->args[i].type)
cmd->nargs = i + 1;
}
return true;
}
struct mp_cmd *mp_input_parse_cmd_node(struct mp_log *log, mpv_node *node)
{
struct mp_cmd *cmd = talloc_ptrtype(NULL, cmd);
talloc_set_destructor(cmd, destroy_cmd);
*cmd = (struct mp_cmd) { .scale = 1, };
if (node->format != MPV_FORMAT_NODE_ARRAY)
goto error;
mpv_node_list *args = node->u.list;
int cur = 0;
while (cur < args->num) {
if (args->values[cur].format != MPV_FORMAT_STRING)
break;
if (!apply_flag(cmd, bstr0(args->values[cur].u.string)))
break;
cur++;
}
bstr cmd_name = {0};
if (cur < args->num && args->values[cur].format == MPV_FORMAT_STRING)
cmd_name = bstr0(args->values[cur++].u.string);
if (!find_cmd(log, cmd, cmd_name))
goto error;
int first = cur;
for (int i = 0; i < args->num - first; i++) {
const struct m_option *opt = get_arg_type(cmd->def, i);
if (!opt) {
mp_err(log, "Command %s: has only %d arguments.\n", cmd->name, i);
goto error;
}
mpv_node *val = &args->values[cur++];
cmd->args[i].type = opt;
void *dst = &cmd->args[i].v;
if (val->format == MPV_FORMAT_STRING) {
int r = m_option_parse(log, opt, bstr0(cmd->name),
bstr0(val->u.string), dst);
if (r < 0) {
mp_err(log, "Command %s: argument %d can't be parsed: %s.\n",
cmd->name, i + 1, m_option_strerror(r));
goto error;
}
} else {
int r = m_option_set_node(opt, dst, val);
if (r < 0) {
mp_err(log, "Command %s: argument %d has incompatible type.\n",
cmd->name, i + 1);
goto error;
}
}
}
if (!finish_cmd(log, cmd))
goto error;
return cmd;
error:
for (int n = 0; n < MP_CMD_MAX_ARGS; n++) {
if (cmd->args[n].type)
m_option_free(cmd->args[n].type, &cmd->args[n].v);
}
talloc_free(cmd);
return NULL;
}
static bool read_token(bstr str, bstr *out_rest, bstr *out_token)
{
bstr t = bstr_lstrip(str);
int next = bstrcspn(t, WHITESPACE "#;");
if (!next)
return false;
*out_token = bstr_splice(t, 0, next);
*out_rest = bstr_cut(t, next);
return true;
}
struct parse_ctx {
struct mp_log *log;
void *tmp;
bstr start, str;
};
static int pctx_read_token(struct parse_ctx *ctx, bstr *out)
{
*out = (bstr){0};
ctx->str = bstr_lstrip(ctx->str);
bstr start = ctx->str;
if (bstr_eatstart0(&ctx->str, "\"")) {
if (!mp_append_escaped_string_noalloc(ctx->tmp, out, &ctx->str)) {
MP_ERR(ctx, "Broken string escapes: ...>%.*s<.\n", BSTR_P(start));
return -1;
}
if (!bstr_eatstart0(&ctx->str, "\"")) {
MP_ERR(ctx, "Unterminated quotes: ...>%.*s<.\n", BSTR_P(start));
return -1;
}
return 1;
}
return read_token(ctx->str, &ctx->str, out) ? 1 : 0;
}
static struct mp_cmd *parse_cmd_str(struct mp_log *log, void *tmp,
bstr *str, const char *loc)
{
struct parse_ctx *ctx = &(struct parse_ctx){
.log = log,
.tmp = tmp,
.str = *str,
.start = *str,
};
struct mp_cmd *cmd = talloc_ptrtype(NULL, cmd);
talloc_set_destructor(cmd, destroy_cmd);
*cmd = (struct mp_cmd) {
.flags = MP_ON_OSD_AUTO | MP_EXPAND_PROPERTIES,
.scale = 1,
};
ctx->str = bstr_lstrip(ctx->str);
bstr old = ctx->str;
if (mp_replace_legacy_cmd(ctx->tmp, &ctx->str)) {
MP_WARN(ctx, "Warning: command '%.*s' is deprecated, "
"replaced with '%.*s' at %s.\n",
BSTR_P(old), BSTR_P(ctx->str), loc);
ctx->start = ctx->str;
}
bstr cur_token;
if (pctx_read_token(ctx, &cur_token) < 0)
goto error;
while (1) {
if (!apply_flag(cmd, cur_token))
break;
if (pctx_read_token(ctx, &cur_token) < 0)
goto error;
for (int n = 0; cmd_flags[n].name; n++) {
if (bstr_equals0(cur_token, cmd_flags[n].name)) {
if (pctx_read_token(ctx, &cur_token) < 0)
goto error;
def_flags &= ~cmd_flags[n].remove;
def_flags |= cmd_flags[n].add;
goto cont;
}
}
break;
cont: ;
}
if (!find_cmd(ctx->log, cmd, cur_token))
if (cur_token.len == 0) {
MP_ERR(ctx, "Command name missing.\n");
goto error;
}
const struct mp_cmd_def *cmd_def = NULL;
for (int n = 0; mp_cmds[n].name; n++) {
if (bstr_equals0(cur_token, mp_cmds[n].name)) {
cmd_def = &mp_cmds[n];
break;
}
}
if (!cmd_def) {
MP_ERR(ctx, "Command '%.*s' not found.\n", BSTR_P(cur_token));
goto error;
}
cmd = talloc_ptrtype(NULL, cmd);
talloc_set_destructor(cmd, destroy_cmd);
*cmd = (struct mp_cmd) {
.name = (char *)cmd_def->name,
.id = cmd_def->id,
.flags = def_flags,
.scale = 1,
.def = cmd_def,
};
for (int i = 0; i < MP_CMD_MAX_ARGS; i++) {
const struct m_option *opt = get_arg_type(cmd->def, i);
if (!opt)
const struct m_option *opt = &cmd_def->args[i];
bool is_vararg = cmd_def->vararg &&
(i + 1 >= MP_CMD_MAX_ARGS || !cmd_def->args[i + 1].type); // last arg
if (!opt->type && is_vararg && cmd->nargs > 0)
opt = cmd->args[cmd->nargs - 1].type;
if (!opt->type)
break;
int r = pctx_read_token(ctx, &cur_token);
r = pctx_read_token(ctx, &cur_token);
if (r < 0) {
MP_ERR(ctx, "Command %s: error in argument %d.\n", cmd->name, i + 1);
goto error;
}
if (r < 1)
break;
if (r < 1) {
if (is_vararg)
continue;
// Skip optional arguments
if (opt->defval || (opt->flags & MP_CMD_OPT_ARG)) {
struct mp_cmd_arg *cmdarg = &cmd->args[cmd->nargs];
cmdarg->type = opt;
if (opt->defval)
m_option_copy(opt, &cmdarg->v, opt->defval);
cmd->nargs++;
continue;
}
MP_ERR(ctx, "Command %s: more than %d arguments required.\n",
cmd->name, cmd->nargs);
goto error;
}
struct mp_cmd_arg *cmdarg = &cmd->args[i];
struct mp_cmd_arg *cmdarg = &cmd->args[cmd->nargs];
cmdarg->type = opt;
cmd->nargs++;
r = m_option_parse(ctx->log, opt, bstr0(cmd->name), cur_token, &cmdarg->v);
if (r < 0) {
MP_ERR(ctx, "Command %s: argument %d can't be parsed: %s.\n",
@@ -295,34 +221,41 @@ static struct mp_cmd *parse_cmd_str(struct mp_log *log, void *tmp,
}
}
if (!finish_cmd(ctx->log, cmd))
goto error;
bstr dummy;
if (read_token(ctx->str, &dummy, &dummy) && ctx->str.len) {
bstr left = pctx_get_trailing(ctx);
if (left.len) {
MP_ERR(ctx, "Command %s has trailing unused arguments: '%.*s'.\n",
cmd->name, BSTR_P(ctx->str));
cmd->name, BSTR_P(left));
// Better make it fatal to make it clear something is wrong.
goto error;
}
bstr orig = {ctx->start.start, ctx->str.start - ctx->start.start};
cmd->original = bstrdup(cmd, bstr_strip(orig));
if (!ctx->array_input) {
bstr orig = {ctx->start.start, ctx->str.start - ctx->start.start};
cmd->original = bstrdup(cmd, bstr_strip(orig));
}
*str = ctx->str;
return cmd;
error:
MP_ERR(ctx, "Command was defined at %s.\n", loc);
MP_ERR(ctx, "Command was defined at %s.\n", ctx->loc);
talloc_free(cmd);
*str = ctx->str;
return NULL;
}
static struct mp_cmd_def list_def = {
.id = MP_CMD_COMMAND_LIST,
.name = "list",
};
static struct mp_cmd *parse_cmd_str(struct mp_log *log, void *tmp,
bstr *str, const char *loc)
{
struct parse_ctx ctx = {
.log = log,
.loc = loc,
.tmp = tmp,
.str = *str,
.start = *str,
};
struct mp_cmd *res = parse_cmd(&ctx, MP_ON_OSD_AUTO | MP_EXPAND_PROPERTIES);
*str = ctx.str;
return res;
}
mp_cmd_t *mp_input_parse_cmd_(struct mp_log *log, bstr str, const char *loc)
{
@@ -346,9 +279,8 @@ mp_cmd_t *mp_input_parse_cmd_(struct mp_log *log, bstr str, const char *loc)
struct mp_cmd *list = talloc_ptrtype(NULL, list);
talloc_set_destructor(list, destroy_cmd);
*list = (struct mp_cmd) {
.id = list_def.id,
.name = (char *)list_def.name,
.def = &list_def,
.id = MP_CMD_COMMAND_LIST,
.name = "list",
.original = bstrdup(list, original),
};
talloc_steal(list, cmd);
@@ -372,21 +304,36 @@ done:
return cmd;
}
struct mp_cmd *mp_input_parse_cmd_strv(struct mp_log *log, const char **argv)
struct mp_cmd *mp_input_parse_cmd_strv(struct mp_log *log, int def_flags,
const char **argv, const char *location)
{
mpv_node items[MP_CMD_MAX_ARGS];
mpv_node_list list = {.values = items};
mpv_node node = {.format = MPV_FORMAT_NODE_ARRAY, .u = {.list = &list}};
while (argv[list.num]) {
if (list.num >= MP_CMD_MAX_ARGS) {
mp_err(log, "Too many arguments to command.\n");
bstr args[MP_CMD_MAX_ARGS];
int num = 0;
for (; argv[num]; num++) {
if (num >= MP_CMD_MAX_ARGS) {
mp_err(log, "%s: too many arguments.\n", location);
return NULL;
}
char *s = (char *)argv[list.num];
items[list.num++] = (mpv_node){.format = MPV_FORMAT_STRING,
.u = {.string = s}};
args[num] = bstr0(argv[num]);
}
return mp_input_parse_cmd_node(log, &node);
return mp_input_parse_cmd_bstrv(log, def_flags, num, args, location);
}
struct mp_cmd *mp_input_parse_cmd_bstrv(struct mp_log *log, int def_flags,
int argc, bstr *argv,
const char *location)
{
struct parse_ctx ctx = {
.log = log,
.loc = location,
.tmp = talloc_new(NULL),
.array_input = true,
.strs = argv,
.num_strs = argc,
};
struct mp_cmd *res = parse_cmd(&ctx, def_flags);
talloc_free(ctx.tmp);
return res;
}
void mp_cmd_free(mp_cmd_t *cmd)
@@ -427,27 +374,6 @@ mp_cmd_t *mp_cmd_clone(mp_cmd_t *cmd)
return ret;
}
void mp_cmd_dump(struct mp_log *log, int msgl, char *header, struct mp_cmd *cmd)
{
if (!mp_msg_test(log, msgl))
return;
if (header)
mp_msg(log, msgl, "%s ", header);
if (!cmd) {
mp_msg(log, msgl, "(NULL)\n");
return;
}
mp_msg(log, msgl, "%s, flags=%d, args=[", cmd->name, cmd->flags);
for (int n = 0; n < cmd->nargs; n++) {
char *s = m_option_print(cmd->args[n].type, &cmd->args[n].v);
if (n)
mp_msg(log, msgl, ", ");
mp_msg(log, msgl, "%s", s ? s : "(NULL)");
talloc_free(s);
}
mp_msg(log, msgl, "]\n");
}
static int parse_cycle_dir(struct mp_log *log, const struct m_option *opt,
struct bstr name, struct bstr param, void *dst)
{
+8 -9
View File
@@ -18,30 +18,29 @@
#ifndef MP_PARSE_COMMAND_H
#define MP_PARSE_COMMAND_H
#include "misc/bstr.h"
struct mp_log;
struct mp_cmd;
struct mpv_node;
// Parse text and return corresponding struct mp_cmd.
// The location parameter is for error messages.
struct mp_cmd *mp_input_parse_cmd_(struct mp_log *log, bstr str, const char *loc);
// Similar to mp_input_parse_cmd(), but takes a list of strings instead.
// Also, MP_ON_OSD_AUTO | MP_EXPAND_PROPERTIES are not set by default.
// Also, def_flags contains initial command flags (see mp_cmd_flags; the default
// as used by mp_input_parse_cmd is MP_ON_OSD_AUTO | MP_EXPAND_PROPERTIES).
// Keep in mind that these functions (naturally) don't take multiple commands,
// i.e. a ";" argument does not start a new command.
struct mp_cmd *mp_input_parse_cmd_strv(struct mp_log *log, const char **argv);
struct mp_cmd *mp_input_parse_cmd_node(struct mp_log *log, struct mpv_node *node);
// The _strv version is limitted to MP_CMD_MAX_ARGS argv array items.
struct mp_cmd *mp_input_parse_cmd_strv(struct mp_log *log, int def_flags,
const char **argv, const char *location);
struct mp_cmd *mp_input_parse_cmd_bstrv(struct mp_log *log, int def_flags,
int argc, bstr *argv,
const char *location);
// After getting a command from mp_input_get_cmd you need to free it using this
// function
void mp_cmd_free(struct mp_cmd *cmd);
void mp_cmd_dump(struct mp_log *log, int msgl, char *header, struct mp_cmd *cmd);
// This creates a copy of a command (used by the auto repeat stuff).
struct mp_cmd *mp_cmd_clone(struct mp_cmd *cmd);
+2 -4
View File
@@ -29,24 +29,22 @@ void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files)
if (all_sub) {
for (int i = 0; i < num_files; i++) {
const char *cmd[] = {
"osd-auto",
"sub_add",
files[i],
NULL
};
mp_input_run_cmd(ictx, cmd);
mp_input_run_cmd(ictx, MP_ON_OSD_AUTO, cmd, "<drop-subtitle>");
}
} else {
for (int i = 0; i < num_files; i++) {
const char *cmd[] = {
"osd-auto",
"loadfile",
files[i],
/* Start playing the dropped files right away */
(i == 0) ? "replace" : "append",
NULL
};
mp_input_run_cmd(ictx, cmd);
mp_input_run_cmd(ictx, MP_ON_OSD_AUTO, cmd, "<drop-files>");
}
}
}
Loaded 100 of 244 files, more files were not shown because too many files have changed in this diff. Show more