Compare commits

..
37 Commits
Author SHA1 Message Date
Alessandro Ghedini 5b6a67bf76 Release 0.5.3 2014-09-10 19:34:45 +02:00
Alessandro Ghedini 41f2ad33e5 player: fix build
This fixes a build failure introduced by c269fa1.
2014-09-10 19:32:54 +02:00
Alessandro Ghedini 27996bf1de Release 0.5.2 2014-09-09 12:18:47 +02:00
wm4 e3d3a47c0d stream_lavf: assume icy title data is terminated with ';'
Should fix #1071.
2014-09-06 14:32:24 +02:00
wm4 b109d20ef7 audio/out: make EOF handling properly event-based
With --gapless-audio=no, changing from one file to the next apparently
made it hang, until the player was woken up by unrelated events like
input. The reason was that the AO doesn't notify the player of EOF
properly. the played was querying ao_eof_reached(), and then just went
to sleep, without anything waking it up.

Make it event-based: the AO wakes up the playloop if the EOF state
changes.

We could have fixed this in a simpler way by synchronously draining the
AO in these cases. But I think proper event handling is preferable.

Fixes: #1069
CC: @mpv-player/stable (perhaps)
2014-09-06 00:22:07 +02:00
wm4 3c7d49c38f audio: correctly initialize output buffer
Just like the previous commit, this takes care of fallout from commit
7ab228, which fixed a bug, but introduced some new ones.

CC: @mpv-player/stable
2014-09-05 21:16:59 +02:00
James Ross-Gowan 0ba2da8a76 player: check for null in resume config search
Signed-off-by: wm4 <wm4@nowhere>

CC: @mpv-player/stable
2014-09-05 21:16:53 +02:00
wm4 1871f2a703 audio: fix obscure audio resync failure with timelines
Somehow, there was a larger misunderstanding in the code: ao_buffer
does not need to be preserved over audio reinit for proper support of
gapless audio. The actual AO internal buffer takes care of this.

In fact, preserving ao_buffer just breaks audio resync. In the ordered
chapter case, end_pts is used, which means not all audio data in the
buffer is played, thus some data is left over when audio decoding
resumes on the next segment. This triggers some code that aborts resync
if there's "audio decoded" (ao_buffer contains something), but no PTS
is known (nothing was actually decoded yet).

Simplify, and always bind the output buffer to the decoder.

CC: @mpv-player/stable (maybe)
2014-09-05 12:01:26 +02:00
wm4 4a8e5ea269 build: handle insane libavcodec API bullshit
The oldest supported FFmpeg release doesn't provide
av_vdpau_alloc_context(). With these versions, the application has no
other choice than to hard code the size of AVVDPAUContext. (On the other
hand, there's av_alloc_vdpaucontext(), which does the same thing, but is
FFmpeg specific - not sure if it was available early enough, so I'm not
touching it.)

Newer FFmpeg and Libav releases require you to call this function, for
ABI compatibility reasons. It's the typcal lakc of foresight that make
FFmpeg APIs terrible. mpv successfully pretended that this crap didn't
exist (ABI compat. is near impossible to reach anyway) - but it appears
newer developments in Libav change the function from initializing the
struct with all-zeros to something else, and mpv vdpau decoding would
stop working as soon as this new work is relewased.

So, add a configure test (sigh).

CC: @mpv-player/stable
2014-09-05 12:01:22 +02:00
wm4 367665b4c4 demux_mkv: allow up to 256 MB of extradata to make broken files work
What the flying fuck?

Unfortunately, these are already in the wild.

CC: @mpv-player/stable
2014-09-05 12:01:17 +02:00
wm4 46437e96c6 path: remove XDG bullshit
Don't worry, your ~/.config/... paths are safe. This merely removes
handling of $XDG_CONFIG_DIRS for global paths.

Maybe there is a better solution for this, like still including the
"traditional" config dir. But I will leave the fine reading of this
(crappy) spec and fixing the code accordingly to someone else. So, if
anyone has interest in getting this behavior back, you will have to
write a patch. This patch should _also_ not break expected behavior.

Fixes #1060.
2014-09-03 23:22:08 +02:00
wm4 29d3bbdaa2 player: properly resume playlists loaded at runtime
If a playlist is loaded from the internal playlist (like
"mpv playlist.m3u"), then attempt to resume from it.

CC: @mpv-player/stable
2014-09-03 23:21:36 +02:00
Alexander Preisinger 16c3b27d0a gl_wayland: fix crash after eglInitialze failed
Only execute most of the opengl termination procedure if we actually have an
egl context.
2014-09-01 17:13:24 +02:00
Martin Herkt f8dc208d0c man: fix a whole bunch of typos
Conflicts:
	DOCS/man/input.rst
	DOCS/man/options.rst
2014-09-01 14:53:55 +02:00
wm4 6d006a5a39 playlist: don't add the base path twice
Loading a playlist with --playlist from a sub-directory added the
playlist's base path twice: one time in the playlist demuxer, and then
again in playlist_parse_file(). The latter function is used only for
--playlist, so it worked when loading the playlist directly.

(This is probably a mess-up when the MPlayer playlist parsers were
replaced with newer code.)

CC: @mpv-player/stable
2014-09-01 14:48:23 +02:00
wm4 6a355959d9 audio/out: prevent burning CPU when seeking while paused
The audio/video sync code in player/audio.c calls ao_reset() each time
audio decoding is entered, but the player is paused, and there would be
more than 1 sample to skip to make audio start match with video start.
This caused a wakeup feedback loop with push.c.

CC: @mpv-player/stable
2014-09-01 14:48:17 +02:00
wm4 36bdbbe716 x11: when using --wid, inherit event flags from parent window
When embedding a X window, it's hard to control whether it receives
mouse/keyboard input or not. It seems the X protocol itself makes this
hard (basically due to the outdated design mismatching with modern
toolkits), and we have to take care of these things explicitly.

Simply do this by manually querying and using the parent window event
flags.

This restores some MPlayer behavior (it doesn't add back exactly the
same code, but it's very similar).

This probably has some potential to interfere with libmpv embedding, so
bump the client API minor.

CC: @mpv-player/stable (if applied, client-api-changes.rst has to be
    adjusted to include the 0.5.2 release)
2014-09-01 14:48:01 +02:00
wm4 c269fa1609 player: fix quit handling when seeking past EOF while paused
The player didn't quit when seeking past EOF in audio-only mode while
paused. The only case when we don't want to quit is when the last video
frame is displayed while paused.

This logic was probably broken a while ago, but I'm not exactly sure.

CC: @mpv-player/stable

Conflicts:
	player/playloop.c
2014-08-31 00:19:40 +02:00
wm4 0f1d6c09e0 audio: restore old speed change behavior
Don't attempt to resync after speed changes. Note that most other cases
of audio reinit (like switching tracks etc.) still resync, but other
code paths take care of setting the audio_status accordingly.

This restores the old behavior of not trying to fix audio desync, which
was probably changed with commit 261506e3.

Note that the code as of now wasn't even entirely correct, since the A/V
sync values are slightly shifted. The dsync depends on the audio buffer
size, so a larger buffer size will show more extreme desync. Also see
mplayer2 commit 213a224e, which should fixed this - it was not merged
into mpv, because it disabled audio for too long, resulting in a worse
user experience. This is similar to the issue this commit attempts to
fix.

Fixes: #1042 (probably)
CC: @mpv-player-stable
2014-08-28 18:10:32 +02:00
Alessandro Ghedini b677937c27 Release 0.5.1 2014-08-26 10:27:40 +02:00
Otto Modinos fdd648bdf9 manpage: mention that mp.commandv doesn't expand properties
The little lua snippet at #488 as well as the actual implementation
seems to indicate that not expanding properties is indeed the correct
behavior.  Document that.

Signed-off-by: wm4 <wm4@nowhere>
2014-08-26 10:24:21 +02:00
Stefano Pigozzi 334fd10f1c coreaudio_device: fix overwriting of user input
Fixes #1030
2014-08-25 11:24:01 +02:00
wm4 226735331c options: compatibility hack for --slave-broken
Seems some programs were still relying on it. Whatever, it's not hard to
support.

CC: @mpv-player/stable
2014-08-25 11:23:30 +02:00
wm4 5523370ab9 player: don't clobber playback position on video end
If video reaches EOF, and audio is also EOF (or is otherwise not
meaningful, like audio disabled), then the playback position was briefly
set to 0. Fix this by not trying to use a bogus audio PTS.

CC: @mpv-player/stable (maybe)
2014-08-25 11:23:26 +02:00
Bin Jin 8c7d61f1d9 manpage: remove duplicated content
Probably be accidentally added in b6b8bffd.

CC: @mpv-player/stable
Signed-off-by: wm4 <wm4@nowhere>
2014-08-25 11:23:05 +02:00
wm4 f021b5a379 etc/mpv.desktop: add audio/flac mimetype
Fixes #1025.

CC: @mpv-player/stable
2014-08-21 15:16:58 +02:00
wm4 43e164964c manpage: fix opengl-hq defaults
The FBO format was changed some time ago.

CC: @mpv-player/stable
2014-08-18 11:14:48 +02:00
Philip Sequeira aeb64723fd TOOLS/zsh.pl: protect global environment 2014-08-17 14:14:48 +02:00
Philip Sequeira f935838b2d TOOLS/zsh.pl: fix a ret that hadn't been changed to rc 2014-08-17 14:14:30 +02:00
Alessandro Ghedini ec46ad79e2 TOOLS/zsh.pl: properly set the return value
The previous commit made the completion script always return non-zero, even when
a match is found. This explicitly sets the return value to zero whenever a match
is found but defaults to non-zero in case nothing is matched.
2014-08-15 16:31:18 +02:00
c_14 d6ccc4d8de TOOLS/zsh.pl: properly return non-zero when no matches are found
Returning a non-zero value signals to the zsh completion system that no matches
were added by the script so that it can try the user-defined matchers (e.g.
those defined with matcher-list).

Fixes #1008.
2014-08-13 23:00:36 +02:00
Alexander Preisinger 8664dc3c47 wayland: pointer interface is created after theme
Just always load the theme. It gets freed properly and dosn't bother anyone.

Fixes #1012.

CC: @mpv-player/stable
2014-08-13 23:00:26 +02:00
wm4 51ce535822 player: don't quit with --loop-file
Fixes #1009.

CC: @mpv-player/stable
2014-08-13 23:00:21 +02:00
Alessandro Ghedini e33df9b7bc vdpau: correctly mark invalid mixer as such on vdp_video_mixer_create() failure
Otherwise vdp_video_mixer_destroy() would later fail when called on an invalid
video mixer handle. With mesa r600 vdpau driver, this would cause a segfault.
2014-08-13 19:16:53 +02:00
wm4 6310bd60e6 vo_vdpau: fix screenshots with anamorphic video
Fixes #1007.

CC: @mpv-player/stable
2014-08-13 10:55:49 +02:00
wm4 6729e39983 DOCS: mark the recent release in client-api-changes.rst
Also add instructions to release-policy.md, since this can be easily
forgotten.

CC: @mpv-player/stable
2014-08-13 10:55:44 +02:00
Alessandro Ghedini 88f247c0af Release 0.5.0 2014-08-12 10:50:38 +02:00
351 changed files with 11137 additions and 20928 deletions

No files matched your search

+4 -26
View File
@@ -4,23 +4,17 @@ os:
- linux
- osx
env:
matrix:
- LIBAV=libav-stable
- LIBAV=libav-git
- LIBAV=ffmpeg-stable
- LIBAV=ffmpeg-git
global:
# Coverity token
- secure: "H21mSRlMhk4BKS0xHZvCFGJxteCP0hRVUxTuNfM2Z9HBsyutuLEYMtViLO86VtM+Tqla3xXPzUdS4ozLwI72Ax/5ZUDXACROj73yW6QhFB5D6rLut12+FjqC7M33Qv2hl0xwgNBmR5dsm1ToP37+Wn+ecJQNvN8fkTXF+HVzOEw="
- LIBAV=libav-stable
- LIBAV=libav-git
- LIBAV=ffmpeg-stable
- LIBAV=ffmpeg-git
compiler:
- clang
- gcc
branches:
only:
- master
- ci
- coverity_scan
matrix:
exclude:
@@ -30,12 +24,6 @@ matrix:
env: LIBAV=libav-git
- os: osx
env: LIBAV=ffmpeg-git
- os: osx
compiler: gcc
- os: linux
env: LIBAV=ffmpeg-stable
- os: linux
compiler: clang
before_install: ./travis-deps libass-stable $LIBAV
script:
@@ -50,13 +38,3 @@ notifications:
- "irc.freenode.org#mpv-player-dev"
on_success: change
on_failure: always
addons:
coverity_scan:
project:
name: "mpv-player/mpv"
description: "Build submitted via Travis CI"
notification_email: mpv-team@googlegroups.com
build_command_prepend: "./bootstrap.py && ./waf configure"
build_command: "./waf build"
branch_pattern: coverity_scan
+1 -31
View File
@@ -25,38 +25,8 @@ 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.
--- mpv 0.5.2 is released ---
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)
-181
View File
@@ -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;
}
+2 -85
View File
@@ -2,23 +2,14 @@
// This example can be built with: qmake && make
#include <clocale>
#include <sstream>
#include <QtGlobal>
#include <QFileDialog>
#include <QStatusBar>
#include <QMenuBar>
#include <QMenu>
#include <QGridLayout>
#include <QApplication>
#include <QTextEdit>
#if QT_VERSION >= 0x050000
#include <QJsonDocument>
#endif
#include <mpv/qthelper.hpp>
#include "qtexample.h"
@@ -35,9 +26,6 @@ static void wakeup(void *ctx)
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
setWindowTitle("Qt embedding demo");
setMinimumSize(640, 480);
QMenu *menu = menuBar()->addMenu(tr("&File"));
QAction *on_open = new QAction(tr("&Open"), this);
on_open->setShortcuts(QKeySequence::Open);
@@ -47,25 +35,16 @@ MainWindow::MainWindow(QWidget *parent) :
statusBar();
QMainWindow *log_window = new QMainWindow(this);
log = new QTextEdit(log_window);
log->setReadOnly(true);
log_window->setCentralWidget(log);
log_window->setWindowTitle("mpv log window");
log_window->setMinimumSize(500, 50);
log_window->show();
mpv = mpv_create();
if (!mpv)
throw "can't create mpv instance";
// Create a video child window. Force Qt to create a native window, and
// pass the window ID to the mpv wid option. Works on: X11, win32, Cocoa
// pass the window ID to the mpv wid option. This doesn't work on OSX,
// because Cocoa doesn't support this form of embedding.
mpv_container = new QWidget(this);
setCentralWidget(mpv_container);
mpv_container->setAttribute(Qt::WA_DontCreateNativeAncestors);
mpv_container->setAttribute(Qt::WA_NativeWindow);
// If you have a HWND, use: int64_t wid = (intptr_t)hwnd;
int64_t wid = mpv_container->winId();
mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid);
@@ -73,21 +52,10 @@ MainWindow::MainWindow(QWidget *parent) :
// 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.
@@ -113,47 +81,9 @@ void MainWindow::handle_mpv_event(mpv_event *event)
// was stopped.
statusBar()->showMessage("");
}
} else if (strcmp(prop->name, "chapter-list") == 0 ||
strcmp(prop->name, "track-list") == 0)
{
// Dump the properties as JSON for demo purposes.
#if QT_VERSION >= 0x050000
if (prop->format == MPV_FORMAT_NODE) {
QVariant v = mpv::qt::node_to_variant((mpv_node *)prop->data);
// Abuse JSON support for easily printing the mpv_node contents.
QJsonDocument d = QJsonDocument::fromVariant(v);
append_log("Change property " + QString(prop->name) + ":\n");
append_log(d.toJson().data());
}
#endif
}
break;
}
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;
@@ -190,14 +120,6 @@ void MainWindow::on_file_open()
}
}
void MainWindow::append_log(const QString &text)
{
QTextCursor cursor = log->textCursor();
cursor.movePosition(QTextCursor::End);
cursor.insertText(text);
log->setTextCursor(cursor);
}
MainWindow::~MainWindow()
{
if (mpv)
@@ -207,11 +129,6 @@ MainWindow::~MainWindow()
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Qt sets the locale in the QApplication constructor, but libmpv requires
// the LC_NUMERIC category to be set to "C", so change it back.
std::setlocale(LC_NUMERIC, "C");
MainWindow w;
w.show();
-5
View File
@@ -5,8 +5,6 @@
#include <mpv/client.h>
class QTextEdit;
class MainWindow : public QMainWindow
{
Q_OBJECT
@@ -24,9 +22,6 @@ private slots:
private:
QWidget *mpv_container;
mpv_handle *mpv;
QTextEdit *log;
void append_log(const QString &text);
void create_player();
void handle_mpv_event(mpv_event *event);
+1 -1
View File
@@ -5,7 +5,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = qtexample
TEMPLATE = app
CONFIG += link_pkgconfig debug
CONFIG += link_pkgconfig
PKGCONFIG = mpv
SOURCES += qtexample.cpp
-1
View File
@@ -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));
-9
View File
@@ -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
View File
@@ -27,13 +27,13 @@ The general simplified syntax is::
<filename>,<start in seconds>,<length in seconds>
If the start time is omitted, 0 is used. If the length is omitted, the
estimated remaining duration of the source file is used.
estimated duration of the source file is used.
Note::
mpv can't use ordered chapter files in EDL entries. Usage of relative or
absolute paths as well as any protocol prefixes is prevented for security
reasons.
mpv can't use ordered chapter files or libquvi-resolved URLs in EDL
entries. Usage of relative or absolute paths as well as any protocol
prefixes is prevented for security reasons.
Syntax of mpv EDL files
+5 -6
View File
@@ -77,12 +77,10 @@ Available filters are:
``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.
@@ -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
+11 -7
View File
@@ -114,18 +114,25 @@ Available audio output drivers are:
with fixed setups.
``coreaudio`` (Mac OS X only)
Native Mac OS X audio output driver using AudioUnits and the CoreAudio
Native Mac OS X audio output driver using the AudioUnits and the CoreAudio
sound server.
Automatically redirects to ``coreaudio_exclusive`` when playing compressed
formats.
``device_id=<id>``
ID of output device to use (0 = default device)
``help``
List all available output devices with their IDs.
``coreaudio_exclusive`` (Mac OS X only)
``coreaudio_device`` (Mac OS X only)
Native Mac OS X audio output driver using direct device access and
exclusive mode (bypasses the sound server).
Supports only compressed formats (AC3 and DTS).
``device_id=<id>``
ID of output device to use (0 = default device)
``help``
List all available output devices with their IDs.
``openal``
Experimental OpenAL audio output driver
@@ -174,9 +181,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
+92 -147
View File
@@ -10,159 +10,104 @@ should generally be treated as a completely different program.
.. note::
These lists are incomplete.
General Changes from MPlayer to mpv
-----------------------------------
General Changes for MPlayer to mpv
----------------------------------
This listing is about changes introduced by mplayer2 and mpv relatively to
MPlayer.
Player
~~~~~~
* New name for the binary (``mpv``). New location for config files (either
``~/.config/mpv/mpv.conf``, or if you want, ``~/.mpv/config``).
* Encoding functionality (replacement for MEncoder, see the `ENCODING`_ section).
* Support for Lua scripting (see the `LUA SCRIPTING`_ section).
* Better pause handling (e.g. do not unpause on a command).
* Precise seeking support.
* Improvements in audio/video sync handling.
* Do not lose settings when playing a new file in the same player instance.
* Slave mode compatibility broken (see below).
* Re-enable screensaver while the player is paused.
* Allow resuming playback at a later point with ``Shift+q``, also see the
``quit_watch_later`` input command.
* Switch to GPLv2+. (Technically speaking, MPlayer as a whole seems to be
GPLv2-only, while mplayer2 is GPLv3+ - see ``Copyright`` file for details.)
* Removal of the internal GUI, MEncoder, OSD menu, video kernel drivers for
Linux 2.4 (including VIDIX)
* Large internal cleanups
* Removal of support for dead platforms
* New build system
* No embedded copy of FFmpeg and other libraries
* Better pause handling (do not unpause on a command)
* Better MKV support (such as ordered chapters)
* vo_vdpau improvements
* Precise seeking support
* Native OpenGL backend for OS X
* General OS X improvements
* Improvements in audio/video sync handling
* Cleaned up terminal output
* Gapless audio support (``--gapless-audio``)
* Improved responsiveness on user input
* Support for modifier keys (alt, shift, ctrl) in input.conf
* OSS4 volume control
* More correct color reproduction (color matrix generation)
* Use libass for subtitle rendering by default (better quality)
* Generally preferring FFmpeg/Libav over internal demuxers and decoders
* Improvements when playing multiple files (``--fixed-vo``)
* Screenshot improvements (instant screenshots without 1-frame delay, allow
taking screenshots even with hardware decoding)
* Improved support for PulseAudio
* Generally improved MS Windows support (dealing with Unicode file names,
improved ``--vo=direct3d``, improved window handling)
* Better OSD rendering (using libass). This has full Unicode support, and
languages like Arabic should be better supported.
* Cleaned up terminal output (nicer status line, less useless noise)
* Support for playing URLs of popular streaming sites directly
(e.g. ``mpv https://www.youtube.com/watch?v=...``)
* Improved OpenGL output (``--vo=opengl-hq``)
* Make ``--softvol`` default (**mpv** is not a mixer control panel)
* Improved support for .cue files
* Screenshot improvements (can save screenshots as JPEG or PNG, configurable
file names, support for taking screenshots with or without subtitles - the
``screenshot`` video filter is not needed anymore, and should not be put
into the mpv config file)
* Removal of teletext support
* Removal of most built-in demuxers, using libavformat instead
* Removal of built-in network support, using libavformat instead (also,
support https via libavformat)
* Replace image VOs (``--vo=jpeg`` etc.) with ``--vo=image``
* Do not lose settings when playing a new file in the same player instance
* New location for config files, new name for the binary.
* Slave mode compatibility broken (see below)
* Encoding functionality (replacement for MEncoder, see ``DOCS/encoding.rst``)
* Remove ``--vo=gif89a``, ``--vo=md5sum``, ``--vo=yuv4mpeg``, as encoding can
handle these use cases. For yuv4mpeg, for example, use:
``mpv input.mkv -o output.y4m --no-audio --oautofps --oneverdrop``.
* Image subtitles (DVDs etc.) are rendered in color and use more correct
positioning (color can be disabled with ``--sub-gray``)
* Wayland support
* Support for precise scrolling which scales the parameter of commands. If the
input doesn't support precise scrolling the scale factor stays 1.
* OS X: Cocoa event loop is independent from MPlayer's event loop, so user
actions like accessing menus and live resizing do not block the playback.
* OS X: Apple Remote support.
* OS X: Media Keys support.
* Windows: Added WASAPI audio output.
* New OSD bar with chapter marks and not positioned in the middle of the video
(though this can be customized with the ``--osd-bar-align-y`` option).
* Allow customizing whether a key binding for seeking shows the video time, the
OSD bar, or nothing (see section `Input Command Prefixes`_).
* Display list of chapters and audio/subtitle tracks on OSD (see section
`Properties`_).
* ``--keep-open`` option to stop the player from closing the window and
exiting after playback ends.
* A client API, that allows embedding **mpv** into applications
(see ``libmpv/client.h`` in the sources).
Input
~~~~~
* Improved default keybindings. MPlayer bindings are also available (see
``etc/mplayer-input.conf`` in the source tree).
* Improved responsiveness on user input.
* Support for modifier keys (alt, shift, ctrl) in input.conf.
* Allow customizing whether a key binding for seeking shows the video time, the
OSD bar, or nothing (see the `Input Command Prefixes`_ section).
* Support mapping multiple commands to one key.
Audio
~~~~~
* Support for gapless audio (see the ``--gapless-audio`` option).
* Support for OSS4 volume control.
* Improved support for PulseAudio.
* Make ``--softvol`` default (**mpv** is not a mixer control panel).
* By default, do pitch correction if playback speed is increased.
* Improved downmixing and output of surround audio:
- Instead of using hardcoded pan filters to do remixing, libavresample is used
- Channel maps are used to identify the channel layout, so e.g. ``3.0`` and
``2.1`` audio can be distinguished.
Video
~~~~~
* Wayland support.
* Native support for VAAPI and VDA. Improved VDPAU video output.
* Improved OpenGL output (see the ``opengl-hq`` video output).
* Make hardware decoding work with the ``opengl`` video output.
* Re-enable screensaver while the player is paused.
* Matroska edition switching at runtime.
* Support for libavfilter (for video->video and audio->audio). This allows
using most of FFmpeg's filters, which improve greatly on the old MPlayer
filters in features, performance, and correctness.
* More correct color reproduction (color matrix generation).
* Better subtitle rendering using libass by default.
* Improvements when playing multiple files (``--fixed-vo`` is default, do not
reset settings by default when playing a new file).
* Replace image video outputs (``--vo=jpeg`` etc.) with ``--vo=image``.
* Removal of ``--vo=gif89a``, ``--vo=md5sum``, ``--vo=yuv4mpeg``, as encoding
can handle these use cases. For yuv4mpeg, for example, use::
mpv input.mkv -o output.y4m --no-audio --oautofps --oneverdrop
* Image subtitles (DVDs etc.) are rendered in color and use more correct
positioning (color for image subs can be disabled with ``--sub-gray``).
OSD and terminal
~~~~~~~~~~~~~~~~
* Cleaned up terminal output: nicer status line, less useless noise.
* Improved OSD rendering using libass, with full Unicode support.
* New OSD bar with chapter marks. Not positioned in the middle of the video
(this can be customized with the ``--osd-bar-align-y`` option).
* Display list of chapters and audio/subtitle tracks on OSD (see the
`Properties`_ section).
Screenshots
~~~~~~~~~~~
* Instant screenshots without 1-frame delay.
* Support for taking screenshots even with hardware decoding.
* Support for saving screenshots as JPEG or PNG.
* Support for configurable file names.
* Support for taking screenshots with or without subtitles.
Note that the ``screenshot`` video filter is not needed anymore, and should not
be put into the mpv config file.
Miscellaneous
~~~~~~~~~~~~~
* Better MKV support (e.g. ordered chapters, 3D metadata).
* Matroska edition switching at runtime.
* Support for playing URLs of popular streaming sites directly.
(e.g. ``mpv https://www.youtube.com/watch?v=...``).
Requires a recent version of ``youtube-dl`` to be installed, and
``ytdl=yes`` in the mpv config file.
* Support for precise scrolling which scales the parameter of commands. If the
input doesn't support precise scrolling the scale factor stays 1.
* Improved downmixing and output of surround audio. Instead of using hardcoded
pan filters to do remixing, use libavresample. Channel maps are used to
identify the channel layout, so e.g. ``3.0`` and ``2.1`` audio can be
distinguished.
* Allow resuming playback at a later point with ``Shift+q``, also see
``quit_watch_later`` input command.
* Support mapping multiple commands to one key.
* Allow changing/adjusting video filters at runtime. (This is also used to make
the ``D`` key insert vf_yadif if deinterlacing is not supported otherwise).
* Improved support for .cue files.
Mac OS X
~~~~~~~~
* Native OpenGL backend.
* Cocoa event loop is independent from MPlayer's event loop, so user
actions like accessing menus and live resizing do not block the playback.
* Apple Remote support.
* Media Keys support.
* VDA support using libavcodec hwaccel API instead of FFmpeg's decoder with up
the ``D`` key insert vf_yadif if deinterlacing is not supported otherwise.)
* Native VAAPI support
* OS X: VDA support using libavcodec hwaccel API instead of FFmpeg's decoder. Up
to 2-2.5x reduction in CPU usage.
Windows
~~~~~~~
* Improved support for Unicode file names.
* Improved window handling.
* Do not block playback when moving the window.
* Improved Direct3D video output.
* Added WASAPI audio output.
Internal changes
~~~~~~~~~~~~~~~~
* Switch to GPLv2+ (see ``Copyright`` file for details).
* Removal of lots of cruft:
- Internal GUI (replaced by the OSC, see the `ON SCREEN CONTROLLER`_ section).
- MEncoder (replaced by native encoding, see the `ENCODING`_ section).
- OSD menu.
- Kernel video drivers for Linux 2.4 (including VIDIX).
- Teletext support.
- Support for dead platforms.
- Most built-in demuxers have been replaced by their libavformat counterparts.
- Built-in network support has been replaced by libavformat's (which also
supports https URLs).
- Embedded copies of libraries (such as FFmpeg).
* General code cleanups (including refactoring or rewrites of many parts).
* New build system.
* Many bug fixes and removal of long-standing issues.
* Generally preferring FFmpeg/Libav over internal demuxers, decoders, and
filters.
* Make hardware decoding in general work with the ``opengl`` video output.
* Lua scripting (see `LUA SCRIPTING`_)
* A client API, that allows embedding **mpv** into applications
(see ``libmpv/client.h`` in the sources)
* General bug fixes and removal of long-standing issues
* General code cleanups (including refactoring or rewrites of many parts)
* Many more changes
Detailed Listing of User-visible Changes
----------------------------------------
@@ -389,8 +334,8 @@ Slave mode
(The option was readded in 0.5.1 and sets exactly these options.)
* A JSON RPC protocol giving access to the client API is also supported. See
`JSON IPC`_ for more information.
* A JSON RPC protocol giving access to the client API is planned, but nothing
has emerged yet.
* **mpv** also provides a client API, which can be used to embed the player
by loading it as shared library. (See ``libmpv/client.h`` in the sources.)
+32 -288
View File
@@ -11,9 +11,8 @@ value (upper case if combined with ``Shift``), or a name for special keys. For
example, ``a`` maps to the ``a`` key without shift, and ``A`` maps to ``a``
with shift.
The file is located in the mpv configuration directory (normally at
``~/.config/mpv/input.conf`` depending on platform). The default bindings are
defined here::
The file is normally located in ``~/.config/mpv/input.conf`` (some platforms
may use different paths). The default bindings are defined here::
https://github.com/mpv-player/mpv/blob/master/etc/input.conf
@@ -160,9 +159,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 +190,7 @@ List of Input Commands
force
Terminate playback if the first file is being played.
``loadfile "<file>" [replace|append|append-play [options]]``
``loadfile "<file>" [replace|append [options]]``
Load the given file and play it.
Second argument:
@@ -204,8 +201,6 @@ List of Input Commands
Append the file to the playlist.
<append-play>
Append the file, and if nothing is currently playing, start playback.
(Always starts with the added file, even if the playlist was not empty
before running this command.)
The third argument is a list of options and values which should be set
while the file is playing. It is of the form ``opt1=value1,opt2=value2,..``.
@@ -262,39 +257,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 +325,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 +394,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 +448,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 +497,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 +527,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.
@@ -674,23 +594,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.
@@ -724,13 +635,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 +644,9 @@ Property list
estimating the playback position from the byte position, if the file
duration is not known.
``ratio-pos`` (RW)
Position in current file (0.0-1.0). higher precision that ``percent-pos``.
``time-pos`` (RW)
Position in current file in seconds.
@@ -814,10 +723,6 @@ Property list
"title" MPV_FORMAT_STRING
"default" MPV_FORMAT_FLAG
``ab-loop-a``, ``ab-loop-b`` (TW)
Set/get A-B loop points. See corresponding options and ``ab_loop`` command.
The special value ``no`` on either of these properties disables looping.
``angle`` (RW)
Current DVD angle.
@@ -891,10 +796,6 @@ Property list
be different ``pause`` in special situations, such as when the player
pauses itself due to low network cache.
This also returns ``yes`` if playback is restarting or if nothing is
playing at all. In other words, it's only ``no`` if there's actually
video playing. (Behavior since mpv 0.7.0.)
``cache``
Network cache fill state (0-100.0).
@@ -920,22 +821,9 @@ Property list
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.
``paused-for-cache``
Returns ``yes`` when playback is paused because of waiting for the cache.
``cache-buffering-state``
Return the percentage (0-100) of the cache fill status until the player
will unpause (related to ``paused-for-cache``).
``eof-reached``
Returns ``yes`` if end of playback was reached, ``no`` otherwise. Note
that this is usually interesting only if ``--keep-open`` is enabled,
@@ -997,9 +885,6 @@ Property list
``deinterlace`` (RW)
See ``--deinterlace``.
``field-dominance`` (RW)
See ``--field-dominance``
``colormatrix`` (RW)
See ``--colormatrix``.
@@ -1122,17 +1007,13 @@ Property list
``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``.
@@ -1152,16 +1033,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``.
@@ -1228,8 +1102,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 +1128,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 +1137,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 +1234,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,141 +1261,15 @@ 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.
-183
View File
@@ -1,183 +0,0 @@
JSON IPC
========
mpv can be controlled by external programs using the JSON-based IPC protocol. It
can be enabled by specifying the path to a unix socket using the option
``--input-unix-socket``. Clients can connect to this socket and send commands to
the player or receive events from it. You can use the ``socat`` tool to send
commands (and receive reply) from the shell.
.. warning::
This is not intended to be a secure network protocol. It is explicitly
insecure: there is no authentication, no encryption, and the commands
themselves are insecure too. For example, the ``run`` command is exposed,
which can run arbitrary system commands. The use-case is controlling the
player locally. This is not different from the MPlayer slave protocol.
Protocol
--------
Clients can execute commands on the player by sending JSON messages of the
following form:
::
{ "command": ["command_name", "param1", "param2", ...] }
where ``command_name`` is the name of the command to be executed, followed by a
list of parameters. Parameters must be formatted as native JSON values
(integers, strings, booleans, ...). Every message **must** be terminated with
``\n``. Additionally, ``\n`` must not appear anywhere inside the message. In
practice this means that messages should be minified before being sent to mpv.
mpv will then send back a reply indicating whether the command was run
correctly, and an additional field holding the command-specific return data (it
can also be null).
::
{ "error": "success", "data": null }
mpv will also send events to clients with JSON messages of the following form:
::
{ "event": "event_name" }
where ``event_name`` is the name of the event. Additional event-specific fields
can also be present. See `List of events`_ for a list of all supported events.
If the first character (after skipping whitespace) is not ``{``, the command
will be interpreted as non-JSON text command, as they are used in input.conf
(or ``mpv_command_string()`` in the client API). Additionally, line starting
with ``#`` and empty lines are ignored.
Currently, embedded 0 bytes terminate the current line, but you should not
rely on this.
Commands
--------
Additionally to the commands described in `List of Input Commands`_, a few
extra commands can also be used as part of the protocol:
``client_name``
Return the name of the client as string. This is the string ``ipc-N`` with
N being an integer number.
``get_time_us``
Return the current mpv internal time in microseconds as a number. This is
basically the system time, with an arbitrary offset.
``get_property``
Return the value of the given property. The value will be sent in the data
field of the replay message.
Example:
::
{ "command": ["get_property", "volume"] }
{ "data": 50.0, "error": "success" }
``get_property_string``
Like ``get_property``, but the resulting data will always be a string.
Example:
::
{ "command": ["get_property_string", "volume"] }
{ "data": "50.000000", "error": "success" }
``set_property``
Set the given property to the given value. See `Properties`_ for more
information about properties.
Example:
::
{ "command": ["set_property", "pause", true] }
{ "error": "success" }
``set_property_string``
Like ``set_property``, but the argument value must be passed as string.
Example:
::
{ "command": ["set_property_string", "pause", "yes"] }
{ "error": "success" }
``observe_property``
Watch a property for changes. If the given property is changed, then an
event of type ``property-change`` will be generated
Example:
::
{ "command": ["observe_property", 1, "volume"] }
{ "error": "success" }
{ "event": "property-change", "id": 1, "data": 52.0, "name": "volume" }
``observe_property_string``
Like ``observe_property``, but the resulting data will always be a string.
Example:
::
{ "command": ["observe_property_string", 1, "volume"] }
{ "error": "success" }
{ "event": "property-change", "id": 1, "data": "52.000000", "name": "volume" }
``unobserve_property``
Undo ``observe_property`` or ``observe_property_string``. This requires the
numeric id passed to the observe command as argument.
Example:
::
{ "command": ["unobserve_property", 1] }
{ "error": "success" }
``request_log_messages``
Enable output of mpv log messages. They will be received as events. The
parameter to this command is the log-level (see ``mpv_request_log_messages``
C API function).
Log message output is meant for humans only (mostly for debugging).
Attempting to retrieve information by parsing these messages will just
lead to breakages with future mpv releases. Instead, make a feature request,
and ask for a proper event that returns the information you need.
``suspend``
Suspend the mpv main loop. There is a long-winded explanation of this in
the C API function ``mpv_suspend()``. In short, this prevents the player
from displaying the next video frame, so that you don't get blocked when
trying to access the player.
``resume``
Undo one ``suspend`` call. ``suspend`` increments an internal counter, and
``resume`` decrements it. When 0 is reached, the player is actually resumed.
``get_version``
Returns the client API version the C API of the remote mpv instance
provides. (Also see ``DOCS/client-api-changes.rst``.)
UTF-8
-----
Normally, all strings are in UTF-8. Sometimes it can happen that strings are
in some broken encoding (often happens with file tags and such, and filenames
on many Unixes are not required to be in UTF-8 either). This means that mpv
sometimes sends invalid JSON. If that is a problem for the client application's
parser, it should filter the raw data for invalid UTF-8 sequences and perform
the desired replacement, before feeding the data to its JSON parser.
mpv will not attempt to construct invalid UTF-8 with broken escape sequences.
+36 -144
View File
@@ -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 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.
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
------------
@@ -94,15 +78,6 @@ The ``mp`` module is preloaded, although it can be loaded manually with
the ``expand-properties`` prefix, or the ``mp.get_property`` family of
functions.
``mp.command_native(table [,def])``
Similar to ``mp.commandv``, but pass the argument list as table. This has
the advantage that in at least some cases, arguments can be passed as
native types.
Returns a result table on success (usually empty), or ``def, error`` on
error. ``def`` is the second parameter provided to the function, and is
nil if it's missing.
``mp.get_property(name [,def])``
Return the value of the given property as string. These are the same
properties as used in input.conf. See `Properties`_ for a list of
@@ -181,7 +156,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
Return the current mpv internal time in seconds as a number. This is
basically the system time, with an arbitrary offset.
``mp.add_key_binding(key, name|fn [,fn [,flags]])``
``mp.add_key_binding(key, name|fn [,fn])``
Register callback to be run on a key binding. The binding will be mapped to
the given ``key``, which is a string describing the physical key. This uses
the same key names as in input.conf, and also allows combinations
@@ -198,23 +173,8 @@ The ``mp`` module is preloaded, although it can be loaded manually with
overwritten. You can omit the name, in which case a random name is generated
internally.
The last argument is used for optional flags. This is a table, which can
have the following entries:
``repeatable``
If set to ``true``, enables key repeat for this specific binding.
``complex``
If set to ``true``, then ``fn`` is called on both key up and down
events (as well as key repeat, if enabled), with the first
argument being a table. This table has an ``event`` entry, which
is set to one of the strings ``down``, ``repeat``, ``up`` or
``press`` (the latter if key up/down can't be tracked). It further
has an ``is_mouse`` entry, which tells whether the event was caused
by a mouse button.
Internally, key bindings are dispatched via the ``script_message_to`` or
``script_binding`` input commands and ``mp.register_script_message``.
Internally, key bindings are dispatched via the ``script_message_to`` input
command and ``mp.register_script_message``.
Trying to map multiple commands to a key will essentially prefer a random
binding, while the other bindings are not called. It is guaranteed that
@@ -237,7 +197,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
::
y script_binding something
y script_message something
This will print the message when the key ``y`` is pressed. (``x`` will
@@ -248,7 +208,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
::
y script_binding fooscript.something
y script_message_to fooscript something
``mp.add_forced_key_binding(...)``
This works almost the same as ``mp.add_key_binding``, but registers the
@@ -526,13 +486,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 +522,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 +563,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 +644,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.
+8 -109
View File
@@ -12,7 +12,8 @@ a media player
SYNOPSIS
========
| **mpv** [options] [file|URL|PLAYLIST|-]
| **mpv** [options] [file|URL|-]
| **mpv** [options] --playlist=PLAYLIST
| **mpv** [options] files
DESCRIPTION
@@ -46,10 +47,6 @@ UP and DOWN
Seek forward/backward 1 minute. Shift+arrow does a 5 second exact seek (see
``--hr-seek``).
Ctrl+LEFT and Ctrl+RIGHT
Seek to the previous/next subtitle. Subject to some restrictions and
might not work always; see ``sub_seek`` command.
[ and ]
Decrease/increase current playback speed by 10%.
@@ -63,7 +60,7 @@ BACKSPACE
Go backward/forward in the playlist.
ENTER
Go forward in the playlist.
Go forward in the playlist, even over the end.
p / SPACE
Pause (pressing again unpauses).
@@ -76,7 +73,7 @@ p / SPACE
Step backward. Pressing once will pause, every consecutive press will
play one frame in reverse and then go into pause mode again.
q
q / ESC
Stop playing and quit.
Q
@@ -101,9 +98,6 @@ m
f
Toggle fullscreen (see also ``--fs``).
ESC
Exit fullscreen mode.
T
Toggle stay-on-top (see also ``--ontop``).
@@ -129,12 +123,6 @@ j and J
x and z
Adjust subtitle delay by +/- 0.1 seconds.
l
Set/clear A-B loop points. See ``ab_loop`` command for details.
Ctrl + and Ctrl -
Adjust audio delay by +/- 0.1 seconds.
V
Toggle subtitle VSFilter aspect compatibility mode. See
``--ass-vsfilter-aspect-compat`` for more info.
@@ -157,10 +145,6 @@ PGUP and PGDWN
"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
Activate/deactivate deinterlacer.
@@ -271,7 +255,7 @@ need to escape special characters. To work this around, the path can be
additionally wrapped in the ``%n%string_of_length_n`` syntax (see above).
Some mpv options interpret paths starting with ``~``. Currently, the prefix
``~~/`` expands to the mpv configuration directory (usually ``~/.config/mpv/``).
``~~/`` expands to the mpv configuration directory (usually ``~/.mpv/``).
``~/`` expands to the user's home directory. (The trailing ``/`` is always
required.)
@@ -361,11 +345,11 @@ File-specific Configuration Files
You can also write file-specific configuration files. If you wish to have a
configuration file for a file called 'video.avi', create a file named
'video.avi.conf' with the file-specific options in it and put it in
``~/.config/mpv/``. You can also put the configuration file in the same directory
as the file to be played. Both require you to set the ``--use-filedir-conf``
``~/.mpv/``. You can also put the configuration file in the same directory
as the file to be played, as long as you give the ``--use-filedir-conf``
option (either on the command line or in your global config file). If a
file-specific configuration file is found in the same directory, no
file-specific configuration is loaded from ``~/.config/mpv``. In addition, the
file-specific configuration is loaded from ``~/.mpv``. In addition, the
``--use-filedir-conf`` option enables directory-specific configuration files.
For this, mpv first tries to load a mpv.conf from the same directory
as the file played and then tries to load any file-specific configuration.
@@ -420,52 +404,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 +412,6 @@ PROTOCOLS
be specified. mpv will never attempt to guess whether a filename is
actually a network address. A protocol prefix is always required.
Note that not all prefixes are documented here. Undocumented prefixes are
either aliases to documented protocols, or are just redirections to
protocols implemented and documented in FFmpeg.
``-``
Play data from stdin.
@@ -567,8 +501,6 @@ PROTOCOLS
.. include:: lua.rst
.. include:: ipc.rst
.. include:: changes.rst
ENVIRONMENT VARIABLES
@@ -724,39 +656,6 @@ override the standard directory ``~/.config/mpv/``.
Also, the old config location at ``~/.mpv/`` is still read, and if the XDG
variant does not exist, will still be preferred.
FILES ON WINDOWS
================
On win32 (if compiled with MinGW, but not Cygwin), the default config file
locations are different:
``%APPDATA%/mpv/mpv.conf``
Preferred mpv config file. This maps to a system and user-specific path,
for example ``C:\users\USERNAME\Application Data\mpv\mpv.conf``. You can
find the exact path by running ``echo %APPDATA%\mpv\mpv.conf`` in cmd.exe.
``%APPDATA%/mpv/input.conf``
key bindings (see `INPUT.CONF`_ section)
``%APPDATA%/mpv/lua/``
equivalent of ``~/.config/mpv/lua/`` on Unix.
The environment variable ``$MPV_HOME`` completely overrides these, like on
UNIX.
Config files located in the same directory as ``mpv.exe`` are loaded with
lower priority. Some config files are loaded only once, which means that
e.g. of 2 ``input.conf`` files located in two config directories, only the
one from the directory with higher priority will be loaded.
A third config directory with lowest priority is the directory named ``mpv``
in the same directory as ``mpv.exe``. This used to be the directory with
highest priority, but is now discouraged to use and might be removed in the
future.
Note that mpv likes to mix ``/`` and ``\`` path separators for simplicity.
kernel32.dll accepts this, but cmd.exe does not.
EXAMPLES OF MPV USAGE
=====================
+163 -442
View File
@@ -45,15 +45,6 @@ Track Selection
``--vid=<ID|auto|no>``
Select video channel. ``auto`` selects the default, ``no`` disables video.
``--ff-aid=<ID|auto|no>``, ``--ff-sid=<ID|auto|no>``, ``--ff-vid=<ID|auto|no>``
Select audio/subtitle/video streams by the FFmpeg stream index. The FFmpeg
stream index is relatively arbitrary, but useful when interacting with
other software using FFmpeg (consider ``ffprobe``).
Note that with external tracks (added with ``--sub-file`` and similar
options) will have duplicate IDs. In that case, the first stream in order
is selected.
``--edition=<ID|auto>``
(Matroska files only)
Specify the edition (set of chapters) to use, where 0 is the first. If set
@@ -104,9 +95,6 @@ Playback Control
``--speed=<0.01-100>``
Slow down or speed up playback by the factor given as parameter.
If ``--audio-pitch-correction`` is used, playing with a speed higher than
normal automatically inserts the ``scaletempo`` audio filter.
``--loop=<N|inf|no>``
Loops playback ``N`` times. A value of ``1`` plays it one time (default),
``2`` two times, etc. ``inf`` means forever. ``no`` is the same as ``1`` and
@@ -128,23 +116,21 @@ Playback Control
no format is detected, t will be treated as list of files, separated by
newline characters. Note that XML playlist formats are not supported.)
You can play playlists directly and without this option, however, this
option disables any security mechanisms that might be in place. You may
also need this option to load plaintext files as playlist.
.. warning::
The way mpv uses playlist files via ``--playlist`` is not safe against
maliciously constructed files. Such files may trigger harmful actions.
The way mpv uses playlist files is not safe against maliciously
constructed files. Such files may trigger harmful actions.
This has been the case for all mpv and MPlayer versions, but
unfortunately this fact was not well documented earlier, and some people
have even misguidedly recommended use of ``--playlist`` with untrusted
sources. Do NOT use ``--playlist`` with random internet sources or files
you do not trust!
Playlist can contain entries using other protocols, such as local files,
or (most severely), special protocols like ``avdevice://``, which are
inherently unsafe.
The main problem is that playlists can point to arbitrary network
addresses (including local addresses inside of your LAN), and thus
can't be considered secure. Playlists also can contain entries using
other protocols, such as local files, or (most severely), special
protocols like ``avdevice://``, which are inherently unsafe.
``--chapter-merge-threshold=<number>``
Threshold for merging almost consecutive ordered chapter parts in
@@ -212,28 +198,26 @@ Playback Control
(i.e. not with stdin, pipe, etc).
``--load-unsafe-playlists``
Load URLs from playlists which are considered unsafe (default: no). This
includes special protocols and anything that doesn't refer to normal files.
Local files and HTTP links on the other hand are always considered safe.
Normally, something like ``mpv playlist.m3u`` won't load the playlist. This
is because the playlist code is unsafe. (This is the same in all other
variations of MPlayer.)
Note that ``--playlist`` always loads all entries, so you use that instead
if you really have the need for this functionality.
See ``--playlist`` for details.
``--loop-file=<N|inf|no>``
Loop a single file N times. ``inf`` means forever, ``no`` means normal
playback. For compatibility, ``--loop-file`` and ``--loop-file=yes`` are
also accepted, and are the same as ``--loop-file=inf``.
Note: this option will allow opening playlists using the ``playlist``
special demuxer. The ``--playlist`` uses different code, and supports more
playlist formats than the playlist demuxer. This means that for now, the
``--playlist`` option should always be used if you intend to open playlists.
Background: the special demuxer contains newly written code, while the
``--playlist`` option uses the old MPlayer code. Adding support for more
playlist formats to the special demuxer is work in progress, and eventually
the old code should disappear.
The difference to ``--loop`` is that this doesn't loop the playlist, just
the file itself. If the playlist contains only a single file, the difference
between the two option is that this option performs a seek on loop, instead
of reloading the file.
``--ab-loop-a=<time>``, ``--ab-loop-b=<time>``
Set loop points. If playback passes the ``b`` timestamp, it will seek to
the ``a`` timestamp. Seeking past the ``b`` point doesn't loop (this is
intentional). The loop-points can be adjusted at runtime with the
corresponding properties. See also ``ab_loop`` command.
``--loop-file``
Loop a single file. The difference to ``--loop=inf`` is that this doesn't
loop the playlist, just the file itself. If the playlist contains only a
single file, the difference between the two option is that this option
performs a seek on loop, instead of reloading the file.
``--ordered-chapters``, ``--no-ordered-chapters``
Enabled by default.
@@ -253,10 +237,6 @@ Playback Control
Note: a playlist can be as simple as a text file containing filenames
separated by newlines.
``--chapters-file=<filename>``
Load chapters from this file, instead of using the chapter metadata found
in the main file.
``--sstep=<sec>``
Skip <sec> seconds after every frame.
@@ -264,12 +244,6 @@ Playback Control
Without ``--hr-seek``, skipping will snap to keyframes.
``--stop-playback-on-init-failure=<yes|no>``
Stop playback if either audio or video fails to initialize. Currently,
the default behavior is ``no`` for the command line player, but ``yes``
for libmpv. With ``no``, playback will continue in video-only or audio-only
mode if one of them fails. This doesn't affect playback of audio-only or
video-only files.
Program Behavior
----------------
@@ -285,8 +259,9 @@ Program Behavior
Print version string and exit.
``--no-config``
Do not load default configuration files. This prevents loading of both the
user-level and system-wide ``mpv.conf`` and ``input.conf`` files. Other
Do not load default configuration files. This prevents loading of
``~/.config/mpv/mpv.conf`` and ``~/.config/mpv/input.conf``, as well as
loading the same files from system wide configuration directories. Other
configuration files are blocked as well, such as resume playback files.
.. note::
@@ -342,8 +317,7 @@ Program Behavior
Specify configuration file to be parsed after the default ones.
``--load-scripts=<yes|no>``
If set to ``no``, don't auto-load scripts from the ``lua`` configuration
subdirectory (usually ``~/.config/mpv/lua/``).
If set to ``no``, don't auto-load scripts from ``~/.mpv/lua/``.
(Default: ``yes``)
``--lua=<filename>``
@@ -358,15 +332,14 @@ Program Behavior
``--merge-files``
Pretend that all files passed to mpv are concatenated into a single, big
file. This uses timeline/EDL support internally. Note that this won't work
for ordered chapter files.
for ordered chapter files or quvi-resolved URLs (such as YouTube links).
This option is interpreted at program start, and doesn't affect for
example files or playlists loaded with the ``loadfile`` or ``loadlist``
commands.
``--no-resume-playback``
Do not restore playback position from the ``watch_later`` configuration
subdirectory (usually ``~/.config/mpv/watch_later/``).
Do not restore playback position from ``~/.mpv/watch_later/``.
See ``quit_watch_later`` input command.
``--profile=<profile1,profile2,...>``
@@ -422,24 +395,6 @@ Program Behavior
May be dangerous if playing from untrusted media.
``--ytdl``, ``--no-ytdl``
Enable the youtube-dl hook-script. It will look at the input URL, and will
play the video located on the website. This works with many streaming sites,
not just the one the scripts are named after. This requires a recent version
of youtube-dl to be installed on the system.
If the script can't do anything with an URL, it will do nothing.
Currently disabled by default, because youtube-dl's generic extractor can
get stuck on some URL, preventing playback.
(Note: this is the replacement for the now removed libquvi support.)
``--ytdl-format=<best|worst|mp4|webm|...>``
Video format/quality that is directly passed to youtube-dl. The possible
values are specific to the website and the video, for a given url the
available formats can be found with the command
``youtube-dl --list-formats URL``.
Video
-----
@@ -475,45 +430,16 @@ Video
Do not sleep when outputting video frames. Useful for benchmarks when used
with ``--no-audio.``
``--framedrop=<mode>``
Skip displaying some frames to maintain A/V sync on slow systems, or
playing high framerate video on video outputs that have an upper framerate
limit.
``--framedrop=<no|yes>``
Skip displaying some frames to maintain A/V sync on slow systems. Video
filters are not applied to such frames. For B-frames even decoding is
skipped completely. May produce unwatchably choppy output.
The argument selects the drop methods, and can be one of the following:
<no>
Disable any framedropping.
<vo>
Drop late frames on video output (default). This still decodes and
filters all frames, but doesn't render them on the VO. It tries to query
the display FPS (X11 only, not correct on multi-monitor systems), or
assumes infinite display FPS if that fails. Drops are indicated in
the terminal status line as ``D:`` field. If the decoder is too slow,
in theory all frames would have to be dropped (because all frames are
too late) - to avoid this, frame dropping stops if the effective
framerate is below 10 FPS.
<decoder>
Old, decoder-based framedrop mode. (This is the same as ``--framedrop=yes``
in mpv 0.5.x and before.) This tells the decoder to skip frames (unless
they are needed to decode future frames). May help with slow systems,
but can produce unwatchably choppy output, or even freeze the display
complete. Not recommended.
The ``--vd-lavc-framedrop`` option controls what frames to drop.
<decoder+vo>
Enable both modes. Not recommended.
The ``--vd-lavc-framedrop`` option controls what frames to drop.
.. note::
``--vo=vdpau`` has its own code for the ``vo`` framedrop mode. Slight
differences to other VOs are possible.
``--display-fps=<fps>``
Set the maximum assumed display FPS used with ``--framedrop``. By default
a detected value is used (X11 only, not correct on multi-monitor systems),
or infinite display FPS if that fails. Infinite FPS means only frames too
late are dropped. If a correct FPS is provided, frames that are predicted
to be too late are dropped too.
Practical use of this feature is questionable. Disabled by default.
``--hwdec=<api>``
Specify the hardware video decoding API that should be used if possible.
@@ -527,19 +453,16 @@ Video
:vdpau: requires ``--vo=vdpau`` or ``--vo=opengl`` (Linux only)
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux with Intel GPUs only)
:vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
:vda: requires ``--vo=opengl`` (OS X only)
:dxva2-copy: copies video back to system RAM (Windows only) (experimental)
:vda: requires ``--vo=opengl`` or ``--vo=corevideo`` (OS X only)
``auto`` tries to automatically enable hardware decoding using the first
available method. This still depends what VO you are using. For example,
if you are not using ``--vo=vdpau`` or ``--vo=opengl``, vdpau decoding will
never be enabled. Also note that if the first found method doesn't actually
work, it will always fall back to software decoding, instead of trying the
next method (might matter on some Linux systems).
if you are not using ``--vo=vdpau``, vdpau decoding will never be enabled.
Also note that if the first found method doesn't actually work, it will
always fall back to software decoding, instead of trying the next method.
The ``vaapi-copy`` mode allows you to use vaapi with any VO. Because
this copies the decoded video back to system RAM, it's likely less efficient
than the ``vaapi`` mode.
The ``vaapi-copy`` function allows you to use vaapi with any VO. Because
this copies the decoded video back to system RAM, it's quite inefficient.
.. note::
@@ -608,22 +531,6 @@ Video
which means the value ``0`` would rotate the video according to the
rotation metadata.)
``--video-stereo-mode=<mode>``
Set the stereo 3D output mode (default: ``mono``). This is done by inserting
the ``stereo3d`` conversion filter.
The mode ``mono`` is an alias to ``ml``, which refers to the left frame in
2D. This is the default, which means mpv will try to show 3D movies in 2D,
instead of the mangled 3D image not intended for consumption (such as
showing the left and right frame side by side, etc.).
The pseudo-mode ``none`` disables automatic conversion completely.
Use ``--video-stereo-mode=help`` to list all available modes. Check with
the ``stereo3d`` filter documentation to see what the names mean. Note that
some names refer to modes not supported by ``stereo3d`` - these modes can
appear in files, but can't be handled properly by mpv.
``--video-zoom=<value>``
Adjust the video display scale factor by the given value. The unit is in
fractions of the (scaled) window video size.
@@ -714,6 +621,31 @@ Video
``mpv --hwdec=vdpau --vo=vdpau --hwdec-codecs=h264,mpeg2video``
Enable vdpau decoding for h264 and mpeg2 only.
``--quvi-format=<best|default|...>``
Video format/quality that is directly passed to libquvi (default: ``best``).
This is used when opening links to streaming sites like YouTube. The
interpretation of this value is highly specific to the streaming site and
the video.
libquvi 0.4.x:
The only well-defined values that work on all sites are ``best``
(best quality/highest bandwidth, default), and ``default`` (lowest
quality).
The quvi command line tool can be used to find out which formats are
supported for a given URL: ``quvi --query-formats URL``.
libquvi 0.9.x:
The following explanations are relevant:
`<http://quvi.sourceforge.net/r/api/0.9/glossary_termino.html#m_stream_id>`_
The ``quvi-format`` property can be used at runtime to cycle through the
list of formats. Unfortunately, this is slow. On libquvi 0.4.x, this
functionality is limited to switching between ``best`` and ``default`` if
the ``cycle`` input command is used.
``--vd-lavc-check-hw-profile=<yes|no>``
Check hardware decoder profile (default: yes). If ``no`` is set, the
highest profile of the hardware decoder is unconditionally selected, and
@@ -787,30 +719,6 @@ Video
Audio
-----
``--audio-pitch-correction=<yes|no>``
If this is enabled (default), playing with a speed higher than normal
automatically inserts the ``scaletempo`` audio filter. For details, see
audio filter section.
``--audio-device=<name>``
Use the given audio device. This consists of the audio output name, e.g.
``alsa``, followed by ``/``, followed by the audio output specific device
name.
You can list audio devices with ``--audio-device=help``. This outputs the
device name in quotes, followed by a description. The device name is what
you have to pass to the ``--audio-device`` option.
The default value for this option is ``auto``, which tries every audio
output in preference order with the default device.
Note that many AOs have a ``device`` sub-option, which overrides the
device selection of this option (but not the audio output selection).
Likewise, forcing an AO with ``--ao`` will override the audio output
selection of ``--audio-device`` (but not the device selecton).
Currently not implemented for most AOs.
``--ao=<driver1[:suboption1[=value]:...],driver2,...[,]>``
Specify a priority list of audio output drivers to be used. For
interactive use one would normally specify a single one to use, but in
@@ -939,9 +847,8 @@ Audio
lists speaker names, which can be used to express arbitrary channel
layouts (e.g. ``fl-fr-lfe`` is 2.1).
You can use ``--audio-channels=auto`` to disable this. In this case, the AO
use the channel layout as the audio filter chain indicates. (``empty`` is
an accepted obsolete for ``auto``.)
You can use ``--audio-channels=empty`` to disable this. In this case, the AO
use the channel layout as the audio filter chain indicates.
This will also request the channel layout from the decoder. If the decoder
does not support the layout, it will fall back to its native channel layout.
@@ -978,7 +885,7 @@ Audio
of the current media, the lavrresample audio filter will be inserted into
the audio filter layer to compensate for the difference.
``--gapless-audio=<no|yes|weak>``
``--gapless-audio=<no|yes|weak``
Try to play consecutive audio files with no silence or disruption at the
point of file change. Default: ``weak``.
@@ -1033,10 +940,9 @@ Audio
maximum amplification, i.e. amplify by 200%. The default volume (no
change in volume) will be ``50`` in this case.
``--audio-client-name=<name>``
The application name the player reports to the audio API. Can be useful
if you want to force a different audio profile (e.g. with PulseAudio),
or to set your own application name when using libmpv.
``--volstep=<0-100>``
Set the step size of mixer volume changes in percent of the full range
(default: 3).
``--volume-restore-data=<string>``
Used internally for use by playback resume (e.g. with ``quit_watch_later``).
@@ -1050,20 +956,6 @@ Audio
Do not use.
``--audio-buffer=<seconds>``
Set the audio output minimum buffer. The audio device might actually create
a larger buffer if it pleases. If the device creates a smaller buffer,
additional audio is buffered in an additional software buffer.
Making this larger will make soft-volume and other filters react slower,
introduce additional issues on playback speed change, and block the
player on audio format changes. A smaller buffer might lead to audio
dropouts.
This option should be used for testing only. If a non-default value helps
significantly, the mpv developers should be contacted.
Default: 0.2 (200 ms).
Subtitles
---------
@@ -1122,7 +1014,7 @@ Subtitles
This affects ASS subtitles as well, and may lead to incorrect subtitle
rendering. Use with care, or use ``--sub-text-font-size`` instead.
``--sub-scale-with-window=<yes|no>``
``--sub-scale-with-window=yes|no``
Make the subtitle font size relative to the window, instead of the video.
This is useful if you always want the same font size, even if the video
doesn't covert the window fully, e.g. because screen aspect and window
@@ -1165,7 +1057,7 @@ Subtitles
Using this option may lead to incorrect subtitle rendering.
``--ass-hinting=<none|light|normal|native>``
``--ass-hinting=none|light|normal|native``
Set font hinting type. <type> can be:
:none: no hinting (default)
@@ -1183,7 +1075,7 @@ Subtitles
``--ass-line-spacing=<value>``
Set line spacing value for SSA/ASS renderer.
``--ass-shaper=<simple|complex>``
``--ass-shaper=simple|complex``
Set the text layout engine used by libass.
:simple: uses Fribidi only, fast, doesn't render some languages correctly
@@ -1207,7 +1099,6 @@ Subtitles
:yes: Apply all the ``--ass-*`` style override options. Changing the default
for any of these options can lead to incorrect subtitle rendering
(default).
:signfs: like ``yes``, but apply ``--sub-scale`` only to signs
:no: Render subtitles as forced by subtitle scripts.
:force: Try to force the font style as defined by the ``--sub-text-*``
options. Requires a modified libass, can break rendering easily.
@@ -1268,6 +1159,15 @@ Subtitles
a subtitle script with another video file. The ``--ass-style-override``
option doesn't affect how this option is interpreted.
``--quvi-fetch-subtitles=<yes|no>``
Toggles fetching of subtitles from streaming sites with libquvi. Disabled
by default, because it's unreliable and slow. Note that when enabled,
subtitles will always be fetched, even if subtitles are explicitly
disabled with ``--no-sub`` (because you might want to enable subtitles
at runtime).
Supported when using libquvi 0.9.x.
``--stretch-dvd-subs=<yes|no>``
Stretch DVD subtitles when playing anamorphic videos for better looking
fonts on badly mastered DVDs. This switch has no effect when the
@@ -1416,19 +1316,12 @@ Subtitles
- ``/path/to/video/sub/``
- ``/path/to/video/subtitles/``
- ``/tmp/subs/``
- the ``sub`` configuration subdirectory (usually ``~/.config/mpv/sub/``)
- ``~/.mpv/sub/``
``--sub-visibility``, ``--no-sub-visibility``
Can be used to disable display of subtitles, but still select and decode
them.
``--sub-clear-on-seek``
(Obscure, rarely useful.) Can be used to play broken mkv files with
duplicate ReadOrder fields. ReadOrder is the first field in a
Matroska-style ASS subtitle packets. It should be unique, and libass
uses it for fast elimination of duplicates. This option disables caching
of subtitles across seeks, so after a seek libass can't eliminate subtitle
packets with the same ReadOrder as earlier packets.
Window
------
@@ -1479,8 +1372,7 @@ Window
See also ``--screen``.
``--keep-open``
Do not terminate when playing or seeking beyond the end of the file, and
there is not next file to be played (and ``--loop`` is not used).
Do not terminate when playing or seeking beyond the end of the file.
Instead, pause the player. When trying to seek beyond end of the file, the
player will pause at an arbitrary playback position (or, in corner cases,
not redraw the window at all).
@@ -1493,12 +1385,6 @@ Window
chapter will terminate playback as well, even if ``--keep-open`` is
given.
Since mpv 0.6.0, this doesn't pause if there is a next file in the playlist,
or the playlist is looped. Approximately, this will pause when the player
would normally exit, but in practice there are corner cases in which this
is not the case (e.g. ``mpv --keep-open file.mkv /dev/null`` will play
file.mkv normally, then fail to open ``/dev/null``, then exit).
``--force-window``
Create a video output window even if there is no video. This can be useful
when pretending that mpv is a GUI application. Currently, the window
@@ -1663,6 +1549,13 @@ Window
there is a change in video parameters, video stream or file. This used to
be the default behavior. Currently only affects X11 VOs.
``--fs-missioncontrol``
(OS X only)
Use OS X Mission Control's fullscreen feature instead of the custom one
provided by mpv. This can potentially break a lot of stuff like
``--geometry`` and is disabled by default. On the other hand it provides
a more 'OS X-like' user experience.
``--heartbeat-cmd=<command>``
Command that is executed every 30 seconds during playback via *system()* -
i.e. using the shell. The time between the commands can be customized with
@@ -1698,19 +1591,12 @@ Window
.. note::
This does not affect the normal screensaver operation in any way.
``--no-keepaspect``, ``--keepaspect``
``--no-keepaspect`` will always stretch the video to window size, and will
disable the window manager hints that force the window aspect ratio.
(Ignored in fullscreen mode.)
``--no-keepaspect-window``, ``--keepaspect-window``
``--keepaspect-window`` (the default) will lock the window size to the
video aspect. ``--no-keepaspect-window`` disables this behavior, and will
instead add black bars if window aspect and video aspect mismatch. Whether
this actually works depends on the VO backend.
(Ignored in fullscreen mode.)
``--monitoraspect=<ratio>``
Set the aspect ratio of your monitor or TV screen. A value of 0 disables a
previous setting (e.g. in the config file). Overrides the
@@ -1738,25 +1624,14 @@ Window
to to work this around using ``--heartbeat-cmd`` instead.
``--wid=<ID>``
This tells mpv to attach to an existing window. If a VO is selected that
supports this option, it will use that window for video output. mpv will
scale the video to the size of this window, and will add black bars to
compensate if the aspect ratio of the video is different.
(X11 and Windows only)
This tells mpv to attach to an existing window. The ID is interpreted as
"Window" on X11, and as HWND on Windows. If a VO is selected that supports
this option, a new window will be created and the given window will be set
as parent. The window will always be resized to cover the parent window
fully, and will add black bars to compensate for the video aspect ratio.
On X11, the ID is interpreted as a ``Window`` on X11. Unlike
MPlayer/mplayer2, mpv always creates its own window, and sets the wid
window as parent. The window will always be resized to cover the parent
window fully. The value ``0`` is interpreted specially, and mpv will
draw directly on the root window.
On win32, the ID is interpreted as ``HWND``. Pass it as value cast to
``intptr_t``. mpv will create its own window, and set the wid window as
parent, like with X11.
On OSX/Cocoa. the ID is interpreted as ``NSView*``. Pass it as value cast
to ``intptr_t``. mpv will creates its own sub-view. Because OSX does not
support window embedding of foreign processes, this works only with libmpv,
and will crash when used from the command line.
See also ``--slave-broken``.
``--no-window-dragging``
Don't move the window when clicking on it and moving the mouse pointer.
@@ -1764,26 +1639,16 @@ Window
``--x11-name``
Set the window class name for X11-based video output methods.
``--x11-netwm=<yes|no|auto>``
``--x11-netwm=no``
(X11 only)
Control the use of NetWM protocol features.
Disable use of the NetWM protocol when switching to or from fullscreen.
This may or may not help with broken window managers. This provides some
functionality that was implemented by the now removed ``--fstype`` option.
Actually, it is not known to the developers to which degree this option
was needed, so feedback is welcome.
Specifically, ``yes`` will force use of NetWM fullscreen support, even if
not advertised by the WM. This can be useful for WMs that are broken on
purpose, like XMonad. (XMonad supposedly doesn't advertise fullscreen
support, because Flash uses it. Apparently, applications which want to
use fullscreen anyway are supposed to either ignore the NetWM support hints,
or provide a workaround. Shame on XMonad for deliberately breaking X
protocols (as if X isn't bad enough already).
By default, NetWM support is autodetected (``auto``).
This option might be removed in the future.
By default, NetWM support is autodetected, and using this option forces
autodetection to fail.
Disc Devices
@@ -1843,7 +1708,7 @@ Disc Devices
Add ``<value>`` sectors to the values reported when addressing tracks.
May be negative.
``--cdda-skip=<yes|no>``
``--cdda-skip=<es|no``
(Never) accept imperfect data reconstruction.
``--dvd-speed=<speed>``
@@ -2062,22 +1927,6 @@ Demuxer
and hr-seeks, and this option changes behavior with relative or imprecise
seeks only.
You can use the ``--demuxer-mkv-subtitle-preroll-secs`` option to specify
how mach data the demuxer should pre-read at most in order to find subtitle
packets that may overlap. Setting this to 0 will effectively disable this
preroll mechanism. Setting a very large value can make seeking very slow,
and an extremely large value would completely reread the entire file from
start to seek target on every seek - seeking can become slower towards the
end of the file. The details are messy, and the value is actually rounded
down to the cluster with the previous video keyframe.
Some files, especially files muxed with newer mkvmerge versions, have
information embedded that can be used to determine what subtitle packets
overlap with a seek target. In these cases, mpv will reduce the amount
of data read to a minimum. (Although it will still read *all* data between
the cluster that contains the first wanted subtitle packet, and the seek
target.)
See also ``--hr-seek-demuxer-offset`` option. This option can achieve a
similar effect, but only if hr-seek is active. It works with any demuxer,
but makes seeking much slower, as it has to decode audio and video data
@@ -2085,23 +1934,12 @@ Demuxer
``--mkv-subtitle-preroll`` is a deprecated alias.
``--demuxer-mkv-subtitle-preroll-secs=<value>``
See ``--demuxer-mkv-subtitle-preroll``.
``--demuxer-mkv-probe-video-duration``
When opening the file, seek to the end of it, and check what timestamp the
last video packet has, and report that as file duration. This is strictly
for compatibility with Haali only. In this mode, it's possible that opening
will be slower (especially when playing over http), or that behavior with
broken files is much worse. So don't use this option.
``--demuxer-rawaudio-channels=<value>``
Number of channels (or channel layout) if ``--demuxer=rawaudio`` is used
(default: stereo).
``--demuxer-rawaudio-format=<value>``
Sample format for ``--demuxer=rawaudio`` (default: s16le).
Use ``--demuxer-rawaudio-format=help`` to get a list of all formats.
``--demuxer-rawaudio-rate=<value>``
Sample rate for ``--demuxer=rawaudio`` (default: 44 kHz).
@@ -2137,32 +1975,20 @@ Demuxer
``--demuxer-thread=<yes|no>``
Run the demuxer in a separate thread, and let it prefetch a certain amount
of packets (default: yes). Having this enabled may lead to smoother
playback, but on the other hand can add delays to seeking or track
switching.
of packets (default: no).
``--demuxer-readahead-secs=<seconds>``
``--demuxer-readahead-packets=N``
If ``--demuxer-thread`` is enabled, this controls how much the demuxer
should buffer ahead in seconds (default: 0.2). As long as no packet has
a timestamp difference higher than the readahead amount relative to the
last packet returned to the decoder, the demuxer keeps reading.
(This value tends to be fuzzy, because many file formats don't store linear
timestamps.)
``--demuxer-readahead-packets=<packets>``
If ``--demuxer-thread`` is enabled, this controls how much the demuxer
should buffer ahead. As long as the number of packets in the packet queue
doesn't exceed ``--demuxer-readahead-packets``, and the total number of
bytes doesn't exceed ``--demuxer-readahead-bytes``, the thread keeps
reading ahead.
should buffer ahead. If the number of packets in the packet queue exceeds
``--demuxer-readahead-packets``, or the total number of bytes exceeds
``--demuxer-readahead-bytes``, the thread stops reading ahead.
Note that if you set these options near the maximum, you might get a
packet queue overflow warning.
See ``--list-options`` for defaults and value range.
``--demuxer-readahead-bytes=<bytes>``
``--demuxer-readahead-bytes=N``
See ``--demuxer-readahead-packets``.
@@ -2182,8 +2008,8 @@ Input
Number of key presses to generate per second on autorepeat.
``--input-conf=<filename>``
Specify input configuration file other than the default location in the mpv
configuration directory (usually ``~/.config/mpv/input.conf``).
Specify input configuration file other than the default
``~/.config/mpv/input.conf``.
``--no-input-default-bindings``
Disable mpv default (built-in) key bindings.
@@ -2213,11 +2039,7 @@ Input
like any other binding). See `INPUT.CONF`_.
``--input-file=<filename>``
Read commands from the given file. Mostly useful with a FIFO. Since
mpv 0.7.0 also understands JSON commands (see `JSON IPC`_), but you can't
get replies or events. Use ``--input-unix-socket`` for something
bi-directional. On MS Windows, JSON commands are not available.
Read commands from the given file. Mostly useful with a FIFO.
See also ``--slave-broken``.
.. note::
@@ -2234,16 +2056,8 @@ Input
or intend to read from stdin later on via the loadfile or loadlist slave
commands.
``--input-unix-socket=<filename>``
Enable the IPC support and create the listening socket at the given path.
See `JSON IPC`_ for details.
Not available on MS Windows.
``--input-appleremote=<yes|no>``
(OS X only)
Enable/disable Apple Remote support. Enabled by default (except for libmpv).
``--input-appleremote``, ``--no-input-appleremote``
Enable/disable AppleIR remote support. Enabled by default.
``--input-cursor``, ``--no-input-cursor``
Permit mpv to receive pointer events reported by the video output
@@ -2263,41 +2077,14 @@ Input
(LIRC only)
Specifies a configuration file for LIRC (default: ``~/.lircrc``).
``--input-media-keys=<yes|no>``
(OS X only)
Enable/disable media keys support. Enabled by default (except for libmpv).
``--input-media-keys``, ``--no-input-media-keys``
OS X only: Enabled by default. Enables/disable media keys support.
``--input-right-alt-gr``, ``--no-input-right-alt-gr``
(Cocoa and Windows only)
Use the right Alt key as Alt Gr to produce special characters. If disabled,
count the right Alt as an Alt modifier key. Enabled by default.
``--input-vo-keyboard=<yes|no>``
Disable all keyboard input on for VOs which can't participate in proper
keyboard input dispatching. This currently affects X11. Generally useful for
embedding only.
On X11, a sub-window with input enabled grabs all keyboard input as long
as it is 1. a child of a focused window, and 2. the mouse is inside of
the sub-window. The can steal away all keyboard input from the
application embedding the mpv window, and on the other hand, the mpv
window will receive no input if the mouse is outside of the mpv window,
even though mpv has focus. Modern toolkits work around this weird X11
behavior, but naively embedding foreign windows breaks it.
The only way to handle this reasonably is using the XEmbed protocol, which
was designed to solve these problems. But Qt has questionable support, and
mpv doesn't implement it yet.
As a workaround, this option is disabled by default in libmpv. (Note that
``input-default-bindings`` is disabled by default in libmpv as well.)
(This option was renamed from ``--input-x11-keyboard``.)
``--input-app-events=<yes|no>``
(OS X only)
Enable/disable application wide keyboard events so that keyboard shortcuts
can be processed without a window. Enabled by default (except for libmpv).
OSD
---
@@ -2337,27 +2124,7 @@ OSD
height: if the window height is larger or smaller than 720, the actual size
of the text increases or decreases as well.
Default: 55.
``--osd-msg1=<string>``
Show this string as message on OSD with OSD level 1 (visible by default).
The message will be visible by default, and as long no other message
covers it, and the OSD level isn't changed (see ``--osd-level``).
Expands properties; see `Property Expansion`_.
``--osd-msg2=<string>``
Similar as ``--osd-msg1``, but for OSD level 2. If this is an empty string
(default), then the playback time is shown.
``--osd-msg3=<string>``
Similar as ``--osd-msg1``, but for OSD level 3. If this is an empty string
(default), then the playback time, duration, and some more information is
shown.
This is also used for the ``show_progress`` command (by default mapped to
``P``), or in some non-default cases when seeking.
``--osd-status-msg`` is a legacy equivalent (but with a minor difference).
Default: 45.
``--osd-status-msg=<string>``
Show a custom string during playback instead of the standard status text.
@@ -2366,17 +2133,6 @@ OSD
non-default cases when seeking. Expands properties. See
`Property Expansion`_.
This option has been replaced with ``--osd-msg3``. The only difference is
that this option implicitly includes ``${osd-sym-cc}``. This option is
ignored if ``--osd-msg3`` is not empty.
``--osd-playing-msg=<string>``
Show a message on OSD when playback starts. The string is expanded for
properties, e.g. ``--osd-playing-msg='file: ${filename}'`` will show the
message ``file:`` followed by a space and the currently played filename.
See `Property Expansion`_.
``--osd-bar-align-x=<-1-1>``
Position of the OSD bar. -1 is far left, 0 is centered, 1 is far right.
Fractional values (like 0.5) are allowed.
@@ -2411,7 +2167,7 @@ OSD
Size of the OSD/sub font border in scaled pixels (see ``--osd-font-size``
for details). A value of 0 disables borders.
Default: 3.
Default: 2.5.
``--osd-color=<color>``, ``--sub-text-color=<color>``
Specify the color used for OSD/unstyled text subtitles.
@@ -2469,12 +2225,12 @@ OSD
for unstyled text subtitles. If you just want to raise the vertical
subtitle position, use ``--sub-pos``.
Default: 22.
Default: 10.
``--osd-scale=<factor>``
OSD font size multiplier, multiplied with ``--osd-font-size`` value.
``--osd-scale-by-window=<yes|no>``
``--osd-scale-by-window=yes|no``
Whether to scale the OSD with the window size (default: yes). If this is
disabled, ``--osd-font-size`` and other OSD options that use scaled pixels
are always in actual pixels. The effect is that changing the window size
@@ -2496,16 +2252,6 @@ OSD
Default: 0.
``--use-text-osd=<yes|no>``
Disable text OSD rendering completely. (This includes the complete OSC as
well.) This is mostly useful for avoiding loading fontconfig in situations
where fontconfig does not behave well, and OSD is unused - this could for
example allow GUI programs using libmpv to workaround fontconfig issues.
Note that selecting subtitles of any kind still initializes fontconfig.
Default: ``no``.
Screenshot
----------
@@ -2951,6 +2697,20 @@ Cache
will not automatically enable the cache e.g. when playing from a network
stream. Note that using ``--cache`` will always override this option.
``--cache-pause-below=<kBytes|no>``
If the cache size goes below the specified value (in KB), pause and wait
until the size set by ``--cache-pause-restart`` is reached, then resume
playback (default: 50). If ``no`` is specified, this behavior is disabled.
When the player is paused this way, the status line shows ``Buffering``
instead of ``Paused``, and the OSD uses a clock symbol instead of the
normal paused symbol.
``--cache-pause-restart=<kBytes>``
If the cache is paused due to the ``--cache-pause-below`` functionality,
then the player unpauses as soon as the cache has this much data (in KB).
(Default: 100)
``--cache-initial=<kBytes>``
Playback will start when the cache has been filled up with this many
kilobytes of data (default: 0).
@@ -2967,40 +2727,22 @@ Cache
on the situation, either of these might be slower than the other method.
This option allows control over this.
``--cache-file=<TMP|path>``
Create a cache file on the filesystem.
``--cache-file=<path>``
Create a cache file on the filesystem with the given name. The file is
always overwritten. When the general cache is enabled, this file cache
will be used to store whatever is read from the source stream.
There are two ways of using this:
This will always overwrite the cache file, and you can't use an existing
cache file to resume playback of a stream. (Technically, mpv wouldn't
even know which blocks in the file are valid and which not.)
1. Passing a path (a filename). The file will always be overwritten. When
the general cache is enabled, this file cache will be used to store
whatever is read from the source stream.
The resulting file will not necessarily contain all data of the source
stream. For example, if you seek, the parts that were skipped over are
never read and consequently are not written to the cache. The skipped over
parts are filled with zeros. This means that the cache file doesn't
necessarily correspond to a full download of the source stream.
This will always overwrite the cache file, and you can't use an existing
cache file to resume playback of a stream. (Technically, mpv wouldn't
even know which blocks in the file are valid and which not.)
The resulting file will not necessarily contain all data of the source
stream. For example, if you seek, the parts that were skipped over are
never read and consequently are not written to the cache. The skipped over
parts are filled with zeros. This means that the cache file doesn't
necessarily correspond to a full download of the source stream.
Both of these issues could be improved if there is any user interest.
.. warning:: Causes random corruption when used with ordered chapters or
with ``--audio-file``.
2. Passing the string ``TMP``. This will not be interpreted as filename.
Instead, an invisible temporary file is created. It depends on your
C library where this file is created (usually ``/tmp/``), and whether
filename is visible (the ``tmpfile()`` function is used). On some
systems, automatic deletion of the cache file might not be guaranteed.
If you want to use a file cache, this mode is recommended, because it
doesn't break ordered chapters or ``--audio-file``. These modes open
multiple cache streams, and using the same file for them obviously
clashes.
Both of these issues could be improved if there is any user interest.
Also see ``--cache-file-size``.
@@ -3008,24 +2750,11 @@ Cache
Maximum size of the file created with ``--cache-file``. For read accesses
above this size, the cache is simply not used.
Keep in mind that some use-cases, like playing ordered chapters with cache
enabled, will actually create multiple cache files, each of which will
use up to this much disk space.
(Default: 1048576, 1 GB.)
``--no-cache``
Turn off input stream caching. See ``--cache``.
``--cache-secs=<seconds>``
How many seconds of audio/video to prefetch if the cache is active. This
overrides the ``--demuxer-readahead-secs`` option if and only if the cache
is enabled. (Default: 2.)
``--cache-pause``, ``--no-cache-pause``
Whether the player should automatically pause when the cache runs low,
and unpause once more data is available ("buffering").
Network
-------
@@ -3034,9 +2763,11 @@ Network
Use ``<string>`` as user agent for HTTP streaming.
``--cookies``, ``--no-cookies``
(network only)
Support cookies when making HTTP requests. Disabled by default.
``--cookies-file=<filename>``
(network only)
Read HTTP cookies from <filename>. The file is assumed to be in Netscape
format.
@@ -3076,17 +2807,6 @@ Network
network transport when playing ``rtsp://...`` URLs. The value ``lavf``
leaves the decision to libavformat.
``--hls-bitrate=<no|min|max>``
If HLS streams are played, this option controls what streams are selected
by default. The option allows the following parameters:
:no: Don't do anything special. Typically, this will simply pick the
first audio/video streams it can find. (Default.)
:min: Pick the streams with the lowest bitrate.
:max: Same, but highest bitrate.
The bitrate as used is sent by the server, and there's no guarantee it's
actually meaningful.
DVB
---
@@ -3095,10 +2815,9 @@ DVB
Specifies using card number 1-4 (default: 1).
``--dvbin-file=<filename>``
Instructs mpv to read the channels list from ``<filename>``. The default is
in the mpv configuration directory (usually ``~/.config/mpv``) with the
filename ``channels.conf.{sat,ter,cbl,atsc}`` (based on your card type) or
``channels.conf`` as a last resort.
Instructs mpv to read the channels list from ``<filename>``. Default is
``~/.mpv/channels.conf.{sat,ter,cbl,atsc}`` (based on your card type) or
``~/.mpv/channels.conf`` as a last resort.
``--dvbin-timeout=<1-30>``
Maximum number of seconds to wait when trying to tune a frequency before
@@ -3222,11 +2941,6 @@ Miscellaneous
You can also try to use ``--no-correct-pts`` for files with completely
broken timestamps.
``--media-title=<string>``
Force the contents of the ``media-title`` property to this value. Useful
for scripts which want to set a title, without overriding the user's
setting in ``--title``.
``--slave-broken``
Switches on the old slave mode. This is for testing only, and incompatible
to the removed ``--slave`` switch.
@@ -3244,3 +2958,10 @@ Miscellaneous
if there is enough interest).
This affects most third-party GUI frontends.
``--softsleep``
Time frames by repeatedly checking the current time instead of asking
the kernel to wake up mpv at the correct time. Useful if your kernel
timing is imprecise and you cannot use the RTC either. Comes at the
price of higher CPU consumption.
+1 -5
View File
@@ -161,7 +161,7 @@ To avoid collisions with other scripts, all options need to be prefixed with
Example::
--lua-opts=osc-optionA=value1,osc-optionB=value2
--lua-opts=osc-optionA=value1:osc-optionB=value2
Configurable Options
@@ -229,10 +229,6 @@ Configurable Options
| Default: yes
| Display a tooltip over the seekbar indicating time at mouse position.
``seekbarstyle``
| Default: slider
| Sets the style of the seekbar, slider (diamond marker) or bar (fill)
Script Commands
~~~~~~~~~~~~~~~
+8 -39
View File
@@ -231,23 +231,12 @@ Available filters are:
``<fmt>``
Format name, e.g. rgb15, bgr24, 420p, etc. (default: 420p).
``pp=[filter string]``
``pp[=[filter1[:option1[:option2...]]/[-]filter2...]]``
Enables the specified chain of post-processing 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]]``
@@ -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
+19 -63
View File
@@ -233,29 +233,18 @@ Available video output drivers are:
Always force textures to power of 2, even if the device reports
non-power-of-2 texture sizes as supported.
``texture-memory=<mode>``
``texture-memory=N``
Only affects operation with shaders/texturing enabled, and (E)OSD.
Possible values:
Values for N:
``default`` (default)
Use ``D3DPOOL_DEFAULT``, with a ``D3DPOOL_SYSTEMMEM`` texture for
locking. If the driver supports ``D3DDEVCAPS_TEXTURESYSTEMMEMORY``,
``D3DPOOL_SYSTEMMEM`` is used directly.
``default-pool``
Use ``D3DPOOL_DEFAULT``. (Like ``default``, but never use a
shadow-texture.)
``default-pool-shadow``
Use ``D3DPOOL_DEFAULT``, with a ``D3DPOOL_SYSTEMMEM`` texture for
locking. (Like ``default``, but always force the shadow-texture.)
``managed``
Use ``D3DPOOL_MANAGED``.
``scratch``
Use ``D3DPOOL_SCRATCH``, with a ``D3DPOOL_SYSTEMMEM`` texture for
locking.
0
default, will often use an additional shadow texture + copy
1
use ``D3DPOOL_MANAGED``
2
use ``D3DPOOL_DEFAULT``
3
use ``D3DPOOL_SYSTEMMEM``, but without shadow texture
``swap-discard``
Use ``D3DSWAPEFFECT_DISCARD``, which might be faster.
@@ -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.
@@ -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
@@ -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.
+17 -35
View File
@@ -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
@@ -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".
@@ -116,7 +103,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,7 +111,7 @@ 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
This translates keyboard input comming from libvo and other sources (such
as remote control devices like Apple IR or client API 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
@@ -135,7 +122,7 @@ input/input.c:
of input commands somewhere else.
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.
@@ -234,14 +218,12 @@ sub/:
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
+8 -10
View File
@@ -3,8 +3,6 @@ mpv
[![Build Status](https://api.travis-ci.org/mpv-player/mpv.png)](https://travis-ci.org/mpv-player/mpv)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/1232/badge.svg)](https://scan.coverity.com/projects/1232)
Overview
--------
@@ -50,7 +48,7 @@ Essential dependencies (incomplete list):
- FFmpeg libraries (libavutil libavcodec libavformat libswscale libavresample)
At least FFmpeg 2.1.4 or Libav 10 is required.
- libjpeg (for screenshots)
- Lua (strictly speaking optional, but required for the OSC pseudo-GUI)
- libquvi if you want to play Youtube videos directly
Libass dependencies:
@@ -119,17 +117,17 @@ make code review easier for both parties later on.
Contact
-------
Most activity happens on the IRC channel and the github issue tracker. The
mailing lists are mostly unused.
These forms of contact are meant to ask questions about mpv usage, give
feedback on mpv and discuss it's development.
- **Github issue tracker**: [issue tracker][issue-tracker] (report bugs here)
- **User IRC Channel**: `#mpv-player` on `irc.freenode.net`
- **Developer IRC Channel**: `#mpv-player-dev` on `irc.freenode.net`
If possible, please avoid posting bugs here and use the [issue tracker][issue-tracker]
instead.
- **Users IRC Channel**: `#mpv-player` on `irc.freenode.net`
- **Users Mailing List**: `mpv-users@googlegroups.com` ([Archive / Subscribe][mpv-users]).
- **Devel Mailing List**: `mpv-devel@googlegroups.com` ([Archive / Subscribe][mpv-devel])
To contact the `mpv` team in private write to `mpv-team@googlegroups.com`. Use
only if discretion is required.
To contact the `mpv` team in private write to `mpv-team@googlegroups.com`.
[changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/man/changes.rst
[mpv-build]: https://github.com/mpv-player/mpv-build
+137 -76
View File
@@ -1,97 +1,158 @@
Release 0.7.1
Release 0.5.3
=============
Changes
-------
- Don't show the volume neutral marker on the OSD if softvol is disabled.
- Don't select a subtitle track when executing the sub_add input command in
"auto" mode.
Bug fixes
---------
- Fix busy loop when seeking while paused (this fixes a problem with pulseaudio
that caused mpv and the pulseaudio daemon to use 100% CPU).
- Fix Lua function utils.subprocess() in Windows versions older than Vista.
- Avoid creating a window bigger than the screen on Windows.
- Don't ignore the last line in m3u playlists.
- Don't crash if a codec could not be opened.
- Dynamically allocate audio channel map entries (this should fix a crash in
the alsa and coreaudio AOs with audio devices that support more than 20
channel maps).
- Ignore the "srgb" option in the opengl VO if hardware decoding is enabled.
- Linearize non-RGB sRGB files correctly (eg. JPEG).
- Fix opening reference URLs (.file/id=) on OS X.
- Fix build failure introduced by the previous release.
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
complete change log can be viewed by running ``git log 41f2ad3..41f2ad3`` in
the git repository.
Release 0.7.0
Release 0.5.2
=============
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).
- Inherit event flags from parent X11 window when using --wid.
- Properly resume playlists loaded at runtime (this makes mpv try to resume a
playlist when loaded like "mpv playlist.m3u").
- Don't honor $XDG_CONFIG_DIRS anymore for global paths (this fixes a regression
on systems that set the variable by default to somewhere else then /etc,
making mpv skip previously created config files).
- Allow up to 256 MB of extradata to make broken files work.
Bug fixes
---------
- Restore old speed change behavior (this fixes a temporary lack of sound after
restoring normal speed e.g. when pressing Backspace).
- Fix quit handling when seeking past EOF while paused.
- Prevent burning CPU when seeking while paused.
- Don't add the playlist base path twice when using --playlist.
- Fix a crash when the initialization of the EGL context fails on Wayland.
- Make EOF handling properly event-based (this fixes a bug that made mpv hang
when changing from one file to the next with --gapless-audio=no).
- Assume icy title data is terminated with ';' (this fixes handling of streams
with metadata containing apostrophes).
This listing is not complete. There are many more bug fixes and changes. The
complete change log can be viewed by running ``git log 0f1d6c0..e3d3a47`` in
the git repository.
Release 0.5.1
=============
Changes
-------
- Add audio/flac to the MIME type list in mpv.desktop.
- The --slave-broken option has been reintroduced and is equivalent to using
--input-file=/dev/stdin and --input-terminal=no.
Bug fixes
---------
- Fix screenshots with anamorphic videos in the vdpau vo.
- Fix a crash happening when mpv fails to create a vdpau mixer in the vdpau vo.
- Don't quit when the --loop-file option is used.
- Always load the cursor theme in the wayland vo (this fixes a crash caused by
moving the mouse cursor over the video).
- Properly return non-zero from the zsh completion script when no matches are
found (this fixes the use of user-defined zsh completion matchers).
- Fix the description of the opengl-hq vo default options in the manpage.
- Remove duplicated content from the manpage that was accidentally added in the
manpage reorganization from the previous release.
- Avoid clobbering playback position on video end (when both audio and video
streams reached the end, the playback poisition was briefly set at 0).
- Avoid overwriting user input in the coreaudio_device ao.
This listing is not complete. There are many more bug fixes and changes. The
complete change log can be viewed by running ``git log 6729e39..fdd648b`` in
the git repository.
Release 0.5.0
=============
Changes
-------
- Support setting the start time relative to the start PTS with --start.
- Use /dev/sr0 as default cdrom/dvd device on Linux.
- Change "cache" property representation to float.
- Split the ao_coreaudio audio output into ao_coreaudio (which uses the
AudioUnits and the CoreAudio sound server) and ao_coreaudio_device (which uses
direct device access).
- Complete supported URL schemes in the zsh completion script.
- Enable compiler optimizations (-O2) during build.
- Allow precise seeking with percent-based seeks too.
- Do precise seeks when clicking on the OSC seekbar.
- Remove support for older VDA hwaccel versions on Mac OS X (requires recent
versions of ffmpeg/libav).
- Remove the last remains of slave mode (--slave-broken option). Support for
reading input commands from stdin can be emulated using: --input-terminal=no
--input-file=/dev/stdin.
- Some key binding were remapped or removed:
+ "LEFT" and "RIGHT" have been remapped to seek 5 seconds instead of 10.
+ "c" for changing the YUV color space was unmapped.
+ OSD level cycling has been remapped from "o" to "O".
+ "o" is now mapped to the show_progress command (like "P").
+ "PGUP" and "PGDWN" have been remapped to chapter-based seeks (the old
bindings "@" and "!" are still available). The old behvaiour has been
remapped to "Shift+PGUP" and "Shift+PGDWM".
+ Increasing/decreasing audio delay have been remapped from "+" and "-" to
"Ctrl++" and "Ctrl+-".
+ "(" and ")" for adjusting audio balance have been unmapped.
+ "F" for toggling the "sub-forced-only" option has been unmapped.
+ "TAB" for cycling TS programs has been unmapped.
+ "A" for cycling DVD angle has been unmapped.
+ "U" for stopping playback has been unmapped.
+ "l" and "L" for cycling quvi format have been unmapped.
- Make it possible to build the client API (libmpv) on Mac OS X.
- Do necessary checks for nested Wayland compositors which only have limited
interfaces.
- The options in the manpage have been re-organized into sections and ordered by
usage to make it easier to navigate.
- The "hard" framedrop mode has been removed (this behaviour is still available
via the --vd-lavc-framedrop option).
- Show the basename of a subtitle file only (without the directory part).
Bug fixes
---------
- Use a default color space for files with no video (this fixes color managed
OSD messages when no video channel is present).
- Fix key equivalent dispatching for Cocoa on Mac OS X.
- Disable non-interleaved audio formats by default with ao_alsa, since some
ALSA plugins don't cope well with them (non-interleaved formats can still be
enabled using the "non-interleaved" ao_alsa option).
- Fix deadlock in the client API when calling mpv_terminate_destroy() before
mpv_initialize().
- Fix flushing of filter chain (this fixes a bug where the last frames would get
dropped on EOF, if buffered by filters).
- Actually use the channels.conf configuration file for DVB.
- Restore the --dvbin-file option.
- Fix off by one error in the command parser which caused a segfault when using
the mp.commandv() Lua function with 10 or more arguments.
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).
- Search for config files in $XDG_CONFIG_HOME and $XDG_CONFIG_DIRS in addition
to ~/.mpv. The config file has also been renamed to mpv.conf (although the
old file still works and will be loaded even if the new one exists).
- Add "playback-time" property that returns the current playback time always
starting at 0. This is now used to show the playback time in the OSD and the
status line, fixing the shown playback time for transport streams that start
at a completely random timestamp.
- Add cache-related "cache-free", "cache-used" and "cache-idle" properties.
- Add option to list supported protocols (--list-protocols).
- Allow setting libavformat stream options with the --stream-lavf-o option.
- Support running the demuxer in a separate thread and prefetch packets (this
can be enabled using the --demuxer-thread option).
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 cb250d4..52f2f67`` in
the git repository.
+2 -2
View File
@@ -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" ;
+53 -50
View File
@@ -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
@@ -26,62 +34,57 @@ testfun()
--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
@@ -91,7 +94,7 @@ case "$verdict" in
[ -n "$ILDETECT_RUN_INTERLACED_ONLY" ] || \
$ILDETECT_MPV "$@"
r=$?
[ $r -eq 0 ] || exit $((r | 16))
[ $r -eq 0 ] || exit $(($r | 16))
exit 0
;;
interlaced-tff)
@@ -101,14 +104,14 @@ case "$verdict" in
[ -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
@@ -120,14 +123,14 @@ case "$verdict" in
[ -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
@@ -139,14 +142,14 @@ case "$verdict" in
[ -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
-6
View File
@@ -232,7 +232,6 @@ sub define_matroska {
elem('Block', 'a1', 'binary'),
elem('BlockDuration', '9b', 'uint'),
elem('ReferenceBlock*', 'fb', 'sint'),
elem('DiscardPadding', '75A2', 'sint'),
}),
elem('SimpleBlock*', 'a3', 'binary'),
}),
@@ -257,8 +256,6 @@ sub define_matroska {
elem('CodecPrivate', '63a2', 'binary'),
elem('CodecName', '258688', 'str'),
elem('CodecDecodeAll', 'aa', 'uint'),
elem('CodecDelay', '56AA', 'uint'),
elem('SeekPreRoll', '56BB', 'uint'),
elem('Video', 'e0', {
elem('FlagInterlaced', '9a', 'uint'),
elem('PixelWidth', 'b0', 'uint'),
@@ -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
View File
@@ -8,5 +8,12 @@ adding the option
to mpv's command line.
Where appropriate, they may also be placed in ~/.config/mpv/lua from where
they will be automatically loaded when mpv starts.
Unless otherwise specified, they are also suitable for inclusion in
the `~/.mpv/lua` directory where they will be loaded every time mpv
starts, obviating the need to load them with the above `--lua=...`
argument. This is acceptable as they do only basic argument parsing
and key-binding registration, until those bound keys are actually
pressed. They should therefore not interfere with normal playback
(unless you have a conflicting user-defined key-binding, in which
case, you may want to modify either the `mp.add_key_binding()` calls
in the scripts, or your keybinding).
+18 -18
View File
@@ -42,11 +42,11 @@ function del_filter_if_present(label)
-- error if the filter doesn't exist
local vfs = mp.get_property_native("vf")
for i,vf in pairs(vfs) do
if vf["label"] == label then
table.remove(vfs, i)
mp.set_property_native("vf", vfs)
return true
end
if vf["label"] == label then
table.remove(vfs, i)
mp.set_property_native("vf", vfs)
return true
end
end
return false
end
@@ -65,10 +65,10 @@ function autocrop_start()
-- insert the cropdetect filter
ret=mp.command(
string.format(
'vf add @%s:lavfi=graph="cropdetect=limit=24:round=2:reset=0"',
cropdetect_label
)
string.format(
'vf add @%s:lavfi=graph="cropdetect=limit=24:round=2:reset=0"',
cropdetect_label
)
)
-- wait to gather data
timer=mp.add_timeout(detect_seconds, do_crop)
@@ -77,15 +77,15 @@ end
function do_crop()
-- get the metadata
local cropdetect_metadata = mp.get_property_native(
string.format("vf-metadata/%s", cropdetect_label)
string.format("vf-metadata/%s", cropdetect_label)
)
-- use it to crop if its valid
if cropdetect_metadata then
if cropdetect_metadata["lavfi.cropdetect.w"]
and cropdetect_metadata["lavfi.cropdetect.h"]
and cropdetect_metadata["lavfi.cropdetect.x"]
and cropdetect_metadata["lavfi.cropdetect.y"]
then
if cropdetect_metadata["lavfi.cropdetect.w"]
and cropdetect_metadata["lavfi.cropdetect.h"]
and cropdetect_metadata["lavfi.cropdetect.x"]
and cropdetect_metadata["lavfi.cropdetect.y"]
then
mp.command(string.format("vf add @%s:crop=%s:%s:%s:%s",
crop_label,
cropdetect_metadata["lavfi.cropdetect.w"],
@@ -94,9 +94,9 @@ function do_crop()
cropdetect_metadata["lavfi.cropdetect.y"]))
else
mp.msg.error(
"Got empty crop data. You might need to increase detect_seconds."
)
end
"Got empty crop data. You might need to increase detect_seconds."
)
end
else
mp.msg.error(
"No crop data. Was the cropdetect filter successfully inserted?"
-164
View File
@@ -1,164 +0,0 @@
-- This script uses the lavfi idet filter to automatically insert the
-- appropriate deinterlacing filter based on a short section of the
-- currently playing video.
--
-- It registers the key-binding ctrl+d, which when pressed, inserts
-- the filter vf=lavfi=idet. After 4 second, it examines the results
-- to determine whether the content progressive or interlaced and the
-- interlacing field dominance. It immediately sets the latter within
-- mpv.
--
-- If the content is judged not to be progressive, it may be either
-- interlaced or telecined. To determine which one, the video is
-- seeked back to the detection starting point with the "pullup"
-- filter inserted. After another four seconds, if idet thinks that
-- the pulled up viedo is "progressive", then pullup is the correct
-- filter and this script leaves it in places while removing the idet
-- filter. Otherwise, the content plain interlaced and mpv's deinterlace
-- property is set. This will usually insert the yadif deinterlacing filter.
--
-- The default detection time may be overridden by adding
--
-- --lua-opts=autodeint.detect_seconds=<number of seconds>
--
-- to mpv's arguments. This may be desirable to allow idet more
-- time to collect data.
--
-- To see counts of the various types of frames for each detection phase,
-- the verbosity can be increased with
--
-- --msg-level autodeint=v
--
-- This script requires a recent version of ffmpeg for which the idet
-- filter adds the required metadata.
require "mp.msg"
script_name = mp.get_script_name()
detect_label = string.format("%s-detect", script_name)
pullup_label = string.format("%s", script_name)
-- number of seconds to gather cropdetect data
detect_seconds = tonumber(mp.get_opt(string.format("%s.detect_seconds", script_name)))
if not detect_seconds then
detect_seconds = 4
end
function del_filter_if_present(label)
-- necessary because mp.command('vf del @label:filter') raises an
-- error if the filter doesn't exist
local vfs = mp.get_property_native("vf")
for i,vf in pairs(vfs) do
if vf["label"] == label then
table.remove(vfs, i)
mp.set_property_native("vf", vfs)
return true
end
end
return false
end
function start_detect()
-- exit if detection is already in progress
if timer then
mp.msg.warn("already detecting!")
return
end
mp.set_property("deinterlace","no")
del_filter_if_present(pullup_label)
percent_pos = mp.get_property("percent-pos")
-- insert the detection filter
local cmd = string.format('vf add @%s:lavfi=graph="idet"', detect_label)
if not mp.command(cmd) then
mp.msg.error("failed to insert detection filter")
return
end
-- wait to gather data
timer = mp.add_timeout(detect_seconds, judge_field_dominance)
end
function stop_detect()
del_filter_if_present(detect_label)
timer = nil
mp.set_property("playback-pos", percent_pos)
end
progressive, interlaced_tff, interlaced_bff, interlaced = 0, 1, 2, 3, 4
function judge()
-- get the metadata
local result = mp.get_property_native(string.format("vf-metadata/%s", detect_label))
num_tff = tonumber(result["lavfi.idet.multiple.tff"])
num_bff = tonumber(result["lavfi.idet.multiple.bff"])
num_progressive = tonumber(result["lavfi.idet.multiple.progressive"])
num_undetermined = tonumber(result["lavfi.idet.multiple.undetermined"])
num_interlaced = num_tff + num_bff
num_determined = num_interlaced + num_progressive
mp.msg.verbose("progressive = "..num_progressive)
mp.msg.verbose("interlaced-tff = "..num_tff)
mp.msg.verbose("interlaced-bff = "..num_bff)
mp.msg.verbose("undetermined = "..num_undetermined)
if num_determined < num_undetermined then
mp.msg.warn("majority undetermined frames")
end
if num_progressive > 20*num_interlaced then
return progressive
elseif num_tff > 10*num_bff then
return interlaced_tff
elseif num_bff > 10*num_tff then
return interlaced_bff
else
return interlaced
end
end
function judge_field_dominance()
verdict = judge()
if verdict == progressive then
stop_detect()
mp.msg.info("progressive: doing nothing")
return
elseif verdict == interlaced_tff then
mp.set_property("field-dominance", "top")
elseif verdict == interlaced_bff then
mp.set_property("field-dominance", "bottom")
elseif verdict == interlaced then
mp.set_property("field-dominance", "auto")
end
local cmd = string.format("vf pre @%s:pullup", pullup_label)
if not mp.command(cmd) then
mp.msg.error("failed to insert pullup filter")
stop_detect()
return
end
-- redo the detection with the pullup filter to see if it's telecined
mp.set_property("percent-pos", percent_pos)
timer = mp.add_timeout(detect_seconds, judge_ivtc)
end
function judge_ivtc()
verdict = judge()
if verdict == progressive then
mp.msg.info(string.format("telecinied with %s field dominance: using pullup", mp.get_property("field-dominance")))
stop_detect()
else
mp.msg.info(string.format("interlaced with %s field dominance: setting deinterlace property", mp.get_property("field-dominance")))
del_filter_if_present(pullup_label)
mp.set_property("deinterlace","yes")
stop_detect()
end
return
end
mp.add_key_binding("ctrl+d", script_name, start_detect)
+7 -11
View File
@@ -26,9 +26,6 @@ function find_and_add_entries()
end
local files = mputils.readdir(dir, "files")
table.sort(files)
if dir == "." then
dir = ""
end
local pl = mp.get_property_native("playlist", {})
local pl_current = mp.get_property_number("playlist-pos", 0) + 1
-- Find the current pl entry (dir+"/"+filename) in the sorted dir list
@@ -43,28 +40,27 @@ function find_and_add_entries()
return
end
local append = {[-1] = {}, [1] = {}}
for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
for dir = -1, 1, 2 do -- 2 iterations, with dir = -1 and +1
for i = 1, MAXENTRIES do
local file = files[current + i * direction]
local pl_e = pl[pl_current + i * direction]
local file = files[current + i * dir]
local pl_e = pl[pl_current + i * dir]
if file == nil or file[1] == "." then
break
end
local filepath = dir .. file
if pl_e then
-- If there's a playlist entry, and it's the same file, stop.
if pl_e.filename == filepath then
if pl_e.filename == file then
break
end
end
if direction == -1 then
if dir == -1 then
if pl_current == 1 then -- never add additional entries in the middle
mp.msg.info("Prepending " .. file)
table.insert(append[-1], 1, filepath)
table.insert(append[-1], 1, file)
end
else
mp.msg.info("Adding " .. file)
table.insert(append[1], filepath)
table.insert(append[1], file)
end
end
end
+3 -3
View File
@@ -20,9 +20,9 @@ pullup_label = string.format("%s-pullup", script_name)
function pullup_on()
for i,vf in pairs(mp.get_property_native('vf')) do
if vf['label'] == pullup_label then
return "yes"
end
if vf['label'] == pullup_label then
return "yes"
end
end
return "no"
end
+29 -29
View File
@@ -21,30 +21,30 @@ script_name = mp.get_script_name()
function print_state(params)
if params then
mp.osd_message(script_name..":\n"
.."method = "..params["method"].."\n"
.."target = "..params["target"])
mp.osd_message(script_name..":\n"
.."method = "..params["method"].."\n"
.."target = "..params["target"])
else
mp.osd_message(script_name..":\noff")
mp.osd_message(script_name..":\noff")
end
end
function get_index_of_drc(afs)
for i,af in pairs(afs) do
if af["label"] == script_name then
return i
end
if af["label"] == script_name then
return i
end
end
end
function append_drc(afs)
afs[#afs+1] = {
name = "drc",
label = script_name,
params = {
method = "1",
target = "0.25"
}
name = "drc",
label = script_name,
params = {
method = "1",
target = "0.25"
}
}
print_state(afs[#afs]["params"])
end
@@ -53,34 +53,34 @@ function modify_or_create_af(fun)
afs = mp.get_property_native("af")
i = get_index_of_drc(afs)
if not i then
append_drc(afs)
append_drc(afs)
else
fun(afs, i)
fun(afs, i)
end
mp.set_property_native("af", afs)
end
function drc_toggle_method_handler()
modify_or_create_af(
function (afs, i)
new_method=(afs[i]["params"]["method"]+1)%3
if new_method == 0 then
table.remove(afs, i)
print_state(nil)
else
afs[i]["params"]["method"] = tostring((afs[i]["params"]["method"])%2+1)
print_state(afs[i]["params"])
end
end
function (afs, i)
new_method=(afs[i]["params"]["method"]+1)%3
if new_method == 0 then
table.remove(afs, i)
print_state(nil)
else
afs[i]["params"]["method"] = tostring((afs[i]["params"]["method"])%2+1)
print_state(afs[i]["params"])
end
end
)
end
function drc_scale_target(factor)
modify_or_create_af(
function (afs)
afs[i]["params"]["target"] = tostring(afs[i]["params"]["target"]*factor)
print_state(afs[i]["params"])
end
function (afs)
afs[i]["params"]["target"] = tostring(afs[i]["params"]["target"]*factor)
print_state(afs[i]["params"])
end
)
end
+150 -127
View File
@@ -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
+18 -31
View File
@@ -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
View File
@@ -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)
+2 -1
View File
@@ -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;
+2 -1
View File
@@ -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);
+1
View File
@@ -11,6 +11,7 @@ esac
: ${MPV:=mpv}
: ${ILDETECT_MPV:=$MPV}
: ${ILDETECT_MPV:=$MPV}
: ${ILDETECT_MPVFLAGS:=--start=35% --length=35}
: ${ILDETECT_DRY_RUN:=}
: ${ILDETECT_QUIET:=}
+2 -1
View File
@@ -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]);
+2 -1
View File
@@ -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;
+2 -1
View File
@@ -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]);
+5 -4
View File
@@ -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"
-43
View File
@@ -37,36 +37,6 @@ 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
@@ -118,16 +88,6 @@ $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
;;
mfiles)
local expl
_tags files urls
@@ -181,9 +141,6 @@ sub parse_opts {
$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\]/);
}
push @list, $entry
+1 -1
View File
@@ -1 +1 @@
0.7.1
0.5.3
+35 -165
View File
@@ -20,13 +20,10 @@
#include <stdlib.h>
#include <assert.h>
#include <libavutil/buffer.h>
#include <libavutil/frame.h>
#include <libavutil/version.h>
#include <libavutil/mem.h>
#include "talloc.h"
#include "common/common.h"
#include "fmt-conversion.h"
#include "audio.h"
static void update_redundant_info(struct mp_audio *mpa)
@@ -91,12 +88,18 @@ bool mp_audio_config_valid(const struct mp_audio *mpa)
&& mpa->rate >= 1 && mpa->rate < 10000000;
}
char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa)
char *mp_audio_fmt_to_str(int srate, const struct mp_chmap *chmap, int format)
{
snprintf(buf, buf_sz, "%dHz %s %dch %s", mpa->rate,
mp_chmap_to_str(&mpa->channels), mpa->channels.num,
af_fmt_to_str(mpa->format));
return buf;
char *chstr = mp_chmap_to_str(chmap);
char *res = talloc_asprintf(NULL, "%dHz %s %dch %s", srate, chstr,
chmap->num, af_fmt_to_str(format));
talloc_free(chstr);
return res;
}
char *mp_audio_config_to_str(struct mp_audio *mpa)
{
return mp_audio_fmt_to_str(mpa->rate, &mpa->channels, mpa->format);
}
void mp_audio_force_interleaved_format(struct mp_audio *mpa)
@@ -113,27 +116,19 @@ int mp_audio_psize(struct mp_audio *mpa)
void mp_audio_set_null_data(struct mp_audio *mpa)
{
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
for (int n = 0; n < MP_NUM_CHANNELS; n++)
mpa->planes[n] = NULL;
mpa->allocated[n] = NULL;
}
mpa->samples = 0;
}
static int get_plane_size(const struct mp_audio *mpa, int samples)
{
if (samples < 0 || !mp_audio_config_valid(mpa))
return -1;
if (samples >= INT_MAX / mpa->sstride)
return -1;
return MPMAX(samples * mpa->sstride, 1);
}
static void mp_audio_destructor(void *ptr)
{
struct mp_audio *mpa = ptr;
for (int n = 0; n < MP_NUM_CHANNELS; n++)
av_buffer_unref(&mpa->allocated[n]);
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
// Note: don't free if not allocated by mp_audio_realloc
if (mpa->allocated[n])
av_free(mpa->planes[n]);
}
}
/* Reallocate the data stored in mpa->planes[n] so that enough samples are
@@ -152,19 +147,27 @@ static void mp_audio_destructor(void *ptr)
*/
void mp_audio_realloc(struct mp_audio *mpa, int samples)
{
int size = get_plane_size(mpa, samples);
if (size < 0)
abort(); // oom or invalid parameters
assert(samples >= 0);
if (samples >= INT_MAX / mpa->sstride)
abort(); // oom
int size = MPMAX(samples * mpa->sstride, 1);
for (int n = 0; n < mpa->num_planes; n++) {
if (!mpa->allocated[n] || size != mpa->allocated[n]->size) {
if (av_buffer_realloc(&mpa->allocated[n], size) < 0)
abort(); // OOM
mpa->planes[n] = mpa->allocated[n]->data;
if (size != mpa->allocated[n]) {
// Note: av_realloc() can't be used (see libavutil doxygen)
void *new = av_malloc(size);
if (!new)
abort();
if (mpa->allocated[n])
memcpy(new, mpa->planes[n], MPMIN(mpa->allocated[n], size));
av_free(mpa->planes[n]);
mpa->planes[n] = new;
mpa->allocated[n] = size;
}
}
for (int n = mpa->num_planes; n < MP_NUM_CHANNELS; n++) {
av_buffer_unref(&mpa->allocated[n]);
av_free(mpa->planes[n]);
mpa->planes[n] = NULL;
mpa->allocated[n] = 0;
}
talloc_set_destructor(mpa, mp_audio_destructor);
}
@@ -191,7 +194,7 @@ int mp_audio_get_allocated_size(struct mp_audio *mpa)
{
int size = 0;
for (int n = 0; n < mpa->num_planes; n++) {
int s = mpa->allocated[n] ? mpa->allocated[n]->size / mpa->sstride : 0;
int s = mpa->allocated[n] / mpa->sstride;
size = n == 0 ? s : MPMIN(size, s);
}
return size;
@@ -237,136 +240,3 @@ void mp_audio_skip_samples(struct mp_audio *data, int samples)
data->samples -= samples;
}
// Make sure the frame owns the audio data, and if not, copy the data.
// Return negative value on failure (which means it can't be made writeable).
// Non-refcounted frames are always considered writeable.
int mp_audio_make_writeable(struct mp_audio *data)
{
bool ok = true;
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
if (data->allocated[n])
ok &= av_buffer_is_writable(data->allocated[n]);
}
if (!ok) {
struct mp_audio *new = talloc(NULL, struct mp_audio);
*new = *data;
mp_audio_set_null_data(new); // use format only
mp_audio_realloc(new, data->samples);
new->samples = data->samples;
mp_audio_copy(new, 0, data, 0, data->samples);
// "Transfer" the reference.
mp_audio_destructor(data);
*data = *new;
talloc_set_destructor(new, NULL);
talloc_free(new);
}
return 0;
}
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe)
{
AVFrame *tmp = NULL;
struct mp_audio *new = talloc_zero(NULL, struct mp_audio);
talloc_set_destructor(new, mp_audio_destructor);
mp_audio_set_format(new, af_from_avformat(avframe->format));
struct mp_chmap lavc_chmap;
mp_chmap_from_lavc(&lavc_chmap, avframe->channel_layout);
#if LIBAVUTIL_VERSION_MICRO >= 100
// FFmpeg being special again
if (lavc_chmap.num != avframe->channels)
mp_chmap_from_channels(&lavc_chmap, avframe->channels);
#endif
new->rate = avframe->sample_rate;
mp_audio_set_channels(new, &lavc_chmap);
// Force refcounted frame.
if (!avframe->buf[0]) {
tmp = av_frame_alloc();
if (!tmp)
goto fail;
if (av_frame_ref(tmp, avframe) < 0)
goto fail;
avframe = tmp;
}
// If we can't handle the format (e.g. too many channels), bail out.
if (!mp_audio_config_valid(new) || avframe->nb_extended_buf)
goto fail;
for (int n = 0; n < AV_NUM_DATA_POINTERS; n++) {
if (!avframe->buf[n])
break;
if (n >= MP_NUM_CHANNELS)
goto fail;
new->allocated[n] = av_buffer_ref(avframe->buf[n]);
if (!new->allocated[n])
goto fail;
}
for (int n = 0; n < new->num_planes; n++)
new->planes[n] = avframe->data[n];
new->samples = avframe->nb_samples;
return new;
fail:
talloc_free(new);
av_frame_free(&tmp);
return NULL;
}
struct mp_audio_pool {
AVBufferPool *avpool;
int element_size;
};
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent)
{
return talloc_zero(ta_parent, struct mp_audio_pool);
}
static void mp_audio_pool_destructor(void *p)
{
struct mp_audio_pool *pool = p;
av_buffer_pool_uninit(&pool->avpool);
}
// Allocate data using the given format and number of samples.
// Returns NULL on error.
struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
const struct mp_audio *fmt, int samples)
{
int size = get_plane_size(fmt, samples);
if (size < 0)
return NULL;
if (!pool->avpool || size > pool->element_size) {
size_t alloc = ta_calc_prealloc_elems(size);
if (alloc >= INT_MAX)
return NULL;
av_buffer_pool_uninit(&pool->avpool);
pool->avpool = av_buffer_pool_init(alloc, NULL);
if (!pool->avpool)
return NULL;
talloc_set_destructor(pool, mp_audio_pool_destructor);
}
struct mp_audio *new = talloc_ptrtype(NULL, new);
talloc_set_destructor(new, mp_audio_destructor);
*new = *fmt;
mp_audio_set_null_data(new);
new->samples = samples;
for (int n = 0; n < new->num_planes; n++) {
new->allocated[n] = av_buffer_pool_get(pool->avpool);
if (!new->allocated[n]) {
talloc_free(new);
return NULL;
}
new->planes[n] = new->allocated[n]->data;
}
return new;
}
+3 -12
View File
@@ -38,7 +38,7 @@ struct mp_audio {
int bps; // size of sub-samples (af_fmt2bps(format))
// private
struct AVBufferRef *allocated[MP_NUM_CHANNELS];
int allocated[MP_NUM_CHANNELS]; // use mp_audio_get_allocated_size()
};
void mp_audio_set_format(struct mp_audio *mpa, int format);
@@ -49,8 +49,8 @@ void mp_audio_copy_config(struct mp_audio *dst, const struct mp_audio *src);
bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b);
bool mp_audio_config_valid(const struct mp_audio *mpa);
char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa);
#define mp_audio_config_to_str(m) mp_audio_config_to_str_buf((char[64]){0}, 64, (m))
char *mp_audio_fmt_to_str(int srate, const struct mp_chmap *chmap, int format);
char *mp_audio_config_to_str(struct mp_audio *mpa);
void mp_audio_force_interleaved_format(struct mp_audio *mpa);
@@ -68,13 +68,4 @@ void mp_audio_copy(struct mp_audio *dst, int dst_offset,
struct mp_audio *src, int src_offset, int length);
void mp_audio_skip_samples(struct mp_audio *data, int samples);
int mp_audio_make_writeable(struct mp_audio *data);
struct AVFrame;
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe);
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent);
struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
const struct mp_audio *fmt, int samples);
#endif
+12 -15
View File
@@ -18,7 +18,6 @@
#include <stdlib.h>
#include <assert.h>
#include "common/common.h"
#include "common/msg.h"
#include "chmap.h"
@@ -87,7 +86,6 @@ static const char *const std_layout_names[][2] = {
{"7.1(wide-side)", "fl-fr-fc-lfe-flc-frc-sl-sr"},
{"octagonal", "fl-fr-fc-bl-br-bc-sl-sr"},
{"downmix", "dl-dr"},
{"auto", ""}, // not in lavc
{0}
};
@@ -377,35 +375,34 @@ void mp_chmap_get_reorder(int dst[MP_NUM_CHANNELS], const struct mp_chmap *from,
// Returns something like "fl-fr-fc". If there's a standard layout in lavc
// order, return that, e.g. "3.0" instead of "fl-fr-fc".
// Unassigned but valid speakers get names like "sp28".
char *mp_chmap_to_str_buf(char *buf, size_t buf_size, const struct mp_chmap *src)
char *mp_chmap_to_str(const struct mp_chmap *src)
{
buf[0] = '\0';
char *res = talloc_strdup(NULL, "");
if (mp_chmap_is_unknown(src)) {
snprintf(buf, buf_size, "unknown%d", src->num);
return buf;
}
if (mp_chmap_is_unknown(src))
return talloc_asprintf_append_buffer(res, "unknown%d", src->num);
for (int n = 0; n < src->num; n++) {
int sp = src->speaker[n];
const char *s = sp < MP_SPEAKER_ID_COUNT ? speaker_names[sp][0] : NULL;
char sp_buf[10];
char buf[10];
if (!s) {
snprintf(sp_buf, sizeof(sp_buf), "sp%d", sp);
s = sp_buf;
snprintf(buf, sizeof(buf), "sp%d", sp);
s = buf;
}
mp_snprintf_cat(buf, buf_size, "%s%s", n > 0 ? "-" : "", s);
res = talloc_asprintf_append_buffer(res, "%s%s", n > 0 ? "-" : "", s);
}
// To standard layout name
for (int n = 0; std_layout_names[n][0]; n++) {
if (strcmp(buf, std_layout_names[n][1]) == 0) {
snprintf(buf, buf_size, "%s", std_layout_names[n][0]);
if (res && strcmp(res, std_layout_names[n][1]) == 0) {
talloc_free(res);
res = talloc_strdup(NULL, std_layout_names[n][0]);
break;
}
}
return buf;
return res;
}
// If src can be parsed as channel map (as produced by mp_chmap_to_str()),
+3 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -36,7 +36,7 @@ struct ad_functions {
int (*init)(struct dec_audio *da, const char *decoder);
void (*uninit)(struct dec_audio *da);
int (*control)(struct dec_audio *da, int cmd, void *arg);
int (*decode_packet)(struct dec_audio *da, struct mp_audio **out);
int (*decode_packet)(struct dec_audio *da);
};
enum ad_ctrl {
+136 -53
View File
@@ -25,7 +25,6 @@
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/common.h>
#include <libavutil/intreadwrite.h>
#include "talloc.h"
@@ -38,13 +37,14 @@
#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);
@@ -73,6 +73,117 @@ 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 void set_data_from_avframe(struct dec_audio *da)
{
struct priv *priv = da->priv;
AVCodecContext *lavc_context = priv->avctx;
// Note: invalid parameters are rejected by dec_audio.c
int fmt = lavc_context->sample_fmt;
mp_audio_set_format(&da->decoded, af_from_avformat(fmt));
if (!da->decoded.format)
MP_FATAL(da, "unsupported lavc format %s", av_get_sample_fmt_name(fmt));
da->decoded.rate = lavc_context->sample_rate;
struct mp_chmap lavc_chmap;
mp_chmap_from_lavc(&lavc_chmap, lavc_context->channel_layout);
// No channel layout or layout disagrees with channel count
if (lavc_chmap.num != lavc_context->channels)
mp_chmap_from_channels(&lavc_chmap, lavc_context->channels);
if (priv->force_channel_map) {
struct sh_audio *sh_audio = da->header->audio;
if (lavc_chmap.num == sh_audio->channels.num)
lavc_chmap = sh_audio->channels;
}
mp_audio_set_channels(&da->decoded, &lavc_chmap);
da->decoded.samples = priv->avframe->nb_samples;
for (int n = 0; n < da->decoded.num_planes; n++)
da->decoded.planes[n] = priv->avframe->data[n];
}
static 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 +196,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 +227,17 @@ static int init(struct dec_audio *da, const char *decoder)
av_opt_set_double(lavc_context, "drc_scale", opts->ac3drc,
AV_OPT_SEARCH_CHILDREN);
#if HAVE_AVFRAME_SKIP_SAMPLES
// Let decoder add AV_FRAME_DATA_SKIP_SAMPLES.
av_opt_set(lavc_context, "flags2", "+skip_manual", AV_OPT_SEARCH_CHILDREN);
#endif
mp_set_avopts(da->log, lavc_context, opts->avopts);
lavc_context->codec_tag = sh->format;
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,
@@ -147,6 +258,8 @@ static int init(struct dec_audio *da, const char *decoder)
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,19 +286,21 @@ static int control(struct dec_audio *da, int cmd, void *arg)
switch (cmd) {
case ADCTRL_RESET:
avcodec_flush_buffers(ctx->avctx);
mp_audio_set_null_data(&da->decoded);
talloc_free(ctx->packet);
ctx->packet = NULL;
ctx->skip_samples = 0;
return CONTROL_TRUE;
}
return CONTROL_UNKNOWN;
}
static int decode_packet(struct dec_audio *da, struct mp_audio **out)
static int decode_packet(struct dec_audio *da)
{
struct priv *priv = da->priv;
AVCodecContext *avctx = priv->avctx;
mp_audio_set_null_data(&da->decoded);
struct demux_packet *mpkt = priv->packet;
if (!mpkt) {
if (demux_read_packet_async(da->header, &mpkt) == 0)
@@ -232,55 +347,23 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
if (!got_frame)
return mpkt ? AD_OK : AD_EOF;
set_data_from_avframe(da);
double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, NULL);
if (out_pts != MP_NOPTS_VALUE) {
da->pts = out_pts;
da->pts_offset = 0;
}
struct mp_audio *mpframe = mp_audio_from_avframe(priv->avframe);
if (!mpframe)
return AD_ERR;
struct mp_chmap lavc_chmap = mpframe->channels;
if (lavc_chmap.num != avctx->channels)
mp_chmap_from_channels(&lavc_chmap, avctx->channels);
if (priv->force_channel_map) {
struct sh_audio *sh_audio = da->header->audio;
if (lavc_chmap.num == sh_audio->channels.num)
lavc_chmap = sh_audio->channels;
}
mp_audio_set_channels(mpframe, &lavc_chmap);
#if HAVE_AVFRAME_SKIP_SAMPLES
AVFrameSideData *sd =
av_frame_get_side_data(priv->avframe, AV_FRAME_DATA_SKIP_SAMPLES);
if (sd && sd->size >= 10) {
char *d = sd->data;
priv->skip_samples += AV_RL32(d + 0);
uint32_t pad = AV_RL32(d + 4);
uint32_t skip = MPMIN(priv->skip_samples, mpframe->samples);
if (skip) {
mp_audio_skip_samples(mpframe, skip);
da->pts_offset += skip;
priv->skip_samples -= skip;
}
if (pad <= mpframe->samples)
mpframe->samples -= pad;
}
#endif
*out = mpframe;
av_frame_unref(priv->avframe);
MP_DBG(da, "Decoded %d -> %d samples\n", in_len, mpframe->samples);
MP_DBG(da, "Decoded %d -> %d samples\n", in_len, da->decoded.samples);
return 0;
}
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 = {
+9 -10
View File
@@ -43,7 +43,6 @@ struct ad_mpg123_context {
short delay;
/* If the stream is actually VBR. */
char vbr;
struct mp_audio frame;
};
static void uninit(struct dec_audio *da)
@@ -198,25 +197,27 @@ static int set_format(struct dec_audio *da)
int encoding;
ret = mpg123_getformat(con->handle, &rate, &channels, &encoding);
if (ret == MPG123_OK) {
mp_audio_set_num_channels(&con->frame, channels);
con->frame.rate = rate;
mp_audio_set_num_channels(&da->decoded, channels);
da->decoded.rate = rate;
int af = mpg123_format_to_af(encoding);
if (!af) {
/* This means we got a funny custom build of libmpg123 that only supports an unknown format. */
MP_ERR(da, "Bad encoding from mpg123: %i.\n", encoding);
return MPG123_ERR;
}
mp_audio_set_format(&con->frame, af);
mp_audio_set_format(&da->decoded, af);
con->sample_size = channels * af_fmt2bps(af);
}
return ret;
}
static int decode_packet(struct dec_audio *da, struct mp_audio **out)
static int decode_packet(struct dec_audio *da)
{
struct ad_mpg123_context *con = da->priv;
int ret;
mp_audio_set_null_data(&da->decoded);
struct demux_packet *pkt;
if (demux_read_packet_async(da->header, &pkt) == 0)
return AD_WAIT;
@@ -256,11 +257,8 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
}
int got_samples = bytes / con->sample_size;
*out = mp_audio_pool_get(da->pool, &con->frame, got_samples);
if (!*out)
return AD_ERR;
memcpy((*out)->planes[0], audio, bytes);
da->decoded.planes[0] = audio;
da->decoded.samples = got_samples;
update_info(da);
return 0;
@@ -276,6 +274,7 @@ static int control(struct dec_audio *da, int cmd, void *arg)
switch (cmd) {
case ADCTRL_RESET:
mp_audio_set_null_data(&da->decoded);
mpg123_close(con->handle);
if (mpg123_open_feed(con->handle) != MPG123_OK) {
+15 -18
View File
@@ -40,7 +40,6 @@ struct spdifContext {
int out_buffer_len;
uint8_t out_buffer[OUTBUF_SIZE];
bool need_close;
struct mp_audio fmt;
};
static int write_packet(void *p, uint8_t *buf, int buf_size)
@@ -118,13 +117,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 +131,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,11 +182,13 @@ fail:
return 0;
}
static int decode_packet(struct dec_audio *da, struct mp_audio **out)
static int decode_packet(struct dec_audio *da)
{
struct spdifContext *spdif_ctx = da->priv;
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
mp_audio_set_null_data(&da->decoded);
spdif_ctx->out_buffer_len = 0;
struct demux_packet *mpkt;
@@ -211,12 +212,8 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
if (ret < 0)
return AD_ERR;
int samples = spdif_ctx->out_buffer_len / spdif_ctx->fmt.sstride;
*out = mp_audio_pool_get(da->pool, &spdif_ctx->fmt, samples);
if (!*out)
return AD_ERR;
memcpy((*out)->planes[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len);
da->decoded.planes[0] = spdif_ctx->out_buffer;
da->decoded.samples = spdif_ctx->out_buffer_len / da->decoded.sstride;
return 0;
}
+168 -48
View File
@@ -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,27 @@ 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
};
// Drop audio buffer and reinit it (after format change)
// Returns whether the format was valid at all.
static bool reinit_audio_buffer(struct dec_audio *da)
{
if (!mp_audio_config_valid(&da->decoded)) {
MP_ERR(da, "Audio decoder did not specify audio "
"format, or requested an unsupported configuration!\n");
return false;
}
mp_audio_buffer_reinit(da->decode_buffer, &da->decoded);
return true;
}
static void uninit_decoder(struct dec_audio *d_audio)
{
if (d_audio->ad_driver) {
@@ -76,6 +89,7 @@ static int init_audio_codec(struct dec_audio *d_audio, const char *decoder)
return 0;
}
d_audio->decode_buffer = mp_audio_buffer_create(NULL);
return 1;
}
@@ -150,26 +164,136 @@ void audio_uninit(struct dec_audio *d_audio)
{
if (!d_audio)
return;
MP_VERBOSE(d_audio, "Uninit audio filters...\n");
af_destroy(d_audio->afilter);
if (d_audio->afilter) {
MP_VERBOSE(d_audio, "Uninit audio filters...\n");
af_destroy(d_audio->afilter);
d_audio->afilter = NULL;
}
uninit_decoder(d_audio);
talloc_free(d_audio->decode_buffer);
talloc_free(d_audio);
}
int audio_init_filters(struct dec_audio *d_audio, int in_samplerate,
int *out_samplerate, struct mp_chmap *out_channels,
int *out_format)
{
if (!d_audio->afilter)
d_audio->afilter = af_new(d_audio->global);
struct af_stream *afs = d_audio->afilter;
// input format: same as codec's output format:
mp_audio_buffer_get_format(d_audio->decode_buffer, &afs->input);
// Sample rate can be different when adjusting playback speed
afs->input.rate = in_samplerate;
// output format: same as ao driver's input format (if missing, fallback to input)
afs->output.rate = *out_samplerate;
mp_audio_set_channels(&afs->output, out_channels);
mp_audio_set_format(&afs->output, *out_format);
afs->replaygain_data = d_audio->replaygain_data;
char *s_from = mp_audio_config_to_str(&afs->input);
char *s_to = mp_audio_config_to_str(&afs->output);
MP_VERBOSE(d_audio, "Building audio filter chain for %s -> %s...\n", s_from, s_to);
talloc_free(s_from);
talloc_free(s_to);
// let's autoprobe it!
if (af_init(afs) != 0) {
af_destroy(afs);
d_audio->afilter = NULL;
return 0; // failed :(
}
*out_samplerate = afs->output.rate;
*out_channels = afs->output.channels;
*out_format = afs->output.format;
return 1;
}
/* Decode packets until we know the audio format. Then reinit the buffer.
* Returns AD_OK on success, negative AD_* code otherwise.
* Also returns AD_OK if already initialized (and does nothing).
*/
int initial_audio_decode(struct dec_audio *da)
{
while (!da->waiting) {
int ret = da->ad_driver->decode_packet(da, &da->waiting);
while (!mp_audio_config_valid(&da->decoded)) {
if (da->decoded.samples > 0)
return AD_ERR; // invalid format, rather than uninitialized
int ret = da->ad_driver->decode_packet(da);
if (ret < 0)
return ret;
}
talloc_steal(da, da->waiting);
da->decode_format = *da->waiting;
return mp_audio_config_valid(da->waiting) ? AD_OK : AD_ERR;
if (mp_audio_buffer_samples(da->decode_buffer) > 0) // avoid accidental flush
return AD_OK;
return reinit_audio_buffer(da) ? AD_OK : AD_ERR;
}
// Filter len bytes of input, put result into outbuf.
static int filter_n_bytes(struct dec_audio *da, struct mp_audio_buffer *outbuf,
int len)
{
bool format_change = false;
int error = 0;
assert(len > 0); // would break EOF logic below
while (mp_audio_buffer_samples(da->decode_buffer) < len) {
// Check for a format change
struct mp_audio config;
mp_audio_buffer_get_format(da->decode_buffer, &config);
format_change = !mp_audio_config_equals(&da->decoded, &config);
if (format_change) {
error = AD_EOF; // drain remaining data left in the current buffer
break;
}
if (da->decoded.samples > 0) {
int copy = MPMIN(da->decoded.samples, len);
struct mp_audio append = da->decoded;
append.samples = copy;
mp_audio_buffer_append(da->decode_buffer, &append);
mp_audio_skip_samples(&da->decoded, copy);
da->pts_offset += copy;
continue;
}
error = da->ad_driver->decode_packet(da);
if (error < 0)
break;
}
if (error == AD_WAIT)
return error;
// Filter
struct mp_audio filter_data;
mp_audio_buffer_peek(da->decode_buffer, &filter_data);
filter_data.rate = da->afilter->input.rate; // due to playback speed change
len = MPMIN(filter_data.samples, len);
filter_data.samples = len;
bool eof = error == AD_EOF && filter_data.samples == 0;
if (af_filter(da->afilter, &filter_data, eof ? AF_FILTER_FLAG_EOF : 0) < 0)
return AD_ERR;
mp_audio_buffer_append(outbuf, &filter_data);
if (error == AD_EOF && filter_data.samples > 0)
error = 0; // don't end playback yet
// remove processed data from decoder buffer:
mp_audio_buffer_skip(da->decode_buffer, len);
// if format was changed, and all data was drained, execute the format change
if (format_change && eof) {
error = AD_NEW_FMT;
if (!reinit_audio_buffer(da))
error = AD_ERR; // switch to invalid format
}
return error;
}
/* Try to get at least minsamples decoded+filtered samples in outbuf
@@ -177,55 +301,52 @@ int initial_audio_decode(struct dec_audio *da)
* Return 0 on success, or negative AD_* error code.
* In the former case outbuf has at least minsamples buffered on return.
* In case of EOF/error it might or might not be. */
int audio_decode(struct dec_audio *da, struct mp_audio_buffer *outbuf,
int audio_decode(struct dec_audio *d_audio, struct mp_audio_buffer *outbuf,
int minsamples)
{
struct af_stream *afs = da->afilter;
if (afs->initialized < 1)
if (!d_audio->afilter)
return AD_ERR;
MP_STATS(da, "start audio");
// Indicates that a filter seems to be buffering large amounts of data
int huge_filter_buffer = 0;
/* Filter output size will be about filter_multiplier times input size.
* If some filter buffers audio in big blocks this might only hold
* as average over time. */
double filter_multiplier = af_calc_filter_multiplier(d_audio->afilter);
int prev_buffered = -1;
int res = 0;
MP_STATS(d_audio, "start audio");
while (res >= 0 && minsamples >= 0) {
int buffered = mp_audio_buffer_samples(outbuf);
if (minsamples < buffered)
if (minsamples < buffered || buffered == prev_buffered)
break;
prev_buffered = buffered;
res = 0;
int decsamples = (minsamples - buffered) / filter_multiplier;
// + some extra for possible filter buffering, and avoid 0
decsamples += 512;
struct mp_audio *mpa = da->waiting;
if (!mpa)
res = da->ad_driver->decode_packet(da, &mpa);
if (res != AD_EOF) {
if (res < 0)
break;
if (!mpa )
continue;
if (huge_filter_buffer) {
/* Some filter must be doing significant buffering if the estimated
* input length didn't produce enough output from filters.
* Feed the filters 250 samples at a time until we have enough
* output. Very small amounts could make filtering inefficient while
* large amounts can make mpv demux the file unnecessarily far ahead
* to get audio data and buffer video frames in memory while doing
* so. However the performance impact of either is probably not too
* significant as long as the value is not completely insane. */
decsamples = 250;
}
if (mpa) {
da->pts_offset += mpa->samples;
da->decode_format = *mpa;
mp_audio_set_null_data(&da->decode_format);
// On format change, make sure to drain the filter chain.
if (!mp_audio_config_equals(&afs->input, mpa)) {
res = AD_NEW_FMT;
da->waiting = talloc_steal(da, mpa);
mpa = NULL;
}
}
/* if this iteration does not fill buffer, we must have lots
* of buffering in filters */
huge_filter_buffer = 1;
if (mpa)
da->waiting = NULL;
if (af_filter(afs, mpa, outbuf) < 0)
return AD_ERR;
res = filter_n_bytes(d_audio, outbuf, decsamples);
}
MP_STATS(da, "end audio");
MP_STATS(d_audio, "end audio");
return res;
}
@@ -233,11 +354,10 @@ void audio_reset_decoding(struct dec_audio *d_audio)
{
if (d_audio->ad_driver)
d_audio->ad_driver->control(d_audio, ADCTRL_RESET, NULL);
af_control_all(d_audio->afilter, AF_CONTROL_RESET, NULL);
if (d_audio->afilter)
af_control_all(d_audio->afilter, AF_CONTROL_RESET, NULL);
d_audio->pts = MP_NOPTS_VALUE;
d_audio->pts_offset = 0;
if (d_audio->waiting) {
talloc_free(d_audio->waiting);
d_audio->waiting = NULL;
}
if (d_audio->decode_buffer)
mp_audio_buffer_clear(d_audio->decode_buffer);
}
+7 -3
View File
@@ -33,13 +33,13 @@ struct dec_audio {
struct mpv_global *global;
const struct ad_functions *ad_driver;
struct sh_stream *header;
struct mp_audio_buffer *decode_buffer;
struct af_stream *afilter;
char *decoder_desc;
struct replaygain_data *replaygain_data;
int init_retries;
struct mp_audio_pool *pool;
struct mp_audio decode_format;
struct mp_audio *waiting; // used on format-change
// set by decoder
struct mp_audio decoded; // decoded audio set by last decode_packet() call
int bitrate; // input bitrate, can change with VBR sources
// last known pts value in output from decoder
double pts;
@@ -65,4 +65,8 @@ int initial_audio_decode(struct dec_audio *d_audio);
void audio_reset_decoding(struct dec_audio *d_audio);
void audio_uninit(struct dec_audio *d_audio);
int audio_init_filters(struct dec_audio *d_audio, int in_samplerate,
int *out_samplerate, struct mp_chmap *out_channels,
int *out_format);
#endif /* MPLAYER_DEC_AUDIO_H */
+41 -94
View File
@@ -28,7 +28,6 @@
#include "options/m_option.h"
#include "options/m_config.h"
#include "audio/audio_buffer.h"
#include "af.h"
// Static list of filters
@@ -54,7 +53,6 @@ extern const struct af_info af_info_center;
extern const struct af_info af_info_sinesuppress;
extern const struct af_info af_info_karaoke;
extern const struct af_info af_info_scaletempo;
extern const struct af_info af_info_forcespeed;
extern const struct af_info af_info_bs2b;
extern const struct af_info af_info_lavfi;
extern const struct af_info af_info_convert24;
@@ -85,7 +83,6 @@ static const struct af_info *const filter_list[] = {
&af_info_center,
&af_info_sinesuppress,
&af_info_karaoke,
&af_info_forcespeed,
&af_info_scaletempo,
#if HAVE_LIBBS2B
&af_info_bs2b,
@@ -124,6 +121,13 @@ const struct m_obj_list af_obj_list = {
},
};
static bool af_config_equals(struct mp_audio *a, struct mp_audio *b)
{
return a->format == b->format
&& mp_chmap_equals(&a->channels, &b->channels)
&& a->rate == b->rate;
}
static void af_copy_unset_fields(struct mp_audio *dst, struct mp_audio *src)
{
if (dst->format == AF_FORMAT_UNKNOWN)
@@ -158,7 +162,7 @@ static int output_control(struct af_instance* af, int cmd, void* arg)
*filter_output = *output;
af_copy_unset_fields(filter_output, in);
*in = *filter_output;
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
return af_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
}
}
return AF_UNKNOWN;
@@ -308,8 +312,11 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
while (af) {
char b[128] = {0};
mp_snprintf_cat(b, sizeof(b), " [%s] ", af->info->name);
if (af->data)
mp_snprintf_cat(b, sizeof(b), "%s", mp_audio_config_to_str(af->data));
if (af->data) {
char *info = mp_audio_config_to_str(af->data);
mp_snprintf_cat(b, sizeof(b), "%s", info);
talloc_free(info);
}
if (af == at)
mp_snprintf_cat(b, sizeof(b), " <-");
MP_MSG(s, msg_level, "%s\n", b);
@@ -317,7 +324,9 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
af = af->next;
}
MP_MSG(s, msg_level, " [ao] %s\n", mp_audio_config_to_str(&s->output));
char *info = mp_audio_config_to_str(&s->output);
MP_MSG(s, msg_level, " [ao] %s\n", info);
talloc_free(info);
}
static int af_count_filters(struct af_stream *s)
@@ -514,16 +523,11 @@ static int af_reinit(struct af_stream *s)
// Reset just in case...
mp_audio_set_null_data(&in);
if (!mp_audio_config_valid(&in))
goto error;
int rv = af->control(af, AF_CONTROL_REINIT, &in);
if (rv == AF_OK && !mp_audio_config_equals(&in, af->prev->data))
rv = AF_FALSE; // conversion filter needed
switch (rv) {
case AF_OK:
if (!mp_audio_config_valid(af->data))
goto error;
af = af->next;
break;
case AF_FALSE: { // Configuration filter is needed
@@ -554,27 +558,22 @@ static int af_reinit(struct af_stream *s)
MP_ERR(s, "Reinitialization did not work, "
"audio filter '%s' returned error code %i\n",
af->info->name, rv);
goto error;
af_print_filter_chain(s, af, MSGL_ERR);
return AF_ERROR;
}
}
af_print_filter_chain(s, NULL, MSGL_V);
/* Set previously unset fields in s->output to those of the filter chain
* output. This is used to make the output format fixed, and even if you
* insert new filters or change the input format, the output format won't
* change. (Audio outputs generally can't change format at runtime.) */
af_copy_unset_fields(&s->output, &s->filter_output);
if (mp_audio_config_equals(&s->output, &s->filter_output)) {
s->initialized = 1;
af_print_filter_chain(s, NULL, MSGL_V);
return AF_OK;
}
goto error;
return af_config_equals(&s->output, &s->filter_output) ? AF_OK : AF_ERROR;
negotiate_error:
MP_ERR(s, "Unable to convert audio input format to output format.\n");
error:
s->initialized = -1;
af_print_filter_chain(s, af, MSGL_ERR);
return AF_ERROR;
}
@@ -584,7 +583,6 @@ void af_uninit(struct af_stream *s)
{
while (s->first->next && s->first->next != s->last)
af_remove(s, s->first->next);
s->initialized = 0;
}
struct af_stream *af_new(struct mpv_global *global)
@@ -634,6 +632,10 @@ void af_destroy(struct af_stream *s)
The return value is 0 if success and -1 if failure */
int af_init(struct af_stream *s)
{
// Sanity check
if (!s)
return -1;
// Precaution in case caller is misbehaving
mp_audio_set_null_data(&s->input);
mp_audio_set_null_data(&s->output);
@@ -643,20 +645,17 @@ int af_init(struct af_stream *s)
// Add all filters in the list (if there are any)
struct m_obj_settings *list = s->opts->af_settings;
for (int i = 0; list && list[i].name; i++) {
struct af_instance *af =
af_prepend(s, s->last, list[i].name, list[i].attribs);
if (!af) {
if (!af_prepend(s, s->last, list[i].name, list[i].attribs)) {
af_uninit(s);
s->initialized = -1;
return -1;
}
af->label = talloc_strdup(af, list[i].label);
}
}
if (af_reinit(s) != AF_OK) {
// Something is stuffed audio out will not work
MP_ERR(s, "Could not create audio filter chain.\n");
af_uninit(s);
return -1;
}
return 0;
@@ -669,6 +668,9 @@ int af_init(struct af_stream *s)
struct af_instance *af_add(struct af_stream *s, char *name, char **args)
{
struct af_instance *new;
// Sanity check
if (!s || !s->first || !name)
return NULL;
// Insert the filter somewhere nice
if (af_is_conversion_filter(s->first->next))
new = af_append(s, s->first->next, name, args);
@@ -679,85 +681,30 @@ struct af_instance *af_add(struct af_stream *s, char *name, char **args)
// Reinitalize the filter list
if (af_reinit(s) != AF_OK) {
af_remove(s, new);
if (af_reinit(s) != AF_OK) {
af_uninit(s);
af_init(s);
}
af_uninit(s);
af_init(s);
return NULL;
}
return new;
}
struct af_instance *af_find_by_label(struct af_stream *s, char *label)
{
for (struct af_instance *af = s->first; af; af = af->next) {
if (af->label && strcmp(af->label, label) == 0)
return af;
}
return NULL;
}
/* Remove the first filter that matches this name. Return number of filters
* removed (0, 1), or a negative error code if reinit after removing failed.
/* Filter data chunk through the filters in the list.
* On success, *data is set to the filtered data/format.
* Warning: input audio data will be overwritten.
*/
int af_remove_by_label(struct af_stream *s, char *label)
{
struct af_instance *af = af_find_by_label(s, label);
if (!af)
return 0;
af_remove(s, af);
if (af_reinit(s) != AF_OK) {
af_uninit(s);
af_init(s);
return -1;
}
return 1;
}
/* Feed "data" to the chain, and write results to output. "data" needs to be
* a refcounted frame, although refcounting is not used yet.
* data==NULL means EOF.
*/
int af_filter(struct af_stream *s, struct mp_audio *data,
struct mp_audio_buffer *output)
int af_filter(struct af_stream *s, struct mp_audio *data, int flags)
{
struct af_instance *af = s->first;
assert(s->initialized > 0);
int flags = 0;
int r = 0;
struct mp_audio tmp;
char dummy[MP_NUM_CHANNELS];
if (data) {
assert(mp_audio_config_equals(af->data, data));
r = mp_audio_make_writeable(data);
} else {
data = &tmp;
*data = *(af->data);
mp_audio_set_null_data(data);
flags = AF_FILTER_FLAG_EOF;
for (int n = 0; n < MP_NUM_CHANNELS; n++)
data->planes[n] = &dummy[n];
}
if (r < 0)
goto done;
struct mp_audio frame = *data;
for (int n = 0; n < MP_NUM_CHANNELS; n++)
frame.allocated[n] = NULL;
assert(mp_audio_config_equals(af->data, data));
// Iterate through all filters
while (af) {
r = af->filter(af, &frame, flags);
int r = af->filter(af, data, flags);
if (r < 0)
goto done;
assert(mp_audio_config_equals(af->data, &frame));
return r;
assert(mp_audio_config_equals(af->data, data));
af = af->next;
}
mp_audio_buffer_append(output, &frame);
done:
if (data != &tmp)
talloc_free(data);
return r;
return 0;
}
// Calculate average ratio of filter output samples to input samples.
+1 -9
View File
@@ -76,13 +76,10 @@ struct af_instance {
* the number of samples passed though. (Ratio of input
* and output, e.g. mul=4 => 1 sample becomes 4 samples) .*/
bool auto_inserted; // inserted by af.c, such as conversion filters
char *label;
};
// Current audio stream
struct af_stream {
int initialized; // 0: no, 1: yes, -1: attempted to, but failed
// The first and last filter in the list
struct af_instance *first;
struct af_instance *last;
@@ -120,7 +117,6 @@ enum af_control {
AF_CONTROL_SET_PAN_BALANCE,
AF_CONTROL_GET_PAN_BALANCE,
AF_CONTROL_SET_PLAYBACK_SPEED,
AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE,
};
// Argument for AF_CONTROL_SET_PAN_LEVEL
@@ -134,11 +130,7 @@ void af_destroy(struct af_stream *s);
int af_init(struct af_stream *s);
void af_uninit(struct af_stream *s);
struct af_instance *af_add(struct af_stream *s, char *name, char **args);
int af_remove_by_label(struct af_stream *s, char *label);
struct af_instance *af_find_by_label(struct af_stream *s, char *label);
struct mp_audio_buffer;
int af_filter(struct af_stream *s, struct mp_audio *data,
struct mp_audio_buffer *output);
int af_filter(struct af_stream *s, struct mp_audio *data, int flags);
struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg);
void af_control_all(struct af_stream *s, int cmd, void *arg);
+49 -20
View File
@@ -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;
-1
View File
@@ -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)
{
+41 -9
View File
@@ -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,
};
+5 -6
View File
@@ -78,7 +78,8 @@ static int control(struct af_instance* af, int cmd, void* arg)
int mapsize;
// Free previous buffers
free(s->buf[0]);
if (s->buf)
free(s->buf[0]);
// unmap previous area
if(s->mmap_area)
@@ -93,10 +94,8 @@ static int control(struct af_instance* af, int cmd, void* arg)
// Allocate new buffers (as one continuous block)
s->buf[0] = calloc(s->sz*af->data->nch, af->data->bps);
if(NULL == s->buf[0]) {
if(NULL == s->buf[0])
MP_FATAL(af, "Out of memory\n");
return AF_ERROR;
}
for(i = 1; i < af->data->nch; i++)
s->buf[i] = (uint8_t *)s->buf[0] + i*s->sz*af->data->bps;
@@ -148,8 +147,8 @@ static int control(struct af_instance* af, int cmd, void* arg)
static void uninit( struct af_instance* af )
{
af_export_t* s = af->priv;
free(s->buf[0]);
if (s->buf)
free(s->buf[0]);
// Free mmaped area
if(s->mmap_area)
-69
View File
@@ -1,69 +0,0 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include "af.h"
struct priv {
double speed;
};
static int control(struct af_instance *af, int cmd, void *arg)
{
struct priv *priv = af->priv;
switch (cmd) {
case AF_CONTROL_REINIT: {
struct mp_audio *in = arg;
struct mp_audio orig_in = *in;
struct mp_audio *out = af->data;
mp_audio_copy_config(out, in);
out->rate = in->rate * priv->speed;
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
}
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
priv->speed = *(double *)arg;
return AF_OK;
}
}
return AF_UNKNOWN;
}
static int filter(struct af_instance *af, struct mp_audio *data, int flags)
{
mp_audio_copy_config(data, af->data);
return 0;
}
static int af_open(struct af_instance *af)
{
struct priv *priv = af->priv;
af->control = control;
af->filter = filter;
priv->speed = 1.0;
return AF_OK;
}
#define OPT_BASE_STRUCT struct priv
const struct af_info af_info_forcespeed = {
.info = "Force audio speed",
.name = "forcespeed",
.open = af_open,
.priv_size = sizeof(struct priv),
};
+2 -2
View File
@@ -80,12 +80,12 @@ static int control(struct af_instance *af, int cmd, void *arg)
force_out_params(af, out);
if (in->nch != out->nch || in->bps != out->bps) {
MP_ERR(af, "Forced input/output formats are incompatible.\n");
MP_ERR(af, "[af_format] Forced input/output formats are incompatible.\n");
return AF_ERROR;
}
if (priv->fail) {
MP_ERR(af, "Failing on purpose.\n");
MP_ERR(af, "[af_format] Failing on purpose.\n");
return AF_ERROR;
}
+17 -35
View File
@@ -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;
+8 -17
View File
@@ -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}
},
+6 -5
View File
@@ -214,7 +214,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 +339,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 +347,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 +371,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;
}
+1 -1
View File
@@ -271,7 +271,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
switch (cmd) {
case AF_CONTROL_REINIT: {
struct mp_audio *data = (struct mp_audio *)arg;
float srate = data->rate / 1000.0;
float srate = data->rate / 1000;
int nch = data->nch;
int use_int = 0;
+38 -26
View File
@@ -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
View File
@@ -25,77 +25,121 @@
#include <stdbool.h>
#include "misc/bstr.h"
#include "osdep/endian.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
View File
@@ -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
+47 -180
View File
@@ -29,7 +29,6 @@
#include "audio/format.h"
#include "audio/audio.h"
#include "input/input.h"
#include "options/options.h"
#include "options/m_config.h"
#include "common/msg.h"
@@ -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:
@@ -98,6 +94,9 @@ static const struct ao_driver * const audio_out_drivers[] = {
#if HAVE_ENCODING
&audio_out_lavc,
#endif
#if HAVE_COREAUDIO
&audio_out_coreaudio_exclusive,
#endif
#if HAVE_RSOUND
&audio_out_rsound,
#endif
@@ -129,34 +128,12 @@ const struct m_obj_list ao_obj_list = {
.allow_trailer = true,
};
// Return true if the ao with the given name matches the opt (--audio-device
// contents). If opt is empty, matching always succeeds. If out_device is not
// NULL, it will be set to the implied audio device (or NULL).
static bool match_ao_driver(const char *ao_name, char *opt, char **out_device)
static struct ao *ao_create(bool probing, struct mpv_global *global,
struct input_ctx *input_ctx,
struct encode_lavc_context *encode_lavc_ctx,
int samplerate, int format, struct mp_chmap channels,
char *name, char **args)
{
bstr ao_nameb = bstr0(ao_name);
bstr optb = bstr0(opt);
char *dummy;
if (!out_device)
out_device = &dummy;
*out_device = NULL;
if (!optb.len || bstr_equals0(optb, "auto"))
return true;
if (!bstr_startswith(optb, ao_nameb))
return false;
if (optb.len > ao_nameb.len && optb.start[ao_nameb.len] != '/')
return false;
char *split = strchr(opt, '/');
*out_device = split ? split + 1 : NULL;
return true;
}
static struct ao *ao_alloc(bool probing, struct mpv_global *global,
struct input_ctx *input_ctx, char *name, char **args)
{
struct MPOpts *opts = global->opts;
struct mp_log *log = mp_log_new(NULL, global->log, "ao");
struct m_obj_desc desc;
if (!m_obj_list_find(&desc, &ao_obj_list, bstr0(name))) {
@@ -169,62 +146,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 +183,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 +204,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 +223,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 +258,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.
@@ -396,22 +324,6 @@ bool ao_eof_reached(struct ao *ao)
return ao->api->get_eof ? ao->api->get_eof(ao) : true;
}
// Query the AO_EVENT_*s as requested by the events parameter, and return them.
int ao_query_and_reset_events(struct ao *ao, int events)
{
int actual_events = 0;
if (atomic_load(&ao->request_reload)) // don't need to reset it
actual_events |= AO_EVENT_RELOAD;
return actual_events & events;
}
// Request that the player core destroys and recreates the AO.
void ao_request_reload(struct ao *ao)
{
atomic_store(&ao->request_reload, true);
mp_input_wakeup(ao->input_ctx);
}
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
struct mp_chmap *map)
{
@@ -448,48 +360,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
View File
@@ -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 -322
View File
@@ -38,13 +38,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 +53,7 @@ struct priv {
snd_pcm_format_t alsa_fmt;
int can_pause;
snd_pcm_sframes_t prepause_frames;
double delay_before_pause;
float delay_before_pause;
int buffersize; // in frames
int outburst; // in frames
@@ -78,12 +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,49 @@ static int try_open_device(struct ao *ao, const char *device, int open_mode)
return snd_pcm_open(&p->alsa, device, SND_PCM_STREAM_PLAYBACK, open_mode);
}
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;
if (p->alsa) {
int err;
err = snd_pcm_close(p->alsa);
CHECK_ALSA_ERROR("pcm close error");
}
alsa_error: ;
}
/*
open & setup audio device
return: 0=success -1=fail
*/
static int init(struct ao *ao)
{
struct priv *p = ao->priv;
int err;
snd_pcm_uframes_t chunk_size;
snd_pcm_uframes_t bufsize;
snd_pcm_uframes_t boundary;
struct priv *p = ao->priv;
if (!p->cfg_ni)
ao->format = af_fmt_from_planar(ao->format);
struct mp_chmap implied_chmap = ao->channels;
/* switch for spdif
* sets opening sequence for SPDIF
* sets also the playback and other switches 'on the fly'
* while opening the abstract alias for the spdif subdevice
* 'iec958'
*/
const char *device;
if (AF_FORMAT_IS_IEC61937(ao->format)) {
device = "iec958";
MP_VERBOSE(ao, "playing AC3/iec61937/iec958, %i channels\n",
ao->channels.num);
} else {
device = select_chmap(ao, &implied_chmap);
// Not-default likely means a hw device - enable software conversions.
if (strcmp(device, "default") != 0)
device = select_chmap(ao);
if (strcmp(device, "default") != 0 && (ao->format & AF_FORMAT_F)) {
// hack - use the converter plugin (why the heck?)
device = talloc_asprintf(ao, "plug:%s", device);
}
}
if (ao->device)
device = ao->device;
if (p->cfg_device && p->cfg_device[0])
device = p->cfg_device;
MP_VERBOSE(ao, "using device: %s\n", device);
MP_VERBOSE(ao, "using ALSA version: %s\n", snd_asoundlib_version());
err = try_open_device(ao, device, p->cfg_block ? 0 : SND_PCM_NONBLOCK);
int open_mode = p->cfg_block ? 0 : SND_PCM_NONBLOCK;
//modes = 0, SND_PCM_NONBLOCK, SND_PCM_ASYNC
err = try_open_device(ao, device, open_mode);
if (err < 0) {
if (err != -EBUSY && !p->cfg_block) {
MP_WARN(ao, "Open in nonblock-mode "
@@ -492,18 +405,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 +417,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 +442,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 +465,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 +494,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 +513,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 +555,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 +571,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 +608,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 +641,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 +661,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 +735,10 @@ alsa_error:
return -1;
}
static void list_devs(struct ao *ao, struct ao_device_list *list)
{
void **hints;
if (snd_device_name_hint(-1, "pcm", &hints) < 0)
return;
for (int n = 0; hints[n]; n++) {
char *name = snd_device_name_get_hint(hints[n], "NAME");
char *desc = snd_device_name_get_hint(hints[n], "DESC");
char *io = snd_device_name_get_hint(hints[n], "IOID");
if (io && strcmp(io, "Output") != 0)
continue;
char desc2[1024];
snprintf(desc2, sizeof(desc2), "%s", desc ? desc : "");
for (int i = 0; desc2[i]; i++) {
if (desc2[i] == '\n')
desc2[i] = '/';
}
ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc2});
}
snd_device_name_free_hint(hints);
}
#define OPT_BASE_STRUCT struct priv
const struct ao_driver audio_out_alsa = {
.description = "ALSA audio output",
.description = "ALSA-0.9.x-1.x audio output",
.name = "alsa",
.init = init,
.uninit = uninit,
@@ -910,7 +752,6 @@ const struct ao_driver audio_out_alsa = {
.drain = drain,
.wait = audio_wait,
.wakeup = ao_wakeup_poll,
.list_devs = list_devs,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.cfg_block = 1,
+19 -16
View File
@@ -33,6 +33,10 @@ struct priv {
AudioUnit audio_unit;
uint64_t hw_latency_us;
// options
int opt_device_id;
int opt_list;
};
bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
@@ -132,10 +136,6 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
return get_volume(ao, arg);
case AOCONTROL_SET_VOLUME:
return set_volume(ao, arg);
case AOCONTROL_HAS_SOFT_VOLUME:
return CONTROL_TRUE;
case AOCONTROL_HAS_PER_APP_VOLUME:
return CONTROL_TRUE;
}
return CONTROL_UNKNOWN;
}
@@ -145,15 +145,14 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd);
static int init(struct ao *ao)
{
if (AF_FORMAT_IS_IEC61937(ao->format)) {
MP_WARN(ao, "detected IEC61937, redirecting to coreaudio_exclusive\n");
ao->redirect = "coreaudio_exclusive";
return CONTROL_ERROR;
}
struct priv *p = ao->priv;
OSStatus err = ca_select_device(ao, ao->device, &p->device);
if (p->opt_list) ca_print_device_list(ao);
ao->per_application_mixer = true;
ao->no_persistent_volume = true;
OSStatus err = ca_select_device(ao, p->opt_device_id, &p->device);
CHECK_CA_ERROR("failed to select device");
if (!init_chmap(ao))
@@ -185,7 +184,7 @@ static bool init_chmap(struct ao *ao)
&layouts, &n_layouts);
CHECK_CA_ERROR("could not get audio device prefered layouts");
struct mp_chmap_sel chmap_sel = {.tmp = p};
struct mp_chmap_sel chmap_sel = {0};
for (int i = 0; i < n_layouts; i++) {
struct mp_chmap chmap = {0};
if (ca_layout_to_mp_chmap(ao, &layouts[i], &chmap))
@@ -221,9 +220,9 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
AudioComponentDescription desc = (AudioComponentDescription) {
.componentType = kAudioUnitType_Output,
.componentSubType = (ao->device) ?
kAudioUnitSubType_HALOutput :
kAudioUnitSubType_DefaultOutput,
.componentSubType = (p->opt_device_id < 0) ?
kAudioUnitSubType_DefaultOutput :
kAudioUnitSubType_HALOutput,
.componentManufacturer = kAudioUnitManufacturer_Apple,
.componentFlags = 0,
.componentFlagsMask = 0,
@@ -440,6 +439,10 @@ const struct ao_driver audio_out_coreaudio = {
.control = control,
.pause = stop,
.resume = start,
.list_devs = ca_get_device_list,
.priv_size = sizeof(struct priv),
.options = (const struct m_option[]) {
OPT_INT("device_id", opt_device_id, 0, OPTDEF_INT(-1)),
OPT_FLAG("list", opt_list, 0),
{0}
},
};
@@ -333,6 +333,10 @@ struct priv {
bool changed_mixing;
int stream_asbd_changed;
bool muted;
// options
int opt_device_id;
int opt_list;
};
static int get_ring_size(struct ao *ao)
@@ -398,15 +402,16 @@ static int init(struct ao *ao)
{
struct priv *p = ao->priv;
OSStatus err = ca_select_device(ao, ao->device, &p->device);
if (p->opt_list) ca_print_device_list(ao);
OSStatus err = ca_select_device(ao, p->opt_device_id, &p->device);
CHECK_CA_ERROR("failed to select device");
ao->format = af_fmt_from_planar(ao->format);
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)) {
/* 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, p->device))
supports_digital = true;
}
@@ -516,7 +521,14 @@ static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd)
err = ca_enable_device_listener(p->device, changed);
CHECK_CA_ERROR("cannot install format change listener during init");
if (p->stream_asbd.mFormatFlags & kAudioFormatFlagIsBigEndian)
#if BYTE_ORDER == BIG_ENDIAN
if (!(p->stream_asdb.mFormatFlags & kAudioFormatFlagIsBigEndian))
#else
/* tell mplayer that we need a byteswap on AC3 streams, */
if (p->stream_asbd.mFormatID & kAudioFormat60958AC3)
ao->format = AF_FORMAT_AC3_LE;
else if (p->stream_asbd.mFormatFlags & kAudioFormatFlagIsBigEndian)
#endif
MP_WARN(ao, "stream has non-native byte order, output may fail\n");
ao->samplerate = p->stream_asbd.mSampleRate;
@@ -581,12 +593,12 @@ static int get_space(struct ao *ao)
return mp_ring_available(p->buffer) / ao->sstride;
}
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
// FIXME: should also report the delay of coreaudio itself (hardware +
// internal buffers)
struct priv *p = ao->priv;
return mp_ring_buffered(p->buffer) / (double)ao->bps;
return mp_ring_buffered(p->buffer) / (float)ao->bps;
}
static void uninit(struct ao *ao)
@@ -654,7 +666,6 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
.reset = reset,
.pause = audio_pause,
.resume = audio_resume,
.list_devs = ca_get_device_list,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv){
.muted = false,
@@ -664,4 +675,9 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
.stream_idx = -1,
.changed_mixing = false,
},
.options = (const struct m_option[]) {
OPT_INT("device_id", opt_device_id, 0, OPTDEF_INT(-1)),
OPT_FLAG("list", opt_list, 0),
{0}
},
};
+2 -2
View File
@@ -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:
+27 -35
View File
@@ -25,51 +25,44 @@
#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)
void ca_print_device_list(struct ao *ao)
{
size_t n_buffers;
AudioBufferList *buffers;
const ca_scope scope = kAudioDevicePropertyStreamConfiguration;
CA_GET_ARY_O(dev, scope, &buffers, &n_buffers);
talloc_free(buffers);
return n_buffers > 0;
}
char *help = talloc_strdup(NULL, "Available output devices:\n");
void ca_get_device_list(struct ao *ao, struct ao_device_list *list)
{
AudioDeviceID *devs;
size_t n_devs;
OSStatus err =
CA_GET_ARY(kAudioObjectSystemObject, kAudioHardwarePropertyDevices,
&devs, &n_devs);
CHECK_CA_ERROR("Failed to get list of output devices.");
for (int i = 0; i < n_devs; i++) {
if (!ca_is_output_device(ao, devs[i]))
continue;
char name[32];
char *desc;
sprintf(name, "%d", devs[i]);
err = CA_GET_STR(devs[i], kAudioObjectPropertyName, &desc);
if (err != noErr)
desc = "Unknown";
ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc});
char *name;
err = CA_GET_STR(devs[i], kAudioObjectPropertyName, &name);
if (err == noErr)
talloc_steal(devs, name);
else
name = "Unknown";
help = talloc_asprintf_append(
help, " * %s (id: %" PRIu32 ")\n", name, devs[i]);
}
talloc_free(devs);
coreaudio_error:
return;
MP_INFO(ao, "%s", help);
talloc_free(help);
}
OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
OSStatus ca_select_device(struct ao *ao, int selection, AudioDeviceID *device)
{
OSStatus err = noErr;
int selection = name ? strtol(name, (char **)NULL, 10) : -1;
if (errno == EINVAL || errno == ERANGE) {
selection = -1;
MP_WARN(ao, "device identifier '%s' is invalid\n", name);
}
*device = 0;
if (selection < 0) {
// device not set by user, get the default one
@@ -82,14 +75,14 @@ OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
}
if (mp_msg_test(ao->log, MSGL_V)) {
char *desc;
err = CA_GET_STR(*device, kAudioObjectPropertyName, &desc);
char *name;
err = CA_GET_STR(*device, kAudioObjectPropertyName, &name);
CHECK_CA_ERROR("could not get selected audio device name");
MP_VERBOSE(ao, "selected audio output device: %s (%" PRIu32 ")\n",
desc, *device);
name, *device);
talloc_free(desc);
talloc_free(name);
}
coreaudio_error:
@@ -136,21 +129,20 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
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) ?
asbd->mFormatID = AF_FORMAT_IS_AC3(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)
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 (BYTE_ORDER == BIG_ENDIAN)
if ((ao->format & AF_FORMAT_END_MASK) == AF_FORMAT_BE)
asbd->mFormatFlags |= kAudioFormatFlagIsBigEndian;
asbd->mFramesPerPacket = 1;
+2 -2
View File
@@ -46,8 +46,8 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message);
if (err != noErr) goto label; \
} while (0)
void ca_get_device_list(struct ao *ao, struct ao_device_list *list);
OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device);
void ca_print_device_list(struct ao *ao);
OSStatus ca_select_device(struct ao *ao, int selection, AudioDeviceID *device);
void ca_fill_asbd(struct ao *ao, AudioStreamBasicDescription *asbd);
void ca_print_asbd(struct ao *ao, const char *description,
+37 -104
View File
@@ -41,7 +41,6 @@
#include "internal.h"
#include "common/msg.h"
#include "osdep/timer.h"
#include "osdep/io.h"
#include "options/m_option.h"
/**
@@ -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 * 0.100; // space for 100ms
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}
},
};
+5 -13
View File
@@ -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)
@@ -176,7 +175,6 @@ static int init(struct ao *ao)
fail:
pthread_mutex_unlock(&ao->encode_lavc_ctx->lock);
ac->shutdown = true;
return -1;
}
@@ -187,9 +185,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 +202,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 +235,8 @@ static int encode(struct ao *ao, double apts, void **data)
frame->format = af_to_avformat(ao->format);
frame->nb_samples = ac->aframesize;
size_t num_planes = af_fmt_is_planar(ao->format) ? ao->channels.num : 1;
assert(num_planes <= AV_NUM_DATA_POINTERS);
for (int n = 0; n < num_planes; n++)
assert(ao->channels.num <= AV_NUM_DATA_POINTERS);
for (int n = 0; n < ao->channels.num; n++)
frame->extended_data[n] = data[n];
frame->linesize[0] = frame->nb_samples * ao->sstride;
@@ -354,12 +346,12 @@ static int play(struct ao *ao, void **data, int samples, int flags)
size_t num_planes = af_fmt_is_planar(ao->format) ? ao->channels.num : 1;
void *tempdata = NULL;
void *padded[MP_NUM_CHANNELS];
if ((flags & AOPLAY_FINAL_CHUNK) && (samples % ac->aframesize)) {
tempdata = talloc_new(NULL);
size_t bytelen = samples * ao->sstride;
size_t extralen = (ac->aframesize - 1) * ao->sstride;
void *padded[MP_NUM_CHANNELS];
for (int n = 0; n < num_planes; n++) {
padded[n] = talloc_size(tempdata, bytelen + extralen);
memcpy(padded[n], data[n], bytelen);
@@ -439,7 +431,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);
+2 -3
View File
@@ -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
View File
@@ -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 -339
View File
@@ -31,7 +31,6 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
@@ -39,8 +38,6 @@
#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 +52,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 +81,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 +143,7 @@ static int oss2format(int format)
if (format_table[n][0] == format)
return format_table[n][1];
}
return 0;
return -1;
}
@@ -194,7 +178,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 +189,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 +210,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 +266,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 +450,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 +556,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 +607,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
View File
@@ -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
+2 -24
View File
@@ -125,24 +125,6 @@ static int validate_device_opt(struct mp_log *log, const m_option_t *opt,
return 0;
}
static void list_devs(struct ao *ao, struct ao_device_list *list)
{
if (Pa_Initialize() < 0)
return;
int count = Pa_GetDeviceCount(); // negative on error
for (int n = 0; n < count; n++) {
const PaDeviceInfo* info = Pa_GetDeviceInfo(n);
char desc[1024];
snprintf(desc, sizeof(desc), "%s, %d channels, latency: %.2f "
"ms, sample rate: %.0f", info->name,
info->maxOutputChannels,
info->defaultHighOutputLatency * 1000,
info->defaultSampleRate);
ao_device_list_add(list, ao, &(struct ao_device_desc){info->name, desc});
}
Pa_Terminate();
}
static int stream_callback(const void *input,
void *output_v,
unsigned long frameCount,
@@ -185,11 +167,8 @@ static int init(struct ao *ao)
return -1;
int pa_device = Pa_GetDefaultOutputDevice();
char *dev_name = priv->cfg_device;
if (!dev_name || !dev_name[0])
dev_name = ao->device;
if (dev_name && dev_name[0])
pa_device = find_device(ao->log, dev_name);
if (priv->cfg_device && priv->cfg_device[0])
pa_device = find_device(ao->log, priv->cfg_device);
if (pa_device == paNoDevice)
goto error_exit;
@@ -267,7 +246,6 @@ const struct ao_driver audio_out_portaudio = {
.uninit = uninit,
.reset = reset,
.resume = resume,
.list_devs = list_devs,
.priv_size = sizeof(struct priv),
.options = (const struct m_option[]) {
OPT_STRING_VALIDATE("device", cfg_device, 0, validate_device_opt),
+63 -164
View File
@@ -35,6 +35,8 @@
#include "ao.h"
#include "internal.h"
#define PULSE_CLIENT_NAME "mpv"
#define VOL_PA2MP(v) ((v) * 100 / PA_VOLUME_NORM)
#define VOL_MP2PA(v) ((v) * PA_VOLUME_NORM / 100)
@@ -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,67 @@ 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;
pthread_mutex_init(&priv->wakeup_lock, NULL);
pthread_cond_init(&priv->wakeup, NULL);
ao->per_application_mixer = 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 +343,12 @@ static int init(struct ao *ao)
goto unlock_and_fail;
}
(void)pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "video");
(void)pa_proplist_sets(proplist, PA_PROP_MEDIA_ICON_NAME, ao->client_name);
(void)pa_proplist_sets(proplist, PA_PROP_MEDIA_ICON_NAME,
PULSE_CLIENT_NAME);
pa_format_info_set_rate(format, ao->samplerate);
pa_format_info_set_channels(format, ao->channels.num);
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,11 +358,10 @@ 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,
@@ -435,28 +376,26 @@ static int init(struct ao *ao)
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);
@@ -530,7 +469,7 @@ static int get_space(struct ao *ao)
return space / ao->sstride;
}
static double get_delay_hackfixed(struct ao *ao)
static float get_delay_hackfixed(struct ao *ao)
{
/* This code basically does what pa_stream_get_latency() _should_
* do, but doesn't due to multiple known bugs in PulseAudio (at
@@ -585,7 +524,7 @@ static double get_delay_hackfixed(struct ao *ao)
return latency / 1e6;
}
static double get_delay_pulse(struct ao *ao)
static float get_delay_pulse(struct ao *ao)
{
struct priv *priv = ao->priv;
pa_usec_t latency = (pa_usec_t) -1;
@@ -603,7 +542,7 @@ static double get_delay_pulse(struct ao *ao)
}
// Return the current latency in seconds
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
struct priv *priv = ao->priv;
if (priv->cfg_latency_hacks) {
@@ -642,7 +581,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 +644,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 +661,6 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
}
}
struct sink_cb_ctx {
struct ao *ao;
struct ao_device_list *list;
};
static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *ud)
{
struct sink_cb_ctx *ctx = ud;
struct priv *priv = ctx->ao->priv;
if (eol) {
pa_threaded_mainloop_signal(priv->mainloop, 0); // wakeup waitop()
return;
}
struct ao_device_desc entry = {.name = i->name, .desc = i->description};
ao_device_list_add(ctx->list, ctx->ao, &entry);
}
static void list_devs(struct ao *ao, struct ao_device_list *list)
{
struct priv *priv = ao->priv;
bool need_uninit = !priv->mainloop;
struct sink_cb_ctx ctx = {ao, list};
if (need_uninit && pa_init_boilerplate(ao) < 0)
return;
pa_threaded_mainloop_lock(priv->mainloop);
waitop(priv, pa_context_get_sink_info_list(priv->context, sink_info_cb, &ctx));
if (need_uninit)
uninit(ao);
}
#define OPT_BASE_STRUCT struct priv
const struct ao_driver audio_out_pulse = {
@@ -778,7 +678,6 @@ const struct ao_driver audio_out_pulse = {
.drain = drain,
.wait = wait_audio,
.wakeup = wakeup,
.list_devs = list_devs,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.cfg_buffer = 250,
@@ -787,7 +686,7 @@ const struct ao_driver audio_out_pulse = {
.options = (const struct m_option[]) {
OPT_STRING("host", cfg_host, 0),
OPT_STRING("sink", cfg_sink, 0),
OPT_CHOICE_OR_INT("buffer", cfg_buffer, 0, 1, 2000, ({"native", 0})),
OPT_CHOICE_OR_INT("buffer", cfg_buffer, 0, 1, 2000, ({"native", -1})),
OPT_FLAG("latency-hacks", cfg_latency_hacks, 0),
{0}
},
+29 -15
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+47 -65
View File
@@ -36,6 +36,11 @@
#include "osdep/timer.h"
#include "osdep/io.h"
#define EXIT_ON_ERROR(hres) \
do { if (FAILED(hres)) { goto exit_label; } } while(0)
#define SAFE_RELEASE(unk, release) \
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
static double get_device_delay(struct wasapi_state *state) {
UINT64 sample_count = atomic_load(&state->sample_count);
UINT64 position, qpc_position;
@@ -45,8 +50,7 @@ static double get_device_delay(struct wasapi_state *state) {
case S_OK: case S_FALSE:
break;
default:
MP_ERR(state, "IAudioClock::GetPosition returned %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
MP_ERR(state, "IAudioClock::GetPosition returned %s\n", wasapi_explain_err(hr));
}
LARGE_INTEGER qpc_count;
@@ -61,7 +65,7 @@ static double get_device_delay(struct wasapi_state *state) {
double diff = sample_count - position;
double delay = diff / state->format.Format.nSamplesPerSec;
MP_TRACE(state, "Device delay: %g samples (%g ms)\n", diff, delay * 1000);
MP_TRACE(state, "device delay: %g samples (%g ms)\n", diff, delay * 1000);
return delay;
}
@@ -79,7 +83,6 @@ static void thread_feed(struct ao *ao)
EXIT_ON_ERROR(hr);
frame_count -= padding;
MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n", frame_count, padding);
}
BYTE *pData;
@@ -100,10 +103,7 @@ static void thread_feed(struct ao *ao)
return;
exit_label:
MP_ERR(state, "Error feeding audio: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
MP_VERBOSE(ao, "Requesting ao reload\n");
ao_request_reload(ao);
MP_ERR(state, "thread_feed fails with %"PRIx32"!\n", (uint32_t)hr);
return;
}
@@ -113,27 +113,20 @@ static DWORD __stdcall ThreadLoop(void *lpParameter)
if (!ao || !ao->priv)
return -1;
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
int thread_ret;
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
state->init_ret = wasapi_thread_init(ao);
SetEvent(state->init_done);
if (state->init_ret != S_OK) {
thread_ret = -1;
if (wasapi_thread_init(ao))
goto exit_label;
}
DWORD waitstatus;
MSG msg;
DWORD waitstatus = WAIT_FAILED;
HANDLE playcontrol[] =
{state->hUninit, state->hFeed, state->hForceFeed, NULL};
MP_DBG(ao, "Entering dispatch loop\n");
MP_VERBOSE(ao, "Entering dispatch loop!\n");
while (1) { /* watch events */
waitstatus = MsgWaitForMultipleObjects(3, playcontrol, FALSE, INFINITE,
QS_POSTMESSAGE | QS_SENDMESSAGE);
switch (waitstatus) {
case WAIT_OBJECT_0: /*shutdown*/
thread_ret = 0;
wasapi_thread_uninit(state);
goto exit_label;
case (WAIT_OBJECT_0 + 1): /* feed */
thread_feed(ao);
@@ -143,52 +136,51 @@ static DWORD __stdcall ThreadLoop(void *lpParameter)
SetEvent(state->hFeedDone);
break;
case (WAIT_OBJECT_0 + 3): /* messages to dispatch (COM marshalling) */
wasapi_dispatch();
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
DispatchMessage(&msg);
}
break;
default:
MP_ERR(ao, "Unhandled case in thread loop");
thread_ret = -1;
goto exit_label;
case WAIT_FAILED: /* ??? */
return -1;
}
}
exit_label:
wasapi_thread_uninit(ao);
CoUninitialize();
return thread_ret;
/* dispatch any possible pending messages */
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
DispatchMessage(&msg);
}
return state->init_ret;
}
static void closehandles(struct ao *ao)
{
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
if (state->init_done) CloseHandle(state->init_done);
if (state->hUninit) CloseHandle(state->hUninit);
if (state->hFeed) CloseHandle(state->hFeed);
if (state->hForceFeed) CloseHandle(state->hForceFeed);
if (state->hFeedDone) CloseHandle(state->hFeedDone);
if (state->threadLoop) CloseHandle(state->threadLoop);
if (state->init_done)
CloseHandle(state->init_done);
if (state->hUninit)
CloseHandle(state->hUninit);
if (state->hFeed)
CloseHandle(state->hFeed);
}
static void uninit(struct ao *ao)
{
MP_DBG(ao, "Uninit wasapi\n");
MP_VERBOSE(ao, "uninit!\n");
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
wasapi_release_proxies(state);
SetEvent(state->hUninit);
/* wait up to 10 seconds */
if (WaitForSingleObject(state->threadLoop, 10000) == WAIT_TIMEOUT){
MP_ERR(ao, "Audio loop thread refuses to abort");
return;
}
if (WaitForSingleObject(state->threadLoop, 10000) == WAIT_TIMEOUT)
MP_ERR(ao, "audio loop thread refuses to abort!");
if (state->VistaBlob.hAvrt)
FreeLibrary(state->VistaBlob.hAvrt);
closehandles(ao);
MP_DBG(ao, "Uninit wasapi done\n");
MP_VERBOSE(ao, "uninit END!\n");
}
static int init(struct ao *ao)
{
MP_DBG(ao, "Init wasapi\n");
MP_VERBOSE(ao, "init!\n");
ao->format = af_fmt_from_planar(ao->format);
struct mp_chmap_sel sel = {0};
mp_chmap_sel_add_waveext(&sel);
@@ -199,9 +191,10 @@ static int init(struct ao *ao)
wasapi_fill_VistaBlob(state);
if (state->opt_list) {
wasapi_enumerate_devices(state->log, NULL, NULL);
wasapi_enumerate_devices(state->log);
}
ao->per_application_mixer = true;
if (state->opt_exclusive) {
state->share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
} else {
@@ -220,26 +213,23 @@ static int init(struct ao *ao)
/* failed to init events */
return -1;
}
state->init_ret = E_FAIL;
state->init_ret = -1;
state->threadLoop = (HANDLE)CreateThread(NULL, 0, &ThreadLoop, ao, 0, NULL);
if (!state->threadLoop) {
/* failed to init thread */
MP_ERR(ao, "Failed to create thread\n");
MP_ERR(ao, "fail to create thread!\n");
return -1;
}
WaitForSingleObject(state->init_done, INFINITE); /* wait on init complete */
if (state->init_ret != S_OK) {
if (!ao->probing)
MP_ERR(ao, "Received failure from audio thread\n");
uninit(ao);
return -1;
}
if (state->init_ret) {
if (!ao->probing) {
MP_ERR(ao, "thread_init failed!\n");
}
} else
MP_VERBOSE(ao, "Init Done!\n");
wasapi_setup_proxies(state);
MP_DBG(ao, "Init wasapi done\n");
return 0;
return state->init_ret;
}
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
@@ -260,7 +250,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
/* check to see if user manually changed volume through mixer;
this information is used in exclusive mode for restoring the mixer volume on uninit */
if (state->audio_volume != state->previous_volume) {
MP_VERBOSE(state, "Mixer difference: %.2g now, expected %.2g\n",
MP_VERBOSE(state, "mixer difference: %.2g now, expected %.2g\n",
state->audio_volume, state->previous_volume);
state->initial_volume = state->audio_volume;
}
@@ -294,8 +284,6 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
ISimpleAudioVolume_SetMute(state->pAudioVolumeProxy, mute, NULL);
return CONTROL_OK;
case AOCONTROL_HAS_PER_APP_VOLUME:
return CONTROL_TRUE;
case AOCONTROL_UPDATE_STREAM_TITLE: {
MP_VERBOSE(state, "Updating stream title to \"%s\"\n", (char*)arg);
wchar_t *title = mp_from_utf8(NULL, (char*)arg);
@@ -340,11 +328,6 @@ static void audio_resume(struct ao *ao)
IAudioClient_Start(state->pAudioClientProxy);
}
static void list_devs(struct ao *ao, struct ao_device_list *list)
{
wasapi_enumerate_devices(mp_null_log, ao, list);
}
#define OPT_BASE_STRUCT struct wasapi_state
const struct ao_driver audio_out_wasapi = {
@@ -353,9 +336,8 @@ const struct ao_driver audio_out_wasapi = {
.init = init,
.uninit = uninit,
.control = control,
.reset = audio_reset,
//.reset = audio_reset, <- doesn't wait for audio callback to return
.resume = audio_resume,
.list_devs = list_devs,
.priv_size = sizeof(wasapi_state),
.options = (const struct m_option[]) {
OPT_FLAG("exclusive", opt_exclusive, 0),
+6 -20
View File
@@ -28,28 +28,14 @@
#include <mmdeviceapi.h>
#include <avrt.h>
#include "osdep/atomics.h"
typedef struct change_notify {
IMMNotificationClient client; /* this must be first in the structure! */
LPWSTR monitored; /* Monitored device */
struct ao *ao;
} change_notify;
HRESULT wasapi_change_init(struct ao* ao);
void wasapi_change_uninit(struct ao* ao);
#define EXIT_ON_ERROR(hres) \
do { if (FAILED(hres)) { goto exit_label; } } while(0)
#define SAFE_RELEASE(unk, release) \
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
#include "compat/atomics.h"
typedef struct wasapi_state {
struct mp_log *log;
HANDLE threadLoop;
/* Init phase */
HRESULT init_ret;
int init_ret;
HANDLE init_done;
int share_mode;
@@ -63,6 +49,10 @@ typedef struct wasapi_state {
/* Buffers */
size_t buffer_block_size; /* Size of each block in bytes */
REFERENCE_TIME
minRequestedDuration; /* minimum wasapi buffer block size, in 100-nanosecond units */
REFERENCE_TIME
defaultRequestedDuration; /* default wasapi default block size, in 100-nanosecond units */
UINT32 bufferFrameCount; /* wasapi buffer block size, number of frames, frame size at format.nBlockAlign */
/* WASAPI handles, owned by other thread */
@@ -72,8 +62,6 @@ typedef struct wasapi_state {
ISimpleAudioVolume *pAudioVolume;
IAudioEndpointVolume *pEndpointVolume;
IAudioSessionControl *pSessionControl;
IMMDeviceEnumerator *pEnumerator;
HANDLE hFeed; /* wasapi event */
HANDLE hForceFeed; /* forces writing a buffer (e.g. before audio_resume) */
HANDLE hFeedDone; /* set only after a hForceFeed */
@@ -112,8 +100,6 @@ typedef struct wasapi_state {
HANDLE (WINAPI *pAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
WINBOOL (WINAPI *pAvRevertMmThreadCharacteristics)(HANDLE);
} VistaBlob;
change_notify change;
} wasapi_state;
#endif
-284
View File
@@ -1,284 +0,0 @@
/*
* This file is part of mpv.
*
* Original author: Jonathan Yong <10walls@gmail.com>
*
* mpv is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#define COBJMACROS 1
#define _WIN32_WINNT 0x600
#include <initguid.h>
#include <audioclient.h>
#include <endpointvolume.h>
#include <mmdeviceapi.h>
#include <wchar.h>
#include <stdlib.h>
#include "ao_wasapi.h"
#include "ao_wasapi_utils.h"
static int GUID_compare(const GUID *l, const GUID *r)
{
unsigned int i;
if (l->Data1 != r->Data1) return 1;
if (l->Data2 != r->Data2) return 1;
if (l->Data3 != r->Data3) return 1;
for (i = 0; i < 8; i++) {
if (l->Data4[i] != r->Data4[i]) return 1;
}
return 0;
}
static int PKEY_compare(const PROPERTYKEY *l, const PROPERTYKEY *r)
{
if (GUID_compare(&l->fmtid, &r->fmtid)) return 1;
if (l->pid != r->pid) return 1;
return 0;
}
static char *GUID_to_str_buf(char *buf, size_t buf_size, const GUID *guid)
{
snprintf(buf, buf_size,
"{%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x}",
(unsigned) guid->Data1, guid->Data2, guid->Data3,
guid->Data4[0], guid->Data4[1],
guid->Data4[2], guid->Data4[3],
guid->Data4[4], guid->Data4[5],
guid->Data4[6], guid->Data4[7]);
return buf;
}
static char *PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey)
{
buf = GUID_to_str_buf(buf, buf_size, &pkey->fmtid);
size_t guid_len = strnlen(buf, buf_size);
snprintf(buf + guid_len, buf_size - guid_len, ",%"PRIu32, (uint32_t)pkey->pid );
return buf;
}
#define PKEY_to_str(pkey) PKEY_to_str_buf((char[42]){0}, 42, (pkey))
static char* ERole_to_str(ERole role)
{
switch(role){
case eConsole: return "console";
case eMultimedia: return "multimedia";
case eCommunications: return "communications";
default: return "<Unknown>";
}
}
static char* EDataFlow_to_str(EDataFlow flow)
{
switch(flow){
case eRender: return "render";
case eCapture: return "capture";
case eAll: return "all";
default: return "<Unknown>";
}
}
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_QueryInterface(
IMMNotificationClient* This, REFIID riid, void **ppvObject)
{
/* Compatible with IMMNotificationClient and IUnknown */
if (!GUID_compare(&IID_IMMNotificationClient, riid) ||
!GUID_compare(&IID_IUnknown, riid)) {
*ppvObject = (void *)This;
return S_OK;
} else {
*ppvObject = NULL;
return E_NOINTERFACE;
}
}
/* these are required, but not actually used */
static ULONG STDMETHODCALLTYPE sIMMNotificationClient_AddRef(
IMMNotificationClient *This)
{
return 1;
}
/* MSDN says it should free itself, but we're static */
static ULONG STDMETHODCALLTYPE sIMMNotificationClient_Release(
IMMNotificationClient *This)
{
return 1;
}
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceStateChanged(
IMMNotificationClient *This,
LPCWSTR pwstrDeviceId,
DWORD dwNewState)
{
change_notify *change = (change_notify *)This;
struct ao *ao = change->ao;
if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)){
switch (dwNewState) {
case DEVICE_STATE_DISABLED:
case DEVICE_STATE_NOTPRESENT:
case DEVICE_STATE_UNPLUGGED:
MP_VERBOSE(ao,
"OnDeviceStateChange triggered - requesting ao reload\n");
ao_request_reload(ao);
case DEVICE_STATE_ACTIVE:
default:
return S_OK;
}
}
return S_OK;
}
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceAdded(
IMMNotificationClient *This,
LPCWSTR pwstrDeviceId)
{
change_notify *change = (change_notify *)This;
struct ao *ao = change->ao;
MP_VERBOSE(ao, "OnDeviceAdded triggered\n");
if(pwstrDeviceId)
MP_VERBOSE(ao, "New device %S\n",pwstrDeviceId);
return S_OK;
}
/* maybe MPV can go over to the prefered device once it is plugged in? */
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceRemoved(
IMMNotificationClient *This,
LPCWSTR pwstrDeviceId)
{
change_notify *change = (change_notify *)This;
struct ao *ao = change->ao;
if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)) {
MP_VERBOSE(ao, "OnDeviceRemoved triggered - requesting ao reload\n");
ao_request_reload(ao);
}
return S_OK;
}
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDefaultDeviceChanged(
IMMNotificationClient *This,
EDataFlow flow,
ERole role,
LPCWSTR pwstrDeviceId)
{
change_notify *change = (change_notify *)This;
struct ao *ao = change->ao;
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered for role:%s flow:%s\n",
ERole_to_str(role), EDataFlow_to_str(flow));
if(pwstrDeviceId)
MP_VERBOSE(ao, "New default device %S\n", pwstrDeviceId);
/* don't care about "eCapture" or non-"eMultimedia" roles */
if ( flow == eCapture ||
role != eMultimedia ) return S_OK;
/* stay on the device the user specified */
if (state->opt_device) {
MP_VERBOSE(ao, "Staying on specified device \"%s\"", state->opt_device);
return S_OK;
}
/* don't reload if already on the new default */
if ( pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId) ){
MP_VERBOSE(ao, "Already using default device, no reload required\n");
return S_OK;
}
/* if we got here, we need to reload */
ao_request_reload(ao);
MP_VERBOSE(ao, "Requesting ao reload\n");
return S_OK;
}
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnPropertyValueChanged(
IMMNotificationClient *This,
LPCWSTR pwstrDeviceId,
const PROPERTYKEY key)
{
change_notify *change = (change_notify *)This;
struct ao *ao = change->ao;
if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)) {
MP_VERBOSE(ao, "OnPropertyValueChanged triggered\n");
MP_VERBOSE(ao, "Changed property: ");
if (!PKEY_compare(&PKEY_AudioEngine_DeviceFormat, &key)) {
MP_VERBOSE(change->ao,
"PKEY_AudioEngine_DeviceFormat - requesting ao reload\n");
ao_request_reload(change->ao);
} else {
MP_VERBOSE(ao, "%s\n", PKEY_to_str(&key));
}
}
return S_OK;
}
static CONST_VTBL IMMNotificationClientVtbl sIMMDeviceEnumeratorVtbl_vtbl = {
.QueryInterface = sIMMNotificationClient_QueryInterface,
.AddRef = sIMMNotificationClient_AddRef,
.Release = sIMMNotificationClient_Release,
.OnDeviceStateChanged = sIMMNotificationClient_OnDeviceStateChanged,
.OnDeviceAdded = sIMMNotificationClient_OnDeviceAdded,
.OnDeviceRemoved = sIMMNotificationClient_OnDeviceRemoved,
.OnDefaultDeviceChanged = sIMMNotificationClient_OnDefaultDeviceChanged,
.OnPropertyValueChanged = sIMMNotificationClient_OnPropertyValueChanged,
};
HRESULT wasapi_change_init(struct ao *ao)
{
MP_DBG(ao, "Setting up monitoring on playback device\n");
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
struct change_notify *change = &state->change;
/* COM voodoo to emulate c++ class */
change->client.lpVtbl = &sIMMDeviceEnumeratorVtbl_vtbl;
/* so the callbacks can access the ao */
change->ao = ao;
/* get the device string to compare with the pwstrDeviceId argument in callbacks */
HRESULT hr = IMMDevice_GetId(state->pDevice, &change->monitored);
EXIT_ON_ERROR(hr);
/* register the change notification client */
hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(
state->pEnumerator, (IMMNotificationClient *)change);
EXIT_ON_ERROR(hr);
MP_VERBOSE(state, "Monitoring changes in device: %S\n", state->change.monitored);
return hr;
exit_label:
MP_ERR(state, "Error setting up device change monitoring: %s\n",
wasapi_explain_err(hr));
wasapi_change_uninit(ao);
return hr;
}
void wasapi_change_uninit(struct ao *ao)
{
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
struct change_notify *change = &state->change;
if( state->pEnumerator && change->client.lpVtbl )
IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(
state->pEnumerator, (IMMNotificationClient *)change);
if (change->monitored) CoTaskMemFree(change->monitored);
}
+157 -253
View File
@@ -20,7 +20,6 @@
#define COBJMACROS 1
#define _WIN32_WINNT 0x600
#include <math.h>
#include <initguid.h>
#include <audioclient.h>
#include <endpointvolume.h>
@@ -31,10 +30,14 @@
#include "audio/format.h"
#include "osdep/io.h"
#include "osdep/timer.h"
#define MIXER_DEFAULT_LABEL L"mpv - video player"
#define EXIT_ON_ERROR(hres) \
do { if (FAILED(hres)) { goto exit_label; } } while(0)
#define SAFE_RELEASE(unk, release) \
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
#ifndef PKEY_Device_FriendlyName
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName,
0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20,
@@ -82,10 +85,6 @@ const char *wasapi_explain_err(const HRESULT hr)
#define E(x) case x : return # x ;
switch (hr) {
E(S_OK)
E(E_FAIL)
E(E_OUTOFMEMORY)
E(E_POINTER)
E(E_INVALIDARG)
E(AUDCLNT_E_NOT_INITIALIZED)
E(AUDCLNT_E_ALREADY_INITIALIZED)
E(AUDCLNT_E_WRONG_ENDPOINT_TYPE)
@@ -143,35 +142,46 @@ static void set_format(WAVEFORMATEXTENSIBLE *wformat, WORD bytepersample,
wformat->dwChannelMask = chanmask;
}
static int format_set_bits(int old_format, int bits, int fp)
{
if (fp) {
switch (bits) {
case 64: return AF_FORMAT_DOUBLE;
case 32: return AF_FORMAT_FLOAT;
default: return 0;
}
static int format_set_bits(int old_format, int bits, int fp) {
int format = old_format;
format &= (~AF_FORMAT_BITS_MASK) & (~AF_FORMAT_POINT_MASK) & (~AF_FORMAT_SIGN_MASK);
format |= AF_FORMAT_SI;
switch (bits) {
case 32:
format |= AF_FORMAT_32BIT;
break;
case 24:
format |= AF_FORMAT_24BIT;
break;
case 16:
format |= AF_FORMAT_16BIT;
break;
default:
abort(); // (should be) unreachable
}
return af_fmt_change_bits(old_format, bits);
if (fp) {
format |= AF_FORMAT_F;
} else {
format |= AF_FORMAT_I;
}
return format;
}
static int set_ao_format(struct wasapi_state *state,
struct ao *const ao,
WAVEFORMATEXTENSIBLE wformat)
{
if (wformat.SubFormat.Data1 != 1 && wformat.SubFormat.Data1 != 3) {
MP_ERR(ao, "Unknown SubFormat %"PRIu32"\n",
(uint32_t)wformat.SubFormat.Data1);
return 0;
}
WAVEFORMATEXTENSIBLE wformat) {
// .Data1 == 1 is PCM, .Data1 == 3 is IEEE_FLOAT
int format = format_set_bits(ao->format,
wformat.Format.wBitsPerSample, wformat.SubFormat.Data1 == 3);
if (!format)
if (wformat.SubFormat.Data1 != 1 && wformat.SubFormat.Data1 != 3) {
MP_ERR(ao, "unknown SubFormat %"PRIu32"\n",
(uint32_t)wformat.SubFormat.Data1);
return 0;
}
ao->samplerate = wformat.Format.nSamplesPerSec;
ao->bps = wformat.Format.nAvgBytesPerSec;
@@ -194,10 +204,8 @@ static int try_format(struct wasapi_state *state,
set_format(&wformat, bits / 8, samplerate, channels.num, mp_chmap_to_waveext(&channels));
int af_format = format_set_bits(ao->format, bits, bits == 32);
if (!af_format)
return 0;
MP_VERBOSE(ao, "Trying %dch %s @ %dhz\n",
MP_VERBOSE(ao, "trying %dch %s @ %dhz\n",
channels.num, af_fmt_to_str(af_format), samplerate);
union WAVEFMT u;
@@ -221,7 +229,7 @@ static int try_format(struct wasapi_state *state,
if (hr == S_FALSE) {
if (set_ao_format(state, ao, wformat)) {
MP_VERBOSE(ao, "Accepted as %dch %s @ %dhz\n",
MP_VERBOSE(ao, "accepted as %dch %s @ %dhz\n",
ao->channels.num, af_fmt_to_str(ao->format), ao->samplerate);
return 1;
@@ -284,13 +292,14 @@ static int try_passthrough(struct wasapi_state *state,
union WAVEFMT u;
u.extensible = &wformat;
MP_VERBOSE(ao, "Trying passthrough for %s...\n", af_fmt_to_str(ao->format));
MP_VERBOSE(ao, "trying passthrough for %s...\n",
af_fmt_to_str((ao->format&~AF_FORMAT_END_MASK) | AF_FORMAT_LE));
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
state->share_mode,
u.ex, NULL);
if (!FAILED(hr)) {
ao->format = ao->format;
ao->format = (ao->format&~AF_FORMAT_END_MASK) | AF_FORMAT_LE;
state->format = wformat;
return 1;
}
@@ -301,11 +310,11 @@ static int find_formats(struct ao *const ao)
{
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
if (AF_FORMAT_IS_IEC61937(ao->format)) {
if (AF_FORMAT_IS_IEC61937(ao->format) || ao->format == AF_FORMAT_MPEG2) {
if (try_passthrough(state, ao))
return 0;
MP_ERR(ao, "Couldn't use passthrough");
MP_ERR(ao, "couldn't use passthrough!");
if (!state->opt_exclusive)
MP_ERR(ao, " (try exclusive mode)");
MP_ERR(ao, "\n");
@@ -327,7 +336,7 @@ static int find_formats(struct ao *const ao)
return 0;
}
MP_WARN(ao, "Couldn't use default mix format\n");
MP_WARN(ao, "couldn't use default mix format!\n");
}
/* Exclusive mode, we have to guess. */
@@ -398,13 +407,13 @@ static int find_formats(struct ao *const ao)
bits = start_bits;
mp_chmap_from_channels(&ao->channels, 2);
} else {
MP_ERR(ao, "Couldn't find acceptable audio format\n");
MP_ERR(ao, "couldn't find acceptable audio format!\n");
return -1;
}
}
}
static HRESULT init_clock(struct wasapi_state *state) {
static int init_clock(struct wasapi_state *state) {
HRESULT hr;
hr = IAudioClient_GetService(state->pAudioClient,
@@ -418,17 +427,16 @@ static HRESULT init_clock(struct wasapi_state *state) {
atomic_store(&state->sample_count, 0);
MP_VERBOSE(state, "IAudioClock::GetFrequency gave a frequency of %"PRIu64".\n",
(uint64_t) state->clock_frequency);
MP_VERBOSE(state, "IAudioClock::GetFrequency gave a frequency of %"PRIu64".\n", (uint64_t) state->clock_frequency);
return S_OK;
return 0;
exit_label:
MP_ERR(state, "Error obtaining the audio device's timing: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
return hr;
MP_ERR(state, "init_clock failed with %s, unable to obtain the audio device's timing!\n",
wasapi_explain_err(hr));
return 1;
}
static HRESULT init_session_display(struct wasapi_state *state) {
static int init_session_display(struct wasapi_state *state) {
HRESULT hr;
wchar_t path[MAX_PATH+12] = {0};
@@ -445,59 +453,44 @@ static HRESULT init_session_display(struct wasapi_state *state) {
hr = IAudioSessionControl_SetIconPath(state->pSessionControl, path, NULL);
EXIT_ON_ERROR(hr);
return S_OK;
return 0;
exit_label:
MP_ERR(state, "Error setting audio session display name: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
return hr;
MP_ERR(state, "init_session_display failed with %s.\n",
wasapi_explain_err(hr));
return 1;
}
static HRESULT fix_format(struct ao *ao)
static int fix_format(struct wasapi_state *state)
{
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
HRESULT hr;
double offset = 0.5;
REFERENCE_TIME devicePeriod, bufferDuration, bufferPeriod;
MP_DBG(state, "IAudioClient::GetDevicePeriod\n");
hr = IAudioClient_GetDevicePeriod(state->pAudioClient,&devicePeriod, NULL);
MP_VERBOSE(state, "Device period: %.2g ms\n", (double) devicePeriod / 10000.0 );
/* integer multiple of device period close to 50ms */
bufferPeriod = bufferDuration = ceil( 50.0 * 10000.0 / devicePeriod ) * devicePeriod;
if (state->share_mode == AUDCLNT_SHAREMODE_SHARED)
bufferPeriod = 0;
/* handle unsupported buffer size */
/* hopefully this shouldn't happen because of the above integer device period */
/* http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875%28v=vs.85%29.aspx */
int retries=0;
/* cargo cult code to negotiate buffer block size, affected by hardware/drivers combinations,
gradually grow it to 10s, by 0.5s, consider failure if it still doesn't work
*/
hr = IAudioClient_GetDevicePeriod(state->pAudioClient,
&state->defaultRequestedDuration,
&state->minRequestedDuration);
reinit:
MP_DBG(state, "IAudioClient::Initialize\n");
hr = IAudioClient_Initialize(state->pAudioClient,
state->share_mode,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
bufferDuration,
bufferPeriod,
state->defaultRequestedDuration,
state->defaultRequestedDuration,
&(state->format.Format),
NULL);
/* something about buffer sizes on Win7 */
/* something about buffer sizes on Win7, fixme might loop forever */
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
MP_VERBOSE(state, "IAudioClient::Initialize negotiation failed with %s (0x%"PRIx32"), used %lld * 100ns\n",
wasapi_explain_err(hr), (uint32_t)hr, bufferDuration);
if (retries > 0) {
hr = E_FAIL;
EXIT_ON_ERROR(hr);
} else {
retries ++;
}
MP_VERBOSE(state, "IAudioClient::Initialize negotiation failed with %s, used %lld * 100ns\n",
wasapi_explain_err(hr), state->defaultRequestedDuration);
if (offset > 10.0)
goto exit_label; /* is 10 enough to break out of the loop?*/
IAudioClient_GetBufferSize(state->pAudioClient, &state->bufferFrameCount);
bufferPeriod = bufferDuration =
state->defaultRequestedDuration =
(REFERENCE_TIME)((10000.0 * 1000 / state->format.Format.nSamplesPerSec *
state->bufferFrameCount) + 0.5);
if (state->share_mode == AUDCLNT_SHAREMODE_SHARED)
bufferPeriod = 0;
state->bufferFrameCount) + offset);
offset += 0.5;
IAudioClient_Release(state->pAudioClient);
state->pAudioClient = NULL;
hr = IMMDeviceActivator_Activate(state->pDevice,
@@ -506,54 +499,40 @@ reinit:
goto reinit;
}
EXIT_ON_ERROR(hr);
MP_DBG(state, "IAudioClient::Initialize pRenderClient\n");
hr = IAudioClient_GetService(state->pAudioClient,
&IID_IAudioRenderClient,
(void **)&state->pRenderClient);
EXIT_ON_ERROR(hr);
MP_DBG(state, "IAudioClient::Initialize pAudioVolume\n");
hr = IAudioClient_GetService(state->pAudioClient,
&IID_ISimpleAudioVolume,
(void **) &state->pAudioVolume);
EXIT_ON_ERROR(hr);
MP_DBG(state, "IAudioClient::Initialize IAudioClient_SetEventHandle\n");
if (!state->hFeed)
goto exit_label;
hr = IAudioClient_SetEventHandle(state->pAudioClient, state->hFeed);
EXIT_ON_ERROR(hr);
MP_DBG(state, "IAudioClient::Initialize IAudioClient_GetBufferSize\n");
hr = IAudioClient_GetBufferSize(state->pAudioClient,
&state->bufferFrameCount);
EXIT_ON_ERROR(hr);
ao->device_buffer = state->bufferFrameCount;
state->buffer_block_size = state->format.Format.nChannels *
state->format.Format.wBitsPerSample / 8 *
state->bufferFrameCount;
bufferDuration =
(REFERENCE_TIME)((10000.0 * 1000 / state->format.Format.nSamplesPerSec *
state->bufferFrameCount) + 0.5);
MP_VERBOSE(state, "Buffer frame count: %"PRIu32" (%.2g ms)\n",
state->bufferFrameCount, (double) bufferDuration / 10000.0 );
hr = init_clock(state);
EXIT_ON_ERROR(hr);
hr = init_session_display(state);
EXIT_ON_ERROR(hr);
if (init_clock(state))
return 1;
if (init_session_display(state))
return 1;
state->hTask =
state->VistaBlob.pAvSetMmThreadCharacteristicsW(L"Pro Audio", &state->taskIndex);
MP_VERBOSE(state, "Format fixed. Using %lld byte buffer block size\n",
MP_VERBOSE(state, "fix_format OK, using %lld byte buffer block size!\n",
(long long) state->buffer_block_size);
return S_OK;
return 0;
exit_label:
MP_ERR(state, "Error initializing device: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
return hr;
MP_ERR(state, "fix_format fails with %s, failed to determine buffer block size!\n",
wasapi_explain_err(hr));
return 1;
}
static char* get_device_id(IMMDevice *pDevice) {
@@ -650,10 +629,8 @@ end:
return found;
}
// Warning: ao and list are NULL in the "--ao=wasapi:device=help" path!
static HRESULT enumerate_with_state(struct mp_log *log, struct ao *ao,
struct ao_device_list *list,
char *header, int status, int with_id)
static HRESULT enumerate_with_state(struct mp_log *log, char *header,
int status, int with_id)
{
HRESULT hr;
IMMDeviceEnumerator *pEnumerator = NULL;
@@ -666,7 +643,7 @@ static HRESULT enumerate_with_state(struct mp_log *log, struct ao *ao,
EXIT_ON_ERROR(hr);
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator,
eRender, eMultimedia,
eRender, eConsole,
&pDevice);
EXIT_ON_ERROR(hr);
@@ -701,12 +678,6 @@ static HRESULT enumerate_with_state(struct mp_log *log, struct ao *ao,
mp_info(log, "%s, ID: %s%s\n", name, id, mark);
}
if (ao) {
char *desc = talloc_asprintf(NULL, "%s, ID: %s%s", name, id, mark);
struct ao_device_desc e = {id, desc};
ao_device_list_add(list, ao, &e);
}
talloc_free(name);
talloc_free(id);
SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice));
@@ -714,8 +685,8 @@ static HRESULT enumerate_with_state(struct mp_log *log, struct ao *ao,
talloc_free(defid);
SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices));
SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator));
return hr;
return S_OK;
exit_label:
talloc_free(defid);
SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice));
@@ -724,50 +695,29 @@ exit_label:
return hr;
}
int wasapi_enumerate_devices(struct mp_log *log, struct ao *ao,
struct ao_device_list *list)
int wasapi_enumerate_devices(struct mp_log *log)
{
HRESULT hr;
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
hr = enumerate_with_state(log, ao, list, "Active devices:",
DEVICE_STATE_ACTIVE, 1);
hr = enumerate_with_state(log, "Active devices:", DEVICE_STATE_ACTIVE, 1);
EXIT_ON_ERROR(hr);
hr = enumerate_with_state(log, ao, list, "Unplugged devices:",
DEVICE_STATE_UNPLUGGED, 0);
hr = enumerate_with_state(log, "Unplugged devices:", DEVICE_STATE_UNPLUGGED, 0);
EXIT_ON_ERROR(hr);
CoUninitialize();
return 0;
exit_label:
mp_err(log, "Error enumerating devices: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
mp_err(log, "Error enumerating devices: HRESULT %08"PRIx32" \"%s\"\n",
(uint32_t)hr, wasapi_explain_err(hr));
CoUninitialize();
return 1;
}
static HRESULT load_default_device(struct ao *ao, IMMDeviceEnumerator* pEnumerator,
IMMDevice **ppDevice)
{
HRESULT hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator,
eRender, eMultimedia,
ppDevice);
EXIT_ON_ERROR(hr);
char *id = get_device_id(*ppDevice);
MP_VERBOSE(ao, "Default device ID: %s\n", id);
talloc_free(id);
return S_OK;
exit_label:
MP_ERR(ao , "Error loading default device: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
return hr;
}
static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumerator,
IMMDevice **ppDevice, char *search)
static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice,
char *search)
{
HRESULT hr;
IMMDeviceEnumerator *pEnumerator = NULL;
IMMDeviceCollection *pDevices = NULL;
IMMDevice *pTempDevice = NULL;
LPWSTR deviceID = NULL;
@@ -780,6 +730,10 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumera
devid = search;
}
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
&IID_IMMDeviceEnumerator, (void**)&pEnumerator);
EXIT_ON_ERROR(hr);
int search_err = 0;
if (devid == NULL) {
@@ -791,16 +745,16 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumera
IMMDeviceCollection_GetCount(pDevices, &count);
if (devno >= count) {
MP_ERR(ao, "No device #%d\n", devno);
MP_ERR(ao, "no device #%d!\n", devno);
} else {
MP_VERBOSE(ao, "Finding device #%d\n", devno);
MP_VERBOSE(ao, "finding device #%d\n", devno);
hr = IMMDeviceCollection_Item(pDevices, devno, &pTempDevice);
EXIT_ON_ERROR(hr);
hr = IMMDevice_GetId(pTempDevice, &deviceID);
EXIT_ON_ERROR(hr);
MP_VERBOSE(ao, "Found device #%d\n", devno);
MP_VERBOSE(ao, "found device #%d\n", devno);
}
} else {
hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eRender,
@@ -811,7 +765,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumera
int count;
IMMDeviceCollection_GetCount(pDevices, &count);
MP_VERBOSE(ao, "Finding device %s\n", devid);
MP_VERBOSE(ao, "finding device %s\n", devid);
IMMDevice *prevDevice = NULL;
@@ -829,7 +783,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumera
if (deviceID) {
char *name;
if (!search_err) {
MP_ERR(ao, "Multiple matching devices found\n");
MP_ERR(ao, "multiple matching devices found!\n");
name = get_device_name(prevDevice);
MP_ERR(ao, "%s\n", name);
talloc_free(name);
@@ -848,7 +802,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumera
}
if (deviceID == NULL) {
MP_ERR(ao, "Could not find device %s\n", devid);
MP_ERR(ao, "could not find device %s!\n", devid);
}
}
@@ -858,20 +812,19 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumera
if (deviceID == NULL || search_err) {
hr = E_NOTFOUND;
} else {
MP_VERBOSE(ao, "Loading device %S\n", deviceID);
MP_VERBOSE(ao, "loading device %S\n", deviceID);
hr = IMMDeviceEnumerator_GetDevice(pEnumerator, deviceID, ppDevice);
if (FAILED(hr)) {
MP_ERR(ao, "Could not load requested device\n");
MP_ERR(ao, "could not load requested device!\n");
}
}
exit_label:
SAFE_RELEASE(pTempDevice, IMMDevice_Release(pTempDevice));
SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices));
CoTaskMemFree(deviceID);
SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator));
return hr;
}
@@ -879,11 +832,11 @@ int wasapi_validate_device(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param)
{
if (bstr_equals0(param, "help")) {
wasapi_enumerate_devices(log, NULL, NULL);
wasapi_enumerate_devices(log);
return M_OPT_EXIT;
}
mp_dbg(log, "Validating device=%s\n", param.start);
mp_dbg(log, "validating device=%s\n", param.start);
char *end;
int devno = (int) strtol(param.start, &end, 10);
@@ -916,8 +869,8 @@ HRESULT wasapi_setup_proxies(struct wasapi_state *state) {
exit_label:
if (hr != S_OK) {
MP_ERR(state, "Error reading COM proxy: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
MP_ERR(state, "error reading COM proxy: %08x %s\n", (unsigned int)hr,
wasapi_explain_err(hr));
}
return hr;
}
@@ -952,129 +905,80 @@ exit_label:
return hr;
}
static void destroy_proxies(struct wasapi_state *state) {
SAFE_RELEASE(state->sAudioClient, IUnknown_Release(state->sAudioClient));
SAFE_RELEASE(state->sAudioVolume, IUnknown_Release(state->sAudioVolume));
SAFE_RELEASE(state->sEndpointVolume, IUnknown_Release(state->sEndpointVolume));
SAFE_RELEASE(state->sSessionControl, IUnknown_Release(state->sSessionControl));
}
void wasapi_dispatch(void)
{
/* dispatch any possible pending messages */
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
DispatchMessage(&msg);
}
HRESULT wasapi_thread_init(struct ao *ao)
int wasapi_thread_init(struct ao *ao)
{
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
HRESULT hr;
MP_DBG(ao, "Init wasapi thread\n");
int64_t retry_wait = 1;
retry:
state->initial_volume = -1.0;
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
&IID_IMMDeviceEnumerator, (void**)&state->pEnumerator);
EXIT_ON_ERROR(hr);
if (!state->opt_device) {
IMMDeviceEnumerator *pEnumerator;
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
&IID_IMMDeviceEnumerator, (void**)&pEnumerator);
EXIT_ON_ERROR(hr);
char *device = state->opt_device;
if (!device || !device[0])
device = ao->device;
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator,
eRender, eConsole,
&state->pDevice);
SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator));
if (!device || !device[0])
hr = load_default_device(ao, state->pEnumerator, &state->pDevice);
else
hr = find_and_load_device(ao, state->pEnumerator, &state->pDevice, device);
char *id = get_device_id(state->pDevice);
MP_VERBOSE(ao, "default device ID: %s\n", id);
talloc_free(id);
} else {
hr = find_and_load_device(ao, &state->pDevice, state->opt_device);
}
EXIT_ON_ERROR(hr);
char *name = get_device_name(state->pDevice);
MP_VERBOSE(ao, "Device loaded: %s\n", name);
MP_VERBOSE(ao, "device loaded: %s\n", name);
talloc_free(name);
MP_DBG(ao, "Activating pAudioClient interface\n");
hr = IMMDeviceActivator_Activate(state->pDevice, &IID_IAudioClient,
CLSCTX_ALL, NULL, (void **)&state->pAudioClient);
EXIT_ON_ERROR(hr);
MP_DBG(ao, "Activating pEndpointVolume interface\n");
hr = IMMDeviceActivator_Activate(state->pDevice, &IID_IAudioEndpointVolume,
CLSCTX_ALL, NULL,
(void **)&state->pEndpointVolume);
EXIT_ON_ERROR(hr);
IAudioEndpointVolume_QueryHardwareSupport(state->pEndpointVolume,
&state->vol_hw_support);
MP_DBG(ao, "Query hardware volume support\n");
hr = IAudioEndpointVolume_QueryHardwareSupport(state->pEndpointVolume,
&state->vol_hw_support);
if ( hr != S_OK )
MP_WARN(ao, "Query hardware volume control: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
state->init_ret = find_formats(ao); /* Probe support formats */
if (state->init_ret)
goto exit_label;
if (!fix_format(state)) { /* now that we're sure what format to use */
EXIT_ON_ERROR(create_proxies(state));
MP_DBG(ao, "Probing formats\n");
if (find_formats(ao)){
hr = E_FAIL;
EXIT_ON_ERROR(hr);
if (state->opt_exclusive)
IAudioEndpointVolume_GetMasterVolumeLevelScalar(state->pEndpointVolume,
&state->initial_volume);
else
ISimpleAudioVolume_GetMasterVolume(state->pAudioVolume,
&state->initial_volume);
state->previous_volume = state->initial_volume;
MP_VERBOSE(ao, "thread_init OK!\n");
SetEvent(state->init_done);
return state->init_ret;
}
MP_DBG(ao, "Fixing format\n");
hr = fix_format(ao);
if ( (hr == AUDCLNT_E_DEVICE_IN_USE ||
hr == AUDCLNT_E_DEVICE_INVALIDATED) &&
retry_wait <= 8 ) {
wasapi_thread_uninit(ao);
MP_WARN(ao, "Retrying in %"PRId64" us\n", retry_wait);
mp_sleep_us(retry_wait);
retry_wait *= 2;
goto retry;
}
EXIT_ON_ERROR(hr);
MP_DBG(ao, "Creating proxies\n");
hr = create_proxies(state);
EXIT_ON_ERROR(hr);
MP_DBG(ao, "Read volume levels\n");
if (state->opt_exclusive)
IAudioEndpointVolume_GetMasterVolumeLevelScalar(state->pEndpointVolume,
&state->initial_volume);
else
ISimpleAudioVolume_GetMasterVolume(state->pAudioVolume,
&state->initial_volume);
state->previous_volume = state->initial_volume;
wasapi_change_init(ao);
MP_DBG(ao, "Init wasapi thread done\n");
return S_OK;
exit_label:
MP_ERR(state, "Error setting up audio thread: %s (0x%"PRIx32")\n",
wasapi_explain_err(hr), (uint32_t)hr);
return hr;
state->init_ret = -1;
SetEvent(state->init_done);
return -1;
}
void wasapi_thread_uninit(struct ao *ao)
void wasapi_thread_uninit(wasapi_state *state)
{
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
wasapi_dispatch();
if (state->pAudioClient)
IAudioClient_Stop(state->pAudioClient);
wasapi_change_uninit(ao);
if (state->opt_exclusive &&
state->pEndpointVolume &&
state->initial_volume > 0 )
if (state->opt_exclusive)
IAudioEndpointVolume_SetMasterVolumeLevelScalar(state->pEndpointVolume,
state->initial_volume, NULL);
destroy_proxies(state);
SAFE_RELEASE(state->pRenderClient, IAudioRenderClient_Release(state->pRenderClient));
SAFE_RELEASE(state->pAudioClock, IAudioClock_Release(state->pAudioClock));
SAFE_RELEASE(state->pAudioVolume, ISimpleAudioVolume_Release(state->pAudioVolume));
@@ -1082,9 +986,9 @@ void wasapi_thread_uninit(struct ao *ao)
SAFE_RELEASE(state->pSessionControl, IAudioSessionControl_Release(state->pSessionControl));
SAFE_RELEASE(state->pAudioClient, IAudioClient_Release(state->pAudioClient));
SAFE_RELEASE(state->pDevice, IMMDevice_Release(state->pDevice));
SAFE_RELEASE(state->pEnumerator, IMMDeviceEnumerator_Release(state->pEnumerator));
if (state->hTask)
state->VistaBlob.pAvRevertMmThreadCharacteristics(state->hTask);
MP_DBG(ao, "Thread uninit done\n");
CoUninitialize();
ExitThread(0);
}
+3 -6
View File
@@ -31,16 +31,13 @@ int wasapi_fill_VistaBlob(wasapi_state *state);
const char *wasapi_explain_err(const HRESULT hr);
int wasapi_enumerate_devices(struct mp_log *log, struct ao *ao,
struct ao_device_list *list);
int wasapi_enumerate_devices(struct mp_log *log);
int wasapi_validate_device(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param);
void wasapi_dispatch(void);
HRESULT wasapi_thread_init(struct ao *ao);
void wasapi_thread_uninit(struct ao *ao);
int wasapi_thread_init(struct ao *ao);
void wasapi_thread_uninit(wasapi_state *state);
HRESULT wasapi_setup_proxies(wasapi_state *state);
void wasapi_release_proxies(wasapi_state *state);
+13 -36
View File
@@ -22,8 +22,16 @@
#include <stdbool.h>
#include <pthread.h>
#include "osdep/atomics.h"
#include "audio/out/ao.h"
#include "audio/chmap.h"
#include "audio/chmap_sel.h"
// Minimum buffer size in seconds.
#define MIN_BUFFER 0.2
// If ao_get_delay() reaches this value after ao_play() was called with the
// AOPLAY_FINAL_CHUNK flag set, the playback core expects that the audio has
// all been played.
#define AO_EOF_DELAY 0.05
/* global data used by ao.c and ao drivers */
struct ao {
@@ -36,6 +44,8 @@ struct ao {
int num_planes;
bool probing; // if true, don't fail loudly on init
bool untimed; // don't assume realtime playback
bool no_persistent_volume; // the AO does the equivalent of af_volume
bool per_application_mixer; // like above, but volume persists (per app)
int device_buffer; // device buffer in samples (guessed by
// common init code if not set by driver)
const struct ao_driver *api; // entrypoints to the wrapper (push.c/pull.c)
@@ -45,21 +55,7 @@ struct ao {
struct input_ctx *input_ctx;
struct mp_log *log; // Using e.g. "[ao/coreaudio]" as prefix
// The device as selected by the user, usually using ao_device_desc.name
// from an entry from the list returned by driver->list_devices. If the
// default device should be used, this is set to NULL.
char *device;
// Application name to report to the audio API.
char *client_name;
// Used during init: if init fails, redirect to this ao
char *redirect;
atomic_bool request_reload;
int buffer;
double def_buffer;
void *api_priv;
};
@@ -135,7 +131,7 @@ struct ao_driver {
// push based: see ao_play()
int (*play)(struct ao *ao, void **data, int samples, int flags);
// push based: see ao_get_delay()
double (*get_delay)(struct ao *ao);
float (*get_delay)(struct ao *ao);
// push based: block until all queued audio is played (optional)
void (*drain)(struct ao *ao);
// Optional. Return true if audio has stopped in any way.
@@ -155,20 +151,6 @@ struct ao_driver {
// In combination with wait(). Lock may or may not be held.
void (*wakeup)(struct ao *ao);
// Return the list of devices currently available in the system. Use
// ao_device_list_add() to add entries. The selected device will be set as
// ao->device (using ao_device_desc.name).
// Warning: the ao struct passed doesn't necessarily have ao_driver->init()
// called on it - in this case, ->uninit() won't be called either
// after this function. The idea is that list_devs can be called
// both when no audio or when audio is active. the latter can
// happen if the audio config change at runtime, and in this case
// we don't want to force a new connection to the audio server
// just to update the device list. For runtime updates, ->init()
// will have been called. In both cases, ao->priv is properly
// allocated. (Runtime updates are not used/supported yet.)
void (*list_devs)(struct ao *ao, struct ao_device_list *list);
// For option parsing (see vo.h)
int priv_size;
const void *priv_defaults;
@@ -189,9 +171,4 @@ bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
struct mp_chmap *map, int num);
// Add a deep copy of e to the list.
// Call from ao_driver->list_devs callback only.
void ao_device_list_add(struct ao_device_list *list, struct ao *ao,
struct ao_device_desc *e);
#endif
+28 -42
View File
@@ -30,7 +30,7 @@
#include "osdep/timer.h"
#include "osdep/threads.h"
#include "osdep/atomics.h"
#include "compat/atomics.h"
#include "misc/ring.h"
/*
@@ -43,11 +43,8 @@ enum {
// finished, but device is open)
AO_STATE_WAIT, // wait for callback to go into AO_STATE_NONE state
AO_STATE_PLAY, // play the buffer
AO_STATE_BUSY, // like AO_STATE_PLAY, but ao_read_data() is being called
};
#define IS_PLAYING(st) ((st) == AO_STATE_PLAY || (st) == AO_STATE_BUSY)
struct ao_pull_state {
// Be very careful with the order when accessing planes.
struct mp_ring *buffers[MP_NUM_CHANNELS];
@@ -59,21 +56,6 @@ struct ao_pull_state {
atomic_llong end_time_us;
};
static void set_state(struct ao *ao, int new_state)
{
struct ao_pull_state *p = ao->api_priv;
while (1) {
int old = atomic_load(&p->state);
if (old == AO_STATE_BUSY) {
// A spinlock, because some audio APIs don't want us to use mutexes.
mp_sleep_us(1);
continue;
}
if (atomic_compare_exchange_strong(&p->state, &old, new_state))
break;
}
}
static int get_space(struct ao *ao)
{
struct ao_pull_state *p = ao->api_priv;
@@ -97,10 +79,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
int r = mp_ring_write(p->buffers[n], data[n], write_bytes);
assert(r == write_bytes);
}
int state = atomic_load(&p->state);
if (!IS_PLAYING(state)) {
set_state(ao, AO_STATE_PLAY);
if (atomic_load(&p->state) != AO_STATE_PLAY) {
atomic_store(&p->state, AO_STATE_PLAY);
ao->driver->resume(ao);
}
@@ -121,13 +101,14 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
struct ao_pull_state *p = ao->api_priv;
int full_bytes = samples * ao->sstride;
bool need_wakeup = false;
int state = atomic_load(&p->state);
int bytes = 0;
// Play silence in states other than AO_STATE_PLAY.
if (!atomic_compare_exchange_strong(&p->state, &(int){AO_STATE_PLAY},
AO_STATE_BUSY))
if (state != AO_STATE_PLAY) {
if (state == AO_STATE_WAIT)
atomic_store(&p->state, AO_STATE_NONE);
goto end;
}
// Since the writer will write the first plane last, its buffered amount
// of data is the minimum amount across all planes.
@@ -143,16 +124,10 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
}
// Half of the buffer played -> request more.
need_wakeup = buffered_bytes - bytes <= mp_ring_size(p->buffers[0]) / 2;
// Should never fail.
atomic_compare_exchange_strong(&p->state, &(int){AO_STATE_BUSY}, AO_STATE_PLAY);
end:
if (need_wakeup)
if (buffered_bytes - bytes <= mp_ring_size(p->buffers[0]) / 2)
mp_input_wakeup_nolock(ao->input_ctx);
end:
// pad with silence (underflow/paused/eof)
for (int n = 0; n < ao->num_planes; n++)
af_fill_silence(data[n], full_bytes - bytes, ao->format);
@@ -172,7 +147,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
// it takes until the last sample in the buffer reaches the speakers. This is
// used for audio/video synchronization, so it's very important to implement
// this correctly.
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
struct ao_pull_state *p = ao->api_priv;
@@ -185,9 +160,19 @@ static double get_delay(struct ao *ao)
static void reset(struct ao *ao)
{
struct ao_pull_state *p = ao->api_priv;
if (ao->driver->reset)
if (ao->driver->reset) {
ao->driver->reset(ao); // assumes the audio callback thread is stopped
set_state(ao, AO_STATE_NONE);
atomic_store(&p->state, AO_STATE_NONE);
} else {
// The thread keeps running. Wait until the audio callback gets into
// a defined state where it won't touch the ringbuffer. We must do
// this, because emptying the ringbuffer is not an atomic operation.
if (atomic_load(&p->state) != AO_STATE_NONE) {
atomic_store(&p->state, AO_STATE_WAIT);
while (atomic_load(&p->state) != AO_STATE_NONE)
mp_sleep_us(1);
}
}
for (int n = 0; n < ao->num_planes; n++)
mp_ring_reset(p->buffers[n]);
atomic_store(&p->end_time_us, 0);
@@ -195,22 +180,23 @@ static void reset(struct ao *ao)
static void pause(struct ao *ao)
{
struct ao_pull_state *p = ao->api_priv;
if (ao->driver->reset)
ao->driver->reset(ao);
set_state(ao, AO_STATE_NONE);
atomic_store(&p->state, AO_STATE_NONE);
}
static void resume(struct ao *ao)
{
set_state(ao, AO_STATE_PLAY);
struct ao_pull_state *p = ao->api_priv;
atomic_store(&p->state, AO_STATE_PLAY);
ao->driver->resume(ao);
}
static void drain(struct ao *ao)
{
struct ao_pull_state *p = ao->api_priv;
int state = atomic_load(&p->state);
if (IS_PLAYING(state))
if (atomic_load(&p->state) == AO_STATE_PLAY)
mp_sleep_us(get_delay(ao) * 1000000);
reset(ao);
}
+98 -101
View File
@@ -35,7 +35,7 @@
#include "osdep/threads.h"
#include "osdep/timer.h"
#include "osdep/atomics.h"
#include "compat/atomics.h"
#include "audio/audio.h"
#include "audio/audio_buffer.h"
@@ -44,15 +44,19 @@ struct ao_push_state {
pthread_t thread;
pthread_mutex_t lock;
pthread_cond_t wakeup;
pthread_cond_t wakeup_drain;
// --- protected by lock
struct mp_audio_buffer *buffer;
bool terminate;
bool wait_on_ao;
bool drain;
bool buffers_full;
bool avoid_ao_wait;
bool still_playing;
bool need_wakeup;
bool requested_data;
bool paused;
// Whether the current buffer contains the complete audio.
@@ -90,14 +94,22 @@ static double unlocked_get_delay(struct ao *ao)
double driver_delay = 0;
if (ao->driver->get_delay)
driver_delay = ao->driver->get_delay(ao);
return driver_delay + mp_audio_buffer_seconds(p->buffer);
double delay = driver_delay + mp_audio_buffer_seconds(p->buffer);
if (delay >= AO_EOF_DELAY && p->expected_end_time) {
if (mp_time_sec() > p->expected_end_time) {
MP_ERR(ao, "Audio device EOF reporting is broken!\n");
MP_ERR(ao, "Please report this problem.\n");
delay = 0;
}
}
return delay;
}
static double get_delay(struct ao *ao)
static float get_delay(struct ao *ao)
{
struct ao_push_state *p = ao->api_priv;
pthread_mutex_lock(&p->lock);
double delay = unlocked_get_delay(ao);
float delay = unlocked_get_delay(ao);
pthread_mutex_unlock(&p->lock);
return delay;
}
@@ -110,9 +122,8 @@ static void reset(struct ao *ao)
ao->driver->reset(ao);
mp_audio_buffer_clear(p->buffer);
p->paused = false;
if (p->still_playing)
wakeup_playthread(ao);
p->still_playing = false;
wakeup_playthread(ao);
pthread_mutex_unlock(&p->lock);
}
@@ -143,27 +154,20 @@ static void drain(struct ao *ao)
{
struct ao_push_state *p = ao->api_priv;
MP_VERBOSE(ao, "draining...\n");
pthread_mutex_lock(&p->lock);
if (p->paused)
goto done;
p->final_chunk = true;
wakeup_playthread(ao);
while (p->still_playing && mp_audio_buffer_samples(p->buffer) > 0)
pthread_cond_wait(&p->wakeup, &p->lock);
if (ao->driver->drain) {
ao->driver->drain(ao);
} else {
double time = unlocked_get_delay(ao);
mp_sleep_us(MPMIN(time, ao->buffer / (double)ao->samplerate + 1) * 1e6);
if (p->paused) {
pthread_mutex_unlock(&p->lock);
return;
}
done:
p->final_chunk = true;
p->drain = true;
wakeup_playthread(ao);
while (p->drain)
pthread_cond_wait(&p->wakeup_drain, &p->lock);
pthread_mutex_unlock(&p->lock);
if (!ao->driver->drain)
mp_sleep_us(get_delay(ao) * 1000000);
reset(ao);
}
@@ -173,17 +177,13 @@ static int unlocked_get_space(struct ao *ao)
int space = mp_audio_buffer_get_write_available(p->buffer);
if (ao->driver->get_space) {
// The following code attempts to keep the total buffered audio to
// ao->buffer in order to improve latency.
// MIN_BUFFER/2+device_buffer in order to improve latency.
int device_space = ao->driver->get_space(ao);
int device_buffered = ao->device_buffer - device_space;
int soft_buffered = mp_audio_buffer_samples(p->buffer);
// The extra margin helps avoiding too many wakeups if the AO is fully
// byte based and doesn't do proper chunked processing.
int min_buffer = ao->buffer + 64;
int missing = min_buffer - device_buffered - soft_buffered;
// But always keep the device's buffer filled as much as we can.
int device_missing = device_space - soft_buffered;
missing = MPMAX(missing, device_missing);
int min_buffer = MIN_BUFFER / 2 * ao->samplerate + ao->device_buffer;
int total_buffer = device_buffered + soft_buffered;
int missing = min_buffer - total_buffer;
space = MPMIN(space, missing);
space = MPMAX(0, space);
}
@@ -217,8 +217,6 @@ static int play(struct ao *ao, void **data, int samples, int flags)
int write_samples = mp_audio_buffer_get_write_available(p->buffer);
write_samples = MPMIN(write_samples, samples);
MP_TRACE(ao, "samples=%d flags=%d r=%d\n", samples, flags, write_samples);
if (write_samples < samples)
flags = flags & ~AOPLAY_FINAL_CHUNK;
bool is_final = flags & AOPLAY_FINAL_CHUNK;
@@ -232,17 +230,17 @@ static int play(struct ao *ao, void **data, int samples, int flags)
bool got_data = write_samples > 0 || p->paused || p->final_chunk != is_final;
p->expected_end_time = 0;
p->final_chunk = is_final;
p->paused = false;
if (got_data) {
p->still_playing = true;
p->expected_end_time = 0;
}
p->still_playing |= write_samples > 0;
// If we don't have new data, the decoder thread basically promises it
// will send new data as soon as it's available.
if (got_data)
if (got_data) {
p->requested_data = false;
wakeup_playthread(ao);
}
pthread_mutex_unlock(&p->lock);
return write_samples;
}
@@ -272,46 +270,68 @@ static void ao_play_data(struct ao *ao)
}
r = MPMAX(r, 0);
// Probably can't copy the rest of the buffer due to period alignment.
bool stuck_eof = r <= 0 && space >= max && data.samples > 0;
if ((flags & AOPLAY_FINAL_CHUNK) && stuck_eof) {
bool stuck = r <= 0 && space >= max && data.samples > 0;
if ((flags & AOPLAY_FINAL_CHUNK) && stuck) {
MP_ERR(ao, "Audio output driver seems to ignore AOPLAY_FINAL_CHUNK.\n");
r = max;
}
mp_audio_buffer_skip(p->buffer, r);
if (r > 0)
p->expected_end_time = 0;
// Nothing written, but more input data than space - this must mean the
// AO's get_space() doesn't do period alignment correctly.
bool stuck = r == 0 && max >= space && space > 0;
if (stuck)
MP_ERR(ao, "Audio output is reporting incorrect buffer status.\n");
// Wait until space becomes available. Also wait if we actually wrote data,
// so the AO wakes us up properly if it needs more data.
p->wait_on_ao = space == 0 || r > 0 || stuck;
if (p->final_chunk && mp_audio_buffer_samples(p->buffer) == 0) {
p->expected_end_time = mp_time_sec() + AO_EOF_DELAY + 0.25; // + margin
if (ao->driver->get_delay)
p->expected_end_time += ao->driver->get_delay(ao);
}
// In both cases, we have to account for space!=0, but the AO not accepting
// any new data (due to rounding to period boundaries).
p->buffers_full = max >= space && r <= 0;
p->avoid_ao_wait = (max == 0 && space > 0) || p->paused || stuck;
p->still_playing |= r > 0;
// If we just filled the AO completely (r == space), don't refill for a
// while. Prevents wakeup feedback with byte-granular AOs.
int needed = unlocked_get_space(ao);
bool more = needed >= (r == space ? ao->device_buffer / 4 : 1) && !stuck;
if (more)
mp_input_wakeup(ao->input_ctx); // request more data
MP_TRACE(ao, "in=%d flags=%d space=%d r=%d wa=%d needed=%d more=%d\n",
max, flags, space, r, p->wait_on_ao, needed, more);
MP_TRACE(ao, "in=%d, space=%d r=%d flags=%d aw=%d full=%d f=%d\n", max,
space, r, flags, p->avoid_ao_wait, p->buffers_full, p->final_chunk);
}
// Estimate when the AO needs data again.
static double ao_estimate_timeout(struct ao *ao)
{
struct ao_push_state *p = ao->api_priv;
double timeout = 0;
if (p->buffers_full && ao->driver->get_delay) {
timeout = ao->driver->get_delay(ao) - 0.050;
// Keep extra safety margin if the buffers are large
if (timeout > 0.100)
timeout = MPMAX(timeout - 0.200, 0.100);
}
return MPMAX(timeout, ao->device_buffer * 0.75 / ao->samplerate);
}
static void *playthread(void *arg)
{
struct ao *ao = arg;
struct ao_push_state *p = ao->api_priv;
mpthread_set_name("ao");
pthread_mutex_lock(&p->lock);
while (!p->terminate) {
if (!p->paused)
ao_play_data(ao);
// Request new data from decoder if buffer goes below "full".
// Allow a small margin of missing data for AOs that use timeouts.
double margin = ao->driver->wait ? 0 : ao->device_buffer / 8;
if (!p->buffers_full && unlocked_get_space(ao) > margin) {
if (!p->requested_data)
mp_input_wakeup(ao->input_ctx);
p->requested_data = true;
}
if (p->drain && (p->avoid_ao_wait || p->paused)) {
if (ao->driver->drain)
ao->driver->drain(ao);
p->drain = false;
pthread_cond_signal(&p->wakeup_drain);
}
if (!p->need_wakeup) {
MP_STATS(ao, "start audio wait");
if (!p->wait_on_ao || p->paused) {
if (p->avoid_ao_wait || p->paused) {
// Avoid busy waiting, because the audio API will still report
// that it needs new data, even if we're not ready yet, or if
// get_space() decides that the amount of audio buffered in the
@@ -322,22 +342,14 @@ static void *playthread(void *arg)
bool was_playing = p->still_playing;
double timeout = -1;
if (p->still_playing && !p->paused && p->final_chunk &&
!mp_audio_buffer_samples(p->buffer))
{
double now = mp_time_sec();
if (!p->expected_end_time)
p->expected_end_time = now + unlocked_get_delay(ao);
if (p->expected_end_time < now) {
if (p->still_playing && !p->paused) {
timeout = unlocked_get_delay(ao);
if (timeout < AO_EOF_DELAY)
p->still_playing = false;
} else {
timeout = p->expected_end_time - now;
}
}
if (was_playing && !p->still_playing)
if (!p->requested_data || (was_playing && !p->still_playing))
mp_input_wakeup(ao->input_ctx);
pthread_cond_signal(&p->wakeup); // for draining
if (p->still_playing && timeout > 0) {
mpthread_cond_timedwait_rel(&p->wakeup, &p->lock, timeout);
@@ -345,15 +357,10 @@ static void *playthread(void *arg)
pthread_cond_wait(&p->wakeup, &p->lock);
}
} else {
// Wait until the device wants us to write more data to it.
if (!ao->driver->wait || ao->driver->wait(ao, &p->lock) < 0) {
// Fallback to guessing.
double timeout = 0;
if (ao->driver->get_delay)
timeout = ao->driver->get_delay(ao);
timeout *= 0.25; // wake up if 25% played
if (!p->need_wakeup)
mpthread_cond_timedwait_rel(&p->wakeup, &p->lock, timeout);
double timeout = ao_estimate_timeout(ao);
mpthread_cond_timedwait_rel(&p->wakeup, &p->lock, timeout);
}
}
MP_STATS(ao, "end audio wait");
@@ -364,19 +371,6 @@ static void *playthread(void *arg)
return NULL;
}
static void destroy_no_thread(struct ao *ao)
{
struct ao_push_state *p = ao->api_priv;
ao->driver->uninit(ao);
for (int n = 0; n < 2; n++)
close(p->wakeup_pipe[n]);
pthread_cond_destroy(&p->wakeup);
pthread_mutex_destroy(&p->lock);
}
static void uninit(struct ao *ao)
{
struct ao_push_state *p = ao->api_priv;
@@ -388,7 +382,14 @@ static void uninit(struct ao *ao)
pthread_join(p->thread, NULL);
destroy_no_thread(ao);
ao->driver->uninit(ao);
for (int n = 0; n < 2; n++)
close(p->wakeup_pipe[n]);
pthread_cond_destroy(&p->wakeup);
pthread_cond_destroy(&p->wakeup_drain);
pthread_mutex_destroy(&p->lock);
}
static int init(struct ao *ao)
@@ -397,13 +398,9 @@ static int init(struct ao *ao)
pthread_mutex_init(&p->lock, NULL);
pthread_cond_init(&p->wakeup, NULL);
pthread_cond_init(&p->wakeup_drain, NULL);
mp_make_wakeup_pipe(p->wakeup_pipe);
if (ao->device_buffer <= 0) {
MP_FATAL(ao, "Couldn't probe device buffer size.\n");
goto err;
}
p->buffer = mp_audio_buffer_create(ao);
mp_audio_buffer_reinit_fmt(p->buffer, ao->format,
&ao->channels, ao->samplerate);
@@ -412,7 +409,7 @@ static int init(struct ao *ao)
goto err;
return 0;
err:
destroy_no_thread(ao);
ao->driver->uninit(ao);
return -1;
}
@@ -464,7 +461,7 @@ int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds,
assert(ao->api == &ao_api_push);
assert(&p->lock == lock);
if (num_fds >= MAX_POLL_FDS || p->wakeup_pipe[0] < 0)
if (num_fds > MAX_POLL_FDS || p->wakeup_pipe[0] < 0)
return -1;
struct pollfd p_fds[MAX_POLL_FDS];
+2 -4
View File
@@ -5,9 +5,9 @@
from __future__ import print_function
import os, sys, stat, hashlib, subprocess
WAFRELEASE = "waf-1.8.1"
WAFRELEASE = "waf-1.7.16"
WAFURL = "http://ftp.waf.io/pub/release/" + WAFRELEASE
SHA256HASH = "ec658116ba0b96629d91fde0b32321849e866e0819f1e835c4c2c7f7ffe1a21d"
SHA256HASH = "b64dc26c882572415fd450b745006107965f3fe17b357e3eb43d6676c9635a61"
if os.path.exists("waf"):
wafver = subprocess.check_output(['./waf', '--version']).decode()
@@ -31,8 +31,6 @@ if SHA256HASH == hashlib.sha256(waf).hexdigest():
print("Checksum verified.")
else:
print("The checksum of the downloaded file does not match!")
print(" - got: {}".format(hashlib.sha256(waf).hexdigest()))
print(" - expected: {}".format(SHA256HASH))
print("Please download and verify the file manually.")
sys.exit(1)
+4 -10
View File
@@ -29,13 +29,11 @@
#include "common/common.h"
#include "misc/ctype.h"
#include "bstr.h"
#include "bstr/bstr.h"
int bstrcmp(struct bstr str1, struct bstr str2)
{
int ret = 0;
if (str1.len && str2.len)
ret = memcmp(str1.start, str2.start, FFMIN(str1.len, str2.len));
int ret = memcmp(str1.start, str2.start, FFMIN(str1.len, str2.len));
if (!ret) {
if (str1.len == str2.len)
@@ -50,9 +48,7 @@ int bstrcmp(struct bstr str1, struct bstr str2)
int bstrcasecmp(struct bstr str1, struct bstr str2)
{
int ret = 0;
if (str1.len && str2.len)
ret = strncasecmp(str1.start, str2.start, FFMIN(str1.len, str2.len));
int ret = strncasecmp(str1.start, str2.start, FFMIN(str1.len, str2.len));
if (!ret) {
if (str1.len == str2.len)
@@ -277,7 +273,7 @@ int bstr_decode_utf8(struct bstr s, struct bstr *out_next)
s.start++; s.len--;
if (codepoint >= 128) {
int bytes = bstr_parse_utf8_code_length(codepoint);
if (bytes < 1 || s.len < bytes - 1)
if (bytes < 0 || s.len < bytes - 1)
return -1;
codepoint &= 127 >> bytes;
for (int n = 1; n < bytes; n++) {
@@ -379,8 +375,6 @@ static void resize_append(void *talloc_ctx, bstr *s, size_t append_min)
// talloc_ctx will be used as parent context, if s->start is NULL.
void bstr_xappend(void *talloc_ctx, bstr *s, bstr append)
{
if (!append.len)
return;
resize_append(talloc_ctx, s, append.len + 1);
memcpy(s->start + s->len, append.start, append.len);
s->len += append.len;
Loaded 100 of 351 files, more files were not shown because too many files have changed in this diff. Show more