Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef41a98a79 | ||
|
|
b18c2d9fe2 | ||
|
|
8f5ebdbe59 | ||
|
|
e8616fea50 | ||
|
|
00997484f1 | ||
|
|
ea556e5398 | ||
|
|
a3fc0b2015 | ||
|
|
2db094203d | ||
|
|
958858f920 | ||
|
|
bd271ae1cc | ||
|
|
c71644b595 | ||
|
|
9b18ed8d8b | ||
|
|
5dce714dfe | ||
|
|
25a0709972 | ||
|
|
6cf2f634b3 | ||
|
|
e165ccbf3b | ||
|
|
f78c0692e2 | ||
|
|
59f456cdd3 | ||
|
|
0e35c230f4 | ||
|
|
9ae80929ea | ||
|
|
98d1358e6e | ||
|
|
e71d68cdae | ||
|
|
950ef5b75b | ||
|
|
0075f8e0cf | ||
|
|
49275b2879 |
No files matched your search
+6
-6
@@ -20,12 +20,12 @@
|
||||
/demux/ebml_types.h
|
||||
/sub/osd_font.h
|
||||
/player/lua/*.inc
|
||||
/DOCS/man/mpv.1
|
||||
/DOCS/man/mpv.aux
|
||||
/DOCS/man/mpv.log
|
||||
/DOCS/man/mpv.out
|
||||
/DOCS/man/mpv.pdf
|
||||
/DOCS/man/mpv.toc
|
||||
/DOCS/man/*/mpv.1
|
||||
/DOCS/man/*/mpv.aux
|
||||
/DOCS/man/*/mpv.log
|
||||
/DOCS/man/*/mpv.out
|
||||
/DOCS/man/*/mpv.pdf
|
||||
/DOCS/man/*/mpv.toc
|
||||
|
||||
/waf
|
||||
/build
|
||||
|
||||
+9
-41
@@ -1,41 +1,16 @@
|
||||
language: c
|
||||
|
||||
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:
|
||||
- os: osx
|
||||
env: LIBAV=libav-stable
|
||||
- os: osx
|
||||
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:
|
||||
@@ -43,20 +18,13 @@ script:
|
||||
- ./waf configure
|
||||
- ./waf build
|
||||
|
||||
notifications-policy: ¬ifications-policy
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
channels:
|
||||
- "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
|
||||
<<: *notifications-policy
|
||||
@@ -1,74 +0,0 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
This file lists all changes that can cause compatibility issues when using
|
||||
mpv through the client API (libmpv and ``client.h``). Since the client API
|
||||
interfaces to input handling (commands, properties) as well as command line
|
||||
options, this list is interesting for other uses of mpv, such as the Lua
|
||||
scripting interface, key bindings in ``input.rst``, or plain command line
|
||||
usage.
|
||||
|
||||
Normally, changes to the C API that are incompatible to previous iterations
|
||||
receive a major version bump (i.e. the first version number is increased),
|
||||
while C API additions bump the minor version (i.e. the second number is
|
||||
increased). Changes to properties/commands/options may also lead to a minor
|
||||
version bump, in particular if they are incompatible.
|
||||
|
||||
The version number is the same as used for MPV_CLIENT_API_VERSION (see
|
||||
``client.h`` how to convert between major/minor version numbers and the flat
|
||||
32 bit integer).
|
||||
|
||||
Also, read the section ``Compatibility`` in ``client.h``.
|
||||
|
||||
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 ---
|
||||
1.3 - add MPV_MAKE_VERSION()
|
||||
1.2 - remove "stream-time-pos" property (no replacement)
|
||||
1.1 - remap dvdnav:// to dvd://
|
||||
- add "--cache-file", "--cache-file-size"
|
||||
- add "--colormatrix-primaries" (and property)
|
||||
- add "primaries" sub-field to image format properties
|
||||
- add "playback-time" property
|
||||
- extend the "--start" option; a leading "+", which was previously
|
||||
insignificant is now significant
|
||||
- add "cache-free" and "cache-used" properties
|
||||
- OSX: the "coreaudio" AO spdif code is split into a separate AO
|
||||
--- mpv 0.4.0 is released ---
|
||||
1.0 - the API is declared stable
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
// Plays a video from the command line in a window provided by mpv.
|
||||
// You likely want to play the video in your own window instead,
|
||||
// but that's not quite ready yet.
|
||||
// You may need a basic Info.plist and MainMenu.xib to make this work.
|
||||
|
||||
#include "../../libmpv/client.h"
|
||||
#include "shared.h"
|
||||
|
||||
#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
|
||||
|
||||
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) {
|
||||
NSLog(@"Expected filename on command line");
|
||||
exit(1);
|
||||
}
|
||||
NSString *filename = args[1];
|
||||
|
||||
[self createWindow];
|
||||
|
||||
// Deal with MPV in the background.
|
||||
queue = dispatch_queue_create("mpv", DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_async(queue, ^{
|
||||
|
||||
mpv = mpv_create();
|
||||
if (!mpv) {
|
||||
printf("failed creating context\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int64_t wid = (intptr_t) [self->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.
|
||||
mpv_set_wakeup_callback(mpv, wakeup, (__bridge void *) self);
|
||||
|
||||
// Load the indicated file
|
||||
const char *cmd[] = {"loadfile", filename.UTF8String, NULL};
|
||||
check_error(mpv_command(mpv, cmd));
|
||||
});
|
||||
}
|
||||
|
||||
- (void) handleEvent:(mpv_event *)event
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case MPV_EVENT_SHUTDOWN:
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
- (void) readEvents
|
||||
{
|
||||
dispatch_async(queue, ^{
|
||||
while (mpv) {
|
||||
mpv_event *event = mpv_wait_event(mpv, 0);
|
||||
if (event->event_id == MPV_EVENT_NONE)
|
||||
break;
|
||||
[self handleEvent:event];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void wakeup(void *context) {
|
||||
AppDelegate *a = (__bridge AppDelegate *) context;
|
||||
[a readEvents];
|
||||
}
|
||||
|
||||
// Ostensibly, mpv's window would be hooked up to this.
|
||||
- (BOOL) windowShouldClose:(id)sender
|
||||
{
|
||||
[self shutdown];
|
||||
if (self->w)
|
||||
[self->w release];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) shutdown
|
||||
{
|
||||
if (mpv) {
|
||||
const char *args[] = {"quit", NULL};
|
||||
mpv_command(mpv, args);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
// Delete this if you already have a main.m.
|
||||
int main(int argc, const char * argv[]) {
|
||||
@autoreleasepool {
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
AppDelegate *delegate = [AppDelegate new];
|
||||
app.delegate = delegate;
|
||||
[app run];
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
// License: pick one of: public domain, WTFPL, ISC, Ms-PL, AGPLv3
|
||||
|
||||
// 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"
|
||||
|
||||
static void wakeup(void *ctx)
|
||||
{
|
||||
// This callback is invoked from any mpv thread (but possibly also
|
||||
// recursively from a thread that is calling the mpv API). Just notify
|
||||
// the Qt GUI thread to wake up (so that it can process events with
|
||||
// mpv_wait_event()), and return as quickly as possible.
|
||||
MainWindow *mainwindow = (MainWindow *)ctx;
|
||||
QCoreApplication::postEvent(mainwindow, new QEvent(QEvent::User));
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent)
|
||||
{
|
||||
setWindowTitle("Qt embedding demo");
|
||||
setMinimumSize(640, 480);
|
||||
|
||||
QMenu *menu = menuBar()->addMenu(tr("&File"));
|
||||
QAction *on_open = new QAction(tr("&Open"), this);
|
||||
on_open->setShortcuts(QKeySequence::Open);
|
||||
on_open->setStatusTip(tr("Open a file"));
|
||||
connect(on_open, SIGNAL(triggered()), this, SLOT(on_file_open()));
|
||||
menu->addAction(on_open);
|
||||
|
||||
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
|
||||
mpv_container = new QWidget(this);
|
||||
setCentralWidget(mpv_container);
|
||||
mpv_container->setAttribute(Qt::WA_DontCreateNativeAncestors);
|
||||
mpv_container->setAttribute(Qt::WA_NativeWindow);
|
||||
// If you have a HWND, use: int64_t wid = (intptr_t)hwnd;
|
||||
int64_t wid = mpv_container->winId();
|
||||
mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid);
|
||||
|
||||
// Enable default bindings, because we're lazy. Normally, a player using
|
||||
// mpv as backend would implement its own key bindings.
|
||||
mpv_set_option_string(mpv, "input-default-bindings", "yes");
|
||||
|
||||
// Enable keyboard input on the X11 window. For the messy details, see
|
||||
// --input-vo-keyboard on the manpage.
|
||||
mpv_set_option_string(mpv, "input-vo-keyboard", "yes");
|
||||
|
||||
// Let us receive property change events with MPV_EVENT_PROPERTY_CHANGE if
|
||||
// this property changes.
|
||||
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
|
||||
|
||||
mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);
|
||||
|
||||
// Request log messages with level "info" or higher.
|
||||
// They are received as MPV_EVENT_LOG_MESSAGE.
|
||||
mpv_request_log_messages(mpv, "info");
|
||||
|
||||
// From this point on, the wakeup function will be called. The callback
|
||||
// can come from any thread, so we use the Qt QEvent mechanism to relay
|
||||
// the wakeup in a thread-safe way.
|
||||
mpv_set_wakeup_callback(mpv, wakeup, this);
|
||||
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
throw "mpv failed to initialize";
|
||||
}
|
||||
|
||||
void MainWindow::handle_mpv_event(mpv_event *event)
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case MPV_EVENT_PROPERTY_CHANGE: {
|
||||
mpv_event_property *prop = (mpv_event_property *)event->data;
|
||||
if (strcmp(prop->name, "time-pos") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double time = *(double *)prop->data;
|
||||
std::stringstream ss;
|
||||
ss << "At: " << time;
|
||||
statusBar()->showMessage(QString::fromStdString(ss.str()));
|
||||
} else if (prop->format == MPV_FORMAT_NONE) {
|
||||
// The property is unavailable, which probably means playback
|
||||
// was stopped.
|
||||
statusBar()->showMessage("");
|
||||
}
|
||||
} else if (strcmp(prop->name, "chapter-list") == 0 ||
|
||||
strcmp(prop->name, "track-list") == 0)
|
||||
{
|
||||
// Dump the properties as JSON for demo purposes.
|
||||
#if QT_VERSION >= 0x050000
|
||||
if (prop->format == MPV_FORMAT_NODE) {
|
||||
QVariant v = mpv::qt::node_to_variant((mpv_node *)prop->data);
|
||||
// Abuse JSON support for easily printing the mpv_node contents.
|
||||
QJsonDocument d = QJsonDocument::fromVariant(v);
|
||||
append_log("Change property " + QString(prop->name) + ":\n");
|
||||
append_log(d.toJson().data());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_VIDEO_RECONFIG: {
|
||||
// Retrieve the new video size.
|
||||
int64_t w, h;
|
||||
if (mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &w) >= 0 &&
|
||||
mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) >= 0 &&
|
||||
w > 0 && h > 0)
|
||||
{
|
||||
// Note that the MPV_EVENT_VIDEO_RECONFIG event doesn't necessarily
|
||||
// imply a resize, and you should check yourself if the video
|
||||
// dimensions really changed.
|
||||
// mpv itself will scale/letter box the video to the container size
|
||||
// if the video doesn't fit.
|
||||
std::stringstream ss;
|
||||
ss << "Reconfig: " << w << " " << h;
|
||||
statusBar()->showMessage(QString::fromStdString(ss.str()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_LOG_MESSAGE: {
|
||||
struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
|
||||
std::stringstream ss;
|
||||
ss << "[" << msg->prefix << "] " << msg->level << ": " << msg->text;
|
||||
append_log(QString::fromStdString(ss.str()));
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_SHUTDOWN: {
|
||||
mpv_terminate_destroy(mpv);
|
||||
mpv = NULL;
|
||||
break;
|
||||
}
|
||||
default: ;
|
||||
// Ignore uninteresting or unknown events.
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWindow::event(QEvent *event)
|
||||
{
|
||||
// QEvent::User is sent by wakeup().
|
||||
if (event->type() == QEvent::User) {
|
||||
// Process all events, until the event queue is empty.
|
||||
while (mpv) {
|
||||
mpv_event *event = mpv_wait_event(mpv, 0);
|
||||
if (event->event_id == MPV_EVENT_NONE)
|
||||
break;
|
||||
handle_mpv_event(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return QMainWindow::event(event);
|
||||
}
|
||||
|
||||
void MainWindow::on_file_open()
|
||||
{
|
||||
QString filename = QFileDialog::getOpenFileName(this, "Open file");
|
||||
if (mpv) {
|
||||
const QByteArray c_filename = filename.toUtf8();
|
||||
const char *args[] = {"loadfile", c_filename.data(), NULL};
|
||||
mpv_command_async(mpv, 0, args);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::append_log(const QString &text)
|
||||
{
|
||||
QTextCursor cursor = log->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
cursor.insertText(text);
|
||||
log->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
if (mpv)
|
||||
mpv_terminate_destroy(mpv);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
|
||||
// Qt sets the locale in the QApplication constructor, but libmpv requires
|
||||
// the LC_NUMERIC category to be set to "C", so change it back.
|
||||
std::setlocale(LC_NUMERIC, "C");
|
||||
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
#ifndef QTEXAMPLE_H
|
||||
#define QTEXAMPLE_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
class QTextEdit;
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
~MainWindow();
|
||||
|
||||
protected:
|
||||
virtual bool event(QEvent *event);
|
||||
|
||||
private slots:
|
||||
void on_file_open();
|
||||
|
||||
private:
|
||||
QWidget *mpv_container;
|
||||
mpv_handle *mpv;
|
||||
QTextEdit *log;
|
||||
|
||||
void append_log(const QString &text);
|
||||
|
||||
void create_player();
|
||||
void handle_mpv_event(mpv_event *event);
|
||||
};
|
||||
|
||||
#endif // QTEXAMPLE_H
|
||||
@@ -1,12 +0,0 @@
|
||||
QT += core gui
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
TARGET = qtexample
|
||||
TEMPLATE = app
|
||||
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG = mpv
|
||||
|
||||
SOURCES += qtexample.cpp
|
||||
HEADERS += qtexample.h
|
||||
@@ -21,7 +21,6 @@ int main(int argc, char *argv[])
|
||||
// Enable default key bindings, so the user can actually interact with
|
||||
// the player (and e.g. close the window).
|
||||
check_error(mpv_set_option_string(ctx, "input-default-bindings", "yes"));
|
||||
mpv_set_option_string(ctx, "input-x11-keyboard", "yes");
|
||||
int val = 1;
|
||||
check_error(mpv_set_option(ctx, "osc", MPV_FORMAT_FLAG, &val));
|
||||
|
||||
|
||||
@@ -26,15 +26,6 @@ created scripts to help ease the process. These are the two recommended methods:
|
||||
Note that MinGW environments included in Linux distributions are often broken,
|
||||
outdated and useless, and usually don't use MinGW-w64.
|
||||
|
||||
Additional dependencies
|
||||
-----------------------
|
||||
|
||||
You need a pthread wrapper. It must be interoperable with native Windows
|
||||
threads. pthreads-win32 or MinGW pthreads might work.
|
||||
|
||||
If you want ``--input-file=...`` to work, you need libwaio. It's available
|
||||
from: git://midipix.org/waio
|
||||
|
||||
Example with MXE
|
||||
----------------
|
||||
|
||||
|
||||
+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
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ from the input video. Note that not all codecs and not all formats support VFR
|
||||
encoding, and some which do have bugs when a target bitrate is specified - use
|
||||
-ofps or -oautofps to force CFR encoding in these cases.
|
||||
|
||||
Of course, the options can be stored in a profile, like this .config/mpv/mpv.conf
|
||||
Of course, the options can be stored in a profile, like this .mpv/config
|
||||
section::
|
||||
|
||||
[myencprofile]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- Rebuild the terminal status line as a lua script
|
||||
-- Rebuild the status line as a lua script
|
||||
-- Be aware that this will require more cpu power!
|
||||
|
||||
-- Add a string to the status line
|
||||
+19
-20
@@ -72,17 +72,15 @@ Available filters are:
|
||||
|
||||
``tospdif=<yes|no>``
|
||||
Output raw AC-3 stream if ``no``, output to S/PDIF for
|
||||
pass-through if ``yes`` (default).
|
||||
passthrough if ``yes`` (default).
|
||||
|
||||
``bitrate=<rate>``
|
||||
The bitrate use for the AC-3 stream. Set it to 384 to get 384 kbps.
|
||||
|
||||
The default is 640. Some receivers might not be able to handle this.
|
||||
|
||||
Valid values: 32, 40, 48, 56, 64, 80, 96, 112, 128,
|
||||
160, 192, 224, 256, 320, 384, 448, 512, 576, 640.
|
||||
|
||||
The special value ``auto`` selects a default bitrate based on the
|
||||
The special value ``default`` selects a default bitrate based on the
|
||||
input channel number:
|
||||
|
||||
:1ch: 96
|
||||
@@ -94,7 +92,7 @@ Available filters are:
|
||||
|
||||
``minchn=<n>``
|
||||
If the input channel number is less than ``<minchn>``, the filter will
|
||||
detach itself (default: 3).
|
||||
detach itself (default: 5).
|
||||
|
||||
``sweep[=speed]``
|
||||
Produces a sine sweep.
|
||||
@@ -103,7 +101,7 @@ Available filters are:
|
||||
Sine function delta, use very low values to hear the sweep.
|
||||
|
||||
``sinesuppress[=freq:decay]``
|
||||
Remove a sine at the specified frequency. Useful to get rid of the 50/60 Hz
|
||||
Remove a sine at the specified frequency. Useful to get rid of the 50/60Hz
|
||||
noise on low quality audio equipment. It only works on mono input.
|
||||
|
||||
``<freq>``
|
||||
@@ -184,7 +182,7 @@ Available filters are:
|
||||
|
||||
``mpv --af=equalizer=11:11:10:5:0:-12:0:5:12:12 media.avi``
|
||||
Would amplify the sound in the upper and lower frequency region
|
||||
while canceling it almost completely around 1 kHz.
|
||||
while canceling it almost completely around 1kHz.
|
||||
|
||||
``channels=nch[:routes]``
|
||||
Can be used for adding, removing, routing and copying audio channels. If
|
||||
@@ -265,8 +263,9 @@ Available filters are:
|
||||
Filter for internal use only. Converts between 24-bit and 32-bit sample
|
||||
formats.
|
||||
|
||||
``convertsign``
|
||||
Filter for internal use only. Converts between signed/unsigned formats.
|
||||
``convertsignendian``
|
||||
Filter for internal use only. Converts between signed/unsigned formats
|
||||
and formats with different endian.
|
||||
|
||||
``volume[=<volumedb>[:...]]``
|
||||
Implements software volume control. Use this filter with caution since it
|
||||
@@ -278,8 +277,8 @@ Available filters are:
|
||||
once for every audio stream.
|
||||
|
||||
``<volumedb>``
|
||||
Sets the desired gain in dB for all channels in the stream from -200 dB
|
||||
to +60 dB, where -200 dB mutes the sound completely and +60 dB equals a
|
||||
Sets the desired gain in dB for all channels in the stream from -200dB
|
||||
to +60dB, where -200dB mutes the sound completely and +60dB equals a
|
||||
gain of 1000 (default: 0).
|
||||
``replaygain-track``
|
||||
Adjust volume gain according to the track-gain replaygain value stored
|
||||
@@ -312,7 +311,7 @@ Available filters are:
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=volume=10.1 media.avi``
|
||||
Would amplify the sound by 10.1 dB and hard-clip if the sound level
|
||||
Would amplify the sound by 10.1dB and hard-clip if the sound level
|
||||
is too high.
|
||||
|
||||
``pan=n:[<matrix>]``
|
||||
@@ -369,8 +368,8 @@ Available filters are:
|
||||
to the subwoofer.
|
||||
|
||||
``<fc>``
|
||||
cutoff frequency in Hz for the low-pass filter (20 Hz to 300 Hz)
|
||||
(default: 60 Hz) For the best result try setting the cutoff frequency
|
||||
cutoff frequency in Hz for the low-pass filter (20Hz to 300Hz)
|
||||
(default: 60Hz) For the best result try setting the cutoff frequency
|
||||
as low as possible. This will improve the stereo or surround sound
|
||||
experience.
|
||||
``<ch>``
|
||||
@@ -382,7 +381,7 @@ Available filters are:
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=sub=100:4 --audio-channels=5 media.avi``
|
||||
Would add a subwoofer channel with a cutoff frequency of 100 Hz to
|
||||
Would add a subwoofer channel with a cutoff frequency of 100Hz to
|
||||
output channel 4.
|
||||
|
||||
``center``
|
||||
@@ -410,7 +409,7 @@ Available filters are:
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=surround=15 --audio-channels=4 media.avi``
|
||||
Would add surround sound decoding with 15 ms delay for the sound to
|
||||
Would add surround sound decoding with 15ms delay for the sound to
|
||||
the rear speakers.
|
||||
|
||||
``delay[=[ch1,ch2,...]]``
|
||||
@@ -438,8 +437,8 @@ Available filters are:
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=delay=[10.5,10.5,0,0,7,0] media.avi``
|
||||
Would delay front left and right by 10.5 ms, the two rear channels
|
||||
and the subwoofer by 0 ms and the center channel by 7 ms.
|
||||
Would delay front left and right by 10.5ms, the two rear channels
|
||||
and the subwoofer by 0ms and the center channel by 7ms.
|
||||
|
||||
``export=mmapped_file:nsamples]``
|
||||
Exports the incoming signal to other processes using memory mapping
|
||||
@@ -589,7 +588,7 @@ Available filters are:
|
||||
tempo.
|
||||
|
||||
``mpv --af=scaletempo=stride=30:overlap=.50:search=10 media.ogg``
|
||||
Would tweak the quality and performance parameters.
|
||||
Would tweak the quality and performace parameters.
|
||||
|
||||
``mpv --af=format=float,scaletempo media.ogg``
|
||||
Would make scaletempo use float code. Maybe faster on some
|
||||
@@ -601,7 +600,7 @@ Available filters are:
|
||||
1.2x.
|
||||
|
||||
``lavfi=graph``
|
||||
Filter audio using FFmpeg's libavfilter.
|
||||
Filter audio using ffmpeg's libavfilter.
|
||||
|
||||
``<graph>``
|
||||
Libavfilter graph. See ``lavfi`` video filter for details - the graph
|
||||
|
||||
+6
-24
@@ -23,7 +23,7 @@ normal driver parameters.
|
||||
where PulseAudio is used. On Windows, ``--ao=wasapi`` is preferred,
|
||||
though it might cause trouble sometimes, in which case ``--ao=dsound``
|
||||
should be used. On BSD systems, ``--ao=oss`` or `--ao=sndio`` may work
|
||||
(the latter being experimental). On OS X systems, use ``--ao=coreaudio``.
|
||||
(the latter being experimental). On OSX systems, use ``--ao=coreaudio``.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
@@ -53,10 +53,6 @@ Available audio output drivers are:
|
||||
Set the index of the mixer channel (default: 0). Consider the output of
|
||||
"``amixer scontrols``", then the index is the number that follows the
|
||||
name of the element.
|
||||
``non-interleaved``
|
||||
Allow output of non-interleaved formats (if the audio decoder uses
|
||||
this format). Currently disabled by default, because some popular
|
||||
ALSA plugins are utterly broken with non-interleaved formats.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -114,17 +110,12 @@ 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
|
||||
sound server.
|
||||
Native Mac OS X audio output driver
|
||||
|
||||
Automatically redirects to ``coreaudio_exclusive`` when playing compressed
|
||||
formats.
|
||||
|
||||
``coreaudio_exclusive`` (Mac OS X only)
|
||||
Native Mac OS X audio output driver using direct device access and
|
||||
exclusive mode (bypasses the sound server).
|
||||
|
||||
Supports only compressed formats (AC3 and DTS).
|
||||
``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
|
||||
@@ -146,12 +137,6 @@ Available audio output drivers are:
|
||||
value makes the audio stream react faster, e.g. to playback speed
|
||||
changes. Default: 250.
|
||||
|
||||
``latency-hacks=<yes|no>``
|
||||
Enable hacks to workaround PulseAudio timing bugs (default: yes). If
|
||||
enabled, mpv will do elaborate latency calculations on its own. If
|
||||
disabled, it will use PulseAudio automatically updated timing
|
||||
information. Disabling this might help with e.g. networked audio.
|
||||
|
||||
``portaudio``
|
||||
PortAudio audio output driver. This works on all platforms, and has
|
||||
extensive MS Windows support.
|
||||
@@ -174,9 +159,6 @@ Available audio output drivers are:
|
||||
Sets the device number to use. Playing a file with ``-v`` will show a
|
||||
list of available devices.
|
||||
|
||||
``buffersize=<ms>``
|
||||
DirectSound buffer size in milliseconds (default: 200).
|
||||
|
||||
``sdl``
|
||||
SDL 1.2+ audio output driver. Should work on any platform supported by SDL
|
||||
1.2, but may require the ``SDL_AUDIODRIVER`` environment variable to be set
|
||||
|
||||
+99
-166
@@ -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 buildsystem
|
||||
* 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 OSX
|
||||
* General OSX 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 filenames,
|
||||
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 JPG or PNG, configurable
|
||||
filenames, 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 builtin demuxers, using libavformat instead
|
||||
* Removal of builtin 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.
|
||||
* OSX: Cocoa event loop is independent from mplayer's event loop, so user
|
||||
actions like accessing menus and live resizing do not block the playback.
|
||||
* OSX: Apple Remote support.
|
||||
* OSX: Media Keys support.
|
||||
* Windows: Added WASAPI audio output.
|
||||
* New OSD bar with chapter marks and not positioned in the middle of the video
|
||||
(though this can be customized with the ``--osd-bar-align-y`` option).
|
||||
* Allow customizing whether a key binding for seeking shows the video time, the
|
||||
OSD bar, or nothing (see section `Input Command Prefixes`_).
|
||||
* Display list of chapters and audio/subtitle tracks on OSD (see section
|
||||
`Properties`_).
|
||||
* ``--keep-open`` option to stop the player from closing the window and
|
||||
exiting after playback ends.
|
||||
* A client API, that allows embedding **mpv** into applications
|
||||
(see ``libmpv/client.h`` in the sources).
|
||||
|
||||
Input
|
||||
~~~~~
|
||||
|
||||
* Improved default keybindings. MPlayer bindings are also available (see
|
||||
``etc/mplayer-input.conf`` in the source tree).
|
||||
* Improved responsiveness on user input.
|
||||
* Support for modifier keys (alt, shift, ctrl) in input.conf.
|
||||
* Allow customizing whether a key binding for seeking shows the video time, the
|
||||
OSD bar, or nothing (see the `Input Command Prefixes`_ section).
|
||||
* Support mapping multiple commands to one key.
|
||||
|
||||
Audio
|
||||
~~~~~
|
||||
|
||||
* Support for gapless audio (see the ``--gapless-audio`` option).
|
||||
* Support for OSS4 volume control.
|
||||
* Improved support for PulseAudio.
|
||||
* Make ``--softvol`` default (**mpv** is not a mixer control panel).
|
||||
* By default, do pitch correction if playback speed is increased.
|
||||
* Improved downmixing and output of surround audio:
|
||||
|
||||
- Instead of using hardcoded pan filters to do remixing, libavresample is used
|
||||
- Channel maps are used to identify the channel layout, so e.g. ``3.0`` and
|
||||
``2.1`` audio can be distinguished.
|
||||
|
||||
Video
|
||||
~~~~~
|
||||
|
||||
* Wayland support.
|
||||
* Native support for VAAPI and VDA. Improved VDPAU video output.
|
||||
* Improved OpenGL output (see the ``opengl-hq`` video output).
|
||||
* Make hardware decoding work with the ``opengl`` video output.
|
||||
* Re-enable screensaver while the player is paused.
|
||||
* Matroska edition switching at runtime.
|
||||
* Support for libavfilter (for video->video and audio->audio). This allows
|
||||
using most of FFmpeg's filters, which improve greatly on the old MPlayer
|
||||
filters in features, performance, and correctness.
|
||||
* More correct color reproduction (color matrix generation).
|
||||
* Better subtitle rendering using libass by default.
|
||||
* Improvements when playing multiple files (``--fixed-vo`` is default, do not
|
||||
reset settings by default when playing a new file).
|
||||
* Replace image video outputs (``--vo=jpeg`` etc.) with ``--vo=image``.
|
||||
* Removal of ``--vo=gif89a``, ``--vo=md5sum``, ``--vo=yuv4mpeg``, as encoding
|
||||
can handle these use cases. For yuv4mpeg, for example, use::
|
||||
|
||||
mpv input.mkv -o output.y4m --no-audio --oautofps --oneverdrop
|
||||
|
||||
* Image subtitles (DVDs etc.) are rendered in color and use more correct
|
||||
positioning (color for image subs can be disabled with ``--sub-gray``).
|
||||
|
||||
OSD and terminal
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
* Cleaned up terminal output: nicer status line, less useless noise.
|
||||
* Improved OSD rendering using libass, with full Unicode support.
|
||||
* New OSD bar with chapter marks. Not positioned in the middle of the video
|
||||
(this can be customized with the ``--osd-bar-align-y`` option).
|
||||
* Display list of chapters and audio/subtitle tracks on OSD (see the
|
||||
`Properties`_ section).
|
||||
|
||||
Screenshots
|
||||
~~~~~~~~~~~
|
||||
|
||||
* Instant screenshots without 1-frame delay.
|
||||
* Support for taking screenshots even with hardware decoding.
|
||||
* Support for saving screenshots as JPEG or PNG.
|
||||
* Support for configurable file names.
|
||||
* Support for taking screenshots with or without subtitles.
|
||||
|
||||
Note that the ``screenshot`` video filter is not needed anymore, and should not
|
||||
be put into the mpv config file.
|
||||
|
||||
Miscellaneous
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
* Better MKV support (e.g. ordered chapters, 3D metadata).
|
||||
* Matroska edition switching at runtime.
|
||||
* Support for playing URLs of popular streaming sites directly.
|
||||
(e.g. ``mpv https://www.youtube.com/watch?v=...``).
|
||||
Requires a recent version of ``youtube-dl`` to be installed, and
|
||||
``ytdl=yes`` in the mpv config file.
|
||||
* Support for precise scrolling which scales the parameter of commands. If the
|
||||
input doesn't support precise scrolling the scale factor stays 1.
|
||||
* Improved downmixing and output of surround audio. Instead of using hardcoded
|
||||
pan filters to do remixing, use libavresample. Channel maps are used to
|
||||
identify the channel layout, so e.g. ``3.0`` and ``2.1`` audio can be
|
||||
distinguished.
|
||||
* Allow resuming playback at a later point with ``Shift+q``, also see
|
||||
``quit_watch_later`` input command.
|
||||
* Support mapping multiple commands to one key.
|
||||
* Allow changing/adjusting video filters at runtime. (This is also used to make
|
||||
the ``D`` key insert vf_yadif if deinterlacing is not supported otherwise).
|
||||
* Improved support for .cue files.
|
||||
|
||||
Mac OS X
|
||||
~~~~~~~~
|
||||
|
||||
* Native OpenGL backend.
|
||||
* Cocoa event loop is independent from MPlayer's event loop, so user
|
||||
actions like accessing menus and live resizing do not block the playback.
|
||||
* Apple Remote support.
|
||||
* Media Keys support.
|
||||
* VDA support using libavcodec hwaccel API instead of FFmpeg's decoder with up
|
||||
the ``D`` key insert vf_yadif if deinterlacing is not supported otherwise.)
|
||||
* Native VAAPI support
|
||||
* OSX: VDA support using libavcodec hwaccel API insted 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
|
||||
----------------------------------------
|
||||
@@ -363,40 +308,28 @@ input.conf and Slave Commands
|
||||
Slave mode
|
||||
~~~~~~~~~~
|
||||
|
||||
* Slave mode was removed. A proper slave mode application needed tons of code
|
||||
and hacks to get
|
||||
* Slave mode is broken. This mode is entirely insane in the ``old`` versions of
|
||||
MPlayer. A proper slave mode application needed tons of code and hacks to get
|
||||
it right. The main problem is that slave mode is a bad and incomplete
|
||||
interface, and to get around that, applications parsed output messages
|
||||
intended for users. It is hard to know which messages exactly are parsed by
|
||||
slave mode applications. This makes it virtually impossible to improve
|
||||
terminal output intended for users without possibly breaking something.
|
||||
|
||||
This is absolutely insane, and since initial improvements to **mpv** quickly
|
||||
made slave mode incompatible to most applications, it was removed as useless
|
||||
cruft. The client API (see below) is provided instead.
|
||||
This is absolutely insane, and **mpv** will not try to keep slave mode
|
||||
compatible. If you are a developer of a slave mode application, contact us,
|
||||
and a new and better protocol can be developed.
|
||||
|
||||
``--identify`` was replaced by the ``TOOLS/mpv_identify.sh`` wrapper script.
|
||||
|
||||
* For some time (until including release 0.4.x), mpv supported a
|
||||
``--slave-broken`` option. The following options are equivalent:
|
||||
|
||||
::
|
||||
|
||||
--input-file=/dev/stdin --input-terminal=no
|
||||
|
||||
|
||||
Assuming the system supports ``/dev/stdin``.
|
||||
|
||||
(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.
|
||||
``--identify`` was already removed (``TOOLS/mpv_identify.sh`` is provided
|
||||
instead), and ``--slave-broken`` might be removed in the future.
|
||||
|
||||
* **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.)
|
||||
It might also be possible to implement a custom slave mode-like protocol
|
||||
using Lua scripting.
|
||||
|
||||
* A slave protocol is planned, but nothing has emerged yet.
|
||||
|
||||
Policy for Removed Features
|
||||
---------------------------
|
||||
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ You can encode files from one format/codec to another using this facility.
|
||||
|
||||
``--oautofps``
|
||||
Sets the output format time base to the guessed frame rate of the input
|
||||
video (simulates MEncoder behavior, useful for AVI; may cause frame drops).
|
||||
video (simulates mencoder behavior, useful for AVI; may cause frame drops).
|
||||
Note that not all codecs and not all formats support VFR encoding, and some
|
||||
which do have bugs when a target bitrate is specified - use ``--ofps`` or
|
||||
``--oautofps`` to force CFR encoding in these cases.
|
||||
@@ -74,7 +74,7 @@ You can encode files from one format/codec to another using this facility.
|
||||
.. admonition:: Example
|
||||
|
||||
"``--oac=libmp3lame --oacopts=b=128000``"
|
||||
selects 128 kbps MP3 encoding.
|
||||
selects 128kbps MP3 encoding.
|
||||
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
|
||||
+45
-324
@@ -4,19 +4,12 @@ INPUT.CONF
|
||||
The input.conf file consists of a list of key bindings, for example::
|
||||
|
||||
s screenshot # take a screenshot with the s key
|
||||
LEFT seek 15 # map the left-arrow key to seeking forward by 15 seconds
|
||||
|
||||
Each line maps a key to an input command. Keys are specified with their literal
|
||||
value (upper case if combined with ``Shift``), or a name for special keys. For
|
||||
example, ``a`` maps to the ``a`` key without shift, and ``A`` maps to ``a``
|
||||
with shift.
|
||||
|
||||
The file is located in the mpv configuration directory (normally at
|
||||
``~/.config/mpv/input.conf`` depending on platform). The default bindings are
|
||||
defined here::
|
||||
|
||||
https://github.com/mpv-player/mpv/blob/master/etc/input.conf
|
||||
|
||||
A list of special keys can be obtained with
|
||||
|
||||
``mpv --input-keylist``
|
||||
@@ -108,14 +101,14 @@ List of Input Commands
|
||||
``revert_seek`` command itself.
|
||||
|
||||
``frame_step``
|
||||
Play one frame, then pause. Does nothing with audio-only playback.
|
||||
Play one frame, then pause.
|
||||
|
||||
``frame_back_step``
|
||||
Go back by one frame, then pause. Note that this can be very slow (it tries
|
||||
to be precise, not fast), and sometimes fails to behave as expected. How
|
||||
well this works depends on whether precise seeking works correctly (e.g.
|
||||
see the ``--hr-seek-demuxer-offset`` option). Video filters or other video
|
||||
post-processing that modifies timing of frames (e.g. deinterlacing) should
|
||||
postprocessing that modifies timing of frames (e.g. deinterlacing) should
|
||||
usually work, but might make backstepping silently behave incorrectly in
|
||||
corner cases. Using ``--hr-seek-framedrop=no`` should help, although it
|
||||
might make precise seeking slower.
|
||||
@@ -160,9 +153,7 @@ List of Input Commands
|
||||
Take a single screenshot.
|
||||
<each-frame>
|
||||
Take a screenshot each frame. Issue this command again to stop taking
|
||||
screenshots. Note that you should disable frame-dropping when using
|
||||
this mode - or you might receive duplicate images in cases when a
|
||||
frame was dropped.
|
||||
screenshots.
|
||||
|
||||
``screenshot_to_file "<filename>" [subtitles|video|window]``
|
||||
Take a screenshot and save it to a given file. The format of the file will
|
||||
@@ -193,7 +184,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:
|
||||
@@ -202,10 +193,6 @@ List of Input Commands
|
||||
Stop playback of the current file, and play the new file immediately.
|
||||
<append>
|
||||
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,39 +249,16 @@ 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>]]]``
|
||||
Load the given subtitle file. It is selected as current subtitle after
|
||||
``sub_add "<file>"``
|
||||
Load the given subtitle file. It is not 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.)
|
||||
|
||||
``sub_reload [<id>]``
|
||||
Reload the given subtitle tracks. If the ``id`` argument is missing, reload
|
||||
Reload the given subtitle tracks. If the ``id`` argument is missing, remove
|
||||
the current track. (Works on external subtitle files only.)
|
||||
|
||||
This works by unloading and re-adding the subtitle track.
|
||||
@@ -353,12 +317,6 @@ List of Input Commands
|
||||
Write the resume config file that the ``quit_watch_later`` command writes,
|
||||
but continue playback normally.
|
||||
|
||||
``stop``
|
||||
Stop playback and clear playlist. With default settings, this is
|
||||
essentially like ``quit``. Useful for the client API: playback can be
|
||||
stopped without terminating the player.
|
||||
|
||||
|
||||
Input Commands that are Possibly Subject to Change
|
||||
--------------------------------------------------
|
||||
|
||||
@@ -428,7 +386,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 +440,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 +489,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).
|
||||
Undocumented commands: ``tv_last_channel`` (TV/DVB only), ``stop`` (questionable
|
||||
use), ``get_property`` (?), ``vo_cmdline`` (experimental).
|
||||
|
||||
Input Command Prefixes
|
||||
----------------------
|
||||
@@ -605,9 +519,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.
|
||||
@@ -652,7 +564,7 @@ Property list
|
||||
See ``--osd-level``.
|
||||
|
||||
``osd-scale`` (RW)
|
||||
OSD font size multiplier, see ``--osd-scale``.
|
||||
OSD font size multiplicator, see ``--osd-scale``.
|
||||
|
||||
``loop`` (RW)
|
||||
See ``--loop``.
|
||||
@@ -674,23 +586,14 @@ Property list
|
||||
``${stream-end}``. For ordered chapters and such, the
|
||||
size of the currently played segment is returned.)
|
||||
|
||||
``estimated-frame-count``
|
||||
Total number of frames in current file.
|
||||
|
||||
.. note:: This is only an estimate. (It's computed from two unreliable
|
||||
quantities: fps and stream length.)
|
||||
|
||||
``estimated-frame-number``
|
||||
Number of current frame in current stream.
|
||||
|
||||
.. note:: This is only an estimate. (It's computed from two unreliable
|
||||
quantities: fps and possibly rounded timestamps.)
|
||||
|
||||
``path``
|
||||
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.
|
||||
|
||||
@@ -710,6 +613,11 @@ Property list
|
||||
``stream-end``
|
||||
Raw end position in bytes in source stream.
|
||||
|
||||
``stream-time-pos`` (RW)
|
||||
Time position in source stream. This only works for DVD and Bluray. This
|
||||
is probably never different from ``time-pos``, because ``time-pos`` is
|
||||
forced to this value anyway.
|
||||
|
||||
``length``
|
||||
Length of the current file in seconds. If the length is unknown, the
|
||||
property is unavailable. Note that the file duration is not always exactly
|
||||
@@ -724,13 +632,8 @@ 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.
|
||||
|
||||
``vo-drop-frame-count``
|
||||
Frames dropped by VO (when using ``--framedrop=vo``).
|
||||
Frames dropped because they arrived to late. Unavailable if video
|
||||
is disabled
|
||||
|
||||
``percent-pos`` (RW)
|
||||
Position in current file (0-100). The advantage over using this instead of
|
||||
@@ -738,6 +641,9 @@ Property list
|
||||
estimating the playback position from the byte position, if the file
|
||||
duration is not known.
|
||||
|
||||
``ratio-pos`` (RW)
|
||||
Position in current file (0.0-1.0). higher precision that ``percent-pos``.
|
||||
|
||||
``time-pos`` (RW)
|
||||
Position in current file in seconds.
|
||||
|
||||
@@ -752,9 +658,6 @@ Property list
|
||||
``playtime-remaining``
|
||||
``time-remaining`` scaled by the the current ``speed``.
|
||||
|
||||
``playback-time``
|
||||
Return the playback time, which is the time difference between start PTS and current PTS.
|
||||
|
||||
``chapter`` (RW)
|
||||
Current chapter number. The number of the first chapter is 0.
|
||||
|
||||
@@ -814,10 +717,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.
|
||||
|
||||
@@ -860,7 +759,7 @@ Property list
|
||||
::
|
||||
|
||||
MPV_FORMAT_NODE_MAP
|
||||
(key and string value for each metadata entry)
|
||||
(key and string value for each metdata entry)
|
||||
|
||||
``chapter-metadata``
|
||||
Metadata of current chapter. Works similar to ``metadata`` property. It
|
||||
@@ -891,12 +790,8 @@ 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).
|
||||
Network cache fill state (0-100).
|
||||
|
||||
``cache-size`` (RW)
|
||||
Total network cache size in KB. This is similar to ``--cache``. This allows
|
||||
@@ -908,34 +803,11 @@ Property list
|
||||
this easier, the cache resizing code will allocate the new cache while the
|
||||
old cache is still allocated.
|
||||
|
||||
Don't use this when playing DVD or Blu-ray.
|
||||
|
||||
``cache-free`` (R)
|
||||
Total free cache size in KB.
|
||||
|
||||
``cache-used`` (R)
|
||||
Total used cache size in KB.
|
||||
|
||||
``cache-idle`` (R)
|
||||
Returns ``yes`` if the cache is idle, which means the cache is filled as
|
||||
much as possible, and is currently not reading more data.
|
||||
|
||||
``demuxer-cache-duration``
|
||||
Approximate duration of video buffered in the demuxer, in seconds. The
|
||||
guess is very unreliable, and often the property will not be available
|
||||
at all, even if data is buffered.
|
||||
|
||||
``demuxer-cache-idle``
|
||||
Returns ``yes`` if the demuxer is idle, which means the demuxer cache is
|
||||
filled to the requested amount, and is currently not reading more data.
|
||||
Don't use this when playing DVD or Bluray.
|
||||
|
||||
``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,
|
||||
@@ -943,12 +815,6 @@ Property list
|
||||
or enter idle mode), and in these cases the ``eof-reached`` property will
|
||||
logically be cleared immediately after it's set.
|
||||
|
||||
``seeking``
|
||||
Returns ``yes`` if the player is currently seeking, or otherwise trying
|
||||
to restart playback. (It's possible that it returns ``yes`` while a file
|
||||
is loaded, or when switching ordered chapter segments. This is because
|
||||
the same underlying code is used for seeking and resyncing.)
|
||||
|
||||
``pts-association-mode`` (RW)
|
||||
See ``--pts-association-mode``.
|
||||
|
||||
@@ -997,9 +863,6 @@ Property list
|
||||
``deinterlace`` (RW)
|
||||
See ``--deinterlace``.
|
||||
|
||||
``field-dominance`` (RW)
|
||||
See ``--field-dominance``
|
||||
|
||||
``colormatrix`` (RW)
|
||||
See ``--colormatrix``.
|
||||
|
||||
@@ -1115,24 +978,19 @@ Property list
|
||||
"par" MPV_FORMAT_DOUBLE
|
||||
"colormatrix" MPV_FORMAT_STRING
|
||||
"colorlevels" MPV_FORMAT_STRING
|
||||
"primaries" MPV_FORMAT_STRING
|
||||
"chroma-location" MPV_FORMAT_STRING
|
||||
"rotate" MPV_FORMAT_INT64
|
||||
|
||||
``dwidth``, ``dheight``
|
||||
Video display size. This is the video size after filters and aspect scaling
|
||||
have been applied. The actual video window size can still be different
|
||||
from this, e.g. if the user resized the video window manually.
|
||||
|
||||
These have the same values as ``video-out-params/dw`` and
|
||||
``video-out-params/dh``.
|
||||
from this.
|
||||
|
||||
``video-out-params``
|
||||
Same as ``video-params``, but after video filters have been applied. If
|
||||
there are no video filters in use, this will contain the same values as
|
||||
``video-params``. Note that this is still not necessarily what the video
|
||||
window uses, since the user can change the window size, and all real VOs
|
||||
do their own scaling independently from the filter chain.
|
||||
window uses, since all real VOs do their own scaling.
|
||||
|
||||
Has the same sub-properties as ``video-params``.
|
||||
|
||||
@@ -1144,7 +1002,7 @@ Property list
|
||||
Estimated/measured FPS of the video filter chain output. (If no filters
|
||||
are used, this corresponds to decoder output.) This uses the average of
|
||||
the 10 past frame durations to calculate the FPS. It will be inaccurate
|
||||
if frame-dropping is involved (such as when framedrop is explicitly
|
||||
if framedropping is involved (such as when framedrop is explicitly
|
||||
enabled, or after precise seeking). Files with imprecise timestamps (such
|
||||
as Matroska) might lead to unstable results.
|
||||
|
||||
@@ -1152,16 +1010,9 @@ Property list
|
||||
Window size multiplier. Setting this will resize the video window to the
|
||||
values contained in ``dwidth`` and ``dheight`` multiplied with the value
|
||||
set with this property. Setting ``1`` will resize to original video size
|
||||
(or to be exact, the size the video filters output). ``2`` will set the
|
||||
(or to be exactly, 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``.
|
||||
|
||||
@@ -1216,7 +1067,7 @@ Property list
|
||||
See ``--sub-forced-only``.
|
||||
|
||||
``sub-scale`` (RW)
|
||||
Subtitle font size multiplier.
|
||||
Subtitle font size multiplicator.
|
||||
|
||||
``ass-use-margins`` (RW)
|
||||
See ``--ass-use-margins``.
|
||||
@@ -1228,8 +1079,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 +1105,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 +1114,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
|
||||
@@ -1369,6 +1211,11 @@ Property list
|
||||
"title" MPV_FORMAT_STRING
|
||||
"time" MPV_FORMAT_DOUBLE
|
||||
|
||||
``quvi-format`` (RW)
|
||||
See ``--quvi-format``. Cycling this property (``cycle``) will attempt to
|
||||
cycle through known format, although currently this feature doesn't work
|
||||
well at all.
|
||||
|
||||
``af`` (RW)
|
||||
See ``--af`` and the ``af`` command.
|
||||
|
||||
@@ -1391,143 +1238,17 @@ 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.
|
||||
The control code is implementation specific and is useless for anything else.
|
||||
|
||||
``osd-ass-cc``
|
||||
``${osd-ass-cc/0}`` disables escaping ASS sequences of text in OSD,
|
||||
``${osd-ass-cc/1}`` enables it again. By default, ASS sequences are
|
||||
escaped to avoid accidental formatting, and this property can disable
|
||||
this behavior. Note that the properties return an opaque OSD control
|
||||
code, which only makes sense for the ``show_text`` command or options
|
||||
which set OSD messages.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
--osd-status-msg='This is ${osd-ass-cc/0}{\\b1}bold text'
|
||||
|
||||
Any ASS override tags as understood by libass can be used.
|
||||
|
||||
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.
|
||||
``write_watch_later_config``
|
||||
Saves current playback position.
|
||||
|
||||
Property Expansion
|
||||
------------------
|
||||
|
||||
@@ -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.
|
||||
+43
-169
@@ -1,16 +1,10 @@
|
||||
LUA SCRIPTING
|
||||
=============
|
||||
|
||||
mpv can load Lua scripts. Scripts passed to the ``--lua`` option, or found in
|
||||
the ``lua`` subdirectory of the mpv configuration directory (usually
|
||||
``~/.config/mpv/lua/``) will be loaded on program start. mpv also appends the
|
||||
``lua`` subdirectory to the end of Lua's path so you can import scripts from
|
||||
there too. Since it's added to the end, don't name scripts you want to import
|
||||
the same as Lua libraries because they will be overshadowed by them.
|
||||
|
||||
mpv provides the built-in module ``mp``, which provides functions to send
|
||||
commands to the mpv core and to retrieve information about playback state, user
|
||||
settings, file information, and so on.
|
||||
mpv can load Lua scripts. Scripts in ``~/.mpv/lua/`` will be loaded on program
|
||||
start, or if passed to ``--lua``. mpv provides the builtin module ``mp``, which
|
||||
provides functions to send commands to the mpv core and to retrieve information
|
||||
about playback state, user settings, file information, and so on.
|
||||
|
||||
These scripts can be used to control mpv in a similar way to slave mode.
|
||||
Technically, the Lua code uses the client API internally.
|
||||
@@ -33,13 +27,12 @@ allow users implement features which are not going to be added to the mpv core.
|
||||
Mode of operation
|
||||
-----------------
|
||||
|
||||
Your script will be loaded by the player at program start from the ``lua``
|
||||
configuration subdirectory, from a path specified with the ``--lua`` option, or
|
||||
in some cases, internally (like ``--osc``). Each script runs in its own
|
||||
thread. Your script is first run "as is", and once that is done, the event loop
|
||||
is entered. This event loop will dispatch events received by mpv and call your
|
||||
own event handlers which you have registered with ``mp.register_event``, or
|
||||
timers added with ``mp.add_timeout`` or similar.
|
||||
Your script will be loaded by the player at program start from ``~/.mpv/lua/``,
|
||||
or ``--lua``, or in some cases, internally (like ``--osc``). Each script runs
|
||||
in its own thread. Your script is first run "as is", and once that is done,
|
||||
the event loop is entered. This event loop will dispatch events received by mpv
|
||||
and call your own event handlers which you have registered with
|
||||
``mp.register_event``, or timers added with ``mp.add_timeout`` or similar.
|
||||
|
||||
When the player quits, all scripts will be asked to terminate. This happens via
|
||||
a ``shutdown`` event, which by default will make the event loop return. If your
|
||||
@@ -54,15 +47,6 @@ The event loop will wait for events and dispatch events registered with
|
||||
``mp.register_event``. It will also handle timers added with ``mp.add_timeout``
|
||||
and similar (by waiting with a timeout).
|
||||
|
||||
Since mpv 0.6.0, the player will wait until the script is fully loaded before
|
||||
continuing normal operation. The player considers a script as fully loaded as
|
||||
soon as it starts waiting for mpv events (or it exits). In practice this means
|
||||
the player will more or less hang until the script returns from the main chunk
|
||||
(and ``mp_event_loop`` is called), or the script calls ``mp_event_loop`` or
|
||||
``mp.dispatch_events`` directly. This is done to make it possible for a script
|
||||
to fully setup event handlers etc. before playback actually starts. In older
|
||||
mpv versions, this happened asynchronously.
|
||||
|
||||
mp functions
|
||||
------------
|
||||
|
||||
@@ -90,19 +74,6 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
These two commands are equivalent, except that the first version breaks
|
||||
if the filename contains spaces or certain special characters.
|
||||
|
||||
Note that properties are *not* expanded. You can use either ``mp.command``,
|
||||
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
|
||||
@@ -123,9 +94,9 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
will always result in a string.
|
||||
|
||||
``mp.get_property_bool(name [,def])``
|
||||
Similar to ``mp.get_property``, but return the property value as Boolean.
|
||||
Similar to ``mp.get_property``, but return the property value as boolean.
|
||||
|
||||
Returns a Boolean on success, or ``def, error`` on error.
|
||||
Returns a boolean on success, or ``def, error`` on error.
|
||||
|
||||
``mp.get_property_number(name [,def])``
|
||||
Similar to ``mp.get_property``, but return the property value as number.
|
||||
@@ -138,7 +109,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
|
||||
``mp.get_property_native(name [,def])``
|
||||
Similar to ``mp.get_property``, but return the property value using the best
|
||||
Lua type for the property. Most time, this will return a string, Boolean,
|
||||
Lua type for the property. Most time, this will return a string, boolean,
|
||||
or number. Some properties (for example ``chapter-list``) are returned as
|
||||
tables.
|
||||
|
||||
@@ -153,7 +124,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
|
||||
``mp.set_property_bool(name, value)``
|
||||
Similar to ``mp.set_property``, but set the given property to the given
|
||||
Boolean value.
|
||||
boolean value.
|
||||
|
||||
``mp.set_property_number(name, value)``
|
||||
Similar to ``mp.set_property``, but set the given property to the given
|
||||
@@ -181,14 +152,14 @@ 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
|
||||
(e.g. ``ctrl+a``).
|
||||
|
||||
After calling this function, key presses will cause the function ``fn`` to
|
||||
be called (unless the user remapped the key with another binding).
|
||||
be called (unless the user overmapped the key with another binding).
|
||||
|
||||
The ``name`` argument should be a short symbolic string. It allows the user
|
||||
to remap the key binding via input.conf using the ``script_message``
|
||||
@@ -198,23 +169,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,18 +193,18 @@ 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
|
||||
still work, unless the user remaps it.)
|
||||
still work, unless the user overmaps it.)
|
||||
|
||||
You can also explicitly send a message to a named script only. Assume the
|
||||
above script was using the filename ``fooscript.lua``:
|
||||
|
||||
::
|
||||
|
||||
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
|
||||
@@ -343,20 +299,6 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
with ``add_timeout()``), this starts the timer from the beginning,
|
||||
using the initially configured timeout.
|
||||
|
||||
``timeout`` (RW)
|
||||
This field contains the current timeout period. This value is not
|
||||
updated as time progresses. It's only used to calculate when the
|
||||
timer should fire next when the timer expires.
|
||||
|
||||
If you write this, you can call ``t:kill() ; t:resume()`` to reset
|
||||
the current timeout to the new one. (``t:stop()`` won't use the
|
||||
new timeout.)
|
||||
|
||||
``oneshot`` (RW)
|
||||
Whether the timer is periodic (``false``) or fires just once
|
||||
(``true``). This value is used when the timer expires (but before
|
||||
the timer callback function fn is run).
|
||||
|
||||
|
||||
``mp.get_opt(key)``
|
||||
Return a setting from the ``--lua-opts`` option. It's up to the user and
|
||||
@@ -417,7 +359,7 @@ are useful only in special situations.
|
||||
control what the Lua script does (instead of being called by the default
|
||||
event loop), you can set the global variable ``mp_event_loop`` to your
|
||||
own function running the event loop. From your event loop, you should call
|
||||
``mp.dispatch_events()`` to dequeue and dispatch mpv events.
|
||||
``mp.dispatch_events()`` to unqueue and dispatch mpv events.
|
||||
|
||||
If the ``allow_wait`` parameter is set to ``true``, the function will block
|
||||
until the next event is received or the next timer expires. Otherwise (and
|
||||
@@ -526,13 +468,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.
|
||||
|
||||
``utils.readdir(path [, filter])``
|
||||
Enumerate all entries at the given path on the filesystem, and return them
|
||||
as array. Each entry is a directory entry (without the path).
|
||||
@@ -569,60 +504,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 +545,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 +626,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.
|
||||
+66
-150
@@ -2,7 +2,7 @@ mpv
|
||||
###
|
||||
|
||||
##############
|
||||
a media player
|
||||
a movie player
|
||||
##############
|
||||
|
||||
:Copyright: GPLv2+
|
||||
@@ -12,13 +12,14 @@ a media player
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
| **mpv** [options] [file|URL|PLAYLIST|-]
|
||||
| **mpv** [options] [file|URL|-]
|
||||
| **mpv** [options] --playlist=PLAYLIST
|
||||
| **mpv** [options] files
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
**mpv** is a media player based on MPlayer and mplayer2. It supports a wide variety of video
|
||||
**mpv** is a movie player based on MPlayer and mplayer2. It supports a wide variety of video
|
||||
file formats, audio and video codecs, and subtitle types. Special input URL
|
||||
types are available to read input from a variety of sources other than disk
|
||||
files. Depending on platform, a variety of different video and audio output
|
||||
@@ -39,16 +40,15 @@ Keyboard Control
|
||||
----------------
|
||||
|
||||
LEFT and RIGHT
|
||||
Seek backward/forward 5 seconds. Shift+arrow does a 1 second exact seek
|
||||
Seek backward/forward 10 seconds. Shift+arrow does a 1 second exact seek
|
||||
(see ``--hr-seek``).
|
||||
|
||||
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.
|
||||
PGUP and PGDWN
|
||||
Seek forward/backward 10 minutes.
|
||||
|
||||
[ and ]
|
||||
Decrease/increase current playback speed by 10%.
|
||||
@@ -63,32 +63,41 @@ 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).
|
||||
|
||||
\.
|
||||
Step forward. Pressing once will pause, every consecutive press will
|
||||
Step forward. Pressing once will pause movie, every consecutive press will
|
||||
play one frame and then go into pause mode again.
|
||||
|
||||
,
|
||||
Step backward. Pressing once will pause, every consecutive press will
|
||||
Step backward. Pressing once will pause movie, every consecutive press will
|
||||
play one frame in reverse and then go into pause mode again.
|
||||
|
||||
q
|
||||
q / ESC
|
||||
Stop playing and quit.
|
||||
|
||||
Q
|
||||
Like ``q``, but store the current playback position. Playing the same file
|
||||
later will resume at the old playback position if possible.
|
||||
|
||||
U
|
||||
Stop playing (and quit if ``--idle`` is not used).
|
||||
|
||||
\+ and -
|
||||
Adjust audio delay by +/- 0.1 seconds.
|
||||
|
||||
/ and *
|
||||
Decrease/increase volume.
|
||||
|
||||
9 and 0
|
||||
Decrease/increase volume.
|
||||
|
||||
( and )
|
||||
Adjust audio balance in favor of left/right channel.
|
||||
|
||||
m
|
||||
Mute sound.
|
||||
|
||||
@@ -98,22 +107,19 @@ m
|
||||
\#
|
||||
Cycle through the available audio tracks.
|
||||
|
||||
TAB (MPEG-TS and libavformat only)
|
||||
Cycle through the available programs.
|
||||
|
||||
f
|
||||
Toggle fullscreen (see also ``--fs``).
|
||||
|
||||
ESC
|
||||
Exit fullscreen mode.
|
||||
|
||||
T
|
||||
Toggle stay-on-top (see also ``--ontop``).
|
||||
|
||||
w and e
|
||||
Decrease/increase pan-and-scan range.
|
||||
|
||||
o (also P)
|
||||
Show progression bar, elapsed time and total duration on the OSD.
|
||||
|
||||
O
|
||||
o
|
||||
Toggle OSD states: none / seek / seek + timer / seek + timer + total time.
|
||||
|
||||
d
|
||||
@@ -126,15 +132,12 @@ v
|
||||
j and J
|
||||
Cycle through the available subtitles.
|
||||
|
||||
F
|
||||
Toggle displaying "forced subtitles".
|
||||
|
||||
x and z
|
||||
Adjust subtitle delay by +/- 0.1 seconds.
|
||||
|
||||
l
|
||||
Set/clear A-B loop points. See ``ab_loop`` command for details.
|
||||
|
||||
Ctrl + and Ctrl -
|
||||
Adjust audio delay by +/- 0.1 seconds.
|
||||
|
||||
V
|
||||
Toggle subtitle VSFilter aspect compatibility mode. See
|
||||
``--ass-vsfilter-aspect-compat`` for more info.
|
||||
@@ -152,18 +155,23 @@ S
|
||||
I
|
||||
Show filename on the OSD.
|
||||
|
||||
PGUP and PGDWN
|
||||
P
|
||||
Show progression bar, elapsed time and total duration on the OSD.
|
||||
|
||||
! and @
|
||||
Seek to the beginning of the previous/next chapter. In most cases,
|
||||
"previous" will actually go to the beginning of the current chapter; see
|
||||
``--chapter-seek-threshold``.
|
||||
|
||||
Shift+PGUP and Shift+PGDWN
|
||||
Seek backward or forward by 10 minutes. (This used to be mapped to
|
||||
PGUP/PGDWN without Shift.)
|
||||
|
||||
D
|
||||
D (``--vo=vdpau``, ``--vf=yadif`` only)
|
||||
Activate/deactivate deinterlacer.
|
||||
|
||||
A
|
||||
Cycle through the available DVD angles.
|
||||
|
||||
c
|
||||
Change YUV colorspace.
|
||||
|
||||
(The following keys are valid only when using a video output that supports the
|
||||
corresponding adjustment, or the software equalizer (``--vf=eq``).)
|
||||
|
||||
@@ -179,24 +187,24 @@ corresponding adjustment, or the software equalizer (``--vf=eq``).)
|
||||
7 and 8
|
||||
Adjust saturation.
|
||||
|
||||
(The following keys are valid only on OS X.)
|
||||
(The following keys are valid only on OSX.)
|
||||
|
||||
command + 0
|
||||
Resize video window to half its original size.
|
||||
Resize movie window to half its original size.
|
||||
(On other platforms, you can bind keys to change the ``window-scale``
|
||||
property.)
|
||||
|
||||
command + 1
|
||||
Resize video window to its original size.
|
||||
Resize movie window to its original size.
|
||||
|
||||
command + 2
|
||||
Resize video window to double its original size.
|
||||
Resize movie window to double its original size.
|
||||
|
||||
command + f
|
||||
Toggle fullscreen (see also ``--fs``).
|
||||
|
||||
command + [ and command + ]
|
||||
Set video window alpha.
|
||||
Set movie window alpha.
|
||||
|
||||
(The following keys are valid if you have a keyboard with multimedia keys.)
|
||||
|
||||
@@ -215,6 +223,12 @@ support.)
|
||||
h and k
|
||||
Select previous/next channel.
|
||||
|
||||
n
|
||||
Change norm.
|
||||
|
||||
u
|
||||
Change channel list.
|
||||
|
||||
Mouse Control
|
||||
-------------
|
||||
|
||||
@@ -271,7 +285,7 @@ need to escape special characters. To work this around, the path can be
|
||||
additionally wrapped in the ``%n%string_of_length_n`` syntax (see above).
|
||||
|
||||
Some mpv options interpret paths starting with ``~``. Currently, the prefix
|
||||
``~~/`` expands to the mpv configuration directory (usually ``~/.config/mpv/``).
|
||||
``~~/`` expands to the mpv configuration directory (usually ``~/.mpv/``).
|
||||
``~/`` expands to the user's home directory. (The trailing ``/`` is always
|
||||
required.)
|
||||
|
||||
@@ -324,7 +338,7 @@ Location and Syntax
|
||||
You can put all of the options in configuration files which will be read every
|
||||
time mpv is run. The system-wide configuration file 'mpv.conf' is in your
|
||||
configuration directory (e.g. ``/etc/mpv`` or ``/usr/local/etc/mpv``), the
|
||||
user-specific one is ``~/.config/mpv/mpv.conf``.
|
||||
user-specific one is ``~/.mpv/config``.
|
||||
User-specific options override system-wide options and options given on the
|
||||
command line override either. The syntax of the configuration files is
|
||||
``option=<value>``; everything after a *#* is considered a comment. Options
|
||||
@@ -359,13 +373,13 @@ File-specific Configuration Files
|
||||
---------------------------------
|
||||
|
||||
You can also write file-specific configuration files. If you wish to have a
|
||||
configuration file for a file called 'video.avi', create a file named
|
||||
'video.avi.conf' with the file-specific options in it and put it in
|
||||
``~/.config/mpv/``. You can also put the configuration file in the same directory
|
||||
as the file to be played. Both require you to set the ``--use-filedir-conf``
|
||||
configuration file for a file called 'movie.avi', create a file named
|
||||
'movie.avi.conf' with the file-specific options in it and put it in
|
||||
``~/.mpv/``. You can also put the configuration file in the same directory
|
||||
as the file to be played, as long as you give the ``--use-filedir-conf``
|
||||
option (either on the command line or in your global config file). If a
|
||||
file-specific configuration file is found in the same directory, no
|
||||
file-specific configuration is loaded from ``~/.config/mpv``. In addition, the
|
||||
file-specific configuration is loaded from ``~/.mpv``. In addition, the
|
||||
``--use-filedir-conf`` option enables directory-specific configuration files.
|
||||
For this, mpv first tries to load a mpv.conf from the same directory
|
||||
as the file played and then tries to load any file-specific configuration.
|
||||
@@ -420,52 +434,6 @@ taking screenshots is handled by the VOs, and adding the screenshot filter will
|
||||
break hardware decoding. (The filter may still be useful for taking screenshots
|
||||
at a certain point within the video chain when using multiple video filters.)
|
||||
|
||||
TERMINAL STATUS LINE
|
||||
====================
|
||||
|
||||
During playback, mpv shows the playback status on the terminal. It looks like
|
||||
something like this:
|
||||
|
||||
``AV: 00:03:12 / 00:24:25 (13%) A-V: -0.000``
|
||||
|
||||
The status line can be overridden with the ``--term-status-msg`` option.
|
||||
|
||||
The following is a list of things that can show up in the status line. Input
|
||||
properties, that can be used to get the same information manually, are also
|
||||
listed.
|
||||
|
||||
- ``AV:`` or ``V:`` (video only) or ``A:`` (audio only)
|
||||
- The current time position in ``HH:MM:SS`` format (``playback-time`` property)
|
||||
- The total file duration (or ``00:00:00`` if unknown) (``length`` property)
|
||||
- Playback speed, e.g. `` x2.0``. Only visible if the speed is not normal. This
|
||||
is the user-requested speed, and not the actual speed (usually they should
|
||||
be the same, unless playback is too slow). (``speed`` property.)
|
||||
- Playback percentage, e.g. ``(13%)``. How much of the file has been played.
|
||||
Normally calculated out of playback position and duration, but can fallback
|
||||
to other methods (like byte position) if these are not available.
|
||||
(``percent-pos`` property.)
|
||||
- The audio/video sync as ``A-V: 0.000``. This is the difference between
|
||||
audio and video time. Normally it should be 0 or close to 0. If it's growing,
|
||||
it might indicate a playback problem. (``avsync`` property.)
|
||||
- Total A/V sync change, e.g. ``ct: -0.417``. Normally invisible. Can show up
|
||||
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.)
|
||||
- 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
|
||||
kilobytes. (``demuxer-cache-duration`` and ``cached-used`` properties.)
|
||||
|
||||
|
||||
PROTOCOLS
|
||||
=========
|
||||
|
||||
@@ -474,10 +442,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.
|
||||
|
||||
@@ -485,8 +449,8 @@ PROTOCOLS
|
||||
Play a path from Samba share.
|
||||
|
||||
``bd://[title][/device]`` ``--bluray-device=PATH``
|
||||
Play a Blu-Ray disc. Currently, this does not accept ISO files. Instead,
|
||||
you must mount the ISO file as filesystem, and point ``--bluray-device``
|
||||
Play a Blu-Ray disc. Currently, this does not accept iso files. Instead,
|
||||
you must mount the iso file as filesystem, and point ``--bluray-device``
|
||||
to the mounted directly.
|
||||
|
||||
``bdnav://[title][/device]``
|
||||
@@ -567,8 +531,6 @@ PROTOCOLS
|
||||
|
||||
.. include:: lua.rst
|
||||
|
||||
.. include:: ipc.rst
|
||||
|
||||
.. include:: changes.rst
|
||||
|
||||
ENVIRONMENT VARIABLES
|
||||
@@ -577,23 +539,15 @@ ENVIRONMENT VARIABLES
|
||||
There are a number of environment variables that can be used to control the
|
||||
behavior of mpv.
|
||||
|
||||
``HOME``, ``XDG_CONFIG_HOME``
|
||||
Used to determine mpv config directory. If ``XDG_CONFIG_HOME`` is not set,
|
||||
``$HOME/.config/mpv`` is used.
|
||||
|
||||
``$HOME/.mpv`` is always added to the list of config search paths with a
|
||||
lower priority.
|
||||
|
||||
``XDG_CONFIG_DIRS``
|
||||
If set, XDG-style system configuration directories are used. Otherwise,
|
||||
the UNIX convention (``PREFIX/etc/mpv/``) is used.
|
||||
``HOME``
|
||||
Used to determine mpv config directory: ``$HOME/.mpv``
|
||||
|
||||
``TERM``
|
||||
Used to determine terminal type.
|
||||
|
||||
``MPV_HOME``
|
||||
Directory where mpv looks for user settings. Overrides ``HOME``, and mpv
|
||||
will try to load the config file as ``$MPV_HOME/mpv.conf``.
|
||||
will try to load the config file as ``$MPV_HOME/config``.
|
||||
|
||||
``MPV_VERBOSE`` (see also ``-v`` and ``--msg-level``)
|
||||
Set the initial verbosity level across all message modules (default: 0).
|
||||
@@ -706,56 +660,18 @@ FILES
|
||||
``/usr/local/etc/mpv/mpv.conf``
|
||||
mpv system-wide settings (depends on ``--prefix`` passed to configure)
|
||||
|
||||
``~/.config/mpv/mpv.conf``
|
||||
``~/.mpv/config``
|
||||
mpv user settings
|
||||
|
||||
``~/.config/mpv/input.conf``
|
||||
key bindings (see `INPUT.CONF`_ section)
|
||||
``~/.mpv/input.conf``
|
||||
input bindings (see ``--input-keylist`` for the full list)
|
||||
|
||||
``~/.config/mpv/lua/``
|
||||
``~/.mpv/lua/``
|
||||
All files in this directly are loaded as if they were passed to the
|
||||
``--lua`` option. They are loaded in alphabetical order, and sub-directories
|
||||
and files with no ``.lua`` extension are ignored. The ``--load-scripts=no``
|
||||
option disables loading these files.
|
||||
|
||||
Note that the environment variables ``$XDG_CONFIG_HOME`` and ``$MPV_HOME`` can
|
||||
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
|
||||
=====================
|
||||
@@ -773,7 +689,7 @@ Play only chapters 5, 6, 7:
|
||||
Play only titles 5, 6, 7:
|
||||
``mpv dvd://5-7``
|
||||
|
||||
Play a multi-angle DVD:
|
||||
Play a multiangle DVD:
|
||||
``mpv dvd://1 --dvd-angle=2``
|
||||
|
||||
Play from a different DVD device:
|
||||
|
||||
+1726
-2129
File diff suppressed because it is too large.
Load diff
+5
-9
@@ -122,11 +122,11 @@ time remaining
|
||||
left-click toggle between total and remaining time
|
||||
============= ================================================
|
||||
|
||||
Key Bindings
|
||||
~~~~~~~~~~~~
|
||||
Keybindings
|
||||
~~~~~~~~~~~
|
||||
|
||||
These key bindings are active by default if nothing else is already bound to
|
||||
these keys. In case of collision, the function needs to be bound to a
|
||||
These keybindings are active by default if nothing else is already bound to
|
||||
these keys. In case of collosision, the function needs to be bound to a
|
||||
different key. See `Script Commands`_ section.
|
||||
|
||||
============= ================================================
|
||||
@@ -161,7 +161,7 @@ To avoid collisions with other scripts, all options need to be prefixed with
|
||||
|
||||
Example::
|
||||
|
||||
--lua-opts=osc-optionA=value1,osc-optionB=value2
|
||||
--lua-opts=osc-optionA=value1:osc-optionB=value2
|
||||
|
||||
|
||||
Configurable Options
|
||||
@@ -229,10 +229,6 @@ Configurable Options
|
||||
| Default: yes
|
||||
| Display a tooltip over the seekbar indicating time at mouse position.
|
||||
|
||||
``seekbarstyle``
|
||||
| Default: slider
|
||||
| Sets the style of the seekbar, slider (diamond marker) or bar (fill)
|
||||
|
||||
Script Commands
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
+24
-61
@@ -47,7 +47,7 @@ Available filters are:
|
||||
|
||||
``crop[=w:h:x:y]``
|
||||
Crops the given part of the image and discards the rest. Useful to remove
|
||||
black bands from widescreen videos.
|
||||
black bands from widescreen movies.
|
||||
|
||||
``<w>,<h>``
|
||||
Cropped width and height, defaults to original width and height.
|
||||
@@ -55,7 +55,7 @@ Available filters are:
|
||||
Position of the cropped picture, defaults to center.
|
||||
|
||||
``expand[=w:h:x:y:aspect:round]``
|
||||
Expands (not scales) video resolution to the given value and places the
|
||||
Expands (not scales) movie resolution to the given value and places the
|
||||
unscaled original at coordinates x, y.
|
||||
|
||||
``<w>,<h>``
|
||||
@@ -93,7 +93,7 @@ Available filters are:
|
||||
|
||||
``scale[=w:h:param:param2:chr-drop:noup:arnd``
|
||||
Scales the image with the software scaler (slow) and performs a YUV<->RGB
|
||||
color space conversion (see also ``--sws``).
|
||||
colorspace conversion (see also ``--sws``).
|
||||
|
||||
All parameters are optional.
|
||||
|
||||
@@ -120,9 +120,9 @@ Available filters are:
|
||||
0.33:0.33 Mitchell-Netravali spline
|
||||
1.00:0.00 cubic B-spline
|
||||
|
||||
--sws=7 (Gaussian): sharpness (0 (soft) - 100 (sharp))
|
||||
--sws=7 (gaussian): sharpness (0 (soft) - 100 (sharp))
|
||||
|
||||
--sws=9 (Lanczos): filter length (1-10)
|
||||
--sws=9 (lanczos): filter length (1-10)
|
||||
|
||||
``<chr-drop>``
|
||||
chroma skipping
|
||||
@@ -222,8 +222,8 @@ Available filters are:
|
||||
====================== =====================
|
||||
|
||||
``noformat[=fmt]``
|
||||
Restricts the color space for the next filter without doing any conversion.
|
||||
Unlike the format filter, this will allow any color space except the one
|
||||
Restricts the colorspace for the next filter without doing any conversion.
|
||||
Unlike the format filter, this will allow any colorspace except the one
|
||||
you specify.
|
||||
|
||||
.. note:: For a list of available formats, see ``noformat=fmt=help``.
|
||||
@@ -231,23 +231,12 @@ Available filters are:
|
||||
``<fmt>``
|
||||
Format name, e.g. rgb15, bgr24, 420p, etc. (default: 420p).
|
||||
|
||||
``pp=[filter string]``
|
||||
Enables the specified chain of post-processing subfilters. Subfilters must
|
||||
``pp[=[filter1[:option1[:option2...]]/[-]filter2...]]``
|
||||
Enables the specified chain of postprocessing subfilters. Subfilters must
|
||||
be separated by '/' and can be disabled by prepending a '-'. Each
|
||||
subfilter and some options have a short and a long name that can be used
|
||||
interchangeably, i.e. ``dr``/``dering`` are the same.
|
||||
|
||||
.. note::
|
||||
|
||||
Unlike in MPlayer or in earlier versions, you must quote the pp string
|
||||
if it contains ``:`` characters, e.g. ``'--vf=pp=[...]'``.
|
||||
|
||||
.. admonition:: Warning
|
||||
|
||||
This filter is most likely useless on modern HD video. It might be
|
||||
helpful with old low-resolution files.
|
||||
|
||||
All subfilters share common options to determine their scope:
|
||||
interchangeably, i.e. ``dr``/``dering`` are the same. All subfilters share
|
||||
common options to determine their scope:
|
||||
|
||||
``a/autoq``
|
||||
Automatically switch the subfilter off if the CPU is too slow.
|
||||
@@ -262,6 +251,11 @@ Available filters are:
|
||||
|
||||
``--vf=pp:help`` shows a list of available subfilters.
|
||||
|
||||
.. note::
|
||||
|
||||
Unlike in MPlayer or in earlier versions, you must quote the pp string
|
||||
if it contains ``:`` characters, e.g. ``'--vf=pp=[...]'``.
|
||||
|
||||
Available subfilters are:
|
||||
|
||||
``hb/hdeblock[:difference[:flatness]]``
|
||||
@@ -342,7 +336,7 @@ Available filters are:
|
||||
filtering every second line with a (-1 4 2 4 -1) filter.
|
||||
|
||||
``l5/lowpass5``
|
||||
Vertically applied FIR low-pass deinterlacing filter that deinterlaces
|
||||
Vertically applied FIR lowpass deinterlacing filter that deinterlaces
|
||||
the given block by filtering all lines with a (-1 2 6 2 -1) filter.
|
||||
|
||||
``fq/forceQuant[:quantizer]``
|
||||
@@ -362,7 +356,7 @@ Available filters are:
|
||||
|
||||
.. note::
|
||||
|
||||
This filter is only available if FFmpeg/Libav has been compiled
|
||||
This filter is only available if FFmpeg/libav has been compiled
|
||||
with libpostproc enabled.
|
||||
|
||||
.. admonition:: Examples
|
||||
@@ -449,7 +443,7 @@ Available filters are:
|
||||
temporal noise (noise pattern changes between frames)
|
||||
|
||||
``uniform``
|
||||
uniform noise (Gaussian otherwise)
|
||||
uniform noise (gaussian otherwise)
|
||||
|
||||
``hq``
|
||||
high quality (slightly better looking, slightly slower) - not available
|
||||
@@ -509,7 +503,7 @@ Available filters are:
|
||||
:1: linear interpolation (default)
|
||||
|
||||
``unsharp[=lx:ly:la:cx:cy:ca]``
|
||||
unsharp mask / Gaussian blur
|
||||
unsharp mask / gaussian blur
|
||||
|
||||
``l`` is for the luma component, ``c`` for the chroma component. ``x``/``y``
|
||||
is the filter size. ``a`` is the amount.
|
||||
@@ -623,7 +617,7 @@ Available filters are:
|
||||
|
||||
``phase[=t|b|p|a|u|T|B|A|U][:v]``
|
||||
Delay interlaced video by one field time so that the field order changes.
|
||||
The intended use is to fix PAL videos that have been captured with the
|
||||
The intended use is to fix PAL movies that have been captured with the
|
||||
opposite field order to the film-to-video transfer. The options are:
|
||||
|
||||
``t``
|
||||
@@ -775,8 +769,8 @@ Available filters are:
|
||||
anaglyph red/cyan color (red filter on left eye, cyan filter on
|
||||
right eye)
|
||||
``arcd`` or ``anaglyph_red_cyan_dubois``
|
||||
anaglyph red/cyan color optimized with the least-squares
|
||||
projection of Dubois (red filter on left eye, cyan filter on right
|
||||
anaglyph red/cyan color optimized with the least squares
|
||||
projection of dubois (red filter on left eye, cyan filter on right
|
||||
eye)
|
||||
``agmg`` or ``anaglyph_green_magenta_gray``
|
||||
anaglyph green/magenta gray (green filter on left eye, magenta
|
||||
@@ -809,7 +803,7 @@ Available filters are:
|
||||
|
||||
``gradfun[=strength[:radius|:size=<size>]]``
|
||||
Fix the banding artifacts that are sometimes introduced into nearly flat
|
||||
regions by truncation to 8-bit color depth. Interpolates the gradients that
|
||||
regions by truncation to 8bit color depth. Interpolates the gradients that
|
||||
should go where the bands are, and dithers them.
|
||||
|
||||
``<strength>``
|
||||
@@ -910,31 +904,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
|
||||
@@ -1000,9 +969,3 @@ Available filters are:
|
||||
Use default VDPAU scaling (default).
|
||||
1-9
|
||||
Apply high quality VDPAU scaling (needs capable hardware).
|
||||
|
||||
``buffer=<num>``
|
||||
Buffer ``<num>`` frames in the filter chain. This filter is probably pretty
|
||||
useless, except for debugging. (Note that this won't help smoothing out
|
||||
latencies with decoding, because the filter will never output a frame if
|
||||
the buffer isn't full, except on EOF.)
|
||||
+44
-88
@@ -38,36 +38,36 @@ Available video output drivers are:
|
||||
.. note:: This driver is for compatibility with old systems.
|
||||
|
||||
``adaptor=<number>``
|
||||
Select a specific XVideo adapter (check xvinfo results).
|
||||
Select a specific XVideo adaptor (check xvinfo results).
|
||||
``port=<number>``
|
||||
Select a specific XVideo port.
|
||||
``ck=<cur|use|set>``
|
||||
Select the source from which the color key is taken (default: cur).
|
||||
Select the source from which the colorkey is taken (default: cur).
|
||||
|
||||
cur
|
||||
The default takes the color key currently set in Xv.
|
||||
The default takes the colorkey currently set in Xv.
|
||||
use
|
||||
Use but do not set the color key from mpv (use the ``--colorkey``
|
||||
Use but do not set the colorkey from mpv (use the ``--colorkey``
|
||||
option to change it).
|
||||
set
|
||||
Same as use but also sets the supplied color key.
|
||||
Same as use but also sets the supplied colorkey.
|
||||
|
||||
``ck-method=<man|bg|auto>``
|
||||
Sets the color key drawing method (default: man).
|
||||
Sets the colorkey drawing method (default: man).
|
||||
|
||||
man
|
||||
Draw the color key manually (reduces flicker in some cases).
|
||||
Draw the colorkey manually (reduces flicker in some cases).
|
||||
bg
|
||||
Set the color key as window background.
|
||||
Set the colorkey as window background.
|
||||
auto
|
||||
Let Xv draw the color key.
|
||||
Let Xv draw the colorkey.
|
||||
|
||||
``colorkey=<number>``
|
||||
Changes the color key to an RGB value of your choice. ``0x000000`` is
|
||||
Changes the colorkey to an RGB value of your choice. ``0x000000`` is
|
||||
black and ``0xffffff`` is white.
|
||||
|
||||
``no-colorkey``
|
||||
Disables color-keying.
|
||||
Disables colorkeying.
|
||||
|
||||
``x11`` (X11 only)
|
||||
Shared memory video output driver without hardware acceleration that works
|
||||
@@ -82,7 +82,7 @@ Available video output drivers are:
|
||||
.. note::
|
||||
|
||||
Earlier versions of mpv (and MPlayer, mplayer2) provided sub-options
|
||||
to tune vdpau post-processing, like ``deint``, ``sharpen``, ``denoise``,
|
||||
to tune vdpau postprocessing, like ``deint``, ``sharpen``, ``denoise``,
|
||||
``chroma-deint``, ``pullup``, ``hqscaling``. These sub-options are
|
||||
deprecated, and you should use the ``vdpaupp`` video filter instead.
|
||||
|
||||
@@ -173,7 +173,7 @@ Available video output drivers are:
|
||||
to a YUV format before the VO. Sometimes useful to force availability
|
||||
of certain YUV-only features, like video equalizer or deinterlacing.
|
||||
|
||||
Using the VDPAU frame queuing functionality controlled by the queuetime
|
||||
Using the VDPAU frame queueing functionality controlled by the queuetime
|
||||
options makes mpv's frame flip timing less sensitive to system CPU load and
|
||||
allows mpv to start decoding the next frame(s) slightly earlier, which can
|
||||
reduce jitter caused by individual slow-to-decode frames. However, the
|
||||
@@ -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.
|
||||
@@ -270,6 +259,13 @@ Available video output drivers are:
|
||||
|
||||
.. note:: This driver is for compatibility with old systems.
|
||||
|
||||
``corevideo`` (Mac OS X 10.6 and later)
|
||||
Mac OS X CoreVideo video output driver. Uses the CoreVideo APIs to fill
|
||||
PixelBuffers and generate OpenGL textures from them (useful as a fallback
|
||||
for ``opengl``).
|
||||
|
||||
.. note:: This driver is for compatibility with old systems.
|
||||
|
||||
``opengl``
|
||||
OpenGL video output driver. It supports extended scaling methods, dithering
|
||||
and color management.
|
||||
@@ -286,7 +282,7 @@ Available video output drivers are:
|
||||
|
||||
Hardware decoding over OpenGL-interop is supported to some degree. Note
|
||||
that in this mode, some corner case might not be gracefully handled, and
|
||||
color space conversion and chroma upsampling is generally in the hand of
|
||||
colorspace conversion and chroma upsampling is generally in the hand of
|
||||
the hardware decoder APIs.
|
||||
|
||||
``lscale=<filter>``
|
||||
@@ -301,9 +297,6 @@ Available video output drivers are:
|
||||
``lanczos3``
|
||||
Lanczos with radius=3.
|
||||
|
||||
``lanczos``
|
||||
Generic Lanczos scaling filter. Set radius with ``lradius``.
|
||||
|
||||
``spline36``
|
||||
This is the default when using ``opengl-hq``.
|
||||
|
||||
@@ -323,14 +316,6 @@ Available video output drivers are:
|
||||
Mitchell-Netravali. The ``b`` and ``c`` parameters can be set with
|
||||
``lparam1`` and ``lparam2``. Both are set to 1/3 by default.
|
||||
|
||||
``gaussian``
|
||||
Gaussian filter with a parameter ``p`` for sharpness control.
|
||||
``p`` can be set to float number between 1(blurry) and 100(sharp)
|
||||
and has a default value of about 28.8 (see ``lparam1``).
|
||||
|
||||
Note that for extremely small value of ``p``, a large filter radius
|
||||
might be required to avoid unintended artifacts (see ``lradius``).
|
||||
|
||||
|
||||
There are some more filters. For a complete list, pass ``help`` as
|
||||
value, e.g.::
|
||||
@@ -344,16 +329,6 @@ Available video output drivers are:
|
||||
``lparam2=<value>``
|
||||
See ``lparam1``.
|
||||
|
||||
``lradius=<r>``
|
||||
Set radius for filters listed below, must be a float number between 1.0
|
||||
and 8.0. Defaults to be 2.0 if not specified.
|
||||
|
||||
``sinc``, ``lanczos``, ``blackman``, ``gaussian``
|
||||
|
||||
Note that depending on filter implementation details and video scaling
|
||||
ratio, the radius that actually being used might be different
|
||||
(most likely being increased a bit).
|
||||
|
||||
``scaler-resizes-only``
|
||||
Disable the scaler if the video image is not resized. In that case,
|
||||
``bilinear`` is used instead whatever is set with ``lscale``. Bilinear
|
||||
@@ -374,7 +349,7 @@ Available video output drivers are:
|
||||
green-magenta
|
||||
Convert side by side input to full-color green-magenta stereo.
|
||||
quadbuffer
|
||||
Convert side by side input to quad buffered stereo. Only supported
|
||||
Convert side by side input to quadbuffered stereo. Only supported
|
||||
by very few OpenGL cards.
|
||||
|
||||
``srgb``
|
||||
@@ -449,22 +424,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``.
|
||||
|
||||
See ``lparam1``, ``lparam2`` and ``lradius``.
|
||||
|
||||
``fancy-downscaling``
|
||||
When using convolution based filters, extend the filter size
|
||||
when downscaling. Trades quality for reduced downscaling performance.
|
||||
@@ -474,16 +439,7 @@ Available video output drivers are:
|
||||
Borders will be distorted due to filtering.
|
||||
|
||||
``glfinish``
|
||||
Call ``glFinish()`` before and after swapping buffers (default: disabled).
|
||||
Slower, but might help getting better results when doing framedropping.
|
||||
The details depend entirely on the OpenGL driver.
|
||||
|
||||
``waitvsync``
|
||||
Call ``glXWaitVideoSyncSGI`` after each buffer swap (default: disabled).
|
||||
This may or may not help with video timing accuracy and frame drop. It's
|
||||
possible that this makes video output slower, or has no effect at all.
|
||||
|
||||
X11 only.
|
||||
Call ``glFinish()`` before swapping buffers
|
||||
|
||||
``sw``
|
||||
Continue even if a software renderer is detected.
|
||||
@@ -496,7 +452,7 @@ Available video output drivers are:
|
||||
auto
|
||||
auto-select (default)
|
||||
cocoa
|
||||
Cocoa/OS X
|
||||
Cocoa/OSX
|
||||
win
|
||||
Win32/WGL
|
||||
x11
|
||||
@@ -523,7 +479,7 @@ Available video output drivers are:
|
||||
|
||||
``gamma=<0.0..10.0>``
|
||||
Set a gamma value. If gamma is adjusted in other ways (like with
|
||||
the ``--gamma`` option or key bindings and the ``gamma`` property), the
|
||||
the ``--gamma`` option or keybindings and the ``gamma`` property), the
|
||||
value is multiplied with the other gamma value.
|
||||
|
||||
Setting this value to 1.0 can be used to always enable gamma control.
|
||||
@@ -531,7 +487,7 @@ Available video output drivers are:
|
||||
|
||||
``icc-profile=<file>``
|
||||
Load an ICC profile and use it to transform linear RGB to screen output.
|
||||
Needs LittleCMS 2 support compiled in. This option overrides the ``srgb``
|
||||
Needs LittleCMS2 support compiled in. This option overrides the ``srgb``
|
||||
property, as using both is somewhat redundant. It also enables linear
|
||||
light scaling.
|
||||
|
||||
@@ -544,12 +500,12 @@ Available video output drivers are:
|
||||
|
||||
``icc-cache=<file>``
|
||||
Store and load the 3D LUT created from the ICC profile in this file.
|
||||
This can be used to speed up loading, since LittleCMS 2 can take a while
|
||||
This can be used to speed up loading, since LittleCMS2 can take a while
|
||||
to create the 3D LUT. Note that this file contains an uncompressed LUT.
|
||||
Its size depends on the ``3dlut-size``, and can be very big.
|
||||
|
||||
``icc-intent=<value>``
|
||||
Specifies the ICC Intent used for transformations between color spaces.
|
||||
Specifies the ICC Intent used for transformations between colorspaces.
|
||||
This affects the rendering when using ``icc-profile`` or ``srgb`` and
|
||||
also affects the way DCP XYZ content gets converted to RGB.
|
||||
|
||||
@@ -568,7 +524,7 @@ Available video output drivers are:
|
||||
for mastering instead of the true curve. Most notably, anything in the
|
||||
Apple ecosystem uses this approximation - including all programs
|
||||
compatible with it. It's a sound idea to try enabling this flag first
|
||||
when watching videos and shows to see if things look better that way.
|
||||
when watching movies and shows to see if things look better that way.
|
||||
|
||||
This only affects the output when using either ``icc-profile`` or ``srgb``.
|
||||
|
||||
@@ -606,7 +562,7 @@ Available video output drivers are:
|
||||
|
||||
This is equivalent to::
|
||||
|
||||
--vo=opengl:lscale=spline36:dither-depth=auto:fbo-format=rgba16
|
||||
--vo=opengl:lscale=spline36:dither-depth=auto:fbo-format=rgb16
|
||||
|
||||
Note that some cheaper LCDs do dithering that gravely interferes with
|
||||
``opengl``'s dithering. Disabling dithering with ``dither-depth=no`` helps.
|
||||
@@ -615,7 +571,7 @@ Available video output drivers are:
|
||||
can achieve better quality or performance by changing the ``fbo-format``
|
||||
suboption to ``rgb16f``, ``rgb32f`` or ``rgb``. Known problems include
|
||||
Mesa/Intel not accepting ``rgb16``, Mesa sometimes not being compiled with
|
||||
float texture support, and some OS X setups being very slow with ``rgb16``
|
||||
float texture support, and some OSX setups being very slow with ``rgb16``
|
||||
but fast with ``rgb32f``.
|
||||
|
||||
``opengl-old``
|
||||
@@ -750,7 +706,7 @@ Available video output drivers are:
|
||||
2
|
||||
Convert side by side input to full-color green-magenta stereo.
|
||||
3
|
||||
Convert side by side input to quad buffered stereo. Only supported
|
||||
Convert side by side input to quadbuffered stereo. Only supported
|
||||
by very few OpenGL cards.
|
||||
|
||||
The following options are only useful if writing your own fragment programs.
|
||||
@@ -792,7 +748,7 @@ Available video output drivers are:
|
||||
auto
|
||||
auto-select (default)
|
||||
cocoa
|
||||
Cocoa/OS X
|
||||
Cocoa/OSX
|
||||
win
|
||||
Win32/WGL
|
||||
x11
|
||||
|
||||
+1
-15
@@ -24,14 +24,11 @@ Release procedure
|
||||
|
||||
- Create and/or update the ``VERSION`` file.
|
||||
|
||||
- Update ``DOCS/client-api-changes.rst`` (on major releases).
|
||||
|
||||
- Create tag v0.X.Y.
|
||||
|
||||
- Push branch and tag to GitHub.
|
||||
|
||||
- Create a new GitHub release using the content of ``RELEASE_NOTES`` related to
|
||||
the new version.
|
||||
- Create a new GitHub release using the content of ``RELEASE_NOTES``.
|
||||
|
||||
Release notes template
|
||||
----------------------
|
||||
@@ -39,9 +36,6 @@ Release notes template
|
||||
Here is a template that can be used for writing the ``RELEASE_NOTES`` file.
|
||||
|
||||
```markdown
|
||||
Release 0.X.Y
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
@@ -61,11 +55,3 @@ This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log <start>..<end>`` in
|
||||
the git repository.
|
||||
```
|
||||
|
||||
Note that the "Release 0.X.Y" title should be removed when creating a new GitHub
|
||||
release.
|
||||
|
||||
When creating a new point release its changes should be added on top of the
|
||||
``RELEASE_NOTES`` file (with the appropriate title) so that all the changes in
|
||||
the current 0.X branch will be included. This way the ``RELEASE_NOTES`` file
|
||||
can be used by distributors as changelog for point releases too.
|
||||
+29
-47
@@ -1,4 +1,4 @@
|
||||
This file intends to give a big picture overview of how mpv is structured.
|
||||
This file intends to give a big picture of how mpv is structured.
|
||||
|
||||
player/*.c:
|
||||
Essentially makes up the player applications, including the main() function
|
||||
@@ -17,12 +17,12 @@ player/*.c:
|
||||
* check help options etc. (call handle_help_options()), possibly exit
|
||||
* call play_files() function that works down the playlist:
|
||||
* run idle loop (idle_loop()), until there are files in the
|
||||
playlist or an exit command was given (only if --idle it set)
|
||||
playlist or an exit command was given (slave mode only)
|
||||
* actually load and play a file in play_current_file():
|
||||
* run all the dozens of functions to load the file and
|
||||
initialize playback
|
||||
* run a small loop that does normal playback, until the file is
|
||||
done or a command terminates playback
|
||||
done or a slave command terminates playback
|
||||
(on each iteration, run_playloop() is called, which is rather
|
||||
big and complicated - it decodes some audio and video on
|
||||
each frame, waits for input, etc.)
|
||||
@@ -31,28 +31,15 @@ player/*.c:
|
||||
* loop, or exit if no next file or quit is requested
|
||||
(see enum stop_play_reason)
|
||||
* call exit_player_with_rc()
|
||||
* run_playloop():
|
||||
* calls fill_audio_out_buffers()
|
||||
This checks whether new audio needs to be decoded, and pushes it
|
||||
to the AO.
|
||||
* calls write_video()
|
||||
Decode new video, and push it to the VO.
|
||||
* determines whether playback of the current file has ended
|
||||
* determines when to start playback after seeks
|
||||
* and calls a whole lot of other stuff
|
||||
(Really, this function does everything.)
|
||||
|
||||
Things worth saying about the playback core:
|
||||
- most state is in MPContext (core.h), which is not available to the
|
||||
subsystems
|
||||
- the currently played tracks are in mpctx->current_tracks, and decoder
|
||||
state in d_video/d_audio/d_sub
|
||||
- the currently played tracks are in sh_video and sh_audio
|
||||
- the timeline stuff is used only with MKV ordered chapters (and some other
|
||||
minor features: cue, edl)
|
||||
- most state is in MPContext (mp_core.h), which is not available to the
|
||||
subsystems
|
||||
- the other subsystems rarely call back into the frontend, and the frontend
|
||||
polls them instead (probably a good thing)
|
||||
- one exceptions are wakeup callbacks, which notify a "higher" component
|
||||
of a changed situation in a subsystem
|
||||
|
||||
I like to call the player/*.c files the "frontend".
|
||||
|
||||
@@ -95,7 +82,7 @@ talloc.h & talloc.c:
|
||||
(See ta/README for details.)
|
||||
|
||||
player/command.c:
|
||||
This contains the implementation for client API commands and properties.
|
||||
This contains the implementation for slave commands and properties.
|
||||
Properties are essentially dynamic variables changed by certain commands.
|
||||
This is basically responsible for all user commands, like initiating
|
||||
seeking, switching tracks, etc. It calls into other player/*.c files,
|
||||
@@ -105,10 +92,9 @@ player/core.h:
|
||||
Data structures and function prototypes for most of player/*.c. They are
|
||||
usually not accessed by other parts of mpv for the sake of modularization.
|
||||
|
||||
player/client.c:
|
||||
This implements the client API (libmpv/client.h). For the most part, this
|
||||
just calls into other parts of the player. This also manages a ringbuffer
|
||||
of events from player to clients.
|
||||
Note that there are lots of global variables floating around everywhere
|
||||
else. This is an ongoing transition, and eventually there should be no
|
||||
global variables anymore.
|
||||
|
||||
options/options.h, options/options.c
|
||||
options.h contains the global option struct MPOpts. The option declarations
|
||||
@@ -116,7 +102,7 @@ options/options.h, options/options.c
|
||||
options.c. Most default values for options and MPOpts are in
|
||||
mp_default_opts at the end of options.c.
|
||||
|
||||
MPOpts is unfortunately quite monolithic, and virtually accessed by
|
||||
MPOpts is unfortunarely quite monolithic, and virtually accessed by
|
||||
everything.But some components (like video outputs and video filters) have
|
||||
their own sub-option tables separate from MPOpts.
|
||||
|
||||
@@ -124,18 +110,19 @@ options/options.h, options/options.c
|
||||
parser-mpcmd.c, and uses the option table in options.c.
|
||||
|
||||
input/input.c:
|
||||
This translates keyboard input comming from VOs and other sources (such
|
||||
as remote control devices like Apple IR or client API commands) to the
|
||||
This translates keyboard input comming from libvo and other sources (such
|
||||
as remote control devices like Apple IR or slave mode commands) to the
|
||||
key bindings listed in the user's (or the builtin) input.conf and turns
|
||||
them into items of type struct mp_cmd. These commands are queued, and read
|
||||
by playloop.c. They get pushed with run_command() to command.c.
|
||||
|
||||
Note that keyboard input and commands used by the client API are the same.
|
||||
The client API only uses the command parser though, and has its own queue
|
||||
of input commands somewhere else.
|
||||
Note that keyboard input and slave mode input are essentially the same
|
||||
things. Just looking at input.conf should make this clear. (The other
|
||||
direction of slave mode communication, mpv to application, consists of
|
||||
random mp_msg() calls all over the code in all parts of the player.)
|
||||
|
||||
common/msg.h:
|
||||
All terminal output must go through mp_msg().
|
||||
All terminal output should go through mp_msg().
|
||||
|
||||
stream/*:
|
||||
File input is implemented here. stream.h/.c provides a simple stream based
|
||||
@@ -194,7 +181,7 @@ video/out/:
|
||||
vo_opengl can pick a windowing system at runtime, e.g. the same binary can
|
||||
provide both X11 and Cocoa support on OSX.
|
||||
|
||||
VOs can be reconfigured at runtime. A vo_config() call can change the video
|
||||
VOs can be reconfigured at runtime. A config() call can change the video
|
||||
resolution and format, without destroying the window.
|
||||
|
||||
vo_vdpau and vo_opengl should be taken as reference.
|
||||
@@ -216,12 +203,9 @@ audio/filter/:
|
||||
audio/out/:
|
||||
Audio outputs.
|
||||
|
||||
Unlike VOs, AOs can't be reconfigured on a format change. On audio format
|
||||
changes, the AO will simply be closed and re-opened.
|
||||
|
||||
There are wrappers to support for two types of audio APIs: push.c and
|
||||
pull.c. ao.c calls into one of these. They contain generic code to deal
|
||||
with the data flow these APIs impose.
|
||||
Unlike VOs, AOs can't be reconfigured on a format change. Without
|
||||
--gapless-audio, even playing a new file will close and re-open the audio
|
||||
device.
|
||||
|
||||
Note that mpv synchronizes the video to the audio. That's the reason
|
||||
why buggy audio drivers can have a bad influence on playback quality.
|
||||
@@ -229,19 +213,17 @@ audio/out/:
|
||||
sub/:
|
||||
Contains subtitle and OSD rendering.
|
||||
|
||||
osd.c/.h is actually the OSD code. It queries dec_sub.c to retrieve
|
||||
sub.c/.h is actually the OSD code. It queries dec_sub.c to retrieve
|
||||
decoded/rendered subtitles. osd_libass.c is the actual implementation of
|
||||
the OSD text renderer (which uses libass, and takes care of all the tricky
|
||||
fontconfig/freetype API usage and text layouting).
|
||||
|
||||
The VOs call osd.c to render OSD and subtitle (via e.g. osd_draw()). osd.c
|
||||
in turn asks dec_sub.c for subtitle overlay bitmaps, which relays the
|
||||
request to one of the sd_*.c subtitle decoders/renderers.
|
||||
|
||||
Subtitle loading is in demux/. The MPlayer subreader.c is mostly gone - parts
|
||||
of it survive in demux_subreader.c. It's used as last fallback, or to handle
|
||||
some text subtitle types on Libav. It should go away eventually. Normally,
|
||||
subtitles are loaded via demux_lavf.c.
|
||||
Subtitle loading is now in demux/ instead. demux_libass.c wraps loading
|
||||
.ass subtitles via libass. demux_lavf.c loads most subtitle types via
|
||||
FFmpeg. demux_subreader.c is the old MPlayer code. It's used as last
|
||||
fallback, or to handle some text subtitle types on Libav. (It also can
|
||||
load UTF-16 encoded subtitles without requiring the use of -sub-codepage.)
|
||||
demux_subreader.c should eventually go away (maybe).
|
||||
|
||||
The subtitles are passed to dec_sub.c and the subtitle decoders in sd_*.c
|
||||
as they are demuxed. All text subtitles are rendered by sd_ass.c. If text
|
||||
|
||||
@@ -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
|
||||
|
||||
+101
-83
@@ -1,97 +1,115 @@
|
||||
Release 0.7.1
|
||||
=============
|
||||
|
||||
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.
|
||||
- Drop support for Libav 9 and earlier FFmpeg releases. At least FFmpeg 2.1.4 or
|
||||
Libav 10 are now required.
|
||||
- Change cache options to take kilobytes instead of percentages, and raise the
|
||||
default values.
|
||||
- Lower --auto-pause/resume defaults values to improve web streams playback.
|
||||
- Use dvdnav support instead of the internal dvd implementation for dvd:// URLs
|
||||
(the old implementation is available under dvdread://).
|
||||
- Rename some options to make them more consistent. Here's a partial list of
|
||||
renamed options:
|
||||
|
||||
+ Audio-related:
|
||||
|
||||
* --audiofile → --audio-file
|
||||
* --audiofile-cache → --audio-file-cache
|
||||
* --channels → --audio-channels
|
||||
* --format → --audio-format
|
||||
* --srate → --audio-samplerate
|
||||
|
||||
+ Input-related:
|
||||
|
||||
* --ar → --input-appleremote
|
||||
* --consolecontrols → --input-terminal
|
||||
* --media-keys → --input-media-keys
|
||||
* --joystick → --input-joystick
|
||||
* --lirc → --input-lirc
|
||||
* --lircconf → --input-lirc-conf
|
||||
* --mouse-movements → --input-cursor
|
||||
* --right-alt-gr → --input-right-alt-gr
|
||||
|
||||
+ Terminal-related:
|
||||
|
||||
* --msgcolor → --msg-color
|
||||
* --msglevel → --msg-level
|
||||
* --msgmodule → --msg-module
|
||||
* --msgtime → --msg-time
|
||||
* --playing-msg → --term-playing-msg
|
||||
* --status-msg → --term-status-msg
|
||||
|
||||
+ Subtitle-related:
|
||||
|
||||
* --ass → --sub-ass
|
||||
* --autosub → --sub-auto
|
||||
* --autosub-match → --sub-auto-match
|
||||
* --sub → --sub-file
|
||||
* --subcp → --sub-codepage
|
||||
* --subfps → --sub-fps
|
||||
|
||||
+ Video-related:
|
||||
|
||||
* --aspect → --video-aspect
|
||||
* --fstype → --x11-fstype
|
||||
* --native-fs → --fs-missioncontrol
|
||||
* --name → --x11-name
|
||||
|
||||
- Remove global options variables for --tv, --pvr, --dvb and --cdda.
|
||||
- Move post-processing support to a separate filter (vdpaupp) for the vdpau vo,
|
||||
and deprecate the old vdpau post-processing options.
|
||||
- Remove radio:// support.
|
||||
- Remove support for ASX, SMIL and NSC playlist formats.
|
||||
- Re-introduce pretty-printing for module names with --msg-module.
|
||||
- Load Lua scripts under `$MPV_HOME/lua/` (default `~/.mpv/lua/`) automatically
|
||||
(this can be disabled by using `--load-scripts=no`).
|
||||
- Corrupt/broken frames are not shown by default anymore (this can be re-enabled
|
||||
by using --vd-lavc-show-all=yes).
|
||||
- Automatically copy metadata from the input file to the output when encoding
|
||||
(this can be disabled by using --no-ometadata).
|
||||
- Change --sub-file semantics: if multiple --sub-file are used, the subtitle
|
||||
tracks can be cycled at runtime and used with --sid and --secondary-sid.
|
||||
- Make the subtitle font size relative to the window, instead of the video, when
|
||||
--sub-scale-with-window=yes is used.
|
||||
- Change default ao from wasapi to dsound on Windows.
|
||||
|
||||
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).
|
||||
- Fix tv:// playback with missing timestamps.
|
||||
- Fix display of cover art inside audio files with the vdpau vo.
|
||||
|
||||
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 client API in the form of the libmpv library, which is intended to be a
|
||||
stable API to get some rough control over the player (this is not supported on
|
||||
Mac OS X).
|
||||
- Add option for approximating BT.709 gamma for the opengl vo (similarly to many
|
||||
programs in the Apple ecosystem).
|
||||
- Add support for non-BT.709 primaries.
|
||||
- Add support for BT.2020 colorspace.
|
||||
- Generate and install zsh completion script (disabled by default since it may
|
||||
interfere with cross-compilation).
|
||||
- Add support for looping a single file (instead of the whole playlist) with the
|
||||
--loop-file option.
|
||||
- Add VapourSynth video filter (this has various restrictions, which are listed
|
||||
in the manpage).
|
||||
- Add support for ReplayGain in the volume audio filter.
|
||||
- Add a --config-dir option to force config directory.
|
||||
- Add support for GPU switching on Mac OS X.
|
||||
- Add support for automatic selection of color profiles on Mac OS X.
|
||||
- Add support for binding commands to sequences of keys.
|
||||
- Add support for passing options to Lua scripts with --lua-opts.
|
||||
- Add some filesystem utility functions to the Lua API.
|
||||
- Add functions for observing property changes to the Lua API .
|
||||
- Add support for creating a cache file on the filesystem with --cache-file.
|
||||
- Add support for window dragging by grabbing with the x11 vo.
|
||||
- Add option to write file name to watch later file.
|
||||
- Add support for a "weak" gapless mode, and make it default (the old mode can
|
||||
be enabled with --gapless-audio or --gapless-audio=yes).
|
||||
- Automatically detect network filesystems on Linux and Windows.
|
||||
|
||||
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 56ce2a3..8bb7d42`` in
|
||||
the git repository.
|
||||
@@ -15,10 +15,10 @@ def is_user_lib(objfile, libname):
|
||||
return not sys_re.match(libname) and \
|
||||
not usr_re.match(libname) and \
|
||||
not exe_re.match(libname) and \
|
||||
not "libobjc." in libname and \
|
||||
not "libSystem." in libname and \
|
||||
not "libc." in libname and \
|
||||
not "libgcc." in libname and \
|
||||
not "libobjc" in libname and \
|
||||
not "libSystem" in libname and \
|
||||
not "libc" in libname and \
|
||||
not "libgcc" in libname and \
|
||||
not os.path.basename(objfile) in libname
|
||||
|
||||
def otool(objfile):
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
conv() {
|
||||
echo
|
||||
identify -format "icon: %w %h" "$1"
|
||||
convert "$1" -depth 8 rgba:-
|
||||
identify -format "icon: %w %h" $1
|
||||
convert $1 -depth 8 rgba:-
|
||||
}
|
||||
|
||||
(echo "# File generated by gen-x11-icon.sh" ;
|
||||
|
||||
+58
-55
@@ -1,15 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
: "${MPV:=mpv}"
|
||||
: "${ILDETECT_MPV:=$MPV}"
|
||||
: "${ILDETECT_MPVFLAGS:=--start=35% --length=35}"
|
||||
: "${ILDETECT_DRY_RUN:=}"
|
||||
: "${ILDETECT_QUIET:=}"
|
||||
: "${ILDETECT_RUN_INTERLACED_ONLY:=}"
|
||||
: "${ILDETECT_FORCE_RUN:=}"
|
||||
case "$0" in
|
||||
*/*)
|
||||
MYDIR=${0%/*}
|
||||
;;
|
||||
*)
|
||||
MYDIR=.
|
||||
;;
|
||||
esac
|
||||
|
||||
# This script uses ffmpeg's "idet" filter for interlace detection. In the
|
||||
# long run this should replace ildetect.sh+ildetect.so.
|
||||
: ${MPV:=mpv}
|
||||
: ${ILDETECT_MPV:=$MPV}
|
||||
: ${ILDETECT_MPV:=$MPV}
|
||||
: ${ILDETECT_MPVFLAGS:=--start=35% --length=35}
|
||||
: ${ILDETECT_DRY_RUN:=}
|
||||
: ${ILDETECT_QUIET:=}
|
||||
: ${ILDETECT_RUN_INTERLACED_ONLY:=}
|
||||
: ${ILDETECT_FORCE_RUN:=}
|
||||
: ${MAKE:=make}
|
||||
|
||||
# exit status:
|
||||
# 0 progressive
|
||||
@@ -22,131 +30,126 @@
|
||||
testfun()
|
||||
{
|
||||
$ILDETECT_MPV "$@" \
|
||||
--vf-add=lavfi="[idet]" --msg-level ffmpeg=v \
|
||||
--vf=lavfi="[idet]" --msg-level ffmpeg=v \
|
||||
--o= --vo=null --no-audio --untimed \
|
||||
$ILDETECT_MPVFLAGS \
|
||||
| { if [ -n "$ILDETECT_QUIET" ]; then cat; else tee /dev/stderr; fi } \
|
||||
| grep "Parsed_idet_0: Multi frame detection: "
|
||||
| grep "Parsed_idet_0: Multi frame detection: " | tail -n 1
|
||||
}
|
||||
|
||||
judge()
|
||||
{
|
||||
tff=0
|
||||
bff=0
|
||||
progressive=0
|
||||
undetermined=0
|
||||
while read -r _ _ _ _ _ _ tff1 _ bff1 _ progressive1 _ undetermined1 _; do
|
||||
case "$tff1$bff1$progressive1$undetermined1" in
|
||||
*[!0-9]*)
|
||||
printf >&2 'ERROR: Unrecognized idet output: %s\n' "$out"
|
||||
exit 16
|
||||
;;
|
||||
esac
|
||||
tff=$((tff + tff1))
|
||||
bff=$((bff + bff1))
|
||||
progressive=$((progressive + progressive1))
|
||||
undetermined=$((undetermined + undetermined1))
|
||||
done <<EOF
|
||||
$(testfun "$@")
|
||||
EOF
|
||||
out=`testfun "$@"`
|
||||
|
||||
tff=${out##* TFF:}; tff=${tff%% *}
|
||||
bff=${out##* BFF:}; bff=${bff%% *}
|
||||
progressive=${out##* Progressive:}; progressive=${progressive%% *}
|
||||
undetermined=${out##* Undetermined:}; undetermined=${undetermined%% *}
|
||||
|
||||
case "$tff$bff$progressive$undetermined" in
|
||||
*[!0-9]*)
|
||||
echo >&2 "ERROR: Unrecognized idet output: $out"
|
||||
exit 16
|
||||
;;
|
||||
esac
|
||||
|
||||
interlaced=$((bff + tff))
|
||||
determined=$((interlaced + progressive))
|
||||
|
||||
if [ "$undetermined" -gt "$determined" ] || [ "$determined" -lt 250 ]; then
|
||||
if [ $undetermined -gt $determined ] || [ $determined -lt 250 ]; then
|
||||
echo >&2 "ERROR: Less than 50% or 250 frames are determined."
|
||||
[ -n "$ILDETECT_FORCE_RUN" ] || exit 8
|
||||
echo >&2 "Assuming interlacing."
|
||||
if [ "$tff" -gt $((bff * 10)) ]; then
|
||||
verdict="interlaced-tff"
|
||||
elif [ "$bff" -gt $((tff * 10)) ]; then
|
||||
verdict="interlaced-bff"
|
||||
if [ $tff -gt $((bff * 10)) ]; then
|
||||
verdict=interlaced-tff
|
||||
elif [ $bff -gt $((tff * 10)) ]; then
|
||||
verdict=interlaced-bff
|
||||
else
|
||||
verdict="interlaced"
|
||||
verdict=interlaced
|
||||
fi
|
||||
elif [ $((interlaced * 20)) -gt "$progressive" ]; then
|
||||
elif [ $((interlaced * 20)) -gt $progressive ]; then
|
||||
# At least 5% of the frames are interlaced!
|
||||
if [ "$tff" -gt $((bff * 10)) ]; then
|
||||
verdict="interlaced-tff"
|
||||
elif [ "$bff" -gt $((tff * 10)) ]; then
|
||||
verdict="interlaced-bff"
|
||||
if [ $tff -gt $((bff * 10)) ]; then
|
||||
verdict=interlaced-tff
|
||||
elif [ $bff -gt $((tff * 10)) ]; then
|
||||
verdict=interlaced-bff
|
||||
else
|
||||
echo >&2 "ERROR: Content is interlaced, but can't determine field order."
|
||||
[ -n "$ILDETECT_FORCE_RUN" ] || exit 8
|
||||
echo >&2 "Assuming interlacing with default field order."
|
||||
verdict="interlaced"
|
||||
verdict=interlaced
|
||||
fi
|
||||
else
|
||||
# Likely progressive
|
||||
verdict="progressive"
|
||||
# Likely progrssive.
|
||||
verdict=progressive
|
||||
fi
|
||||
|
||||
printf '%s\n' "$verdict"
|
||||
echo "$verdict"
|
||||
}
|
||||
|
||||
judge "$@" --vf-clr
|
||||
judge "$@"
|
||||
case "$verdict" in
|
||||
progressive)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
[ -n "$ILDETECT_RUN_INTERLACED_ONLY" ] || \
|
||||
$ILDETECT_MPV "$@"
|
||||
r=$?
|
||||
[ $r -eq 0 ] || exit $((r | 16))
|
||||
[ $r -eq 0 ] || exit $(($r | 16))
|
||||
exit 0
|
||||
;;
|
||||
interlaced-tff)
|
||||
judge "$@" --vf-clr --vf-pre=pullup --field-dominance=top
|
||||
judge "$@" --vf-pre=pullup --field-dominance=top
|
||||
case "$verdict" in
|
||||
progressive)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
$ILDETECT_MPV "$@" --vf-pre=pullup --field-dominance=top
|
||||
r=$?
|
||||
[ $r -eq 0 ] || exit $((r | 16))
|
||||
[ $r -eq 0 ] || exit $(($r | 16))
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
$ILDETECT_MPV "$@" --vf-pre=yadif --field-dominance=top
|
||||
r=$?
|
||||
[ $r -eq 0 ] || exit $((r | 16))
|
||||
[ $r -eq 0 ] || exit $(($r | 16))
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
interlaced-bff)
|
||||
judge "$@" --vf-clr --vf-pre=pullup --field-dominance=bottom
|
||||
judge "$@" --vf-pre=pullup --field-dominance=bottom
|
||||
case "$verdict" in
|
||||
progressive)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
$ILDETECT_MPV "$@" --vf-pre=pullup --field-dominance=bottom
|
||||
r=$?
|
||||
[ $r -eq 0 ] || exit $((r | 16))
|
||||
[ $r -eq 0 ] || exit $(($r | 16))
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
$ILDETECT_MPV "$@" --vf-pre=yadif --field-dominance=bottom
|
||||
r=$?
|
||||
[ $r -eq 0 ] || exit $((r | 16))
|
||||
[ $r -eq 0 ] || exit $(($r | 16))
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
interlaced)
|
||||
judge "$@" --vf-clr --vf-pre=pullup
|
||||
judge "$@" --vf-pre=pullup
|
||||
case "$verdict" in
|
||||
progressive)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
$ILDETECT_MPV "$@" --vf-pre=pullup
|
||||
r=$?
|
||||
[ $r -eq 0 ] || exit $((r | 16))
|
||||
[ $r -eq 0 ] || exit $(($r | 16))
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
$ILDETECT_MPV "$@" --vf-pre=yadif
|
||||
r=$?
|
||||
[ $r -eq 0 ] || exit $((r | 16))
|
||||
[ $r -eq 0 ] || exit $(($r | 16))
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -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'),
|
||||
@@ -268,7 +265,6 @@ sub define_matroska {
|
||||
elem('DisplayUnit', '54b2', 'uint'),
|
||||
elem('FrameRate', '2383e3', 'float'),
|
||||
elem('ColourSpace', '2eb524', 'binary'),
|
||||
elem('StereoMode', '53b8', 'uint'),
|
||||
}),
|
||||
elem('Audio', 'e1', {
|
||||
elem('SamplingFrequency', 'b5', 'float'),
|
||||
@@ -296,8 +292,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,16 +0,0 @@
|
||||
-- Test script for property change notification mechanism.
|
||||
|
||||
function format_property_val(v)
|
||||
if type(v) == "table" then
|
||||
return mp.format_table(v) -- undocumented function; might be removed
|
||||
else
|
||||
return tostring(v)
|
||||
end
|
||||
end
|
||||
|
||||
for i,name in ipairs(mp.get_property_native("property-list")) do
|
||||
mp.observe_property(name, "native", function(name, val)
|
||||
print("property '" .. name .. "' changed to '" ..
|
||||
format_property_val(val) .. "'")
|
||||
end)
|
||||
end
|
||||
+150
-127
@@ -17,134 +17,157 @@
|
||||
# When multiple files were specified, their info will be put into FOO_* for the
|
||||
# first file, FOO_1_* for the second file, FOO_2_* for the third file, etc.
|
||||
|
||||
__midentify__main() {
|
||||
|
||||
case "$0" in
|
||||
mpv_identify.sh|*/mpv_identify.sh)
|
||||
# we are NOT being sourced
|
||||
[ -n "$1" ] && set -- '' "$@"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$#" -lt 2 ]; then
|
||||
cat >&2 <<EOF
|
||||
Usage 1 (for humans only): $0 filename.mkv
|
||||
will print all property values.
|
||||
Note that this output really shouldn't be parsed, as the
|
||||
format is subject to change.
|
||||
|
||||
Usage 2 (for use by scripts): see top of this file
|
||||
|
||||
NOTE: for mkv with ordered chapters, this may
|
||||
not always identify the specified file, but the
|
||||
file providing the first chapter. Specify
|
||||
--no-ordered-chapters to prevent this.
|
||||
EOF
|
||||
return 2
|
||||
fi
|
||||
|
||||
local LF="
|
||||
"
|
||||
|
||||
local nextprefix="$1"
|
||||
shift
|
||||
|
||||
if [ -n "$nextprefix" ]; then
|
||||
# in case of error, we always want this unset
|
||||
unset "${nextprefix}path"
|
||||
fi
|
||||
|
||||
local allprops="
|
||||
filename
|
||||
path
|
||||
stream-start
|
||||
stream-end
|
||||
stream-length
|
||||
|
||||
demuxer
|
||||
|
||||
length
|
||||
chapters
|
||||
editions
|
||||
titles
|
||||
|
||||
audio
|
||||
audio-bitrate
|
||||
audio-codec
|
||||
audio-format
|
||||
channels
|
||||
samplerate
|
||||
|
||||
video
|
||||
angle
|
||||
video-bitrate
|
||||
video-codec
|
||||
video-format
|
||||
video-aspect
|
||||
fps
|
||||
width
|
||||
height
|
||||
dwidth
|
||||
dheight
|
||||
|
||||
sub
|
||||
"
|
||||
# TODO add metadata support once mpv can do it
|
||||
|
||||
local propstr="X-MIDENTIFY-START:$LF"
|
||||
local key
|
||||
for key in $allprops; do
|
||||
propstr="${propstr}X-MIDENTIFY: $key \${=$key}$LF"
|
||||
key="$(printf '%s\n' "$key" | tr - _)"
|
||||
unset "$nextprefix$key"
|
||||
done
|
||||
|
||||
local fileindex=0
|
||||
local prefix=
|
||||
local line
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
X-MIDENTIFY-START:)
|
||||
if [ -n "$nextprefix" ]; then
|
||||
prefix="$nextprefix"
|
||||
if [ "$fileindex" -gt 0 ]; then
|
||||
nextprefix="${prefix%${fileindex}_}"
|
||||
fi
|
||||
fileindex="$((fileindex+1))"
|
||||
nextprefix="${nextprefix}${fileindex}_"
|
||||
for key in $allprops; do
|
||||
key="$(printf '%s\n' "$key" | tr - _)"
|
||||
unset "$nextprefix$key"
|
||||
done
|
||||
else
|
||||
if [ "$fileindex" -gt 0 ]; then
|
||||
printf '\n'
|
||||
fi
|
||||
fileindex="$((fileindex+1))"
|
||||
fi
|
||||
case "$0" in
|
||||
mpv_identify.sh|*/mpv_identify.sh)
|
||||
# we are NOT being sourced
|
||||
case "$1" in
|
||||
'')
|
||||
;;
|
||||
X-MIDENTIFY:\ *)
|
||||
local key="${line#X-MIDENTIFY: }"
|
||||
local value="${key#* }"
|
||||
key="${key%% *}"
|
||||
key="$(printf '%s\n' "$key" | tr - _)"
|
||||
if [ -n "$nextprefix" ]; then
|
||||
if [ -z "$prefix" ]; then
|
||||
echo >&2 "Got X-MIDENTIFY: without X-MIDENTIFY-START:"
|
||||
elif [ -n "$value" ]; then
|
||||
eval "$prefix$key"='"$value"'
|
||||
fi
|
||||
else
|
||||
if [ -n "$value" ]; then
|
||||
printf '%s=%s\n' "$key" "$value"
|
||||
fi
|
||||
fi
|
||||
*)
|
||||
set -- '' "$@"
|
||||
;;
|
||||
esac
|
||||
done <<EOF
|
||||
$(${MPV:-mpv} --term-playing-msg="$propstr" --vo=null --ao=null \
|
||||
--frames=1 --quiet --no-cache --no-config -- "$@")
|
||||
EOF
|
||||
}
|
||||
;;
|
||||
esac
|
||||
|
||||
__midentify__main "$@"
|
||||
if [ $# -lt 2 ]; then
|
||||
echo >&2 "Usage 1 (for humans only): $0 filename.mkv"
|
||||
echo >&2 "will print all property values."
|
||||
echo >&2 "Note that this output really shouldn't be parsed, as the"
|
||||
echo >&2 "format is subject to change."
|
||||
echo >&2
|
||||
echo >&2 "Usage 2 (for use by scripts): see top of this file"
|
||||
echo >&2
|
||||
echo >&2 "NOTE: for mkv with ordered chapters, this may"
|
||||
echo >&2 "not always identify the specified file, but the"
|
||||
echo >&2 "file providing the first chapter. Specify"
|
||||
echo >&2 "--no-ordered-chapters to prevent this."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$MPV" ]; then
|
||||
MPV="mpv"
|
||||
fi
|
||||
|
||||
__midentify__LF="
|
||||
"
|
||||
|
||||
__midentify__nextprefix=$1
|
||||
shift
|
||||
|
||||
if [ -n "$__midentify__nextprefix" ]; then
|
||||
# in case of error, we always want this unset
|
||||
eval unset $__midentify__nextprefix'path'
|
||||
fi
|
||||
|
||||
__midentify__allprops="
|
||||
filename
|
||||
path
|
||||
stream-start
|
||||
stream-end
|
||||
stream-length
|
||||
|
||||
demuxer
|
||||
|
||||
length
|
||||
chapters
|
||||
editions
|
||||
titles
|
||||
|
||||
audio
|
||||
audio-bitrate
|
||||
audio-codec
|
||||
audio-format
|
||||
channels
|
||||
samplerate
|
||||
|
||||
video
|
||||
angle
|
||||
video-bitrate
|
||||
video-codec
|
||||
video-format
|
||||
video-aspect
|
||||
fps
|
||||
width
|
||||
height
|
||||
dwidth
|
||||
dheight
|
||||
|
||||
sub
|
||||
"
|
||||
# TODO add metadata support once mpv can do it
|
||||
|
||||
__midentify__propstr="X-MIDENTIFY-START:$__midentify__LF"
|
||||
for __midentify__key in $__midentify__allprops; do
|
||||
__midentify__propstr=$__midentify__propstr"X-MIDENTIFY: $__midentify__key \${=$__midentify__key}$__midentify__LF"
|
||||
__midentify__key=`echo "$__midentify__key" | tr - _`
|
||||
eval unset $__midentify__nextprefix$__midentify__key
|
||||
done
|
||||
|
||||
__midentify__output=`$MPV --term-playing-msg="$__midentify__propstr" --vo=null --ao=null --frames=1 --quiet --no-cache --no-config "$@"`
|
||||
__midentify__fileindex=0
|
||||
__midentify__prefix=
|
||||
while :; do
|
||||
case "$__midentify__output" in
|
||||
'')
|
||||
break
|
||||
;;
|
||||
*$__midentify__LF*)
|
||||
__midentify__line=${__midentify__output%%$__midentify__LF*}
|
||||
__midentify__output=${__midentify__output#*$__midentify__LF}
|
||||
;;
|
||||
*)
|
||||
__midentify__line=$__midentify__output
|
||||
__midentify__output=
|
||||
;;
|
||||
esac
|
||||
case "$__midentify__line" in
|
||||
X-MIDENTIFY-START:)
|
||||
if [ -n "$__midentify__nextprefix" ]; then
|
||||
__midentify__prefix=$__midentify__nextprefix
|
||||
if [ $__midentify__fileindex -gt 0 ]; then
|
||||
__midentify__nextprefix=${__midentify__prefix%$__midentify__fileindex\_}
|
||||
fi
|
||||
__midentify__fileindex=$(($__midentify__fileindex+1))
|
||||
__midentify__nextprefix=$__midentify__nextprefix$__midentify__fileindex\_
|
||||
for __midentify__key in $__midentify__allprops; do
|
||||
__midentify__key=`echo "$__midentify__key" | tr - _`
|
||||
eval unset $__midentify__nextprefix$__midentify__key
|
||||
done
|
||||
else
|
||||
if [ $__midentify__fileindex -gt 0 ]; then
|
||||
echo
|
||||
fi
|
||||
__midentify__fileindex=$(($__midentify__fileindex+1))
|
||||
fi
|
||||
;;
|
||||
X-MIDENTIFY:\ *)
|
||||
__midentify__key=${__midentify__line#X-MIDENTIFY:\ }
|
||||
__midentify__value=${__midentify__key#* }
|
||||
__midentify__key=${__midentify__key%% *}
|
||||
__midentify__key=`echo "$__midentify__key" | tr - _`
|
||||
if [ -n "$__midentify__nextprefix" ]; then
|
||||
if [ -z "$__midentify__prefix" ]; then
|
||||
echo >&2 "Got X-MIDENTIFY: without X-MIDENTIFY-START:"
|
||||
elif [ -n "$__midentify__value" ]; then
|
||||
eval $__midentify__prefix$__midentify__key=\$__midentify__value
|
||||
fi
|
||||
else
|
||||
if [ -n "$__midentify__value" ]; then
|
||||
echo "$__midentify__key=$__midentify__value"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
unset __midentify__fileindex
|
||||
unset __midentify__allprops
|
||||
unset __midentify__key
|
||||
unset __midentify__LF
|
||||
unset __midentify__line
|
||||
unset __midentify__output
|
||||
unset __midentify__nextprefix
|
||||
unset __midentify__prefix
|
||||
unset __midentify__propstr
|
||||
unset __midentify__value
|
||||
@@ -624,90 +624,6 @@
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.movie</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>AVC raw stream</string>
|
||||
<key>UTTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.mpv.h264</string>
|
||||
<key>UTTypeReferenceURL</key>
|
||||
<string>http://www.itu.int/rec/T-REC-H.264</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>264</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.movie</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>HEVC raw stream</string>
|
||||
<key>UTTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.mpv.hevc</string>
|
||||
<key>UTTypeReferenceURL</key>
|
||||
<string>http://hevc.info</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>hevc</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.movie</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>YUV stream</string>
|
||||
<key>UTTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.mpv.yuv</string>
|
||||
<key>UTTypeReferenceURL</key>
|
||||
<string>http://en.wikipedia.org/wiki/YUV</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>yuv</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.movie</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>YUV4MPEG2 stream</string>
|
||||
<key>UTTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.mpv.y4m</string>
|
||||
<key>UTTypeReferenceURL</key>
|
||||
<string>http://wiki.multimedia.cx/index.php?title=YUV4MPEG2</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>y4m</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
|
||||
+18
-31
@@ -18,11 +18,10 @@ e.g.:
|
||||
|
||||
Currently, the following event types are supported:
|
||||
|
||||
'start' <name> start of the named event
|
||||
'end' <name> end of the named event
|
||||
'value' <float> <name> a normal value (as opposed to event)
|
||||
'event-timed' <ts> <name> singular event at the given timestamp
|
||||
<name> singular event
|
||||
'start' <name> start of the named event
|
||||
'end' <name> end of the named event
|
||||
'value' <float> <name> a normal value (as opposed to event)
|
||||
<event> singular event
|
||||
|
||||
"""
|
||||
|
||||
@@ -30,32 +29,19 @@ class G:
|
||||
events = {}
|
||||
sevents = [] # events, deterministically sorted
|
||||
start = None
|
||||
# http://matplotlib.org/api/markers_api.html#module-matplotlib.markers
|
||||
markers = ["o", "8", "s", "p", "*", "h", "+", "x", "D"]
|
||||
|
||||
def find_marker():
|
||||
if len(G.markers) == 0:
|
||||
return "o"
|
||||
m = G.markers[0]
|
||||
G.markers = G.markers[1:]
|
||||
return m
|
||||
|
||||
class Event:
|
||||
pass
|
||||
|
||||
def get_event(event, evtype):
|
||||
def get_event(event):
|
||||
if event not in G.events:
|
||||
e = Event()
|
||||
G.events[event] = e
|
||||
e.name = event
|
||||
e.vals = []
|
||||
e.type = evtype
|
||||
e.marker = "o"
|
||||
e.numid = len(G.events)
|
||||
e.type = "unknown"
|
||||
G.sevents = list(G.events.values())
|
||||
G.sevents.sort(key=lambda x: x.name)
|
||||
if e.type == "event-signal":
|
||||
e.marker = find_marker()
|
||||
return G.events[event]
|
||||
|
||||
for line in [line.split("#")[0].strip() for line in open(filename, "r")]:
|
||||
@@ -68,29 +54,30 @@ for line in [line.split("#")[0].strip() for line in open(filename, "r")]:
|
||||
G.start = ts
|
||||
ts = ts - G.start
|
||||
if event.startswith("start "):
|
||||
e = get_event(event[6:], "event")
|
||||
e = get_event(event[6:])
|
||||
e.type = "event"
|
||||
e.vals.append((ts, 0))
|
||||
e.vals.append((ts, 1))
|
||||
elif event.startswith("end "):
|
||||
e = get_event(event[4:], "event")
|
||||
e = get_event(event[4:])
|
||||
e.type = "event"
|
||||
e.vals.append((ts, 1))
|
||||
e.vals.append((ts, 0))
|
||||
elif event.startswith("value "):
|
||||
_, val, name = event.split(" ", 2)
|
||||
val = float(val)
|
||||
e = get_event(name, "value")
|
||||
e = get_event(name)
|
||||
e.type = "value"
|
||||
e.vals.append((ts, val))
|
||||
elif event.startswith("event-timed "):
|
||||
_, val, name = event.split(" ", 2)
|
||||
val = int(val) / 1000 - G.start
|
||||
e = get_event(name, "event-signal")
|
||||
e.vals.append((val, 1))
|
||||
else:
|
||||
e = get_event(event, "event-signal")
|
||||
e = get_event(event)
|
||||
e.type = "event-signal"
|
||||
e.vals.append((ts, 1))
|
||||
|
||||
ao_events = ["ao fill", "audio wait"]
|
||||
for e in G.sevents:
|
||||
e.vals = [(x, y * e.numid / len(G.events)) for (x, y) in e.vals]
|
||||
if e.name in ao_events:
|
||||
e.vals = [(x, y * 0.5) for (x, y) in e.vals]
|
||||
|
||||
plot.hold(True)
|
||||
mainpl = plot.subplot(2, 1, 1)
|
||||
@@ -102,7 +89,7 @@ for e in G.sevents:
|
||||
plot.subplot(2, 1, 1)
|
||||
pl, = plot.plot([x for x,y in e.vals], [y for x,y in e.vals], label=e.name)
|
||||
if e.type == "event-signal":
|
||||
plot.setp(pl, marker = e.marker, linestyle = "None")
|
||||
plot.setp(pl, marker = "o", linestyle = "None")
|
||||
legend.append(pl)
|
||||
plot.subplot(2, 1, 1)
|
||||
plot.legend(legend, [pl.get_label() for pl in legend])
|
||||
|
||||
+41
-16
@@ -21,13 +21,20 @@ 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.
|
||||
Note: you can supply custom options with the UMPV_OPTIONS environment variable.
|
||||
The environment variable will be split on whitespace, and each item is
|
||||
passed as option to mpv _if_ the script starts mpv. If mpv is not started
|
||||
by the script (i.e. mpv is already running), the options 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
|
||||
@@ -38,17 +45,21 @@ import fcntl
|
||||
import stat
|
||||
import string
|
||||
|
||||
if len(sys.argv) < 1:
|
||||
sys.exit(1)
|
||||
files = sys.argv[1:]
|
||||
|
||||
# this is the same method mpv uses to decide this
|
||||
# this is about the same method mpv uses to decide this
|
||||
def is_url(filename):
|
||||
parts = filename.split("://", 1)
|
||||
parts = filename.split(":", 1)
|
||||
if len(parts) < 2:
|
||||
return False
|
||||
# protocol prefix has no special characters => it's an URL
|
||||
allowed_symbols = string.ascii_letters + string.digits + '_'
|
||||
prefix = parts[0]
|
||||
return all(map(lambda c: c in allowed_symbols, prefix))
|
||||
for c in prefix:
|
||||
if string.ascii_letters.find(c) < 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
# make them absolute; also makes them safe against interpretation as options
|
||||
def make_abs(filename):
|
||||
@@ -57,7 +68,10 @@ def make_abs(filename):
|
||||
return filename
|
||||
files = [make_abs(f) for f in files]
|
||||
|
||||
FIFO = os.path.join(os.getenv("HOME"), ".umpv_fifo")
|
||||
opts_env = os.getenv("UMPV_OPTIONS")
|
||||
opts_env = opts_env.split() if opts_env else []
|
||||
|
||||
FIFO = "/tmp/umpv-fifo-" + os.getenv("USER")
|
||||
|
||||
fifo_fd = -1
|
||||
try:
|
||||
@@ -70,6 +84,14 @@ 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())):
|
||||
print("error: command FIFO is not a FIFO or has bogus permissions")
|
||||
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,12 +108,15 @@ else:
|
||||
try:
|
||||
os.unlink(FIFO)
|
||||
except OSError as e:
|
||||
pass
|
||||
os.mkfifo(FIFO, 0o600)
|
||||
if e.errno == errno.ENOENT:
|
||||
pass
|
||||
else:
|
||||
raise e
|
||||
os.mkfifo(FIFO, int("0600", 8))
|
||||
|
||||
opts = (os.getenv("MPV") or "mpv").split()
|
||||
opts.extend(["--no-terminal", "--force-window", "--input-file=" + FIFO,
|
||||
"--"])
|
||||
opts = ["mpv", "--no-terminal", "--force-window", "--input-file=" + FIFO]
|
||||
opts.extend(opts_env)
|
||||
opts.append("--")
|
||||
opts.extend(files)
|
||||
|
||||
subprocess.check_call(opts)
|
||||
@@ -90,7 +90,8 @@ int vf_dlopen_getcontext(struct vf_dlopen_context *ctx, int argc, const char **a
|
||||
if (argc != 1 && argc != 2)
|
||||
return -1;
|
||||
|
||||
framestep_data_t *framestep = calloc(1,sizeof(framestep_data_t));
|
||||
framestep_data_t *framestep = malloc(sizeof(framestep_data_t));
|
||||
memset(framestep, 0, sizeof(*framestep));
|
||||
|
||||
framestep->step = atoi(argv[0]);
|
||||
framestep->pos = (argc >= 2) ? atoi(argv[1]) : 0;
|
||||
|
||||
@@ -267,7 +267,8 @@ int vf_dlopen_getcontext(struct vf_dlopen_context *ctx, int argc, const char **a
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
ildetect_data_t *il = calloc(1,sizeof(ildetect_data_t));
|
||||
ildetect_data_t *il = malloc(sizeof(ildetect_data_t));
|
||||
memset(il, 0, sizeof(*il));
|
||||
|
||||
#define A(i,d) ((argc>(i) && *argv[i]) ? atof(argv[i]) : (d))
|
||||
il->method = A(0, 0);
|
||||
|
||||
@@ -11,6 +11,7 @@ esac
|
||||
|
||||
: ${MPV:=mpv}
|
||||
: ${ILDETECT_MPV:=$MPV}
|
||||
: ${ILDETECT_MPV:=$MPV}
|
||||
: ${ILDETECT_MPVFLAGS:=--start=35% --length=35}
|
||||
: ${ILDETECT_DRY_RUN:=}
|
||||
: ${ILDETECT_QUIET:=}
|
||||
|
||||
@@ -348,7 +348,8 @@ int vf_dlopen_getcontext(struct vf_dlopen_context *ctx, int argc, const char **a
|
||||
{ "yuv420p", "yuv420p" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
privdata *priv = calloc(1,sizeof(privdata));
|
||||
privdata *priv = malloc(sizeof(privdata));
|
||||
memset(priv, 0, sizeof(*priv));
|
||||
priv->step = 8;
|
||||
if(argc >= 1)
|
||||
priv->w = atoi(argv[0]);
|
||||
|
||||
@@ -237,7 +237,8 @@ int vf_dlopen_getcontext(struct vf_dlopen_context *ctx, int argc, const char **a
|
||||
if (!a0[0] || a0[1] || !a1[0] || argc > 2)
|
||||
return -1;
|
||||
|
||||
tc_data_t *tc = calloc(1,sizeof(tc_data_t));
|
||||
tc_data_t *tc = malloc(sizeof(tc_data_t));
|
||||
memset(tc, 0, sizeof(*tc));
|
||||
|
||||
if (a0[0] == 't')
|
||||
tc->firstfield = 0;
|
||||
|
||||
@@ -150,7 +150,8 @@ int vf_dlopen_getcontext(struct vf_dlopen_context *ctx, int argc, const char **a
|
||||
if (argc != 2)
|
||||
return -1;
|
||||
|
||||
tile_data_t *tile = calloc(1,sizeof(tile_data_t));
|
||||
tile_data_t *tile = malloc(sizeof(tile_data_t));
|
||||
memset(tile, 0, sizeof(*tile));
|
||||
|
||||
tile->cols = atoi(argv[0]);
|
||||
tile->rows = atoi(argv[1]);
|
||||
|
||||
@@ -10,13 +10,14 @@ video_url="$(youtube-dl \
|
||||
--user-agent="$user_agent" \
|
||||
--cookies="$cookies_file" \
|
||||
--get-url \
|
||||
"$@")"
|
||||
"$1")"
|
||||
|
||||
set -f
|
||||
${MPV:-mpv} \
|
||||
shift
|
||||
|
||||
mpv \
|
||||
--cookies \
|
||||
--cookies-file="$cookies_file" \
|
||||
--user-agent="$user_agent" \
|
||||
-- $video_url
|
||||
"$@" -- $video_url
|
||||
|
||||
rm -rf "$cookies_dir"
|
||||
+44
-100
@@ -15,9 +15,7 @@ my @vo = parse_opts("$mpv --vo=help", '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @af = parse_opts("$mpv --af=help", '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vf = parse_opts("$mpv --vf=help", '^ ([^\s\:]*)\s*: (.*)');
|
||||
|
||||
my @protos = parse_opts("$mpv --list-protocols", '^ ([^\s]*)');
|
||||
|
||||
my ($opts_str, $ao_str, $vo_str, $af_str, $vf_str, $protos_str);
|
||||
my ($opts_str, $ao_str, $vo_str, $af_str, $vf_str);
|
||||
|
||||
$opts_str .= qq{ '$_' \\\n} foreach (@opts);
|
||||
chomp $opts_str;
|
||||
@@ -34,52 +32,14 @@ chomp $af_str;
|
||||
$vf_str .= qq{ '$_' \\\n} foreach (@vf);
|
||||
chomp $vf_str;
|
||||
|
||||
$protos_str .= qq{$_ } foreach (@protos);
|
||||
chomp $protos_str;
|
||||
|
||||
my $profile_comp = <<'EOS';
|
||||
local -a profiles
|
||||
local current
|
||||
for current in "${(@f)$($words[1] --profile=help)}"; do
|
||||
current=${current//\*/\\\*}
|
||||
current=${current//\:/\\\:}
|
||||
current=${current//\[/\\\[}
|
||||
current=${current//\]/\\\]}
|
||||
if [[ $current =~ $'\t'([^$'\t']*)$'\t'(.*) ]]; then
|
||||
if [[ -n $match[2] ]]; then
|
||||
current="$match[1][$match[2]]"
|
||||
else
|
||||
current="$match[1]"
|
||||
fi
|
||||
profiles=($profiles $current)
|
||||
fi
|
||||
done
|
||||
if [[ $state == profile ]]; then
|
||||
# For --show-profile, only one allowed
|
||||
if (( ${#profiles} > 0 )); then
|
||||
_values 'profile' $profiles && rc=0
|
||||
fi
|
||||
else
|
||||
# For --profile, multiple allowed
|
||||
profiles=($profiles 'help[list profiles]')
|
||||
_values -s , 'profile(s)' $profiles && rc=0
|
||||
fi
|
||||
EOS
|
||||
chomp $profile_comp;
|
||||
|
||||
my $tmpl = <<"EOS";
|
||||
#compdef mpv
|
||||
|
||||
# mpv zsh completion
|
||||
|
||||
local curcontext="\$curcontext" state state_descr line
|
||||
typeset -A opt_args
|
||||
|
||||
local rc=1
|
||||
|
||||
_arguments -C -S \\
|
||||
_x_arguments -C -s \\
|
||||
$opts_str
|
||||
'*:files:->mfiles' && rc=0
|
||||
'*:files:->mfiles'
|
||||
|
||||
case \$state in
|
||||
ao)
|
||||
@@ -88,7 +48,7 @@ case \$state in
|
||||
$ao_str
|
||||
)
|
||||
|
||||
_describe -t values 'audio outputs' values && rc=0
|
||||
_describe -t values 'audio outputs' values
|
||||
;;
|
||||
|
||||
vo)
|
||||
@@ -97,7 +57,7 @@ $ao_str
|
||||
$vo_str
|
||||
)
|
||||
|
||||
_describe -t values 'video outputs' values && rc=0
|
||||
_describe -t values 'video outputs' values
|
||||
;;
|
||||
|
||||
af)
|
||||
@@ -106,7 +66,7 @@ $vo_str
|
||||
$af_str
|
||||
)
|
||||
|
||||
_describe -t values 'audio filters' values && rc=0
|
||||
_describe -t values 'audio filters' values
|
||||
;;
|
||||
|
||||
vf)
|
||||
@@ -115,88 +75,72 @@ $af_str
|
||||
$vf_str
|
||||
)
|
||||
|
||||
_describe -t values 'video filters' values && rc=0
|
||||
;;
|
||||
|
||||
profile|profiles)
|
||||
$profile_comp
|
||||
;;
|
||||
|
||||
files)
|
||||
compset -P '*,'
|
||||
compset -S ',*'
|
||||
_files -r ',/ \\t\\n\\-' && rc=0
|
||||
_describe -t values 'video filters' values
|
||||
;;
|
||||
|
||||
mfiles)
|
||||
local expl
|
||||
_tags files urls
|
||||
while _tags; do
|
||||
_requested files expl 'media file' _files -g \\
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|qt|rm|ts|vob|wav|webm|wma|wmv)(-.)" && rc=0
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|qt|rm|ts|vob|wav|webm|wma|wmv)(-.)" && ret=0
|
||||
if _requested urls; then
|
||||
while _next_label urls expl URL; do
|
||||
_urls "\$expl[@]" && rc=0
|
||||
compadd -S '' "\$expl[@]" $protos_str && rc=0
|
||||
_urls "\$expl[@]" && ret=0
|
||||
compadd -S '' "\$expl[@]" \{dvd,vcd,cdda,cddb,tv\}:// && ret=0
|
||||
done
|
||||
fi
|
||||
(( rc )) || return 0
|
||||
(( ret )) || return 0
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
return rc
|
||||
EOS
|
||||
|
||||
print $tmpl;
|
||||
|
||||
sub parse_opts {
|
||||
my ($cmd, $regex, $parsing_main_options) = @_;
|
||||
my ($cmd, $regex, $parsing_main_options) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = split /\n/, `$cmd`;
|
||||
my @list;
|
||||
my @lines = split /\n/, `$cmd`;
|
||||
|
||||
foreach my $line (@lines) {
|
||||
if ($line !~ /^$regex/) {
|
||||
next;
|
||||
}
|
||||
foreach my $line (@lines) {
|
||||
if ($line !~ /^$regex/) {
|
||||
next;
|
||||
}
|
||||
|
||||
my $entry = $1;
|
||||
my $entry = $1;
|
||||
|
||||
if ($parsing_main_options) {
|
||||
$entry .= '=-';
|
||||
}
|
||||
if ($parsing_main_options) {
|
||||
$entry .= '=-';
|
||||
}
|
||||
|
||||
if (defined $2) {
|
||||
my $desc = $2;
|
||||
$desc =~ s/\:/\\:/g;
|
||||
if (defined $2) {
|
||||
my $desc = $2;
|
||||
$desc =~ s/\:/\\:/g;
|
||||
|
||||
$entry .= ':' . $desc;
|
||||
}
|
||||
$entry .= ':' . $desc;
|
||||
}
|
||||
|
||||
if ($parsing_main_options) {
|
||||
$entry .= ':';
|
||||
if ($parsing_main_options) {
|
||||
$entry .= ':';
|
||||
|
||||
$entry .= '->ao' if ($1 eq '--ao');
|
||||
$entry .= '->vo' if ($1 eq '--vo');
|
||||
$entry .= '->af' if ($1 eq '--af');
|
||||
$entry .= '->vf' if ($1 eq '--vf');
|
||||
$entry .= '->profiles' if ($1 eq '--profile');
|
||||
$entry .= '->profile' if ($1 eq '--show-profile');
|
||||
$entry .= '->files' if ($line =~ /\[file\]/);
|
||||
}
|
||||
$entry .= '->ao' if ($1 eq '--ao');
|
||||
$entry .= '->vo' if ($1 eq '--vo');
|
||||
$entry .= '->af' if ($1 eq '--af');
|
||||
$entry .= '->vf' if ($1 eq '--vf');
|
||||
}
|
||||
|
||||
push @list, $entry
|
||||
}
|
||||
push @list, $entry
|
||||
}
|
||||
|
||||
if ($parsing_main_options) {
|
||||
@list = sort {
|
||||
$a =~ /(.*?)\:/; my $ma = $1;
|
||||
$b =~ /(.*?)\:/; my $mb = $1;
|
||||
if ($parsing_main_options) {
|
||||
@list = sort {
|
||||
$a =~ /(.*?)\:/; my $ma = $1;
|
||||
$b =~ /(.*?)\:/; my $mb = $1;
|
||||
|
||||
length($mb) <=> length($ma)
|
||||
} @list;
|
||||
}
|
||||
length($mb) <=> length($ma)
|
||||
} @list;
|
||||
}
|
||||
|
||||
return @list;
|
||||
return @list;
|
||||
}
|
||||
+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()),
|
||||
|
||||
+3
-6
@@ -20,7 +20,7 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include "misc/bstr.h"
|
||||
#include "bstr/bstr.h"
|
||||
|
||||
#define MP_NUM_CHANNELS 8
|
||||
|
||||
@@ -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_audio)(struct dec_audio *da, struct mp_audio *buffer, int maxlen);
|
||||
};
|
||||
|
||||
enum ad_ctrl {
|
||||
|
||||
+196
-61
@@ -25,7 +25,6 @@
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/intreadwrite.h>
|
||||
|
||||
#include "talloc.h"
|
||||
|
||||
@@ -34,27 +33,30 @@
|
||||
#include "common/codecs.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/options.h"
|
||||
#include "common/av_opts.h"
|
||||
|
||||
#include "ad.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
#include "compat/libav.h"
|
||||
|
||||
struct priv {
|
||||
AVCodecContext *avctx;
|
||||
AVFrame *avframe;
|
||||
struct mp_audio frame;
|
||||
bool force_channel_map;
|
||||
struct demux_packet *packet;
|
||||
uint32_t skip_samples;
|
||||
};
|
||||
|
||||
static void uninit(struct dec_audio *da);
|
||||
static int decode_new_packet(struct dec_audio *da);
|
||||
|
||||
#define OPT_BASE_STRUCT struct ad_lavc_params
|
||||
struct ad_lavc_params {
|
||||
float ac3drc;
|
||||
int downmix;
|
||||
int threads;
|
||||
char **avopts;
|
||||
char *avopt;
|
||||
};
|
||||
|
||||
const struct m_sub_options ad_lavc_conf = {
|
||||
@@ -62,7 +64,7 @@ const struct m_sub_options ad_lavc_conf = {
|
||||
OPT_FLOATRANGE("ac3drc", ac3drc, 0, 0, 2),
|
||||
OPT_FLAG("downmix", downmix, 0),
|
||||
OPT_INTRANGE("threads", threads, 0, 1, 16),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
OPT_STRING("o", avopt, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct ad_lavc_params),
|
||||
@@ -73,6 +75,121 @@ const struct m_sub_options ad_lavc_conf = {
|
||||
},
|
||||
};
|
||||
|
||||
struct pcm_map
|
||||
{
|
||||
int tag;
|
||||
const char *codecs[6]; // {any, 1byte, 2bytes, 3bytes, 4bytes, 8bytes}
|
||||
};
|
||||
|
||||
// NOTE: these are needed to make rawaudio with demux_mkv work.
|
||||
static const struct pcm_map tag_map[] = {
|
||||
// Microsoft PCM
|
||||
{0x0, {NULL, "pcm_u8", "pcm_s16le", "pcm_s24le", "pcm_s32le"}},
|
||||
{0x1, {NULL, "pcm_u8", "pcm_s16le", "pcm_s24le", "pcm_s32le"}},
|
||||
// MS PCM, Extended
|
||||
{0xfffe, {NULL, "pcm_u8", "pcm_s16le", "pcm_s24le", "pcm_s32le"}},
|
||||
// IEEE float
|
||||
{0x3, {"pcm_f32le", [5] = "pcm_f64le"}},
|
||||
// 'raw '
|
||||
{0x20776172, {"pcm_s16be", [1] = "pcm_u8"}},
|
||||
// 'twos', used by demux_mkv.c internally
|
||||
{MKTAG('t', 'w', 'o', 's'),
|
||||
{NULL, "pcm_s8", "pcm_s16be", "pcm_s24be", "pcm_s32be"}},
|
||||
{-1},
|
||||
};
|
||||
|
||||
// For demux_rawaudio.c; needed because ffmpeg doesn't have these sample
|
||||
// formats natively.
|
||||
static const struct pcm_map af_map[] = {
|
||||
{AF_FORMAT_U8, {"pcm_u8"}},
|
||||
{AF_FORMAT_S8, {"pcm_u8"}},
|
||||
{AF_FORMAT_U16_LE, {"pcm_u16le"}},
|
||||
{AF_FORMAT_U16_BE, {"pcm_u16be"}},
|
||||
{AF_FORMAT_S16_LE, {"pcm_s16le"}},
|
||||
{AF_FORMAT_S16_BE, {"pcm_s16be"}},
|
||||
{AF_FORMAT_U24_LE, {"pcm_u24le"}},
|
||||
{AF_FORMAT_U24_BE, {"pcm_u24be"}},
|
||||
{AF_FORMAT_S24_LE, {"pcm_s24le"}},
|
||||
{AF_FORMAT_S24_BE, {"pcm_s24be"}},
|
||||
{AF_FORMAT_U32_LE, {"pcm_u32le"}},
|
||||
{AF_FORMAT_U32_BE, {"pcm_u32be"}},
|
||||
{AF_FORMAT_S32_LE, {"pcm_s32le"}},
|
||||
{AF_FORMAT_S32_BE, {"pcm_s32be"}},
|
||||
{AF_FORMAT_FLOAT_LE, {"pcm_f32le"}},
|
||||
{AF_FORMAT_FLOAT_BE, {"pcm_f32be"}},
|
||||
{AF_FORMAT_DOUBLE_LE, {"pcm_f64le"}},
|
||||
{AF_FORMAT_DOUBLE_BE, {"pcm_f64be"}},
|
||||
{-1},
|
||||
};
|
||||
|
||||
static const char *find_pcm_decoder(const struct pcm_map *map, int format,
|
||||
int bits_per_sample)
|
||||
{
|
||||
int bytes = (bits_per_sample + 7) / 8;
|
||||
if (bytes == 8)
|
||||
bytes = 5; // 64 bit entry
|
||||
for (int n = 0; map[n].tag != -1; n++) {
|
||||
const struct pcm_map *entry = &map[n];
|
||||
if (entry->tag == format) {
|
||||
const char *dec = NULL;
|
||||
if (bytes >= 1 && bytes <= 5)
|
||||
dec = entry->codecs[bytes];
|
||||
if (!dec)
|
||||
dec = entry->codecs[0];
|
||||
if (dec)
|
||||
return dec;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int setup_format(struct dec_audio *da)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *lavc_context = priv->avctx;
|
||||
struct sh_audio *sh_audio = da->header->audio;
|
||||
|
||||
// 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;
|
||||
if (!da->decoded.rate && sh_audio->wf) {
|
||||
// If not set, try container samplerate.
|
||||
// (Maybe this can't happen, and it's an artifact from the past.)
|
||||
da->decoded.rate = sh_audio->wf->nSamplesPerSec;
|
||||
MP_WARN(da, "using container rate.\n");
|
||||
}
|
||||
|
||||
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) {
|
||||
if (lavc_chmap.num == sh_audio->channels.num)
|
||||
lavc_chmap = sh_audio->channels;
|
||||
}
|
||||
mp_audio_set_channels(&da->decoded, &lavc_chmap);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void set_from_wf(AVCodecContext *avctx, MP_WAVEFORMATEX *wf)
|
||||
{
|
||||
avctx->channels = wf->nChannels;
|
||||
avctx->sample_rate = wf->nSamplesPerSec;
|
||||
avctx->bit_rate = wf->nAvgBytesPerSec * 8;
|
||||
avctx->block_align = wf->nBlockAlign;
|
||||
avctx->bits_per_coded_sample = wf->wBitsPerSample;
|
||||
|
||||
if (wf->cbSize > 0)
|
||||
mp_lavc_set_extradata(avctx, wf + 1, wf->cbSize);
|
||||
}
|
||||
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
{
|
||||
struct MPOpts *mpopts = da->opts;
|
||||
@@ -85,7 +202,13 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
struct priv *ctx = talloc_zero(NULL, struct priv);
|
||||
da->priv = ctx;
|
||||
|
||||
ctx->force_channel_map = sh_audio->force_channels;
|
||||
if (sh_audio->wf && strcmp(decoder, "pcm") == 0) {
|
||||
decoder = find_pcm_decoder(tag_map, sh->format,
|
||||
sh_audio->wf->wBitsPerSample);
|
||||
} else if (sh_audio->wf && strcmp(decoder, "mp-pcm") == 0) {
|
||||
decoder = find_pcm_decoder(af_map, sh->format, 0);
|
||||
ctx->force_channel_map = true;
|
||||
}
|
||||
|
||||
lavc_codec = avcodec_find_decoder_by_name(decoder);
|
||||
if (!lavc_codec) {
|
||||
@@ -110,23 +233,23 @@ 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);
|
||||
if (opts->avopt) {
|
||||
if (parse_avopts(lavc_context, opts->avopt) < 0) {
|
||||
MP_ERR(da, "setting AVOptions '%s' failed.\n", opts->avopt);
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
lavc_context->codec_tag = sh->format;
|
||||
lavc_context->sample_rate = sh_audio->samplerate;
|
||||
lavc_context->bit_rate = sh_audio->bitrate;
|
||||
lavc_context->block_align = sh_audio->block_align;
|
||||
lavc_context->bits_per_coded_sample = sh_audio->bits_per_coded_sample;
|
||||
lavc_context->channels = sh_audio->channels.num;
|
||||
if (!mp_chmap_is_unknown(&sh_audio->channels))
|
||||
lavc_context->channel_layout = mp_chmap_to_lavc(&sh_audio->channels);
|
||||
lavc_context->channel_layout = mp_chmap_to_lavc(&sh_audio->channels);
|
||||
|
||||
// demux_mkv
|
||||
if (sh_audio->wf)
|
||||
set_from_wf(lavc_context, sh_audio->wf);
|
||||
|
||||
// demux_mkv, demux_mpg
|
||||
if (sh_audio->codecdata_len && sh_audio->codecdata &&
|
||||
!lavc_context->extradata) {
|
||||
mp_lavc_set_extradata(lavc_context, sh_audio->codecdata,
|
||||
@@ -144,9 +267,27 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
MP_VERBOSE(da, "INFO: libavcodec \"%s\" init OK!\n",
|
||||
lavc_codec->name);
|
||||
|
||||
// Decode at least 1 sample: (to get header filled)
|
||||
for (int tries = 1; ; tries++) {
|
||||
int x = decode_new_packet(da);
|
||||
if (x >= 0 && ctx->frame.samples > 0) {
|
||||
MP_VERBOSE(da, "Initial decode succeeded after %d packets.\n", tries);
|
||||
break;
|
||||
}
|
||||
if (tries >= 50) {
|
||||
MP_ERR(da, "initial decode failed\n");
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (lavc_context->bit_rate != 0)
|
||||
da->bitrate = lavc_context->bit_rate;
|
||||
else if (sh_audio->wf && sh_audio->wf->nAvgBytesPerSec)
|
||||
da->bitrate = sh_audio->wf->nAvgBytesPerSec * 8;
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -173,24 +314,24 @@ static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESET:
|
||||
avcodec_flush_buffers(ctx->avctx);
|
||||
ctx->frame.samples = 0;
|
||||
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_new_packet(struct dec_audio *da)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
|
||||
priv->frame.samples = 0;
|
||||
|
||||
struct demux_packet *mpkt = priv->packet;
|
||||
if (!mpkt) {
|
||||
if (demux_read_packet_async(da->header, &mpkt) == 0)
|
||||
return AD_WAIT;
|
||||
}
|
||||
if (!mpkt)
|
||||
mpkt = demux_read_packet(da->header);
|
||||
|
||||
priv->packet = talloc_steal(priv, mpkt);
|
||||
|
||||
@@ -223,14 +364,22 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
}
|
||||
// LATM may need many packets to find mux info
|
||||
if (ret == AVERROR(EAGAIN))
|
||||
return AD_OK;
|
||||
return 0;
|
||||
}
|
||||
if (ret < 0) {
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
return AD_ERR;
|
||||
MP_VERBOSE(da, "lavc_audio: error\n");
|
||||
return -1;
|
||||
}
|
||||
if (!got_frame)
|
||||
return mpkt ? AD_OK : AD_EOF;
|
||||
return mpkt ? 0 : -1; // -1: eof
|
||||
|
||||
if (setup_format(da) < 0)
|
||||
return -1;
|
||||
|
||||
priv->frame.samples = priv->avframe->nb_samples;
|
||||
mp_audio_copy_config(&priv->frame, &da->decoded);
|
||||
for (int n = 0; n < priv->frame.num_planes; n++)
|
||||
priv->frame.planes[n] = priv->avframe->data[n];
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, NULL);
|
||||
if (out_pts != MP_NOPTS_VALUE) {
|
||||
@@ -238,49 +387,35 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
|
||||
struct mp_audio *mpframe = mp_audio_from_avframe(priv->avframe);
|
||||
if (!mpframe)
|
||||
return AD_ERR;
|
||||
MP_DBG(da, "Decoded %d -> %d samples\n", in_len,
|
||||
priv->frame.samples);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
static int decode_audio(struct dec_audio *da, struct mp_audio *buffer, int maxlen)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
|
||||
if (!priv->frame.samples) {
|
||||
if (decode_new_packet(da) < 0)
|
||||
return -1;
|
||||
}
|
||||
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
|
||||
if (!mp_audio_config_equals(buffer, &priv->frame))
|
||||
return 0;
|
||||
|
||||
*out = mpframe;
|
||||
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
MP_DBG(da, "Decoded %d -> %d samples\n", in_len, mpframe->samples);
|
||||
buffer->samples = MPMIN(priv->frame.samples, maxlen);
|
||||
mp_audio_copy(buffer, 0, &priv->frame, 0, buffer->samples);
|
||||
mp_audio_skip_samples(&priv->frame, buffer->samples);
|
||||
da->pts_offset += buffer->samples;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void add_decoders(struct mp_decoder_list *list)
|
||||
{
|
||||
mp_add_lavc_decoders(list, AVMEDIA_TYPE_AUDIO);
|
||||
mp_add_decoder(list, "lavc", "pcm", "pcm", "Raw PCM");
|
||||
mp_add_decoder(list, "lavc", "mp-pcm", "mp-pcm", "Raw PCM");
|
||||
}
|
||||
|
||||
const struct ad_functions ad_lavc = {
|
||||
@@ -289,5 +424,5 @@ const struct ad_functions ad_lavc = {
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.decode_packet = decode_packet,
|
||||
.decode_audio = decode_audio,
|
||||
};
|
||||
+148
-75
@@ -35,7 +35,9 @@
|
||||
|
||||
struct ad_mpg123_context {
|
||||
mpg123_handle *handle;
|
||||
bool new_format;
|
||||
int sample_size;
|
||||
bool need_data;
|
||||
/* Running mean for bit rate, stream length estimation. */
|
||||
float mean_rate;
|
||||
unsigned int mean_count;
|
||||
@@ -43,7 +45,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)
|
||||
@@ -57,7 +58,7 @@ static void uninit(struct dec_audio *da)
|
||||
|
||||
/* This initializes libmpg123 and prepares the handle, including funky
|
||||
* parameters. */
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
static int preinit(struct dec_audio *da)
|
||||
{
|
||||
int err;
|
||||
struct ad_mpg123_context *con;
|
||||
@@ -110,18 +111,15 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
* We need at least 1152 samples. dec_audio.c normally guarantees this. */
|
||||
mpg123_param(con->handle, MPG123_REMOVE_FLAGS, MPG123_AUTO_RESAMPLE, 0.);
|
||||
|
||||
err = mpg123_open_feed(con->handle);
|
||||
if (err != MPG123_OK)
|
||||
goto bad_end;
|
||||
|
||||
return 1;
|
||||
|
||||
bad_end:
|
||||
if (!con->handle) {
|
||||
MP_ERR(da, "mpg123 preinit error: %s\n", mpg123_plain_strerror(err));
|
||||
} else {
|
||||
MP_ERR(da, "mpg123 preinit error: %s\n", mpg123_strerror(con->handle));
|
||||
}
|
||||
if (!con->handle)
|
||||
MP_ERR(da, "mpg123 preinit error: %s\n",
|
||||
mpg123_plain_strerror(err));
|
||||
else
|
||||
MP_ERR(da, "mpg123 preinit error: %s\n",
|
||||
mpg123_strerror(con->handle));
|
||||
|
||||
uninit(da);
|
||||
return 0;
|
||||
@@ -142,6 +140,111 @@ static int mpg123_format_to_af(int mpg123_encoding)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* libmpg123 has a new format ready; query and store, return return value
|
||||
of mpg123_getformat() */
|
||||
static int set_format(struct dec_audio *da)
|
||||
{
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
int ret;
|
||||
long rate;
|
||||
int channels;
|
||||
int encoding;
|
||||
ret = mpg123_getformat(con->handle, &rate, &channels, &encoding);
|
||||
if (ret == MPG123_OK) {
|
||||
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(&da->decoded, af);
|
||||
con->sample_size = channels * af_fmt2bps(af);
|
||||
con->new_format = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int feed_new_packet(struct dec_audio *da)
|
||||
{
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
int ret;
|
||||
|
||||
struct demux_packet *pkt = demux_read_packet(da->header);
|
||||
if (!pkt)
|
||||
return -1; /* EOF. */
|
||||
|
||||
/* Next bytes from that presentation time. */
|
||||
if (pkt->pts != MP_NOPTS_VALUE) {
|
||||
da->pts = pkt->pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
|
||||
/* Have to use mpg123_feed() to avoid decoding here. */
|
||||
ret = mpg123_feed(con->handle, pkt->buffer, pkt->len);
|
||||
talloc_free(pkt);
|
||||
|
||||
if (ret == MPG123_ERR)
|
||||
return -1;
|
||||
|
||||
if (ret == MPG123_NEW_FORMAT)
|
||||
con->new_format = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Now we really start accessing some data and determining file format.
|
||||
* Format now is allowed to change on-the-fly. Here is the only point
|
||||
* that has MPlayer react to errors. We have to pray that exceptional
|
||||
* erros in other places simply cannot occur. */
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
{
|
||||
if (!preinit(da))
|
||||
return 0;
|
||||
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
int ret;
|
||||
|
||||
ret = mpg123_open_feed(con->handle);
|
||||
if (ret != MPG123_OK)
|
||||
goto fail;
|
||||
|
||||
for (int n = 0; ; n++) {
|
||||
if (feed_new_packet(da) < 0) {
|
||||
ret = MPG123_NEED_MORE;
|
||||
goto fail;
|
||||
}
|
||||
size_t got_now = 0;
|
||||
ret = mpg123_decode_frame(con->handle, NULL, NULL, &got_now);
|
||||
if (ret == MPG123_OK || ret == MPG123_NEW_FORMAT) {
|
||||
ret = set_format(da);
|
||||
if (ret == MPG123_OK)
|
||||
break;
|
||||
}
|
||||
if (ret != MPG123_NEED_MORE)
|
||||
goto fail;
|
||||
// max. 16 retries (randomly chosen number)
|
||||
if (n > 16) {
|
||||
ret = MPG123_NEED_MORE;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
if (ret == MPG123_NEED_MORE) {
|
||||
MP_ERR(da, "Could not find mp3 stream.\n");
|
||||
} else {
|
||||
MP_ERR(da, "mpg123 init error: %s\n",
|
||||
mpg123_strerror(con->handle));
|
||||
}
|
||||
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Compute bitrate from frame size. */
|
||||
static int compute_bitrate(struct mpg123_frameinfo *i)
|
||||
{
|
||||
@@ -187,87 +290,57 @@ static void update_info(struct dec_audio *da)
|
||||
}
|
||||
}
|
||||
|
||||
/* libmpg123 has a new format ready; query and store, return return value
|
||||
of mpg123_getformat() */
|
||||
static int set_format(struct dec_audio *da)
|
||||
static int decode_audio(struct dec_audio *da, struct mp_audio *buffer, int maxlen)
|
||||
{
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
void *buf = buffer->planes[0];
|
||||
int ret;
|
||||
long rate;
|
||||
int channels;
|
||||
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;
|
||||
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;
|
||||
|
||||
if (con->new_format) {
|
||||
ret = set_format(da);
|
||||
if (ret == MPG123_OK) {
|
||||
return 0; // let caller handle format change
|
||||
} else if (ret == MPG123_NEED_MORE) {
|
||||
con->need_data = true;
|
||||
} else {
|
||||
goto mpg123_fail;
|
||||
}
|
||||
mp_audio_set_format(&con->frame, af);
|
||||
con->sample_size = channels * af_fmt2bps(af);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
{
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
int ret;
|
||||
|
||||
struct demux_packet *pkt;
|
||||
if (demux_read_packet_async(da->header, &pkt) == 0)
|
||||
return AD_WAIT;
|
||||
if (!pkt)
|
||||
return AD_EOF;
|
||||
|
||||
/* Next bytes from that presentation time. */
|
||||
if (pkt->pts != MP_NOPTS_VALUE) {
|
||||
da->pts = pkt->pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
|
||||
/* Have to use mpg123_feed() to avoid decoding here. */
|
||||
ret = mpg123_feed(con->handle, pkt->buffer, pkt->len);
|
||||
talloc_free(pkt);
|
||||
if (con->need_data) {
|
||||
if (feed_new_packet(da) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ret != MPG123_OK)
|
||||
goto mpg123_fail;
|
||||
|
||||
unsigned char *audio = NULL;
|
||||
size_t bytes = 0;
|
||||
ret = mpg123_decode_frame(con->handle, NULL, &audio, &bytes);
|
||||
|
||||
if (ret == MPG123_NEED_MORE)
|
||||
if (!mp_audio_config_equals(&da->decoded, buffer))
|
||||
return 0;
|
||||
|
||||
if (ret != MPG123_OK && ret != MPG123_DONE && ret != MPG123_NEW_FORMAT)
|
||||
goto mpg123_fail;
|
||||
|
||||
ret = set_format(da);
|
||||
size_t got_now = 0;
|
||||
ret = mpg123_replace_buffer(con->handle, buf, maxlen * con->sample_size);
|
||||
if (ret != MPG123_OK)
|
||||
goto mpg123_fail;
|
||||
|
||||
if (con->sample_size < 1) {
|
||||
MP_ERR(da, "no sample size\n");
|
||||
return AD_ERR;
|
||||
ret = mpg123_decode_frame(con->handle, NULL, NULL, &got_now);
|
||||
|
||||
int got_samples = got_now / con->sample_size;
|
||||
buffer->samples += got_samples;
|
||||
da->pts_offset += got_samples;
|
||||
|
||||
if (ret == MPG123_NEW_FORMAT) {
|
||||
con->new_format = true;
|
||||
} else if (ret == MPG123_NEED_MORE) {
|
||||
con->need_data = true;
|
||||
} else if (ret != MPG123_OK && ret != MPG123_DONE) {
|
||||
goto mpg123_fail;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
update_info(da);
|
||||
return 0;
|
||||
|
||||
mpg123_fail:
|
||||
MP_ERR(da, "mpg123 decoding error: %s\n", mpg123_strerror(con->handle));
|
||||
return AD_ERR;
|
||||
MP_ERR(da, "mpg123 decoding error: %s\n",
|
||||
mpg123_strerror(con->handle));
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
@@ -300,5 +373,5 @@ const struct ad_functions ad_mpg123 = {
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.decode_packet = decode_packet,
|
||||
.decode_audio = decode_audio,
|
||||
};
|
||||
+29
-29
@@ -38,16 +38,16 @@ struct spdifContext {
|
||||
AVFormatContext *lavf_ctx;
|
||||
int iec61937_packet_size;
|
||||
int out_buffer_len;
|
||||
uint8_t out_buffer[OUTBUF_SIZE];
|
||||
int out_buffer_size;
|
||||
uint8_t *out_buffer;
|
||||
bool need_close;
|
||||
struct mp_audio fmt;
|
||||
};
|
||||
|
||||
static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
{
|
||||
struct spdifContext *ctx = p;
|
||||
|
||||
int buffer_left = OUTBUF_SIZE - ctx->out_buffer_len;
|
||||
int buffer_left = ctx->out_buffer_size - ctx->out_buffer_len;
|
||||
if (buf_size > buffer_left) {
|
||||
MP_ERR(ctx, "spdif packet too large.\n");
|
||||
buf_size = buffer_left;
|
||||
@@ -118,13 +118,13 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
switch (stream->codec->codec_id) {
|
||||
case AV_CODEC_ID_AAC:
|
||||
spdif_ctx->iec61937_packet_size = 16384;
|
||||
sample_format = AF_FORMAT_S_AAC;
|
||||
sample_format = AF_FORMAT_IEC61937_LE;
|
||||
samplerate = 48000;
|
||||
num_channels = 2;
|
||||
break;
|
||||
case AV_CODEC_ID_AC3:
|
||||
spdif_ctx->iec61937_packet_size = 6144;
|
||||
sample_format = AF_FORMAT_S_AC3;
|
||||
sample_format = AF_FORMAT_AC3_LE;
|
||||
samplerate = 48000;
|
||||
num_channels = 2;
|
||||
break;
|
||||
@@ -132,40 +132,40 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
if (da->opts->dtshd) {
|
||||
av_dict_set(&format_opts, "dtshd_rate", "768000", 0); // 4*192000
|
||||
spdif_ctx->iec61937_packet_size = 32768;
|
||||
sample_format = AF_FORMAT_S_DTSHD;
|
||||
sample_format = AF_FORMAT_IEC61937_LE;
|
||||
samplerate = 192000;
|
||||
num_channels = 2*4;
|
||||
} else {
|
||||
spdif_ctx->iec61937_packet_size = 32768;
|
||||
sample_format = AF_FORMAT_S_DTS;
|
||||
sample_format = AF_FORMAT_AC3_LE;
|
||||
samplerate = 48000;
|
||||
num_channels = 2;
|
||||
}
|
||||
break;
|
||||
case AV_CODEC_ID_EAC3:
|
||||
spdif_ctx->iec61937_packet_size = 24576;
|
||||
sample_format = AF_FORMAT_S_EAC3;
|
||||
sample_format = AF_FORMAT_IEC61937_LE;
|
||||
samplerate = 192000;
|
||||
num_channels = 2;
|
||||
break;
|
||||
case AV_CODEC_ID_MP3:
|
||||
spdif_ctx->iec61937_packet_size = 4608;
|
||||
sample_format = AF_FORMAT_S_MP3;
|
||||
sample_format = AF_FORMAT_MPEG2;
|
||||
samplerate = 48000;
|
||||
num_channels = 2;
|
||||
break;
|
||||
case AV_CODEC_ID_TRUEHD:
|
||||
spdif_ctx->iec61937_packet_size = 61440;
|
||||
sample_format = AF_FORMAT_S_TRUEHD;
|
||||
sample_format = AF_FORMAT_IEC61937_LE;
|
||||
samplerate = 192000;
|
||||
num_channels = 8;
|
||||
break;
|
||||
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,19 +183,24 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
static int decode_audio(struct dec_audio *da, struct mp_audio *buffer, int maxlen)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
|
||||
|
||||
int sstride = 2 * da->decoded.channels.num;
|
||||
assert(sstride == buffer->sstride);
|
||||
|
||||
if (maxlen * sstride < spdif_ctx->iec61937_packet_size)
|
||||
return 0;
|
||||
|
||||
spdif_ctx->out_buffer_len = 0;
|
||||
spdif_ctx->out_buffer_size = maxlen * sstride;
|
||||
spdif_ctx->out_buffer = buffer->planes[0];
|
||||
|
||||
struct demux_packet *mpkt;
|
||||
if (demux_read_packet_async(da->header, &mpkt) == 0)
|
||||
return AD_WAIT;
|
||||
|
||||
struct demux_packet *mpkt = demux_read_packet(da->header);
|
||||
if (!mpkt)
|
||||
return AD_EOF;
|
||||
return -1;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
@@ -206,17 +211,12 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
int ret = av_write_frame(lavf_ctx, &pkt);
|
||||
talloc_free(mpkt);
|
||||
avio_flush(lavf_ctx->pb);
|
||||
buffer->samples = spdif_ctx->out_buffer_len / sstride;
|
||||
da->pts_offset += buffer->samples;
|
||||
talloc_free(mpkt);
|
||||
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);
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -253,5 +253,5 @@ const struct ad_functions ad_spdif = {
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.decode_packet = decode_packet,
|
||||
.decode_audio = decode_audio,
|
||||
};
|
||||
+174
-57
@@ -28,7 +28,7 @@
|
||||
#include "config.h"
|
||||
#include "common/codecs.h"
|
||||
#include "common/msg.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "bstr/bstr.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
#include "demux/demux.h"
|
||||
@@ -48,14 +48,35 @@ extern const struct ad_functions ad_lavc;
|
||||
extern const struct ad_functions ad_spdif;
|
||||
|
||||
static const struct ad_functions * const ad_drivers[] = {
|
||||
&ad_lavc,
|
||||
#if HAVE_MPG123
|
||||
&ad_mpg123,
|
||||
#endif
|
||||
&ad_lavc,
|
||||
&ad_spdif,
|
||||
NULL
|
||||
};
|
||||
|
||||
// ad_mpg123 needs to be able to decode 1152 samples at once
|
||||
// ad_spdif needs up to 8192
|
||||
#define DECODE_MAX_UNIT MPMAX(8192, 1152)
|
||||
|
||||
// At least 8192 samples, plus hack for ad_mpg123 and ad_spdif
|
||||
#define DECODE_BUFFER_SAMPLES (8192 + DECODE_MAX_UNIT)
|
||||
|
||||
// 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);
|
||||
mp_audio_buffer_preallocate_min(da->decode_buffer, DECODE_BUFFER_SAMPLES);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void uninit_decoder(struct dec_audio *d_audio)
|
||||
{
|
||||
if (d_audio->ad_driver) {
|
||||
@@ -76,6 +97,12 @@ static int init_audio_codec(struct dec_audio *d_audio, const char *decoder)
|
||||
return 0;
|
||||
}
|
||||
|
||||
d_audio->decode_buffer = mp_audio_buffer_create(NULL);
|
||||
if (!reinit_audio_buffer(d_audio)) {
|
||||
uninit_decoder(d_audio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -137,6 +164,9 @@ int audio_init_best_codec(struct dec_audio *d_audio, char *audio_decoders)
|
||||
talloc_asprintf(d_audio, "%s [%s:%s]", decoder->desc, decoder->family,
|
||||
decoder->decoder);
|
||||
MP_VERBOSE(d_audio, "Selected audio codec: %s\n", d_audio->decoder_desc);
|
||||
MP_VERBOSE(d_audio, "AUDIO: %d Hz, %d ch, %s\n",
|
||||
d_audio->decoded.rate, d_audio->decoded.channels.num,
|
||||
af_fmt_to_str(d_audio->decoded.format));
|
||||
} else {
|
||||
MP_ERR(d_audio, "Failed to initialize an audio decoder for codec '%s'.\n",
|
||||
d_audio->header->codec ? d_audio->header->codec : "<unknown>");
|
||||
@@ -150,82 +180,170 @@ 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);
|
||||
}
|
||||
|
||||
/* 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)
|
||||
|
||||
int audio_init_filters(struct dec_audio *d_audio, int in_samplerate,
|
||||
int *out_samplerate, struct mp_chmap *out_channels,
|
||||
int *out_format)
|
||||
{
|
||||
while (!da->waiting) {
|
||||
int ret = da->ad_driver->decode_packet(da, &da->waiting);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
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->metadata = d_audio->metadata;
|
||||
|
||||
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 :(
|
||||
}
|
||||
talloc_steal(da, da->waiting);
|
||||
da->decode_format = *da->waiting;
|
||||
return mp_audio_config_valid(da->waiting) ? AD_OK : AD_ERR;
|
||||
|
||||
*out_samplerate = afs->output.rate;
|
||||
*out_channels = afs->output.channels;
|
||||
*out_format = afs->output.format;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
struct mp_audio config;
|
||||
mp_audio_buffer_get_format(da->decode_buffer, &config);
|
||||
|
||||
while (mp_audio_buffer_samples(da->decode_buffer) < len) {
|
||||
int maxlen = mp_audio_buffer_get_write_available(da->decode_buffer);
|
||||
if (maxlen < DECODE_MAX_UNIT)
|
||||
break;
|
||||
struct mp_audio buffer;
|
||||
mp_audio_buffer_get_write_buffer(da->decode_buffer, maxlen, &buffer);
|
||||
buffer.samples = 0;
|
||||
error = da->ad_driver->decode_audio(da, &buffer, maxlen);
|
||||
if (error < 0)
|
||||
break;
|
||||
// Commit the data just read as valid data
|
||||
mp_audio_buffer_finish_write(da->decode_buffer, buffer.samples);
|
||||
// Format change
|
||||
if (!mp_audio_config_equals(&da->decoded, &config)) {
|
||||
// If there are still samples left in the buffer, let them drain
|
||||
// first, and don't signal a format change to the caller yet.
|
||||
if (mp_audio_buffer_samples(da->decode_buffer) > 0)
|
||||
break;
|
||||
error = -2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = filter_data.samples == 0 && error < 0;
|
||||
|
||||
if (af_filter(da->afilter, &filter_data, eof ? AF_FILTER_FLAG_EOF : 0) < 0)
|
||||
return -1;
|
||||
|
||||
mp_audio_buffer_append(outbuf, &filter_data);
|
||||
if (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);
|
||||
|
||||
// Assume the filter chain is drained from old data at this point.
|
||||
// (If not, the remaining old data is discarded.)
|
||||
if (error == -2) {
|
||||
if (!reinit_audio_buffer(da))
|
||||
error = -1; // switch to invalid format
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Try to get at least minsamples decoded+filtered samples in outbuf
|
||||
* (total length including possible existing data).
|
||||
* Return 0 on success, or negative AD_* error code.
|
||||
* Return 0 on success, -1 on error/EOF (not distinguidaed).
|
||||
* 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)
|
||||
return AD_ERR;
|
||||
// Indicates that a filter seems to be buffering large amounts of data
|
||||
int huge_filter_buffer = 0;
|
||||
// Decoded audio must be cut at boundaries of this many samples
|
||||
// (Note: the reason for this is unknown, possibly a refactoring artifact)
|
||||
int unitsize = 16;
|
||||
|
||||
MP_STATS(da, "start audio");
|
||||
/* 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
|
||||
decsamples += unitsize << 5;
|
||||
|
||||
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 +351,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);
|
||||
}
|
||||
+10
-13
@@ -33,13 +33,15 @@ 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;
|
||||
int init_retries;
|
||||
struct mp_audio_pool *pool;
|
||||
struct mp_audio decode_format;
|
||||
struct mp_audio *waiting; // used on format-change
|
||||
struct mp_tags *metadata;
|
||||
struct replaygain_data *replaygain_data;
|
||||
// set by decoder
|
||||
struct mp_audio decoded; // format of decoded audio (no data, temporarily
|
||||
// different from decode_buffer during format
|
||||
// changes)
|
||||
int bitrate; // input bitrate, can change with VBR sources
|
||||
// last known pts value in output from decoder
|
||||
double pts;
|
||||
@@ -49,20 +51,15 @@ struct dec_audio {
|
||||
void *priv;
|
||||
};
|
||||
|
||||
enum {
|
||||
AD_OK = 0,
|
||||
AD_ERR = -1,
|
||||
AD_EOF = -2,
|
||||
AD_NEW_FMT = -3,
|
||||
AD_WAIT = -4,
|
||||
};
|
||||
|
||||
struct mp_decoder_list *audio_decoder_list(void);
|
||||
int audio_init_best_codec(struct dec_audio *d_audio, char *audio_decoders);
|
||||
int audio_decode(struct dec_audio *d_audio, struct mp_audio_buffer *outbuf,
|
||||
int minsamples);
|
||||
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 */
|
||||
+46
-98
@@ -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;
|
||||
@@ -200,6 +204,7 @@ static struct af_instance *af_create(struct af_stream *s, char *name,
|
||||
.mul = 1,
|
||||
.data = talloc_zero(af, struct mp_audio),
|
||||
.log = mp_log_new(af, s->log, name),
|
||||
.metadata = s->metadata,
|
||||
.replaygain_data = s->replaygain_data,
|
||||
};
|
||||
struct m_config *config = m_config_from_obj_desc(af, s->log, &desc);
|
||||
@@ -306,18 +311,23 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
|
||||
struct af_instance *af = s->first;
|
||||
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));
|
||||
MP_MSG(s, msg_level, " [%s] ", af->info->name);
|
||||
if (af->data) {
|
||||
char *info = mp_audio_config_to_str(af->data);
|
||||
MP_MSG(s, msg_level, "%s", info);
|
||||
talloc_free(info);
|
||||
}
|
||||
if (af == at)
|
||||
mp_snprintf_cat(b, sizeof(b), " <-");
|
||||
MP_MSG(s, msg_level, "%s\n", b);
|
||||
MP_MSG(s, msg_level, " <-");
|
||||
MP_MSG(s, msg_level, "\n");
|
||||
|
||||
af = af->next;
|
||||
}
|
||||
|
||||
MP_MSG(s, msg_level, " [ao] %s\n", mp_audio_config_to_str(&s->output));
|
||||
MP_MSG(s, msg_level, " [ao] ");
|
||||
char *info = mp_audio_config_to_str(&s->output);
|
||||
MP_MSG(s, msg_level, "%s\n", info);
|
||||
talloc_free(info);
|
||||
}
|
||||
|
||||
static int af_count_filters(struct af_stream *s)
|
||||
@@ -514,16 +524,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 +559,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 +584,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 +633,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 +646,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 +669,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 +682,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.
|
||||
|
||||
+3
-9
@@ -59,6 +59,7 @@ struct af_info {
|
||||
struct af_instance {
|
||||
const struct af_info *info;
|
||||
struct mp_log *log;
|
||||
struct mp_tags *metadata;
|
||||
struct replaygain_data *replaygain_data;
|
||||
int (*control)(struct af_instance *af, int cmd, void *arg);
|
||||
void (*uninit)(struct af_instance *af);
|
||||
@@ -76,13 +77,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;
|
||||
@@ -94,6 +92,7 @@ struct af_stream {
|
||||
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mp_tags *metadata;
|
||||
struct replaygain_data *replaygain_data;
|
||||
};
|
||||
|
||||
@@ -120,7 +119,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 +132,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);
|
||||
|
||||
|
||||
+49
-20
@@ -48,12 +48,20 @@ static int filter_##name(struct af_instance *af, struct mp_audio *data, int f) \
|
||||
}
|
||||
|
||||
FILTER(f, float)
|
||||
FILTER(s32, int32_t)
|
||||
FILTER(u32, uint32_t)
|
||||
FILTER(s24, bs2b_int24_t)
|
||||
FILTER(u24, bs2b_uint24_t)
|
||||
FILTER(s16, int16_t)
|
||||
FILTER(u16, uint16_t)
|
||||
FILTER(fbe, float)
|
||||
FILTER(fle, float)
|
||||
FILTER(s32be, int32_t)
|
||||
FILTER(u32be, uint32_t)
|
||||
FILTER(s32le, int32_t)
|
||||
FILTER(u32le, uint32_t)
|
||||
FILTER(s24be, bs2b_int24_t)
|
||||
FILTER(u24be, bs2b_uint24_t)
|
||||
FILTER(s24le, bs2b_int24_t)
|
||||
FILTER(u24le, bs2b_uint24_t)
|
||||
FILTER(s16be, int16_t)
|
||||
FILTER(u16be, uint16_t)
|
||||
FILTER(s16le, int16_t)
|
||||
FILTER(u16le, uint16_t)
|
||||
FILTER(s8, int8_t)
|
||||
FILTER(u8, uint8_t)
|
||||
|
||||
@@ -77,26 +85,47 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
/* check for formats supported by libbs2b
|
||||
and assign corresponding handlers */
|
||||
switch (format) {
|
||||
case AF_FORMAT_FLOAT:
|
||||
af->filter = filter_f;
|
||||
case AF_FORMAT_FLOAT_BE:
|
||||
af->filter = filter_fbe;
|
||||
break;
|
||||
case AF_FORMAT_S32:
|
||||
af->filter = filter_s32;
|
||||
case AF_FORMAT_FLOAT_LE:
|
||||
af->filter = filter_fle;
|
||||
break;
|
||||
case AF_FORMAT_U32:
|
||||
af->filter = filter_u32;
|
||||
case AF_FORMAT_S32_BE:
|
||||
af->filter = filter_s32be;
|
||||
break;
|
||||
case AF_FORMAT_S24:
|
||||
af->filter = filter_s24;
|
||||
case AF_FORMAT_U32_BE:
|
||||
af->filter = filter_u32be;
|
||||
break;
|
||||
case AF_FORMAT_U24:
|
||||
af->filter = filter_u24;
|
||||
case AF_FORMAT_S32_LE:
|
||||
af->filter = filter_s32le;
|
||||
break;
|
||||
case AF_FORMAT_S16:
|
||||
af->filter = filter_s16;
|
||||
case AF_FORMAT_U32_LE:
|
||||
af->filter = filter_u32le;
|
||||
break;
|
||||
case AF_FORMAT_U16:
|
||||
af->filter = filter_u16;
|
||||
case AF_FORMAT_S24_BE:
|
||||
af->filter = filter_s24be;
|
||||
break;
|
||||
case AF_FORMAT_U24_BE:
|
||||
af->filter = filter_u24be;
|
||||
break;
|
||||
case AF_FORMAT_S24_LE:
|
||||
af->filter = filter_s24le;
|
||||
break;
|
||||
case AF_FORMAT_U24_LE:
|
||||
af->filter = filter_u24le;
|
||||
break;
|
||||
case AF_FORMAT_S16_BE:
|
||||
af->filter = filter_s16be;
|
||||
break;
|
||||
case AF_FORMAT_U16_BE:
|
||||
af->filter = filter_u16be;
|
||||
break;
|
||||
case AF_FORMAT_S16_LE:
|
||||
af->filter = filter_s16le;
|
||||
break;
|
||||
case AF_FORMAT_U16_LE:
|
||||
af->filter = filter_u16le;
|
||||
break;
|
||||
case AF_FORMAT_S8:
|
||||
af->filter = filter_s8;
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "af.h"
|
||||
#include "osdep/endian.h"
|
||||
|
||||
static bool test_conversion(int src_format, int dst_format)
|
||||
{
|
||||
|
||||
@@ -20,16 +20,21 @@
|
||||
|
||||
#include "af.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/endian.h"
|
||||
#include "compat/mpbswap.h"
|
||||
|
||||
static bool test_conversion(int src_format, int dst_format)
|
||||
{
|
||||
if ((src_format & AF_FORMAT_PLANAR) ||
|
||||
(dst_format & AF_FORMAT_PLANAR))
|
||||
return false;
|
||||
if (((src_format & ~AF_FORMAT_SIGN_MASK) ==
|
||||
(dst_format & ~AF_FORMAT_SIGN_MASK)) &&
|
||||
((src_format & AF_FORMAT_TYPE_MASK) == AF_FORMAT_I))
|
||||
int src_noend = src_format & ~AF_FORMAT_END_MASK;
|
||||
int dst_noend = dst_format & ~AF_FORMAT_END_MASK;
|
||||
// We can swap endian for all formats, but sign only for integer formats.
|
||||
if (src_noend == dst_noend)
|
||||
return true;
|
||||
if (((src_noend & ~AF_FORMAT_SIGN_MASK) ==
|
||||
(dst_noend & ~AF_FORMAT_SIGN_MASK)) &&
|
||||
((src_noend & AF_FORMAT_POINT_MASK) == AF_FORMAT_I))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -58,11 +63,34 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void si2us(void *data, int len, int bps)
|
||||
static void endian(void *data, int len, int bps)
|
||||
{
|
||||
switch (bps) {
|
||||
case 2:
|
||||
for (int i = 0; i < len; i++) {
|
||||
((uint16_t*)data)[i] = bswap_16(((uint16_t *)data)[i]);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
for(int i = 0; i < len; i++) {
|
||||
uint8_t s = ((uint8_t *)data)[3 * i];
|
||||
((uint8_t *)data)[3 * i] = ((uint8_t *)data)[3 * i + 2];
|
||||
((uint8_t *)data)[3 * i + 2] = s;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
for(int i = 0; i < len; i++) {
|
||||
((uint32_t*)data)[i] = bswap_32(((uint32_t *)data)[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void si2us(void *data, int len, int bps, bool le)
|
||||
{
|
||||
ptrdiff_t i = -(len * bps);
|
||||
uint8_t *p = &((uint8_t *)data)[len * bps];
|
||||
if (BYTE_ORDER == LITTLE_ENDIAN && bps > 1)
|
||||
if (le && bps > 1)
|
||||
p += bps - 1;
|
||||
if (len <= 0)
|
||||
return;
|
||||
@@ -77,8 +105,12 @@ static int filter(struct af_instance *af, struct mp_audio *data, int flags)
|
||||
int outfmt = af->data->format;
|
||||
size_t len = data->samples * data->nch;
|
||||
|
||||
if ((infmt & AF_FORMAT_END_MASK) != (outfmt & AF_FORMAT_END_MASK))
|
||||
endian(data->planes[0], len, data->bps);
|
||||
|
||||
if ((infmt & AF_FORMAT_SIGN_MASK) != (outfmt & AF_FORMAT_SIGN_MASK))
|
||||
si2us(data->planes[0], len, data->bps);
|
||||
si2us(data->planes[0], len, data->bps,
|
||||
(outfmt & AF_FORMAT_END_MASK) == AF_FORMAT_LE);
|
||||
|
||||
mp_audio_set_format(data, outfmt);
|
||||
return 0;
|
||||
@@ -92,8 +124,8 @@ static int af_open(struct af_instance *af)
|
||||
}
|
||||
|
||||
const struct af_info af_info_convertsignendian = {
|
||||
.info = "Convert between sample format sign",
|
||||
.name = "convertsign",
|
||||
.info = "Convert between sample format sign/endian",
|
||||
.name = "convertsignendian",
|
||||
.open = af_open,
|
||||
.test_conversion = test_conversion,
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+17
-35
@@ -284,27 +284,6 @@ static inline void update_ch(af_hrtf_t *s, short *in, const int k)
|
||||
s->ba_r[k] = in[4] + in[1] + in[3];
|
||||
}
|
||||
|
||||
static void clear_coeff(af_hrtf_t *s, float *c)
|
||||
{
|
||||
memset(c, 0, s->dlbuflen * sizeof(float));
|
||||
}
|
||||
|
||||
static void reset(af_hrtf_t *s)
|
||||
{
|
||||
clear_coeff(s, s->lf);
|
||||
clear_coeff(s, s->rf);
|
||||
clear_coeff(s, s->lr);
|
||||
clear_coeff(s, s->rr);
|
||||
clear_coeff(s, s->cf);
|
||||
clear_coeff(s, s->cr);
|
||||
clear_coeff(s, s->ba_l);
|
||||
clear_coeff(s, s->ba_r);
|
||||
clear_coeff(s, s->fwrbuf_l);
|
||||
clear_coeff(s, s->fwrbuf_r);
|
||||
clear_coeff(s, s->fwrbuf_lr);
|
||||
clear_coeff(s, s->fwrbuf_rr);
|
||||
}
|
||||
|
||||
/* Initialization and runtime control */
|
||||
static int control(struct af_instance *af, int cmd, void* arg)
|
||||
{
|
||||
@@ -313,27 +292,30 @@ static int control(struct af_instance *af, int cmd, void* arg)
|
||||
|
||||
switch(cmd) {
|
||||
case AF_CONTROL_REINIT:
|
||||
reset(s);
|
||||
af->data->rate = 48000;
|
||||
mp_audio_set_channels_old(af->data, ((struct mp_audio*)arg)->nch);
|
||||
if(af->data->nch == 2) {
|
||||
/* 2 channel input */
|
||||
if(s->decode_mode != HRTF_MIX_MATRIX2CH) {
|
||||
/* Default behavior is stereo mixing. */
|
||||
s->decode_mode = HRTF_MIX_STEREO;
|
||||
}
|
||||
} else if (af->data->nch < 5) {
|
||||
mp_audio_set_channels_old(af->data, 5);
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
if(af->data->rate != 48000) {
|
||||
// automatic samplerate adjustment in the filter chain
|
||||
// is not yet supported.
|
||||
MP_ERR(af, "ERROR: Sampling rate is not 48000 Hz (%d)!\n",
|
||||
af->data->rate);
|
||||
return AF_ERROR;
|
||||
}
|
||||
mp_audio_set_channels_old(af->data, ((struct mp_audio*)arg)->nch);
|
||||
if(af->data->nch == 2) {
|
||||
/* 2 channel input */
|
||||
if(s->decode_mode != HRTF_MIX_MATRIX2CH) {
|
||||
/* Default behavior is stereo mixing. */
|
||||
s->decode_mode = HRTF_MIX_STEREO;
|
||||
}
|
||||
}
|
||||
else if (af->data->nch < 5)
|
||||
mp_audio_set_channels_old(af->data, 5);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
test_output_res = af_test_output(af, (struct mp_audio*)arg);
|
||||
// after testing input set the real output format
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
s->print_flag = 1;
|
||||
return test_output_res;
|
||||
case AF_CONTROL_RESET:
|
||||
reset(s);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
return AF_UNKNOWN;
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include <libavutil/audioconvert.h>
|
||||
#include <libavutil/intreadwrite.h>
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/bswap.h>
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "common/common.h"
|
||||
@@ -88,7 +87,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
if (in->nch > AC3_MAX_CHANNELS)
|
||||
mp_audio_set_num_channels(in, AC3_MAX_CHANNELS);
|
||||
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S_AC3);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_AC3_BE);
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
|
||||
if (!mp_audio_config_equals(in, &orig_in))
|
||||
@@ -151,12 +150,6 @@ static void uninit(struct af_instance* af)
|
||||
}
|
||||
}
|
||||
|
||||
static void swap_16(uint16_t *ptr, size_t size)
|
||||
{
|
||||
for (size_t n = 0; n < size; n++)
|
||||
ptr[n] = av_bswap16(ptr[n]);
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static int filter(struct af_instance* af, struct mp_audio* audio, int flags)
|
||||
{
|
||||
@@ -230,11 +223,11 @@ static int filter(struct af_instance* af, struct mp_audio* audio, int flags)
|
||||
frame_size = AC3_FRAME_SIZE * 2 * 2;
|
||||
header_len = 8;
|
||||
|
||||
AV_WL16(hdr, 0xF872); // iec 61937 syncword 1
|
||||
AV_WL16(hdr + 2, 0x4E1F); // iec 61937 syncword 2
|
||||
hdr[5] = bsmod; // bsmod
|
||||
hdr[4] = 0x01; // data-type ac3
|
||||
AV_WL16(hdr + 6, len << 3); // number of bits in payload
|
||||
AV_WB16(hdr, 0xF872); // iec 61937 syncword 1
|
||||
AV_WB16(hdr + 2, 0x4E1F); // iec 61937 syncword 2
|
||||
hdr[4] = bsmod; // bsmod
|
||||
hdr[5] = 0x01; // data-type ac3
|
||||
AV_WB16(hdr + 6, len << 3); // number of bits in payload
|
||||
}
|
||||
|
||||
size_t max_size = (max_out_samples - out->samples) * out->sstride;
|
||||
@@ -246,7 +239,6 @@ static int filter(struct af_instance* af, struct mp_audio* audio, int flags)
|
||||
memcpy(buf + header_len, s->pkt.data, s->pkt.size);
|
||||
memset(buf + header_len + s->pkt.size, 0,
|
||||
frame_size - (header_len + s->pkt.size));
|
||||
swap_16((uint16_t *)(buf + header_len), s->pkt.size / 2);
|
||||
out->samples += frame_size / out->sstride;
|
||||
}
|
||||
|
||||
@@ -320,13 +312,12 @@ const struct af_info af_info_lavcac3enc = {
|
||||
.priv_size = sizeof(struct af_ac3enc_s),
|
||||
.priv_defaults = &(const struct af_ac3enc_s){
|
||||
.cfg_add_iec61937_header = 1,
|
||||
.cfg_bit_rate = 640,
|
||||
.cfg_min_channel_num = 3,
|
||||
.cfg_min_channel_num = 5,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("tospdif", cfg_add_iec61937_header, 0),
|
||||
OPT_CHOICE_OR_INT("bitrate", cfg_bit_rate, 0, 32, 640,
|
||||
({"auto", 0}, {"default", 0})),
|
||||
({"default", 0})),
|
||||
OPT_INTRANGE("minch", cfg_min_channel_num, 0, 2, 6),
|
||||
{0}
|
||||
},
|
||||
|
||||
@@ -38,9 +38,8 @@
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "af.h"
|
||||
|
||||
#include "common/av_common.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "common/av_opts.h"
|
||||
|
||||
// FFmpeg and Libav have slightly different APIs, just enough to cause us
|
||||
// unnecessary pain. <Expletive deleted.>
|
||||
@@ -63,7 +62,7 @@ struct priv {
|
||||
|
||||
// options
|
||||
char *cfg_graph;
|
||||
char **cfg_avopts;
|
||||
char *cfg_avopts;
|
||||
};
|
||||
|
||||
static void destroy_graph(struct af_instance *af)
|
||||
@@ -91,8 +90,11 @@ static bool recreate_graph(struct af_instance *af, struct mp_audio *config)
|
||||
if (!graph)
|
||||
goto error;
|
||||
|
||||
if (mp_set_avopts(af->log, graph, p->cfg_avopts) < 0)
|
||||
if (parse_avopts(graph, p->cfg_avopts) < 0) {
|
||||
MP_FATAL(af, "lavfi: could not set opts: '%s'\n",
|
||||
p->cfg_avopts);
|
||||
goto error;
|
||||
}
|
||||
|
||||
AVFilterInOut *outputs = avfilter_inout_alloc();
|
||||
AVFilterInOut *inputs = avfilter_inout_alloc();
|
||||
@@ -304,7 +306,7 @@ const struct af_info af_info_lavfi = {
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("graph", cfg_graph, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
OPT_STRING("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -52,9 +52,9 @@
|
||||
#error "config.h broken or no resampler found"
|
||||
#endif
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/av_opts.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
@@ -74,7 +74,7 @@ struct af_resample_opts {
|
||||
|
||||
struct af_resample {
|
||||
int allow_detach;
|
||||
char **avopts;
|
||||
char *avopts;
|
||||
struct AVAudioResampleContext *avrctx;
|
||||
struct AVAudioResampleContext *avrctx_out; // for output channel reordering
|
||||
struct af_resample_opts ctx; // opts in the context
|
||||
@@ -164,8 +164,10 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
|
||||
av_opt_set_double(s->avrctx, "cutoff", s->ctx.cutoff, 0);
|
||||
|
||||
if (mp_set_avopts(af->log, s->avrctx, s->avopts) < 0)
|
||||
if (parse_avopts(s->avrctx, s->avopts) < 0) {
|
||||
MP_FATAL(af, "af_lavrresample: could not set opts: '%s'\n", s->avopts);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
struct mp_chmap map_in = in->channels;
|
||||
struct mp_chmap map_out = out->channels;
|
||||
@@ -214,7 +216,8 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
if (avresample_open(s->avrctx) < 0 ||
|
||||
avresample_open(s->avrctx_out) < 0)
|
||||
{
|
||||
MP_ERR(af, "Cannot open Libavresample Context. \n");
|
||||
MP_ERR(af, "Cannot open "
|
||||
"Libavresample Context. \n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
return AF_OK;
|
||||
@@ -338,7 +341,7 @@ static int filter(struct af_instance *af, struct mp_audio *data, int flags)
|
||||
if (needs_reorder(s->reorder_out, out->nch)) {
|
||||
if (af_fmt_is_planar(out->format)) {
|
||||
reorder_planes(data, s->reorder_out);
|
||||
} else if (out->samples) {
|
||||
} else {
|
||||
int out_size = out->samples * out->sstride;
|
||||
if (talloc_get_size(s->reorder_buffer) < out_size)
|
||||
s->reorder_buffer = talloc_realloc_size(s, s->reorder_buffer, out_size);
|
||||
@@ -346,8 +349,7 @@ static int filter(struct af_instance *af, struct mp_audio *data, int flags)
|
||||
int out_samples = avresample_convert(s->avrctx_out,
|
||||
(uint8_t **) data->planes, out_size, out->samples,
|
||||
(uint8_t **) out->planes, out_size, out->samples);
|
||||
if (out_samples < 0)
|
||||
MP_ERR(af, "Reordering failed.\n");
|
||||
assert(out_samples == data->samples);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,7 +373,8 @@ static int af_open(struct af_instance *af)
|
||||
if (s->avrctx && s->avrctx_out) {
|
||||
return AF_OK;
|
||||
} else {
|
||||
MP_ERR(af, "Cannot initialize Libavresample Context. \n");
|
||||
MP_ERR(af, "Cannot initialize "
|
||||
"Libavresample Context. \n");
|
||||
uninit(af);
|
||||
return AF_ERROR;
|
||||
}
|
||||
@@ -399,7 +402,7 @@ const struct af_info af_info_lavrresample = {
|
||||
OPT_FLAG("linear", opts.linear, 0),
|
||||
OPT_DOUBLE("cutoff", opts.cutoff, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLAG("detach", allow_detach, 0),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
OPT_STRING("o", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
|
||||
+38
-26
@@ -30,6 +30,9 @@
|
||||
|
||||
int af_fmt2bps(int format)
|
||||
{
|
||||
if (AF_FORMAT_IS_AC3(format)) return 2;
|
||||
if (format == AF_FORMAT_UNKNOWN)
|
||||
return 0;
|
||||
switch (format & AF_FORMAT_BITS_MASK) {
|
||||
case AF_FORMAT_8BIT: return 1;
|
||||
case AF_FORMAT_16BIT: return 2;
|
||||
@@ -59,7 +62,7 @@ static int bits_to_mask(int bits)
|
||||
|
||||
int af_fmt_change_bits(int format, int bits)
|
||||
{
|
||||
if (!af_fmt_is_valid(format))
|
||||
if (!af_fmt_is_valid(format) || (format & AF_FORMAT_SPECIAL_MASK))
|
||||
return 0;
|
||||
int mask = bits_to_mask(bits);
|
||||
format = (format & ~AF_FORMAT_BITS_MASK) | mask;
|
||||
@@ -106,31 +109,35 @@ bool af_fmt_is_planar(int format)
|
||||
return !!(format & AF_FORMAT_PLANAR);
|
||||
}
|
||||
|
||||
#define FMT(string, id) \
|
||||
{string, id},
|
||||
|
||||
#define FMT_ENDIAN(string, id) \
|
||||
{string, id}, \
|
||||
{string "le", MP_CONCAT(id, _LE)}, \
|
||||
{string "be", MP_CONCAT(id, _BE)}, \
|
||||
|
||||
const struct af_fmt_entry af_fmtstr_table[] = {
|
||||
{"u8", AF_FORMAT_U8},
|
||||
{"s8", AF_FORMAT_S8},
|
||||
{"u16", AF_FORMAT_U16},
|
||||
{"s16", AF_FORMAT_S16},
|
||||
{"u24", AF_FORMAT_U24},
|
||||
{"s24", AF_FORMAT_S24},
|
||||
{"u32", AF_FORMAT_U32},
|
||||
{"s32", AF_FORMAT_S32},
|
||||
{"float", AF_FORMAT_FLOAT},
|
||||
{"double", AF_FORMAT_DOUBLE},
|
||||
FMT("mpeg2", AF_FORMAT_MPEG2)
|
||||
FMT_ENDIAN("ac3", AF_FORMAT_AC3)
|
||||
FMT_ENDIAN("iec61937", AF_FORMAT_IEC61937)
|
||||
|
||||
{"u8p", AF_FORMAT_U8P},
|
||||
{"s16p", AF_FORMAT_S16P},
|
||||
{"s32p", AF_FORMAT_S32P},
|
||||
{"floatp", AF_FORMAT_FLOATP},
|
||||
{"doublep", AF_FORMAT_DOUBLEP},
|
||||
FMT("u8", AF_FORMAT_U8)
|
||||
FMT("s8", AF_FORMAT_S8)
|
||||
FMT_ENDIAN("u16", AF_FORMAT_U16)
|
||||
FMT_ENDIAN("s16", AF_FORMAT_S16)
|
||||
FMT_ENDIAN("u24", AF_FORMAT_U24)
|
||||
FMT_ENDIAN("s24", AF_FORMAT_S24)
|
||||
FMT_ENDIAN("u32", AF_FORMAT_U32)
|
||||
FMT_ENDIAN("s32", AF_FORMAT_S32)
|
||||
FMT_ENDIAN("float", AF_FORMAT_FLOAT)
|
||||
FMT_ENDIAN("double", AF_FORMAT_DOUBLE)
|
||||
|
||||
{"spdif-aac", AF_FORMAT_S_AAC},
|
||||
{"spdif-ac3", AF_FORMAT_S_AC3},
|
||||
{"spdif-dts", AF_FORMAT_S_DTS},
|
||||
{"spdif-dtshd", AF_FORMAT_S_DTSHD},
|
||||
{"spdif-eac3", AF_FORMAT_S_EAC3},
|
||||
{"spdif-mp3", AF_FORMAT_S_MP3},
|
||||
{"spdif-truehd",AF_FORMAT_S_TRUEHD},
|
||||
FMT("u8p", AF_FORMAT_U8P)
|
||||
FMT("s16p", AF_FORMAT_S16P)
|
||||
FMT("s32p", AF_FORMAT_S32P)
|
||||
FMT("floatp", AF_FORMAT_FLOATP)
|
||||
FMT("doublep", AF_FORMAT_DOUBLEP)
|
||||
|
||||
{0}
|
||||
};
|
||||
@@ -192,17 +199,22 @@ int af_format_conversion_score(int dst_format, int src_format)
|
||||
return INT_MIN;
|
||||
if (dst_format == src_format)
|
||||
return 1024;
|
||||
// Just endian swapping (separate because it works for special formats)
|
||||
if ((dst_format & ~AF_FORMAT_END_MASK) == (src_format & ~AF_FORMAT_END_MASK))
|
||||
return 1024 - 2;
|
||||
// Can't be normally converted
|
||||
if (AF_FORMAT_IS_SPECIAL(dst_format) || AF_FORMAT_IS_SPECIAL(src_format))
|
||||
return INT_MIN;
|
||||
int score = 1024;
|
||||
if (FMT_DIFF(AF_FORMAT_INTERLEAVING_MASK, dst_format, src_format))
|
||||
score -= 1; // has to (de-)planarize
|
||||
if (FMT_DIFF(AF_FORMAT_END_MASK, dst_format, src_format))
|
||||
score -= 2; // has to swap endian
|
||||
if (FMT_DIFF(AF_FORMAT_SIGN_MASK, dst_format, src_format))
|
||||
score -= 4; // has to swap sign
|
||||
if (FMT_DIFF(AF_FORMAT_TYPE_MASK, dst_format, src_format)) {
|
||||
if (FMT_DIFF(AF_FORMAT_POINT_MASK, dst_format, src_format)) {
|
||||
int dst_bits = dst_format & AF_FORMAT_BITS_MASK;
|
||||
if ((dst_format & AF_FORMAT_TYPE_MASK) == AF_FORMAT_F) {
|
||||
if ((dst_format & AF_FORMAT_POINT_MASK) == AF_FORMAT_F) {
|
||||
// For int->float, always prefer 32 bit float.
|
||||
score -= dst_bits == AF_FORMAT_32BIT ? 8 : 0;
|
||||
} else {
|
||||
@@ -218,7 +230,7 @@ int af_format_conversion_score(int dst_format, int src_format)
|
||||
}
|
||||
}
|
||||
// Consider this the worst case.
|
||||
if (FMT_DIFF(AF_FORMAT_TYPE_MASK, dst_format, src_format))
|
||||
if (FMT_DIFF(AF_FORMAT_POINT_MASK, dst_format, src_format))
|
||||
score -= 2048; // has to convert float<->int
|
||||
return score;
|
||||
}
|
||||
+91
-47
@@ -24,78 +24,122 @@
|
||||
#define MPLAYER_AF_FORMAT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "misc/bstr.h"
|
||||
#include "bstr/bstr.h"
|
||||
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
#define AF_SELECT_LE_BE(LE, BE) BE
|
||||
#else
|
||||
#define AF_SELECT_LE_BE(LE, BE) LE
|
||||
#endif
|
||||
|
||||
// Endianness
|
||||
#define AF_FORMAT_BE (0<<0) // Big Endian
|
||||
#define AF_FORMAT_LE (1<<0) // Little Endian
|
||||
#define AF_FORMAT_END_MASK (1<<0)
|
||||
|
||||
#define AF_FORMAT_NE AF_SELECT_LE_BE(AF_FORMAT_LE, AF_FORMAT_BE)
|
||||
|
||||
// Signed/unsigned
|
||||
#define AF_FORMAT_SI (0<<0) // Signed
|
||||
#define AF_FORMAT_US (1<<0) // Unsigned
|
||||
#define AF_FORMAT_SIGN_MASK (1<<0)
|
||||
#define AF_FORMAT_SI (0<<1) // Signed
|
||||
#define AF_FORMAT_US (1<<1) // Unsigned
|
||||
#define AF_FORMAT_SIGN_MASK (1<<1)
|
||||
|
||||
// Bits used
|
||||
// Some code assumes they're sorted by size.
|
||||
#define AF_FORMAT_8BIT (0<<1)
|
||||
#define AF_FORMAT_16BIT (1<<1)
|
||||
#define AF_FORMAT_24BIT (2<<1)
|
||||
#define AF_FORMAT_32BIT (3<<1)
|
||||
#define AF_FORMAT_64BIT (4<<1)
|
||||
#define AF_FORMAT_BITS_MASK (7<<1)
|
||||
#define AF_FORMAT_8BIT (0<<3)
|
||||
#define AF_FORMAT_16BIT (1<<3)
|
||||
#define AF_FORMAT_24BIT (2<<3)
|
||||
#define AF_FORMAT_32BIT (3<<3)
|
||||
#define AF_FORMAT_64BIT (4<<3)
|
||||
#define AF_FORMAT_BITS_MASK (7<<3)
|
||||
|
||||
// Fixed/floating point/special
|
||||
#define AF_FORMAT_I (1<<4) // Int
|
||||
#define AF_FORMAT_F (2<<4) // Foating point
|
||||
#define AF_FORMAT_S (4<<4) // special (IEC61937)
|
||||
#define AF_FORMAT_TYPE_MASK (7<<4)
|
||||
// Special flags refering to non pcm data (note: 1<<6, 2<<6, 5<<6 unused)
|
||||
#define AF_FORMAT_S_MPEG2 (3<<6) // MPEG(2) audio
|
||||
#define AF_FORMAT_S_AC3 (4<<6) // Dolby Digital AC3
|
||||
#define AF_FORMAT_S_IEC61937 (6<<6)
|
||||
#define AF_FORMAT_SPECIAL_MASK (7<<6)
|
||||
|
||||
// Fixed or floating point
|
||||
#define AF_FORMAT_I (1<<9) // Int
|
||||
#define AF_FORMAT_F (2<<9) // Foating point
|
||||
#define AF_FORMAT_POINT_MASK (3<<9)
|
||||
|
||||
// Interleaving (planar formats have data for each channel in separate planes)
|
||||
#define AF_FORMAT_INTERLEAVED (0<<7) // must be 0
|
||||
#define AF_FORMAT_PLANAR (1<<7)
|
||||
#define AF_FORMAT_INTERLEAVING_MASK (1<<7)
|
||||
|
||||
#define AF_FORMAT_S_CODEC(n) ((n)<<8)
|
||||
#define AF_FORMAT_S_CODEC_MASK (15 <<8) // 16 codecs max.
|
||||
#define AF_FORMAT_INTERLEAVED (0<<11) // must be 0
|
||||
#define AF_FORMAT_PLANAR (1<<11)
|
||||
#define AF_FORMAT_INTERLEAVING_MASK (1<<11)
|
||||
|
||||
#define AF_FORMAT_MASK ((1<<12)-1)
|
||||
|
||||
#define AF_INTP (AF_FORMAT_I|AF_FORMAT_PLANAR)
|
||||
#define AF_FLTP (AF_FORMAT_F|AF_FORMAT_PLANAR)
|
||||
#define AF_FORMAT_S_(n) (AF_FORMAT_S_CODEC(n)|AF_FORMAT_S|AF_FORMAT_16BIT)
|
||||
|
||||
// actual sample formats
|
||||
enum af_format {
|
||||
AF_FORMAT_UNKNOWN = 0,
|
||||
|
||||
AF_FORMAT_U8 = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_8BIT),
|
||||
AF_FORMAT_S8 = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_8BIT),
|
||||
AF_FORMAT_U16 = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_16BIT),
|
||||
AF_FORMAT_S16 = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_16BIT),
|
||||
AF_FORMAT_U24 = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_24BIT),
|
||||
AF_FORMAT_S24 = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_24BIT),
|
||||
AF_FORMAT_U32 = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_32BIT),
|
||||
AF_FORMAT_S32 = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_32BIT),
|
||||
AF_FORMAT_U8 = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_8BIT|AF_FORMAT_NE),
|
||||
AF_FORMAT_S8 = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_8BIT|AF_FORMAT_NE),
|
||||
AF_FORMAT_U16_LE = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_16BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_U16_BE = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_16BIT|AF_FORMAT_BE),
|
||||
AF_FORMAT_S16_LE = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_16BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_S16_BE = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_16BIT|AF_FORMAT_BE),
|
||||
AF_FORMAT_U24_LE = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_24BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_U24_BE = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_24BIT|AF_FORMAT_BE),
|
||||
AF_FORMAT_S24_LE = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_24BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_S24_BE = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_24BIT|AF_FORMAT_BE),
|
||||
AF_FORMAT_U32_LE = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_32BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_U32_BE = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_32BIT|AF_FORMAT_BE),
|
||||
AF_FORMAT_S32_LE = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_32BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_S32_BE = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_32BIT|AF_FORMAT_BE),
|
||||
|
||||
AF_FORMAT_FLOAT = (AF_FORMAT_F|AF_FORMAT_32BIT),
|
||||
AF_FORMAT_DOUBLE = (AF_FORMAT_F|AF_FORMAT_64BIT),
|
||||
AF_FORMAT_FLOAT_LE = (AF_FORMAT_F|AF_FORMAT_32BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_FLOAT_BE = (AF_FORMAT_F|AF_FORMAT_32BIT|AF_FORMAT_BE),
|
||||
|
||||
AF_FORMAT_DOUBLE_LE = (AF_FORMAT_F|AF_FORMAT_64BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_DOUBLE_BE = (AF_FORMAT_F|AF_FORMAT_64BIT|AF_FORMAT_BE),
|
||||
|
||||
AF_FORMAT_AC3_LE = (AF_FORMAT_S_AC3|AF_FORMAT_16BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_AC3_BE = (AF_FORMAT_S_AC3|AF_FORMAT_16BIT|AF_FORMAT_BE),
|
||||
|
||||
AF_FORMAT_IEC61937_LE = (AF_FORMAT_S_IEC61937|AF_FORMAT_16BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_IEC61937_BE = (AF_FORMAT_S_IEC61937|AF_FORMAT_16BIT|AF_FORMAT_BE),
|
||||
|
||||
AF_FORMAT_MPEG2 = (AF_FORMAT_S_MPEG2),
|
||||
|
||||
// Planar variants
|
||||
AF_FORMAT_U8P = (AF_INTP|AF_FORMAT_US|AF_FORMAT_8BIT),
|
||||
AF_FORMAT_S16P = (AF_INTP|AF_FORMAT_SI|AF_FORMAT_16BIT),
|
||||
AF_FORMAT_S32P = (AF_INTP|AF_FORMAT_SI|AF_FORMAT_32BIT),
|
||||
AF_FORMAT_FLOATP = (AF_FLTP|AF_FORMAT_32BIT),
|
||||
AF_FORMAT_DOUBLEP = (AF_FLTP|AF_FORMAT_64BIT),
|
||||
AF_FORMAT_U8P = (AF_INTP|AF_FORMAT_US|AF_FORMAT_8BIT|AF_FORMAT_NE),
|
||||
AF_FORMAT_S16P = (AF_INTP|AF_FORMAT_SI|AF_FORMAT_16BIT|AF_FORMAT_NE),
|
||||
AF_FORMAT_S32P = (AF_INTP|AF_FORMAT_SI|AF_FORMAT_32BIT|AF_FORMAT_NE),
|
||||
AF_FORMAT_FLOATP = (AF_FLTP|AF_FORMAT_32BIT|AF_FORMAT_NE),
|
||||
AF_FORMAT_DOUBLEP = (AF_FLTP|AF_FORMAT_64BIT|AF_FORMAT_NE),
|
||||
|
||||
// All of these use IEC61937 framing, and otherwise pretend to be like PCM.
|
||||
AF_FORMAT_S_AAC = AF_FORMAT_S_(0),
|
||||
AF_FORMAT_S_AC3 = AF_FORMAT_S_(1),
|
||||
AF_FORMAT_S_DTS = AF_FORMAT_S_(2),
|
||||
AF_FORMAT_S_DTSHD = AF_FORMAT_S_(3),
|
||||
AF_FORMAT_S_EAC3 = AF_FORMAT_S_(4),
|
||||
AF_FORMAT_S_MP3 = AF_FORMAT_S_(5),
|
||||
AF_FORMAT_S_TRUEHD = AF_FORMAT_S_(6),
|
||||
// Native endian variants
|
||||
AF_FORMAT_U16 = AF_SELECT_LE_BE(AF_FORMAT_U16_LE, AF_FORMAT_U16_BE),
|
||||
AF_FORMAT_S16 = AF_SELECT_LE_BE(AF_FORMAT_S16_LE, AF_FORMAT_S16_BE),
|
||||
AF_FORMAT_U24 = AF_SELECT_LE_BE(AF_FORMAT_U24_LE, AF_FORMAT_U24_BE),
|
||||
AF_FORMAT_S24 = AF_SELECT_LE_BE(AF_FORMAT_S24_LE, AF_FORMAT_S24_BE),
|
||||
AF_FORMAT_U32 = AF_SELECT_LE_BE(AF_FORMAT_U32_LE, AF_FORMAT_U32_BE),
|
||||
AF_FORMAT_S32 = AF_SELECT_LE_BE(AF_FORMAT_S32_LE, AF_FORMAT_S32_BE),
|
||||
|
||||
AF_FORMAT_FLOAT = AF_SELECT_LE_BE(AF_FORMAT_FLOAT_LE, AF_FORMAT_FLOAT_BE),
|
||||
AF_FORMAT_DOUBLE = AF_SELECT_LE_BE(AF_FORMAT_DOUBLE_LE, AF_FORMAT_DOUBLE_BE),
|
||||
|
||||
AF_FORMAT_AC3 = AF_SELECT_LE_BE(AF_FORMAT_AC3_LE, AF_FORMAT_AC3_BE),
|
||||
AF_FORMAT_IEC61937 = AF_SELECT_LE_BE(AF_FORMAT_IEC61937_LE, AF_FORMAT_IEC61937_BE),
|
||||
};
|
||||
|
||||
#define AF_FORMAT_IS_IEC61937(f) (((f) & AF_FORMAT_TYPE_MASK) == AF_FORMAT_S)
|
||||
#define AF_FORMAT_IS_SPECIAL(f) AF_FORMAT_IS_IEC61937(f)
|
||||
#define AF_FORMAT_IS_AC3(fmt) \
|
||||
(((fmt) & AF_FORMAT_SPECIAL_MASK) == AF_FORMAT_S_AC3)
|
||||
|
||||
#define AF_FORMAT_IS_IEC61937(fmt) \
|
||||
(AF_FORMAT_IS_AC3(fmt) || \
|
||||
((fmt) & AF_FORMAT_SPECIAL_MASK) == AF_FORMAT_S_IEC61937)
|
||||
|
||||
#define AF_FORMAT_IS_SPECIAL(fmt) \
|
||||
((fmt & AF_FORMAT_SPECIAL_MASK) != 0)
|
||||
|
||||
struct af_fmt_entry {
|
||||
const char *name;
|
||||
|
||||
+8
-10
@@ -36,8 +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 ao_softvol; // AO has private or per-app volume
|
||||
bool softvol; // use AO (true) or af_volume (false)
|
||||
bool emulate_mute; // if true, emulate mute with volume=0
|
||||
// Last known values (possibly out of sync with reality)
|
||||
float vol_l, vol_r;
|
||||
@@ -72,7 +71,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)
|
||||
@@ -246,13 +245,11 @@ char *mixer_get_volume_restore_data(struct mixer *mixer)
|
||||
|
||||
static void probe_softvol(struct mixer *mixer)
|
||||
{
|
||||
mixer->ao_softvol =
|
||||
ao_control(mixer->ao, AOCONTROL_HAS_SOFT_VOLUME, 0) == 1 ||
|
||||
ao_control(mixer->ao, AOCONTROL_HAS_PER_APP_VOLUME, 0) == 1;
|
||||
|
||||
if (mixer->opts->softvol == SOFTVOL_AUTO) {
|
||||
// No system-wide volume => fine with AO volume control.
|
||||
mixer->softvol = !mixer->ao_softvol;
|
||||
mixer->softvol =
|
||||
ao_control(mixer->ao, AOCONTROL_HAS_TEMP_VOLUME, 0) != 1 ||
|
||||
ao_control(mixer->ao, AOCONTROL_HAS_PER_APP_VOLUME, 0) != 1;
|
||||
} else {
|
||||
mixer->softvol = mixer->opts->softvol == SOFTVOL_YES;
|
||||
}
|
||||
@@ -285,7 +282,8 @@ static void restore_volume(struct mixer *mixer)
|
||||
const char *prev_driver = mixer->driver;
|
||||
mixer->driver = mixer->softvol ? "softvol" : ao_get_name(ao);
|
||||
|
||||
bool restore = mixer->softvol || mixer->ao_softvol;
|
||||
bool restore =
|
||||
mixer->softvol || ao_control(ao, AOCONTROL_HAS_TEMP_VOLUME, 0) == 1;
|
||||
|
||||
// Restore old parameters if volume won't survive reinitialization.
|
||||
// But not if volume scale is possibly different.
|
||||
@@ -363,7 +361,7 @@ void mixer_uninit_audio(struct mixer *mixer)
|
||||
return;
|
||||
|
||||
checkvolume(mixer);
|
||||
if (mixer->muted_by_us && !mixer->softvol && !mixer->ao_softvol) {
|
||||
if (mixer->muted_by_us) {
|
||||
/* Current audio output API combines playing the remaining buffered
|
||||
* audio and uninitializing the AO into one operation, even though
|
||||
* ideally unmute would happen between those two steps. We can't do
|
||||
|
||||
+58
-183
@@ -29,16 +29,15 @@
|
||||
#include "audio/format.h"
|
||||
#include "audio/audio.h"
|
||||
|
||||
#include "input/input.h"
|
||||
#include "options/options.h"
|
||||
#include "options/m_config.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
|
||||
extern const struct ao_driver audio_out_oss;
|
||||
extern const struct ao_driver audio_out_coreaudio;
|
||||
extern const struct ao_driver audio_out_coreaudio_exclusive;
|
||||
extern const struct ao_driver audio_out_rsound;
|
||||
extern const struct ao_driver audio_out_sndio;
|
||||
extern const struct ao_driver audio_out_pulse;
|
||||
@@ -61,15 +60,18 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#if HAVE_PULSE
|
||||
&audio_out_pulse,
|
||||
#endif
|
||||
#if HAVE_SNDIO
|
||||
&audio_out_sndio,
|
||||
#endif
|
||||
#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
|
||||
@@ -85,12 +87,6 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#endif
|
||||
#if HAVE_SDL1 || HAVE_SDL2
|
||||
&audio_out_sdl,
|
||||
#endif
|
||||
#if HAVE_SNDIO
|
||||
&audio_out_sndio,
|
||||
#endif
|
||||
#if HAVE_COREAUDIO
|
||||
&audio_out_coreaudio_exclusive,
|
||||
#endif
|
||||
&audio_out_null,
|
||||
// should not be auto-selected:
|
||||
@@ -129,34 +125,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 +143,33 @@ 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,
|
||||
};
|
||||
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 +180,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);
|
||||
}
|
||||
ao->buffer = MPMAX(ao->device_buffer, MIN_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 +201,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 +220,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;
|
||||
@@ -339,7 +255,16 @@ int ao_play(struct ao *ao, void **data, int samples, int flags)
|
||||
|
||||
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
return ao->api->control ? ao->api->control(ao, cmd, arg) : CONTROL_UNKNOWN;
|
||||
switch (cmd) {
|
||||
case AOCONTROL_HAS_TEMP_VOLUME:
|
||||
return !ao->no_persistent_volume;
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return !!ao->per_application_mixer;
|
||||
default:
|
||||
if (ao->api->control)
|
||||
return ao->api->control(ao, cmd, arg);
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
// Return size of the buffered data in seconds. Can include the device latency.
|
||||
@@ -384,32 +309,27 @@ void ao_resume(struct ao *ao)
|
||||
ao->api->resume(ao);
|
||||
}
|
||||
|
||||
// Be careful with locking
|
||||
void ao_wait_drain(struct ao *ao)
|
||||
{
|
||||
// This is probably not entirely accurate, but good enough.
|
||||
mp_sleep_us(ao_get_delay(ao) * 1000000);
|
||||
ao_reset(ao);
|
||||
}
|
||||
|
||||
// Block until the current audio buffer has played completely.
|
||||
void ao_drain(struct ao *ao)
|
||||
{
|
||||
if (ao->api->drain)
|
||||
if (ao->api->drain) {
|
||||
ao->api->drain(ao);
|
||||
} else {
|
||||
ao_wait_drain(ao);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return ao_get_delay(ao) < AO_EOF_DELAY;
|
||||
}
|
||||
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
@@ -448,48 +368,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);
|
||||
}
|
||||
+2
-23
@@ -21,7 +21,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "misc/bstr.h"
|
||||
#include "bstr/bstr.h"
|
||||
#include "common/common.h"
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
@@ -36,9 +36,7 @@ enum aocontrol {
|
||||
AOCONTROL_SET_MUTE,
|
||||
// Has char* as argument, which contains the desired stream title.
|
||||
AOCONTROL_UPDATE_STREAM_TITLE,
|
||||
// the AO does the equivalent of af_volume (return CONTROL_TRUE if yes)
|
||||
AOCONTROL_HAS_SOFT_VOLUME,
|
||||
// like above, but volume persists (per app), mpv won't restore volume
|
||||
AOCONTROL_HAS_TEMP_VOLUME,
|
||||
AOCONTROL_HAS_PER_APP_VOLUME,
|
||||
};
|
||||
|
||||
@@ -46,25 +44,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 +73,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 */
|
||||
+163
-327
@@ -31,6 +31,7 @@
|
||||
#include <sys/time.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -38,13 +39,12 @@
|
||||
#include "options/options.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/msg.h"
|
||||
#include "osdep/endian.h"
|
||||
|
||||
#define ALSA_PCM_NEW_HW_PARAMS_API
|
||||
#define ALSA_PCM_NEW_SW_PARAMS_API
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#define HAVE_CHMAP_API \
|
||||
(defined(SND_CHMAP_API_VERSION) && SND_CHMAP_API_VERSION >= (1 << 16))
|
||||
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "audio/format.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
|
||||
|
||||
@@ -64,7 +64,6 @@ struct priv {
|
||||
char *cfg_mixer_name;
|
||||
int cfg_mixer_index;
|
||||
int cfg_resample;
|
||||
int cfg_ni;
|
||||
};
|
||||
|
||||
#define BUFFER_TIME 250000 // 250ms
|
||||
@@ -78,12 +77,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;
|
||||
@@ -102,11 +99,13 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
long get_vol, set_vol;
|
||||
float f_multi;
|
||||
|
||||
if (AF_FORMAT_IS_SPECIAL(ao->format))
|
||||
if (AF_FORMAT_IS_IEC61937(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 +137,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 +149,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: {
|
||||
@@ -207,15 +208,25 @@ alsa_error:
|
||||
static const int mp_to_alsa_format[][2] = {
|
||||
{AF_FORMAT_S8, SND_PCM_FORMAT_S8},
|
||||
{AF_FORMAT_U8, SND_PCM_FORMAT_U8},
|
||||
{AF_FORMAT_U16, SND_PCM_FORMAT_U16},
|
||||
{AF_FORMAT_S16, SND_PCM_FORMAT_S16},
|
||||
{AF_FORMAT_U32, SND_PCM_FORMAT_U32},
|
||||
{AF_FORMAT_S32, SND_PCM_FORMAT_S32},
|
||||
{AF_FORMAT_U24,
|
||||
MP_SELECT_LE_BE(SND_PCM_FORMAT_U24_3LE, SND_PCM_FORMAT_U24_3BE)},
|
||||
{AF_FORMAT_S24,
|
||||
MP_SELECT_LE_BE(SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S24_3BE)},
|
||||
{AF_FORMAT_FLOAT, SND_PCM_FORMAT_FLOAT},
|
||||
{AF_FORMAT_U16_LE, SND_PCM_FORMAT_U16_LE},
|
||||
{AF_FORMAT_U16_BE, SND_PCM_FORMAT_U16_BE},
|
||||
{AF_FORMAT_S16_LE, SND_PCM_FORMAT_S16_LE},
|
||||
{AF_FORMAT_S16_BE, SND_PCM_FORMAT_S16_BE},
|
||||
{AF_FORMAT_U32_LE, SND_PCM_FORMAT_U32_LE},
|
||||
{AF_FORMAT_U32_BE, SND_PCM_FORMAT_U32_BE},
|
||||
{AF_FORMAT_S32_LE, SND_PCM_FORMAT_S32_LE},
|
||||
{AF_FORMAT_S32_BE, SND_PCM_FORMAT_S32_BE},
|
||||
{AF_FORMAT_U24_LE, SND_PCM_FORMAT_U24_3LE},
|
||||
{AF_FORMAT_U24_BE, SND_PCM_FORMAT_U24_3BE},
|
||||
{AF_FORMAT_S24_LE, SND_PCM_FORMAT_S24_3LE},
|
||||
{AF_FORMAT_S24_BE, SND_PCM_FORMAT_S24_3BE},
|
||||
{AF_FORMAT_FLOAT_LE, SND_PCM_FORMAT_FLOAT_LE},
|
||||
{AF_FORMAT_FLOAT_BE, SND_PCM_FORMAT_FLOAT_BE},
|
||||
{AF_FORMAT_AC3_LE, SND_PCM_FORMAT_S16_LE},
|
||||
{AF_FORMAT_AC3_BE, SND_PCM_FORMAT_S16_BE},
|
||||
{AF_FORMAT_IEC61937_LE, SND_PCM_FORMAT_S16_LE},
|
||||
{AF_FORMAT_IEC61937_BE, SND_PCM_FORMAT_S16_BE},
|
||||
{AF_FORMAT_MPEG2, SND_PCM_FORMAT_MPEG},
|
||||
{AF_FORMAT_UNKNOWN, SND_PCM_FORMAT_UNKNOWN},
|
||||
};
|
||||
|
||||
@@ -229,98 +240,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 +257,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 +270,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 +302,34 @@ static int map_iec958_srate(int srate)
|
||||
}
|
||||
}
|
||||
|
||||
// ALSA device strings can have parameters. They are usually appended to the
|
||||
// device name. Since there can be various forms, and we (sometimes) want to
|
||||
// append them to unknown device strings, which possibly already include params.
|
||||
static char *append_params(void *ta_parent, const char *device, const char *p)
|
||||
{
|
||||
if (!p || !p[0])
|
||||
return talloc_strdup(ta_parent, device);
|
||||
|
||||
int len = strlen(device);
|
||||
char *end = strchr(device, ':');
|
||||
if (!end) {
|
||||
/* no existing parameters: add it behind device name */
|
||||
return talloc_asprintf(ta_parent, "%s:%s", device, p);
|
||||
} else if (end[1] == '\0') {
|
||||
/* ":" but no parameters */
|
||||
return talloc_asprintf(ta_parent, "%s%s", device, p);
|
||||
} else if (end[1] == '{' && device[len - 1] == '}') {
|
||||
/* parameters in config syntax: add it inside the { } block */
|
||||
return talloc_asprintf(ta_parent, "%.*s %s}", len - 1, device, p);
|
||||
} else {
|
||||
/* a simple list of parameters: add it at the end of the list */
|
||||
return talloc_asprintf(ta_parent, "%s,%s", device, p);
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
static int try_open_device(struct ao *ao, const char *device, int open_mode)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format)) {
|
||||
void *tmp = talloc_new(NULL);
|
||||
/* to set the non-audio bit, use AES0=6 */
|
||||
char *params = talloc_asprintf(tmp,
|
||||
"AES0=%d,AES1=%d,AES2=0,AES3=%d",
|
||||
IEC958_AES0_NONAUDIO | IEC958_AES0_PRO_EMPHASIS_NONE,
|
||||
IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER,
|
||||
map_iec958_srate(ao->samplerate));
|
||||
const char *ac3_device = append_params(tmp, device, params);
|
||||
const char *ac3_device = device;
|
||||
int len = strlen(device);
|
||||
char *end = strchr(device, ':');
|
||||
if (!end) {
|
||||
/* no existing parameters: add it behind device name */
|
||||
ac3_device = talloc_asprintf(tmp, "%s:%s", device, params);
|
||||
} else if (end[1] == '\0') {
|
||||
/* ":" but no parameters */
|
||||
ac3_device = talloc_asprintf(tmp, "%s%s", device, params);
|
||||
} else if (end[1] == '{' && device[len - 1] == '}') {
|
||||
/* parameters in config syntax: add it inside the { } block */
|
||||
ac3_device = talloc_asprintf(tmp, "%.*s %s}", len - 1, device, params);
|
||||
} else {
|
||||
/* a simple list of parameters: add it at the end of the list */
|
||||
ac3_device = talloc_asprintf(tmp, "%s,%s", device, params);
|
||||
}
|
||||
int err = snd_pcm_open
|
||||
(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, open_mode);
|
||||
talloc_free(tmp);
|
||||
@@ -427,49 +340,46 @@ 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;
|
||||
|
||||
if (!p->cfg_ni)
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
struct mp_chmap implied_chmap = ao->channels;
|
||||
/* switch for spdif
|
||||
* sets opening sequence for SPDIF
|
||||
* sets also the playback and other switches 'on the fly'
|
||||
* while opening the abstract alias for the spdif subdevice
|
||||
* 'iec958'
|
||||
*/
|
||||
const char *device;
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format)) {
|
||||
device = "iec958";
|
||||
MP_VERBOSE(ao, "playing AC3/iec61937/iec958, %i channels\n",
|
||||
ao->channels.num);
|
||||
} else {
|
||||
device = select_chmap(ao, &implied_chmap);
|
||||
// Not-default likely means a hw device - enable software conversions.
|
||||
if (strcmp(device, "default") != 0)
|
||||
device = select_chmap(ao);
|
||||
if (strcmp(device, "default") != 0 && (ao->format & AF_FORMAT_F)) {
|
||||
// hack - use the converter plugin (why the heck?)
|
||||
device = talloc_asprintf(ao, "plug:%s", device);
|
||||
}
|
||||
}
|
||||
if (ao->device)
|
||||
device = ao->device;
|
||||
if (p->cfg_device && p->cfg_device[0])
|
||||
device = p->cfg_device;
|
||||
|
||||
MP_VERBOSE(ao, "using device: %s\n", device);
|
||||
MP_VERBOSE(ao, "using ALSA version: %s\n", snd_asoundlib_version());
|
||||
|
||||
err = try_open_device(ao, device, p->cfg_block ? 0 : SND_PCM_NONBLOCK);
|
||||
int open_mode = p->cfg_block ? 0 : SND_PCM_NONBLOCK;
|
||||
//modes = 0, SND_PCM_NONBLOCK, SND_PCM_ASYNC
|
||||
err = try_open_device(ao, device, open_mode);
|
||||
if (err < 0) {
|
||||
if (err != -EBUSY && !p->cfg_block) {
|
||||
MP_WARN(ao, "Open in nonblock-mode "
|
||||
@@ -492,18 +402,11 @@ 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");
|
||||
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format)) {
|
||||
if (ao->format == AF_FORMAT_S_MP3) {
|
||||
p->alsa_fmt = SND_PCM_FORMAT_MPEG;
|
||||
} else {
|
||||
p->alsa_fmt = SND_PCM_FORMAT_S16;
|
||||
}
|
||||
} else {
|
||||
p->alsa_fmt = find_alsa_format(ao->format);
|
||||
}
|
||||
p->alsa_fmt = find_alsa_format(ao->format);
|
||||
if (p->alsa_fmt == SND_PCM_FORMAT_UNKNOWN) {
|
||||
p->alsa_fmt = SND_PCM_FORMAT_S16;
|
||||
ao->format = AF_FORMAT_S16;
|
||||
@@ -511,12 +414,15 @@ static int init(struct ao *ao)
|
||||
|
||||
err = snd_pcm_hw_params_test_format(p->alsa, alsa_hwparams, p->alsa_fmt);
|
||||
if (err < 0) {
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format))
|
||||
CHECK_ALSA_ERROR("Unable to set IEC61937 format");
|
||||
MP_INFO(ao, "Format %s is not supported by hardware, trying default.\n",
|
||||
af_fmt_to_str(ao->format));
|
||||
p->alsa_fmt = SND_PCM_FORMAT_S16;
|
||||
ao->format = AF_FORMAT_S16;
|
||||
p->alsa_fmt = SND_PCM_FORMAT_S16_LE;
|
||||
if (AF_FORMAT_IS_AC3(ao->format))
|
||||
ao->format = AF_FORMAT_AC3_LE;
|
||||
else if (AF_FORMAT_IS_IEC61937(ao->format))
|
||||
ao->format = AF_FORMAT_IEC61937_LE;
|
||||
else
|
||||
ao->format = AF_FORMAT_S16_LE;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_set_format(p->alsa, alsa_hwparams, p->alsa_fmt);
|
||||
@@ -533,24 +439,11 @@ static int init(struct ao *ao)
|
||||
}
|
||||
CHECK_ALSA_ERROR("Unable to set access type");
|
||||
|
||||
struct mp_chmap dev_chmap = ao->channels;
|
||||
if (query_chmaps(ao, &dev_chmap)) {
|
||||
ao->channels = dev_chmap;
|
||||
} else {
|
||||
dev_chmap.num = 0;
|
||||
ao->channels = implied_chmap;
|
||||
}
|
||||
|
||||
int num_channels = ao->channels.num;
|
||||
err = snd_pcm_hw_params_set_channels_near
|
||||
(p->alsa, alsa_hwparams, &num_channels);
|
||||
CHECK_ALSA_ERROR("Unable to set channels");
|
||||
|
||||
if (num_channels > MP_NUM_CHANNELS) {
|
||||
MP_FATAL(ao, "Too many audio channels (%d).\n", num_channels);
|
||||
goto alsa_error;
|
||||
}
|
||||
|
||||
if (num_channels != ao->channels.num) {
|
||||
MP_ERR(ao, "Couldn't get requested number of channels.\n");
|
||||
mp_chmap_from_channels_alsa(&ao->channels, num_channels);
|
||||
@@ -569,79 +462,25 @@ static int init(struct ao *ao)
|
||||
|
||||
err = snd_pcm_hw_params_set_buffer_time_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){BUFFER_TIME}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set buffer time near");
|
||||
CHECK_ALSA_ERROR("Unable to set buffer time near");
|
||||
|
||||
err = snd_pcm_hw_params_set_periods_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){FRAGCOUNT}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set periods");
|
||||
CHECK_ALSA_ERROR("Unable to set periods");
|
||||
|
||||
/* finally install hardware parameters */
|
||||
err = snd_pcm_hw_params(p->alsa, alsa_hwparams);
|
||||
CHECK_ALSA_ERROR("Unable to set hw-parameters");
|
||||
|
||||
/* end setting hw-params */
|
||||
// end setting hw-params
|
||||
|
||||
#if HAVE_CHMAP_API
|
||||
if (mp_chmap_is_valid(&dev_chmap)) {
|
||||
snd_pcm_chmap_t *alsa_chmap =
|
||||
calloc(1, sizeof(*alsa_chmap) +
|
||||
sizeof(alsa_chmap->pos[0]) * dev_chmap.num);
|
||||
if (!alsa_chmap)
|
||||
goto alsa_error;
|
||||
|
||||
alsa_chmap->channels = dev_chmap.num;
|
||||
for (int c = 0; c < dev_chmap.num; c++)
|
||||
alsa_chmap->pos[c] = find_alsa_channel(dev_chmap.speaker[c]);
|
||||
|
||||
char tmp[128];
|
||||
if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0)
|
||||
MP_VERBOSE(ao, "trying to set ALSA channel map: %s\n", tmp);
|
||||
|
||||
err = snd_pcm_set_chmap(p->alsa, alsa_chmap);
|
||||
if (err == -ENXIO) {
|
||||
MP_WARN(ao, "Device does not support requested channel map\n");
|
||||
} else {
|
||||
CHECK_ALSA_WARN("Channel map setup failed");
|
||||
}
|
||||
}
|
||||
|
||||
snd_pcm_chmap_t *alsa_chmap = snd_pcm_get_chmap(p->alsa);
|
||||
if (alsa_chmap) {
|
||||
char tmp[128];
|
||||
if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0)
|
||||
MP_VERBOSE(ao, "channel map reported by ALSA: %s\n", tmp);
|
||||
|
||||
struct mp_chmap chmap = {.num = alsa_chmap->channels};
|
||||
for (int c = 0; c < chmap.num; c++)
|
||||
chmap.speaker[c] = find_mp_channel(alsa_chmap->pos[c]);
|
||||
|
||||
MP_VERBOSE(ao, "which we understand as: %s\n", mp_chmap_to_str(&chmap));
|
||||
|
||||
if (mp_chmap_is_valid(&chmap)) {
|
||||
if (mp_chmap_equals(&chmap, &ao->channels)) {
|
||||
MP_VERBOSE(ao, "which is what we requested.\n");
|
||||
} else if (chmap.num == ao->channels.num) {
|
||||
MP_VERBOSE(ao, "using the ALSA channel map.\n");
|
||||
ao->channels = chmap;
|
||||
} else {
|
||||
MP_WARN(ao, "ALSA channel map conflicts with channel count!\n");
|
||||
}
|
||||
} else {
|
||||
MP_WARN(ao, "Got unknown channel map from ALSA.\n");
|
||||
}
|
||||
|
||||
free(alsa_chmap);
|
||||
}
|
||||
#endif
|
||||
|
||||
snd_pcm_uframes_t bufsize;
|
||||
// gets buffersize for control
|
||||
err = snd_pcm_hw_params_get_buffer_size(alsa_hwparams, &bufsize);
|
||||
CHECK_ALSA_ERROR("Unable to get buffersize");
|
||||
|
||||
p->buffersize = bufsize;
|
||||
MP_VERBOSE(ao, "got buffersize=%i samples\n", p->buffersize);
|
||||
|
||||
snd_pcm_uframes_t chunk_size;
|
||||
err = snd_pcm_hw_params_get_period_size(alsa_hwparams, &chunk_size, NULL);
|
||||
CHECK_ALSA_ERROR("Unable to get period size");
|
||||
|
||||
@@ -652,7 +491,6 @@ static int init(struct ao *ao)
|
||||
err = snd_pcm_sw_params_current(p->alsa, alsa_swparams);
|
||||
CHECK_ALSA_ERROR("Unable to get sw-parameters");
|
||||
|
||||
snd_pcm_uframes_t boundary;
|
||||
err = snd_pcm_sw_params_get_boundary(alsa_swparams, &boundary);
|
||||
CHECK_ALSA_ERROR("Unable to get boundary");
|
||||
|
||||
@@ -672,17 +510,40 @@ static int init(struct ao *ao)
|
||||
CHECK_ALSA_ERROR("Unable to set silence size");
|
||||
|
||||
err = snd_pcm_sw_params(p->alsa, alsa_swparams);
|
||||
CHECK_ALSA_ERROR("Unable to set sw-parameters");
|
||||
CHECK_ALSA_ERROR("Unable to get sw-parameters");
|
||||
|
||||
/* end setting sw-params */
|
||||
|
||||
p->can_pause = snd_pcm_hw_params_can_pause(alsa_hwparams);
|
||||
|
||||
MP_VERBOSE(ao, "opened: %d Hz/%d channels/%d bps/%d samples buffer/%s\n",
|
||||
ao->samplerate, ao->channels.num, af_fmt2bits(ao->format),
|
||||
p->buffersize, snd_pcm_format_description(p->alsa_fmt));
|
||||
|
||||
return 0;
|
||||
|
||||
alsa_error:
|
||||
uninit(ao);
|
||||
return -1;
|
||||
} // end init
|
||||
|
||||
|
||||
/* close audio device */
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (p->alsa) {
|
||||
int err;
|
||||
|
||||
err = snd_pcm_close(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm close error");
|
||||
|
||||
MP_VERBOSE(ao, "uninit: pcm closed\n");
|
||||
}
|
||||
|
||||
alsa_error:
|
||||
p->alsa = NULL;
|
||||
}
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
@@ -691,46 +552,6 @@ static void drain(struct ao *ao)
|
||||
snd_pcm_drain(p->alsa);
|
||||
}
|
||||
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_status_t *status;
|
||||
int err;
|
||||
|
||||
snd_pcm_status_alloca(&status);
|
||||
|
||||
err = snd_pcm_status(p->alsa, status);
|
||||
CHECK_ALSA_ERROR("cannot get pcm status");
|
||||
|
||||
unsigned space = snd_pcm_status_get_avail(status);
|
||||
if (space > p->buffersize) // Buffer underrun?
|
||||
space = p->buffersize;
|
||||
return space / p->outburst * p->outburst;
|
||||
|
||||
alsa_error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* delay in seconds between first and last sample in buffer */
|
||||
static double get_delay(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_sframes_t delay;
|
||||
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED)
|
||||
return p->delay_before_pause;
|
||||
|
||||
if (snd_pcm_delay(p->alsa, &delay) < 0)
|
||||
return 0;
|
||||
|
||||
if (delay < 0) {
|
||||
/* underrun - move the application pointer forward to catch up */
|
||||
snd_pcm_forward(p->alsa, -delay);
|
||||
delay = 0;
|
||||
}
|
||||
return delay / (double)ao->samplerate;
|
||||
}
|
||||
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
@@ -747,7 +568,7 @@ static void audio_pause(struct ao *ao)
|
||||
if (snd_pcm_delay(p->alsa, &p->prepause_frames) < 0
|
||||
|| p->prepause_frames < 0)
|
||||
p->prepause_frames = 0;
|
||||
p->delay_before_pause = p->prepause_frames / (double)ao->samplerate;
|
||||
p->delay_before_pause = p->prepause_frames / (float)ao->samplerate;
|
||||
|
||||
err = snd_pcm_drop(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm drop error");
|
||||
@@ -784,6 +605,7 @@ static void audio_resume(struct ao *ao)
|
||||
alsa_error: ;
|
||||
}
|
||||
|
||||
/* stop playing and empty buffers (for seeking/pause) */
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
@@ -816,7 +638,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
res = snd_pcm_writei(p->alsa, data[0], samples);
|
||||
}
|
||||
|
||||
if (res == -EINTR || res == -EAGAIN) { /* retry */
|
||||
if (res == -EINTR) {
|
||||
/* nothing to do */
|
||||
res = 0;
|
||||
} else if (res == -ESTRPIPE) { /* suspend */
|
||||
audio_resume(ao);
|
||||
@@ -835,6 +658,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;
|
||||
|
||||
alsa_error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* delay in seconds between first and last sample in buffer */
|
||||
static float get_delay(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_sframes_t delay;
|
||||
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED)
|
||||
return p->delay_before_pause;
|
||||
|
||||
if (snd_pcm_delay(p->alsa, &delay) < 0)
|
||||
return 0;
|
||||
|
||||
if (delay < 0) {
|
||||
/* underrun - move the application pointer forward to catch up */
|
||||
snd_pcm_forward(p->alsa, -delay);
|
||||
delay = 0;
|
||||
}
|
||||
return (float)delay / (float)ao->samplerate;
|
||||
}
|
||||
|
||||
#define MAX_POLL_FDS 20
|
||||
static int audio_wait(struct ao *ao, pthread_mutex_t *lock)
|
||||
{
|
||||
@@ -869,34 +732,10 @@ alsa_error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
void **hints;
|
||||
if (snd_device_name_hint(-1, "pcm", &hints) < 0)
|
||||
return;
|
||||
|
||||
for (int n = 0; hints[n]; n++) {
|
||||
char *name = snd_device_name_get_hint(hints[n], "NAME");
|
||||
char *desc = snd_device_name_get_hint(hints[n], "DESC");
|
||||
char *io = snd_device_name_get_hint(hints[n], "IOID");
|
||||
if (io && strcmp(io, "Output") != 0)
|
||||
continue;
|
||||
char desc2[1024];
|
||||
snprintf(desc2, sizeof(desc2), "%s", desc ? desc : "");
|
||||
for (int i = 0; desc2[i]; i++) {
|
||||
if (desc2[i] == '\n')
|
||||
desc2[i] = '/';
|
||||
}
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc2});
|
||||
}
|
||||
|
||||
snd_device_name_free_hint(hints);
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_alsa = {
|
||||
.description = "ALSA audio output",
|
||||
.description = "ALSA-0.9.x-1.x audio output",
|
||||
.name = "alsa",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
@@ -910,14 +749,12 @@ 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,
|
||||
.cfg_mixer_device = "default",
|
||||
.cfg_mixer_name = "Master",
|
||||
.cfg_mixer_index = 0,
|
||||
.cfg_ni = 0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("device", cfg_device, 0),
|
||||
@@ -926,7 +763,6 @@ const struct ao_driver audio_out_alsa = {
|
||||
OPT_STRING("mixer-device", cfg_mixer_device, 0),
|
||||
OPT_STRING("mixer-name", cfg_mixer_name, 0),
|
||||
OPT_INTRANGE("mixer-index", cfg_mixer_index, 0, 0, 99),
|
||||
OPT_FLAG("non-interleaved", cfg_ni, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+538
-278
@@ -1,21 +1,39 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
* CoreAudio audio output driver for Mac OS X
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* original copyright (C) Timothy J. Wood - Aug 2000
|
||||
* ported to MPlayer libao2 by Dan Christiansen
|
||||
*
|
||||
* Chris Roccati
|
||||
* Stefano Pigozzi
|
||||
*
|
||||
* The S/PDIF part of the code is based on the auhal audio output
|
||||
* module from VideoLAN:
|
||||
* Copyright (c) 2006 Derk-Jan Hartman <hartman at videolan dot org>
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer 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,
|
||||
* MPlayer 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/>.
|
||||
* along with MPlayer; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <CoreAudio/HostTime.h>
|
||||
/*
|
||||
* The MacOS X CoreAudio framework doesn't mesh as simply as some
|
||||
* simpler frameworks do. This is due to the fact that CoreAudio pulls
|
||||
* audio samples rather than having them pushed at it (which is nice
|
||||
* when you are wanting to do good buffering of audio).
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "ao.h"
|
||||
@@ -28,60 +46,61 @@
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
|
||||
struct priv {
|
||||
AudioDeviceID device;
|
||||
AudioUnit audio_unit;
|
||||
static void audio_pause(struct ao *ao);
|
||||
static void audio_resume(struct ao *ao);
|
||||
static void reset(struct ao *ao);
|
||||
|
||||
uint64_t hw_latency_us;
|
||||
static void print_buffer(struct ao *ao, struct mp_ring *buffer)
|
||||
{
|
||||
void *tctx = talloc_new(NULL);
|
||||
MP_VERBOSE(ao, "%s\n", mp_ring_repr(buffer, tctx));
|
||||
talloc_free(tctx);
|
||||
}
|
||||
|
||||
struct priv_d {
|
||||
// digital render callback
|
||||
AudioDeviceIOProcID render_cb;
|
||||
|
||||
// pid set for hog mode, (-1) means that hog mode on the device was
|
||||
// released. hog mode is exclusive access to a device
|
||||
pid_t hog_pid;
|
||||
|
||||
// stream selected for digital playback by the detection in init
|
||||
AudioStreamID stream;
|
||||
|
||||
// stream index in an AudioBufferList
|
||||
int stream_idx;
|
||||
|
||||
// format we changed the stream to: for the digital case each application
|
||||
// sets the stream format for a device to what it needs
|
||||
AudioStreamBasicDescription stream_asbd;
|
||||
AudioStreamBasicDescription original_asbd;
|
||||
|
||||
bool changed_mixing;
|
||||
int stream_asbd_changed;
|
||||
bool muted;
|
||||
};
|
||||
|
||||
bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
struct mp_chmap *chmap);
|
||||
struct priv {
|
||||
AudioDeviceID device; // selected device
|
||||
bool is_digital; // running in digital mode?
|
||||
|
||||
static int64_t ca_frames_to_us(struct ao *ao, uint32_t frames)
|
||||
AudioUnit audio_unit; // AudioUnit for lpcm output
|
||||
|
||||
bool paused;
|
||||
|
||||
struct mp_ring *buffer;
|
||||
struct priv_d *digital;
|
||||
|
||||
// options
|
||||
int opt_device_id;
|
||||
int opt_list;
|
||||
};
|
||||
|
||||
static int get_ring_size(struct ao *ao)
|
||||
{
|
||||
return frames / (float) ao->samplerate * 1e6;
|
||||
}
|
||||
|
||||
static int64_t ca_get_hardware_latency(struct ao *ao) {
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
double audiounit_latency_sec = 0.0;
|
||||
uint32_t size = sizeof(audiounit_latency_sec);
|
||||
OSStatus err = AudioUnitGetProperty(
|
||||
p->audio_unit,
|
||||
kAudioUnitProperty_Latency,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&audiounit_latency_sec,
|
||||
&size);
|
||||
CHECK_CA_ERROR("cannot get audio unit latency");
|
||||
|
||||
uint32_t frames = 0;
|
||||
err = CA_GET_O(p->device, kAudioDevicePropertyLatency, &frames);
|
||||
CHECK_CA_ERROR("cannot get device latency");
|
||||
|
||||
uint64_t audiounit_latency_us = audiounit_latency_sec * 1e6;
|
||||
uint64_t device_latency_us = ca_frames_to_us(ao, frames);
|
||||
|
||||
MP_VERBOSE(ao, "audiounit latency [us]: %lld\n", audiounit_latency_us);
|
||||
MP_VERBOSE(ao, "device latency [us]: %lld\n", device_latency_us);
|
||||
|
||||
return audiounit_latency_us + device_latency_us;
|
||||
|
||||
coreaudio_error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int64_t ca_get_latency(const AudioTimeStamp *ts)
|
||||
{
|
||||
uint64_t out = AudioConvertHostTimeToNanos(ts->mHostTime);
|
||||
uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
|
||||
|
||||
if (now > out)
|
||||
return 0;
|
||||
|
||||
return (out - now) * 1e-3;
|
||||
return af_fmt_seconds_to_bytes(
|
||||
ao->format, 0.5, ao->channels.num, ao->samplerate);
|
||||
}
|
||||
|
||||
static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||
@@ -90,130 +109,259 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||
{
|
||||
struct ao *ao = ctx;
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
AudioBuffer buf = buffer_list->mBuffers[0];
|
||||
int requested = buf.mDataByteSize;
|
||||
|
||||
int64_t end = mp_time_us();
|
||||
end += p->hw_latency_us + ca_get_latency(ts) + ca_frames_to_us(ao, frames);
|
||||
if (mp_ring_buffered(p->buffer) < requested) {
|
||||
MP_VERBOSE(ao, "buffer underrun\n");
|
||||
audio_pause(ao);
|
||||
memset(buf.mData, 0, requested);
|
||||
} else {
|
||||
mp_ring_read(p->buffer, buf.mData, requested);
|
||||
}
|
||||
|
||||
ao_read_data(ao, &buf.mData, frames, end);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static int get_volume(struct ao *ao, struct ao_control_vol *vol) {
|
||||
struct priv *p = ao->priv;
|
||||
float auvol;
|
||||
OSStatus err =
|
||||
AudioUnitGetParameter(p->audio_unit, kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Global, 0, &auvol);
|
||||
static OSStatus render_cb_digital(
|
||||
AudioDeviceID device, const AudioTimeStamp *ts,
|
||||
const void *in_data, const AudioTimeStamp *in_ts,
|
||||
AudioBufferList *out_data, const AudioTimeStamp *out_ts, void *ctx)
|
||||
{
|
||||
struct ao *ao = ctx;
|
||||
struct priv *p = ao->priv;
|
||||
struct priv_d *d = p->digital;
|
||||
AudioBuffer buf = out_data->mBuffers[d->stream_idx];
|
||||
int requested = buf.mDataByteSize;
|
||||
|
||||
CHECK_CA_ERROR("could not get HAL output volume");
|
||||
vol->left = vol->right = auvol * 100.0;
|
||||
return CONTROL_TRUE;
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
if (d->muted)
|
||||
mp_ring_drain(p->buffer, requested);
|
||||
else
|
||||
mp_ring_read(p->buffer, buf.mData, requested);
|
||||
|
||||
static int set_volume(struct ao *ao, struct ao_control_vol *vol) {
|
||||
struct priv *p = ao->priv;
|
||||
float auvol = (vol->left + vol->right) / 200.0;
|
||||
OSStatus err =
|
||||
AudioUnitSetParameter(p->audio_unit, kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Global, 0, auvol, 0);
|
||||
CHECK_CA_ERROR("could not set HAL output volume");
|
||||
return CONTROL_TRUE;
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
ao_control_vol_t *control_vol;
|
||||
OSStatus err;
|
||||
Float32 vol;
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
return get_volume(ao, arg);
|
||||
control_vol = (ao_control_vol_t *)arg;
|
||||
if (p->is_digital) {
|
||||
struct priv_d *d = p->digital;
|
||||
// Digital output has no volume adjust.
|
||||
int digitalvol = d->muted ? 0 : 100;
|
||||
*control_vol = (ao_control_vol_t) {
|
||||
.left = digitalvol, .right = digitalvol,
|
||||
};
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
|
||||
err = AudioUnitGetParameter(p->audio_unit, kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Global, 0, &vol);
|
||||
|
||||
CHECK_CA_ERROR("could not get HAL output volume");
|
||||
control_vol->left = control_vol->right = vol * 100.0;
|
||||
return CONTROL_TRUE;
|
||||
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
return set_volume(ao, arg);
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
control_vol = (ao_control_vol_t *)arg;
|
||||
|
||||
if (p->is_digital) {
|
||||
struct priv_d *d = p->digital;
|
||||
// Digital output can not set volume. Here we have to return true
|
||||
// to make mixer forget it. Else mixer will add a soft filter,
|
||||
// that's not we expected and the filter not support ac3 stream
|
||||
// will cause mplayer die.
|
||||
|
||||
// Although not support set volume, but at least we support mute.
|
||||
// MPlayer set mute by set volume to zero, we handle it.
|
||||
if (control_vol->left == 0 && control_vol->right == 0)
|
||||
d->muted = true;
|
||||
else
|
||||
d->muted = false;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
|
||||
vol = (control_vol->left + control_vol->right) / 200.0;
|
||||
err = AudioUnitSetParameter(p->audio_unit, kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Global, 0, vol, 0);
|
||||
|
||||
CHECK_CA_ERROR("could not set HAL output volume");
|
||||
return CONTROL_TRUE;
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
|
||||
} // end switch
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static bool init_chmap(struct ao *ao);
|
||||
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);
|
||||
CHECK_CA_ERROR("failed to select device");
|
||||
|
||||
if (!init_chmap(ao))
|
||||
goto coreaudio_error;
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
AudioStreamBasicDescription asbd;
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
|
||||
if (!init_audiounit(ao, asbd))
|
||||
goto coreaudio_error;
|
||||
|
||||
return CONTROL_OK;
|
||||
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static bool init_chmap(struct ao *ao)
|
||||
static void print_list(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err;
|
||||
AudioChannelLayout *layouts;
|
||||
size_t n_layouts;
|
||||
char *help = talloc_strdup(NULL, "Available output devices:\n");
|
||||
|
||||
err = CA_GET_ARY_O(p->device,
|
||||
kAudioDevicePropertyPreferredChannelLayout,
|
||||
&layouts, &n_layouts);
|
||||
CHECK_CA_ERROR("could not get audio device prefered layouts");
|
||||
AudioDeviceID *devs;
|
||||
size_t n_devs;
|
||||
|
||||
struct mp_chmap_sel chmap_sel = {.tmp = p};
|
||||
for (int i = 0; i < n_layouts; i++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
if (ca_layout_to_mp_chmap(ao, &layouts[i], &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
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++) {
|
||||
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(layouts);
|
||||
|
||||
if (ao->channels.num < 3) {
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_from_channels(&chmap, ao->channels.num);
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
}
|
||||
|
||||
if (!ao_chmap_sel_adjust(ao, &chmap_sel, &ao->channels)) {
|
||||
MP_ERR(ao, "could not select a suitable channel map among the "
|
||||
"hardware supported ones. Make sure to configure your "
|
||||
"output device correctly in 'Audio MIDI Setup.app'\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
return true;
|
||||
talloc_free(devs);
|
||||
|
||||
coreaudio_error:
|
||||
return false;
|
||||
MP_INFO(ao, "%s", help);
|
||||
talloc_free(help);
|
||||
}
|
||||
|
||||
static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
static int init_lpcm(struct ao *ao, AudioStreamBasicDescription asbd);
|
||||
static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd);
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
OSStatus err;
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (p->opt_list) print_list(ao);
|
||||
|
||||
struct priv_d *d = talloc_zero(p, struct priv_d);
|
||||
|
||||
*d = (struct priv_d) {
|
||||
.muted = false,
|
||||
.stream_asbd_changed = 0,
|
||||
.hog_pid = -1,
|
||||
.stream = 0,
|
||||
.stream_idx = -1,
|
||||
.changed_mixing = false,
|
||||
};
|
||||
|
||||
p->digital = d;
|
||||
|
||||
ao->per_application_mixer = true;
|
||||
ao->no_persistent_volume = true;
|
||||
|
||||
AudioDeviceID selected_device = 0;
|
||||
if (p->opt_device_id < 0) {
|
||||
// device not set by user, get the default one
|
||||
err = CA_GET(kAudioObjectSystemObject,
|
||||
kAudioHardwarePropertyDefaultOutputDevice,
|
||||
&selected_device);
|
||||
CHECK_CA_ERROR("could not get default audio device");
|
||||
} else {
|
||||
selected_device = p->opt_device_id;
|
||||
}
|
||||
|
||||
if (mp_msg_test(ao->log, MSGL_V)) {
|
||||
char *name;
|
||||
err = CA_GET_STR(selected_device, kAudioObjectPropertyName, &name);
|
||||
CHECK_CA_ERROR("could not get selected audio device name");
|
||||
|
||||
MP_VERBOSE(ao, "selected audio output device: %s (%" PRIu32 ")\n",
|
||||
name, selected_device);
|
||||
|
||||
talloc_free(name);
|
||||
}
|
||||
|
||||
// Save selected device id
|
||||
p->device = selected_device;
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
bool supports_digital = false;
|
||||
/* Probe whether device support S/PDIF stream output if input is AC3 stream. */
|
||||
if (AF_FORMAT_IS_AC3(ao->format)) {
|
||||
if (ca_device_supports_digital(ao, selected_device))
|
||||
supports_digital = true;
|
||||
}
|
||||
|
||||
if (!supports_digital) {
|
||||
AudioChannelLayout *layouts;
|
||||
size_t n_layouts;
|
||||
err = CA_GET_ARY_O(selected_device,
|
||||
kAudioDevicePropertyPreferredChannelLayout,
|
||||
&layouts, &n_layouts);
|
||||
CHECK_CA_ERROR("could not get audio device prefered layouts");
|
||||
|
||||
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))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
}
|
||||
|
||||
talloc_free(layouts);
|
||||
|
||||
if (ao->channels.num < 3) {
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_from_channels(&chmap, ao->channels.num);
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
}
|
||||
|
||||
if (!ao_chmap_sel_adjust(ao, &chmap_sel, &ao->channels)) {
|
||||
MP_ERR(ao, "could not select a suitable channel map among the "
|
||||
"hardware supported ones. Make sure to configure your "
|
||||
"output device correctly in 'Audio MIDI Setup.app'\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
} // closes if (!supports_digital)
|
||||
|
||||
// Build ASBD for the input format
|
||||
AudioStreamBasicDescription asbd;
|
||||
asbd.mSampleRate = ao->samplerate;
|
||||
asbd.mFormatID = supports_digital ?
|
||||
kAudioFormat60958AC3 : kAudioFormatLinearPCM;
|
||||
asbd.mChannelsPerFrame = ao->channels.num;
|
||||
asbd.mBitsPerChannel = af_fmt2bits(ao->format);
|
||||
asbd.mFormatFlags = kAudioFormatFlagIsPacked;
|
||||
|
||||
if ((ao->format & AF_FORMAT_POINT_MASK) == AF_FORMAT_F)
|
||||
asbd.mFormatFlags |= kAudioFormatFlagIsFloat;
|
||||
|
||||
if ((ao->format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_SI)
|
||||
asbd.mFormatFlags |= kAudioFormatFlagIsSignedInteger;
|
||||
|
||||
if ((ao->format & AF_FORMAT_END_MASK) == AF_FORMAT_BE)
|
||||
asbd.mFormatFlags |= kAudioFormatFlagIsBigEndian;
|
||||
|
||||
asbd.mFramesPerPacket = 1;
|
||||
asbd.mBytesPerPacket = asbd.mBytesPerFrame =
|
||||
asbd.mFramesPerPacket * asbd.mChannelsPerFrame *
|
||||
(asbd.mBitsPerChannel / 8);
|
||||
|
||||
ca_print_asbd(ao, "source format:", &asbd);
|
||||
|
||||
if (supports_digital)
|
||||
return init_digital(ao, asbd);
|
||||
else
|
||||
return init_lpcm(ao, asbd);
|
||||
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static int init_lpcm(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
{
|
||||
OSStatus err;
|
||||
uint32_t size;
|
||||
@@ -221,9 +369,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,
|
||||
@@ -238,6 +386,7 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
err = AudioComponentInstanceNew(comp, &(p->audio_unit));
|
||||
CHECK_CA_ERROR("unable to open audio component");
|
||||
|
||||
// Initialize AudioUnit
|
||||
err = AudioUnitInitialize(p->audio_unit);
|
||||
CHECK_CA_ERROR_L(coreaudio_error_component,
|
||||
"unable to initialize audio unit");
|
||||
@@ -250,6 +399,7 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"unable to set the input format on the audio unit");
|
||||
|
||||
//Set the Current Device to the Default Output Unit.
|
||||
err = AudioUnitSetProperty(p->audio_unit,
|
||||
kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global, 0, &p->device,
|
||||
@@ -257,7 +407,8 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"can't link audio unit to selected device");
|
||||
|
||||
p->hw_latency_us = ca_get_hardware_latency(ao);
|
||||
p->buffer = mp_ring_new(p, get_ring_size(ao));
|
||||
print_buffer(ao, p->buffer);
|
||||
|
||||
AURenderCallbackStruct render_cb = (AURenderCallbackStruct) {
|
||||
.inputProc = render_cb_lpcm,
|
||||
@@ -272,174 +423,283 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"unable to set render callback on audio unit");
|
||||
|
||||
return true;
|
||||
reset(ao);
|
||||
return CONTROL_OK;
|
||||
|
||||
coreaudio_error_audiounit:
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
coreaudio_error_component:
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
coreaudio_error:
|
||||
return false;
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static void stop(struct ao *ao)
|
||||
static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = AudioOutputUnitStop(p->audio_unit);
|
||||
CHECK_CA_WARN("can't stop audio unit");
|
||||
struct priv_d *d = p->digital;
|
||||
OSStatus err = noErr;
|
||||
|
||||
uint32_t is_alive = 1;
|
||||
err = CA_GET(p->device, kAudioDevicePropertyDeviceIsAlive, &is_alive);
|
||||
CHECK_CA_WARN("could not check whether device is alive");
|
||||
|
||||
if (!is_alive)
|
||||
MP_WARN(ao , "device is not alive\n");
|
||||
|
||||
p->is_digital = 1;
|
||||
|
||||
err = ca_lock_device(p->device, &d->hog_pid);
|
||||
CHECK_CA_WARN("failed to set hogmode");
|
||||
|
||||
err = ca_disable_mixing(ao, p->device, &d->changed_mixing);
|
||||
CHECK_CA_WARN("failed to disable mixing");
|
||||
|
||||
AudioStreamID *streams;
|
||||
size_t n_streams;
|
||||
|
||||
/* Get a list of all the streams on this device. */
|
||||
err = CA_GET_ARY_O(p->device, kAudioDevicePropertyStreams,
|
||||
&streams, &n_streams);
|
||||
|
||||
CHECK_CA_ERROR("could not get number of streams");
|
||||
|
||||
for (int i = 0; i < n_streams && d->stream_idx < 0; i++) {
|
||||
bool digital = ca_stream_supports_digital(ao, streams[i]);
|
||||
|
||||
if (digital) {
|
||||
err = CA_GET(streams[i], kAudioStreamPropertyPhysicalFormat,
|
||||
&d->original_asbd);
|
||||
if (!CHECK_CA_WARN("could not get stream's physical format to "
|
||||
"revert to, getting the next one"))
|
||||
continue;
|
||||
|
||||
AudioStreamRangedDescription *formats;
|
||||
size_t n_formats;
|
||||
|
||||
err = CA_GET_ARY(streams[i],
|
||||
kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
&formats, &n_formats);
|
||||
|
||||
if (!CHECK_CA_WARN("could not get number of stream formats"))
|
||||
continue; // try next one
|
||||
|
||||
int req_rate_format = -1;
|
||||
int max_rate_format = -1;
|
||||
|
||||
d->stream = streams[i];
|
||||
d->stream_idx = i;
|
||||
|
||||
for (int j = 0; j < n_formats; j++)
|
||||
if (ca_format_is_digital(formats[j].mFormat)) {
|
||||
// select the digital format that has exactly the same
|
||||
// samplerate. If an exact match cannot be found, select
|
||||
// the format with highest samplerate as backup.
|
||||
if (formats[j].mFormat.mSampleRate == asbd.mSampleRate) {
|
||||
req_rate_format = j;
|
||||
break;
|
||||
} else if (max_rate_format < 0 ||
|
||||
formats[j].mFormat.mSampleRate >
|
||||
formats[max_rate_format].mFormat.mSampleRate)
|
||||
max_rate_format = j;
|
||||
}
|
||||
|
||||
if (req_rate_format >= 0)
|
||||
d->stream_asbd = formats[req_rate_format].mFormat;
|
||||
else
|
||||
d->stream_asbd = formats[max_rate_format].mFormat;
|
||||
|
||||
talloc_free(formats);
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
if (d->stream_idx < 0) {
|
||||
MP_WARN(ao , "can't find any digital output stream format\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
if (!ca_change_format(ao, d->stream, d->stream_asbd))
|
||||
goto coreaudio_error;
|
||||
|
||||
void *changed = (void *) &(d->stream_asbd_changed);
|
||||
err = ca_enable_device_listener(p->device, changed);
|
||||
CHECK_CA_ERROR("cannot install format change listener during init");
|
||||
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
if (!(p->stream_asdb.mFormatFlags & kAudioFormatFlagIsBigEndian))
|
||||
#else
|
||||
/* tell mplayer that we need a byteswap on AC3 streams, */
|
||||
if (d->stream_asbd.mFormatID & kAudioFormat60958AC3)
|
||||
ao->format = AF_FORMAT_AC3_LE;
|
||||
else if (d->stream_asbd.mFormatFlags & kAudioFormatFlagIsBigEndian)
|
||||
#endif
|
||||
MP_WARN(ao, "stream has non-native byte order, output may fail\n");
|
||||
|
||||
ao->samplerate = d->stream_asbd.mSampleRate;
|
||||
ao->bps = ao->samplerate *
|
||||
(d->stream_asbd.mBytesPerPacket /
|
||||
d->stream_asbd.mFramesPerPacket);
|
||||
|
||||
p->buffer = mp_ring_new(p, get_ring_size(ao));
|
||||
print_buffer(ao, p->buffer);
|
||||
|
||||
err = AudioDeviceCreateIOProcID(p->device,
|
||||
(AudioDeviceIOProc)render_cb_digital,
|
||||
(void *)ao,
|
||||
&d->render_cb);
|
||||
|
||||
CHECK_CA_ERROR("failed to register digital render callback");
|
||||
|
||||
reset(ao);
|
||||
|
||||
return CONTROL_TRUE;
|
||||
|
||||
coreaudio_error:
|
||||
err = ca_unlock_device(p->device, &d->hog_pid);
|
||||
CHECK_CA_WARN("can't release hog mode");
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static void start(struct ao *ao)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
struct priv_d *d = p->digital;
|
||||
void *output_samples = data[0];
|
||||
int num_bytes = samples * ao->sstride;
|
||||
|
||||
// Check whether we need to reset the digital output stream.
|
||||
if (p->is_digital && d->stream_asbd_changed) {
|
||||
d->stream_asbd_changed = 0;
|
||||
if (ca_stream_supports_digital(ao, d->stream)) {
|
||||
if (!ca_change_format(ao, d->stream, d->stream_asbd)) {
|
||||
MP_WARN(ao , "can't restore digital output\n");
|
||||
} else {
|
||||
MP_WARN(ao, "restoring digital output succeeded.\n");
|
||||
reset(ao);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int wrote = mp_ring_write(p->buffer, output_samples, num_bytes);
|
||||
audio_resume(ao);
|
||||
|
||||
return wrote / ao->sstride;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = AudioOutputUnitStart(p->audio_unit);
|
||||
CHECK_CA_WARN("can't start audio unit");
|
||||
audio_pause(ao);
|
||||
mp_ring_reset(p->buffer);
|
||||
}
|
||||
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
return mp_ring_available(p->buffer) / ao->sstride;
|
||||
}
|
||||
|
||||
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) / (float)ao->bps;
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
AudioOutputUnitStop(p->audio_unit);
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
OSStatus err = noErr;
|
||||
|
||||
if (!p->is_digital) {
|
||||
AudioOutputUnitStop(p->audio_unit);
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
} else {
|
||||
struct priv_d *d = p->digital;
|
||||
|
||||
void *changed = (void *) &(d->stream_asbd_changed);
|
||||
err = ca_disable_device_listener(p->device, changed);
|
||||
CHECK_CA_WARN("can't remove device listener, this may cause a crash");
|
||||
|
||||
err = AudioDeviceStop(p->device, d->render_cb);
|
||||
CHECK_CA_WARN("failed to stop audio device");
|
||||
|
||||
err = AudioDeviceDestroyIOProcID(p->device, d->render_cb);
|
||||
CHECK_CA_WARN("failed to remove device render callback");
|
||||
|
||||
if (!ca_change_format(ao, d->stream, d->original_asbd))
|
||||
MP_WARN(ao, "can't revert to original device format");
|
||||
|
||||
err = ca_enable_mixing(ao, p->device, d->changed_mixing);
|
||||
CHECK_CA_WARN("can't re-enable mixing");
|
||||
|
||||
err = ca_unlock_device(p->device, &d->hog_pid);
|
||||
CHECK_CA_WARN("can't release hog mode");
|
||||
}
|
||||
}
|
||||
|
||||
// Channel Mapping functions
|
||||
static const int speaker_map[][2] = {
|
||||
{ kAudioChannelLabel_Left, MP_SPEAKER_ID_FL },
|
||||
{ kAudioChannelLabel_Right, MP_SPEAKER_ID_FR },
|
||||
{ kAudioChannelLabel_Center, MP_SPEAKER_ID_FC },
|
||||
{ kAudioChannelLabel_LFEScreen, MP_SPEAKER_ID_LFE },
|
||||
{ kAudioChannelLabel_LeftSurround, MP_SPEAKER_ID_BL },
|
||||
{ kAudioChannelLabel_RightSurround, MP_SPEAKER_ID_BR },
|
||||
{ kAudioChannelLabel_LeftCenter, MP_SPEAKER_ID_FLC },
|
||||
{ kAudioChannelLabel_RightCenter, MP_SPEAKER_ID_FRC },
|
||||
{ kAudioChannelLabel_CenterSurround, MP_SPEAKER_ID_BC },
|
||||
{ kAudioChannelLabel_LeftSurroundDirect, MP_SPEAKER_ID_SL },
|
||||
{ kAudioChannelLabel_RightSurroundDirect, MP_SPEAKER_ID_SR },
|
||||
{ kAudioChannelLabel_TopCenterSurround, MP_SPEAKER_ID_TC },
|
||||
{ kAudioChannelLabel_VerticalHeightLeft, MP_SPEAKER_ID_TFL },
|
||||
{ kAudioChannelLabel_VerticalHeightCenter, MP_SPEAKER_ID_TFC },
|
||||
{ kAudioChannelLabel_VerticalHeightRight, MP_SPEAKER_ID_TFR },
|
||||
{ kAudioChannelLabel_TopBackLeft, MP_SPEAKER_ID_TBL },
|
||||
{ kAudioChannelLabel_TopBackCenter, MP_SPEAKER_ID_TBC },
|
||||
{ kAudioChannelLabel_TopBackRight, MP_SPEAKER_ID_TBR },
|
||||
|
||||
// unofficial extensions
|
||||
{ kAudioChannelLabel_RearSurroundLeft, MP_SPEAKER_ID_SDL },
|
||||
{ kAudioChannelLabel_RearSurroundRight, MP_SPEAKER_ID_SDR },
|
||||
{ kAudioChannelLabel_LeftWide, MP_SPEAKER_ID_WL },
|
||||
{ kAudioChannelLabel_RightWide, MP_SPEAKER_ID_WR },
|
||||
{ kAudioChannelLabel_LFE2, MP_SPEAKER_ID_LFE2 },
|
||||
|
||||
{ kAudioChannelLabel_HeadphonesLeft, MP_SPEAKER_ID_DL },
|
||||
{ kAudioChannelLabel_HeadphonesRight, MP_SPEAKER_ID_DR },
|
||||
|
||||
{ kAudioChannelLabel_Unknown, -1 },
|
||||
};
|
||||
|
||||
static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
for (int i = 0; speaker_map[i][1] >= 0; i++)
|
||||
if (speaker_map[i][0] == label)
|
||||
return speaker_map[i][1];
|
||||
return -1;
|
||||
}
|
||||
struct priv *p = ao->priv;
|
||||
OSErr err = noErr;
|
||||
|
||||
static void ca_log_layout(struct ao *ao, AudioChannelLayout *layout)
|
||||
{
|
||||
if (!mp_msg_test(ao->log, MSGL_V))
|
||||
if (p->paused)
|
||||
return;
|
||||
|
||||
AudioChannelDescription *descs = layout->mChannelDescriptions;
|
||||
|
||||
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 <%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 (!p->is_digital) {
|
||||
err = AudioOutputUnitStop(p->audio_unit);
|
||||
CHECK_CA_WARN("can't stop audio unit");
|
||||
} else {
|
||||
struct priv_d *d = p->digital;
|
||||
err = AudioDeviceStop(p->device, d->render_cb);
|
||||
CHECK_CA_WARN("can't stop digital device");
|
||||
}
|
||||
|
||||
p->paused = true;
|
||||
}
|
||||
|
||||
bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
struct mp_chmap *chmap)
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
AudioChannelLayoutTag tag = layout->mChannelLayoutTag;
|
||||
uint32_t layout_size = sizeof(layout);
|
||||
OSStatus err;
|
||||
struct priv *p = ao->priv;
|
||||
OSErr err = noErr;
|
||||
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
|
||||
err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(uint32_t),
|
||||
&layout->mChannelBitmap,
|
||||
&layout_size,
|
||||
layout);
|
||||
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions");
|
||||
} else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
|
||||
err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag),
|
||||
&layout->mChannelLayoutTag,
|
||||
&layout_size,
|
||||
layout);
|
||||
CHECK_CA_ERROR("failed to convert channel tag to descriptions");
|
||||
if (!p->paused)
|
||||
return;
|
||||
|
||||
if (!p->is_digital) {
|
||||
err = AudioOutputUnitStart(p->audio_unit);
|
||||
CHECK_CA_WARN("can't start audio unit");
|
||||
} else {
|
||||
struct priv_d *d = p->digital;
|
||||
err = AudioDeviceStart(p->device, d->render_cb);
|
||||
CHECK_CA_WARN("can't start digital device");
|
||||
}
|
||||
|
||||
ca_log_layout(ao, layout);
|
||||
|
||||
// If the channel layout uses channel descriptions, from my
|
||||
// experiments there are there three possibile cases:
|
||||
// * The description has a label kAudioChannelLabel_Unknown:
|
||||
// Can't do anything about this (looks like non surround
|
||||
// layouts are like this).
|
||||
// * The description uses positional information: this in
|
||||
// theory could be used but one would have to map spatial
|
||||
// positions to labels which is not really feasible.
|
||||
// * The description has a well known label which can be mapped
|
||||
// to the waveextensible definition: this is the kind of
|
||||
// descriptions we process here.
|
||||
|
||||
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=%d unusable to build channel "
|
||||
"bitmap, skipping layout\n", label);
|
||||
} else {
|
||||
chmap->speaker[n] = speaker;
|
||||
chmap->num = n + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return chmap->num > 0;
|
||||
coreaudio_error:
|
||||
ca_log_layout(ao, layout);
|
||||
return false;
|
||||
p->paused = false;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_coreaudio = {
|
||||
.description = "CoreAudio AudioUnit",
|
||||
.description = "CoreAudio (OS X Audio Output)",
|
||||
.name = "coreaudio",
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.play = play,
|
||||
.control = control,
|
||||
.pause = stop,
|
||||
.resume = start,
|
||||
.list_devs = ca_get_device_list,
|
||||
.get_space = get_space,
|
||||
.get_delay = get_delay,
|
||||
.reset = reset,
|
||||
.pause = audio_pause,
|
||||
.resume = audio_resume,
|
||||
.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}
|
||||
},
|
||||
};
|
||||
@@ -1,667 +0,0 @@
|
||||
/*
|
||||
* CoreAudio audio output driver for Mac OS X
|
||||
*
|
||||
* original copyright (C) Timothy J. Wood - Aug 2000
|
||||
* ported to MPlayer libao2 by Dan Christiansen
|
||||
*
|
||||
* Chris Roccati
|
||||
* Stefano Pigozzi
|
||||
*
|
||||
* The S/PDIF part of the code is based on the auhal audio output
|
||||
* module from VideoLAN:
|
||||
* Copyright (c) 2006 Derk-Jan Hartman <hartman at videolan dot org>
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer 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.
|
||||
*
|
||||
* MPlayer 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
|
||||
* along with MPlayer; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* The MacOS X CoreAudio framework doesn't mesh as simply as some
|
||||
* simpler frameworks do. This is due to the fact that CoreAudio pulls
|
||||
* audio samples rather than having them pushed at it (which is nice
|
||||
* when you are wanting to do good buffering of audio).
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "options/m_option.h"
|
||||
#include "misc/ring.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
|
||||
static void audio_pause(struct ao *ao);
|
||||
static void audio_resume(struct ao *ao);
|
||||
static void reset(struct ao *ao);
|
||||
|
||||
static bool ca_format_is_digital(AudioStreamBasicDescription asbd)
|
||||
{
|
||||
switch (asbd.mFormatID)
|
||||
case 'IAC3':
|
||||
case 'iac3':
|
||||
case kAudioFormat60958AC3:
|
||||
case kAudioFormatAC3:
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ca_stream_supports_digital(struct ao *ao, AudioStreamID stream)
|
||||
{
|
||||
AudioStreamRangedDescription *formats = NULL;
|
||||
size_t n_formats;
|
||||
|
||||
OSStatus err =
|
||||
CA_GET_ARY(stream, kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
&formats, &n_formats);
|
||||
|
||||
CHECK_CA_ERROR("Could not get number of stream formats.");
|
||||
|
||||
for (int i = 0; i < n_formats; i++) {
|
||||
AudioStreamBasicDescription asbd = formats[i].mFormat;
|
||||
ca_print_asbd(ao, "supported format:", &(asbd));
|
||||
if (ca_format_is_digital(asbd)) {
|
||||
talloc_free(formats);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(formats);
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ca_device_supports_digital(struct ao *ao, AudioDeviceID device)
|
||||
{
|
||||
AudioStreamID *streams = NULL;
|
||||
size_t n_streams;
|
||||
|
||||
/* Retrieve all the output streams. */
|
||||
OSStatus err =
|
||||
CA_GET_ARY_O(device, kAudioDevicePropertyStreams, &streams, &n_streams);
|
||||
|
||||
CHECK_CA_ERROR("could not get number of streams.");
|
||||
|
||||
for (int i = 0; i < n_streams; i++) {
|
||||
if (ca_stream_supports_digital(ao, streams[i])) {
|
||||
talloc_free(streams);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
static OSStatus ca_property_listener(
|
||||
AudioObjectPropertySelector selector,
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
void *talloc_ctx = talloc_new(NULL);
|
||||
|
||||
for (int i = 0; i < n_addresses; i++) {
|
||||
if (addresses[i].mSelector == selector) {
|
||||
if (data) *(volatile int *)data = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
talloc_free(talloc_ctx);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_stream_listener(
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
return ca_property_listener(kAudioStreamPropertyPhysicalFormat,
|
||||
object, n_addresses, addresses, data);
|
||||
}
|
||||
|
||||
static OSStatus ca_device_listener(
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
return ca_property_listener(kAudioDevicePropertyDeviceHasChanged,
|
||||
object, n_addresses, addresses, data);
|
||||
}
|
||||
|
||||
static OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid) {
|
||||
*pid = getpid();
|
||||
OSStatus err = CA_SET(device, kAudioDevicePropertyHogMode, pid);
|
||||
if (err != noErr)
|
||||
*pid = -1;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid) {
|
||||
if (*pid == getpid()) {
|
||||
*pid = -1;
|
||||
return CA_SET(device, kAudioDevicePropertyHogMode, &pid);
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_change_mixing(struct ao *ao, AudioDeviceID device,
|
||||
uint32_t val, bool *changed) {
|
||||
*changed = false;
|
||||
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertySupportsMixing,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
if (AudioObjectHasProperty(device, &p_addr)) {
|
||||
OSStatus err;
|
||||
Boolean writeable = 0;
|
||||
err = CA_SETTABLE(device, kAudioDevicePropertySupportsMixing,
|
||||
&writeable);
|
||||
|
||||
if (!CHECK_CA_WARN("can't tell if mixing property is settable")) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!writeable)
|
||||
return noErr;
|
||||
|
||||
err = CA_SET(device, kAudioDevicePropertySupportsMixing, &val);
|
||||
if (err != noErr)
|
||||
return err;
|
||||
|
||||
if (!CHECK_CA_WARN("can't set mix mode")) {
|
||||
return err;
|
||||
}
|
||||
|
||||
*changed = true;
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_disable_mixing(struct ao *ao,
|
||||
AudioDeviceID device, bool *changed) {
|
||||
return ca_change_mixing(ao, device, 0, changed);
|
||||
}
|
||||
|
||||
static OSStatus ca_enable_mixing(struct ao *ao,
|
||||
AudioDeviceID device, bool changed) {
|
||||
if (changed) {
|
||||
bool dont_care = false;
|
||||
return ca_change_mixing(ao, device, 1, &dont_care);
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_change_device_listening(AudioDeviceID device,
|
||||
void *flag, bool enabled)
|
||||
{
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertyDeviceHasChanged,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
if (enabled) {
|
||||
return AudioObjectAddPropertyListener(
|
||||
device, &p_addr, ca_device_listener, flag);
|
||||
} else {
|
||||
return AudioObjectRemovePropertyListener(
|
||||
device, &p_addr, ca_device_listener, flag);
|
||||
}
|
||||
}
|
||||
|
||||
static OSStatus ca_enable_device_listener(AudioDeviceID device, void *flag) {
|
||||
return ca_change_device_listening(device, flag, true);
|
||||
}
|
||||
|
||||
static OSStatus ca_disable_device_listener(AudioDeviceID device, void *flag) {
|
||||
return ca_change_device_listening(device, flag, false);
|
||||
}
|
||||
|
||||
static bool ca_change_format(struct ao *ao, AudioStreamID stream,
|
||||
AudioStreamBasicDescription change_format)
|
||||
{
|
||||
OSStatus err = noErr;
|
||||
AudioObjectPropertyAddress p_addr;
|
||||
volatile int stream_format_changed = 0;
|
||||
|
||||
ca_print_asbd(ao, "setting stream format:", &change_format);
|
||||
|
||||
/* Install the callback. */
|
||||
p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioStreamPropertyPhysicalFormat,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
err = AudioObjectAddPropertyListener(stream, &p_addr, ca_stream_listener,
|
||||
(void *)&stream_format_changed);
|
||||
if (!CHECK_CA_WARN("can't add property listener during format change")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Change the format. */
|
||||
err = CA_SET(stream, kAudioStreamPropertyPhysicalFormat, &change_format);
|
||||
if (!CHECK_CA_WARN("error changing physical format")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The AudioStreamSetProperty is not only asynchronious,
|
||||
* it is also not Atomic, in its behaviour.
|
||||
* Therefore we check 5 times before we really give up. */
|
||||
bool format_set = false;
|
||||
for (int i = 0; !format_set && i < 5; i++) {
|
||||
for (int j = 0; !stream_format_changed && j < 50; j++)
|
||||
mp_sleep_us(10000);
|
||||
|
||||
if (stream_format_changed) {
|
||||
stream_format_changed = 0;
|
||||
} else {
|
||||
MP_VERBOSE(ao, "reached timeout\n");
|
||||
}
|
||||
|
||||
AudioStreamBasicDescription actual_format;
|
||||
err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &actual_format);
|
||||
|
||||
ca_print_asbd(ao, "actual format in use:", &actual_format);
|
||||
if (actual_format.mSampleRate == change_format.mSampleRate &&
|
||||
actual_format.mFormatID == change_format.mFormatID &&
|
||||
actual_format.mFramesPerPacket == change_format.mFramesPerPacket) {
|
||||
format_set = true;
|
||||
}
|
||||
}
|
||||
|
||||
err = AudioObjectRemovePropertyListener(stream, &p_addr, ca_stream_listener,
|
||||
(void *)&stream_format_changed);
|
||||
|
||||
if (!CHECK_CA_WARN("can't remove property listener")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return format_set;
|
||||
}
|
||||
|
||||
|
||||
struct priv {
|
||||
AudioDeviceID device; // selected device
|
||||
|
||||
bool paused;
|
||||
|
||||
struct mp_ring *buffer;
|
||||
|
||||
// digital render callback
|
||||
AudioDeviceIOProcID render_cb;
|
||||
|
||||
// pid set for hog mode, (-1) means that hog mode on the device was
|
||||
// released. hog mode is exclusive access to a device
|
||||
pid_t hog_pid;
|
||||
|
||||
// stream selected for digital playback by the detection in init
|
||||
AudioStreamID stream;
|
||||
|
||||
// stream index in an AudioBufferList
|
||||
int stream_idx;
|
||||
|
||||
// format we changed the stream to: for the digital case each application
|
||||
// sets the stream format for a device to what it needs
|
||||
AudioStreamBasicDescription stream_asbd;
|
||||
AudioStreamBasicDescription original_asbd;
|
||||
|
||||
bool changed_mixing;
|
||||
int stream_asbd_changed;
|
||||
bool muted;
|
||||
};
|
||||
|
||||
static int get_ring_size(struct ao *ao)
|
||||
{
|
||||
return af_fmt_seconds_to_bytes(
|
||||
ao->format, 0.5, ao->channels.num, ao->samplerate);
|
||||
}
|
||||
|
||||
static OSStatus render_cb_digital(
|
||||
AudioDeviceID device, const AudioTimeStamp *ts,
|
||||
const void *in_data, const AudioTimeStamp *in_ts,
|
||||
AudioBufferList *out_data, const AudioTimeStamp *out_ts, void *ctx)
|
||||
{
|
||||
struct ao *ao = ctx;
|
||||
struct priv *p = ao->priv;
|
||||
AudioBuffer buf = out_data->mBuffers[p->stream_idx];
|
||||
int requested = buf.mDataByteSize;
|
||||
|
||||
if (p->muted)
|
||||
mp_ring_drain(p->buffer, requested);
|
||||
else
|
||||
mp_ring_read(p->buffer, buf.mData, requested);
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
ao_control_vol_t *control_vol;
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
control_vol = (ao_control_vol_t *)arg;
|
||||
// Digital output has no volume adjust.
|
||||
int digitalvol = p->muted ? 0 : 100;
|
||||
*control_vol = (ao_control_vol_t) {
|
||||
.left = digitalvol, .right = digitalvol,
|
||||
};
|
||||
return CONTROL_TRUE;
|
||||
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
control_vol = (ao_control_vol_t *)arg;
|
||||
// Digital output can not set volume. Here we have to return true
|
||||
// to make mixer forget it. Else mixer will add a soft filter,
|
||||
// that's not we expected and the filter not support ac3 stream
|
||||
// will cause mplayer die.
|
||||
|
||||
// Although not support set volume, but at least we support mute.
|
||||
// MPlayer set mute by set volume to zero, we handle it.
|
||||
if (control_vol->left == 0 && control_vol->right == 0)
|
||||
p->muted = true;
|
||||
else
|
||||
p->muted = false;
|
||||
return CONTROL_TRUE;
|
||||
|
||||
} // end switch
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd);
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
OSStatus err = ca_select_device(ao, ao->device, &p->device);
|
||||
CHECK_CA_ERROR("failed to select device");
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
bool supports_digital = false;
|
||||
/* Probe whether device support S/PDIF stream output if input is AC3 stream,
|
||||
* or anything else IEC61937-framed. */
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format)) {
|
||||
if (ca_device_supports_digital(ao, p->device))
|
||||
supports_digital = true;
|
||||
}
|
||||
|
||||
if (!supports_digital) {
|
||||
MP_ERR(ao, "selected device doesn't support digital formats\n");
|
||||
goto coreaudio_error;
|
||||
} // closes if (!supports_digital)
|
||||
|
||||
// Build ASBD for the input format
|
||||
AudioStreamBasicDescription asbd;
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
|
||||
return init_digital(ao, asbd);
|
||||
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = noErr;
|
||||
|
||||
uint32_t is_alive = 1;
|
||||
err = CA_GET(p->device, kAudioDevicePropertyDeviceIsAlive, &is_alive);
|
||||
CHECK_CA_WARN("could not check whether device is alive");
|
||||
|
||||
if (!is_alive)
|
||||
MP_WARN(ao , "device is not alive\n");
|
||||
|
||||
err = ca_lock_device(p->device, &p->hog_pid);
|
||||
CHECK_CA_WARN("failed to set hogmode");
|
||||
|
||||
err = ca_disable_mixing(ao, p->device, &p->changed_mixing);
|
||||
CHECK_CA_WARN("failed to disable mixing");
|
||||
|
||||
AudioStreamID *streams;
|
||||
size_t n_streams;
|
||||
|
||||
/* Get a list of all the streams on this device. */
|
||||
err = CA_GET_ARY_O(p->device, kAudioDevicePropertyStreams,
|
||||
&streams, &n_streams);
|
||||
|
||||
CHECK_CA_ERROR("could not get number of streams");
|
||||
|
||||
for (int i = 0; i < n_streams && p->stream_idx < 0; i++) {
|
||||
bool digital = ca_stream_supports_digital(ao, streams[i]);
|
||||
|
||||
if (digital) {
|
||||
err = CA_GET(streams[i], kAudioStreamPropertyPhysicalFormat,
|
||||
&p->original_asbd);
|
||||
if (!CHECK_CA_WARN("could not get stream's physical format to "
|
||||
"revert to, getting the next one"))
|
||||
continue;
|
||||
|
||||
AudioStreamRangedDescription *formats;
|
||||
size_t n_formats;
|
||||
|
||||
err = CA_GET_ARY(streams[i],
|
||||
kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
&formats, &n_formats);
|
||||
|
||||
if (!CHECK_CA_WARN("could not get number of stream formats"))
|
||||
continue; // try next one
|
||||
|
||||
int req_rate_format = -1;
|
||||
int max_rate_format = -1;
|
||||
|
||||
p->stream = streams[i];
|
||||
p->stream_idx = i;
|
||||
|
||||
for (int j = 0; j < n_formats; j++)
|
||||
if (ca_format_is_digital(formats[j].mFormat)) {
|
||||
// select the digital format that has exactly the same
|
||||
// samplerate. If an exact match cannot be found, select
|
||||
// the format with highest samplerate as backup.
|
||||
if (formats[j].mFormat.mSampleRate == asbd.mSampleRate) {
|
||||
req_rate_format = j;
|
||||
break;
|
||||
} else if (max_rate_format < 0 ||
|
||||
formats[j].mFormat.mSampleRate >
|
||||
formats[max_rate_format].mFormat.mSampleRate)
|
||||
max_rate_format = j;
|
||||
}
|
||||
|
||||
if (req_rate_format >= 0)
|
||||
p->stream_asbd = formats[req_rate_format].mFormat;
|
||||
else
|
||||
p->stream_asbd = formats[max_rate_format].mFormat;
|
||||
|
||||
talloc_free(formats);
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
if (p->stream_idx < 0) {
|
||||
MP_WARN(ao , "can't find any digital output stream format\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
if (!ca_change_format(ao, p->stream, p->stream_asbd))
|
||||
goto coreaudio_error;
|
||||
|
||||
void *changed = (void *) &(p->stream_asbd_changed);
|
||||
err = ca_enable_device_listener(p->device, changed);
|
||||
CHECK_CA_ERROR("cannot install format change listener during init");
|
||||
|
||||
if (p->stream_asbd.mFormatFlags & kAudioFormatFlagIsBigEndian)
|
||||
MP_WARN(ao, "stream has non-native byte order, output may fail\n");
|
||||
|
||||
ao->samplerate = p->stream_asbd.mSampleRate;
|
||||
ao->bps = ao->samplerate *
|
||||
(p->stream_asbd.mBytesPerPacket /
|
||||
p->stream_asbd.mFramesPerPacket);
|
||||
|
||||
p->buffer = mp_ring_new(p, get_ring_size(ao));
|
||||
|
||||
err = AudioDeviceCreateIOProcID(p->device,
|
||||
(AudioDeviceIOProc)render_cb_digital,
|
||||
(void *)ao,
|
||||
&p->render_cb);
|
||||
|
||||
CHECK_CA_ERROR("failed to register digital render callback");
|
||||
|
||||
reset(ao);
|
||||
|
||||
return CONTROL_TRUE;
|
||||
|
||||
coreaudio_error:
|
||||
err = ca_unlock_device(p->device, &p->hog_pid);
|
||||
CHECK_CA_WARN("can't release hog mode");
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
void *output_samples = data[0];
|
||||
int num_bytes = samples * ao->sstride;
|
||||
|
||||
// Check whether we need to reset the digital output stream.
|
||||
if (p->stream_asbd_changed) {
|
||||
p->stream_asbd_changed = 0;
|
||||
if (ca_stream_supports_digital(ao, p->stream)) {
|
||||
if (!ca_change_format(ao, p->stream, p->stream_asbd)) {
|
||||
MP_WARN(ao , "can't restore digital output\n");
|
||||
} else {
|
||||
MP_WARN(ao, "restoring digital output succeeded.\n");
|
||||
reset(ao);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int wrote = mp_ring_write(p->buffer, output_samples, num_bytes);
|
||||
audio_resume(ao);
|
||||
|
||||
return wrote / ao->sstride;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
audio_pause(ao);
|
||||
mp_ring_reset(p->buffer);
|
||||
}
|
||||
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
return mp_ring_available(p->buffer) / ao->sstride;
|
||||
}
|
||||
|
||||
static double 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;
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = noErr;
|
||||
|
||||
void *changed = (void *) &(p->stream_asbd_changed);
|
||||
err = ca_disable_device_listener(p->device, changed);
|
||||
CHECK_CA_WARN("can't remove device listener, this may cause a crash");
|
||||
|
||||
err = AudioDeviceStop(p->device, p->render_cb);
|
||||
CHECK_CA_WARN("failed to stop audio device");
|
||||
|
||||
err = AudioDeviceDestroyIOProcID(p->device, p->render_cb);
|
||||
CHECK_CA_WARN("failed to remove device render callback");
|
||||
|
||||
if (!ca_change_format(ao, p->stream, p->original_asbd))
|
||||
MP_WARN(ao, "can't revert to original device format");
|
||||
|
||||
err = ca_enable_mixing(ao, p->device, p->changed_mixing);
|
||||
CHECK_CA_WARN("can't re-enable mixing");
|
||||
|
||||
err = ca_unlock_device(p->device, &p->hog_pid);
|
||||
CHECK_CA_WARN("can't release hog mode");
|
||||
}
|
||||
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (p->paused)
|
||||
return;
|
||||
|
||||
OSStatus err = AudioDeviceStop(p->device, p->render_cb);
|
||||
CHECK_CA_WARN("can't stop digital device");
|
||||
|
||||
p->paused = true;
|
||||
}
|
||||
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (!p->paused)
|
||||
return;
|
||||
|
||||
OSStatus err = AudioDeviceStart(p->device, p->render_cb);
|
||||
CHECK_CA_WARN("can't start digital device");
|
||||
|
||||
p->paused = false;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_coreaudio_exclusive = {
|
||||
.description = "CoreAudio Exclusive Mode",
|
||||
.name = "coreaudio_exclusive",
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.play = play,
|
||||
.control = control,
|
||||
.get_space = get_space,
|
||||
.get_delay = get_delay,
|
||||
.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,
|
||||
.stream_asbd_changed = 0,
|
||||
.hog_pid = -1,
|
||||
.stream = 0,
|
||||
.stream_idx = -1,
|
||||
.changed_mixing = false,
|
||||
},
|
||||
};
|
||||
@@ -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:
|
||||
|
||||
@@ -37,7 +37,6 @@ OSStatus ca_set(AudioObjectID id, ca_scope scope, ca_sel selector,
|
||||
|
||||
#define CA_GET(id, sel, data) ca_get(id, CA_GLOBAL, sel, sizeof(*(data)), data)
|
||||
#define CA_SET(id, sel, data) ca_set(id, CA_GLOBAL, sel, sizeof(*(data)), data)
|
||||
#define CA_GET_O(id, sel, data) ca_get(id, CA_OUTPUT, sel, sizeof(*(data)), data)
|
||||
|
||||
OSStatus ca_get_ary(AudioObjectID id, ca_scope scope, ca_sel selector,
|
||||
uint32_t element_size, void **data, size_t *elements);
|
||||
|
||||
+374
-98
@@ -25,82 +25,12 @@
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/endian.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
static bool ca_is_output_device(struct ao *ao, AudioDeviceID dev)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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});
|
||||
}
|
||||
talloc_free(devs);
|
||||
coreaudio_error:
|
||||
return;
|
||||
}
|
||||
|
||||
OSStatus ca_select_device(struct ao *ao, char* name, 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
|
||||
err = CA_GET(kAudioObjectSystemObject,
|
||||
kAudioHardwarePropertyDefaultOutputDevice,
|
||||
device);
|
||||
CHECK_CA_ERROR("could not get default audio device");
|
||||
} else {
|
||||
*device = selection;
|
||||
}
|
||||
|
||||
if (mp_msg_test(ao->log, MSGL_V)) {
|
||||
char *desc;
|
||||
err = CA_GET_STR(*device, kAudioObjectPropertyName, &desc);
|
||||
CHECK_CA_ERROR("could not get selected audio device name");
|
||||
|
||||
MP_VERBOSE(ao, "selected audio output device: %s (%" PRIu32 ")\n",
|
||||
desc, *device);
|
||||
|
||||
talloc_free(desc);
|
||||
}
|
||||
|
||||
coreaudio_error:
|
||||
return err;
|
||||
}
|
||||
|
||||
char *fourcc_repr(void *talloc_ctx, uint32_t code)
|
||||
{
|
||||
// Extract FourCC letters from the uint32_t and finde out if it's a valid
|
||||
// code that is made of letters.
|
||||
unsigned char fcc[4] = {
|
||||
char fcc[4] = {
|
||||
(code >> 24) & 0xFF,
|
||||
(code >> 16) & 0xFF,
|
||||
(code >> 8) & 0xFF,
|
||||
@@ -109,7 +39,7 @@ char *fourcc_repr(void *talloc_ctx, uint32_t code)
|
||||
|
||||
bool valid_fourcc = true;
|
||||
for (int i = 0; i < 4; i++)
|
||||
if (fcc[i] >= 32 && fcc[i] < 128)
|
||||
if (!isprint(fcc[i]))
|
||||
valid_fourcc = false;
|
||||
|
||||
char *repr;
|
||||
@@ -133,32 +63,6 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
|
||||
return false;
|
||||
}
|
||||
|
||||
void ca_fill_asbd(struct ao *ao, AudioStreamBasicDescription *asbd)
|
||||
{
|
||||
asbd->mSampleRate = ao->samplerate;
|
||||
// Set "AC3" for other spdif formats too - unknown if that works.
|
||||
asbd->mFormatID = AF_FORMAT_IS_IEC61937(ao->format) ?
|
||||
kAudioFormat60958AC3 :
|
||||
kAudioFormatLinearPCM;
|
||||
asbd->mChannelsPerFrame = ao->channels.num;
|
||||
asbd->mBitsPerChannel = af_fmt2bits(ao->format);
|
||||
asbd->mFormatFlags = kAudioFormatFlagIsPacked;
|
||||
|
||||
if ((ao->format & AF_FORMAT_TYPE_MASK) == AF_FORMAT_F)
|
||||
asbd->mFormatFlags |= kAudioFormatFlagIsFloat;
|
||||
|
||||
if ((ao->format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_SI)
|
||||
asbd->mFormatFlags |= kAudioFormatFlagIsSignedInteger;
|
||||
|
||||
if (BYTE_ORDER == BIG_ENDIAN)
|
||||
asbd->mFormatFlags |= kAudioFormatFlagIsBigEndian;
|
||||
|
||||
asbd->mFramesPerPacket = 1;
|
||||
asbd->mBytesPerPacket = asbd->mBytesPerFrame =
|
||||
asbd->mFramesPerPacket * asbd->mChannelsPerFrame *
|
||||
(asbd->mBitsPerChannel / 8);
|
||||
}
|
||||
|
||||
void ca_print_asbd(struct ao *ao, const char *description,
|
||||
const AudioStreamBasicDescription *asbd)
|
||||
{
|
||||
@@ -183,3 +87,375 @@ void ca_print_asbd(struct ao *ao, const char *description,
|
||||
talloc_free(format);
|
||||
}
|
||||
|
||||
bool ca_format_is_digital(AudioStreamBasicDescription asbd)
|
||||
{
|
||||
switch (asbd.mFormatID)
|
||||
case 'IAC3':
|
||||
case 'iac3':
|
||||
case kAudioFormat60958AC3:
|
||||
case kAudioFormatAC3:
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ca_stream_supports_digital(struct ao *ao, AudioStreamID stream)
|
||||
{
|
||||
AudioStreamRangedDescription *formats = NULL;
|
||||
size_t n_formats;
|
||||
|
||||
OSStatus err =
|
||||
CA_GET_ARY(stream, kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
&formats, &n_formats);
|
||||
|
||||
CHECK_CA_ERROR("Could not get number of stream formats.");
|
||||
|
||||
for (int i = 0; i < n_formats; i++) {
|
||||
AudioStreamBasicDescription asbd = formats[i].mFormat;
|
||||
ca_print_asbd(ao, "supported format:", &(asbd));
|
||||
if (ca_format_is_digital(asbd)) {
|
||||
talloc_free(formats);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(formats);
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ca_device_supports_digital(struct ao *ao, AudioDeviceID device)
|
||||
{
|
||||
AudioStreamID *streams = NULL;
|
||||
size_t n_streams;
|
||||
|
||||
/* Retrieve all the output streams. */
|
||||
OSStatus err =
|
||||
CA_GET_ARY_O(device, kAudioDevicePropertyStreams, &streams, &n_streams);
|
||||
|
||||
CHECK_CA_ERROR("could not get number of streams.");
|
||||
|
||||
for (int i = 0; i < n_streams; i++) {
|
||||
if (ca_stream_supports_digital(ao, streams[i])) {
|
||||
talloc_free(streams);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
OSStatus ca_property_listener(AudioObjectPropertySelector selector,
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
void *talloc_ctx = talloc_new(NULL);
|
||||
|
||||
for (int i = 0; i < n_addresses; i++) {
|
||||
if (addresses[i].mSelector == selector) {
|
||||
if (data) *(volatile int *)data = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
talloc_free(talloc_ctx);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSStatus ca_stream_listener(AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
return ca_property_listener(kAudioStreamPropertyPhysicalFormat,
|
||||
object, n_addresses, addresses, data);
|
||||
}
|
||||
|
||||
OSStatus ca_device_listener(AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
return ca_property_listener(kAudioDevicePropertyDeviceHasChanged,
|
||||
object, n_addresses, addresses, data);
|
||||
}
|
||||
|
||||
OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid) {
|
||||
*pid = getpid();
|
||||
OSStatus err = CA_SET(device, kAudioDevicePropertyHogMode, pid);
|
||||
if (err != noErr)
|
||||
*pid = -1;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid) {
|
||||
if (*pid == getpid()) {
|
||||
*pid = -1;
|
||||
return CA_SET(device, kAudioDevicePropertyHogMode, &pid);
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_change_mixing(struct ao *ao, AudioDeviceID device,
|
||||
uint32_t val, bool *changed) {
|
||||
*changed = false;
|
||||
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertySupportsMixing,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
if (AudioObjectHasProperty(device, &p_addr)) {
|
||||
OSStatus err;
|
||||
Boolean writeable = 0;
|
||||
err = CA_SETTABLE(device, kAudioDevicePropertySupportsMixing,
|
||||
&writeable);
|
||||
|
||||
if (!CHECK_CA_WARN("can't tell if mixing property is settable")) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!writeable)
|
||||
return noErr;
|
||||
|
||||
err = CA_SET(device, kAudioDevicePropertySupportsMixing, &val);
|
||||
if (err != noErr)
|
||||
return err;
|
||||
|
||||
if (!CHECK_CA_WARN("can't set mix mode")) {
|
||||
return err;
|
||||
}
|
||||
|
||||
*changed = true;
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSStatus ca_disable_mixing(struct ao *ao, AudioDeviceID device, bool *changed) {
|
||||
return ca_change_mixing(ao, device, 0, changed);
|
||||
}
|
||||
|
||||
OSStatus ca_enable_mixing(struct ao *ao, AudioDeviceID device, bool changed) {
|
||||
if (changed) {
|
||||
bool dont_care = false;
|
||||
return ca_change_mixing(ao, device, 1, &dont_care);
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_change_device_listening(AudioDeviceID device,
|
||||
void *flag, bool enabled)
|
||||
{
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertyDeviceHasChanged,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
if (enabled) {
|
||||
return AudioObjectAddPropertyListener(
|
||||
device, &p_addr, ca_device_listener, flag);
|
||||
} else {
|
||||
return AudioObjectRemovePropertyListener(
|
||||
device, &p_addr, ca_device_listener, flag);
|
||||
}
|
||||
}
|
||||
|
||||
OSStatus ca_enable_device_listener(AudioDeviceID device, void *flag) {
|
||||
return ca_change_device_listening(device, flag, true);
|
||||
}
|
||||
|
||||
OSStatus ca_disable_device_listener(AudioDeviceID device, void *flag) {
|
||||
return ca_change_device_listening(device, flag, false);
|
||||
}
|
||||
|
||||
bool ca_change_format(struct ao *ao, AudioStreamID stream,
|
||||
AudioStreamBasicDescription change_format)
|
||||
{
|
||||
OSStatus err = noErr;
|
||||
AudioObjectPropertyAddress p_addr;
|
||||
volatile int stream_format_changed = 0;
|
||||
|
||||
ca_print_asbd(ao, "setting stream format:", &change_format);
|
||||
|
||||
/* Install the callback. */
|
||||
p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioStreamPropertyPhysicalFormat,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
err = AudioObjectAddPropertyListener(stream, &p_addr, ca_stream_listener,
|
||||
(void *)&stream_format_changed);
|
||||
if (!CHECK_CA_WARN("can't add property listener during format change")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Change the format. */
|
||||
err = CA_SET(stream, kAudioStreamPropertyPhysicalFormat, &change_format);
|
||||
if (!CHECK_CA_WARN("error changing physical format")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The AudioStreamSetProperty is not only asynchronious,
|
||||
* it is also not Atomic, in its behaviour.
|
||||
* Therefore we check 5 times before we really give up. */
|
||||
bool format_set = false;
|
||||
for (int i = 0; !format_set && i < 5; i++) {
|
||||
for (int j = 0; !stream_format_changed && j < 50; j++)
|
||||
mp_sleep_us(10000);
|
||||
|
||||
if (stream_format_changed) {
|
||||
stream_format_changed = 0;
|
||||
} else {
|
||||
MP_VERBOSE(ao, "reached timeout\n");
|
||||
}
|
||||
|
||||
AudioStreamBasicDescription actual_format;
|
||||
err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &actual_format);
|
||||
|
||||
ca_print_asbd(ao, "actual format in use:", &actual_format);
|
||||
if (actual_format.mSampleRate == change_format.mSampleRate &&
|
||||
actual_format.mFormatID == change_format.mFormatID &&
|
||||
actual_format.mFramesPerPacket == change_format.mFramesPerPacket) {
|
||||
format_set = true;
|
||||
}
|
||||
}
|
||||
|
||||
err = AudioObjectRemovePropertyListener(stream, &p_addr, ca_stream_listener,
|
||||
(void *)&stream_format_changed);
|
||||
|
||||
if (!CHECK_CA_WARN("can't remove property listener")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return format_set;
|
||||
}
|
||||
|
||||
static const int speaker_map[][2] = {
|
||||
{ kAudioChannelLabel_Left, MP_SPEAKER_ID_FL },
|
||||
{ kAudioChannelLabel_Right, MP_SPEAKER_ID_FR },
|
||||
{ kAudioChannelLabel_Center, MP_SPEAKER_ID_FC },
|
||||
{ kAudioChannelLabel_LFEScreen, MP_SPEAKER_ID_LFE },
|
||||
{ kAudioChannelLabel_LeftSurround, MP_SPEAKER_ID_BL },
|
||||
{ kAudioChannelLabel_RightSurround, MP_SPEAKER_ID_BR },
|
||||
{ kAudioChannelLabel_LeftCenter, MP_SPEAKER_ID_FLC },
|
||||
{ kAudioChannelLabel_RightCenter, MP_SPEAKER_ID_FRC },
|
||||
{ kAudioChannelLabel_CenterSurround, MP_SPEAKER_ID_BC },
|
||||
{ kAudioChannelLabel_LeftSurroundDirect, MP_SPEAKER_ID_SL },
|
||||
{ kAudioChannelLabel_RightSurroundDirect, MP_SPEAKER_ID_SR },
|
||||
{ kAudioChannelLabel_TopCenterSurround, MP_SPEAKER_ID_TC },
|
||||
{ kAudioChannelLabel_VerticalHeightLeft, MP_SPEAKER_ID_TFL },
|
||||
{ kAudioChannelLabel_VerticalHeightCenter, MP_SPEAKER_ID_TFC },
|
||||
{ kAudioChannelLabel_VerticalHeightRight, MP_SPEAKER_ID_TFR },
|
||||
{ kAudioChannelLabel_TopBackLeft, MP_SPEAKER_ID_TBL },
|
||||
{ kAudioChannelLabel_TopBackCenter, MP_SPEAKER_ID_TBC },
|
||||
{ kAudioChannelLabel_TopBackRight, MP_SPEAKER_ID_TBR },
|
||||
|
||||
// unofficial extensions
|
||||
{ kAudioChannelLabel_RearSurroundLeft, MP_SPEAKER_ID_SDL },
|
||||
{ kAudioChannelLabel_RearSurroundRight, MP_SPEAKER_ID_SDR },
|
||||
{ kAudioChannelLabel_LeftWide, MP_SPEAKER_ID_WL },
|
||||
{ kAudioChannelLabel_RightWide, MP_SPEAKER_ID_WR },
|
||||
{ kAudioChannelLabel_LFE2, MP_SPEAKER_ID_LFE2 },
|
||||
|
||||
{ kAudioChannelLabel_HeadphonesLeft, MP_SPEAKER_ID_DL },
|
||||
{ kAudioChannelLabel_HeadphonesRight, MP_SPEAKER_ID_DR },
|
||||
|
||||
{ kAudioChannelLabel_Unknown, -1 },
|
||||
};
|
||||
|
||||
static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
{
|
||||
for (int i = 0; speaker_map[i][1] >= 0; i++)
|
||||
if (speaker_map[i][0] == label)
|
||||
return speaker_map[i][1];
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ca_log_layout(struct ao *ao, AudioChannelLayout *layout)
|
||||
{
|
||||
if (!mp_msg_test(ao->log, MSGL_V))
|
||||
return;
|
||||
|
||||
AudioChannelDescription *descs = layout->mChannelDescriptions;
|
||||
|
||||
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 <%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]);
|
||||
}
|
||||
}
|
||||
|
||||
bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
struct mp_chmap *chmap)
|
||||
{
|
||||
AudioChannelLayoutTag tag = layout->mChannelLayoutTag;
|
||||
uint32_t layout_size = sizeof(layout);
|
||||
OSStatus err;
|
||||
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
|
||||
err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(uint32_t),
|
||||
&layout->mChannelBitmap,
|
||||
&layout_size,
|
||||
layout);
|
||||
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions");
|
||||
} else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
|
||||
err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag),
|
||||
&layout->mChannelLayoutTag,
|
||||
&layout_size,
|
||||
layout);
|
||||
CHECK_CA_ERROR("failed to convert channel tag to descriptions");
|
||||
}
|
||||
|
||||
ca_log_layout(ao, layout);
|
||||
|
||||
// If the channel layout uses channel descriptions, from my
|
||||
// experiments there are there three possibile cases:
|
||||
// * The description has a label kAudioChannelLabel_Unknown:
|
||||
// Can't do anything about this (looks like non surround
|
||||
// layouts are like this).
|
||||
// * The description uses positional information: this in
|
||||
// theory could be used but one would have to map spatial
|
||||
// positions to labels which is not really feasible.
|
||||
// * The description has a well known label which can be mapped
|
||||
// to the waveextensible definition: this is the kind of
|
||||
// descriptions we process here.
|
||||
|
||||
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=%d unusable to build channel "
|
||||
"bitmap, skipping layout\n", label);
|
||||
} else {
|
||||
chmap->speaker[n] = speaker;
|
||||
chmap->num = n + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return chmap->num > 0;
|
||||
coreaudio_error:
|
||||
ca_log_layout(ao, layout);
|
||||
return false;
|
||||
}
|
||||
@@ -46,11 +46,38 @@ 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_fill_asbd(struct ao *ao, AudioStreamBasicDescription *asbd);
|
||||
void ca_print_asbd(struct ao *ao, const char *description,
|
||||
const AudioStreamBasicDescription *asbd);
|
||||
|
||||
bool ca_format_is_digital(AudioStreamBasicDescription asbd);
|
||||
bool ca_stream_supports_digital(struct ao *ao, AudioStreamID stream);
|
||||
bool ca_device_supports_digital(struct ao *ao, AudioDeviceID device);
|
||||
|
||||
OSStatus ca_property_listener(AudioObjectPropertySelector selector,
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data);
|
||||
|
||||
OSStatus ca_stream_listener(AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data);
|
||||
|
||||
OSStatus ca_device_listener(AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data);
|
||||
|
||||
OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid);
|
||||
OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid);
|
||||
OSStatus ca_disable_mixing(struct ao *ao, AudioDeviceID device, bool *changed);
|
||||
OSStatus ca_enable_mixing(struct ao *ao, AudioDeviceID device, bool changed);
|
||||
|
||||
OSStatus ca_enable_device_listener(AudioDeviceID device, void *flag);
|
||||
OSStatus ca_disable_device_listener(AudioDeviceID device, void *flag);
|
||||
|
||||
bool ca_change_format(struct ao *ao, AudioStreamID stream,
|
||||
AudioStreamBasicDescription change_format);
|
||||
|
||||
bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
struct mp_chmap *chmap);
|
||||
|
||||
#endif /* MPV_COREAUDIO_UTILS_H */
|
||||
+37
-104
@@ -41,7 +41,6 @@
|
||||
#include "internal.h"
|
||||
#include "common/msg.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/io.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
/**
|
||||
@@ -99,11 +98,10 @@ struct priv {
|
||||
int outburst; ///play in multiple of chunks of this size
|
||||
|
||||
int cfg_device;
|
||||
int cfg_buffersize;
|
||||
|
||||
struct ao_device_list *listing; ///temporary during list_devs()
|
||||
};
|
||||
|
||||
static float get_delay(struct ao *ao);
|
||||
|
||||
/***************************************************************************************/
|
||||
|
||||
/**
|
||||
@@ -173,84 +171,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 +187,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");
|
||||
@@ -414,8 +350,6 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
IDirectSoundBuffer_SetVolume(p->hdsbuf, volume);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
@@ -436,6 +370,7 @@ static int init(struct ao *ao)
|
||||
if (!InitDirectSound(ao))
|
||||
return -1;
|
||||
|
||||
ao->no_persistent_volume = true;
|
||||
p->audio_volume = 100;
|
||||
|
||||
// ok, now create the buffers
|
||||
@@ -445,33 +380,34 @@ static int init(struct ao *ao)
|
||||
int format = af_fmt_from_planar(ao->format);
|
||||
int rate = ao->samplerate;
|
||||
|
||||
if (!AF_FORMAT_IS_IEC61937(format)) {
|
||||
if (AF_FORMAT_IS_AC3(format))
|
||||
format = AF_FORMAT_AC3;
|
||||
else {
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_waveext(&sel);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
return -1;
|
||||
}
|
||||
switch (format) {
|
||||
case AF_FORMAT_S24:
|
||||
case AF_FORMAT_S16:
|
||||
case AF_FORMAT_AC3:
|
||||
case AF_FORMAT_S24_LE:
|
||||
case AF_FORMAT_S16_LE:
|
||||
case AF_FORMAT_U8:
|
||||
break;
|
||||
default:
|
||||
if (AF_FORMAT_IS_IEC61937(format))
|
||||
break;
|
||||
MP_VERBOSE(ao, "format %s not supported defaulting to Signed 16-bit Little-Endian\n",
|
||||
af_fmt_to_str(format));
|
||||
format = AF_FORMAT_S16;
|
||||
format = AF_FORMAT_S16_LE;
|
||||
}
|
||||
//set our audio parameters
|
||||
ao->samplerate = rate;
|
||||
ao->format = format;
|
||||
ao->bps = ao->channels.num * rate * af_fmt2bps(format);
|
||||
int buffersize = ao->bps * p->cfg_buffersize / 1000;
|
||||
int buffersize = ao->bps; // space for 1 sec
|
||||
MP_VERBOSE(ao, "Samplerate:%iHz Channels:%i Format:%s\n", rate,
|
||||
ao->channels.num, af_fmt_to_str(format));
|
||||
MP_VERBOSE(ao, "Buffersize:%d bytes (%f msec)\n",
|
||||
buffersize, buffersize * 1000.0 / ao->bps);
|
||||
MP_VERBOSE(ao, "Buffersize:%d bytes (%d msec)\n",
|
||||
buffersize, buffersize / ao->bps * 1000);
|
||||
|
||||
//fill waveformatex
|
||||
ZeroMemory(&wformat, sizeof(WAVEFORMATEXTENSIBLE));
|
||||
@@ -479,8 +415,7 @@ static int init(struct ao *ao)
|
||||
? sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) : 0;
|
||||
wformat.Format.nChannels = ao->channels.num;
|
||||
wformat.Format.nSamplesPerSec = rate;
|
||||
if (AF_FORMAT_IS_IEC61937(format)) {
|
||||
// Whether it also works with e.g. DTS is unknown, but probably does.
|
||||
if (AF_FORMAT_IS_AC3(format)) {
|
||||
wformat.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF;
|
||||
wformat.Format.wBitsPerSample = 16;
|
||||
wformat.Format.nBlockAlign = 4;
|
||||
@@ -648,7 +583,7 @@ static int get_space(struct ao *ao)
|
||||
int space = check_free_buffer_size(ao);
|
||||
if (space < p->min_free_space)
|
||||
return 0;
|
||||
return (space - p->min_free_space) / p->outburst * p->outburst / ao->sstride;
|
||||
return (space - p->min_free_space) / ao->sstride;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -676,12 +611,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,11 +633,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_INTRANGE("buffersize", cfg_buffersize, 0, 1, 10000, OPTDEF_INT(200)),
|
||||
OPT_INT("device", cfg_device, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+15
-25
@@ -28,6 +28,7 @@
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/audioconvert.h>
|
||||
|
||||
#include "compat/libav.h"
|
||||
#include "config.h"
|
||||
#include "options/options.h"
|
||||
#include "common/common.h"
|
||||
@@ -56,8 +57,6 @@ struct priv {
|
||||
|
||||
AVRational worst_time_base;
|
||||
int worst_time_base_is_stream;
|
||||
|
||||
bool shutdown;
|
||||
};
|
||||
|
||||
static void select_format(struct ao *ao, AVCodec *codec)
|
||||
@@ -72,7 +71,7 @@ static void select_format(struct ao *ao, AVCodec *codec)
|
||||
{
|
||||
int fmt = af_from_avformat(*sampleformat);
|
||||
if (!fmt) {
|
||||
MP_WARN(ao, "unsupported lavc format %s\n",
|
||||
MP_WARN(ao, "unsupported lavc format %s",
|
||||
av_get_sample_fmt_name(*sampleformat));
|
||||
continue;
|
||||
}
|
||||
@@ -115,11 +114,12 @@ static int init(struct ao *ao)
|
||||
|
||||
codec = encode_lavc_get_codec(ao->encode_lavc_ctx, ac->stream);
|
||||
|
||||
// TODO: Remove this redundancy with encode_lavc_alloc_stream also
|
||||
// setting the time base.
|
||||
// Using codec->time_bvase is deprecated, but needed for older lavf.
|
||||
ac->stream->time_base.num = 1;
|
||||
ac->stream->time_base.den = ao->samplerate;
|
||||
// ac->stream->time_base.num = 1;
|
||||
// ac->stream->time_base.den = ao->samplerate;
|
||||
// doing this breaks mpeg2ts in ffmpeg
|
||||
// which doesn't properly force the time base to be 90000
|
||||
// furthermore, ffmpeg.c doesn't do this either and works
|
||||
|
||||
ac->stream->codec->time_base.num = 1;
|
||||
ac->stream->codec->time_base.den = ao->samplerate;
|
||||
|
||||
@@ -176,7 +176,6 @@ static int init(struct ao *ao)
|
||||
|
||||
fail:
|
||||
pthread_mutex_unlock(&ao->encode_lavc_ctx->lock);
|
||||
ac->shutdown = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -187,9 +186,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 +203,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 +236,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;
|
||||
@@ -349,17 +342,18 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
}
|
||||
|
||||
double pts = ectx->last_audio_in_pts;
|
||||
pts += ectx->samples_since_last_pts / (double)ao->samplerate;
|
||||
pts += ectx->samples_since_last_pts / ao->samplerate;
|
||||
ectx->samples_since_last_pts += samples;
|
||||
|
||||
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 +433,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);
|
||||
@@ -457,10 +451,6 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
}
|
||||
|
||||
talloc_free(tempdata);
|
||||
|
||||
int taken = FFMIN(bufpos, orig_samples);
|
||||
ectx->samples_since_last_pts += taken;
|
||||
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
if (flags & AOPLAY_FINAL_CHUNK) {
|
||||
@@ -473,7 +463,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
}
|
||||
}
|
||||
|
||||
return taken;
|
||||
return FFMIN(bufpos, orig_samples);
|
||||
}
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
|
||||
+3
-4
@@ -90,7 +90,7 @@ static int init(struct ao *ao)
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
mp_chmap_from_channels(&ao->channels, 2);
|
||||
return -1;
|
||||
|
||||
priv->latency = priv->latency_sec * ao->samplerate;
|
||||
|
||||
@@ -148,8 +148,7 @@ static int get_space(struct ao *ao)
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
drain(ao);
|
||||
int samples = priv->buffersize - priv->latency - priv->buffered;
|
||||
return samples / priv->outburst * priv->outburst;
|
||||
return priv->buffersize - priv->latency - priv->buffered;
|
||||
}
|
||||
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
@@ -176,7 +175,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;
|
||||
|
||||
|
||||
+4
-20
@@ -76,8 +76,6 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
vol->left = vol->right = volume * 100;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
@@ -101,17 +99,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];
|
||||
@@ -145,6 +132,7 @@ static int init(struct ao *ao)
|
||||
return -1;
|
||||
}
|
||||
ao_data = ao;
|
||||
ao->no_persistent_volume = true;
|
||||
struct mp_chmap_sel sel = {0};
|
||||
for (i = 0; speaker_pos[i].id != -1; i++)
|
||||
mp_chmap_sel_add_speaker(&sel, speaker_pos[i].id);
|
||||
@@ -162,10 +150,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 +278,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 +301,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),
|
||||
|
||||
+300
-340
@@ -31,16 +31,12 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "options/options.h"
|
||||
#include "common/msg.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/endian.h"
|
||||
|
||||
#if HAVE_SYS_SOUNDCARD_H
|
||||
#include <sys/soundcard.h>
|
||||
@@ -55,20 +51,14 @@
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
|
||||
// Define to 0 if the device must be reopened to reset it (stop all playback,
|
||||
// clear the buffer), and the device should be closed when unused.
|
||||
// Define to 1 if SNDCTL_DSP_RESET should be used to reset without close.
|
||||
#define KEEP_DEVICE (defined(SNDCTL_DSP_RESET) && !defined(__NetBSD__))
|
||||
|
||||
struct priv {
|
||||
int audio_fd;
|
||||
int prepause_samples;
|
||||
int prepause_space;
|
||||
int oss_mixer_channel;
|
||||
audio_buf_info zz;
|
||||
int audio_delay_method;
|
||||
int buffersize;
|
||||
int outburst;
|
||||
bool device_failed;
|
||||
double audio_end;
|
||||
|
||||
char *dsp;
|
||||
char *oss_mixer_device;
|
||||
@@ -90,60 +80,53 @@ static const struct mp_chmap oss_layouts[MP_NUM_CHANNELS + 1] = {
|
||||
MP_CHMAP8(FL, FR, BL, BR, FC, LFE, SL, SR), // 7.1
|
||||
};
|
||||
|
||||
#if !defined(AFMT_S16_NE) && defined(AFMT_S16_LE) && defined(AFMT_S16_BE)
|
||||
#define AFMT_S16_NE MP_SELECT_LE_BE(AFMT_S16_LE, AFMT_S16_BE)
|
||||
#endif
|
||||
|
||||
#if !defined(AFMT_U16_NE) && defined(AFMT_U16_LE) && defined(AFMT_U16_BE)
|
||||
#define AFMT_U16_NE MP_SELECT_LE_BE(AFMT_U16_LE, AFMT_U16_BE)
|
||||
#endif
|
||||
|
||||
#if !defined(AFMT_U24_NE) && defined(AFMT_U24_LE) && defined(AFMT_U24_BE)
|
||||
#define AFMT_U24_NE MP_SELECT_LE_BE(AFMT_U24_LE, AFMT_U24_BE)
|
||||
#endif
|
||||
|
||||
#if !defined(AFMT_S24_NE) && defined(AFMT_S24_LE) && defined(AFMT_S24_BE)
|
||||
#define AFMT_S24_NE MP_SELECT_LE_BE(AFMT_S24_LE, AFMT_S24_BE)
|
||||
#endif
|
||||
|
||||
#if !defined(AFMT_U32_NE) && defined(AFMT_U32_LE) && defined(AFMT_U32_BE)
|
||||
#define AFMT_U32MP_SELECT_LE_BE(AFMT_U32_LE, AFMT_U32_BE)
|
||||
#endif
|
||||
|
||||
#if !defined(AFMT_S32_NE) && defined(AFMT_S32_LE) && defined(AFMT_S32_BE)
|
||||
#define AFMT_S32MP_SELECT_LE_BE(AFMT_S32_LE, AFMT_S32_BE)
|
||||
#endif
|
||||
|
||||
static const int format_table[][2] = {
|
||||
{AFMT_U8, AF_FORMAT_U8},
|
||||
{AFMT_S8, AF_FORMAT_S8},
|
||||
{AFMT_U16_NE, AF_FORMAT_U16},
|
||||
{AFMT_S16_NE, AF_FORMAT_S16},
|
||||
#ifdef AFMT_U24_NE
|
||||
{AFMT_U24_NE, AF_FORMAT_U24},
|
||||
{AFMT_U16_LE, AF_FORMAT_U16_LE},
|
||||
{AFMT_U16_BE, AF_FORMAT_U16_BE},
|
||||
{AFMT_S16_LE, AF_FORMAT_S16_LE},
|
||||
{AFMT_S16_BE, AF_FORMAT_S16_BE},
|
||||
#ifdef AFMT_S24_PACKED
|
||||
{AFMT_S24_PACKED, AF_FORMAT_S24_LE},
|
||||
#endif
|
||||
#ifdef AFMT_S24_NE
|
||||
{AFMT_S24_NE, AF_FORMAT_S24},
|
||||
#ifdef AFMT_U24_LE
|
||||
{AFMT_U24_LE, AF_FORMAT_U24_LE},
|
||||
#endif
|
||||
#ifdef AFMT_U32_NE
|
||||
{AFMT_U32_NE, AF_FORMAT_U32},
|
||||
#ifdef AFMT_U24_BE
|
||||
{AFMT_U24_BE, AF_FORMAT_U24_BE},
|
||||
#endif
|
||||
#ifdef AFMT_S32_NE
|
||||
{AFMT_S32_NE, AF_FORMAT_S32},
|
||||
#ifdef AFMT_S24_LE
|
||||
{AFMT_S24_LE, AF_FORMAT_S24_LE},
|
||||
#endif
|
||||
#ifdef AFMT_S24_BE
|
||||
{AFMT_S24_BE, AF_FORMAT_S24_BE},
|
||||
#endif
|
||||
#ifdef AFMT_U32_LE
|
||||
{AFMT_U32_LE, AF_FORMAT_U32_LE},
|
||||
#endif
|
||||
#ifdef AFMT_U32_BE
|
||||
{AFMT_U32_BE, AF_FORMAT_U32_BE},
|
||||
#endif
|
||||
#ifdef AFMT_S32_LE
|
||||
{AFMT_S32_LE, AF_FORMAT_S32_LE},
|
||||
#endif
|
||||
#ifdef AFMT_S32_BE
|
||||
{AFMT_S32_BE, AF_FORMAT_S32_BE},
|
||||
#endif
|
||||
#ifdef AFMT_FLOAT
|
||||
{AFMT_FLOAT, AF_FORMAT_FLOAT},
|
||||
#endif
|
||||
// SPECIALS
|
||||
#ifdef AFMT_MPEG
|
||||
{AFMT_MPEG, AF_FORMAT_S_MP3},
|
||||
{AFMT_MPEG, AF_FORMAT_MPEG2},
|
||||
#endif
|
||||
#ifdef AFMT_AC3
|
||||
{AFMT_AC3, AF_FORMAT_AC3},
|
||||
#endif
|
||||
{-1, -1}
|
||||
};
|
||||
|
||||
#ifndef AFMT_AC3
|
||||
#define AFMT_AC3 -1
|
||||
#endif
|
||||
|
||||
static int format2oss(int format)
|
||||
{
|
||||
for (int n = 0; format_table[n][0] != -1; n++) {
|
||||
@@ -159,7 +142,7 @@ static int oss2format(int format)
|
||||
if (format_table[n][0] == format)
|
||||
return format_table[n][1];
|
||||
}
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +177,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
struct priv *p = ao->priv;
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
case AOCONTROL_SET_VOLUME: {
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
{
|
||||
ao_control_vol_t *vol = (ao_control_vol_t *)arg;
|
||||
int fd, v, devs;
|
||||
|
||||
@@ -204,7 +188,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return CONTROL_OK;
|
||||
#endif
|
||||
|
||||
if (AF_FORMAT_IS_SPECIAL(ao->format))
|
||||
if (AF_FORMAT_IS_AC3(ao->format))
|
||||
return CONTROL_TRUE;
|
||||
|
||||
if ((fd = open(p->oss_mixer_device, O_RDONLY)) != -1) {
|
||||
@@ -225,205 +209,22 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
close(fd);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
}
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
#ifdef SNDCTL_DSP_GETPLAYVOL
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
#endif
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
// 1: ok, 0: not writable, -1: error
|
||||
static int device_writable(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
struct pollfd fd = {.fd = p->audio_fd, .events = POLLOUT};
|
||||
return poll(&fd, 1, 0);
|
||||
}
|
||||
|
||||
static void close_device(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
p->device_failed = false;
|
||||
if (p->audio_fd == -1)
|
||||
return;
|
||||
#if defined(SNDCTL_DSP_RESET)
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
|
||||
#endif
|
||||
close(p->audio_fd);
|
||||
p->audio_fd = -1;
|
||||
}
|
||||
|
||||
// close audio device
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
close_device(ao);
|
||||
}
|
||||
|
||||
static bool try_format(struct ao *ao, int *format)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
int oss_format = format2oss(*format);
|
||||
if (oss_format == -1 && AF_FORMAT_IS_IEC61937(*format))
|
||||
oss_format = AFMT_AC3;
|
||||
|
||||
if (oss_format == -1) {
|
||||
MP_VERBOSE(ao, "Unknown/not supported internal format: %s\n",
|
||||
af_fmt_to_str(*format));
|
||||
*format = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
int actual_format = oss_format;
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_SETFMT, &actual_format) < 0)
|
||||
actual_format = -1;
|
||||
|
||||
if (actual_format == oss_format)
|
||||
return true;
|
||||
|
||||
MP_WARN(ao, "Can't set audio device to %s output.\n", af_fmt_to_str(*format));
|
||||
*format = oss2format(actual_format);
|
||||
if (actual_format != -1 && !*format)
|
||||
MP_ERR(ao, "Unknown/Unsupported OSS format: 0x%x.\n", actual_format);
|
||||
return false;
|
||||
}
|
||||
|
||||
static int reopen_device(struct ao *ao, bool allow_format_changes)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
int samplerate = ao->samplerate;
|
||||
int format = ao->format;
|
||||
struct mp_chmap channels = ao->channels;
|
||||
|
||||
#ifdef __linux__
|
||||
p->audio_fd = open(p->dsp, O_WRONLY | O_NONBLOCK);
|
||||
#else
|
||||
p->audio_fd = open(p->dsp, O_WRONLY);
|
||||
#endif
|
||||
if (p->audio_fd < 0) {
|
||||
MP_ERR(ao, "Can't open audio device %s: %s\n", p->dsp, strerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
/* Remove the non-blocking flag */
|
||||
if (fcntl(p->audio_fd, F_SETFL, 0) < 0) {
|
||||
MP_ERR(ao, "Can't make file descriptor blocking: %s\n", strerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(FD_CLOEXEC) && defined(F_SETFD)
|
||||
fcntl(p->audio_fd, F_SETFD, FD_CLOEXEC);
|
||||
#endif
|
||||
|
||||
if (AF_FORMAT_IS_IEC61937(format)) {
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &samplerate) == -1)
|
||||
goto fail;
|
||||
// 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));
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
int try_formats[] = {format, AF_FORMAT_S32, AF_FORMAT_S24, AF_FORMAT_S16, 0};
|
||||
for (int n = 0; try_formats[n]; n++) {
|
||||
format = try_formats[n];
|
||||
if (try_format(ao, &format))
|
||||
break;
|
||||
}
|
||||
|
||||
if (!format) {
|
||||
MP_ERR(ao, "Can't set sample format.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
MP_VERBOSE(ao, "sample format: %s\n", af_fmt_to_str(format));
|
||||
|
||||
if (!AF_FORMAT_IS_IEC61937(format)) {
|
||||
struct mp_chmap_sel sel = {0};
|
||||
for (int n = 0; n < MP_NUM_CHANNELS + 1; n++)
|
||||
mp_chmap_sel_add_map(&sel, &oss_layouts[n]);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &channels))
|
||||
goto fail;
|
||||
int reqchannels = channels.num;
|
||||
// We only use SNDCTL_DSP_CHANNELS for >2 channels, in case some drivers don't have it
|
||||
if (reqchannels > 2) {
|
||||
int nchannels = reqchannels;
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1 ||
|
||||
nchannels != reqchannels)
|
||||
{
|
||||
MP_ERR(ao, "Failed to set audio device to %d channels.\n",
|
||||
reqchannels);
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
int c = reqchannels - 1;
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_STEREO, &c) == -1) {
|
||||
MP_ERR(ao, "Failed to set audio device to %d channels.\n",
|
||||
reqchannels);
|
||||
goto fail;
|
||||
}
|
||||
if (!ao_chmap_sel_get_def(ao, &sel, &channels, c + 1))
|
||||
goto fail;
|
||||
}
|
||||
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;
|
||||
MP_VERBOSE(ao, "using %d Hz samplerate\n", samplerate);
|
||||
}
|
||||
|
||||
audio_buf_info zz = {0};
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &zz) == -1) {
|
||||
int r = 0;
|
||||
MP_WARN(ao, "driver doesn't support SNDCTL_DSP_GETOSPACE\n");
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_GETBLKSIZE, &r) == -1)
|
||||
MP_VERBOSE(ao, "%d bytes/frag (config.h)\n", p->outburst);
|
||||
else {
|
||||
p->outburst = r;
|
||||
MP_VERBOSE(ao, "%d bytes/frag (GETBLKSIZE)\n", p->outburst);
|
||||
}
|
||||
} else {
|
||||
MP_VERBOSE(ao, "frags: %3d/%d (%d bytes/frag) free: %6d\n",
|
||||
zz.fragments, zz.fragstotal, zz.fragsize, zz.bytes);
|
||||
p->buffersize = zz.bytes;
|
||||
p->outburst = zz.fragsize;
|
||||
}
|
||||
|
||||
if (allow_format_changes) {
|
||||
ao->format = format;
|
||||
ao->samplerate = samplerate;
|
||||
ao->channels = channels;
|
||||
} else {
|
||||
if (format != ao->format || samplerate != ao->samplerate ||
|
||||
!mp_chmap_equals(&channels, &ao->channels))
|
||||
{
|
||||
MP_ERR(ao, "Could not reselect previous audio format.\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
p->outburst -= p->outburst % (channels.num * af_fmt2bps(format)); // round down
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
close_device(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// open & setup audio device
|
||||
// return: 0=success -1=fail
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int oss_format;
|
||||
|
||||
#ifdef SNDCTL_DSP_GETPLAYVOL
|
||||
ao->no_persistent_volume = true;
|
||||
#endif
|
||||
|
||||
const char *mchan = NULL;
|
||||
if (p->cfg_oss_mixer_channel && p->cfg_oss_mixer_channel[0])
|
||||
@@ -464,36 +265,178 @@ static int init(struct ao *ao)
|
||||
MP_VERBOSE(ao, "using '%s' mixer device\n", p->oss_mixer_device);
|
||||
MP_VERBOSE(ao, "using '%s' mixer device\n", mixer_channels[p->oss_mixer_channel]);
|
||||
|
||||
#ifdef __linux__
|
||||
p->audio_fd = open(p->dsp, O_WRONLY | O_NONBLOCK);
|
||||
#else
|
||||
p->audio_fd = open(p->dsp, O_WRONLY);
|
||||
#endif
|
||||
if (p->audio_fd < 0) {
|
||||
MP_ERR(ao, "Can't open audio device %s: %s\n", p->dsp, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
/* Remove the non-blocking flag */
|
||||
if (fcntl(p->audio_fd, F_SETFL, 0) < 0) {
|
||||
MP_ERR(ao, "Can't make file descriptor blocking: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(FD_CLOEXEC) && defined(F_SETFD)
|
||||
fcntl(p->audio_fd, F_SETFD, FD_CLOEXEC);
|
||||
#endif
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
if (reopen_device(ao, true) < 0)
|
||||
goto fail;
|
||||
if (AF_FORMAT_IS_AC3(ao->format)) {
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
|
||||
}
|
||||
|
||||
ac3_retry:
|
||||
if (AF_FORMAT_IS_AC3(ao->format))
|
||||
ao->format = AF_FORMAT_AC3;
|
||||
oss_format = format2oss(ao->format);
|
||||
if (oss_format == -1) {
|
||||
MP_VERBOSE(ao, "Unknown/not supported internal format: %s\n",
|
||||
af_fmt_to_str(ao->format));
|
||||
#if defined(AFMT_S32_LE) && defined(AFMT_S32_BE)
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
oss_format = AFMT_S32_BE;
|
||||
#else
|
||||
oss_format = AFMT_S32_LE;
|
||||
#endif
|
||||
ao->format = AF_FORMAT_S32;
|
||||
#elif defined(AFMT_S24_LE) && defined(AFMT_S24_BE)
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
oss_format = AFMT_S24_BE;
|
||||
#else
|
||||
oss_format = AFMT_S24_LE;
|
||||
#endif
|
||||
ao->format = AF_FORMAT_S24;
|
||||
#else
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
oss_format = AFMT_S16_BE;
|
||||
#else
|
||||
oss_format = AFMT_S16_LE;
|
||||
#endif
|
||||
ao->format = AF_FORMAT_S16;
|
||||
#endif
|
||||
}
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_SETFMT, &oss_format) < 0 ||
|
||||
oss_format != format2oss(ao->format))
|
||||
{
|
||||
MP_WARN(ao, "Can't set audio device %s to %s output, trying %s...\n",
|
||||
p->dsp, af_fmt_to_str(ao->format),
|
||||
af_fmt_to_str(AF_FORMAT_S16));
|
||||
ao->format = AF_FORMAT_S16;
|
||||
goto ac3_retry;
|
||||
}
|
||||
|
||||
ao->format = oss2format(oss_format);
|
||||
if (ao->format == -1) {
|
||||
MP_ERR(ao, "Unknown/Unsupported OSS format: %x.\n", oss_format);
|
||||
return -1;
|
||||
}
|
||||
|
||||
MP_VERBOSE(ao, "sample format: %s\n", af_fmt_to_str(ao->format));
|
||||
|
||||
if (!AF_FORMAT_IS_AC3(ao->format)) {
|
||||
struct mp_chmap_sel sel = {0};
|
||||
for (int n = 0; n < MP_NUM_CHANNELS + 1; n++)
|
||||
mp_chmap_sel_add_map(&sel, &oss_layouts[n]);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
return -1;
|
||||
int reqchannels = ao->channels.num;
|
||||
// We only use SNDCTL_DSP_CHANNELS for >2 channels, in case some drivers don't have it
|
||||
if (reqchannels > 2) {
|
||||
int nchannels = reqchannels;
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1 ||
|
||||
nchannels != reqchannels)
|
||||
{
|
||||
MP_ERR(ao, "Failed to set audio device to %d channels.\n",
|
||||
reqchannels);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
int c = reqchannels - 1;
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_STEREO, &c) == -1) {
|
||||
MP_ERR(ao, "Failed to set audio device to %d channels.\n",
|
||||
reqchannels);
|
||||
return -1;
|
||||
}
|
||||
if (!ao_chmap_sel_get_def(ao, &sel, &ao->channels, c + 1))
|
||||
return -1;
|
||||
}
|
||||
MP_VERBOSE(ao, "using %d channels (requested: %d)\n",
|
||||
ao->channels.num, reqchannels);
|
||||
// set rate
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
|
||||
MP_VERBOSE(ao, "using %d Hz samplerate\n", ao->samplerate);
|
||||
}
|
||||
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &p->zz) == -1) {
|
||||
int r = 0;
|
||||
MP_WARN(ao, "driver doesn't support SNDCTL_DSP_GETOSPACE\n");
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_GETBLKSIZE, &r) == -1)
|
||||
MP_VERBOSE(ao, "%d bytes/frag (config.h)\n", p->outburst);
|
||||
else {
|
||||
p->outburst = r;
|
||||
MP_VERBOSE(ao, "%d bytes/frag (GETBLKSIZE)\n", p->outburst);
|
||||
}
|
||||
} else {
|
||||
MP_VERBOSE(ao, "frags: %3d/%d (%d bytes/frag) free: %6d\n",
|
||||
p->zz.fragments, p->zz.fragstotal, p->zz.fragsize, p->zz.bytes);
|
||||
p->buffersize = p->zz.bytes;
|
||||
p->outburst = p->zz.fragsize;
|
||||
}
|
||||
|
||||
if (p->buffersize == -1) {
|
||||
// Measuring buffer size:
|
||||
void *data = malloc(p->outburst);
|
||||
if (!data) {
|
||||
MP_ERR(ao, "Out of memory, or broken outburst size.\n");
|
||||
goto fail;
|
||||
}
|
||||
void *data;
|
||||
p->buffersize = 0;
|
||||
#if HAVE_AUDIO_SELECT
|
||||
data = malloc(p->outburst);
|
||||
memset(data, 0, p->outburst);
|
||||
while (p->buffersize < 0x40000 && device_writable(ao) > 0) {
|
||||
while (p->buffersize < 0x40000) {
|
||||
fd_set rfds;
|
||||
struct timeval tv;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(p->audio_fd, &rfds);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 0;
|
||||
if (!select(p->audio_fd + 1, NULL, &rfds, NULL, &tv))
|
||||
break;
|
||||
write(p->audio_fd, data, p->outburst);
|
||||
p->buffersize += p->outburst;
|
||||
}
|
||||
free(data);
|
||||
if (p->buffersize == 0) {
|
||||
MP_ERR(ao, "Your OSS audio driver DOES NOT support poll().\n");
|
||||
goto fail;
|
||||
MP_ERR(ao, "*** Your audio driver DOES NOT support select() ***\n");
|
||||
MP_ERR(ao, "Recompile mpv with #define HAVE_AUDIO_SELECT 0 in config.h!\n");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
ao->bps = ao->channels.num * af_fmt2bps(ao->format);
|
||||
p->outburst -= p->outburst % ao->bps; // round down
|
||||
ao->bps *= ao->samplerate;
|
||||
|
||||
fail:
|
||||
uninit(ao);
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// close audio device
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (p->audio_fd == -1)
|
||||
return;
|
||||
#ifdef SNDCTL_DSP_RESET
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
|
||||
#endif
|
||||
close(p->audio_fd);
|
||||
p->audio_fd = -1;
|
||||
}
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
@@ -506,39 +449,104 @@ static void drain(struct ao *ao)
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef SNDCTL_DSP_RESET
|
||||
static void close_device(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
close(p->audio_fd);
|
||||
p->audio_fd = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
// stop playing and empty buffers (for seeking/pause)
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
#if KEEP_DEVICE
|
||||
struct priv *p = ao->priv;
|
||||
int oss_format;
|
||||
#ifdef SNDCTL_DSP_RESET
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
|
||||
#else
|
||||
close_device(ao);
|
||||
p->audio_fd = open(p->dsp, O_WRONLY);
|
||||
if (p->audio_fd < 0) {
|
||||
MP_ERR(ao, "Fatal error: *** CANNOT "
|
||||
"RE-OPEN / RESET AUDIO DEVICE *** %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(FD_CLOEXEC) && defined(F_SETFD)
|
||||
fcntl(p->audio_fd, F_SETFD, FD_CLOEXEC);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
oss_format = format2oss(ao->format);
|
||||
if (AF_FORMAT_IS_AC3(ao->format))
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_SETFMT, &oss_format);
|
||||
if (!AF_FORMAT_IS_AC3(ao->format)) {
|
||||
int c = ao->channels.num;
|
||||
if (ao->channels.num > 2)
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_CHANNELS, &c);
|
||||
else {
|
||||
c--;
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_STEREO, &c);
|
||||
}
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
|
||||
}
|
||||
}
|
||||
|
||||
// return: how many bytes can be played without blocking
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int playsize = p->outburst;
|
||||
|
||||
#ifdef SNDCTL_DSP_GETOSPACE
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &p->zz) != -1) {
|
||||
// calculate exact buffer space:
|
||||
playsize = p->zz.fragments * p->zz.fragsize;
|
||||
return playsize / ao->sstride;
|
||||
}
|
||||
#endif
|
||||
|
||||
// check buffer
|
||||
#if HAVE_AUDIO_SELECT
|
||||
{
|
||||
fd_set rfds;
|
||||
struct timeval tv;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(p->audio_fd, &rfds);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 0;
|
||||
if (!select(p->audio_fd + 1, NULL, &rfds, NULL, &tv))
|
||||
return 0; // not block!
|
||||
}
|
||||
#endif
|
||||
|
||||
return p->outburst / ao->sstride;
|
||||
}
|
||||
|
||||
// stop playing, keep buffers (for pause)
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
p->prepause_space = get_space(ao) * ao->sstride;
|
||||
#ifdef SNDCTL_DSP_RESET
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
|
||||
#else
|
||||
close_device(ao);
|
||||
#endif
|
||||
}
|
||||
|
||||
// plays 'len' samples of 'data'
|
||||
// plays 'len' bytes of 'data'
|
||||
// it should round it down to outburst*n
|
||||
// return: number of samples played
|
||||
// return: number of bytes played
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int len = samples * ao->sstride;
|
||||
if (len == 0)
|
||||
return len;
|
||||
|
||||
if (p->audio_fd < 0 && !p->device_failed && reopen_device(ao, false) < 0)
|
||||
MP_ERR(ao, "Fatal error: *** CANNOT RE-OPEN / RESET AUDIO DEVICE ***\n");
|
||||
if (p->audio_fd < 0) {
|
||||
// Let playback continue normally, even with a closed device.
|
||||
p->device_failed = true;
|
||||
double now = mp_time_sec();
|
||||
if (p->audio_end < now)
|
||||
p->audio_end = now;
|
||||
p->audio_end += samples / (double)ao->samplerate;
|
||||
return samples;
|
||||
}
|
||||
|
||||
if (len > p->outburst || !(flags & AOPLAY_FINAL_CHUNK)) {
|
||||
len /= p->outburst;
|
||||
len *= p->outburst;
|
||||
@@ -547,86 +555,40 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
return len / ao->sstride;
|
||||
}
|
||||
|
||||
// resume playing, after audio_pause()
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
#ifndef SNDCTL_DSP_RESET
|
||||
reset(ao);
|
||||
#endif
|
||||
int fillframes = get_space(ao) - p->prepause_space / ao->sstride;
|
||||
if (fillframes > 0)
|
||||
ao_play_silence(ao, fillframes);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
double rest = p->audio_end - mp_time_sec();
|
||||
if (rest > 0)
|
||||
return rest;
|
||||
return 0;
|
||||
}
|
||||
/* Calculate how many bytes/second is sent out */
|
||||
if (p->audio_delay_method == 2) {
|
||||
#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;
|
||||
// SNDCTL_DSP_GETOSPACE
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &p->zz) != -1) {
|
||||
return ((float)(p->buffersize -
|
||||
p->zz.bytes)) / (float)ao->bps;
|
||||
}
|
||||
p->audio_delay_method = 0; // fallback if not supported
|
||||
}
|
||||
return p->buffersize / (double)ao->bps;
|
||||
}
|
||||
|
||||
|
||||
// return: how many samples can be played without blocking
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
audio_buf_info zz = {0};
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &zz) != -1) {
|
||||
// calculate exact buffer space:
|
||||
return zz.fragments * zz.fragsize / ao->sstride;
|
||||
}
|
||||
|
||||
if (p->audio_fd < 0 && p->device_failed && get_delay(ao) > 0.2)
|
||||
return 0;
|
||||
|
||||
if (p->audio_fd < 0 || device_writable(ao) > 0)
|
||||
return p->outburst / ao->sstride;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// stop playing, keep buffers (for pause)
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
p->prepause_samples = get_delay(ao) * ao->samplerate;
|
||||
#if KEEP_DEVICE
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
|
||||
#else
|
||||
close_device(ao);
|
||||
#endif
|
||||
}
|
||||
|
||||
// resume playing, after audio_pause()
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
p->audio_end = 0;
|
||||
if (p->prepause_samples > 0)
|
||||
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;
|
||||
return ((float)p->buffersize) / (float)ao->bps;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
@@ -644,8 +606,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,
|
||||
|
||||
+26
-29
@@ -35,7 +35,6 @@
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "common/msg.h"
|
||||
#include "osdep/endian.h"
|
||||
|
||||
#ifdef __MINGW32__
|
||||
// for GetFileType to detect pipes
|
||||
@@ -72,35 +71,40 @@ static void fput32le(uint32_t val, FILE *fp)
|
||||
|
||||
static void write_wave_header(struct ao *ao, FILE *fp, uint64_t data_length)
|
||||
{
|
||||
uint16_t fmt = ao->format == AF_FORMAT_FLOAT ? WAV_ID_FLOAT_PCM : WAV_ID_PCM;
|
||||
bool use_waveex = true;
|
||||
uint16_t fmt = ao->format == AF_FORMAT_FLOAT_LE ?
|
||||
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);
|
||||
@@ -120,23 +124,16 @@ static int init(struct ao *ao)
|
||||
if (priv->waveheader) {
|
||||
// WAV files must have one of the following formats
|
||||
|
||||
// And they don't work in big endian; fixing it would be simple, but
|
||||
// nobody cares.
|
||||
if (BYTE_ORDER == BIG_ENDIAN) {
|
||||
MP_FATAL(ao, "Not supported on big endian.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (ao->format) {
|
||||
case AF_FORMAT_U8:
|
||||
case AF_FORMAT_S16:
|
||||
case AF_FORMAT_S24:
|
||||
case AF_FORMAT_S32:
|
||||
case AF_FORMAT_FLOAT:
|
||||
case AF_FORMAT_S16_LE:
|
||||
case AF_FORMAT_S24_LE:
|
||||
case AF_FORMAT_S32_LE:
|
||||
case AF_FORMAT_FLOAT_LE:
|
||||
case AF_FORMAT_AC3_LE:
|
||||
break;
|
||||
default:
|
||||
if (!AF_FORMAT_IS_IEC61937(ao->format))
|
||||
ao->format = AF_FORMAT_S16;
|
||||
ao->format = AF_FORMAT_S16_LE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -206,7 +203,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
|
||||
fwrite(data[0], len, 1, priv->fp);
|
||||
priv->data_length += len;
|
||||
return samples;
|
||||
return len / ao->sstride;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
@@ -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),
|
||||
|
||||
+84
-201
@@ -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)
|
||||
|
||||
@@ -51,6 +53,7 @@ struct priv {
|
||||
// temporary during control()
|
||||
struct pa_sink_input_info pi;
|
||||
|
||||
bool broken_pause;
|
||||
int retval;
|
||||
|
||||
// for wakeup handling
|
||||
@@ -61,7 +64,6 @@ struct priv {
|
||||
char *cfg_host;
|
||||
char *cfg_sink;
|
||||
int cfg_buffer;
|
||||
int cfg_latency_hacks;
|
||||
};
|
||||
|
||||
#define GENERIC_ERR_MSG(str) \
|
||||
@@ -111,7 +113,7 @@ static void stream_request_cb(pa_stream *s, size_t length, void *userdata)
|
||||
pa_threaded_mainloop_signal(priv->mainloop, 0);
|
||||
}
|
||||
|
||||
static int wait_audio(struct ao *ao, pthread_mutex_t *lock)
|
||||
static int wait(struct ao *ao, pthread_mutex_t *lock)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
// We don't use this mutex, because pulse like to call stream_request_cb
|
||||
@@ -167,31 +169,16 @@ static const struct format_map {
|
||||
int mp_format;
|
||||
pa_sample_format_t pa_format;
|
||||
} format_maps[] = {
|
||||
{AF_FORMAT_S16, PA_SAMPLE_S16NE},
|
||||
{AF_FORMAT_S32, PA_SAMPLE_S32NE},
|
||||
{AF_FORMAT_FLOAT, PA_SAMPLE_FLOAT32NE},
|
||||
{AF_FORMAT_S16_LE, PA_SAMPLE_S16LE},
|
||||
{AF_FORMAT_S16_BE, PA_SAMPLE_S16BE},
|
||||
{AF_FORMAT_S32_LE, PA_SAMPLE_S32LE},
|
||||
{AF_FORMAT_S32_BE, PA_SAMPLE_S32BE},
|
||||
{AF_FORMAT_FLOAT_LE, PA_SAMPLE_FLOAT32LE},
|
||||
{AF_FORMAT_FLOAT_BE, PA_SAMPLE_FLOAT32BE},
|
||||
{AF_FORMAT_U8, PA_SAMPLE_U8},
|
||||
{AF_FORMAT_UNKNOWN, 0}
|
||||
};
|
||||
|
||||
static pa_encoding_t map_digital_format(int format)
|
||||
{
|
||||
switch (format) {
|
||||
case AF_FORMAT_S_AC3: return PA_ENCODING_AC3_IEC61937;
|
||||
case AF_FORMAT_S_EAC3: return PA_ENCODING_EAC3_IEC61937;
|
||||
case AF_FORMAT_S_MP3: return PA_ENCODING_MPEG_IEC61937;
|
||||
case AF_FORMAT_S_DTS:
|
||||
case AF_FORMAT_S_DTSHD: return PA_ENCODING_DTS_IEC61937;
|
||||
#ifdef PA_ENCODING_MPEG2_AAC_IEC61937
|
||||
case AF_FORMAT_S_AAC: return PA_ENCODING_MPEG2_AAC_IEC61937;
|
||||
#endif
|
||||
default:
|
||||
if (AF_FORMAT_IS_IEC61937(format))
|
||||
return PA_ENCODING_ANY;
|
||||
return PA_ENCODING_PCM;
|
||||
}
|
||||
}
|
||||
|
||||
static const int speaker_map[][2] = {
|
||||
{PA_CHANNEL_POSITION_FRONT_LEFT, MP_SPEAKER_ID_FL},
|
||||
{PA_CHANNEL_POSITION_FRONT_RIGHT, MP_SPEAKER_ID_FR},
|
||||
@@ -285,105 +272,81 @@ 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_sample_spec ss;
|
||||
struct pa_channel_map map;
|
||||
pa_proplist *proplist = 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;
|
||||
const char *version = pa_get_library_version();
|
||||
|
||||
pthread_mutex_init(&priv->wakeup_lock, NULL);
|
||||
pthread_cond_init(&priv->wakeup, NULL);
|
||||
|
||||
ao->per_application_mixer = true;
|
||||
|
||||
priv->broken_pause = false;
|
||||
/* not sure which versions are affected, assume 0.9.11* to 0.9.14*
|
||||
* known bad: 0.9.14, 0.9.13
|
||||
* known good: 0.9.9, 0.9.10, 0.9.15
|
||||
* To test: pause, wait ca. 5 seconds, framestep and see if MPlayer
|
||||
* hangs somewhen. */
|
||||
if (strncmp(version, "0.9.1", 5) == 0 && version[5] >= '1'
|
||||
&& version[5] <= '4') {
|
||||
MP_WARN(ao, "working around probably broken pause functionality,\n"
|
||||
" see http://www.pulseaudio.org/ticket/440\n");
|
||||
priv->broken_pause = true;
|
||||
}
|
||||
|
||||
if (!(priv->mainloop = pa_threaded_mainloop_new())) {
|
||||
MP_ERR(ao, "Failed to allocate main loop\n");
|
||||
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;
|
||||
}
|
||||
|
||||
MP_VERBOSE(ao, "Library version: %s\n", pa_get_library_version());
|
||||
MP_VERBOSE(ao, "Proto: %lu\n",
|
||||
(long)pa_context_get_protocol_version(priv->context));
|
||||
MP_VERBOSE(ao, "Server proto: %lu\n",
|
||||
(long)pa_context_get_server_protocol_version(priv->context));
|
||||
|
||||
pa_context_set_state_callback(priv->context, context_state_cb, 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 (!(format = pa_format_info_new()))
|
||||
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;
|
||||
|
||||
ss.channels = ao->channels.num;
|
||||
ss.rate = ao->samplerate;
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
format->encoding = map_digital_format(ao->format);
|
||||
if (format->encoding == PA_ENCODING_PCM) {
|
||||
const struct format_map *fmt_map = format_maps;
|
||||
|
||||
while (fmt_map->mp_format != ao->format) {
|
||||
if (fmt_map->mp_format == AF_FORMAT_UNKNOWN) {
|
||||
MP_VERBOSE(ao, "Unsupported format, using default\n");
|
||||
fmt_map = format_maps;
|
||||
break;
|
||||
}
|
||||
fmt_map++;
|
||||
const struct format_map *fmt_map = format_maps;
|
||||
while (fmt_map->mp_format != ao->format) {
|
||||
if (fmt_map->mp_format == AF_FORMAT_UNKNOWN) {
|
||||
MP_VERBOSE(ao, "Unsupported format, using default\n");
|
||||
fmt_map = format_maps;
|
||||
break;
|
||||
}
|
||||
ao->format = fmt_map->mp_format;
|
||||
fmt_map++;
|
||||
}
|
||||
ao->format = fmt_map->mp_format;
|
||||
ss.format = fmt_map->pa_format;
|
||||
|
||||
pa_format_info_set_sample_format(format, fmt_map->pa_format);
|
||||
if (!pa_sample_spec_valid(&ss)) {
|
||||
MP_ERR(ao, "Invalid sample spec\n");
|
||||
goto unlock_and_fail;
|
||||
}
|
||||
|
||||
if (!select_chmap(ao, &map))
|
||||
@@ -394,19 +357,10 @@ 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);
|
||||
|
||||
pa_format_info_set_rate(format, ao->samplerate);
|
||||
pa_format_info_set_channels(format, ao->channels.num);
|
||||
pa_format_info_set_channel_map(format, &map);
|
||||
|
||||
if (!pa_format_info_valid(format)) {
|
||||
MP_ERR(ao, "Invalid audio format\n");
|
||||
goto unlock_and_fail;
|
||||
}
|
||||
|
||||
if (!(priv->stream = pa_stream_new_extended(priv->context, "audio stream",
|
||||
&format, 1, proplist)))
|
||||
if (!(priv->stream = pa_stream_new_with_proplist(priv->context,
|
||||
"audio stream", &ss,
|
||||
&map, proplist)))
|
||||
goto unlock_and_fail;
|
||||
|
||||
pa_proplist_free(proplist);
|
||||
@@ -416,47 +370,39 @@ static int init(struct ao *ao)
|
||||
pa_stream_set_write_callback(priv->stream, stream_request_cb, ao);
|
||||
pa_stream_set_latency_update_callback(priv->stream,
|
||||
stream_latency_update_cb, ao);
|
||||
int buf_size = af_fmt_seconds_to_bytes(ao->format, priv->cfg_buffer / 1000.0,
|
||||
ao->channels.num, ao->samplerate);
|
||||
pa_buffer_attr bufattr = {
|
||||
.maxlength = -1,
|
||||
.tlength = buf_size > 0 ? buf_size : (uint32_t)-1,
|
||||
.tlength = priv->cfg_buffer > 0 ?
|
||||
pa_usec_to_bytes(priv->cfg_buffer * 1000, &ss) : (uint32_t)-1,
|
||||
.prebuf = -1,
|
||||
.minreq = -1,
|
||||
.fragsize = -1,
|
||||
};
|
||||
|
||||
int flags = PA_STREAM_NOT_MONOTONIC;
|
||||
if (!priv->cfg_latency_hacks)
|
||||
flags |= PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE;
|
||||
|
||||
if (pa_stream_connect_playback(priv->stream, sink, &bufattr,
|
||||
flags, NULL, NULL) < 0)
|
||||
PA_STREAM_NOT_MONOTONIC, NULL, NULL) < 0)
|
||||
goto unlock_and_fail;
|
||||
|
||||
/* Wait until the stream is ready */
|
||||
while (1) {
|
||||
int state = pa_stream_get_state(priv->stream);
|
||||
if (state == PA_STREAM_READY)
|
||||
break;
|
||||
if (!PA_STREAM_IS_GOOD(state))
|
||||
goto unlock_and_fail;
|
||||
pa_threaded_mainloop_wait(priv->mainloop);
|
||||
}
|
||||
pa_threaded_mainloop_wait(priv->mainloop);
|
||||
|
||||
if (pa_stream_is_suspended(priv->stream)) {
|
||||
MP_ERR(ao, "The stream is suspended. Bailing out.\n");
|
||||
if (pa_stream_get_state(priv->stream) != PA_STREAM_READY)
|
||||
goto unlock_and_fail;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(priv->mainloop);
|
||||
|
||||
return 0;
|
||||
|
||||
unlock_and_fail:
|
||||
pa_threaded_mainloop_unlock(priv->mainloop);
|
||||
|
||||
if (format)
|
||||
pa_format_info_free(format);
|
||||
if (priv->mainloop)
|
||||
pa_threaded_mainloop_unlock(priv->mainloop);
|
||||
|
||||
fail:
|
||||
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);
|
||||
@@ -517,6 +463,12 @@ static void pause(struct ao *ao)
|
||||
// Resume the audio stream by uncorking it on the server
|
||||
static void resume(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
/* Without this, certain versions will cause an infinite hang because
|
||||
* pa_stream_writable_size returns 0 always.
|
||||
* Note that this workaround causes A-V desync after pause. */
|
||||
if (priv->broken_pause)
|
||||
reset(ao);
|
||||
cork(ao, false);
|
||||
}
|
||||
|
||||
@@ -530,7 +482,8 @@ static int get_space(struct ao *ao)
|
||||
return space / ao->sstride;
|
||||
}
|
||||
|
||||
static double get_delay_hackfixed(struct ao *ao)
|
||||
// Return the current latency in seconds
|
||||
static float get_delay(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,34 +538,6 @@ static double get_delay_hackfixed(struct ao *ao)
|
||||
return latency / 1e6;
|
||||
}
|
||||
|
||||
static double get_delay_pulse(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
pa_usec_t latency = (pa_usec_t) -1;
|
||||
pa_threaded_mainloop_lock(priv->mainloop);
|
||||
while (pa_stream_get_latency(priv->stream, &latency, NULL) < 0) {
|
||||
if (pa_context_errno(priv->context) != PA_ERR_NODATA) {
|
||||
GENERIC_ERR_MSG("pa_stream_get_latency() failed");
|
||||
break;
|
||||
}
|
||||
/* Wait until latency data is available again */
|
||||
pa_threaded_mainloop_wait(priv->mainloop);
|
||||
}
|
||||
pa_threaded_mainloop_unlock(priv->mainloop);
|
||||
return latency == (pa_usec_t) -1 ? 0 : latency / 1000000.0;
|
||||
}
|
||||
|
||||
// Return the current latency in seconds
|
||||
static double get_delay(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
if (priv->cfg_latency_hacks) {
|
||||
return get_delay_hackfixed(ao);
|
||||
} else {
|
||||
return get_delay_pulse(ao);
|
||||
}
|
||||
}
|
||||
|
||||
/* A callback function that is called when the
|
||||
* pa_context_get_sink_input_info() operation completes. Saves the
|
||||
* volume field of the specified structure to the global variable volume.
|
||||
@@ -642,7 +567,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
pa_threaded_mainloop_lock(priv->mainloop);
|
||||
if (!waitop(priv, pa_context_get_sink_input_info(priv->context, devidx,
|
||||
info_func, ao))) {
|
||||
GENERIC_ERR_MSG("pa_context_get_sink_input_info() failed");
|
||||
GENERIC_ERR_MSG("pa_stream_get_sink_input_info() failed");
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
// Warning: some information in pi might be unaccessible, because
|
||||
@@ -705,9 +630,6 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return CONTROL_OK;
|
||||
}
|
||||
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
|
||||
case AOCONTROL_UPDATE_STREAM_TITLE: {
|
||||
char *title = (char *)arg;
|
||||
pa_threaded_mainloop_lock(priv->mainloop);
|
||||
@@ -725,42 +647,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 = {
|
||||
@@ -776,19 +662,16 @@ const struct ao_driver audio_out_pulse = {
|
||||
.pause = pause,
|
||||
.resume = resume,
|
||||
.drain = drain,
|
||||
.wait = wait_audio,
|
||||
.wait = wait,
|
||||
.wakeup = wakeup,
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.cfg_buffer = 250,
|
||||
.cfg_latency_hacks = 1,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("host", cfg_host, 0),
|
||||
OPT_STRING("sink", cfg_sink, 0),
|
||||
OPT_CHOICE_OR_INT("buffer", cfg_buffer, 0, 1, 2000, ({"native", 0})),
|
||||
OPT_FLAG("latency-hacks", cfg_latency_hacks, 0),
|
||||
OPT_CHOICE_OR_INT("buffer", cfg_buffer, 0, 1, 2000, ({"native", -1})),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+29
-15
@@ -52,26 +52,40 @@ static int set_format(struct ao *ao)
|
||||
case AF_FORMAT_S8:
|
||||
rsd_format = RSD_S8;
|
||||
break;
|
||||
case AF_FORMAT_S16:
|
||||
rsd_format = RSD_S16_NE;
|
||||
case AF_FORMAT_S16_LE:
|
||||
rsd_format = RSD_S16_LE;
|
||||
break;
|
||||
case AF_FORMAT_U16:
|
||||
rsd_format = RSD_U16_NE;
|
||||
case AF_FORMAT_S16_BE:
|
||||
rsd_format = RSD_S16_BE;
|
||||
break;
|
||||
case AF_FORMAT_S24:
|
||||
case AF_FORMAT_U24:
|
||||
rsd_format = RSD_S32_NE;
|
||||
ao->format = AF_FORMAT_S32;
|
||||
case AF_FORMAT_U16_LE:
|
||||
rsd_format = RSD_U16_LE;
|
||||
break;
|
||||
case AF_FORMAT_S32:
|
||||
rsd_format = RSD_S32_NE;
|
||||
case AF_FORMAT_U16_BE:
|
||||
rsd_format = RSD_U16_BE;
|
||||
break;
|
||||
case AF_FORMAT_U32:
|
||||
rsd_format = RSD_U32_NE;
|
||||
case AF_FORMAT_S24_LE:
|
||||
case AF_FORMAT_S24_BE:
|
||||
case AF_FORMAT_U24_LE:
|
||||
case AF_FORMAT_U24_BE:
|
||||
rsd_format = RSD_S32_LE;
|
||||
ao->format = AF_FORMAT_S32_LE;
|
||||
break;
|
||||
case AF_FORMAT_S32_LE:
|
||||
rsd_format = RSD_S32_LE;
|
||||
break;
|
||||
case AF_FORMAT_S32_BE:
|
||||
rsd_format = RSD_S32_BE;
|
||||
break;
|
||||
case AF_FORMAT_U32_LE:
|
||||
rsd_format = RSD_U32_LE;
|
||||
break;
|
||||
case AF_FORMAT_U32_BE:
|
||||
rsd_format = RSD_U32_BE;
|
||||
break;
|
||||
default:
|
||||
rsd_format = RSD_S16_NE;
|
||||
ao->format = AF_FORMAT_S16;
|
||||
rsd_format = RSD_S16_LE;
|
||||
ao->format = AF_FORMAT_S16_LE;
|
||||
}
|
||||
|
||||
return rsd_format;
|
||||
@@ -153,7 +167,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;
|
||||
|
||||
+44
-32
@@ -38,20 +38,6 @@ struct priv
|
||||
float buflen;
|
||||
};
|
||||
|
||||
static const int fmtmap[][2] = {
|
||||
{AF_FORMAT_U8, AUDIO_U8},
|
||||
{AF_FORMAT_S8, AUDIO_S8},
|
||||
{AF_FORMAT_U16, AUDIO_U16SYS},
|
||||
{AF_FORMAT_S16, AUDIO_S16SYS},
|
||||
#ifdef AUDIO_S32SYS
|
||||
{AF_FORMAT_S32, AUDIO_S32SYS},
|
||||
#endif
|
||||
#ifdef AUDIO_F32SYS
|
||||
{AF_FORMAT_FLOAT, AUDIO_F32SYS},
|
||||
#endif
|
||||
{0}
|
||||
};
|
||||
|
||||
static void audio_callback(void *userdata, Uint8 *stream, int len)
|
||||
{
|
||||
struct ao *ao = userdata;
|
||||
@@ -118,12 +104,26 @@ static int init(struct ao *ao)
|
||||
|
||||
SDL_AudioSpec desired, obtained;
|
||||
|
||||
desired.format = AUDIO_S16SYS;
|
||||
for (int n = 0; fmtmap[n][0]; n++) {
|
||||
if (ao->format == fmtmap[n][0]) {
|
||||
desired.format = fmtmap[n][1];
|
||||
break;
|
||||
}
|
||||
switch (ao->format) {
|
||||
case AF_FORMAT_U8: desired.format = AUDIO_U8; break;
|
||||
case AF_FORMAT_S8: desired.format = AUDIO_S8; break;
|
||||
case AF_FORMAT_U16_LE: desired.format = AUDIO_U16LSB; break;
|
||||
case AF_FORMAT_U16_BE: desired.format = AUDIO_U16MSB; break;
|
||||
default:
|
||||
case AF_FORMAT_S16_LE: desired.format = AUDIO_S16LSB; break;
|
||||
case AF_FORMAT_S16_BE: desired.format = AUDIO_S16MSB; break;
|
||||
#ifdef AUDIO_S32LSB
|
||||
case AF_FORMAT_S32_LE: desired.format = AUDIO_S32LSB; break;
|
||||
#endif
|
||||
#ifdef AUDIO_S32MSB
|
||||
case AF_FORMAT_S32_BE: desired.format = AUDIO_S32MSB; break;
|
||||
#endif
|
||||
#ifdef AUDIO_F32LSB
|
||||
case AF_FORMAT_FLOAT_LE: desired.format = AUDIO_F32LSB; break;
|
||||
#endif
|
||||
#ifdef AUDIO_F32MSB
|
||||
case AF_FORMAT_FLOAT_BE: desired.format = AUDIO_F32MSB; break;
|
||||
#endif
|
||||
}
|
||||
desired.freq = ao->samplerate;
|
||||
desired.channels = ao->channels.num;
|
||||
@@ -156,18 +156,30 @@ static int init(struct ao *ao)
|
||||
// large, this will help.
|
||||
ao->device_buffer = 3 * obtained.samples;
|
||||
|
||||
ao->format = 0;
|
||||
for (int n = 0; fmtmap[n][0]; n++) {
|
||||
if (obtained.format == fmtmap[n][1]) {
|
||||
ao->format = fmtmap[n][0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ao->format) {
|
||||
if (!ao->probing)
|
||||
MP_ERR(ao, "could not find matching format\n");
|
||||
uninit(ao);
|
||||
return -1;
|
||||
switch (obtained.format) {
|
||||
case AUDIO_U8: ao->format = AF_FORMAT_U8; break;
|
||||
case AUDIO_S8: ao->format = AF_FORMAT_S8; break;
|
||||
case AUDIO_S16LSB: ao->format = AF_FORMAT_S16_LE; break;
|
||||
case AUDIO_S16MSB: ao->format = AF_FORMAT_S16_BE; break;
|
||||
case AUDIO_U16LSB: ao->format = AF_FORMAT_U16_LE; break;
|
||||
case AUDIO_U16MSB: ao->format = AF_FORMAT_U16_BE; break;
|
||||
#ifdef AUDIO_S32LSB
|
||||
case AUDIO_S32LSB: ao->format = AF_FORMAT_S32_LE; break;
|
||||
#endif
|
||||
#ifdef AUDIO_S32MSB
|
||||
case AUDIO_S32MSB: ao->format = AF_FORMAT_S32_BE; break;
|
||||
#endif
|
||||
#ifdef AUDIO_F32LSB
|
||||
case AUDIO_F32LSB: ao->format = AF_FORMAT_FLOAT_LE; break;
|
||||
#endif
|
||||
#ifdef AUDIO_F32MSB
|
||||
case AUDIO_F32MSB: ao->format = AF_FORMAT_FLOAT_BE; break;
|
||||
#endif
|
||||
default:
|
||||
if (!ao->probing)
|
||||
MP_ERR(ao, "could not find matching format\n");
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!ao_chmap_sel_get_def(ao, &sel, &ao->channels, obtained.channels)) {
|
||||
|
||||
+57
-64
@@ -33,7 +33,6 @@ struct priv {
|
||||
struct sio_hdl *hdl;
|
||||
struct sio_par par;
|
||||
int delay;
|
||||
bool playing;
|
||||
int vol;
|
||||
int havevol;
|
||||
#define SILENCE_NMAX 0x1000
|
||||
@@ -61,8 +60,6 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return CONTROL_FALSE;
|
||||
sio_setvol(p->hdl, vol->left * SIO_MAXVOL / 100);
|
||||
break;
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
default:
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
@@ -75,7 +72,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
static void movecb(void *addr, int delta)
|
||||
{
|
||||
struct priv *p = addr;
|
||||
p->delay -= delta;
|
||||
p->delay -= delta * (int)(p->par.bps * p->par.pchan);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -110,22 +107,26 @@ static int init(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
struct af_to_par {
|
||||
int format, bits, sig;
|
||||
};
|
||||
static const struct af_to_par af_to_par[] = {
|
||||
{AF_FORMAT_U8, 8, 0},
|
||||
{AF_FORMAT_S8, 8, 1},
|
||||
{AF_FORMAT_U16, 16, 0},
|
||||
{AF_FORMAT_S16, 16, 1},
|
||||
{AF_FORMAT_U24, 24, 0},
|
||||
{AF_FORMAT_S24, 24, 1},
|
||||
{AF_FORMAT_U32, 32, 0},
|
||||
{AF_FORMAT_S32, 32, 1},
|
||||
};
|
||||
const struct af_to_par *ap;
|
||||
int format, bits, sig, le;
|
||||
} static const af_to_par[] = {
|
||||
{AF_FORMAT_U8, 8, 0, 0},
|
||||
{AF_FORMAT_S8, 8, 1, 0},
|
||||
{AF_FORMAT_U16_LE, 16, 0, 1},
|
||||
{AF_FORMAT_U16_BE, 16, 0, 0},
|
||||
{AF_FORMAT_S16_LE, 16, 1, 1},
|
||||
{AF_FORMAT_S16_BE, 16, 1, 0},
|
||||
{AF_FORMAT_U24_LE, 16, 0, 1},
|
||||
{AF_FORMAT_U24_BE, 24, 0, 0},
|
||||
{AF_FORMAT_S24_LE, 24, 1, 1},
|
||||
{AF_FORMAT_S24_BE, 24, 1, 0},
|
||||
{AF_FORMAT_U32_LE, 32, 0, 1},
|
||||
{AF_FORMAT_U32_BE, 32, 0, 0},
|
||||
{AF_FORMAT_S32_LE, 32, 1, 1},
|
||||
{AF_FORMAT_S32_BE, 32, 1, 0}
|
||||
}, *ap;
|
||||
int i;
|
||||
|
||||
p->hdl = sio_open(p->dev, SIO_PLAY, 1);
|
||||
p->hdl = sio_open(p->dev, SIO_PLAY, 0);
|
||||
if (p->hdl == NULL) {
|
||||
MP_ERR(ao, "can't open sndio %s\n", p->dev);
|
||||
goto error;
|
||||
@@ -146,7 +147,7 @@ static int init(struct ao *ao)
|
||||
p->par.bits = ap->bits;
|
||||
p->par.sig = ap->sig;
|
||||
if (ap->bits > 8)
|
||||
p->par.le = SIO_LE_NATIVE;
|
||||
p->par.le = ap->le;
|
||||
if (ap->bits != SIO_BPS(ap->bits))
|
||||
p->par.bps = ap->bits / 8;
|
||||
break;
|
||||
@@ -172,25 +173,30 @@ static int init(struct ao *ao)
|
||||
MP_ERR(ao, "couldn't get params\n");
|
||||
goto error;
|
||||
}
|
||||
if (p->par.bps > 1 && p->par.le != SIO_LE_NATIVE) {
|
||||
MP_ERR(ao, "swapped endian output not supported\n");
|
||||
goto error;
|
||||
}
|
||||
if (p->par.bits == 8 && p->par.bps == 1) {
|
||||
ao->format = p->par.sig ? AF_FORMAT_S8 : AF_FORMAT_U8;
|
||||
} else if (p->par.bits == 16 && p->par.bps == 2) {
|
||||
ao->format = p->par.sig ? AF_FORMAT_S16 : AF_FORMAT_U16;
|
||||
ao->format = p->par.sig ?
|
||||
(p->par.le ? AF_FORMAT_S16_LE : AF_FORMAT_S16_BE) :
|
||||
(p->par.le ? AF_FORMAT_U16_LE : AF_FORMAT_U16_BE);
|
||||
} else if ((p->par.bits == 24 || p->par.msb) && p->par.bps == 3) {
|
||||
ao->format = p->par.sig ? AF_FORMAT_S24 : AF_FORMAT_U24;
|
||||
ao->format = p->par.sig ?
|
||||
(p->par.le ? AF_FORMAT_S24_LE : AF_FORMAT_S24_BE) :
|
||||
(p->par.le ? AF_FORMAT_U24_LE : AF_FORMAT_U24_BE);
|
||||
} else if ((p->par.bits == 32 || p->par.msb) && p->par.bps == 4) {
|
||||
ao->format = p->par.sig ? AF_FORMAT_S32 : AF_FORMAT_U32;
|
||||
ao->format = p->par.sig ?
|
||||
(p->par.le ? AF_FORMAT_S32_LE : AF_FORMAT_S32_BE) :
|
||||
(p->par.le ? AF_FORMAT_U32_LE : AF_FORMAT_U32_BE);
|
||||
} else {
|
||||
MP_ERR(ao, "couldn't set format\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ao->bps = p->par.bps * p->par.pchan * p->par.rate;
|
||||
ao->no_persistent_volume = true;
|
||||
p->havevol = sio_onvol(p->hdl, volcb, p);
|
||||
sio_onmove(p->hdl, movecb, p);
|
||||
p->delay = 0;
|
||||
if (!sio_start(p->hdl))
|
||||
MP_ERR(ao, "init: couldn't start\n");
|
||||
|
||||
@@ -227,68 +233,55 @@ static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (p->playing) {
|
||||
MP_WARN(ao, "Blocking until remaining audio is played... (sndio design bug).\n");
|
||||
|
||||
p->playing = false;
|
||||
|
||||
if (!sio_stop(p->hdl))
|
||||
MP_ERR(ao, "reset: couldn't stop\n");
|
||||
p->delay = 0;
|
||||
if (!sio_start(p->hdl))
|
||||
MP_ERR(ao, "reset: couldn't start\n");
|
||||
}
|
||||
if (!sio_stop(p->hdl))
|
||||
MP_ERR(ao, "reset: couldn't stop\n");
|
||||
p->delay = 0;
|
||||
if (!sio_start(p->hdl))
|
||||
MP_ERR(ao, "reset: couldn't start\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* play given number of samples until sio_write() blocks
|
||||
* play given number of bytes until sio_write() blocks
|
||||
*/
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int n;
|
||||
|
||||
n = sio_write(p->hdl, data[0], samples * ao->sstride) / ao->sstride;
|
||||
n = sio_write(p->hdl, data[0], samples * ao->sstride);
|
||||
p->delay += n;
|
||||
p->playing = true;
|
||||
/* on AOPLAY_FINAL_CHUNK, just let it underrun */
|
||||
return n;
|
||||
if (flags & AOPLAY_FINAL_CHUNK)
|
||||
reset(ao);
|
||||
return n / ao->sstride;
|
||||
}
|
||||
|
||||
/*
|
||||
* make libsndio call movecb()
|
||||
*/
|
||||
static void update(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int n = sio_pollfd(p->hdl, p->pfd, POLLOUT);
|
||||
while (poll(p->pfd, n, 0) < 0 && errno == EINTR) {}
|
||||
sio_revents(p->hdl, p->pfd);
|
||||
}
|
||||
|
||||
/*
|
||||
* how many samples can be played without blocking
|
||||
* how many bytes can be played without blocking
|
||||
*/
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int n;
|
||||
|
||||
update(ao);
|
||||
/*
|
||||
* call poll() and sio_revents(), so the
|
||||
* delay counter is updated
|
||||
*/
|
||||
n = sio_pollfd(p->hdl, p->pfd, POLLOUT);
|
||||
while (poll(p->pfd, n, 0) < 0 && errno == EINTR)
|
||||
; /* nothing */
|
||||
sio_revents(p->hdl, p->pfd);
|
||||
|
||||
int samples = p->par.bufsz - p->delay;
|
||||
return samples / p->par.round * p->par.round;
|
||||
return (p->par.bufsz * p->par.pchan * p->par.bps - p->delay) / ao->sstride;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
update(ao);
|
||||
|
||||
return p->delay / (double)p->par.rate;
|
||||
return (float)p->delay / (p->par.bps * p->par.pchan * p->par.rate);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -308,8 +301,8 @@ static void audio_resume(struct ao *ao)
|
||||
int n, count, todo;
|
||||
|
||||
/*
|
||||
* we want to start with buffers full, because mpv uses
|
||||
* get_delay() as clock, which would cause video to
|
||||
* we want to start with buffers full, because mplayer uses
|
||||
* get_space() pointer as clock, which would cause video to
|
||||
* accelerate while buffers are filled.
|
||||
*/
|
||||
todo = p->par.bufsz * p->par.pchan * p->par.bps;
|
||||
|
||||
Loaded 100 of 399 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user