Compare commits

..
1 Commits
Author SHA1 Message Date
Alessandro Ghedini 88f247c0af Release 0.5.0 2014-08-12 10:50:38 +02:00
356 changed files with 11282 additions and 21211 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
-33
View File
@@ -25,39 +25,6 @@ API changes
::
--- mpv 0.7.0 is released ---
1.10 - deprecate/disable everything directly related to script_dispatch
(most likely affects nobody)
1.9 - add enum mpv_end_file_reason for mpv_event_end_file.reason
- add MPV_END_FILE_REASON_ERROR and the mpv_event_end_file.error field
for slightly better error reporting on playback failure
- add --stop-playback-on-init-failure option, and make it the default
behavior for libmpv only
- add qthelper.hpp set_option_variant()
- mark the following events as deprecated:
MPV_EVENT_TRACKS_CHANGED
MPV_EVENT_TRACK_SWITCHED
MPV_EVENT_PAUSE
MPV_EVENT_UNPAUSE
MPV_EVENT_METADATA_UPDATE
MPV_EVENT_CHAPTER_CHANGE
They are handled better with mpv_observe_property() as mentioned in
the documentation comments. They are not removed and still work.
1.8 - add qthelper.hpp
1.7 - add mpv_command_node(), mpv_command_node_async()
1.6 - modify "core-idle" property behavior
- MPV_EVENT_LOG_MESSAGE now always sends complete lines
- introduce numeric log levels (mpv_log_level)
--- mpv 0.6.0 is released ---
1.5 - change in X11 and "--wid" behavior again. The previous change didn't
work as expected, and now the behavior can be explicitly controlled
with the "input-x11-keyboard" option. This is only a temporary
measure until XEmbed is implemented and confirmed working.
Note: in 1.6, "input-x11-keyboard" was renamed to "input-vo-keyboard",
although the old option name still works.
1.4 - subtle change in X11 and "--wid" behavior
(this change was added to 0.5.2, and broke some things, see #1090)
--- mpv 0.5.0 is released ---
1.3 - add MPV_MAKE_VERSION()
1.2 - remove "stream-time-pos" property (no replacement)
1.1 - remap dvdnav:// to dvd://
-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
+19 -20
View File
@@ -72,17 +72,15 @@ Available filters are:
``tospdif=<yes|no>``
Output raw AC-3 stream if ``no``, output to S/PDIF for
pass-through if ``yes`` (default).
passthrough if ``yes`` (default).
``bitrate=<rate>``
The bitrate use for the AC-3 stream. Set it to 384 to get 384 kbps.
The default is 640. Some receivers might not be able to handle this.
Valid values: 32, 40, 48, 56, 64, 80, 96, 112, 128,
160, 192, 224, 256, 320, 384, 448, 512, 576, 640.
The special value ``auto`` selects a default bitrate based on the
The special value ``default`` selects a default bitrate based on the
input channel number:
:1ch: 96
@@ -94,7 +92,7 @@ Available filters are:
``minchn=<n>``
If the input channel number is less than ``<minchn>``, the filter will
detach itself (default: 3).
detach itself (default: 5).
``sweep[=speed]``
Produces a sine sweep.
@@ -103,7 +101,7 @@ Available filters are:
Sine function delta, use very low values to hear the sweep.
``sinesuppress[=freq:decay]``
Remove a sine at the specified frequency. Useful to get rid of the 50/60 Hz
Remove a sine at the specified frequency. Useful to get rid of the 50/60Hz
noise on low quality audio equipment. It only works on mono input.
``<freq>``
@@ -184,7 +182,7 @@ Available filters are:
``mpv --af=equalizer=11:11:10:5:0:-12:0:5:12:12 media.avi``
Would amplify the sound in the upper and lower frequency region
while canceling it almost completely around 1 kHz.
while canceling it almost completely around 1kHz.
``channels=nch[:routes]``
Can be used for adding, removing, routing and copying audio channels. If
@@ -265,8 +263,9 @@ Available filters are:
Filter for internal use only. Converts between 24-bit and 32-bit sample
formats.
``convertsign``
Filter for internal use only. Converts between signed/unsigned formats.
``convertsignendian``
Filter for internal use only. Converts between signed/unsigned formats
and formats with different endian.
``volume[=<volumedb>[:...]]``
Implements software volume control. Use this filter with caution since it
@@ -278,8 +277,8 @@ Available filters are:
once for every audio stream.
``<volumedb>``
Sets the desired gain in dB for all channels in the stream from -200 dB
to +60 dB, where -200 dB mutes the sound completely and +60 dB equals a
Sets the desired gain in dB for all channels in the stream from -200dB
to +60dB, where -200dB mutes the sound completely and +60dB equals a
gain of 1000 (default: 0).
``replaygain-track``
Adjust volume gain according to the track-gain replaygain value stored
@@ -312,7 +311,7 @@ Available filters are:
.. admonition:: Example
``mpv --af=volume=10.1 media.avi``
Would amplify the sound by 10.1 dB and hard-clip if the sound level
Would amplify the sound by 10.1dB and hard-clip if the sound level
is too high.
``pan=n:[<matrix>]``
@@ -369,8 +368,8 @@ Available filters are:
to the subwoofer.
``<fc>``
cutoff frequency in Hz for the low-pass filter (20 Hz to 300 Hz)
(default: 60 Hz) For the best result try setting the cutoff frequency
cutoff frequency in Hz for the low-pass filter (20Hz to 300Hz)
(default: 60Hz) For the best result try setting the cutoff frequency
as low as possible. This will improve the stereo or surround sound
experience.
``<ch>``
@@ -382,7 +381,7 @@ Available filters are:
.. admonition:: Example
``mpv --af=sub=100:4 --audio-channels=5 media.avi``
Would add a subwoofer channel with a cutoff frequency of 100 Hz to
Would add a subwoofer channel with a cutoff frequency of 100Hz to
output channel 4.
``center``
@@ -410,7 +409,7 @@ Available filters are:
.. admonition:: Example
``mpv --af=surround=15 --audio-channels=4 media.avi``
Would add surround sound decoding with 15 ms delay for the sound to
Would add surround sound decoding with 15ms delay for the sound to
the rear speakers.
``delay[=[ch1,ch2,...]]``
@@ -438,8 +437,8 @@ Available filters are:
.. admonition:: Example
``mpv --af=delay=[10.5,10.5,0,0,7,0] media.avi``
Would delay front left and right by 10.5 ms, the two rear channels
and the subwoofer by 0 ms and the center channel by 7 ms.
Would delay front left and right by 10.5ms, the two rear channels
and the subwoofer by 0ms and the center channel by 7ms.
``export=mmapped_file:nsamples]``
Exports the incoming signal to other processes using memory mapping
@@ -589,7 +588,7 @@ Available filters are:
tempo.
``mpv --af=scaletempo=stride=30:overlap=.50:search=10 media.ogg``
Would tweak the quality and performance parameters.
Would tweak the quality and performace parameters.
``mpv --af=format=float,scaletempo media.ogg``
Would make scaletempo use float code. Maybe faster on some
@@ -601,7 +600,7 @@ Available filters are:
1.2x.
``lavfi=graph``
Filter audio using FFmpeg's libavfilter.
Filter audio using ffmpeg's libavfilter.
``<graph>``
Libavfilter graph. See ``lavfi`` video filter for details - the graph
+12 -8
View File
@@ -23,7 +23,7 @@ normal driver parameters.
where PulseAudio is used. On Windows, ``--ao=wasapi`` is preferred,
though it might cause trouble sometimes, in which case ``--ao=dsound``
should be used. On BSD systems, ``--ao=oss`` or `--ao=sndio`` may work
(the latter being experimental). On OS X systems, use ``--ao=coreaudio``.
(the latter being experimental). On OSX systems, use ``--ao=coreaudio``.
.. admonition:: Examples
@@ -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 -149
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 buildsystem
* No embedded copy of ffmpeg and other libraries
* Better pause handling (do not unpause on a command)
* Better MKV support (such as ordered chapters)
* vo_vdpau improvements
* Precise seeking support
* Native OpenGL backend for OSX
* General OSX improvements
* Improvements in audio/video sync handling
* Cleaned up terminal output
* Gapless audio support (``--gapless-audio``)
* Improved responsiveness on user input
* Support for modifier keys (alt, shift, ctrl) in input.conf
* OSS4 volume control
* More correct color reproduction (color matrix generation)
* Use libass for subtitle rendering by default (better quality)
* Generally preferring ffmpeg/libav over internal demuxers and decoders
* Improvements when playing multiple files (``--fixed-vo``)
* Screenshot improvements (instant screenshots without 1-frame delay, allow
taking screenshots even with hardware decoding)
* Improved support for PulseAudio
* Generally improved MS Windows support (dealing with unicode filenames,
improved ``--vo=direct3d``, improved window handling)
* Better OSD rendering (using libass). This has full unicode support, and
languages like Arabic should be better supported.
* Cleaned up terminal output (nicer status line, less useless noise)
* Support for playing URLs of popular streaming sites directly
(e.g. ``mpv https://www.youtube.com/watch?v=...``)
* Improved OpenGL output (``--vo=opengl-hq``)
* Make ``--softvol`` default (**mpv** is not a mixer control panel)
* Improved support for .cue files
* Screenshot improvements (can save screenshots as JPG or PNG, configurable
filenames, support for taking screenshots with or without subtitles - the
``screenshot`` video filter is not needed anymore, and should not be put
into the mpv config file)
* Removal of teletext support
* Removal of most builtin demuxers, using libavformat instead
* Removal of builtin network support, using libavformat instead (also,
support https via libavformat)
* Replace image VOs (``--vo=jpeg`` etc.) with ``--vo=image``
* Do not lose settings when playing a new file in the same player instance
* New location for config files, new name for the binary.
* Slave mode compatibility broken (see below)
* Encoding functionality (replacement for mencoder, see ``DOCS/encoding.rst``)
* Remove ``--vo=gif89a``, ``--vo=md5sum``, ``--vo=yuv4mpeg``, as encoding can
handle these use cases. For yuv4mpeg, for example, use:
``mpv input.mkv -o output.y4m --no-audio --oautofps --oneverdrop``.
* Image subtitles (DVDs etc.) are rendered in color and use more correct
positioning (color can be disabled with ``--sub-gray``)
* Wayland support
* Support for precise scrolling which scales the parameter of commands. If the
input doesn't support precise scrolling the scale factor stays 1.
* OSX: Cocoa event loop is independent from mplayer's event loop, so user
actions like accessing menus and live resizing do not block the playback.
* OSX: Apple Remote support.
* OSX: Media Keys support.
* Windows: Added WASAPI audio output.
* New OSD bar with chapter marks and not positioned in the middle of the video
(though this can be customized with the ``--osd-bar-align-y`` option).
* Allow customizing whether a key binding for seeking shows the video time, the
OSD bar, or nothing (see section `Input Command Prefixes`_).
* Display list of chapters and audio/subtitle tracks on OSD (see section
`Properties`_).
* ``--keep-open`` option to stop the player from closing the window and
exiting after playback ends.
* A client API, that allows embedding **mpv** into applications
(see ``libmpv/client.h`` in the sources).
Input
~~~~~
* Improved default keybindings. MPlayer bindings are also available (see
``etc/mplayer-input.conf`` in the source tree).
* Improved responsiveness on user input.
* Support for modifier keys (alt, shift, ctrl) in input.conf.
* Allow customizing whether a key binding for seeking shows the video time, the
OSD bar, or nothing (see the `Input Command Prefixes`_ section).
* Support mapping multiple commands to one key.
Audio
~~~~~
* Support for gapless audio (see the ``--gapless-audio`` option).
* Support for OSS4 volume control.
* Improved support for PulseAudio.
* Make ``--softvol`` default (**mpv** is not a mixer control panel).
* By default, do pitch correction if playback speed is increased.
* Improved downmixing and output of surround audio:
- Instead of using hardcoded pan filters to do remixing, libavresample is used
- Channel maps are used to identify the channel layout, so e.g. ``3.0`` and
``2.1`` audio can be distinguished.
Video
~~~~~
* Wayland support.
* Native support for VAAPI and VDA. Improved VDPAU video output.
* Improved OpenGL output (see the ``opengl-hq`` video output).
* Make hardware decoding work with the ``opengl`` video output.
* Re-enable screensaver while the player is paused.
* Matroska edition switching at runtime.
* Support for libavfilter (for video->video and audio->audio). This allows
using most of FFmpeg's filters, which improve greatly on the old MPlayer
filters in features, performance, and correctness.
* More correct color reproduction (color matrix generation).
* Better subtitle rendering using libass by default.
* Improvements when playing multiple files (``--fixed-vo`` is default, do not
reset settings by default when playing a new file).
* Replace image video outputs (``--vo=jpeg`` etc.) with ``--vo=image``.
* Removal of ``--vo=gif89a``, ``--vo=md5sum``, ``--vo=yuv4mpeg``, as encoding
can handle these use cases. For yuv4mpeg, for example, use::
mpv input.mkv -o output.y4m --no-audio --oautofps --oneverdrop
* Image subtitles (DVDs etc.) are rendered in color and use more correct
positioning (color for image subs can be disabled with ``--sub-gray``).
OSD and terminal
~~~~~~~~~~~~~~~~
* Cleaned up terminal output: nicer status line, less useless noise.
* Improved OSD rendering using libass, with full Unicode support.
* New OSD bar with chapter marks. Not positioned in the middle of the video
(this can be customized with the ``--osd-bar-align-y`` option).
* Display list of chapters and audio/subtitle tracks on OSD (see the
`Properties`_ section).
Screenshots
~~~~~~~~~~~
* Instant screenshots without 1-frame delay.
* Support for taking screenshots even with hardware decoding.
* Support for saving screenshots as JPEG or PNG.
* Support for configurable file names.
* Support for taking screenshots with or without subtitles.
Note that the ``screenshot`` video filter is not needed anymore, and should not
be put into the mpv config file.
Miscellaneous
~~~~~~~~~~~~~
* Better MKV support (e.g. ordered chapters, 3D metadata).
* Matroska edition switching at runtime.
* Support for playing URLs of popular streaming sites directly.
(e.g. ``mpv https://www.youtube.com/watch?v=...``).
Requires a recent version of ``youtube-dl`` to be installed, and
``ytdl=yes`` in the mpv config file.
* Support for precise scrolling which scales the parameter of commands. If the
input doesn't support precise scrolling the scale factor stays 1.
* Improved downmixing and output of surround audio. Instead of using hardcoded
pan filters to do remixing, use libavresample. Channel maps are used to
identify the channel layout, so e.g. ``3.0`` and ``2.1`` audio can be
distinguished.
* Allow resuming playback at a later point with ``Shift+q``, also see
``quit_watch_later`` input command.
* Support mapping multiple commands to one key.
* Allow changing/adjusting video filters at runtime. (This is also used to make
the ``D`` key insert vf_yadif if deinterlacing is not supported otherwise).
* Improved support for .cue files.
Mac OS X
~~~~~~~~
* Native OpenGL backend.
* Cocoa event loop is independent from MPlayer's event loop, so user
actions like accessing menus and live resizing do not block the playback.
* Apple Remote support.
* Media Keys support.
* VDA support using libavcodec hwaccel API instead of FFmpeg's decoder with up
the ``D`` key insert vf_yadif if deinterlacing is not supported otherwise.)
* Native VAAPI support
* OSX: VDA support using libavcodec hwaccel API insted of FFmpeg's decoder. Up
to 2-2.5x reduction in CPU usage.
Windows
~~~~~~~
* Improved support for Unicode file names.
* Improved window handling.
* Do not block playback when moving the window.
* Improved Direct3D video output.
* Added WASAPI audio output.
Internal changes
~~~~~~~~~~~~~~~~
* Switch to GPLv2+ (see ``Copyright`` file for details).
* Removal of lots of cruft:
- Internal GUI (replaced by the OSC, see the `ON SCREEN CONTROLLER`_ section).
- MEncoder (replaced by native encoding, see the `ENCODING`_ section).
- OSD menu.
- Kernel video drivers for Linux 2.4 (including VIDIX).
- Teletext support.
- Support for dead platforms.
- Most built-in demuxers have been replaced by their libavformat counterparts.
- Built-in network support has been replaced by libavformat's (which also
supports https URLs).
- Embedded copies of libraries (such as FFmpeg).
* General code cleanups (including refactoring or rewrites of many parts).
* New build system.
* Many bug fixes and removal of long-standing issues.
* Generally preferring FFmpeg/Libav over internal demuxers, decoders, and
filters.
* Make hardware decoding in general work with the ``opengl`` video output.
* Lua scripting (see `LUA SCRIPTING`_)
* A client API, that allows embedding **mpv** into applications
(see ``libmpv/client.h`` in the sources)
* General bug fixes and removal of long-standing issues
* General code cleanups (including refactoring or rewrites of many parts)
* Many more changes
Detailed Listing of User-visible Changes
----------------------------------------
@@ -387,10 +332,8 @@ Slave mode
Assuming the system supports ``/dev/stdin``.
(The option was readded in 0.5.1 and sets exactly these options.)
* A JSON RPC protocol giving access to the client API is also supported. See
`JSON IPC`_ for more information.
* 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.)
+2 -2
View File
@@ -38,7 +38,7 @@ You can encode files from one format/codec to another using this facility.
``--oautofps``
Sets the output format time base to the guessed frame rate of the input
video (simulates MEncoder behavior, useful for AVI; may cause frame drops).
video (simulates mencoder behavior, useful for AVI; may cause frame drops).
Note that not all codecs and not all formats support VFR encoding, and some
which do have bugs when a target bitrate is specified - use ``--ofps`` or
``--oautofps`` to force CFR encoding in these cases.
@@ -74,7 +74,7 @@ You can encode files from one format/codec to another using this facility.
.. admonition:: Example
"``--oac=libmp3lame --oacopts=b=128000``"
selects 128 kbps MP3 encoding.
selects 128kbps MP3 encoding.
Options are managed in lists. There are a few commands to manage the
options list.
+38 -294
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
@@ -115,7 +114,7 @@ List of Input Commands
to be precise, not fast), and sometimes fails to behave as expected. How
well this works depends on whether precise seeking works correctly (e.g.
see the ``--hr-seek-demuxer-offset`` option). Video filters or other video
post-processing that modifies timing of frames (e.g. deinterlacing) should
postprocessing that modifies timing of frames (e.g. deinterlacing) should
usually work, but might make backstepping silently behave incorrectly in
corner cases. Using ``--hr-seek-framedrop=no`` should help, although it
might make precise seeking slower.
@@ -160,9 +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.
@@ -652,7 +572,7 @@ Property list
See ``--osd-level``.
``osd-scale`` (RW)
OSD font size multiplier, see ``--osd-scale``.
OSD font size multiplicator, see ``--osd-scale``.
``loop`` (RW)
See ``--loop``.
@@ -674,23 +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.
@@ -860,7 +765,7 @@ Property list
::
MPV_FORMAT_NODE_MAP
(key and string value for each metadata entry)
(key and string value for each metdata entry)
``chapter-metadata``
Metadata of current chapter. Works similar to ``metadata`` property. It
@@ -891,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).
@@ -908,7 +809,7 @@ Property list
this easier, the cache resizing code will allocate the new cache while the
old cache is still allocated.
Don't use this when playing DVD or Blu-ray.
Don't use this when playing DVD or Bluray.
``cache-free`` (R)
Total free cache size in KB.
@@ -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``.
@@ -1144,7 +1025,7 @@ Property list
Estimated/measured FPS of the video filter chain output. (If no filters
are used, this corresponds to decoder output.) This uses the average of
the 10 past frame durations to calculate the FPS. It will be inaccurate
if frame-dropping is involved (such as when framedrop is explicitly
if framedropping is involved (such as when framedrop is explicitly
enabled, or after precise seeking). Files with imprecise timestamps (such
as Matroska) might lead to unstable results.
@@ -1152,16 +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``.
@@ -1216,7 +1090,7 @@ Property list
See ``--sub-forced-only``.
``sub-scale`` (RW)
Subtitle font size multiplier.
Subtitle font size multiplicator.
``ass-use-margins`` (RW)
See ``--ass-use-margins``.
@@ -1228,8 +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.
+44 -156
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 builtin module ``mp``, which
provides functions to send commands to the mpv core and to retrieve information
about playback state, user settings, file information, and so on.
These scripts can be used to control mpv in a similar way to slave mode.
Technically, the Lua code uses the client API internally.
@@ -33,13 +27,12 @@ allow users implement features which are not going to be added to the mpv core.
Mode of operation
-----------------
Your script will be loaded by the player at program start from the ``lua``
configuration subdirectory, from a path specified with the ``--lua`` option, or
in some cases, internally (like ``--osc``). Each script runs in its own
thread. Your script is first run "as is", and once that is done, the event loop
is entered. This event loop will dispatch events received by mpv and call your
own event handlers which you have registered with ``mp.register_event``, or
timers added with ``mp.add_timeout`` or similar.
Your script will be loaded by the player at program start from ``~/.mpv/lua/``,
or ``--lua``, or in some cases, internally (like ``--osc``). Each script runs
in its own thread. Your script is first run "as is", and once that is done,
the event loop is entered. This event loop will dispatch events received by mpv
and call your own event handlers which you have registered with
``mp.register_event``, or timers added with ``mp.add_timeout`` or similar.
When the player quits, all scripts will be asked to terminate. This happens via
a ``shutdown`` event, which by default will make the event loop return. If your
@@ -54,15 +47,6 @@ The event loop will wait for events and dispatch events registered with
``mp.register_event``. It will also handle timers added with ``mp.add_timeout``
and similar (by waiting with a timeout).
Since mpv 0.6.0, the player will wait until the script is fully loaded before
continuing normal operation. The player considers a script as fully loaded as
soon as it starts waiting for mpv events (or it exits). In practice this means
the player will more or less hang until the script returns from the main chunk
(and ``mp_event_loop`` is called), or the script calls ``mp_event_loop`` or
``mp.dispatch_events`` directly. This is done to make it possible for a script
to fully setup event handlers etc. before playback actually starts. In older
mpv versions, this happened asynchronously.
mp functions
------------
@@ -90,19 +74,6 @@ The ``mp`` module is preloaded, although it can be loaded manually with
These two commands are equivalent, except that the first version breaks
if the filename contains spaces or certain special characters.
Note that properties are *not* expanded. You can use either ``mp.command``,
the ``expand-properties`` prefix, or the ``mp.get_property`` family of
functions.
``mp.command_native(table [,def])``
Similar to ``mp.commandv``, but pass the argument list as table. This has
the advantage that in at least some cases, arguments can be passed as
native types.
Returns a result table on success (usually empty), or ``def, error`` on
error. ``def`` is the second parameter provided to the function, and is
nil if it's missing.
``mp.get_property(name [,def])``
Return the value of the given property as string. These are the same
properties as used in input.conf. See `Properties`_ for a list of
@@ -123,9 +94,9 @@ The ``mp`` module is preloaded, although it can be loaded manually with
will always result in a string.
``mp.get_property_bool(name [,def])``
Similar to ``mp.get_property``, but return the property value as Boolean.
Similar to ``mp.get_property``, but return the property value as boolean.
Returns a Boolean on success, or ``def, error`` on error.
Returns a boolean on success, or ``def, error`` on error.
``mp.get_property_number(name [,def])``
Similar to ``mp.get_property``, but return the property value as number.
@@ -138,7 +109,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
``mp.get_property_native(name [,def])``
Similar to ``mp.get_property``, but return the property value using the best
Lua type for the property. Most time, this will return a string, Boolean,
Lua type for the property. Most time, this will return a string, boolean,
or number. Some properties (for example ``chapter-list``) are returned as
tables.
@@ -153,7 +124,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
``mp.set_property_bool(name, value)``
Similar to ``mp.set_property``, but set the given property to the given
Boolean value.
boolean value.
``mp.set_property_number(name, value)``
Similar to ``mp.set_property``, but set the given property to the given
@@ -181,14 +152,14 @@ The ``mp`` module is preloaded, although it can be loaded manually with
Return the current mpv internal time in seconds as a number. This is
basically the system time, with an arbitrary offset.
``mp.add_key_binding(key, name|fn [,fn [,flags]])``
``mp.add_key_binding(key, name|fn [,fn])``
Register callback to be run on a key binding. The binding will be mapped to
the given ``key``, which is a string describing the physical key. This uses
the same key names as in input.conf, and also allows combinations
(e.g. ``ctrl+a``).
After calling this function, key presses will cause the function ``fn`` to
be called (unless the user remapped the key with another binding).
be called (unless the user overmapped the key with another binding).
The ``name`` argument should be a short symbolic string. It allows the user
to remap the key binding via input.conf using the ``script_message``
@@ -198,23 +169,8 @@ The ``mp`` module is preloaded, although it can be loaded manually with
overwritten. You can omit the name, in which case a random name is generated
internally.
The last argument is used for optional flags. This is a table, which can
have the following entries:
``repeatable``
If set to ``true``, enables key repeat for this specific binding.
``complex``
If set to ``true``, then ``fn`` is called on both key up and down
events (as well as key repeat, if enabled), with the first
argument being a table. This table has an ``event`` entry, which
is set to one of the strings ``down``, ``repeat``, ``up`` or
``press`` (the latter if key up/down can't be tracked). It further
has an ``is_mouse`` entry, which tells whether the event was caused
by a mouse button.
Internally, key bindings are dispatched via the ``script_message_to`` or
``script_binding`` input commands and ``mp.register_script_message``.
Internally, key bindings are dispatched via the ``script_message_to`` input
command and ``mp.register_script_message``.
Trying to map multiple commands to a key will essentially prefer a random
binding, while the other bindings are not called. It is guaranteed that
@@ -237,18 +193,18 @@ The ``mp`` module is preloaded, although it can be loaded manually with
::
y script_binding something
y script_message something
This will print the message when the key ``y`` is pressed. (``x`` will
still work, unless the user remaps it.)
still work, unless the user overmaps it.)
You can also explicitly send a message to a named script only. Assume the
above script was using the filename ``fooscript.lua``:
::
y script_binding fooscript.something
y script_message_to fooscript something
``mp.add_forced_key_binding(...)``
This works almost the same as ``mp.add_key_binding``, but registers the
@@ -355,7 +311,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
``oneshot`` (RW)
Whether the timer is periodic (``false``) or fires just once
(``true``). This value is used when the timer expires (but before
the timer callback function fn is run).
the timer calback function fn is run).
``mp.get_opt(key)``
@@ -417,7 +373,7 @@ are useful only in special situations.
control what the Lua script does (instead of being called by the default
event loop), you can set the global variable ``mp_event_loop`` to your
own function running the event loop. From your event loop, you should call
``mp.dispatch_events()`` to dequeue and dispatch mpv events.
``mp.dispatch_events()`` to unqueue and dispatch mpv events.
If the ``allow_wait`` parameter is set to ``true``, the function will block
until the next event is received or the next timer expires. Otherwise (and
@@ -526,13 +482,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 +518,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 +559,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 +640,8 @@ List of events
``audio-reconfig``
Happens on audio output or filter reconfig.
The following events also happen, but are deprecated: ``tracks-changed``,
``track-switched``, ``pause``, ``unpause``, ``metadata-update``,
``chapter-change``. Use ``mp.observe_property()`` instead.
``metadata-update``
Metadata (like file tags) was updated.
Extras
------
This documents experimental features, or features that are "too special" and
we don't guarantee a stable interface to it.
``mp.add_hook(type, priority, fn)``
Add a hook callback for ``type`` (a string identifying a certain kind of
hook). These hooks allow the player to call script functions and wait for
their result (normally, the Lua scripting interface is asynchronous from
the point of view of the player core). ``priority`` is an arbitrary integer
that allows ordering among hooks of the same kind. Using the value 50 is
recommended as neutral default value. ``fn`` is the function that will be
called during execution of the hook.
Currently existing hooks:
``on_load``
Called when a file is to be opened, before anything is actually done.
For example, you could read and write the ``stream-open-filename``
property to redirect an URL to something else (consider support for
streaming sites which rarely give the user a direct media URL), or
you could set per-file options with by setting the property
``file-local-options/<option name>``. The player will wait until all
hooks are run.
``chapter-change``
The current chapter possibly changed.
+12 -113
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.
@@ -179,7 +163,7 @@ corresponding adjustment, or the software equalizer (``--vf=eq``).)
7 and 8
Adjust saturation.
(The following keys are valid only on OS X.)
(The following keys are valid only on OSX.)
command + 0
Resize video window to half its original size.
@@ -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.
@@ -485,8 +419,8 @@ PROTOCOLS
Play a path from Samba share.
``bd://[title][/device]`` ``--bluray-device=PATH``
Play a Blu-Ray disc. Currently, this does not accept ISO files. Instead,
you must mount the ISO file as filesystem, and point ``--bluray-device``
Play a Blu-Ray disc. Currently, this does not accept iso files. Instead,
you must mount the iso file as filesystem, and point ``--bluray-device``
to the mounted directly.
``bdnav://[title][/device]``
@@ -567,8 +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
=====================
@@ -773,7 +672,7 @@ Play only chapters 5, 6, 7:
Play only titles 5, 6, 7:
``mpv dvd://5-7``
Play a multi-angle DVD:
Play a multiangle DVD:
``mpv dvd://1 --dvd-angle=2``
Play from a different DVD device:
+222 -472
View File
File diff suppressed because it is too large. Load diff
+5 -9
View File
@@ -122,11 +122,11 @@ time remaining
left-click toggle between total and remaining time
============= ================================================
Key Bindings
~~~~~~~~~~~~
Keybindings
~~~~~~~~~~~
These key bindings are active by default if nothing else is already bound to
these keys. In case of collision, the function needs to be bound to a
These keybindings are active by default if nothing else is already bound to
these keys. In case of collosision, the function needs to be bound to a
different key. See `Script Commands`_ section.
============= ================================================
@@ -161,7 +161,7 @@ To avoid collisions with other scripts, all options need to be prefixed with
Example::
--lua-opts=osc-optionA=value1,osc-optionB=value2
--lua-opts=osc-optionA=value1:osc-optionB=value2
Configurable Options
@@ -229,10 +229,6 @@ Configurable Options
| Default: yes
| Display a tooltip over the seekbar indicating time at mouse position.
``seekbarstyle``
| Default: slider
| Sets the style of the seekbar, slider (diamond marker) or bar (fill)
Script Commands
~~~~~~~~~~~~~~~
+21 -52
View File
@@ -93,7 +93,7 @@ Available filters are:
``scale[=w:h:param:param2:chr-drop:noup:arnd``
Scales the image with the software scaler (slow) and performs a YUV<->RGB
color space conversion (see also ``--sws``).
colorspace conversion (see also ``--sws``).
All parameters are optional.
@@ -120,9 +120,9 @@ Available filters are:
0.33:0.33 Mitchell-Netravali spline
1.00:0.00 cubic B-spline
--sws=7 (Gaussian): sharpness (0 (soft) - 100 (sharp))
--sws=7 (gaussian): sharpness (0 (soft) - 100 (sharp))
--sws=9 (Lanczos): filter length (1-10)
--sws=9 (lanczos): filter length (1-10)
``<chr-drop>``
chroma skipping
@@ -222,8 +222,8 @@ Available filters are:
====================== =====================
``noformat[=fmt]``
Restricts the color space for the next filter without doing any conversion.
Unlike the format filter, this will allow any color space except the one
Restricts the colorspace for the next filter without doing any conversion.
Unlike the format filter, this will allow any colorspace except the one
you specify.
.. note:: For a list of available formats, see ``noformat=fmt=help``.
@@ -231,23 +231,12 @@ Available filters are:
``<fmt>``
Format name, e.g. rgb15, bgr24, 420p, etc. (default: 420p).
``pp=[filter string]``
Enables the specified chain of post-processing subfilters. Subfilters must
``pp[=[filter1[:option1[:option2...]]/[-]filter2...]]``
Enables the specified chain of postprocessing subfilters. Subfilters must
be separated by '/' and can be disabled by prepending a '-'. Each
subfilter and some options have a short and a long name that can be used
interchangeably, i.e. ``dr``/``dering`` are the same.
.. note::
Unlike in MPlayer or in earlier versions, you must quote the pp string
if it contains ``:`` characters, e.g. ``'--vf=pp=[...]'``.
.. admonition:: Warning
This filter is most likely useless on modern HD video. It might be
helpful with old low-resolution files.
All subfilters share common options to determine their scope:
interchangeably, i.e. ``dr``/``dering`` are the same. All subfilters share
common options to determine their scope:
``a/autoq``
Automatically switch the subfilter off if the CPU is too slow.
@@ -262,6 +251,11 @@ Available filters are:
``--vf=pp:help`` shows a list of available subfilters.
.. note::
Unlike in MPlayer or in earlier versions, you must quote the pp string
if it contains ``:`` characters, e.g. ``'--vf=pp=[...]'``.
Available subfilters are:
``hb/hdeblock[:difference[:flatness]]``
@@ -342,7 +336,7 @@ Available filters are:
filtering every second line with a (-1 4 2 4 -1) filter.
``l5/lowpass5``
Vertically applied FIR low-pass deinterlacing filter that deinterlaces
Vertically applied FIR lowpass deinterlacing filter that deinterlaces
the given block by filtering all lines with a (-1 2 6 2 -1) filter.
``fq/forceQuant[:quantizer]``
@@ -362,7 +356,7 @@ Available filters are:
.. note::
This filter is only available if FFmpeg/Libav has been compiled
This filter is only available if FFmpeg/libav has been compiled
with libpostproc enabled.
.. admonition:: Examples
@@ -449,7 +443,7 @@ Available filters are:
temporal noise (noise pattern changes between frames)
``uniform``
uniform noise (Gaussian otherwise)
uniform noise (gaussian otherwise)
``hq``
high quality (slightly better looking, slightly slower) - not available
@@ -509,7 +503,7 @@ Available filters are:
:1: linear interpolation (default)
``unsharp[=lx:ly:la:cx:cy:ca]``
unsharp mask / Gaussian blur
unsharp mask / gaussian blur
``l`` is for the luma component, ``c`` for the chroma component. ``x``/``y``
is the filter size. ``a`` is the amount.
@@ -775,8 +769,8 @@ Available filters are:
anaglyph red/cyan color (red filter on left eye, cyan filter on
right eye)
``arcd`` or ``anaglyph_red_cyan_dubois``
anaglyph red/cyan color optimized with the least-squares
projection of Dubois (red filter on left eye, cyan filter on right
anaglyph red/cyan color optimized with the least squares
projection of dubois (red filter on left eye, cyan filter on right
eye)
``agmg`` or ``anaglyph_green_magenta_gray``
anaglyph green/magenta gray (green filter on left eye, magenta
@@ -809,7 +803,7 @@ Available filters are:
``gradfun[=strength[:radius|:size=<size>]]``
Fix the banding artifacts that are sometimes introduced into nearly flat
regions by truncation to 8-bit color depth. Interpolates the gradients that
regions by truncation to 8bit color depth. Interpolates the gradients that
should go where the bands are, and dithers them.
``<strength>``
@@ -910,31 +904,6 @@ Available filters are:
making it higher than the number of cores can actually make it
slower.
``vapoursynth-lazy``
The same as ``vapoursynth``, but doesn't load Python scripts. Instead, a
custom backend using Lua and the raw VapourSynth API is used. The syntax
is completely different, and absolutely no conveniencve features are
provided. There's no type checking either, and you can trigger crashes.
.. admonition:: Example:
::
video_out = invoke("morpho", "Open", {clip = video_in})
The special variable ``video_in`` is the mpv video source, while the
special variable ``video_out`` is used to read video from. The 1st argument
is the plugin (queried with ``getPluginByNs``), the 2nd is the filter name,
and the 3rd argument is a table with the arguments. Positional arguments
are not supported. The types must match exactly. Since Lua is terrible and
can't distinguish integers and floats, integer arguments must be prefixed
with ``i_``, in which case the prefix is removed and the argument is cast
to an integer. Should the argument's name start with ``i_``, you're out of
luck.
Clips (VSNodeRef) are passed as light userdata, so trying to pass any
other userdata type will result in hard crashes.
``vavpp``
VA-AP-API video post processing. Works with ``--vo=vaapi`` and ``--vo=opengl``
only. Currently deinterlaces. This filter is automatically inserted if
+43 -87
View File
@@ -38,36 +38,36 @@ Available video output drivers are:
.. note:: This driver is for compatibility with old systems.
``adaptor=<number>``
Select a specific XVideo adapter (check xvinfo results).
Select a specific XVideo adaptor (check xvinfo results).
``port=<number>``
Select a specific XVideo port.
``ck=<cur|use|set>``
Select the source from which the color key is taken (default: cur).
Select the source from which the colorkey is taken (default: cur).
cur
The default takes the color key currently set in Xv.
The default takes the colorkey currently set in Xv.
use
Use but do not set the color key from mpv (use the ``--colorkey``
Use but do not set the colorkey from mpv (use the ``--colorkey``
option to change it).
set
Same as use but also sets the supplied color key.
Same as use but also sets the supplied colorkey.
``ck-method=<man|bg|auto>``
Sets the color key drawing method (default: man).
Sets the colorkey drawing method (default: man).
man
Draw the color key manually (reduces flicker in some cases).
Draw the colorkey manually (reduces flicker in some cases).
bg
Set the color key as window background.
Set the colorkey as window background.
auto
Let Xv draw the color key.
Let Xv draw the colorkey.
``colorkey=<number>``
Changes the color key to an RGB value of your choice. ``0x000000`` is
Changes the colorkey to an RGB value of your choice. ``0x000000`` is
black and ``0xffffff`` is white.
``no-colorkey``
Disables color-keying.
Disables colorkeying.
``x11`` (X11 only)
Shared memory video output driver without hardware acceleration that works
@@ -82,7 +82,7 @@ Available video output drivers are:
.. note::
Earlier versions of mpv (and MPlayer, mplayer2) provided sub-options
to tune vdpau post-processing, like ``deint``, ``sharpen``, ``denoise``,
to tune vdpau postprocessing, like ``deint``, ``sharpen``, ``denoise``,
``chroma-deint``, ``pullup``, ``hqscaling``. These sub-options are
deprecated, and you should use the ``vdpaupp`` video filter instead.
@@ -173,7 +173,7 @@ Available video output drivers are:
to a YUV format before the VO. Sometimes useful to force availability
of certain YUV-only features, like video equalizer or deinterlacing.
Using the VDPAU frame queuing functionality controlled by the queuetime
Using the VDPAU frame queueing functionality controlled by the queuetime
options makes mpv's frame flip timing less sensitive to system CPU load and
allows mpv to start decoding the next frame(s) slightly earlier, which can
reduce jitter caused by individual slow-to-decode frames. However, the
@@ -233,29 +233,18 @@ Available video output drivers are:
Always force textures to power of 2, even if the device reports
non-power-of-2 texture sizes as supported.
``texture-memory=<mode>``
``texture-memory=N``
Only affects operation with shaders/texturing enabled, and (E)OSD.
Possible values:
Values for N:
``default`` (default)
Use ``D3DPOOL_DEFAULT``, with a ``D3DPOOL_SYSTEMMEM`` texture for
locking. If the driver supports ``D3DDEVCAPS_TEXTURESYSTEMMEMORY``,
``D3DPOOL_SYSTEMMEM`` is used directly.
``default-pool``
Use ``D3DPOOL_DEFAULT``. (Like ``default``, but never use a
shadow-texture.)
``default-pool-shadow``
Use ``D3DPOOL_DEFAULT``, with a ``D3DPOOL_SYSTEMMEM`` texture for
locking. (Like ``default``, but always force the shadow-texture.)
``managed``
Use ``D3DPOOL_MANAGED``.
``scratch``
Use ``D3DPOOL_SCRATCH``, with a ``D3DPOOL_SYSTEMMEM`` texture for
locking.
0
default, will often use an additional shadow texture + copy
1
use ``D3DPOOL_MANAGED``
2
use ``D3DPOOL_DEFAULT``
3
use ``D3DPOOL_SYSTEMMEM``, but without shadow texture
``swap-discard``
Use ``D3DSWAPEFFECT_DISCARD``, which might be faster.
@@ -270,6 +259,13 @@ Available video output drivers are:
.. note:: This driver is for compatibility with old systems.
``corevideo`` (Mac OS X 10.6 and later)
Mac OS X CoreVideo video output driver. Uses the CoreVideo APIs to fill
PixelBuffers and generate OpenGL textures from them (useful as a fallback
for ``opengl``).
.. note:: This driver is for compatibility with old systems.
``opengl``
OpenGL video output driver. It supports extended scaling methods, dithering
and color management.
@@ -286,7 +282,7 @@ Available video output drivers are:
Hardware decoding over OpenGL-interop is supported to some degree. Note
that in this mode, some corner case might not be gracefully handled, and
color space conversion and chroma upsampling is generally in the hand of
colorspace conversion and chroma upsampling is generally in the hand of
the hardware decoder APIs.
``lscale=<filter>``
@@ -301,9 +297,6 @@ Available video output drivers are:
``lanczos3``
Lanczos with radius=3.
``lanczos``
Generic Lanczos scaling filter. Set radius with ``lradius``.
``spline36``
This is the default when using ``opengl-hq``.
@@ -323,14 +316,6 @@ Available video output drivers are:
Mitchell-Netravali. The ``b`` and ``c`` parameters can be set with
``lparam1`` and ``lparam2``. Both are set to 1/3 by default.
``gaussian``
Gaussian filter with a parameter ``p`` for sharpness control.
``p`` can be set to float number between 1(blurry) and 100(sharp)
and has a default value of about 28.8 (see ``lparam1``).
Note that for extremely small value of ``p``, a large filter radius
might be required to avoid unintended artifacts (see ``lradius``).
There are some more filters. For a complete list, pass ``help`` as
value, e.g.::
@@ -344,16 +329,6 @@ Available video output drivers are:
``lparam2=<value>``
See ``lparam1``.
``lradius=<r>``
Set radius for filters listed below, must be a float number between 1.0
and 8.0. Defaults to be 2.0 if not specified.
``sinc``, ``lanczos``, ``blackman``, ``gaussian``
Note that depending on filter implementation details and video scaling
ratio, the radius that actually being used might be different
(most likely being increased a bit).
``scaler-resizes-only``
Disable the scaler if the video image is not resized. In that case,
``bilinear`` is used instead whatever is set with ``lscale``. Bilinear
@@ -374,7 +349,7 @@ Available video output drivers are:
green-magenta
Convert side by side input to full-color green-magenta stereo.
quadbuffer
Convert side by side input to quad buffered stereo. Only supported
Convert side by side input to quadbuffered stereo. Only supported
by very few OpenGL cards.
``srgb``
@@ -449,22 +424,12 @@ Available video output drivers are:
this options will make rendering a single operation.
Note that chroma scalers are always done as 1-pass filters.
``cscale=<filter>``
``cscale=<n>``
As ``lscale``, but for chroma (2x slower with little visible effect).
Note that with some scaling filters, upscaling is always done in
RGB. If chroma is not subsampled, this option is ignored, and the
luma scaler is used instead. Setting this option is often useless.
``lscale-down=<filter>``, ``cscale-down=<filter>``
Like ``lscale`` and ``cscale``, but apply these filters on downscaling
instead. If these options are unset, the filter implied by ``lscale``
(and ``cscale``, respectively) will be applied.
``cparam1``, ``cparam2``, ``cradius``
Set filter parameters and radius for ``cscale``.
See ``lparam1``, ``lparam2`` and ``lradius``.
``fancy-downscaling``
When using convolution based filters, extend the filter size
when downscaling. Trades quality for reduced downscaling performance.
@@ -474,16 +439,7 @@ Available video output drivers are:
Borders will be distorted due to filtering.
``glfinish``
Call ``glFinish()`` before and after swapping buffers (default: disabled).
Slower, but might help getting better results when doing framedropping.
The details depend entirely on the OpenGL driver.
``waitvsync``
Call ``glXWaitVideoSyncSGI`` after each buffer swap (default: disabled).
This may or may not help with video timing accuracy and frame drop. It's
possible that this makes video output slower, or has no effect at all.
X11 only.
Call ``glFinish()`` before swapping buffers
``sw``
Continue even if a software renderer is detected.
@@ -496,7 +452,7 @@ Available video output drivers are:
auto
auto-select (default)
cocoa
Cocoa/OS X
Cocoa/OSX
win
Win32/WGL
x11
@@ -523,7 +479,7 @@ Available video output drivers are:
``gamma=<0.0..10.0>``
Set a gamma value. If gamma is adjusted in other ways (like with
the ``--gamma`` option or key bindings and the ``gamma`` property), the
the ``--gamma`` option or keybindings and the ``gamma`` property), the
value is multiplied with the other gamma value.
Setting this value to 1.0 can be used to always enable gamma control.
@@ -531,7 +487,7 @@ Available video output drivers are:
``icc-profile=<file>``
Load an ICC profile and use it to transform linear RGB to screen output.
Needs LittleCMS 2 support compiled in. This option overrides the ``srgb``
Needs LittleCMS2 support compiled in. This option overrides the ``srgb``
property, as using both is somewhat redundant. It also enables linear
light scaling.
@@ -544,12 +500,12 @@ Available video output drivers are:
``icc-cache=<file>``
Store and load the 3D LUT created from the ICC profile in this file.
This can be used to speed up loading, since LittleCMS 2 can take a while
This can be used to speed up loading, since LittleCMS2 can take a while
to create the 3D LUT. Note that this file contains an uncompressed LUT.
Its size depends on the ``3dlut-size``, and can be very big.
``icc-intent=<value>``
Specifies the ICC Intent used for transformations between color spaces.
Specifies the ICC Intent used for transformations between colorspaces.
This affects the rendering when using ``icc-profile`` or ``srgb`` and
also affects the way DCP XYZ content gets converted to RGB.
@@ -606,7 +562,7 @@ Available video output drivers are:
This is equivalent to::
--vo=opengl:lscale=spline36:dither-depth=auto:fbo-format=rgba16
--vo=opengl:lscale=spline36:dither-depth=auto:fbo-format=rgb16
Note that some cheaper LCDs do dithering that gravely interferes with
``opengl``'s dithering. Disabling dithering with ``dither-depth=no`` helps.
@@ -615,7 +571,7 @@ Available video output drivers are:
can achieve better quality or performance by changing the ``fbo-format``
suboption to ``rgb16f``, ``rgb32f`` or ``rgb``. Known problems include
Mesa/Intel not accepting ``rgb16``, Mesa sometimes not being compiled with
float texture support, and some OS X setups being very slow with ``rgb16``
float texture support, and some OSX setups being very slow with ``rgb16``
but fast with ``rgb32f``.
``opengl-old``
@@ -750,7 +706,7 @@ Available video output drivers are:
2
Convert side by side input to full-color green-magenta stereo.
3
Convert side by side input to quad buffered stereo. Only supported
Convert side by side input to quadbuffered stereo. Only supported
by very few OpenGL cards.
The following options are only useful if writing your own fragment programs.
@@ -792,7 +748,7 @@ Available video output drivers are:
auto
auto-select (default)
cocoa
Cocoa/OS X
Cocoa/OSX
win
Win32/WGL
x11
-2
View File
@@ -24,8 +24,6 @@ Release procedure
- Create and/or update the ``VERSION`` file.
- Update ``DOCS/client-api-changes.rst`` (on major releases).
- Create tag v0.X.Y.
- Push branch and tag to GitHub.
+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
+67 -81
View File
@@ -1,97 +1,83 @@
Release 0.7.1
Release 0.5.0
=============
Changes
-------
- Don't show the volume neutral marker on the OSD if softvol is disabled.
- Don't select a subtitle track when executing the sub_add input command in
"auto" mode.
- 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
---------
- Fix busy loop when seeking while paused (this fixes a problem with pulseaudio
that caused mpv and the pulseaudio daemon to use 100% CPU).
- Fix Lua function utils.subprocess() in Windows versions older than Vista.
- Avoid creating a window bigger than the screen on Windows.
- Don't ignore the last line in m3u playlists.
- Don't crash if a codec could not be opened.
- Dynamically allocate audio channel map entries (this should fix a crash in
the alsa and coreaudio AOs with audio devices that support more than 20
channel maps).
- Ignore the "srgb" option in the opengl VO if hardware decoding is enabled.
- Linearize non-RGB sRGB files correctly (eg. JPEG).
- Fix opening reference URLs (.file/id=) on OS X.
This listing is not complete. There are many more bug fixes and changes. The
complete change log can be viewed by running ``git log 8d8b36d..6583ad6`` in
the git repository.
Release 0.7.0
=============
Changes
-------
- Buffer partial log messages in the client API (the client API will now only
pass full log messages to clients).
- Remove ncurses/terminfo/termcap support (it was disabled by default and
replaced by new code since v0.6.0).
- Enable cdda:// support by default again (it was disabled since v0.6.0).
- Cascade-load input.conf (if there are several input.confs in the set of valid
config paths, load them all).
- Draw the OSD twice in 3D mode (this fixes subtitles display in 3D mode).
- Make wasapi the default AO on Windows again since many of its problems have
been solved.
- Use "site-functions" subdir to install the zsh completion script instead of
the Debian-specific "vendor-completions" (also provide the --zshdir waf
configure option for changing this value).
- Improve synchronization between the Cocoa GUI and the player (this fixes some
long standing deadlock issues on Mac OS X).
- Remove --fs-missioncontrol option (only relevant to Mac OS X).
- 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"
+9 -60
View File
@@ -37,49 +37,14 @@ chomp $vf_str;
$protos_str .= qq{$_ } foreach (@protos);
chomp $protos_str;
my $profile_comp = <<'EOS';
local -a profiles
local current
for current in "${(@f)$($words[1] --profile=help)}"; do
current=${current//\*/\\\*}
current=${current//\:/\\\:}
current=${current//\[/\\\[}
current=${current//\]/\\\]}
if [[ $current =~ $'\t'([^$'\t']*)$'\t'(.*) ]]; then
if [[ -n $match[2] ]]; then
current="$match[1][$match[2]]"
else
current="$match[1]"
fi
profiles=($profiles $current)
fi
done
if [[ $state == profile ]]; then
# For --show-profile, only one allowed
if (( ${#profiles} > 0 )); then
_values 'profile' $profiles && rc=0
fi
else
# For --profile, multiple allowed
profiles=($profiles 'help[list profiles]')
_values -s , 'profile(s)' $profiles && rc=0
fi
EOS
chomp $profile_comp;
my $tmpl = <<"EOS";
#compdef mpv
# mpv zsh completion
local curcontext="\$curcontext" state state_descr line
typeset -A opt_args
local rc=1
_arguments -C -S \\
$opts_str
'*:files:->mfiles' && rc=0
'*:files:->mfiles'
case \$state in
ao)
@@ -88,7 +53,7 @@ case \$state in
$ao_str
)
_describe -t values 'audio outputs' values && rc=0
_describe -t values 'audio outputs' values
;;
vo)
@@ -97,7 +62,7 @@ $ao_str
$vo_str
)
_describe -t values 'video outputs' values && rc=0
_describe -t values 'video outputs' values
;;
af)
@@ -106,7 +71,7 @@ $vo_str
$af_str
)
_describe -t values 'audio filters' values && rc=0
_describe -t values 'audio filters' values
;;
vf)
@@ -115,37 +80,24 @@ $af_str
$vf_str
)
_describe -t values 'video filters' values && rc=0
;;
profile|profiles)
$profile_comp
;;
files)
compset -P '*,'
compset -S ',*'
_files -r ',/ \\t\\n\\-' && rc=0
_describe -t values 'video filters' values
;;
mfiles)
local expl
_tags files urls
while _tags; do
_requested files expl 'media file' _files -g \\
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|qt|rm|ts|vob|wav|webm|wma|wmv)(-.)" && rc=0
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|qt|rm|ts|vob|wav|webm|wma|wmv)(-.)" && ret=0
if _requested urls; then
while _next_label urls expl URL; do
_urls "\$expl[@]" && rc=0
compadd -S '' "\$expl[@]" $protos_str && rc=0
_urls "\$expl[@]" && ret=0
compadd -S '' "\$expl[@]" $protos_str && ret=0
done
fi
(( rc )) || return 0
(( ret )) || return 0
done
;;
esac
return rc
EOS
print $tmpl;
@@ -181,9 +133,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.0
+35 -165
View File
@@ -20,13 +20,10 @@
#include <stdlib.h>
#include <assert.h>
#include <libavutil/buffer.h>
#include <libavutil/frame.h>
#include <libavutil/version.h>
#include <libavutil/mem.h>
#include "talloc.h"
#include "common/common.h"
#include "fmt-conversion.h"
#include "audio.h"
static void update_redundant_info(struct mp_audio *mpa)
@@ -91,12 +88,18 @@ bool mp_audio_config_valid(const struct mp_audio *mpa)
&& mpa->rate >= 1 && mpa->rate < 10000000;
}
char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa)
char *mp_audio_fmt_to_str(int srate, const struct mp_chmap *chmap, int format)
{
snprintf(buf, buf_sz, "%dHz %s %dch %s", mpa->rate,
mp_chmap_to_str(&mpa->channels), mpa->channels.num,
af_fmt_to_str(mpa->format));
return buf;
char *chstr = mp_chmap_to_str(chmap);
char *res = talloc_asprintf(NULL, "%dHz %s %dch %s", srate, chstr,
chmap->num, af_fmt_to_str(format));
talloc_free(chstr);
return res;
}
char *mp_audio_config_to_str(struct mp_audio *mpa)
{
return mp_audio_fmt_to_str(mpa->rate, &mpa->channels, mpa->format);
}
void mp_audio_force_interleaved_format(struct mp_audio *mpa)
@@ -113,27 +116,19 @@ int mp_audio_psize(struct mp_audio *mpa)
void mp_audio_set_null_data(struct mp_audio *mpa)
{
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
for (int n = 0; n < MP_NUM_CHANNELS; n++)
mpa->planes[n] = NULL;
mpa->allocated[n] = NULL;
}
mpa->samples = 0;
}
static int get_plane_size(const struct mp_audio *mpa, int samples)
{
if (samples < 0 || !mp_audio_config_valid(mpa))
return -1;
if (samples >= INT_MAX / mpa->sstride)
return -1;
return MPMAX(samples * mpa->sstride, 1);
}
static void mp_audio_destructor(void *ptr)
{
struct mp_audio *mpa = ptr;
for (int n = 0; n < MP_NUM_CHANNELS; n++)
av_buffer_unref(&mpa->allocated[n]);
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
// Note: don't free if not allocated by mp_audio_realloc
if (mpa->allocated[n])
av_free(mpa->planes[n]);
}
}
/* Reallocate the data stored in mpa->planes[n] so that enough samples are
@@ -152,19 +147,27 @@ static void mp_audio_destructor(void *ptr)
*/
void mp_audio_realloc(struct mp_audio *mpa, int samples)
{
int size = get_plane_size(mpa, samples);
if (size < 0)
abort(); // oom or invalid parameters
assert(samples >= 0);
if (samples >= INT_MAX / mpa->sstride)
abort(); // oom
int size = MPMAX(samples * mpa->sstride, 1);
for (int n = 0; n < mpa->num_planes; n++) {
if (!mpa->allocated[n] || size != mpa->allocated[n]->size) {
if (av_buffer_realloc(&mpa->allocated[n], size) < 0)
abort(); // OOM
mpa->planes[n] = mpa->allocated[n]->data;
if (size != mpa->allocated[n]) {
// Note: av_realloc() can't be used (see libavutil doxygen)
void *new = av_malloc(size);
if (!new)
abort();
if (mpa->allocated[n])
memcpy(new, mpa->planes[n], MPMIN(mpa->allocated[n], size));
av_free(mpa->planes[n]);
mpa->planes[n] = new;
mpa->allocated[n] = size;
}
}
for (int n = mpa->num_planes; n < MP_NUM_CHANNELS; n++) {
av_buffer_unref(&mpa->allocated[n]);
av_free(mpa->planes[n]);
mpa->planes[n] = NULL;
mpa->allocated[n] = 0;
}
talloc_set_destructor(mpa, mp_audio_destructor);
}
@@ -191,7 +194,7 @@ int mp_audio_get_allocated_size(struct mp_audio *mpa)
{
int size = 0;
for (int n = 0; n < mpa->num_planes; n++) {
int s = mpa->allocated[n] ? mpa->allocated[n]->size / mpa->sstride : 0;
int s = mpa->allocated[n] / mpa->sstride;
size = n == 0 ? s : MPMIN(size, s);
}
return size;
@@ -237,136 +240,3 @@ void mp_audio_skip_samples(struct mp_audio *data, int samples)
data->samples -= samples;
}
// Make sure the frame owns the audio data, and if not, copy the data.
// Return negative value on failure (which means it can't be made writeable).
// Non-refcounted frames are always considered writeable.
int mp_audio_make_writeable(struct mp_audio *data)
{
bool ok = true;
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
if (data->allocated[n])
ok &= av_buffer_is_writable(data->allocated[n]);
}
if (!ok) {
struct mp_audio *new = talloc(NULL, struct mp_audio);
*new = *data;
mp_audio_set_null_data(new); // use format only
mp_audio_realloc(new, data->samples);
new->samples = data->samples;
mp_audio_copy(new, 0, data, 0, data->samples);
// "Transfer" the reference.
mp_audio_destructor(data);
*data = *new;
talloc_set_destructor(new, NULL);
talloc_free(new);
}
return 0;
}
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe)
{
AVFrame *tmp = NULL;
struct mp_audio *new = talloc_zero(NULL, struct mp_audio);
talloc_set_destructor(new, mp_audio_destructor);
mp_audio_set_format(new, af_from_avformat(avframe->format));
struct mp_chmap lavc_chmap;
mp_chmap_from_lavc(&lavc_chmap, avframe->channel_layout);
#if LIBAVUTIL_VERSION_MICRO >= 100
// FFmpeg being special again
if (lavc_chmap.num != avframe->channels)
mp_chmap_from_channels(&lavc_chmap, avframe->channels);
#endif
new->rate = avframe->sample_rate;
mp_audio_set_channels(new, &lavc_chmap);
// Force refcounted frame.
if (!avframe->buf[0]) {
tmp = av_frame_alloc();
if (!tmp)
goto fail;
if (av_frame_ref(tmp, avframe) < 0)
goto fail;
avframe = tmp;
}
// If we can't handle the format (e.g. too many channels), bail out.
if (!mp_audio_config_valid(new) || avframe->nb_extended_buf)
goto fail;
for (int n = 0; n < AV_NUM_DATA_POINTERS; n++) {
if (!avframe->buf[n])
break;
if (n >= MP_NUM_CHANNELS)
goto fail;
new->allocated[n] = av_buffer_ref(avframe->buf[n]);
if (!new->allocated[n])
goto fail;
}
for (int n = 0; n < new->num_planes; n++)
new->planes[n] = avframe->data[n];
new->samples = avframe->nb_samples;
return new;
fail:
talloc_free(new);
av_frame_free(&tmp);
return NULL;
}
struct mp_audio_pool {
AVBufferPool *avpool;
int element_size;
};
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent)
{
return talloc_zero(ta_parent, struct mp_audio_pool);
}
static void mp_audio_pool_destructor(void *p)
{
struct mp_audio_pool *pool = p;
av_buffer_pool_uninit(&pool->avpool);
}
// Allocate data using the given format and number of samples.
// Returns NULL on error.
struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
const struct mp_audio *fmt, int samples)
{
int size = get_plane_size(fmt, samples);
if (size < 0)
return NULL;
if (!pool->avpool || size > pool->element_size) {
size_t alloc = ta_calc_prealloc_elems(size);
if (alloc >= INT_MAX)
return NULL;
av_buffer_pool_uninit(&pool->avpool);
pool->avpool = av_buffer_pool_init(alloc, NULL);
if (!pool->avpool)
return NULL;
talloc_set_destructor(pool, mp_audio_pool_destructor);
}
struct mp_audio *new = talloc_ptrtype(NULL, new);
talloc_set_destructor(new, mp_audio_destructor);
*new = *fmt;
mp_audio_set_null_data(new);
new->samples = samples;
for (int n = 0; n < new->num_planes; n++) {
new->allocated[n] = av_buffer_pool_get(pool->avpool);
if (!new->allocated[n]) {
talloc_free(new);
return NULL;
}
new->planes[n] = new->allocated[n]->data;
}
return new;
}
+3 -12
View File
@@ -38,7 +38,7 @@ struct mp_audio {
int bps; // size of sub-samples (af_fmt2bps(format))
// private
struct AVBufferRef *allocated[MP_NUM_CHANNELS];
int allocated[MP_NUM_CHANNELS]; // use mp_audio_get_allocated_size()
};
void mp_audio_set_format(struct mp_audio *mpa, int format);
@@ -49,8 +49,8 @@ void mp_audio_copy_config(struct mp_audio *dst, const struct mp_audio *src);
bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b);
bool mp_audio_config_valid(const struct mp_audio *mpa);
char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa);
#define mp_audio_config_to_str(m) mp_audio_config_to_str_buf((char[64]){0}, 64, (m))
char *mp_audio_fmt_to_str(int srate, const struct mp_chmap *chmap, int format);
char *mp_audio_config_to_str(struct mp_audio *mpa);
void mp_audio_force_interleaved_format(struct mp_audio *mpa);
@@ -68,13 +68,4 @@ void mp_audio_copy(struct mp_audio *dst, int dst_offset,
struct mp_audio *src, int src_offset, int length);
void mp_audio_skip_samples(struct mp_audio *data, int samples);
int mp_audio_make_writeable(struct mp_audio *data);
struct AVFrame;
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe);
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent);
struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
const struct mp_audio *fmt, int samples);
#endif
+12 -15
View File
@@ -18,7 +18,6 @@
#include <stdlib.h>
#include <assert.h>
#include "common/common.h"
#include "common/msg.h"
#include "chmap.h"
@@ -87,7 +86,6 @@ static const char *const std_layout_names[][2] = {
{"7.1(wide-side)", "fl-fr-fc-lfe-flc-frc-sl-sr"},
{"octagonal", "fl-fr-fc-bl-br-bc-sl-sr"},
{"downmix", "dl-dr"},
{"auto", ""}, // not in lavc
{0}
};
@@ -377,35 +375,34 @@ void mp_chmap_get_reorder(int dst[MP_NUM_CHANNELS], const struct mp_chmap *from,
// Returns something like "fl-fr-fc". If there's a standard layout in lavc
// order, return that, e.g. "3.0" instead of "fl-fr-fc".
// Unassigned but valid speakers get names like "sp28".
char *mp_chmap_to_str_buf(char *buf, size_t buf_size, const struct mp_chmap *src)
char *mp_chmap_to_str(const struct mp_chmap *src)
{
buf[0] = '\0';
char *res = talloc_strdup(NULL, "");
if (mp_chmap_is_unknown(src)) {
snprintf(buf, buf_size, "unknown%d", src->num);
return buf;
}
if (mp_chmap_is_unknown(src))
return talloc_asprintf_append_buffer(res, "unknown%d", src->num);
for (int n = 0; n < src->num; n++) {
int sp = src->speaker[n];
const char *s = sp < MP_SPEAKER_ID_COUNT ? speaker_names[sp][0] : NULL;
char sp_buf[10];
char buf[10];
if (!s) {
snprintf(sp_buf, sizeof(sp_buf), "sp%d", sp);
s = sp_buf;
snprintf(buf, sizeof(buf), "sp%d", sp);
s = buf;
}
mp_snprintf_cat(buf, buf_size, "%s%s", n > 0 ? "-" : "", s);
res = talloc_asprintf_append_buffer(res, "%s%s", n > 0 ? "-" : "", s);
}
// To standard layout name
for (int n = 0; std_layout_names[n][0]; n++) {
if (strcmp(buf, std_layout_names[n][1]) == 0) {
snprintf(buf, buf_size, "%s", std_layout_names[n][0]);
if (res && strcmp(res, std_layout_names[n][1]) == 0) {
talloc_free(res);
res = talloc_strdup(NULL, std_layout_names[n][0]);
break;
}
}
return buf;
return res;
}
// If src can be parsed as channel map (as produced by mp_chmap_to_str()),
+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
+48 -181
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.
@@ -393,23 +321,7 @@ void ao_drain(struct ao *ao)
bool ao_eof_reached(struct ao *ao)
{
return ao->api->get_eof ? ao->api->get_eof(ao) : true;
}
// Query the AO_EVENT_*s as requested by the events parameter, and return them.
int ao_query_and_reset_events(struct ao *ao, int events)
{
int actual_events = 0;
if (atomic_load(&ao->request_reload)) // don't need to reset it
actual_events |= AO_EVENT_RELOAD;
return actual_events & events;
}
// Request that the player core destroys and recreates the AO.
void ao_request_reload(struct ao *ao)
{
atomic_store(&ao->request_reload, true);
mp_input_wakeup(ao->input_ctx);
return ao_get_delay(ao) < AO_EOF_DELAY;
}
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
@@ -448,48 +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,25 @@ 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);
*p = (struct priv) {
.muted = false,
.stream_asbd_changed = 0,
.hog_pid = -1,
.stream = 0,
.stream_idx = -1,
.changed_mixing = false,
};
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 +530,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 +602,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,14 +675,10 @@ 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,
.stream_asbd_changed = 0,
.hog_pid = -1,
.stream = 0,
.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 -38
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,11 +131,9 @@ 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.
bool (*get_eof)(struct ao *ao);
// Wait until the audio buffer needs to be refilled. The lock is the
// internal mutex usually protecting the internal AO state (and used to
// protect driver calls), and must be temporarily unlocked while waiting.
@@ -155,20 +149,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 +169,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 -51
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,34 +180,27 @@ 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);
}
static bool get_eof(struct ao *ao)
{
struct ao_pull_state *p = ao->api_priv;
// For simplicity, ignore the latency. Otherwise, we would have to run an
// extra thread to time it.
return mp_ring_buffered(p->buffers[0]) == 0;
}
static void uninit(struct ao *ao)
{
ao->driver->uninit(ao);
@@ -247,7 +225,6 @@ const struct ao_driver ao_api_pull = {
.get_space = get_space,
.play = play,
.get_delay = get_delay,
.get_eof = get_eof,
.pause = pause,
.resume = resume,
.priv_size = sizeof(struct ao_pull_state),
+96 -135
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,18 @@ 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 still_playing;
bool drain;
bool buffers_full;
bool avoid_ao_wait;
bool need_wakeup;
bool requested_data;
bool paused;
// Whether the current buffer contains the complete audio.
@@ -84,21 +87,22 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
return r;
}
static double unlocked_get_delay(struct ao *ao)
{
struct ao_push_state *p = ao->api_priv;
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);
}
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);
double driver_delay = 0;
if (ao->driver->get_delay)
driver_delay = ao->driver->get_delay(ao);
double delay = driver_delay + mp_audio_buffer_seconds(p->buffer);
pthread_mutex_unlock(&p->lock);
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;
}
@@ -110,9 +114,7 @@ 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 +145,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 +168,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);
}
@@ -199,15 +190,6 @@ static int get_space(struct ao *ao)
return space;
}
static bool get_eof(struct ao *ao)
{
struct ao_push_state *p = ao->api_priv;
pthread_mutex_lock(&p->lock);
bool eof = !p->still_playing;
pthread_mutex_unlock(&p->lock);
return eof;
}
static int play(struct ao *ao, void **data, int samples, int flags)
{
struct ao_push_state *p = ao->api_priv;
@@ -217,8 +199,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 +212,16 @@ 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;
}
// 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 +251,67 @@ 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;
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);
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;
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
@@ -319,41 +319,13 @@ static void *playthread(void *arg)
// The most important part is that the decoder is woken up, so
// that the decoder will wake up us in turn.
MP_TRACE(ao, "buffer inactive.\n");
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) {
p->still_playing = false;
} else {
timeout = p->expected_end_time - now;
}
}
if (was_playing && !p->still_playing)
mp_input_wakeup(ao->input_ctx);
pthread_cond_signal(&p->wakeup); // for draining
if (p->still_playing && timeout > 0) {
mpthread_cond_timedwait_rel(&p->wakeup, &p->lock, timeout);
} else {
pthread_cond_wait(&p->wakeup, &p->lock);
}
mp_input_wakeup(ao->input_ctx);
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 +336,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 +347,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 +363,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 +374,7 @@ static int init(struct ao *ao)
goto err;
return 0;
err:
destroy_no_thread(ao);
ao->driver->uninit(ao);
return -1;
}
@@ -427,7 +389,6 @@ const struct ao_driver ao_api_push = {
.pause = audio_pause,
.resume = resume,
.drain = drain,
.get_eof = get_eof,
.priv_size = sizeof(struct ao_push_state),
};
@@ -464,7 +425,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];
Loaded 100 of 356 files, more files were not shown because too many files have changed in this diff. Show more