Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d9f8f75ea | ||
|
|
2ffdc0da13 | ||
|
|
7ae6484cd4 | ||
|
|
08d144138b | ||
|
|
58b511d947 | ||
|
|
9b8b2933f3 | ||
|
|
24c05b2a82 | ||
|
|
1412b0171d | ||
|
|
5c1c99b858 | ||
|
|
f66e320d7b | ||
|
|
fa2381765e | ||
|
|
f220617267 | ||
|
|
381843eb88 | ||
|
|
9258d0461e | ||
|
|
e2f59fcabd | ||
|
|
5b76ce3865 | ||
|
|
1b29964a3e | ||
|
|
180fed4bc4 | ||
|
|
fb3bf1ae81 | ||
|
|
f842b3f2b2 | ||
|
|
e4f6235a47 | ||
|
|
89df07aa6d | ||
|
|
04a8dd38d5 | ||
|
|
72e881b60d | ||
|
|
45b99d9799 | ||
|
|
b784083fb5 | ||
|
|
d7cf596e2a | ||
|
|
a2e38ca657 | ||
|
|
3e410f423d | ||
|
|
061f509757 | ||
|
|
03bfc2b3fd |
No files matched your search
+4
-26
@@ -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,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 ---
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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,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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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. Can be
|
||||
disabled with ``ytdl=no`` 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.)
|
||||
|
||||
+16
-220
@@ -193,7 +193,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 +204,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 +260,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 +403,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 +457,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 +506,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 +536,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 +619,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 +656,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``).
|
||||
@@ -814,10 +745,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 +818,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 +855,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 +916,6 @@ Property list
|
||||
``deinterlace`` (RW)
|
||||
See ``--deinterlace``.
|
||||
|
||||
``field-dominance`` (RW)
|
||||
See ``--field-dominance``
|
||||
|
||||
``colormatrix`` (RW)
|
||||
See ``--colormatrix``.
|
||||
|
||||
@@ -1155,13 +1071,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 +1137,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 +1163,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 +1172,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 +1291,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 +1316,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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
+26
-114
@@ -94,15 +94,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 +172,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 +189,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 +213,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 +224,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 +502,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 +542,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 parameter ``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 +583,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 +664,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.
|
||||
+12
-58
@@ -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,9 +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.
|
||||
|
||||
@@ -362,7 +356,7 @@ 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``
|
||||
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
|
||||
@@ -451,15 +445,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 +467,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 +556,6 @@ PROTOCOLS
|
||||
|
||||
.. include:: lua.rst
|
||||
|
||||
.. include:: ipc.rst
|
||||
|
||||
.. include:: changes.rst
|
||||
|
||||
ENVIRONMENT VARIABLES
|
||||
@@ -724,39 +711,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
|
||||
=====================
|
||||
|
||||
|
||||
+82
-213
@@ -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
|
||||
----------------
|
||||
@@ -358,7 +328,7 @@ 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``
|
||||
@@ -422,24 +392,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. (Enabled by default.)
|
||||
|
||||
If the script can't do anything with an URL, it will do nothing.
|
||||
|
||||
(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``. See youtube-dl's documentation for
|
||||
available aliases. To use experimental DASH support for youtube, use
|
||||
``bestvideo+bestaudio``.
|
||||
(Default: ``best``)
|
||||
|
||||
Video
|
||||
-----
|
||||
@@ -528,18 +480,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::
|
||||
|
||||
@@ -577,7 +526,7 @@ Video
|
||||
Disable scaling of the video. If the window is larger than the video,
|
||||
black bars are added. Otherwise, the video is cropped. The video still
|
||||
can be influenced by the other ``--video-...`` options. (If the
|
||||
``--video-zoom`` option is set to a value other than ``0``, scaling is
|
||||
``--video-zoom`` option is set to a value other than ``1``, scaling is
|
||||
enabled, but the video isn't automatically scaled to the window size.)
|
||||
|
||||
The video and monitor aspects aspect will be ignored. Aspect correction
|
||||
@@ -714,6 +663,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 +756,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 +884,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 +922,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 +977,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 +1061,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 +1104,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 +1122,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 +1146,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 +1206,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
|
||||
@@ -1663,6 +1610,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 +1658,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 +1685,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 +1700,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 +1769,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 +1988,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 +1995,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 +2041,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 +2050,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 +2062,7 @@ Demuxer
|
||||
|
||||
See ``--list-options`` for defaults and value range.
|
||||
|
||||
``--demuxer-readahead-bytes=<bytes>``
|
||||
``--demuxer-readahead-bytes=N``
|
||||
See ``--demuxer-readahead-packets``.
|
||||
|
||||
|
||||
@@ -2213,11 +2113,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 +2130,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 +2151,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 +2178,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 +2216,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 +2290,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 +2348,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 +2375,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
|
||||
----------
|
||||
|
||||
@@ -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
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -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
@@ -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``.
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ mpv
|
||||
|
||||
[](https://travis-ci.org/mpv-player/mpv)
|
||||
|
||||
[](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
|
||||
|
||||
+77
-110
@@ -1,135 +1,102 @@
|
||||
Release 0.7.2
|
||||
Release 0.6.1
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
- Give precedence to the DVD menu navigation keyboard bindings so that user
|
||||
defined LEFT/RIGHT/... bindings don't break DVD menu navigation.
|
||||
- Try to fallback to the "default" device if the selected device is busy in the
|
||||
alsa AO.
|
||||
- Don't create Dock icon for audio only files on OS X.
|
||||
- Save screenshots to desktop when using the app bundle on OS X.
|
||||
- Restore ab-loop settings with playback resume.
|
||||
- Bump required youtube-dl version to 2014.11.26 and enable the ytdl_hook Lua
|
||||
script by default (now playing videos from YouTube and the like will work
|
||||
out of the box without any configuration change needed).
|
||||
- Don't show message on "osd" command by default (this essentially reverts a
|
||||
change introduced in the last release).
|
||||
- Add an alternative input configuration that reverts the key binding changes
|
||||
introduced by previous releases (see etc/restore-old-bindings.conf in the
|
||||
sources).
|
||||
- Automatically install example input configuration files.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Don't signal an error if --stream-dump is used.
|
||||
- Fix removing key bindings from Lua scripts.
|
||||
- Reject channel descriptions with too many channels in the coreaudio AO.
|
||||
- Don't async redraw when waiting for VO redraw on OS X (this fixes the very
|
||||
annoying glitch where the black bars disappear for a single frame when going
|
||||
fullscreen).
|
||||
- Fix mono playback with the also AO.
|
||||
- Don't crash if framebuffers are not available in the opengl VO.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
- Try to handle multi-arc videos in the ytdl_hook Lua script.
|
||||
- Make stream language work with Bluray as well (the previous release
|
||||
introduced this for DVDs, but it didn't work for Blurays).
|
||||
- Don't try to create surfaces of size 0 in the VDPAU vo.
|
||||
- Don't print audio/video init failure message twice.
|
||||
- Fix playback on the root window on X11 (i.e. when using --wid=0).
|
||||
- Fix the autodetection of the input format in the vf_stereo3d filter when mpv
|
||||
is built against libav (this makes --video-stereo-mode work with libav as
|
||||
well).
|
||||
- Fix compilation with PulseAudio 1.0.
|
||||
- Fix compilation with clang.
|
||||
- Try harder to decode cover art picture only once.
|
||||
- Fix cursor autohide (--cursor-autohide) with audio-only files on file change
|
||||
(e.g. when skipping to the next playlist file).
|
||||
|
||||
This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log 47ec404..c7d6b21`` in
|
||||
complete change log can be viewed by running ``git log 061f509..2ffdc0d`` in
|
||||
the git repository.
|
||||
|
||||
Release 0.7.1
|
||||
Release 0.6.0
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
- Don't show the volume neutral marker on the OSD if softvol is disabled.
|
||||
- Don't select a subtitle track when executing the sub_add input command in
|
||||
"auto" mode.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fix busy loop when seeking while paused (this fixes a problem with pulseaudio
|
||||
that caused mpv and the pulseaudio daemon to use 100% CPU).
|
||||
- Fix Lua function utils.subprocess() in Windows versions older than Vista.
|
||||
- Avoid creating a window bigger than the screen on Windows.
|
||||
- Don't ignore the last line in m3u playlists.
|
||||
- Don't crash if a codec could not be opened.
|
||||
- Dynamically allocate audio channel map entries (this should fix a crash in
|
||||
the alsa and coreaudio AOs with audio devices that support more than 20
|
||||
channel maps).
|
||||
- Ignore the "srgb" option in the opengl VO if hardware decoding is enabled.
|
||||
- Linearize non-RGB sRGB files correctly (eg. JPEG).
|
||||
- Fix opening reference URLs (.file/id=) on OS X.
|
||||
|
||||
This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log 8d8b36d..6583ad6`` in
|
||||
the git repository.
|
||||
|
||||
Release 0.7.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
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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?"
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
screenshot-template=~/Desktop/shot%n
|
||||
+23
-3
@@ -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()
|
||||
|
||||
@@ -11,6 +11,7 @@ esac
|
||||
|
||||
: ${MPV:=mpv}
|
||||
: ${ILDETECT_MPV:=$MPV}
|
||||
: ${ILDETECT_MPV:=$MPV}
|
||||
: ${ILDETECT_MPVFLAGS:=--start=35% --length=35}
|
||||
: ${ILDETECT_DRY_RUN:=}
|
||||
: ${ILDETECT_QUIET:=}
|
||||
|
||||
+35
-165
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
+5
-12
@@ -81,21 +81,14 @@ void mp_chmap_sel_add_alsa_def(struct mp_chmap_sel *s)
|
||||
}
|
||||
}
|
||||
|
||||
#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
// Add a channel map that should be allowed.
|
||||
void mp_chmap_sel_add_map(struct mp_chmap_sel *s, const struct mp_chmap *map)
|
||||
{
|
||||
if (!mp_chmap_is_valid(map))
|
||||
return;
|
||||
if (!s->chmaps)
|
||||
s->chmaps = s->chmaps_storage;
|
||||
if (s->num_chmaps == MP_ARRAY_SIZE(s->chmaps_storage)) {
|
||||
if (!s->tmp)
|
||||
return;
|
||||
s->chmaps = talloc_memdup(s->tmp, s->chmaps, sizeof(s->chmaps_storage));
|
||||
}
|
||||
if (s->chmaps != s->chmaps_storage)
|
||||
MP_TARRAY_GROW(s->tmp, s->chmaps, s->num_chmaps);
|
||||
s->chmaps[s->num_chmaps++] = *map;
|
||||
assert(s->num_chmaps < ARRAY_LEN(s->chmaps));
|
||||
if (mp_chmap_is_valid(map))
|
||||
s->chmaps[s->num_chmaps++] = *map;
|
||||
}
|
||||
|
||||
// Allow all waveext formats in default order.
|
||||
|
||||
+1
-5
@@ -26,12 +26,8 @@ struct mp_chmap_sel {
|
||||
// should be considered opaque
|
||||
bool allow_any, allow_waveext;
|
||||
bool speakers[MP_SPEAKER_ID_COUNT];
|
||||
struct mp_chmap *chmaps;
|
||||
struct mp_chmap chmaps[20];
|
||||
int num_chmaps;
|
||||
|
||||
struct mp_chmap chmaps_storage[20];
|
||||
|
||||
void *tmp; // set to any talloc context to allow more chmaps entries
|
||||
};
|
||||
|
||||
void mp_chmap_sel_add_any(struct mp_chmap_sel *s);
|
||||
|
||||
+1
-1
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -280,13 +280,8 @@ static inline void update_ch(af_hrtf_t *s, short *in, const int k)
|
||||
}
|
||||
|
||||
/* We need to update the bass compensation delay line, too. */
|
||||
// TODO: should this use lf/cf/rf etc. instead?
|
||||
s->ba_l[k] = in[0];
|
||||
s->ba_r[k] = in[1];
|
||||
if (s->decode_mode == HRTF_MIX_51) {
|
||||
s->ba_l[k] += in[4] + in[2];
|
||||
s->ba_r[k] += in[4] + in[3];
|
||||
}
|
||||
s->ba_l[k] = in[0] + in[4] + in[2];
|
||||
s->ba_r[k] = in[4] + in[1] + in[3];
|
||||
}
|
||||
|
||||
static void clear_coeff(af_hrtf_t *s, float *c)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ struct mixer {
|
||||
struct ao *ao;
|
||||
struct af_stream *af;
|
||||
// Static, dependent on ao/softvol settings
|
||||
bool softvol; // use AO (false) or af_volume (true)
|
||||
bool softvol; // use AO (true) or af_volume (false)
|
||||
bool ao_softvol; // AO has private or per-app volume
|
||||
bool emulate_mute; // if true, emulate mute with volume=0
|
||||
// Last known values (possibly out of sync with reality)
|
||||
@@ -72,7 +72,7 @@ bool mixer_audio_initialized(struct mixer *mixer)
|
||||
float mixer_getneutralvolume(struct mixer *mixer)
|
||||
{
|
||||
// gain == 1
|
||||
return mixer->softvol ? 1.0 / mixer->opts->softvol_max * 100.0 * 100.0 : 100;
|
||||
return 1.0 / mixer->opts->softvol_max * 100.0 * 100.0;
|
||||
}
|
||||
|
||||
static void checkvolume(struct mixer *mixer)
|
||||
|
||||
+34
-175
@@ -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);
|
||||
}
|
||||
@@ -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 */
|
||||
+136
-307
@@ -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 = {.tmp = p};
|
||||
|
||||
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,56 +331,51 @@ 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);
|
||||
}
|
||||
}
|
||||
const char *old_dev = device;
|
||||
if (ao->device)
|
||||
device = ao->device;
|
||||
if (p->cfg_device && p->cfg_device[0])
|
||||
device = p->cfg_device;
|
||||
bool user_set_device = device != old_dev; // not strcmp()
|
||||
|
||||
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 && !user_set_device && strcmp(device, "default") != 0) {
|
||||
MP_WARN(ao, "Device '%s' busy, retrying default.\n", device);
|
||||
err = try_open_device(ao, "default", 0);
|
||||
} else if (err != -EBUSY && !p->cfg_block) {
|
||||
if (err != -EBUSY && !p->cfg_block) {
|
||||
MP_WARN(ao, "Open in nonblock-mode "
|
||||
"failed, trying to open in block-mode.\n");
|
||||
err = try_open_device(ao, device, 0);
|
||||
@@ -497,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");
|
||||
|
||||
@@ -538,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);
|
||||
@@ -574,82 +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");
|
||||
}
|
||||
|
||||
if (ao->channels.num == 1)
|
||||
ao->channels.speaker[0] = MP_SP(FC);
|
||||
|
||||
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");
|
||||
|
||||
@@ -660,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");
|
||||
|
||||
@@ -680,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)
|
||||
@@ -699,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;
|
||||
@@ -755,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");
|
||||
@@ -792,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;
|
||||
@@ -824,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);
|
||||
@@ -843,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)
|
||||
{
|
||||
@@ -877,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,
|
||||
@@ -918,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,
|
||||
|
||||
+28
-38
@@ -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))
|
||||
@@ -185,7 +185,7 @@ static bool init_chmap(struct ao *ao)
|
||||
&layouts, &n_layouts);
|
||||
CHECK_CA_ERROR("could not get audio device prefered layouts");
|
||||
|
||||
struct mp_chmap_sel chmap_sel = {.tmp = p};
|
||||
struct mp_chmap_sel chmap_sel = {0};
|
||||
for (int i = 0; i < n_layouts; i++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
if (ca_layout_to_mp_chmap(ao, &layouts[i], &chmap))
|
||||
@@ -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,
|
||||
@@ -354,28 +354,22 @@ static void ca_log_layout(struct ao *ao, AudioChannelLayout *layout)
|
||||
|
||||
AudioChannelDescription *descs = layout->mChannelDescriptions;
|
||||
|
||||
MP_VERBOSE(ao, "layout: tag: <%u>, bitmap: <%u>, "
|
||||
"descriptions <%u>\n",
|
||||
(unsigned) layout->mChannelLayoutTag,
|
||||
(unsigned) layout->mChannelBitmap,
|
||||
(unsigned) layout->mNumberChannelDescriptions);
|
||||
MP_VERBOSE(ao, "layout: tag: <%d>, bitmap: <%d>, "
|
||||
"descriptions <%d>\n",
|
||||
layout->mChannelLayoutTag,
|
||||
layout->mChannelBitmap,
|
||||
layout->mNumberChannelDescriptions);
|
||||
|
||||
for (int i = 0; i < layout->mNumberChannelDescriptions; i++) {
|
||||
AudioChannelDescription d = descs[i];
|
||||
MP_VERBOSE(ao, " - description %d: label <%u, %u>, "
|
||||
" flags: <%u>, coords: <%f, %f, %f>\n", i,
|
||||
(unsigned) d.mChannelLabel,
|
||||
(unsigned) ca_label_to_mp_speaker_id(d.mChannelLabel),
|
||||
(unsigned) d.mChannelFlags,
|
||||
MP_VERBOSE(ao, " - description %d: label <%d, %d>, flags: <%u>, "
|
||||
"coords: <%f, %f, %f>\n", i,
|
||||
d.mChannelLabel,
|
||||
ca_label_to_mp_speaker_id(d.mChannelLabel),
|
||||
d.mChannelFlags,
|
||||
d.mCoordinates[0],
|
||||
d.mCoordinates[1],
|
||||
d.mCoordinates[2]);
|
||||
|
||||
if (i >= 32) {
|
||||
MP_VERBOSE(ao, " detected more than 32 channel descriptions, "
|
||||
"skipping output");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,22 +410,14 @@ bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
// to the waveextensible definition: this is the kind of
|
||||
// descriptions we process here.
|
||||
|
||||
if (layout->mNumberChannelDescriptions > MP_NUM_CHANNELS) {
|
||||
MP_VERBOSE(ao, "layout has too many descriptions (%u, max: %d)\n",
|
||||
(unsigned) layout->mNumberChannelDescriptions,
|
||||
MP_NUM_CHANNELS);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int n = 0; n < layout->mNumberChannelDescriptions; n++) {
|
||||
AudioChannelLabel label = layout->mChannelDescriptions[n].mChannelLabel;
|
||||
uint8_t speaker = ca_label_to_mp_speaker_id(label);
|
||||
if (label == kAudioChannelLabel_Unknown)
|
||||
continue;
|
||||
if (speaker < 0) {
|
||||
MP_VERBOSE(ao, "channel label=%u unusable to build channel "
|
||||
"bitmap, skipping layout\n", (unsigned) label);
|
||||
goto coreaudio_error;
|
||||
MP_VERBOSE(ao, "channel label=%d unusable to build channel "
|
||||
"bitmap, skipping layout\n", label);
|
||||
} else {
|
||||
chmap->speaker[n] = speaker;
|
||||
chmap->num = n + 1;
|
||||
@@ -454,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}
|
||||
},
|
||||
};
|
||||
@@ -62,7 +62,7 @@ OSStatus ca_get_ary(AudioObjectID id, ca_scope scope, ca_sel selector,
|
||||
err = AudioObjectGetPropertyDataSize(id, &p_addr, 0, NULL, &p_size);
|
||||
CHECK_CA_ERROR_SILENT_L(coreaudio_error);
|
||||
|
||||
*data = talloc_zero_size(NULL, p_size);
|
||||
*data = talloc_size(NULL, p_size);
|
||||
*elements = p_size / element_size;
|
||||
|
||||
err = ca_get(id, scope, selector, p_size, *data);
|
||||
@@ -87,7 +87,7 @@ OSStatus ca_get_str(AudioObjectID id, ca_scope scope, ca_sel selector,
|
||||
CFStringGetMaximumSizeForEncoding(
|
||||
CFStringGetLength(string), CA_CFSTR_ENCODING) + 1;
|
||||
|
||||
*data = talloc_zero_size(NULL, size);
|
||||
*data = talloc_size(NULL, size);
|
||||
CFStringGetCString(string, *data, size, CA_CFSTR_ENCODING);
|
||||
CFRelease(string);
|
||||
coreaudio_error:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
+32
-91
@@ -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);
|
||||
@@ -450,13 +419,22 @@ static int init(struct ao *ao)
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
@@ -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
@@ -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},
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
+3
-35
@@ -22,8 +22,8 @@
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "osdep/atomics.h"
|
||||
#include "audio/out/ao.h"
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
|
||||
/* global data used by ao.c and ao drivers */
|
||||
struct ao {
|
||||
@@ -45,19 +45,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 +122,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 +142,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 +162,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
@@ -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);
|
||||
}
|
||||
|
||||
+5
-10
@@ -93,11 +93,11 @@ static double unlocked_get_delay(struct ao *ao)
|
||||
return driver_delay + mp_audio_buffer_seconds(p->buffer);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -234,10 +234,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
|
||||
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.
|
||||
@@ -303,7 +300,6 @@ 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)
|
||||
@@ -352,8 +348,7 @@ static void *playthread(void *arg)
|
||||
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");
|
||||
@@ -464,7 +459,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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
-1
@@ -275,7 +275,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
-27
@@ -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;
|
||||
@@ -190,11 +189,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)
|
||||
@@ -286,8 +282,6 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
.buf_size = 0,
|
||||
.buf = av_mallocz(PROBE_BUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE),
|
||||
};
|
||||
if (!avpd.buf)
|
||||
return -1;
|
||||
|
||||
bool final_probe = false;
|
||||
do {
|
||||
@@ -593,7 +587,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;
|
||||
|
||||
@@ -681,8 +674,6 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
return -1;
|
||||
|
||||
avfc = avformat_alloc_context();
|
||||
if (!avfc)
|
||||
return -1;
|
||||
|
||||
if (lavfdopts->cryptokey)
|
||||
parse_cryptokey(avfc, lavfdopts->cryptokey);
|
||||
@@ -715,13 +706,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 {
|
||||
@@ -737,10 +724,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) {
|
||||
@@ -774,9 +761,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);
|
||||
}
|
||||
|
||||
@@ -837,12 +828,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)
|
||||
@@ -867,8 +852,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)
|
||||
|
||||
+77
-212
@@ -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)
|
||||
{
|
||||
@@ -345,10 +338,6 @@ static int demux_mkv_read_info(demuxer_t *demuxer)
|
||||
if (info.n_timecode_scale) {
|
||||
mkv_d->tc_scale = info.timecode_scale;
|
||||
MP_VERBOSE(demuxer, "| + timecode scale: %" PRIu64 "\n", mkv_d->tc_scale);
|
||||
if (mkv_d->tc_scale < 1 || mkv_d->tc_scale > INT_MAX) {
|
||||
res = -1;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
if (info.n_duration) {
|
||||
mkv_d->duration = info.duration * mkv_d->tc_scale / 1e9;
|
||||
@@ -637,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;
|
||||
}
|
||||
|
||||
@@ -666,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;
|
||||
}
|
||||
|
||||
@@ -723,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];
|
||||
@@ -736,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -904,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) {
|
||||
@@ -1174,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);
|
||||
@@ -1418,22 +1393,20 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
sh_a->bitrate = 16000 * 8;
|
||||
sh_a->block_align = 1024;
|
||||
|
||||
if (!strcmp(track->codec_id, MKV_A_AAC)) {
|
||||
if (track->private_data && !extradata_len) {
|
||||
if (!strcmp(track->codec_id, MKV_A_AAC) && track->private_data) {
|
||||
if (!extradata_len) {
|
||||
extradata = track->private_data;
|
||||
extradata_len = track->private_size;
|
||||
}
|
||||
} else {
|
||||
/* Recreate the 'private data' */
|
||||
/* which faad2 uses in its initialization */
|
||||
srate_idx = aac_get_sample_rate_index(track->a_sfreq);
|
||||
const char *tail = "";
|
||||
if (strlen(track->codec_id) >= 12)
|
||||
tail = &track->codec_id[12];
|
||||
if (!strncmp(tail, "MAIN", 4))
|
||||
if (!strncmp(&track->codec_id[12], "MAIN", 4))
|
||||
profile = 0;
|
||||
else if (!strncmp(tail, "LC", 2))
|
||||
else if (!strncmp(&track->codec_id[12], "LC", 2))
|
||||
profile = 1;
|
||||
else if (!strncmp(tail, "SSR", 3))
|
||||
else if (!strncmp(&track->codec_id[12], "SSR", 3))
|
||||
profile = 2;
|
||||
else
|
||||
profile = 3;
|
||||
@@ -1477,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 !? */
|
||||
|
||||
@@ -1513,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;
|
||||
@@ -1771,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");
|
||||
@@ -1790,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;
|
||||
@@ -1859,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;
|
||||
}
|
||||
|
||||
@@ -2019,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;
|
||||
@@ -2302,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;
|
||||
@@ -2323,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2403,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)
|
||||
@@ -2452,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
|
||||
@@ -2463,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++;
|
||||
}
|
||||
@@ -2508,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;
|
||||
@@ -2739,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;
|
||||
}
|
||||
@@ -2803,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;
|
||||
@@ -2826,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)
|
||||
|
||||
+15
-31
@@ -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;
|
||||
@@ -86,7 +75,7 @@ static int parse_m3u(struct pl_parser *p)
|
||||
return -1;
|
||||
if (p->probing)
|
||||
return 0;
|
||||
while (line.len || !pl_eof(p)) {
|
||||
while (!pl_eof(p)) {
|
||||
if (line.len > 0 && !bstr_startswith0(line, "#"))
|
||||
pl_add(p, line);
|
||||
line = bstr_strip(pl_get_line(p));
|
||||
@@ -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, "Redirecting 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
@@ -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,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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
+4
-19
@@ -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)
|
||||
|
||||
Loaded 100 of 249 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user