Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49275b2879 |
No files matched your search
+6
-6
@@ -20,12 +20,12 @@
|
||||
/demux/ebml_types.h
|
||||
/sub/osd_font.h
|
||||
/player/lua/*.inc
|
||||
/DOCS/man/mpv.1
|
||||
/DOCS/man/mpv.aux
|
||||
/DOCS/man/mpv.log
|
||||
/DOCS/man/mpv.out
|
||||
/DOCS/man/mpv.pdf
|
||||
/DOCS/man/mpv.toc
|
||||
/DOCS/man/*/mpv.1
|
||||
/DOCS/man/*/mpv.aux
|
||||
/DOCS/man/*/mpv.log
|
||||
/DOCS/man/*/mpv.out
|
||||
/DOCS/man/*/mpv.pdf
|
||||
/DOCS/man/*/mpv.toc
|
||||
|
||||
/waf
|
||||
/build
|
||||
|
||||
+5
-15
@@ -1,8 +1,5 @@
|
||||
language: c
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
env:
|
||||
- LIBAV=libav-stable
|
||||
- LIBAV=libav-git
|
||||
@@ -10,31 +7,24 @@ env:
|
||||
- LIBAV=ffmpeg-git
|
||||
compiler:
|
||||
- clang
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- ci
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
env: LIBAV=libav-stable
|
||||
- os: osx
|
||||
env: LIBAV=libav-git
|
||||
- os: osx
|
||||
env: LIBAV=ffmpeg-git
|
||||
|
||||
before_install: ./travis-deps libass-stable $LIBAV
|
||||
script:
|
||||
- ./bootstrap.py
|
||||
- ./waf configure
|
||||
- ./waf build
|
||||
|
||||
notifications-policy: ¬ifications-policy
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
channels:
|
||||
- "irc.freenode.org#mpv-player-dev"
|
||||
on_success: change
|
||||
on_failure: always
|
||||
<<: *notifications-policy
|
||||
@@ -1,44 +0,0 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
This file lists all changes that can cause compatibility issues when using
|
||||
mpv through the client API (libmpv and ``client.h``). Since the client API
|
||||
interfaces to input handling (commands, properties) as well as command line
|
||||
options, this list is interesting for other uses of mpv, such as the Lua
|
||||
scripting interface, key bindings in ``input.rst``, or plain command line
|
||||
usage.
|
||||
|
||||
Normally, changes to the C API that are incompatible to previous iterations
|
||||
receive a major version bump (i.e. the first version number is increased),
|
||||
while C API additions bump the minor version (i.e. the second number is
|
||||
increased). Changes to properties/commands/options may also lead to a minor
|
||||
version bump, in particular if they are incompatible.
|
||||
|
||||
The version number is the same as used for MPV_CLIENT_API_VERSION (see
|
||||
``client.h`` how to convert between major/minor version numbers and the flat
|
||||
32 bit integer).
|
||||
|
||||
Also, read the section ``Compatibility`` in ``client.h``.
|
||||
|
||||
API changes
|
||||
===========
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.5.2 is released ---
|
||||
1.4 - subtle change in X11 and "--wid" behavior
|
||||
--- mpv 0.5.0 is released ---
|
||||
1.3 - add MPV_MAKE_VERSION()
|
||||
1.2 - remove "stream-time-pos" property (no replacement)
|
||||
1.1 - remap dvdnav:// to dvd://
|
||||
- add "--cache-file", "--cache-file-size"
|
||||
- add "--colormatrix-primaries" (and property)
|
||||
- add "primaries" sub-field to image format properties
|
||||
- add "playback-time" property
|
||||
- extend the "--start" option; a leading "+", which was previously
|
||||
insignificant is now significant
|
||||
- add "cache-free" and "cache-used" properties
|
||||
- OSX: the "coreaudio" AO spdif code is split into a separate AO
|
||||
--- mpv 0.4.0 is released ---
|
||||
1.0 - the API is declared stable
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
// License: pick one of: public domain, WTFPL, ISC, Ms-PL, AGPLv3
|
||||
|
||||
// This example can be built with: qmake && make
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QStatusBar>
|
||||
#include <QMenuBar>
|
||||
#include <QMenu>
|
||||
#include <QGridLayout>
|
||||
#include <QApplication>
|
||||
|
||||
#include "qtexample.h"
|
||||
|
||||
static void wakeup(void *ctx)
|
||||
{
|
||||
// This callback is invoked from any mpv thread (but possibly also
|
||||
// recursively from a thread that is calling the mpv API). Just notify
|
||||
// the Qt GUI thread to wake up (so that it can process events with
|
||||
// mpv_wait_event()), and return as quickly as possible.
|
||||
MainWindow *mainwindow = (MainWindow *)ctx;
|
||||
QCoreApplication::postEvent(mainwindow, new QEvent(QEvent::User));
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent)
|
||||
{
|
||||
QMenu *menu = menuBar()->addMenu(tr("&File"));
|
||||
QAction *on_open = new QAction(tr("&Open"), this);
|
||||
on_open->setShortcuts(QKeySequence::Open);
|
||||
on_open->setStatusTip(tr("Open a file"));
|
||||
connect(on_open, SIGNAL(triggered()), this, SLOT(on_file_open()));
|
||||
menu->addAction(on_open);
|
||||
|
||||
statusBar();
|
||||
|
||||
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. 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_NativeWindow);
|
||||
int64_t wid = mpv_container->winId();
|
||||
mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid);
|
||||
|
||||
// Enable default bindings, because we're lazy. Normally, a player using
|
||||
// mpv as backend would implement its own key bindings.
|
||||
mpv_set_option_string(mpv, "input-default-bindings", "yes");
|
||||
|
||||
// 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);
|
||||
|
||||
// From this point on, the wakeup function will be called. The callback
|
||||
// can come from any thread, so we use the Qt QEvent mechanism to relay
|
||||
// the wakeup in a thread-safe way.
|
||||
mpv_set_wakeup_callback(mpv, wakeup, this);
|
||||
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
throw "mpv failed to initialize";
|
||||
}
|
||||
|
||||
void MainWindow::handle_mpv_event(mpv_event *event)
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case MPV_EVENT_PROPERTY_CHANGE: {
|
||||
mpv_event_property *prop = (mpv_event_property *)event->data;
|
||||
if (strcmp(prop->name, "time-pos") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double time = *(double *)prop->data;
|
||||
std::stringstream ss;
|
||||
ss << "At: " << time;
|
||||
statusBar()->showMessage(QString::fromStdString(ss.str()));
|
||||
} else if (prop->format == MPV_FORMAT_NONE) {
|
||||
// The property is unavailable, which probably means playback
|
||||
// was stopped.
|
||||
statusBar()->showMessage("");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_SHUTDOWN: {
|
||||
mpv_terminate_destroy(mpv);
|
||||
mpv = NULL;
|
||||
break;
|
||||
}
|
||||
default: ;
|
||||
// Ignore uninteresting or unknown events.
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWindow::event(QEvent *event)
|
||||
{
|
||||
// QEvent::User is sent by wakeup().
|
||||
if (event->type() == QEvent::User) {
|
||||
// Process all events, until the event queue is empty.
|
||||
while (mpv) {
|
||||
mpv_event *event = mpv_wait_event(mpv, 0);
|
||||
if (event->event_id == MPV_EVENT_NONE)
|
||||
break;
|
||||
handle_mpv_event(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return QMainWindow::event(event);
|
||||
}
|
||||
|
||||
void MainWindow::on_file_open()
|
||||
{
|
||||
QString filename = QFileDialog::getOpenFileName(this, "Open file");
|
||||
if (mpv) {
|
||||
const QByteArray c_filename = filename.toUtf8();
|
||||
const char *args[] = {"loadfile", c_filename.data(), NULL};
|
||||
mpv_command_async(mpv, 0, args);
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
if (mpv)
|
||||
mpv_terminate_destroy(mpv);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#ifndef QTEXAMPLE_H
|
||||
#define QTEXAMPLE_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
~MainWindow();
|
||||
|
||||
protected:
|
||||
virtual bool event(QEvent *event);
|
||||
|
||||
private slots:
|
||||
void on_file_open();
|
||||
|
||||
private:
|
||||
QWidget *mpv_container;
|
||||
mpv_handle *mpv;
|
||||
|
||||
void create_player();
|
||||
void handle_mpv_event(mpv_event *event);
|
||||
};
|
||||
|
||||
#endif // QTEXAMPLE_H
|
||||
@@ -1,12 +0,0 @@
|
||||
QT += core gui
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
TARGET = qtexample
|
||||
TEMPLATE = app
|
||||
|
||||
CONFIG += link_pkgconfig
|
||||
PKGCONFIG = mpv
|
||||
|
||||
SOURCES += qtexample.cpp
|
||||
HEADERS += qtexample.h
|
||||
+1
-1
@@ -25,7 +25,7 @@ from the input video. Note that not all codecs and not all formats support VFR
|
||||
encoding, and some which do have bugs when a target bitrate is specified - use
|
||||
-ofps or -oautofps to force CFR encoding in these cases.
|
||||
|
||||
Of course, the options can be stored in a profile, like this .config/mpv/mpv.conf
|
||||
Of course, the options can be stored in a profile, like this .mpv/config
|
||||
section::
|
||||
|
||||
[myencprofile]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- Rebuild the terminal status line as a lua script
|
||||
-- Rebuild the status line as a lua script
|
||||
-- Be aware that this will require more cpu power!
|
||||
|
||||
-- Add a string to the status line
|
||||
+14
-14
@@ -72,7 +72,7 @@ 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.
|
||||
@@ -101,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>``
|
||||
@@ -182,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
|
||||
@@ -277,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
|
||||
@@ -311,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>]``
|
||||
@@ -368,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>``
|
||||
@@ -381,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``
|
||||
@@ -409,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,...]]``
|
||||
@@ -437,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
|
||||
@@ -588,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
|
||||
@@ -600,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
|
||||
|
||||
+2
-24
@@ -23,7 +23,7 @@ normal driver parameters.
|
||||
where PulseAudio is used. On Windows, ``--ao=wasapi`` is preferred,
|
||||
though it might cause trouble sometimes, in which case ``--ao=dsound``
|
||||
should be used. On BSD systems, ``--ao=oss`` or `--ao=sndio`` may work
|
||||
(the latter being experimental). On OS X systems, use ``--ao=coreaudio``.
|
||||
(the latter being experimental). On OSX systems, use ``--ao=coreaudio``.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
@@ -53,10 +53,6 @@ Available audio output drivers are:
|
||||
Set the index of the mixer channel (default: 0). Consider the output of
|
||||
"``amixer scontrols``", then the index is the number that follows the
|
||||
name of the element.
|
||||
``non-interleaved``
|
||||
Allow output of non-interleaved formats (if the audio decoder uses
|
||||
this format). Currently disabled by default, because some popular
|
||||
ALSA plugins are utterly broken with non-interleaved formats.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -114,19 +110,7 @@ Available audio output drivers are:
|
||||
with fixed setups.
|
||||
|
||||
``coreaudio`` (Mac OS X only)
|
||||
Native Mac OS X audio output driver using the AudioUnits and the CoreAudio
|
||||
sound server.
|
||||
|
||||
``device_id=<id>``
|
||||
ID of output device to use (0 = default device)
|
||||
``help``
|
||||
List all available output devices with their IDs.
|
||||
|
||||
``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).
|
||||
Native Mac OS X audio output driver
|
||||
|
||||
``device_id=<id>``
|
||||
ID of output device to use (0 = default device)
|
||||
@@ -153,12 +137,6 @@ Available audio output drivers are:
|
||||
value makes the audio stream react faster, e.g. to playback speed
|
||||
changes. Default: 250.
|
||||
|
||||
``latency-hacks=<yes|no>``
|
||||
Enable hacks to workaround PulseAudio timing bugs (default: yes). If
|
||||
enabled, mpv will do elaborate latency calculations on its own. If
|
||||
disabled, it will use PulseAudio automatically updated timing
|
||||
information. Disabling this might help with e.g. networked audio.
|
||||
|
||||
``portaudio``
|
||||
PortAudio audio output driver. This works on all platforms, and has
|
||||
extensive MS Windows support.
|
||||
|
||||
+25
-37
@@ -19,14 +19,14 @@ General Changes for MPlayer to mpv
|
||||
Linux 2.4 (including VIDIX)
|
||||
* Large internal cleanups
|
||||
* Removal of support for dead platforms
|
||||
* New build system
|
||||
* No embedded copy of FFmpeg and other libraries
|
||||
* 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 OS X
|
||||
* General OS X improvements
|
||||
* Native OpenGL backend for OSX
|
||||
* General OSX improvements
|
||||
* Improvements in audio/video sync handling
|
||||
* Cleaned up terminal output
|
||||
* Gapless audio support (``--gapless-audio``)
|
||||
@@ -35,14 +35,14 @@ General Changes for MPlayer to mpv
|
||||
* 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
|
||||
* Generally preferring ffmpeg/libav over internal demuxers and decoders
|
||||
* Improvements when playing multiple files (``--fixed-vo``)
|
||||
* Screenshot improvements (instant screenshots without 1-frame delay, allow
|
||||
taking screenshots even with hardware decoding)
|
||||
* Improved support for PulseAudio
|
||||
* Generally improved MS Windows support (dealing with Unicode file names,
|
||||
* 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
|
||||
* 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
|
||||
@@ -50,19 +50,19 @@ General Changes for MPlayer to mpv
|
||||
* Improved OpenGL output (``--vo=opengl-hq``)
|
||||
* Make ``--softvol`` default (**mpv** is not a mixer control panel)
|
||||
* Improved support for .cue files
|
||||
* Screenshot improvements (can save screenshots as JPEG or PNG, configurable
|
||||
file names, support for taking screenshots with or without subtitles - the
|
||||
* Screenshot 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 built-in demuxers, using libavformat instead
|
||||
* Removal of built-in network support, using libavformat instead (also,
|
||||
* 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``)
|
||||
* 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``.
|
||||
@@ -71,10 +71,10 @@ General Changes for MPlayer to mpv
|
||||
* Wayland support
|
||||
* Support for precise scrolling which scales the parameter of commands. If the
|
||||
input doesn't support precise scrolling the scale factor stays 1.
|
||||
* OS X: Cocoa event loop is independent from MPlayer's event loop, so user
|
||||
* 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.
|
||||
* OS X: Apple Remote support.
|
||||
* OS X: Media Keys support.
|
||||
* 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).
|
||||
@@ -99,7 +99,7 @@ General Changes for MPlayer to mpv
|
||||
* 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.)
|
||||
* Native VAAPI support
|
||||
* OS X: VDA support using libavcodec hwaccel API instead of FFmpeg's decoder. Up
|
||||
* OSX: VDA support using libavcodec hwaccel API insted of FFmpeg's decoder. Up
|
||||
to 2-2.5x reduction in CPU usage.
|
||||
* Make hardware decoding in general work with the ``opengl`` video output.
|
||||
* Lua scripting (see `LUA SCRIPTING`_)
|
||||
@@ -308,40 +308,28 @@ input.conf and Slave Commands
|
||||
Slave mode
|
||||
~~~~~~~~~~
|
||||
|
||||
* Slave mode was removed. A proper slave mode application needed tons of code
|
||||
and hacks to get
|
||||
* Slave mode is broken. This mode is entirely insane in the ``old`` versions of
|
||||
MPlayer. A proper slave mode application needed tons of code and hacks to get
|
||||
it right. The main problem is that slave mode is a bad and incomplete
|
||||
interface, and to get around that, applications parsed output messages
|
||||
intended for users. It is hard to know which messages exactly are parsed by
|
||||
slave mode applications. This makes it virtually impossible to improve
|
||||
terminal output intended for users without possibly breaking something.
|
||||
|
||||
This is absolutely insane, and since initial improvements to **mpv** quickly
|
||||
made slave mode incompatible to most applications, it was removed as useless
|
||||
cruft. The client API (see below) is provided instead.
|
||||
This is absolutely insane, and **mpv** will not try to keep slave mode
|
||||
compatible. If you are a developer of a slave mode application, contact us,
|
||||
and a new and better protocol can be developed.
|
||||
|
||||
``--identify`` was replaced by the ``TOOLS/mpv_identify.sh`` wrapper script.
|
||||
|
||||
* For some time (until including release 0.4.x), mpv supported a
|
||||
``--slave-broken`` option. The following options are equivalent:
|
||||
|
||||
::
|
||||
|
||||
--input-file=/dev/stdin --input-terminal=no
|
||||
|
||||
|
||||
Assuming the system supports ``/dev/stdin``.
|
||||
|
||||
(The option was readded in 0.5.1 and sets exactly these options.)
|
||||
|
||||
* A JSON RPC protocol giving access to the client API is planned, but nothing
|
||||
has emerged yet.
|
||||
``--identify`` was already removed (``TOOLS/mpv_identify.sh`` is provided
|
||||
instead), and ``--slave-broken`` might be removed in the future.
|
||||
|
||||
* **mpv** also provides a client API, which can be used to embed the player
|
||||
by loading it as shared library. (See ``libmpv/client.h`` in the sources.)
|
||||
It might also be possible to implement a custom slave mode-like protocol
|
||||
using Lua scripting.
|
||||
|
||||
* A slave protocol is planned, but nothing has emerged yet.
|
||||
|
||||
Policy for Removed Features
|
||||
---------------------------
|
||||
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ You can encode files from one format/codec to another using this facility.
|
||||
|
||||
``--oautofps``
|
||||
Sets the output format time base to the guessed frame rate of the input
|
||||
video (simulates MEncoder behavior, useful for AVI; may cause frame drops).
|
||||
video (simulates mencoder behavior, useful for AVI; may cause frame drops).
|
||||
Note that not all codecs and not all formats support VFR encoding, and some
|
||||
which do have bugs when a target bitrate is specified - use ``--ofps`` or
|
||||
``--oautofps`` to force CFR encoding in these cases.
|
||||
@@ -74,7 +74,7 @@ You can encode files from one format/codec to another using this facility.
|
||||
.. admonition:: Example
|
||||
|
||||
"``--oac=libmp3lame --oacopts=b=128000``"
|
||||
selects 128 kbps MP3 encoding.
|
||||
selects 128kbps MP3 encoding.
|
||||
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
|
||||
+15
-38
@@ -4,18 +4,12 @@ INPUT.CONF
|
||||
The input.conf file consists of a list of key bindings, for example::
|
||||
|
||||
s screenshot # take a screenshot with the s key
|
||||
LEFT seek 15 # map the left-arrow key to seeking forward by 15 seconds
|
||||
|
||||
Each line maps a key to an input command. Keys are specified with their literal
|
||||
value (upper case if combined with ``Shift``), or a name for special keys. For
|
||||
example, ``a`` maps to the ``a`` key without shift, and ``A`` maps to ``a``
|
||||
with shift.
|
||||
|
||||
The file is 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
|
||||
|
||||
A list of special keys can be obtained with
|
||||
|
||||
``mpv --input-keylist``
|
||||
@@ -107,14 +101,14 @@ List of Input Commands
|
||||
``revert_seek`` command itself.
|
||||
|
||||
``frame_step``
|
||||
Play one frame, then pause. Does nothing with audio-only playback.
|
||||
Play one frame, then pause.
|
||||
|
||||
``frame_back_step``
|
||||
Go back by one frame, then pause. Note that this can be very slow (it tries
|
||||
to be precise, not fast), and sometimes fails to behave as expected. How
|
||||
well this works depends on whether precise seeking works correctly (e.g.
|
||||
see the ``--hr-seek-demuxer-offset`` option). Video filters or other video
|
||||
post-processing that modifies timing of frames (e.g. deinterlacing) should
|
||||
postprocessing that modifies timing of frames (e.g. deinterlacing) should
|
||||
usually work, but might make backstepping silently behave incorrectly in
|
||||
corner cases. Using ``--hr-seek-framedrop=no`` should help, although it
|
||||
might make precise seeking slower.
|
||||
@@ -199,8 +193,6 @@ List of Input Commands
|
||||
Stop playback of the current file, and play the new file immediately.
|
||||
<append>
|
||||
Append the file to the playlist.
|
||||
<append-play>
|
||||
Append the file, and if nothing is currently playing, start playback.
|
||||
|
||||
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,..``.
|
||||
@@ -572,7 +564,7 @@ Property list
|
||||
See ``--osd-level``.
|
||||
|
||||
``osd-scale`` (RW)
|
||||
OSD font size multiplier, see ``--osd-scale``.
|
||||
OSD font size multiplicator, see ``--osd-scale``.
|
||||
|
||||
``loop`` (RW)
|
||||
See ``--loop``.
|
||||
@@ -621,6 +613,11 @@ Property list
|
||||
``stream-end``
|
||||
Raw end position in bytes in source stream.
|
||||
|
||||
``stream-time-pos`` (RW)
|
||||
Time position in source stream. This only works for DVD and Bluray. This
|
||||
is probably never different from ``time-pos``, because ``time-pos`` is
|
||||
forced to this value anyway.
|
||||
|
||||
``length``
|
||||
Length of the current file in seconds. If the length is unknown, the
|
||||
property is unavailable. Note that the file duration is not always exactly
|
||||
@@ -661,9 +658,6 @@ Property list
|
||||
``playtime-remaining``
|
||||
``time-remaining`` scaled by the the current ``speed``.
|
||||
|
||||
``playback-time``
|
||||
Return the playback time, which is the time difference between start PTS and current PTS.
|
||||
|
||||
``chapter`` (RW)
|
||||
Current chapter number. The number of the first chapter is 0.
|
||||
|
||||
@@ -765,7 +759,7 @@ Property list
|
||||
::
|
||||
|
||||
MPV_FORMAT_NODE_MAP
|
||||
(key and string value for each metadata entry)
|
||||
(key and string value for each metdata entry)
|
||||
|
||||
``chapter-metadata``
|
||||
Metadata of current chapter. Works similar to ``metadata`` property. It
|
||||
@@ -797,7 +791,7 @@ Property list
|
||||
pauses itself due to low network cache.
|
||||
|
||||
``cache``
|
||||
Network cache fill state (0-100.0).
|
||||
Network cache fill state (0-100).
|
||||
|
||||
``cache-size`` (RW)
|
||||
Total network cache size in KB. This is similar to ``--cache``. This allows
|
||||
@@ -809,17 +803,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.
|
||||
|
||||
``cache-free`` (R)
|
||||
Total free cache size in KB.
|
||||
|
||||
``cache-used`` (R)
|
||||
Total used cache size in KB.
|
||||
|
||||
``cache-idle`` (R)
|
||||
Returns ``yes`` if the cache is idle, which means the cache is filled as
|
||||
much as possible, and is currently not reading more data.
|
||||
Don't use this when playing DVD or Bluray.
|
||||
|
||||
``paused-for-cache``
|
||||
Returns ``yes`` when playback is paused because of waiting for the cache.
|
||||
@@ -831,12 +815,6 @@ Property list
|
||||
or enter idle mode), and in these cases the ``eof-reached`` property will
|
||||
logically be cleared immediately after it's set.
|
||||
|
||||
``seeking``
|
||||
Returns ``yes`` if the player is currently seeking, or otherwise trying
|
||||
to restart playback. (It's possible that it returns ``yes`` while a file
|
||||
is loaded, or when switching ordered chapter segments. This is because
|
||||
the same underlying code is used for seeking and resyncing.)
|
||||
|
||||
``pts-association-mode`` (RW)
|
||||
See ``--pts-association-mode``.
|
||||
|
||||
@@ -1000,7 +978,6 @@ Property list
|
||||
"par" MPV_FORMAT_DOUBLE
|
||||
"colormatrix" MPV_FORMAT_STRING
|
||||
"colorlevels" MPV_FORMAT_STRING
|
||||
"primaries" MPV_FORMAT_STRING
|
||||
"chroma-location" MPV_FORMAT_STRING
|
||||
"rotate" MPV_FORMAT_INT64
|
||||
|
||||
@@ -1025,7 +1002,7 @@ Property list
|
||||
Estimated/measured FPS of the video filter chain output. (If no filters
|
||||
are used, this corresponds to decoder output.) This uses the average of
|
||||
the 10 past frame durations to calculate the FPS. It will be inaccurate
|
||||
if frame-dropping is involved (such as when framedrop is explicitly
|
||||
if framedropping is involved (such as when framedrop is explicitly
|
||||
enabled, or after precise seeking). Files with imprecise timestamps (such
|
||||
as Matroska) might lead to unstable results.
|
||||
|
||||
@@ -1090,7 +1067,7 @@ Property list
|
||||
See ``--sub-forced-only``.
|
||||
|
||||
``sub-scale`` (RW)
|
||||
Subtitle font size multiplier.
|
||||
Subtitle font size multiplicator.
|
||||
|
||||
``ass-use-margins`` (RW)
|
||||
See ``--ass-use-margins``.
|
||||
@@ -1270,8 +1247,8 @@ Property list
|
||||
require reloading the file for changes to take effect. If there is an
|
||||
equivalent property, prefer setting the property instead.
|
||||
|
||||
``property-list``
|
||||
Return the list of top-level properties.
|
||||
``write_watch_later_config``
|
||||
Saves current playback position.
|
||||
|
||||
Property Expansion
|
||||
------------------
|
||||
|
||||
+8
-26
@@ -2,7 +2,7 @@ LUA SCRIPTING
|
||||
=============
|
||||
|
||||
mpv can load Lua scripts. Scripts in ``~/.mpv/lua/`` will be loaded on program
|
||||
start, or if passed to ``--lua``. mpv provides the built-in module ``mp``, which
|
||||
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.
|
||||
|
||||
@@ -74,10 +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.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
|
||||
@@ -98,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.
|
||||
@@ -113,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.
|
||||
|
||||
@@ -128,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
|
||||
@@ -163,7 +159,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
(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``
|
||||
@@ -201,7 +197,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
|
||||
|
||||
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``:
|
||||
@@ -303,20 +299,6 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
with ``add_timeout()``), this starts the timer from the beginning,
|
||||
using the initially configured timeout.
|
||||
|
||||
``timeout`` (RW)
|
||||
This field contains the current timeout period. This value is not
|
||||
updated as time progresses. It's only used to calculate when the
|
||||
timer should fire next when the timer expires.
|
||||
|
||||
If you write this, you can call ``t:kill() ; t:resume()`` to reset
|
||||
the current timeout to the new one. (``t:stop()`` won't use the
|
||||
new timeout.)
|
||||
|
||||
``oneshot`` (RW)
|
||||
Whether the timer is periodic (``false``) or fires just once
|
||||
(``true``). This value is used when the timer expires (but before
|
||||
the timer callback function fn is run).
|
||||
|
||||
|
||||
``mp.get_opt(key)``
|
||||
Return a setting from the ``--lua-opts`` option. It's up to the user and
|
||||
@@ -377,7 +359,7 @@ are useful only in special situations.
|
||||
control what the Lua script does (instead of being called by the default
|
||||
event loop), you can set the global variable ``mp_event_loop`` to your
|
||||
own function running the event loop. From your event loop, you should call
|
||||
``mp.dispatch_events()`` to dequeue and dispatch mpv events.
|
||||
``mp.dispatch_events()`` to unqueue and dispatch mpv events.
|
||||
|
||||
If the ``allow_wait`` parameter is set to ``true``, the function will block
|
||||
until the next event is received or the next timer expires. Otherwise (and
|
||||
|
||||
+59
-42
@@ -2,7 +2,7 @@ mpv
|
||||
###
|
||||
|
||||
##############
|
||||
a media player
|
||||
a movie player
|
||||
##############
|
||||
|
||||
:Copyright: GPLv2+
|
||||
@@ -19,7 +19,7 @@ SYNOPSIS
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
**mpv** is a media player based on MPlayer and mplayer2. It supports a wide variety of video
|
||||
**mpv** is a movie player based on MPlayer and mplayer2. It supports a wide variety of video
|
||||
file formats, audio and video codecs, and subtitle types. Special input URL
|
||||
types are available to read input from a variety of sources other than disk
|
||||
files. Depending on platform, a variety of different video and audio output
|
||||
@@ -40,13 +40,16 @@ Keyboard Control
|
||||
----------------
|
||||
|
||||
LEFT and RIGHT
|
||||
Seek backward/forward 5 seconds. Shift+arrow does a 1 second exact seek
|
||||
Seek backward/forward 10 seconds. Shift+arrow does a 1 second exact seek
|
||||
(see ``--hr-seek``).
|
||||
|
||||
UP and DOWN
|
||||
Seek forward/backward 1 minute. Shift+arrow does a 5 second exact seek (see
|
||||
``--hr-seek``).
|
||||
|
||||
PGUP and PGDWN
|
||||
Seek forward/backward 10 minutes.
|
||||
|
||||
[ and ]
|
||||
Decrease/increase current playback speed by 10%.
|
||||
|
||||
@@ -66,11 +69,11 @@ p / SPACE
|
||||
Pause (pressing again unpauses).
|
||||
|
||||
\.
|
||||
Step forward. Pressing once will pause, every consecutive press will
|
||||
Step forward. Pressing once will pause movie, every consecutive press will
|
||||
play one frame and then go into pause mode again.
|
||||
|
||||
,
|
||||
Step backward. Pressing once will pause, every consecutive press will
|
||||
Step backward. Pressing once will pause movie, every consecutive press will
|
||||
play one frame in reverse and then go into pause mode again.
|
||||
|
||||
q / ESC
|
||||
@@ -80,12 +83,21 @@ Q
|
||||
Like ``q``, but store the current playback position. Playing the same file
|
||||
later will resume at the old playback position if possible.
|
||||
|
||||
U
|
||||
Stop playing (and quit if ``--idle`` is not used).
|
||||
|
||||
\+ and -
|
||||
Adjust audio delay by +/- 0.1 seconds.
|
||||
|
||||
/ and *
|
||||
Decrease/increase volume.
|
||||
|
||||
9 and 0
|
||||
Decrease/increase volume.
|
||||
|
||||
( and )
|
||||
Adjust audio balance in favor of left/right channel.
|
||||
|
||||
m
|
||||
Mute sound.
|
||||
|
||||
@@ -95,6 +107,9 @@ m
|
||||
\#
|
||||
Cycle through the available audio tracks.
|
||||
|
||||
TAB (MPEG-TS and libavformat only)
|
||||
Cycle through the available programs.
|
||||
|
||||
f
|
||||
Toggle fullscreen (see also ``--fs``).
|
||||
|
||||
@@ -104,10 +119,7 @@ T
|
||||
w and e
|
||||
Decrease/increase pan-and-scan range.
|
||||
|
||||
o (also P)
|
||||
Show progression bar, elapsed time and total duration on the OSD.
|
||||
|
||||
O
|
||||
o
|
||||
Toggle OSD states: none / seek / seek + timer / seek + timer + total time.
|
||||
|
||||
d
|
||||
@@ -120,6 +132,9 @@ v
|
||||
j and J
|
||||
Cycle through the available subtitles.
|
||||
|
||||
F
|
||||
Toggle displaying "forced subtitles".
|
||||
|
||||
x and z
|
||||
Adjust subtitle delay by +/- 0.1 seconds.
|
||||
|
||||
@@ -140,14 +155,23 @@ S
|
||||
I
|
||||
Show filename on the OSD.
|
||||
|
||||
PGUP and PGDWN
|
||||
P
|
||||
Show progression bar, elapsed time and total duration on the OSD.
|
||||
|
||||
! and @
|
||||
Seek to the beginning of the previous/next chapter. In most cases,
|
||||
"previous" will actually go to the beginning of the current chapter; see
|
||||
``--chapter-seek-threshold``.
|
||||
|
||||
D
|
||||
D (``--vo=vdpau``, ``--vf=yadif`` only)
|
||||
Activate/deactivate deinterlacer.
|
||||
|
||||
A
|
||||
Cycle through the available DVD angles.
|
||||
|
||||
c
|
||||
Change YUV colorspace.
|
||||
|
||||
(The following keys are valid only when using a video output that supports the
|
||||
corresponding adjustment, or the software equalizer (``--vf=eq``).)
|
||||
|
||||
@@ -163,24 +187,24 @@ corresponding adjustment, or the software equalizer (``--vf=eq``).)
|
||||
7 and 8
|
||||
Adjust saturation.
|
||||
|
||||
(The following keys are valid only on OS X.)
|
||||
(The following keys are valid only on OSX.)
|
||||
|
||||
command + 0
|
||||
Resize video window to half its original size.
|
||||
Resize movie window to half its original size.
|
||||
(On other platforms, you can bind keys to change the ``window-scale``
|
||||
property.)
|
||||
|
||||
command + 1
|
||||
Resize video window to its original size.
|
||||
Resize movie window to its original size.
|
||||
|
||||
command + 2
|
||||
Resize video window to double its original size.
|
||||
Resize movie window to double its original size.
|
||||
|
||||
command + f
|
||||
Toggle fullscreen (see also ``--fs``).
|
||||
|
||||
command + [ and command + ]
|
||||
Set video window alpha.
|
||||
Set movie window alpha.
|
||||
|
||||
(The following keys are valid if you have a keyboard with multimedia keys.)
|
||||
|
||||
@@ -199,6 +223,12 @@ support.)
|
||||
h and k
|
||||
Select previous/next channel.
|
||||
|
||||
n
|
||||
Change norm.
|
||||
|
||||
u
|
||||
Change channel list.
|
||||
|
||||
Mouse Control
|
||||
-------------
|
||||
|
||||
@@ -308,7 +338,7 @@ Location and Syntax
|
||||
You can put all of the options in configuration files which will be read every
|
||||
time mpv is run. The system-wide configuration file 'mpv.conf' is in your
|
||||
configuration directory (e.g. ``/etc/mpv`` or ``/usr/local/etc/mpv``), the
|
||||
user-specific one is ``~/.config/mpv/mpv.conf``.
|
||||
user-specific one is ``~/.mpv/config``.
|
||||
User-specific options override system-wide options and options given on the
|
||||
command line override either. The syntax of the configuration files is
|
||||
``option=<value>``; everything after a *#* is considered a comment. Options
|
||||
@@ -343,8 +373,8 @@ 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
|
||||
configuration file for a file called 'movie.avi', create a file named
|
||||
'movie.avi.conf' with the file-specific options in it and put it in
|
||||
``~/.mpv/``. You can also put the configuration file in the same directory
|
||||
as the file to be played, as long as you give the ``--use-filedir-conf``
|
||||
option (either on the command line or in your global config file). If a
|
||||
@@ -419,8 +449,8 @@ PROTOCOLS
|
||||
Play a path from Samba share.
|
||||
|
||||
``bd://[title][/device]`` ``--bluray-device=PATH``
|
||||
Play a Blu-Ray disc. Currently, this does not accept ISO files. Instead,
|
||||
you must mount the ISO file as filesystem, and point ``--bluray-device``
|
||||
Play a Blu-Ray disc. Currently, this does not accept iso files. Instead,
|
||||
you must mount the iso file as filesystem, and point ``--bluray-device``
|
||||
to the mounted directly.
|
||||
|
||||
``bdnav://[title][/device]``
|
||||
@@ -509,23 +539,15 @@ ENVIRONMENT VARIABLES
|
||||
There are a number of environment variables that can be used to control the
|
||||
behavior of mpv.
|
||||
|
||||
``HOME``, ``XDG_CONFIG_HOME``
|
||||
Used to determine mpv config directory. If ``XDG_CONFIG_HOME`` is not set,
|
||||
``$HOME/.config/mpv`` is used.
|
||||
|
||||
``$HOME/.mpv`` is always added to the list of config search paths with a
|
||||
lower priority.
|
||||
|
||||
``XDG_CONFIG_DIRS``
|
||||
If set, XDG-style system configuration directories are used. Otherwise,
|
||||
the UNIX convention (``PREFIX/etc/mpv/``) is used.
|
||||
``HOME``
|
||||
Used to determine mpv config directory: ``$HOME/.mpv``
|
||||
|
||||
``TERM``
|
||||
Used to determine terminal type.
|
||||
|
||||
``MPV_HOME``
|
||||
Directory where mpv looks for user settings. Overrides ``HOME``, and mpv
|
||||
will try to load the config file as ``$MPV_HOME/mpv.conf``.
|
||||
will try to load the config file as ``$MPV_HOME/config``.
|
||||
|
||||
``MPV_VERBOSE`` (see also ``-v`` and ``--msg-level``)
|
||||
Set the initial verbosity level across all message modules (default: 0).
|
||||
@@ -638,23 +660,18 @@ FILES
|
||||
``/usr/local/etc/mpv/mpv.conf``
|
||||
mpv system-wide settings (depends on ``--prefix`` passed to configure)
|
||||
|
||||
``~/.config/mpv/mpv.conf``
|
||||
``~/.mpv/config``
|
||||
mpv user settings
|
||||
|
||||
``~/.config/mpv/input.conf``
|
||||
key bindings (see `INPUT.CONF`_ section)
|
||||
``~/.mpv/input.conf``
|
||||
input bindings (see ``--input-keylist`` for the full list)
|
||||
|
||||
``~/.config/mpv/lua/``
|
||||
``~/.mpv/lua/``
|
||||
All files in this directly are loaded as if they were passed to the
|
||||
``--lua`` option. They are loaded in alphabetical order, and sub-directories
|
||||
and files with no ``.lua`` extension are ignored. The ``--load-scripts=no``
|
||||
option disables loading these files.
|
||||
|
||||
Note that the environment variables ``$XDG_CONFIG_HOME`` and ``$MPV_HOME`` can
|
||||
override the standard directory ``~/.config/mpv/``.
|
||||
|
||||
Also, the old config location at ``~/.mpv/`` is still read, and if the XDG
|
||||
variant does not exist, will still be preferred.
|
||||
|
||||
EXAMPLES OF MPV USAGE
|
||||
=====================
|
||||
@@ -672,7 +689,7 @@ Play only chapters 5, 6, 7:
|
||||
Play only titles 5, 6, 7:
|
||||
``mpv dvd://5-7``
|
||||
|
||||
Play a multi-angle DVD:
|
||||
Play a multiangle DVD:
|
||||
``mpv dvd://1 --dvd-angle=2``
|
||||
|
||||
Play from a different DVD device:
|
||||
|
||||
+1727
-1851
File diff suppressed because it is too large.
Load diff
+4
-4
@@ -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.
|
||||
|
||||
============= ================================================
|
||||
|
||||
+16
-22
@@ -47,7 +47,7 @@ Available filters are:
|
||||
|
||||
``crop[=w:h:x:y]``
|
||||
Crops the given part of the image and discards the rest. Useful to remove
|
||||
black bands from widescreen videos.
|
||||
black bands from widescreen movies.
|
||||
|
||||
``<w>,<h>``
|
||||
Cropped width and height, defaults to original width and height.
|
||||
@@ -55,7 +55,7 @@ Available filters are:
|
||||
Position of the cropped picture, defaults to center.
|
||||
|
||||
``expand[=w:h:x:y:aspect:round]``
|
||||
Expands (not scales) video resolution to the given value and places the
|
||||
Expands (not scales) movie resolution to the given value and places the
|
||||
unscaled original at coordinates x, y.
|
||||
|
||||
``<w>,<h>``
|
||||
@@ -93,7 +93,7 @@ Available filters are:
|
||||
|
||||
``scale[=w:h:param:param2:chr-drop:noup:arnd``
|
||||
Scales the image with the software scaler (slow) and performs a YUV<->RGB
|
||||
color space conversion (see also ``--sws``).
|
||||
colorspace conversion (see also ``--sws``).
|
||||
|
||||
All parameters are optional.
|
||||
|
||||
@@ -120,9 +120,9 @@ Available filters are:
|
||||
0.33:0.33 Mitchell-Netravali spline
|
||||
1.00:0.00 cubic B-spline
|
||||
|
||||
--sws=7 (Gaussian): sharpness (0 (soft) - 100 (sharp))
|
||||
--sws=7 (gaussian): sharpness (0 (soft) - 100 (sharp))
|
||||
|
||||
--sws=9 (Lanczos): filter length (1-10)
|
||||
--sws=9 (lanczos): filter length (1-10)
|
||||
|
||||
``<chr-drop>``
|
||||
chroma skipping
|
||||
@@ -222,8 +222,8 @@ Available filters are:
|
||||
====================== =====================
|
||||
|
||||
``noformat[=fmt]``
|
||||
Restricts the color space for the next filter without doing any conversion.
|
||||
Unlike the format filter, this will allow any color space except the one
|
||||
Restricts the colorspace for the next filter without doing any conversion.
|
||||
Unlike the format filter, this will allow any colorspace except the one
|
||||
you specify.
|
||||
|
||||
.. note:: For a list of available formats, see ``noformat=fmt=help``.
|
||||
@@ -232,7 +232,7 @@ Available filters are:
|
||||
Format name, e.g. rgb15, bgr24, 420p, etc. (default: 420p).
|
||||
|
||||
``pp[=[filter1[:option1[:option2...]]/[-]filter2...]]``
|
||||
Enables the specified chain of post-processing subfilters. Subfilters must
|
||||
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. All subfilters share
|
||||
@@ -336,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]``
|
||||
@@ -356,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
|
||||
@@ -443,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
|
||||
@@ -503,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.
|
||||
@@ -617,7 +617,7 @@ Available filters are:
|
||||
|
||||
``phase[=t|b|p|a|u|T|B|A|U][:v]``
|
||||
Delay interlaced video by one field time so that the field order changes.
|
||||
The intended use is to fix PAL videos that have been captured with the
|
||||
The intended use is to fix PAL movies that have been captured with the
|
||||
opposite field order to the film-to-video transfer. The options are:
|
||||
|
||||
``t``
|
||||
@@ -769,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
|
||||
@@ -803,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>``
|
||||
@@ -969,9 +969,3 @@ Available filters are:
|
||||
Use default VDPAU scaling (default).
|
||||
1-9
|
||||
Apply high quality VDPAU scaling (needs capable hardware).
|
||||
|
||||
``buffer=<num>``
|
||||
Buffer ``<num>`` frames in the filter chain. This filter is probably pretty
|
||||
useless, except for debugging. (Note that this won't help smoothing out
|
||||
latencies with decoding, because the filter will never output a frame if
|
||||
the buffer isn't full, except on EOF.)
|
||||
+25
-25
@@ -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
|
||||
@@ -282,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>``
|
||||
@@ -349,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``
|
||||
@@ -452,7 +452,7 @@ Available video output drivers are:
|
||||
auto
|
||||
auto-select (default)
|
||||
cocoa
|
||||
Cocoa/OS X
|
||||
Cocoa/OSX
|
||||
win
|
||||
Win32/WGL
|
||||
x11
|
||||
@@ -479,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.
|
||||
@@ -487,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.
|
||||
|
||||
@@ -500,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.
|
||||
|
||||
@@ -524,7 +524,7 @@ Available video output drivers are:
|
||||
for mastering instead of the true curve. Most notably, anything in the
|
||||
Apple ecosystem uses this approximation - including all programs
|
||||
compatible with it. It's a sound idea to try enabling this flag first
|
||||
when watching videos and shows to see if things look better that way.
|
||||
when watching movies and shows to see if things look better that way.
|
||||
|
||||
This only affects the output when using either ``icc-profile`` or ``srgb``.
|
||||
|
||||
@@ -562,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.
|
||||
@@ -571,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``
|
||||
@@ -706,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.
|
||||
@@ -748,7 +748,7 @@ Available video output drivers are:
|
||||
auto
|
||||
auto-select (default)
|
||||
cocoa
|
||||
Cocoa/OS X
|
||||
Cocoa/OSX
|
||||
win
|
||||
Win32/WGL
|
||||
x11
|
||||
|
||||
+1
-15
@@ -24,14 +24,11 @@ Release procedure
|
||||
|
||||
- Create and/or update the ``VERSION`` file.
|
||||
|
||||
- Update ``DOCS/client-api-changes.rst`` (on major releases).
|
||||
|
||||
- Create tag v0.X.Y.
|
||||
|
||||
- Push branch and tag to GitHub.
|
||||
|
||||
- Create a new GitHub release using the content of ``RELEASE_NOTES`` related to
|
||||
the new version.
|
||||
- Create a new GitHub release using the content of ``RELEASE_NOTES``.
|
||||
|
||||
Release notes template
|
||||
----------------------
|
||||
@@ -39,9 +36,6 @@ Release notes template
|
||||
Here is a template that can be used for writing the ``RELEASE_NOTES`` file.
|
||||
|
||||
```markdown
|
||||
Release 0.X.Y
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
@@ -61,11 +55,3 @@ This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log <start>..<end>`` in
|
||||
the git repository.
|
||||
```
|
||||
|
||||
Note that the "Release 0.X.Y" title should be removed when creating a new GitHub
|
||||
release.
|
||||
|
||||
When creating a new point release its changes should be added on top of the
|
||||
``RELEASE_NOTES`` file (with the appropriate title) so that all the changes in
|
||||
the current 0.X branch will be included. This way the ``RELEASE_NOTES`` file
|
||||
can be used by distributors as changelog for point releases too.
|
||||
+12
-12
@@ -17,12 +17,12 @@ player/*.c:
|
||||
* check help options etc. (call handle_help_options()), possibly exit
|
||||
* call play_files() function that works down the playlist:
|
||||
* run idle loop (idle_loop()), until there are files in the
|
||||
playlist or an exit command was given (only if --idle it set)
|
||||
playlist or an exit command was given (slave mode only)
|
||||
* actually load and play a file in play_current_file():
|
||||
* run all the dozens of functions to load the file and
|
||||
initialize playback
|
||||
* run a small loop that does normal playback, until the file is
|
||||
done or a command terminates playback
|
||||
done or a slave command terminates playback
|
||||
(on each iteration, run_playloop() is called, which is rather
|
||||
big and complicated - it decodes some audio and video on
|
||||
each frame, waits for input, etc.)
|
||||
@@ -82,7 +82,7 @@ talloc.h & talloc.c:
|
||||
(See ta/README for details.)
|
||||
|
||||
player/command.c:
|
||||
This contains the implementation for client API commands and properties.
|
||||
This contains the implementation for slave commands and properties.
|
||||
Properties are essentially dynamic variables changed by certain commands.
|
||||
This is basically responsible for all user commands, like initiating
|
||||
seeking, switching tracks, etc. It calls into other player/*.c files,
|
||||
@@ -92,10 +92,9 @@ player/core.h:
|
||||
Data structures and function prototypes for most of player/*.c. They are
|
||||
usually not accessed by other parts of mpv for the sake of modularization.
|
||||
|
||||
player/client.c:
|
||||
This implements the client API (libmpv/client.h). For the most part, this
|
||||
just calls into other parts of the player. This also manages a ringbuffer
|
||||
of events from player to clients.
|
||||
Note that there are lots of global variables floating around everywhere
|
||||
else. This is an ongoing transition, and eventually there should be no
|
||||
global variables anymore.
|
||||
|
||||
options/options.h, options/options.c
|
||||
options.h contains the global option struct MPOpts. The option declarations
|
||||
@@ -112,14 +111,15 @@ options/options.h, options/options.c
|
||||
|
||||
input/input.c:
|
||||
This translates keyboard input comming from libvo and other sources (such
|
||||
as remote control devices like Apple IR or client API commands) to the
|
||||
as remote control devices like Apple IR or slave mode commands) to the
|
||||
key bindings listed in the user's (or the builtin) input.conf and turns
|
||||
them into items of type struct mp_cmd. These commands are queued, and read
|
||||
by playloop.c. They get pushed with run_command() to command.c.
|
||||
|
||||
Note that keyboard input and commands used by the client API are the same.
|
||||
The client API only uses the command parser though, and has its own queue
|
||||
of input commands somewhere else.
|
||||
Note that keyboard input and slave mode input are essentially the same
|
||||
things. Just looking at input.conf should make this clear. (The other
|
||||
direction of slave mode communication, mpv to application, consists of
|
||||
random mp_msg() calls all over the code in all parts of the player.)
|
||||
|
||||
common/msg.h:
|
||||
All terminal output should go through mp_msg().
|
||||
@@ -213,7 +213,7 @@ audio/out/:
|
||||
sub/:
|
||||
Contains subtitle and OSD rendering.
|
||||
|
||||
osd.c/.h is actually the OSD code. It queries dec_sub.c to retrieve
|
||||
sub.c/.h is actually the OSD code. It queries dec_sub.c to retrieve
|
||||
decoded/rendered subtitles. osd_libass.c is the actual implementation of
|
||||
the OSD text renderer (which uses libass, and takes care of all the tricky
|
||||
fontconfig/freetype API usage and text layouting).
|
||||
|
||||
@@ -136,4 +136,4 @@ To contact the `mpv` team in private write to `mpv-team@googlegroups.com`.
|
||||
[mpv-users]: https://groups.google.com/forum/?hl=en#!forum/mpv-users
|
||||
[mpv-devel]: https://groups.google.com/forum/?hl=en#!forum/mpv-devel
|
||||
[ffmpeg_vs_libav]: https://github.com/mpv-player/mpv/wiki/FFmpeg-versus-Libav
|
||||
[release-policy]: https://github.com/mpv-player/mpv/blob/master/DOCS/release-policy.md
|
||||
[release-policy]: https://github.com/mpv-player/mpv/blob/master/release-policy.md
|
||||
+101
-159
@@ -1,173 +1,115 @@
|
||||
Release 0.5.4
|
||||
=============
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fix generation of watch later config file for multiline file names when
|
||||
--write-filename-in-watch-later-config is used (avoid writing the new lines
|
||||
in the config, which would make it possible for the filename to set options).
|
||||
- Fix random crashes on audio uninit.
|
||||
|
||||
This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log 6f7f7c3..1a7b6f1`` in
|
||||
the git repository.
|
||||
|
||||
Release 0.5.3
|
||||
=============
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fix build failure introduced by the previous release.
|
||||
|
||||
This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log 41f2ad3..41f2ad3`` in
|
||||
the git repository.
|
||||
|
||||
Release 0.5.2
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
- Inherit event flags from parent X11 window when using --wid.
|
||||
- Properly resume playlists loaded at runtime (this makes mpv try to resume a
|
||||
playlist when loaded like "mpv playlist.m3u").
|
||||
- Don't honor $XDG_CONFIG_DIRS anymore for global paths (this fixes a regression
|
||||
on systems that set the variable by default to somewhere else then /etc,
|
||||
making mpv skip previously created config files).
|
||||
- Allow up to 256 MB of extradata to make broken files work.
|
||||
- Drop support for Libav 9 and earlier FFmpeg releases. At least FFmpeg 2.1.4 or
|
||||
Libav 10 are now required.
|
||||
- Change cache options to take kilobytes instead of percentages, and raise the
|
||||
default values.
|
||||
- Lower --auto-pause/resume defaults values to improve web streams playback.
|
||||
- Use dvdnav support instead of the internal dvd implementation for dvd:// URLs
|
||||
(the old implementation is available under dvdread://).
|
||||
- Rename some options to make them more consistent. Here's a partial list of
|
||||
renamed options:
|
||||
|
||||
+ Audio-related:
|
||||
|
||||
* --audiofile → --audio-file
|
||||
* --audiofile-cache → --audio-file-cache
|
||||
* --channels → --audio-channels
|
||||
* --format → --audio-format
|
||||
* --srate → --audio-samplerate
|
||||
|
||||
+ Input-related:
|
||||
|
||||
* --ar → --input-appleremote
|
||||
* --consolecontrols → --input-terminal
|
||||
* --media-keys → --input-media-keys
|
||||
* --joystick → --input-joystick
|
||||
* --lirc → --input-lirc
|
||||
* --lircconf → --input-lirc-conf
|
||||
* --mouse-movements → --input-cursor
|
||||
* --right-alt-gr → --input-right-alt-gr
|
||||
|
||||
+ Terminal-related:
|
||||
|
||||
* --msgcolor → --msg-color
|
||||
* --msglevel → --msg-level
|
||||
* --msgmodule → --msg-module
|
||||
* --msgtime → --msg-time
|
||||
* --playing-msg → --term-playing-msg
|
||||
* --status-msg → --term-status-msg
|
||||
|
||||
+ Subtitle-related:
|
||||
|
||||
* --ass → --sub-ass
|
||||
* --autosub → --sub-auto
|
||||
* --autosub-match → --sub-auto-match
|
||||
* --sub → --sub-file
|
||||
* --subcp → --sub-codepage
|
||||
* --subfps → --sub-fps
|
||||
|
||||
+ Video-related:
|
||||
|
||||
* --aspect → --video-aspect
|
||||
* --fstype → --x11-fstype
|
||||
* --native-fs → --fs-missioncontrol
|
||||
* --name → --x11-name
|
||||
|
||||
- Remove global options variables for --tv, --pvr, --dvb and --cdda.
|
||||
- Move post-processing support to a separate filter (vdpaupp) for the vdpau vo,
|
||||
and deprecate the old vdpau post-processing options.
|
||||
- Remove radio:// support.
|
||||
- Remove support for ASX, SMIL and NSC playlist formats.
|
||||
- Re-introduce pretty-printing for module names with --msg-module.
|
||||
- Load Lua scripts under `$MPV_HOME/lua/` (default `~/.mpv/lua/`) automatically
|
||||
(this can be disabled by using `--load-scripts=no`).
|
||||
- Corrupt/broken frames are not shown by default anymore (this can be re-enabled
|
||||
by using --vd-lavc-show-all=yes).
|
||||
- Automatically copy metadata from the input file to the output when encoding
|
||||
(this can be disabled by using --no-ometadata).
|
||||
- Change --sub-file semantics: if multiple --sub-file are used, the subtitle
|
||||
tracks can be cycled at runtime and used with --sid and --secondary-sid.
|
||||
- Make the subtitle font size relative to the window, instead of the video, when
|
||||
--sub-scale-with-window=yes is used.
|
||||
- Change default ao from wasapi to dsound on Windows.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Restore old speed change behavior (this fixes a temporary lack of sound after
|
||||
restoring normal speed e.g. when pressing Backspace).
|
||||
- Fix quit handling when seeking past EOF while paused.
|
||||
- Prevent burning CPU when seeking while paused.
|
||||
- Don't add the playlist base path twice when using --playlist.
|
||||
- Fix a crash when the initialization of the EGL context fails on Wayland.
|
||||
- Make EOF handling properly event-based (this fixes a bug that made mpv hang
|
||||
when changing from one file to the next with --gapless-audio=no).
|
||||
- Assume icy title data is terminated with ';' (this fixes handling of streams
|
||||
with metadata containing apostrophes).
|
||||
|
||||
This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log 0f1d6c0..e3d3a47`` in
|
||||
the git repository.
|
||||
|
||||
Release 0.5.1
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
- Add audio/flac to the MIME type list in mpv.desktop.
|
||||
- The --slave-broken option has been reintroduced and is equivalent to using
|
||||
--input-file=/dev/stdin and --input-terminal=no.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fix screenshots with anamorphic videos in the vdpau vo.
|
||||
- Fix a crash happening when mpv fails to create a vdpau mixer in the vdpau vo.
|
||||
- Don't quit when the --loop-file option is used.
|
||||
- Always load the cursor theme in the wayland vo (this fixes a crash caused by
|
||||
moving the mouse cursor over the video).
|
||||
- Properly return non-zero from the zsh completion script when no matches are
|
||||
found (this fixes the use of user-defined zsh completion matchers).
|
||||
- Fix the description of the opengl-hq vo default options in the manpage.
|
||||
- Remove duplicated content from the manpage that was accidentally added in the
|
||||
manpage reorganization from the previous release.
|
||||
- Avoid clobbering playback position on video end (when both audio and video
|
||||
streams reached the end, the playback poisition was briefly set at 0).
|
||||
- Avoid overwriting user input in the coreaudio_device ao.
|
||||
|
||||
This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log 6729e39..fdd648b`` in
|
||||
the git repository.
|
||||
|
||||
Release 0.5.0
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
- Support setting the start time relative to the start PTS with --start.
|
||||
- Use /dev/sr0 as default cdrom/dvd device on Linux.
|
||||
- Change "cache" property representation to float.
|
||||
- Split the ao_coreaudio audio output into ao_coreaudio (which uses the
|
||||
AudioUnits and the CoreAudio sound server) and ao_coreaudio_device (which uses
|
||||
direct device access).
|
||||
- Complete supported URL schemes in the zsh completion script.
|
||||
- Enable compiler optimizations (-O2) during build.
|
||||
- Allow precise seeking with percent-based seeks too.
|
||||
- Do precise seeks when clicking on the OSC seekbar.
|
||||
- Remove support for older VDA hwaccel versions on Mac OS X (requires recent
|
||||
versions of ffmpeg/libav).
|
||||
- Remove the last remains of slave mode (--slave-broken option). Support for
|
||||
reading input commands from stdin can be emulated using: --input-terminal=no
|
||||
--input-file=/dev/stdin.
|
||||
- Some key binding were remapped or removed:
|
||||
+ "LEFT" and "RIGHT" have been remapped to seek 5 seconds instead of 10.
|
||||
+ "c" for changing the YUV color space was unmapped.
|
||||
+ OSD level cycling has been remapped from "o" to "O".
|
||||
+ "o" is now mapped to the show_progress command (like "P").
|
||||
+ "PGUP" and "PGDWN" have been remapped to chapter-based seeks (the old
|
||||
bindings "@" and "!" are still available). The old behvaiour has been
|
||||
remapped to "Shift+PGUP" and "Shift+PGDWM".
|
||||
+ Increasing/decreasing audio delay have been remapped from "+" and "-" to
|
||||
"Ctrl++" and "Ctrl+-".
|
||||
+ "(" and ")" for adjusting audio balance have been unmapped.
|
||||
+ "F" for toggling the "sub-forced-only" option has been unmapped.
|
||||
+ "TAB" for cycling TS programs has been unmapped.
|
||||
+ "A" for cycling DVD angle has been unmapped.
|
||||
+ "U" for stopping playback has been unmapped.
|
||||
+ "l" and "L" for cycling quvi format have been unmapped.
|
||||
- Make it possible to build the client API (libmpv) on Mac OS X.
|
||||
- Do necessary checks for nested Wayland compositors which only have limited
|
||||
interfaces.
|
||||
- The options in the manpage have been re-organized into sections and ordered by
|
||||
usage to make it easier to navigate.
|
||||
- The "hard" framedrop mode has been removed (this behaviour is still available
|
||||
via the --vd-lavc-framedrop option).
|
||||
- Show the basename of a subtitle file only (without the directory part).
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Use a default color space for files with no video (this fixes color managed
|
||||
OSD messages when no video channel is present).
|
||||
- Fix key equivalent dispatching for Cocoa on Mac OS X.
|
||||
- Disable non-interleaved audio formats by default with ao_alsa, since some
|
||||
ALSA plugins don't cope well with them (non-interleaved formats can still be
|
||||
enabled using the "non-interleaved" ao_alsa option).
|
||||
- Fix deadlock in the client API when calling mpv_terminate_destroy() before
|
||||
mpv_initialize().
|
||||
- Fix flushing of filter chain (this fixes a bug where the last frames would get
|
||||
dropped on EOF, if buffered by filters).
|
||||
- Actually use the channels.conf configuration file for DVB.
|
||||
- Restore the --dvbin-file option.
|
||||
- Fix off by one error in the command parser which caused a segfault when using
|
||||
the mp.commandv() Lua function with 10 or more arguments.
|
||||
- Fix tv:// playback with missing timestamps.
|
||||
- Fix display of cover art inside audio files with the vdpau vo.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
- 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).
|
||||
- Add client API in the form of the libmpv library, which is intended to be a
|
||||
stable API to get some rough control over the player (this is not supported on
|
||||
Mac OS X).
|
||||
- Add option for approximating BT.709 gamma for the opengl vo (similarly to many
|
||||
programs in the Apple ecosystem).
|
||||
- Add support for non-BT.709 primaries.
|
||||
- Add support for BT.2020 colorspace.
|
||||
- Generate and install zsh completion script (disabled by default since it may
|
||||
interfere with cross-compilation).
|
||||
- Add support for looping a single file (instead of the whole playlist) with the
|
||||
--loop-file option.
|
||||
- Add VapourSynth video filter (this has various restrictions, which are listed
|
||||
in the manpage).
|
||||
- Add support for ReplayGain in the volume audio filter.
|
||||
- Add a --config-dir option to force config directory.
|
||||
- Add support for GPU switching on Mac OS X.
|
||||
- Add support for automatic selection of color profiles on Mac OS X.
|
||||
- Add support for binding commands to sequences of keys.
|
||||
- Add support for passing options to Lua scripts with --lua-opts.
|
||||
- Add some filesystem utility functions to the Lua API.
|
||||
- Add functions for observing property changes to the Lua API .
|
||||
- Add support for creating a cache file on the filesystem with --cache-file.
|
||||
- Add support for window dragging by grabbing with the x11 vo.
|
||||
- Add option to write file name to watch later file.
|
||||
- Add support for a "weak" gapless mode, and make it default (the old mode can
|
||||
be enabled with --gapless-audio or --gapless-audio=yes).
|
||||
- Automatically detect network filesystems on Linux and Windows.
|
||||
|
||||
This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log cb250d4..52f2f67`` in
|
||||
complete change log can be viewed by running ``git log 56ce2a3..8bb7d42`` in
|
||||
the git repository.
|
||||
@@ -15,10 +15,10 @@ def is_user_lib(objfile, libname):
|
||||
return not sys_re.match(libname) and \
|
||||
not usr_re.match(libname) and \
|
||||
not exe_re.match(libname) and \
|
||||
not "libobjc." in libname and \
|
||||
not "libSystem." in libname and \
|
||||
not "libc." in libname and \
|
||||
not "libgcc." in libname and \
|
||||
not "libobjc" in libname and \
|
||||
not "libSystem" in libname and \
|
||||
not "libc" in libname and \
|
||||
not "libgcc" in libname and \
|
||||
not os.path.basename(objfile) in libname
|
||||
|
||||
def otool(objfile):
|
||||
|
||||
+5
-5
@@ -30,7 +30,7 @@ esac
|
||||
testfun()
|
||||
{
|
||||
$ILDETECT_MPV "$@" \
|
||||
--vf-add=lavfi="[idet]" --msg-level ffmpeg=v \
|
||||
--vf=lavfi="[idet]" --msg-level ffmpeg=v \
|
||||
--o= --vo=null --no-audio --untimed \
|
||||
$ILDETECT_MPVFLAGS \
|
||||
| { if [ -n "$ILDETECT_QUIET" ]; then cat; else tee /dev/stderr; fi } \
|
||||
@@ -87,7 +87,7 @@ judge()
|
||||
echo "$verdict"
|
||||
}
|
||||
|
||||
judge "$@" --vf-clr
|
||||
judge "$@"
|
||||
case "$verdict" in
|
||||
progressive)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
@@ -98,7 +98,7 @@ case "$verdict" in
|
||||
exit 0
|
||||
;;
|
||||
interlaced-tff)
|
||||
judge "$@" --vf-clr --vf-pre=pullup --field-dominance=top
|
||||
judge "$@" --vf-pre=pullup --field-dominance=top
|
||||
case "$verdict" in
|
||||
progressive)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
@@ -117,7 +117,7 @@ case "$verdict" in
|
||||
esac
|
||||
;;
|
||||
interlaced-bff)
|
||||
judge "$@" --vf-clr --vf-pre=pullup --field-dominance=bottom
|
||||
judge "$@" --vf-pre=pullup --field-dominance=bottom
|
||||
case "$verdict" in
|
||||
progressive)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
@@ -136,7 +136,7 @@ case "$verdict" in
|
||||
esac
|
||||
;;
|
||||
interlaced)
|
||||
judge "$@" --vf-clr --vf-pre=pullup
|
||||
judge "$@" --vf-pre=pullup
|
||||
case "$verdict" in
|
||||
progressive)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
-- Test script for property change notification mechanism.
|
||||
|
||||
function format_property_val(v)
|
||||
if type(v) == "table" then
|
||||
return mp.format_table(v) -- undocumented function; might be removed
|
||||
else
|
||||
return tostring(v)
|
||||
end
|
||||
end
|
||||
|
||||
for i,name in ipairs(mp.get_property_native("property-list")) do
|
||||
mp.observe_property(name, "native", function(name, val)
|
||||
print("property '" .. name .. "' changed to '" ..
|
||||
format_property_val(val) .. "'")
|
||||
end)
|
||||
end
|
||||
@@ -624,90 +624,6 @@
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.movie</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>AVC raw stream</string>
|
||||
<key>UTTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.mpv.h264</string>
|
||||
<key>UTTypeReferenceURL</key>
|
||||
<string>http://www.itu.int/rec/T-REC-H.264</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>264</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.movie</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>HEVC raw stream</string>
|
||||
<key>UTTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.mpv.hevc</string>
|
||||
<key>UTTypeReferenceURL</key>
|
||||
<string>http://hevc.info</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>hevc</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.movie</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>YUV stream</string>
|
||||
<key>UTTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.mpv.yuv</string>
|
||||
<key>UTTypeReferenceURL</key>
|
||||
<string>http://en.wikipedia.org/wiki/YUV</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>yuv</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.movie</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>YUV4MPEG2 stream</string>
|
||||
<key>UTTypeIconFile</key>
|
||||
<string>document.icns</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.mpv.y4m</string>
|
||||
<key>UTTypeReferenceURL</key>
|
||||
<string>http://wiki.multimedia.cx/index.php?title=YUV4MPEG2</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>y4m</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
|
||||
+33
-63
@@ -7,7 +7,7 @@ use warnings;
|
||||
|
||||
my $mpv = $ARGV[0] || 'mpv';
|
||||
|
||||
my @opts = parse_opts("$mpv --list-options", '^ (\-\-[^\s\*]*)\*?\s*(.*)', 1);
|
||||
my @opts = parse_opts("$mpv --list-options", '^ (\-\-[^\s\*]*)\*?\s*(.*)');
|
||||
|
||||
my @ao = parse_opts("$mpv --ao=help", '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vo = parse_opts("$mpv --vo=help", '^ ([^\s\:]*)\s*: (.*)');
|
||||
@@ -15,11 +15,9 @@ my @vo = parse_opts("$mpv --vo=help", '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @af = parse_opts("$mpv --af=help", '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vf = parse_opts("$mpv --vf=help", '^ ([^\s\:]*)\s*: (.*)');
|
||||
|
||||
my @protos = parse_opts("$mpv --list-protocols", '^ ([^\s]*)');
|
||||
my ($opts_str, $ao_str, $vo_str, $af_str, $vf_str);
|
||||
|
||||
my ($opts_str, $ao_str, $vo_str, $af_str, $vf_str, $protos_str);
|
||||
|
||||
$opts_str .= qq{ '$_' \\\n} foreach (@opts);
|
||||
$opts_str .= qq{ '$_': \\\n} foreach (@opts);
|
||||
chomp $opts_str;
|
||||
|
||||
$ao_str .= qq{ '$_' \\\n} foreach (@ao);
|
||||
@@ -34,22 +32,14 @@ chomp $af_str;
|
||||
$vf_str .= qq{ '$_' \\\n} foreach (@vf);
|
||||
chomp $vf_str;
|
||||
|
||||
$protos_str .= qq{$_ } foreach (@protos);
|
||||
chomp $protos_str;
|
||||
|
||||
my $tmpl = <<"EOS";
|
||||
#compdef mpv
|
||||
|
||||
# mpv zsh completion
|
||||
|
||||
local curcontext="\$curcontext" state state_descr line
|
||||
typeset -A opt_args
|
||||
|
||||
local rc=1
|
||||
|
||||
_arguments -C -S \\
|
||||
_x_arguments -C -s \\
|
||||
$opts_str
|
||||
'*:files:->mfiles' && rc=0
|
||||
'*:files:->mfiles'
|
||||
|
||||
case \$state in
|
||||
ao)
|
||||
@@ -58,7 +48,7 @@ case \$state in
|
||||
$ao_str
|
||||
)
|
||||
|
||||
_describe -t values 'audio outputs' values && rc=0
|
||||
_describe -t values 'audio outputs' values
|
||||
;;
|
||||
|
||||
vo)
|
||||
@@ -67,7 +57,7 @@ $ao_str
|
||||
$vo_str
|
||||
)
|
||||
|
||||
_describe -t values 'video outputs' values && rc=0
|
||||
_describe -t values 'video outputs' values
|
||||
;;
|
||||
|
||||
af)
|
||||
@@ -76,7 +66,7 @@ $vo_str
|
||||
$af_str
|
||||
)
|
||||
|
||||
_describe -t values 'audio filters' values && rc=0
|
||||
_describe -t values 'audio filters' values
|
||||
;;
|
||||
|
||||
vf)
|
||||
@@ -85,75 +75,55 @@ $af_str
|
||||
$vf_str
|
||||
)
|
||||
|
||||
_describe -t values 'video filters' values && rc=0
|
||||
_describe -t values 'video filters' values
|
||||
;;
|
||||
|
||||
mfiles)
|
||||
local expl
|
||||
_tags files urls
|
||||
while _tags; do
|
||||
_requested files expl 'media file' _files -g \\
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|qt|rm|ts|vob|wav|webm|wma|wmv)(-.)" && rc=0
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|qt|rm|ts|vob|wav|webm|wma|wmv)(-.)" && ret=0
|
||||
if _requested urls; then
|
||||
while _next_label urls expl URL; do
|
||||
_urls "\$expl[@]" && rc=0
|
||||
compadd -S '' "\$expl[@]" $protos_str && rc=0
|
||||
_urls "\$expl[@]" && ret=0
|
||||
compadd -S '' "\$expl[@]" \{dvd,vcd,cdda,cddb,tv\}:// && ret=0
|
||||
done
|
||||
fi
|
||||
(( rc )) || return 0
|
||||
(( ret )) || return 0
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
return rc
|
||||
EOS
|
||||
|
||||
print $tmpl;
|
||||
|
||||
sub parse_opts {
|
||||
my ($cmd, $regex, $parsing_main_options) = @_;
|
||||
my ($cmd, $regex) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = split /\n/, `$cmd`;
|
||||
my @list;
|
||||
my @lines = split /\n/, `$cmd`;
|
||||
|
||||
foreach my $line (@lines) {
|
||||
if ($line !~ /^$regex/) {
|
||||
next;
|
||||
}
|
||||
foreach my $line (@lines) {
|
||||
if ($line !~ /^$regex/) {
|
||||
next;
|
||||
}
|
||||
|
||||
my $entry = $1;
|
||||
my $entry = "$1:";
|
||||
|
||||
if ($parsing_main_options) {
|
||||
$entry .= '=-';
|
||||
}
|
||||
if (defined $2) {
|
||||
my $desc = $2;
|
||||
$desc =~ s/\:/\\:/;
|
||||
|
||||
if (defined $2) {
|
||||
my $desc = $2;
|
||||
$desc =~ s/\:/\\:/g;
|
||||
$entry .= $desc;
|
||||
}
|
||||
|
||||
$entry .= ':' . $desc;
|
||||
}
|
||||
$entry .= ':->ao' if ($1 eq '--ao');
|
||||
$entry .= ':->vo' if ($1 eq '--vo');
|
||||
$entry .= ':->af' if ($1 eq '--af');
|
||||
$entry .= ':->vf' if ($1 eq '--vf');
|
||||
|
||||
if ($parsing_main_options) {
|
||||
$entry .= ':';
|
||||
push @list, $entry if ($line =~ /^$regex/)
|
||||
}
|
||||
|
||||
$entry .= '->ao' if ($1 eq '--ao');
|
||||
$entry .= '->vo' if ($1 eq '--vo');
|
||||
$entry .= '->af' if ($1 eq '--af');
|
||||
$entry .= '->vf' if ($1 eq '--vf');
|
||||
}
|
||||
|
||||
push @list, $entry
|
||||
}
|
||||
|
||||
if ($parsing_main_options) {
|
||||
@list = sort {
|
||||
$a =~ /(.*?)\:/; my $ma = $1;
|
||||
$b =~ /(.*?)\:/; my $mb = $1;
|
||||
|
||||
length($mb) <=> length($ma)
|
||||
} @list;
|
||||
}
|
||||
|
||||
return @list;
|
||||
return @list;
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ struct ad_functions {
|
||||
int (*init)(struct dec_audio *da, const char *decoder);
|
||||
void (*uninit)(struct dec_audio *da);
|
||||
int (*control)(struct dec_audio *da, int cmd, void *arg);
|
||||
int (*decode_packet)(struct dec_audio *da);
|
||||
int (*decode_audio)(struct dec_audio *da, struct mp_audio *buffer, int maxlen);
|
||||
};
|
||||
|
||||
enum ad_ctrl {
|
||||
|
||||
+74
-22
@@ -33,6 +33,7 @@
|
||||
#include "common/codecs.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/options.h"
|
||||
#include "common/av_opts.h"
|
||||
|
||||
#include "ad.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
@@ -48,13 +49,14 @@ struct priv {
|
||||
};
|
||||
|
||||
static void uninit(struct dec_audio *da);
|
||||
static int decode_new_packet(struct dec_audio *da);
|
||||
|
||||
#define OPT_BASE_STRUCT struct ad_lavc_params
|
||||
struct ad_lavc_params {
|
||||
float ac3drc;
|
||||
int downmix;
|
||||
int threads;
|
||||
char **avopts;
|
||||
char *avopt;
|
||||
};
|
||||
|
||||
const struct m_sub_options ad_lavc_conf = {
|
||||
@@ -62,7 +64,7 @@ const struct m_sub_options ad_lavc_conf = {
|
||||
OPT_FLOATRANGE("ac3drc", ac3drc, 0, 0, 2),
|
||||
OPT_FLAG("downmix", downmix, 0),
|
||||
OPT_INTRANGE("threads", threads, 0, 1, 16),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
OPT_STRING("o", avopt, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct ad_lavc_params),
|
||||
@@ -141,10 +143,11 @@ static const char *find_pcm_decoder(const struct pcm_map *map, int format,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void set_data_from_avframe(struct dec_audio *da)
|
||||
static int setup_format(struct dec_audio *da)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *lavc_context = priv->avctx;
|
||||
struct sh_audio *sh_audio = da->header->audio;
|
||||
|
||||
// Note: invalid parameters are rejected by dec_audio.c
|
||||
|
||||
@@ -154,6 +157,12 @@ static void set_data_from_avframe(struct dec_audio *da)
|
||||
MP_FATAL(da, "unsupported lavc format %s", av_get_sample_fmt_name(fmt));
|
||||
|
||||
da->decoded.rate = lavc_context->sample_rate;
|
||||
if (!da->decoded.rate && sh_audio->wf) {
|
||||
// If not set, try container samplerate.
|
||||
// (Maybe this can't happen, and it's an artifact from the past.)
|
||||
da->decoded.rate = sh_audio->wf->nSamplesPerSec;
|
||||
MP_WARN(da, "using container rate.\n");
|
||||
}
|
||||
|
||||
struct mp_chmap lavc_chmap;
|
||||
mp_chmap_from_lavc(&lavc_chmap, lavc_context->channel_layout);
|
||||
@@ -161,15 +170,12 @@ static void set_data_from_avframe(struct dec_audio *da)
|
||||
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];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void set_from_wf(AVCodecContext *avctx, MP_WAVEFORMATEX *wf)
|
||||
@@ -227,7 +233,13 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
av_opt_set_double(lavc_context, "drc_scale", opts->ac3drc,
|
||||
AV_OPT_SEARCH_CHILDREN);
|
||||
|
||||
mp_set_avopts(da->log, lavc_context, opts->avopts);
|
||||
if (opts->avopt) {
|
||||
if (parse_avopts(lavc_context, opts->avopt) < 0) {
|
||||
MP_ERR(da, "setting AVOptions '%s' failed.\n", opts->avopt);
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
lavc_context->codec_tag = sh->format;
|
||||
lavc_context->sample_rate = sh_audio->samplerate;
|
||||
@@ -255,6 +267,22 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
MP_VERBOSE(da, "INFO: libavcodec \"%s\" init OK!\n",
|
||||
lavc_codec->name);
|
||||
|
||||
// Decode at least 1 sample: (to get header filled)
|
||||
for (int tries = 1; ; tries++) {
|
||||
int x = decode_new_packet(da);
|
||||
if (x >= 0 && ctx->frame.samples > 0) {
|
||||
MP_VERBOSE(da, "Initial decode succeeded after %d packets.\n", tries);
|
||||
break;
|
||||
}
|
||||
if (tries >= 50) {
|
||||
MP_ERR(da, "initial decode failed\n");
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (lavc_context->bit_rate != 0)
|
||||
da->bitrate = lavc_context->bit_rate;
|
||||
@@ -286,7 +314,7 @@ 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);
|
||||
ctx->frame.samples = 0;
|
||||
talloc_free(ctx->packet);
|
||||
ctx->packet = NULL;
|
||||
return CONTROL_TRUE;
|
||||
@@ -294,18 +322,16 @@ static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static int decode_packet(struct dec_audio *da)
|
||||
static int decode_new_packet(struct dec_audio *da)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
|
||||
mp_audio_set_null_data(&da->decoded);
|
||||
priv->frame.samples = 0;
|
||||
|
||||
struct demux_packet *mpkt = priv->packet;
|
||||
if (!mpkt) {
|
||||
if (demux_read_packet_async(da->header, &mpkt) == 0)
|
||||
return AD_WAIT;
|
||||
}
|
||||
if (!mpkt)
|
||||
mpkt = demux_read_packet(da->header);
|
||||
|
||||
priv->packet = talloc_steal(priv, mpkt);
|
||||
|
||||
@@ -338,16 +364,22 @@ static int decode_packet(struct dec_audio *da)
|
||||
}
|
||||
// LATM may need many packets to find mux info
|
||||
if (ret == AVERROR(EAGAIN))
|
||||
return AD_OK;
|
||||
return 0;
|
||||
}
|
||||
if (ret < 0) {
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
return AD_ERR;
|
||||
MP_VERBOSE(da, "lavc_audio: error\n");
|
||||
return -1;
|
||||
}
|
||||
if (!got_frame)
|
||||
return mpkt ? AD_OK : AD_EOF;
|
||||
return mpkt ? 0 : -1; // -1: eof
|
||||
|
||||
set_data_from_avframe(da);
|
||||
if (setup_format(da) < 0)
|
||||
return -1;
|
||||
|
||||
priv->frame.samples = priv->avframe->nb_samples;
|
||||
mp_audio_copy_config(&priv->frame, &da->decoded);
|
||||
for (int n = 0; n < priv->frame.num_planes; n++)
|
||||
priv->frame.planes[n] = priv->avframe->data[n];
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, NULL);
|
||||
if (out_pts != MP_NOPTS_VALUE) {
|
||||
@@ -355,7 +387,27 @@ static int decode_packet(struct dec_audio *da)
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
|
||||
MP_DBG(da, "Decoded %d -> %d samples\n", in_len, da->decoded.samples);
|
||||
MP_DBG(da, "Decoded %d -> %d samples\n", in_len,
|
||||
priv->frame.samples);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode_audio(struct dec_audio *da, struct mp_audio *buffer, int maxlen)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
|
||||
if (!priv->frame.samples) {
|
||||
if (decode_new_packet(da) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!mp_audio_config_equals(buffer, &priv->frame))
|
||||
return 0;
|
||||
|
||||
buffer->samples = MPMIN(priv->frame.samples, maxlen);
|
||||
mp_audio_copy(buffer, 0, &priv->frame, 0, buffer->samples);
|
||||
mp_audio_skip_samples(&priv->frame, buffer->samples);
|
||||
da->pts_offset += buffer->samples;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -372,5 +424,5 @@ const struct ad_functions ad_lavc = {
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.decode_packet = decode_packet,
|
||||
.decode_audio = decode_audio,
|
||||
};
|
||||
+148
-74
@@ -35,7 +35,9 @@
|
||||
|
||||
struct ad_mpg123_context {
|
||||
mpg123_handle *handle;
|
||||
bool new_format;
|
||||
int sample_size;
|
||||
bool need_data;
|
||||
/* Running mean for bit rate, stream length estimation. */
|
||||
float mean_rate;
|
||||
unsigned int mean_count;
|
||||
@@ -56,7 +58,7 @@ static void uninit(struct dec_audio *da)
|
||||
|
||||
/* This initializes libmpg123 and prepares the handle, including funky
|
||||
* parameters. */
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
static int preinit(struct dec_audio *da)
|
||||
{
|
||||
int err;
|
||||
struct ad_mpg123_context *con;
|
||||
@@ -109,18 +111,15 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
* We need at least 1152 samples. dec_audio.c normally guarantees this. */
|
||||
mpg123_param(con->handle, MPG123_REMOVE_FLAGS, MPG123_AUTO_RESAMPLE, 0.);
|
||||
|
||||
err = mpg123_open_feed(con->handle);
|
||||
if (err != MPG123_OK)
|
||||
goto bad_end;
|
||||
|
||||
return 1;
|
||||
|
||||
bad_end:
|
||||
if (!con->handle) {
|
||||
MP_ERR(da, "mpg123 preinit error: %s\n", mpg123_plain_strerror(err));
|
||||
} else {
|
||||
MP_ERR(da, "mpg123 preinit error: %s\n", mpg123_strerror(con->handle));
|
||||
}
|
||||
if (!con->handle)
|
||||
MP_ERR(da, "mpg123 preinit error: %s\n",
|
||||
mpg123_plain_strerror(err));
|
||||
else
|
||||
MP_ERR(da, "mpg123 preinit error: %s\n",
|
||||
mpg123_strerror(con->handle));
|
||||
|
||||
uninit(da);
|
||||
return 0;
|
||||
@@ -141,6 +140,111 @@ static int mpg123_format_to_af(int mpg123_encoding)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* libmpg123 has a new format ready; query and store, return return value
|
||||
of mpg123_getformat() */
|
||||
static int set_format(struct dec_audio *da)
|
||||
{
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
int ret;
|
||||
long rate;
|
||||
int channels;
|
||||
int encoding;
|
||||
ret = mpg123_getformat(con->handle, &rate, &channels, &encoding);
|
||||
if (ret == MPG123_OK) {
|
||||
mp_audio_set_num_channels(&da->decoded, channels);
|
||||
da->decoded.rate = rate;
|
||||
int af = mpg123_format_to_af(encoding);
|
||||
if (!af) {
|
||||
/* This means we got a funny custom build of libmpg123 that only supports an unknown format. */
|
||||
MP_ERR(da, "Bad encoding from mpg123: %i.\n", encoding);
|
||||
return MPG123_ERR;
|
||||
}
|
||||
mp_audio_set_format(&da->decoded, af);
|
||||
con->sample_size = channels * af_fmt2bps(af);
|
||||
con->new_format = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int feed_new_packet(struct dec_audio *da)
|
||||
{
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
int ret;
|
||||
|
||||
struct demux_packet *pkt = demux_read_packet(da->header);
|
||||
if (!pkt)
|
||||
return -1; /* EOF. */
|
||||
|
||||
/* Next bytes from that presentation time. */
|
||||
if (pkt->pts != MP_NOPTS_VALUE) {
|
||||
da->pts = pkt->pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
|
||||
/* Have to use mpg123_feed() to avoid decoding here. */
|
||||
ret = mpg123_feed(con->handle, pkt->buffer, pkt->len);
|
||||
talloc_free(pkt);
|
||||
|
||||
if (ret == MPG123_ERR)
|
||||
return -1;
|
||||
|
||||
if (ret == MPG123_NEW_FORMAT)
|
||||
con->new_format = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Now we really start accessing some data and determining file format.
|
||||
* Format now is allowed to change on-the-fly. Here is the only point
|
||||
* that has MPlayer react to errors. We have to pray that exceptional
|
||||
* erros in other places simply cannot occur. */
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
{
|
||||
if (!preinit(da))
|
||||
return 0;
|
||||
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
int ret;
|
||||
|
||||
ret = mpg123_open_feed(con->handle);
|
||||
if (ret != MPG123_OK)
|
||||
goto fail;
|
||||
|
||||
for (int n = 0; ; n++) {
|
||||
if (feed_new_packet(da) < 0) {
|
||||
ret = MPG123_NEED_MORE;
|
||||
goto fail;
|
||||
}
|
||||
size_t got_now = 0;
|
||||
ret = mpg123_decode_frame(con->handle, NULL, NULL, &got_now);
|
||||
if (ret == MPG123_OK || ret == MPG123_NEW_FORMAT) {
|
||||
ret = set_format(da);
|
||||
if (ret == MPG123_OK)
|
||||
break;
|
||||
}
|
||||
if (ret != MPG123_NEED_MORE)
|
||||
goto fail;
|
||||
// max. 16 retries (randomly chosen number)
|
||||
if (n > 16) {
|
||||
ret = MPG123_NEED_MORE;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
if (ret == MPG123_NEED_MORE) {
|
||||
MP_ERR(da, "Could not find mp3 stream.\n");
|
||||
} else {
|
||||
MP_ERR(da, "mpg123 init error: %s\n",
|
||||
mpg123_strerror(con->handle));
|
||||
}
|
||||
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Compute bitrate from frame size. */
|
||||
static int compute_bitrate(struct mpg123_frameinfo *i)
|
||||
{
|
||||
@@ -186,86 +290,57 @@ static void update_info(struct dec_audio *da)
|
||||
}
|
||||
}
|
||||
|
||||
/* libmpg123 has a new format ready; query and store, return return value
|
||||
of mpg123_getformat() */
|
||||
static int set_format(struct dec_audio *da)
|
||||
static int decode_audio(struct dec_audio *da, struct mp_audio *buffer, int maxlen)
|
||||
{
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
void *buf = buffer->planes[0];
|
||||
int ret;
|
||||
long rate;
|
||||
int channels;
|
||||
int encoding;
|
||||
ret = mpg123_getformat(con->handle, &rate, &channels, &encoding);
|
||||
if (ret == MPG123_OK) {
|
||||
mp_audio_set_num_channels(&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;
|
||||
|
||||
if (con->new_format) {
|
||||
ret = set_format(da);
|
||||
if (ret == MPG123_OK) {
|
||||
return 0; // let caller handle format change
|
||||
} else if (ret == MPG123_NEED_MORE) {
|
||||
con->need_data = true;
|
||||
} else {
|
||||
goto mpg123_fail;
|
||||
}
|
||||
mp_audio_set_format(&da->decoded, af);
|
||||
con->sample_size = channels * af_fmt2bps(af);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
if (!pkt)
|
||||
return AD_EOF;
|
||||
|
||||
/* Next bytes from that presentation time. */
|
||||
if (pkt->pts != MP_NOPTS_VALUE) {
|
||||
da->pts = pkt->pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
|
||||
/* Have to use mpg123_feed() to avoid decoding here. */
|
||||
ret = mpg123_feed(con->handle, pkt->buffer, pkt->len);
|
||||
talloc_free(pkt);
|
||||
if (con->need_data) {
|
||||
if (feed_new_packet(da) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ret != MPG123_OK)
|
||||
goto mpg123_fail;
|
||||
|
||||
unsigned char *audio = NULL;
|
||||
size_t bytes = 0;
|
||||
ret = mpg123_decode_frame(con->handle, NULL, &audio, &bytes);
|
||||
|
||||
if (ret == MPG123_NEED_MORE)
|
||||
if (!mp_audio_config_equals(&da->decoded, buffer))
|
||||
return 0;
|
||||
|
||||
if (ret != MPG123_OK && ret != MPG123_DONE && ret != MPG123_NEW_FORMAT)
|
||||
goto mpg123_fail;
|
||||
|
||||
ret = set_format(da);
|
||||
size_t got_now = 0;
|
||||
ret = mpg123_replace_buffer(con->handle, buf, maxlen * con->sample_size);
|
||||
if (ret != MPG123_OK)
|
||||
goto mpg123_fail;
|
||||
|
||||
if (con->sample_size < 1) {
|
||||
MP_ERR(da, "no sample size\n");
|
||||
return AD_ERR;
|
||||
}
|
||||
ret = mpg123_decode_frame(con->handle, NULL, NULL, &got_now);
|
||||
|
||||
int got_samples = bytes / con->sample_size;
|
||||
da->decoded.planes[0] = audio;
|
||||
da->decoded.samples = got_samples;
|
||||
int got_samples = got_now / con->sample_size;
|
||||
buffer->samples += got_samples;
|
||||
da->pts_offset += got_samples;
|
||||
|
||||
if (ret == MPG123_NEW_FORMAT) {
|
||||
con->new_format = true;
|
||||
} else if (ret == MPG123_NEED_MORE) {
|
||||
con->need_data = true;
|
||||
} else if (ret != MPG123_OK && ret != MPG123_DONE) {
|
||||
goto mpg123_fail;
|
||||
}
|
||||
|
||||
update_info(da);
|
||||
return 0;
|
||||
|
||||
mpg123_fail:
|
||||
MP_ERR(da, "mpg123 decoding error: %s\n", mpg123_strerror(con->handle));
|
||||
return AD_ERR;
|
||||
MP_ERR(da, "mpg123 decoding error: %s\n",
|
||||
mpg123_strerror(con->handle));
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
@@ -274,7 +349,6 @@ 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) {
|
||||
@@ -299,5 +373,5 @@ const struct ad_functions ad_mpg123 = {
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.decode_packet = decode_packet,
|
||||
.decode_audio = decode_audio,
|
||||
};
|
||||
+18
-15
@@ -38,7 +38,8 @@ struct spdifContext {
|
||||
AVFormatContext *lavf_ctx;
|
||||
int iec61937_packet_size;
|
||||
int out_buffer_len;
|
||||
uint8_t out_buffer[OUTBUF_SIZE];
|
||||
int out_buffer_size;
|
||||
uint8_t *out_buffer;
|
||||
bool need_close;
|
||||
};
|
||||
|
||||
@@ -46,7 +47,7 @@ static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
{
|
||||
struct spdifContext *ctx = p;
|
||||
|
||||
int buffer_left = OUTBUF_SIZE - ctx->out_buffer_len;
|
||||
int buffer_left = ctx->out_buffer_size - ctx->out_buffer_len;
|
||||
if (buf_size > buffer_left) {
|
||||
MP_ERR(ctx, "spdif packet too large.\n");
|
||||
buf_size = buffer_left;
|
||||
@@ -182,21 +183,24 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode_packet(struct dec_audio *da)
|
||||
static int decode_audio(struct dec_audio *da, struct mp_audio *buffer, int maxlen)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
|
||||
|
||||
mp_audio_set_null_data(&da->decoded);
|
||||
int sstride = 2 * da->decoded.channels.num;
|
||||
assert(sstride == buffer->sstride);
|
||||
|
||||
if (maxlen * sstride < spdif_ctx->iec61937_packet_size)
|
||||
return 0;
|
||||
|
||||
spdif_ctx->out_buffer_len = 0;
|
||||
spdif_ctx->out_buffer_size = maxlen * sstride;
|
||||
spdif_ctx->out_buffer = buffer->planes[0];
|
||||
|
||||
struct demux_packet *mpkt;
|
||||
if (demux_read_packet_async(da->header, &mpkt) == 0)
|
||||
return AD_WAIT;
|
||||
|
||||
struct demux_packet *mpkt = demux_read_packet(da->header);
|
||||
if (!mpkt)
|
||||
return AD_EOF;
|
||||
return -1;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
@@ -207,13 +211,12 @@ static int decode_packet(struct dec_audio *da)
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
int ret = av_write_frame(lavf_ctx, &pkt);
|
||||
talloc_free(mpkt);
|
||||
avio_flush(lavf_ctx->pb);
|
||||
buffer->samples = spdif_ctx->out_buffer_len / sstride;
|
||||
da->pts_offset += buffer->samples;
|
||||
talloc_free(mpkt);
|
||||
if (ret < 0)
|
||||
return AD_ERR;
|
||||
|
||||
da->decoded.planes[0] = spdif_ctx->out_buffer;
|
||||
da->decoded.samples = spdif_ctx->out_buffer_len / da->decoded.sstride;
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -250,5 +253,5 @@ const struct ad_functions ad_spdif = {
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.decode_packet = decode_packet,
|
||||
.decode_audio = decode_audio,
|
||||
};
|
||||
+50
-53
@@ -56,6 +56,13 @@ static const struct ad_functions * const ad_drivers[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
// ad_mpg123 needs to be able to decode 1152 samples at once
|
||||
// ad_spdif needs up to 8192
|
||||
#define DECODE_MAX_UNIT MPMAX(8192, 1152)
|
||||
|
||||
// At least 8192 samples, plus hack for ad_mpg123 and ad_spdif
|
||||
#define DECODE_BUFFER_SAMPLES (8192 + DECODE_MAX_UNIT)
|
||||
|
||||
// Drop audio buffer and reinit it (after format change)
|
||||
// Returns whether the format was valid at all.
|
||||
static bool reinit_audio_buffer(struct dec_audio *da)
|
||||
@@ -66,6 +73,7 @@ static bool reinit_audio_buffer(struct dec_audio *da)
|
||||
return false;
|
||||
}
|
||||
mp_audio_buffer_reinit(da->decode_buffer, &da->decoded);
|
||||
mp_audio_buffer_preallocate_min(da->decode_buffer, DECODE_BUFFER_SAMPLES);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -90,6 +98,11 @@ static int init_audio_codec(struct dec_audio *d_audio, const char *decoder)
|
||||
}
|
||||
|
||||
d_audio->decode_buffer = mp_audio_buffer_create(NULL);
|
||||
if (!reinit_audio_buffer(d_audio)) {
|
||||
uninit_decoder(d_audio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -151,6 +164,9 @@ int audio_init_best_codec(struct dec_audio *d_audio, char *audio_decoders)
|
||||
talloc_asprintf(d_audio, "%s [%s:%s]", decoder->desc, decoder->family,
|
||||
decoder->decoder);
|
||||
MP_VERBOSE(d_audio, "Selected audio codec: %s\n", d_audio->decoder_desc);
|
||||
MP_VERBOSE(d_audio, "AUDIO: %d Hz, %d ch, %s\n",
|
||||
d_audio->decoded.rate, d_audio->decoded.channels.num,
|
||||
af_fmt_to_str(d_audio->decoded.format));
|
||||
} else {
|
||||
MP_ERR(d_audio, "Failed to initialize an audio decoder for codec '%s'.\n",
|
||||
d_audio->header->codec ? d_audio->header->codec : "<unknown>");
|
||||
@@ -193,6 +209,8 @@ int audio_init_filters(struct dec_audio *d_audio, int in_samplerate,
|
||||
mp_audio_set_channels(&afs->output, out_channels);
|
||||
mp_audio_set_format(&afs->output, *out_format);
|
||||
|
||||
afs->metadata = d_audio->metadata;
|
||||
|
||||
afs->replaygain_data = d_audio->replaygain_data;
|
||||
|
||||
char *s_from = mp_audio_config_to_str(&afs->input);
|
||||
@@ -215,82 +233,61 @@ int audio_init_filters(struct dec_audio *d_audio, int in_samplerate,
|
||||
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 (!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;
|
||||
}
|
||||
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
|
||||
struct mp_audio config;
|
||||
mp_audio_buffer_get_format(da->decode_buffer, &config);
|
||||
|
||||
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
|
||||
int maxlen = mp_audio_buffer_get_write_available(da->decode_buffer);
|
||||
if (maxlen < DECODE_MAX_UNIT)
|
||||
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);
|
||||
struct mp_audio buffer;
|
||||
mp_audio_buffer_get_write_buffer(da->decode_buffer, maxlen, &buffer);
|
||||
buffer.samples = 0;
|
||||
error = da->ad_driver->decode_audio(da, &buffer, maxlen);
|
||||
if (error < 0)
|
||||
break;
|
||||
// Commit the data just read as valid data
|
||||
mp_audio_buffer_finish_write(da->decode_buffer, buffer.samples);
|
||||
// Format change
|
||||
if (!mp_audio_config_equals(&da->decoded, &config)) {
|
||||
// If there are still samples left in the buffer, let them drain
|
||||
// first, and don't signal a format change to the caller yet.
|
||||
if (mp_audio_buffer_samples(da->decode_buffer) > 0)
|
||||
break;
|
||||
error = -2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
bool eof = filter_data.samples == 0 && error < 0;
|
||||
|
||||
if (af_filter(da->afilter, &filter_data, eof ? AF_FILTER_FLAG_EOF : 0) < 0)
|
||||
return AD_ERR;
|
||||
return -1;
|
||||
|
||||
mp_audio_buffer_append(outbuf, &filter_data);
|
||||
if (error == AD_EOF && filter_data.samples > 0)
|
||||
if (eof && filter_data.samples > 0)
|
||||
error = 0; // don't end playback yet
|
||||
|
||||
// remove processed data from decoder buffer:
|
||||
mp_audio_buffer_skip(da->decode_buffer, len);
|
||||
|
||||
// if format was changed, and all data was drained, execute the format change
|
||||
if (format_change && eof) {
|
||||
error = AD_NEW_FMT;
|
||||
// Assume the filter chain is drained from old data at this point.
|
||||
// (If not, the remaining old data is discarded.)
|
||||
if (error == -2) {
|
||||
if (!reinit_audio_buffer(da))
|
||||
error = AD_ERR; // switch to invalid format
|
||||
error = -1; // switch to invalid format
|
||||
}
|
||||
|
||||
return error;
|
||||
@@ -298,17 +295,17 @@ static int filter_n_bytes(struct dec_audio *da, struct mp_audio_buffer *outbuf,
|
||||
|
||||
/* Try to get at least minsamples decoded+filtered samples in outbuf
|
||||
* (total length including possible existing data).
|
||||
* Return 0 on success, or negative AD_* error code.
|
||||
* Return 0 on success, -1 on error/EOF (not distinguidaed).
|
||||
* In the former case outbuf has at least minsamples buffered on return.
|
||||
* In case of EOF/error it might or might not be. */
|
||||
int audio_decode(struct dec_audio *d_audio, struct mp_audio_buffer *outbuf,
|
||||
int minsamples)
|
||||
{
|
||||
if (!d_audio->afilter)
|
||||
return AD_ERR;
|
||||
|
||||
// Indicates that a filter seems to be buffering large amounts of data
|
||||
int huge_filter_buffer = 0;
|
||||
// Decoded audio must be cut at boundaries of this many samples
|
||||
// (Note: the reason for this is unknown, possibly a refactoring artifact)
|
||||
int unitsize = 16;
|
||||
|
||||
/* Filter output size will be about filter_multiplier times input size.
|
||||
* If some filter buffers audio in big blocks this might only hold
|
||||
@@ -325,8 +322,8 @@ int audio_decode(struct dec_audio *d_audio, struct mp_audio_buffer *outbuf,
|
||||
prev_buffered = buffered;
|
||||
|
||||
int decsamples = (minsamples - buffered) / filter_multiplier;
|
||||
// + some extra for possible filter buffering, and avoid 0
|
||||
decsamples += 512;
|
||||
// + some extra for possible filter buffering
|
||||
decsamples += unitsize << 5;
|
||||
|
||||
if (huge_filter_buffer) {
|
||||
/* Some filter must be doing significant buffering if the estimated
|
||||
|
||||
@@ -36,10 +36,12 @@ struct dec_audio {
|
||||
struct mp_audio_buffer *decode_buffer;
|
||||
struct af_stream *afilter;
|
||||
char *decoder_desc;
|
||||
struct mp_tags *metadata;
|
||||
struct replaygain_data *replaygain_data;
|
||||
int init_retries;
|
||||
// set by decoder
|
||||
struct mp_audio decoded; // decoded audio set by last decode_packet() call
|
||||
struct mp_audio decoded; // format of decoded audio (no data, temporarily
|
||||
// different from decode_buffer during format
|
||||
// changes)
|
||||
int bitrate; // input bitrate, can change with VBR sources
|
||||
// last known pts value in output from decoder
|
||||
double pts;
|
||||
@@ -49,19 +51,10 @@ struct dec_audio {
|
||||
void *priv;
|
||||
};
|
||||
|
||||
enum {
|
||||
AD_OK = 0,
|
||||
AD_ERR = -1,
|
||||
AD_EOF = -2,
|
||||
AD_NEW_FMT = -3,
|
||||
AD_WAIT = -4,
|
||||
};
|
||||
|
||||
struct mp_decoder_list *audio_decoder_list(void);
|
||||
int audio_init_best_codec(struct dec_audio *d_audio, char *audio_decoders);
|
||||
int audio_decode(struct dec_audio *d_audio, struct mp_audio_buffer *outbuf,
|
||||
int minsamples);
|
||||
int initial_audio_decode(struct dec_audio *d_audio);
|
||||
void audio_reset_decoding(struct dec_audio *d_audio);
|
||||
void audio_uninit(struct dec_audio *d_audio);
|
||||
|
||||
|
||||
+7
-6
@@ -204,6 +204,7 @@ static struct af_instance *af_create(struct af_stream *s, char *name,
|
||||
.mul = 1,
|
||||
.data = talloc_zero(af, struct mp_audio),
|
||||
.log = mp_log_new(af, s->log, name),
|
||||
.metadata = s->metadata,
|
||||
.replaygain_data = s->replaygain_data,
|
||||
};
|
||||
struct m_config *config = m_config_from_obj_desc(af, s->log, &desc);
|
||||
@@ -310,22 +311,22 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
|
||||
struct af_instance *af = s->first;
|
||||
while (af) {
|
||||
char b[128] = {0};
|
||||
mp_snprintf_cat(b, sizeof(b), " [%s] ", af->info->name);
|
||||
MP_MSG(s, msg_level, " [%s] ", af->info->name);
|
||||
if (af->data) {
|
||||
char *info = mp_audio_config_to_str(af->data);
|
||||
mp_snprintf_cat(b, sizeof(b), "%s", info);
|
||||
MP_MSG(s, msg_level, "%s", info);
|
||||
talloc_free(info);
|
||||
}
|
||||
if (af == at)
|
||||
mp_snprintf_cat(b, sizeof(b), " <-");
|
||||
MP_MSG(s, msg_level, "%s\n", b);
|
||||
MP_MSG(s, msg_level, " <-");
|
||||
MP_MSG(s, msg_level, "\n");
|
||||
|
||||
af = af->next;
|
||||
}
|
||||
|
||||
MP_MSG(s, msg_level, " [ao] ");
|
||||
char *info = mp_audio_config_to_str(&s->output);
|
||||
MP_MSG(s, msg_level, " [ao] %s\n", info);
|
||||
MP_MSG(s, msg_level, "%s\n", info);
|
||||
talloc_free(info);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ struct af_info {
|
||||
struct af_instance {
|
||||
const struct af_info *info;
|
||||
struct mp_log *log;
|
||||
struct mp_tags *metadata;
|
||||
struct replaygain_data *replaygain_data;
|
||||
int (*control)(struct af_instance *af, int cmd, void *arg);
|
||||
void (*uninit)(struct af_instance *af);
|
||||
@@ -91,6 +92,7 @@ struct af_stream {
|
||||
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mp_tags *metadata;
|
||||
struct replaygain_data *replaygain_data;
|
||||
};
|
||||
|
||||
|
||||
@@ -38,9 +38,8 @@
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "af.h"
|
||||
|
||||
#include "common/av_common.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "common/av_opts.h"
|
||||
|
||||
// FFmpeg and Libav have slightly different APIs, just enough to cause us
|
||||
// unnecessary pain. <Expletive deleted.>
|
||||
@@ -63,7 +62,7 @@ struct priv {
|
||||
|
||||
// options
|
||||
char *cfg_graph;
|
||||
char **cfg_avopts;
|
||||
char *cfg_avopts;
|
||||
};
|
||||
|
||||
static void destroy_graph(struct af_instance *af)
|
||||
@@ -91,8 +90,11 @@ static bool recreate_graph(struct af_instance *af, struct mp_audio *config)
|
||||
if (!graph)
|
||||
goto error;
|
||||
|
||||
if (mp_set_avopts(af->log, graph, p->cfg_avopts) < 0)
|
||||
if (parse_avopts(graph, p->cfg_avopts) < 0) {
|
||||
MP_FATAL(af, "lavfi: could not set opts: '%s'\n",
|
||||
p->cfg_avopts);
|
||||
goto error;
|
||||
}
|
||||
|
||||
AVFilterInOut *outputs = avfilter_inout_alloc();
|
||||
AVFilterInOut *inputs = avfilter_inout_alloc();
|
||||
@@ -304,7 +306,7 @@ const struct af_info af_info_lavfi = {
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("graph", cfg_graph, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
OPT_STRING("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -52,9 +52,9 @@
|
||||
#error "config.h broken or no resampler found"
|
||||
#endif
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/av_opts.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
@@ -74,7 +74,7 @@ struct af_resample_opts {
|
||||
|
||||
struct af_resample {
|
||||
int allow_detach;
|
||||
char **avopts;
|
||||
char *avopts;
|
||||
struct AVAudioResampleContext *avrctx;
|
||||
struct AVAudioResampleContext *avrctx_out; // for output channel reordering
|
||||
struct af_resample_opts ctx; // opts in the context
|
||||
@@ -164,8 +164,10 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
|
||||
av_opt_set_double(s->avrctx, "cutoff", s->ctx.cutoff, 0);
|
||||
|
||||
if (mp_set_avopts(af->log, s->avrctx, s->avopts) < 0)
|
||||
if (parse_avopts(s->avrctx, s->avopts) < 0) {
|
||||
MP_FATAL(af, "af_lavrresample: could not set opts: '%s'\n", s->avopts);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
struct mp_chmap map_in = in->channels;
|
||||
struct mp_chmap map_out = out->channels;
|
||||
@@ -400,7 +402,7 @@ const struct af_info af_info_lavrresample = {
|
||||
OPT_FLAG("linear", opts.linear, 0),
|
||||
OPT_DOUBLE("cutoff", opts.cutoff, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLAG("detach", allow_detach, 0),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
OPT_STRING("o", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -79,7 +79,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
if (!s->rgain_clip) // clipping prevention
|
||||
s->rgain = MPMIN(s->rgain, 1.0 / peak);
|
||||
}
|
||||
if (s->detach && fabs(s->level * s->rgain - 1.0) < 0.00001)
|
||||
if (s->detach && fabs(s->level + s->rgain - 2.0) < 0.00001)
|
||||
return AF_DETACH;
|
||||
return af_test_output(af, in);
|
||||
}
|
||||
@@ -97,7 +97,7 @@ static void filter_plane(struct af_instance *af, void *ptr, int num_samples)
|
||||
{
|
||||
struct priv *s = af->priv;
|
||||
|
||||
float level = s->level * s->rgain;
|
||||
float level = s->level + s->rgain - 1.0;
|
||||
|
||||
if (af_fmt_from_planar(af->data->format) == AF_FORMAT_S16) {
|
||||
int16_t *a = ptr;
|
||||
|
||||
+1
-1
@@ -24,8 +24,8 @@
|
||||
#define MPLAYER_AF_FORMAT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "osdep/endian.h"
|
||||
#include "bstr/bstr.h"
|
||||
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
|
||||
+14
-6
@@ -31,13 +31,13 @@
|
||||
|
||||
#include "options/options.h"
|
||||
#include "options/m_config.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
|
||||
extern const struct ao_driver audio_out_oss;
|
||||
extern const struct ao_driver audio_out_coreaudio;
|
||||
extern const struct ao_driver audio_out_coreaudio_exclusive;
|
||||
extern const struct ao_driver audio_out_rsound;
|
||||
extern const struct ao_driver audio_out_sndio;
|
||||
extern const struct ao_driver audio_out_pulse;
|
||||
@@ -94,9 +94,6 @@ 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
|
||||
@@ -312,16 +309,27 @@ void ao_resume(struct ao *ao)
|
||||
ao->api->resume(ao);
|
||||
}
|
||||
|
||||
// Be careful with locking
|
||||
void ao_wait_drain(struct ao *ao)
|
||||
{
|
||||
// This is probably not entirely accurate, but good enough.
|
||||
mp_sleep_us(ao_get_delay(ao) * 1000000);
|
||||
ao_reset(ao);
|
||||
}
|
||||
|
||||
// Block until the current audio buffer has played completely.
|
||||
void ao_drain(struct ao *ao)
|
||||
{
|
||||
if (ao->api->drain)
|
||||
if (ao->api->drain) {
|
||||
ao->api->drain(ao);
|
||||
} else {
|
||||
ao_wait_drain(ao);
|
||||
}
|
||||
}
|
||||
|
||||
bool ao_eof_reached(struct ao *ao)
|
||||
{
|
||||
return ao->api->get_eof ? ao->api->get_eof(ao) : true;
|
||||
return ao_get_delay(ao) < AO_EOF_DELAY;
|
||||
}
|
||||
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
|
||||
+1
-6
@@ -31,6 +31,7 @@
|
||||
#include <sys/time.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -63,7 +64,6 @@ struct priv {
|
||||
char *cfg_mixer_name;
|
||||
int cfg_mixer_index;
|
||||
int cfg_resample;
|
||||
int cfg_ni;
|
||||
};
|
||||
|
||||
#define BUFFER_TIME 250000 // 250ms
|
||||
@@ -353,9 +353,6 @@ static int init(struct ao *ao)
|
||||
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (!p->cfg_ni)
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
/* switch for spdif
|
||||
* sets opening sequence for SPDIF
|
||||
* sets also the playback and other switches 'on the fly'
|
||||
@@ -758,7 +755,6 @@ const struct ao_driver audio_out_alsa = {
|
||||
.cfg_mixer_device = "default",
|
||||
.cfg_mixer_name = "Master",
|
||||
.cfg_mixer_index = 0,
|
||||
.cfg_ni = 0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("device", cfg_device, 0),
|
||||
@@ -767,7 +763,6 @@ const struct ao_driver audio_out_alsa = {
|
||||
OPT_STRING("mixer-device", cfg_mixer_device, 0),
|
||||
OPT_STRING("mixer-name", cfg_mixer_name, 0),
|
||||
OPT_INTRANGE("mixer-index", cfg_mixer_index, 0, 0, 99),
|
||||
OPT_FLAG("non-interleaved", cfg_ni, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+528
-271
@@ -1,21 +1,39 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
* CoreAudio audio output driver for Mac OS X
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* original copyright (C) Timothy J. Wood - Aug 2000
|
||||
* ported to MPlayer libao2 by Dan Christiansen
|
||||
*
|
||||
* Chris Roccati
|
||||
* Stefano Pigozzi
|
||||
*
|
||||
* The S/PDIF part of the code is based on the auhal audio output
|
||||
* module from VideoLAN:
|
||||
* Copyright (c) 2006 Derk-Jan Hartman <hartman at videolan dot org>
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* MPlayer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with MPlayer; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <CoreAudio/HostTime.h>
|
||||
/*
|
||||
* The MacOS X CoreAudio framework doesn't mesh as simply as some
|
||||
* simpler frameworks do. This is due to the fact that CoreAudio pulls
|
||||
* audio samples rather than having them pushed at it (which is nice
|
||||
* when you are wanting to do good buffering of audio).
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "ao.h"
|
||||
@@ -28,64 +46,61 @@
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
|
||||
struct priv {
|
||||
AudioDeviceID device;
|
||||
AudioUnit audio_unit;
|
||||
static void audio_pause(struct ao *ao);
|
||||
static void audio_resume(struct ao *ao);
|
||||
static void reset(struct ao *ao);
|
||||
|
||||
uint64_t hw_latency_us;
|
||||
static void print_buffer(struct ao *ao, struct mp_ring *buffer)
|
||||
{
|
||||
void *tctx = talloc_new(NULL);
|
||||
MP_VERBOSE(ao, "%s\n", mp_ring_repr(buffer, tctx));
|
||||
talloc_free(tctx);
|
||||
}
|
||||
|
||||
struct priv_d {
|
||||
// digital render callback
|
||||
AudioDeviceIOProcID render_cb;
|
||||
|
||||
// pid set for hog mode, (-1) means that hog mode on the device was
|
||||
// released. hog mode is exclusive access to a device
|
||||
pid_t hog_pid;
|
||||
|
||||
// stream selected for digital playback by the detection in init
|
||||
AudioStreamID stream;
|
||||
|
||||
// stream index in an AudioBufferList
|
||||
int stream_idx;
|
||||
|
||||
// format we changed the stream to: for the digital case each application
|
||||
// sets the stream format for a device to what it needs
|
||||
AudioStreamBasicDescription stream_asbd;
|
||||
AudioStreamBasicDescription original_asbd;
|
||||
|
||||
bool changed_mixing;
|
||||
int stream_asbd_changed;
|
||||
bool muted;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
AudioDeviceID device; // selected device
|
||||
bool is_digital; // running in digital mode?
|
||||
|
||||
AudioUnit audio_unit; // AudioUnit for lpcm output
|
||||
|
||||
bool paused;
|
||||
|
||||
struct mp_ring *buffer;
|
||||
struct priv_d *digital;
|
||||
|
||||
// options
|
||||
int opt_device_id;
|
||||
int opt_list;
|
||||
};
|
||||
|
||||
bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
struct mp_chmap *chmap);
|
||||
|
||||
static int64_t ca_frames_to_us(struct ao *ao, uint32_t frames)
|
||||
static int get_ring_size(struct ao *ao)
|
||||
{
|
||||
return frames / (float) ao->samplerate * 1e6;
|
||||
}
|
||||
|
||||
static int64_t ca_get_hardware_latency(struct ao *ao) {
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
double audiounit_latency_sec = 0.0;
|
||||
uint32_t size = sizeof(audiounit_latency_sec);
|
||||
OSStatus err = AudioUnitGetProperty(
|
||||
p->audio_unit,
|
||||
kAudioUnitProperty_Latency,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&audiounit_latency_sec,
|
||||
&size);
|
||||
CHECK_CA_ERROR("cannot get audio unit latency");
|
||||
|
||||
uint32_t frames = 0;
|
||||
err = CA_GET_O(p->device, kAudioDevicePropertyLatency, &frames);
|
||||
CHECK_CA_ERROR("cannot get device latency");
|
||||
|
||||
uint64_t audiounit_latency_us = audiounit_latency_sec * 1e6;
|
||||
uint64_t device_latency_us = ca_frames_to_us(ao, frames);
|
||||
|
||||
MP_VERBOSE(ao, "audiounit latency [us]: %lld\n", audiounit_latency_us);
|
||||
MP_VERBOSE(ao, "device latency [us]: %lld\n", device_latency_us);
|
||||
|
||||
return audiounit_latency_us + device_latency_us;
|
||||
|
||||
coreaudio_error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int64_t ca_get_latency(const AudioTimeStamp *ts)
|
||||
{
|
||||
uint64_t out = AudioConvertHostTimeToNanos(ts->mHostTime);
|
||||
uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
|
||||
|
||||
if (now > out)
|
||||
return 0;
|
||||
|
||||
return (out - now) * 1e-3;
|
||||
return af_fmt_seconds_to_bytes(
|
||||
ao->format, 0.5, ao->channels.num, ao->samplerate);
|
||||
}
|
||||
|
||||
static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||
@@ -94,125 +109,259 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||
{
|
||||
struct ao *ao = ctx;
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
AudioBuffer buf = buffer_list->mBuffers[0];
|
||||
int requested = buf.mDataByteSize;
|
||||
|
||||
int64_t end = mp_time_us();
|
||||
end += p->hw_latency_us + ca_get_latency(ts) + ca_frames_to_us(ao, frames);
|
||||
if (mp_ring_buffered(p->buffer) < requested) {
|
||||
MP_VERBOSE(ao, "buffer underrun\n");
|
||||
audio_pause(ao);
|
||||
memset(buf.mData, 0, requested);
|
||||
} else {
|
||||
mp_ring_read(p->buffer, buf.mData, requested);
|
||||
}
|
||||
|
||||
ao_read_data(ao, &buf.mData, frames, end);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static int get_volume(struct ao *ao, struct ao_control_vol *vol) {
|
||||
struct priv *p = ao->priv;
|
||||
float auvol;
|
||||
OSStatus err =
|
||||
AudioUnitGetParameter(p->audio_unit, kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Global, 0, &auvol);
|
||||
static OSStatus render_cb_digital(
|
||||
AudioDeviceID device, const AudioTimeStamp *ts,
|
||||
const void *in_data, const AudioTimeStamp *in_ts,
|
||||
AudioBufferList *out_data, const AudioTimeStamp *out_ts, void *ctx)
|
||||
{
|
||||
struct ao *ao = ctx;
|
||||
struct priv *p = ao->priv;
|
||||
struct priv_d *d = p->digital;
|
||||
AudioBuffer buf = out_data->mBuffers[d->stream_idx];
|
||||
int requested = buf.mDataByteSize;
|
||||
|
||||
CHECK_CA_ERROR("could not get HAL output volume");
|
||||
vol->left = vol->right = auvol * 100.0;
|
||||
return CONTROL_TRUE;
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
if (d->muted)
|
||||
mp_ring_drain(p->buffer, requested);
|
||||
else
|
||||
mp_ring_read(p->buffer, buf.mData, requested);
|
||||
|
||||
static int set_volume(struct ao *ao, struct ao_control_vol *vol) {
|
||||
struct priv *p = ao->priv;
|
||||
float auvol = (vol->left + vol->right) / 200.0;
|
||||
OSStatus err =
|
||||
AudioUnitSetParameter(p->audio_unit, kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Global, 0, auvol, 0);
|
||||
CHECK_CA_ERROR("could not set HAL output volume");
|
||||
return CONTROL_TRUE;
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
ao_control_vol_t *control_vol;
|
||||
OSStatus err;
|
||||
Float32 vol;
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
return get_volume(ao, arg);
|
||||
control_vol = (ao_control_vol_t *)arg;
|
||||
if (p->is_digital) {
|
||||
struct priv_d *d = p->digital;
|
||||
// Digital output has no volume adjust.
|
||||
int digitalvol = d->muted ? 0 : 100;
|
||||
*control_vol = (ao_control_vol_t) {
|
||||
.left = digitalvol, .right = digitalvol,
|
||||
};
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
|
||||
err = AudioUnitGetParameter(p->audio_unit, kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Global, 0, &vol);
|
||||
|
||||
CHECK_CA_ERROR("could not get HAL output volume");
|
||||
control_vol->left = control_vol->right = vol * 100.0;
|
||||
return CONTROL_TRUE;
|
||||
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
return set_volume(ao, arg);
|
||||
}
|
||||
control_vol = (ao_control_vol_t *)arg;
|
||||
|
||||
if (p->is_digital) {
|
||||
struct priv_d *d = p->digital;
|
||||
// Digital output can not set volume. Here we have to return true
|
||||
// to make mixer forget it. Else mixer will add a soft filter,
|
||||
// that's not we expected and the filter not support ac3 stream
|
||||
// will cause mplayer die.
|
||||
|
||||
// Although not support set volume, but at least we support mute.
|
||||
// MPlayer set mute by set volume to zero, we handle it.
|
||||
if (control_vol->left == 0 && control_vol->right == 0)
|
||||
d->muted = true;
|
||||
else
|
||||
d->muted = false;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
|
||||
vol = (control_vol->left + control_vol->right) / 200.0;
|
||||
err = AudioUnitSetParameter(p->audio_unit, kHALOutputParam_Volume,
|
||||
kAudioUnitScope_Global, 0, vol, 0);
|
||||
|
||||
CHECK_CA_ERROR("could not set HAL output volume");
|
||||
return CONTROL_TRUE;
|
||||
|
||||
} // end switch
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static bool init_chmap(struct ao *ao);
|
||||
static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd);
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
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))
|
||||
goto coreaudio_error;
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
AudioStreamBasicDescription asbd;
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
|
||||
if (!init_audiounit(ao, asbd))
|
||||
goto coreaudio_error;
|
||||
|
||||
return CONTROL_OK;
|
||||
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static bool init_chmap(struct ao *ao)
|
||||
static void print_list(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err;
|
||||
AudioChannelLayout *layouts;
|
||||
size_t n_layouts;
|
||||
char *help = talloc_strdup(NULL, "Available output devices:\n");
|
||||
|
||||
err = CA_GET_ARY_O(p->device,
|
||||
kAudioDevicePropertyPreferredChannelLayout,
|
||||
&layouts, &n_layouts);
|
||||
CHECK_CA_ERROR("could not get audio device prefered layouts");
|
||||
AudioDeviceID *devs;
|
||||
size_t n_devs;
|
||||
|
||||
struct mp_chmap_sel chmap_sel = {0};
|
||||
for (int i = 0; i < n_layouts; i++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
if (ca_layout_to_mp_chmap(ao, &layouts[i], &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
OSStatus err =
|
||||
CA_GET_ARY(kAudioObjectSystemObject, kAudioHardwarePropertyDevices,
|
||||
&devs, &n_devs);
|
||||
|
||||
CHECK_CA_ERROR("Failed to get list of output devices.");
|
||||
|
||||
for (int i = 0; i < n_devs; i++) {
|
||||
char *name;
|
||||
err = CA_GET_STR(devs[i], kAudioObjectPropertyName, &name);
|
||||
|
||||
if (err == noErr)
|
||||
talloc_steal(devs, name);
|
||||
else
|
||||
name = "Unknown";
|
||||
|
||||
help = talloc_asprintf_append(
|
||||
help, " * %s (id: %" PRIu32 ")\n", name, devs[i]);
|
||||
}
|
||||
|
||||
talloc_free(layouts);
|
||||
|
||||
if (ao->channels.num < 3) {
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_from_channels(&chmap, ao->channels.num);
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
}
|
||||
|
||||
if (!ao_chmap_sel_adjust(ao, &chmap_sel, &ao->channels)) {
|
||||
MP_ERR(ao, "could not select a suitable channel map among the "
|
||||
"hardware supported ones. Make sure to configure your "
|
||||
"output device correctly in 'Audio MIDI Setup.app'\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
return true;
|
||||
talloc_free(devs);
|
||||
|
||||
coreaudio_error:
|
||||
return false;
|
||||
MP_INFO(ao, "%s", help);
|
||||
talloc_free(help);
|
||||
}
|
||||
|
||||
static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
static int init_lpcm(struct ao *ao, AudioStreamBasicDescription asbd);
|
||||
static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd);
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
OSStatus err;
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (p->opt_list) print_list(ao);
|
||||
|
||||
struct priv_d *d = talloc_zero(p, struct priv_d);
|
||||
|
||||
*d = (struct priv_d) {
|
||||
.muted = false,
|
||||
.stream_asbd_changed = 0,
|
||||
.hog_pid = -1,
|
||||
.stream = 0,
|
||||
.stream_idx = -1,
|
||||
.changed_mixing = false,
|
||||
};
|
||||
|
||||
p->digital = d;
|
||||
|
||||
ao->per_application_mixer = true;
|
||||
ao->no_persistent_volume = true;
|
||||
|
||||
AudioDeviceID selected_device = 0;
|
||||
if (p->opt_device_id < 0) {
|
||||
// device not set by user, get the default one
|
||||
err = CA_GET(kAudioObjectSystemObject,
|
||||
kAudioHardwarePropertyDefaultOutputDevice,
|
||||
&selected_device);
|
||||
CHECK_CA_ERROR("could not get default audio device");
|
||||
} else {
|
||||
selected_device = p->opt_device_id;
|
||||
}
|
||||
|
||||
if (mp_msg_test(ao->log, MSGL_V)) {
|
||||
char *name;
|
||||
err = CA_GET_STR(selected_device, kAudioObjectPropertyName, &name);
|
||||
CHECK_CA_ERROR("could not get selected audio device name");
|
||||
|
||||
MP_VERBOSE(ao, "selected audio output device: %s (%" PRIu32 ")\n",
|
||||
name, selected_device);
|
||||
|
||||
talloc_free(name);
|
||||
}
|
||||
|
||||
// Save selected device id
|
||||
p->device = selected_device;
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
bool supports_digital = false;
|
||||
/* Probe whether device support S/PDIF stream output if input is AC3 stream. */
|
||||
if (AF_FORMAT_IS_AC3(ao->format)) {
|
||||
if (ca_device_supports_digital(ao, selected_device))
|
||||
supports_digital = true;
|
||||
}
|
||||
|
||||
if (!supports_digital) {
|
||||
AudioChannelLayout *layouts;
|
||||
size_t n_layouts;
|
||||
err = CA_GET_ARY_O(selected_device,
|
||||
kAudioDevicePropertyPreferredChannelLayout,
|
||||
&layouts, &n_layouts);
|
||||
CHECK_CA_ERROR("could not get audio device prefered layouts");
|
||||
|
||||
struct mp_chmap_sel chmap_sel = {0};
|
||||
for (int i = 0; i < n_layouts; i++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
if (ca_layout_to_mp_chmap(ao, &layouts[i], &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
}
|
||||
|
||||
talloc_free(layouts);
|
||||
|
||||
if (ao->channels.num < 3) {
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_from_channels(&chmap, ao->channels.num);
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
}
|
||||
|
||||
if (!ao_chmap_sel_adjust(ao, &chmap_sel, &ao->channels)) {
|
||||
MP_ERR(ao, "could not select a suitable channel map among the "
|
||||
"hardware supported ones. Make sure to configure your "
|
||||
"output device correctly in 'Audio MIDI Setup.app'\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
} // closes if (!supports_digital)
|
||||
|
||||
// Build ASBD for the input format
|
||||
AudioStreamBasicDescription asbd;
|
||||
asbd.mSampleRate = ao->samplerate;
|
||||
asbd.mFormatID = supports_digital ?
|
||||
kAudioFormat60958AC3 : kAudioFormatLinearPCM;
|
||||
asbd.mChannelsPerFrame = ao->channels.num;
|
||||
asbd.mBitsPerChannel = af_fmt2bits(ao->format);
|
||||
asbd.mFormatFlags = kAudioFormatFlagIsPacked;
|
||||
|
||||
if ((ao->format & AF_FORMAT_POINT_MASK) == AF_FORMAT_F)
|
||||
asbd.mFormatFlags |= kAudioFormatFlagIsFloat;
|
||||
|
||||
if ((ao->format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_SI)
|
||||
asbd.mFormatFlags |= kAudioFormatFlagIsSignedInteger;
|
||||
|
||||
if ((ao->format & AF_FORMAT_END_MASK) == AF_FORMAT_BE)
|
||||
asbd.mFormatFlags |= kAudioFormatFlagIsBigEndian;
|
||||
|
||||
asbd.mFramesPerPacket = 1;
|
||||
asbd.mBytesPerPacket = asbd.mBytesPerFrame =
|
||||
asbd.mFramesPerPacket * asbd.mChannelsPerFrame *
|
||||
(asbd.mBitsPerChannel / 8);
|
||||
|
||||
ca_print_asbd(ao, "source format:", &asbd);
|
||||
|
||||
if (supports_digital)
|
||||
return init_digital(ao, asbd);
|
||||
else
|
||||
return init_lpcm(ao, asbd);
|
||||
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static int init_lpcm(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
{
|
||||
OSStatus err;
|
||||
uint32_t size;
|
||||
@@ -237,6 +386,7 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
err = AudioComponentInstanceNew(comp, &(p->audio_unit));
|
||||
CHECK_CA_ERROR("unable to open audio component");
|
||||
|
||||
// Initialize AudioUnit
|
||||
err = AudioUnitInitialize(p->audio_unit);
|
||||
CHECK_CA_ERROR_L(coreaudio_error_component,
|
||||
"unable to initialize audio unit");
|
||||
@@ -249,6 +399,7 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"unable to set the input format on the audio unit");
|
||||
|
||||
//Set the Current Device to the Default Output Unit.
|
||||
err = AudioUnitSetProperty(p->audio_unit,
|
||||
kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global, 0, &p->device,
|
||||
@@ -256,7 +407,8 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"can't link audio unit to selected device");
|
||||
|
||||
p->hw_latency_us = ca_get_hardware_latency(ao);
|
||||
p->buffer = mp_ring_new(p, get_ring_size(ao));
|
||||
print_buffer(ao, p->buffer);
|
||||
|
||||
AURenderCallbackStruct render_cb = (AURenderCallbackStruct) {
|
||||
.inputProc = render_cb_lpcm,
|
||||
@@ -271,174 +423,279 @@ static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"unable to set render callback on audio unit");
|
||||
|
||||
return true;
|
||||
reset(ao);
|
||||
return CONTROL_OK;
|
||||
|
||||
coreaudio_error_audiounit:
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
coreaudio_error_component:
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
coreaudio_error:
|
||||
return false;
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static void stop(struct ao *ao)
|
||||
static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = AudioOutputUnitStop(p->audio_unit);
|
||||
CHECK_CA_WARN("can't stop audio unit");
|
||||
struct priv_d *d = p->digital;
|
||||
OSStatus err = noErr;
|
||||
|
||||
uint32_t is_alive = 1;
|
||||
err = CA_GET(p->device, kAudioDevicePropertyDeviceIsAlive, &is_alive);
|
||||
CHECK_CA_WARN("could not check whether device is alive");
|
||||
|
||||
if (!is_alive)
|
||||
MP_WARN(ao , "device is not alive\n");
|
||||
|
||||
p->is_digital = 1;
|
||||
|
||||
err = ca_lock_device(p->device, &d->hog_pid);
|
||||
CHECK_CA_WARN("failed to set hogmode");
|
||||
|
||||
err = ca_disable_mixing(ao, p->device, &d->changed_mixing);
|
||||
CHECK_CA_WARN("failed to disable mixing");
|
||||
|
||||
AudioStreamID *streams;
|
||||
size_t n_streams;
|
||||
|
||||
/* Get a list of all the streams on this device. */
|
||||
err = CA_GET_ARY_O(p->device, kAudioDevicePropertyStreams,
|
||||
&streams, &n_streams);
|
||||
|
||||
CHECK_CA_ERROR("could not get number of streams");
|
||||
|
||||
for (int i = 0; i < n_streams && d->stream_idx < 0; i++) {
|
||||
bool digital = ca_stream_supports_digital(ao, streams[i]);
|
||||
|
||||
if (digital) {
|
||||
err = CA_GET(streams[i], kAudioStreamPropertyPhysicalFormat,
|
||||
&d->original_asbd);
|
||||
if (!CHECK_CA_WARN("could not get stream's physical format to "
|
||||
"revert to, getting the next one"))
|
||||
continue;
|
||||
|
||||
AudioStreamRangedDescription *formats;
|
||||
size_t n_formats;
|
||||
|
||||
err = CA_GET_ARY(streams[i],
|
||||
kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
&formats, &n_formats);
|
||||
|
||||
if (!CHECK_CA_WARN("could not get number of stream formats"))
|
||||
continue; // try next one
|
||||
|
||||
int req_rate_format = -1;
|
||||
int max_rate_format = -1;
|
||||
|
||||
d->stream = streams[i];
|
||||
d->stream_idx = i;
|
||||
|
||||
for (int j = 0; j < n_formats; j++)
|
||||
if (ca_format_is_digital(formats[j].mFormat)) {
|
||||
// select the digital format that has exactly the same
|
||||
// samplerate. If an exact match cannot be found, select
|
||||
// the format with highest samplerate as backup.
|
||||
if (formats[j].mFormat.mSampleRate == asbd.mSampleRate) {
|
||||
req_rate_format = j;
|
||||
break;
|
||||
} else if (max_rate_format < 0 ||
|
||||
formats[j].mFormat.mSampleRate >
|
||||
formats[max_rate_format].mFormat.mSampleRate)
|
||||
max_rate_format = j;
|
||||
}
|
||||
|
||||
if (req_rate_format >= 0)
|
||||
d->stream_asbd = formats[req_rate_format].mFormat;
|
||||
else
|
||||
d->stream_asbd = formats[max_rate_format].mFormat;
|
||||
|
||||
talloc_free(formats);
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
if (d->stream_idx < 0) {
|
||||
MP_WARN(ao , "can't find any digital output stream format\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
if (!ca_change_format(ao, d->stream, d->stream_asbd))
|
||||
goto coreaudio_error;
|
||||
|
||||
void *changed = (void *) &(d->stream_asbd_changed);
|
||||
err = ca_enable_device_listener(p->device, changed);
|
||||
CHECK_CA_ERROR("cannot install format change listener during init");
|
||||
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
if (!(p->stream_asdb.mFormatFlags & kAudioFormatFlagIsBigEndian))
|
||||
#else
|
||||
/* tell mplayer that we need a byteswap on AC3 streams, */
|
||||
if (d->stream_asbd.mFormatID & kAudioFormat60958AC3)
|
||||
ao->format = AF_FORMAT_AC3_LE;
|
||||
else if (d->stream_asbd.mFormatFlags & kAudioFormatFlagIsBigEndian)
|
||||
#endif
|
||||
MP_WARN(ao, "stream has non-native byte order, output may fail\n");
|
||||
|
||||
ao->samplerate = d->stream_asbd.mSampleRate;
|
||||
ao->bps = ao->samplerate *
|
||||
(d->stream_asbd.mBytesPerPacket /
|
||||
d->stream_asbd.mFramesPerPacket);
|
||||
|
||||
p->buffer = mp_ring_new(p, get_ring_size(ao));
|
||||
print_buffer(ao, p->buffer);
|
||||
|
||||
err = AudioDeviceCreateIOProcID(p->device,
|
||||
(AudioDeviceIOProc)render_cb_digital,
|
||||
(void *)ao,
|
||||
&d->render_cb);
|
||||
|
||||
CHECK_CA_ERROR("failed to register digital render callback");
|
||||
|
||||
reset(ao);
|
||||
|
||||
return CONTROL_TRUE;
|
||||
|
||||
coreaudio_error:
|
||||
err = ca_unlock_device(p->device, &d->hog_pid);
|
||||
CHECK_CA_WARN("can't release hog mode");
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static void start(struct ao *ao)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
struct priv_d *d = p->digital;
|
||||
void *output_samples = data[0];
|
||||
int num_bytes = samples * ao->sstride;
|
||||
|
||||
// Check whether we need to reset the digital output stream.
|
||||
if (p->is_digital && d->stream_asbd_changed) {
|
||||
d->stream_asbd_changed = 0;
|
||||
if (ca_stream_supports_digital(ao, d->stream)) {
|
||||
if (!ca_change_format(ao, d->stream, d->stream_asbd)) {
|
||||
MP_WARN(ao , "can't restore digital output\n");
|
||||
} else {
|
||||
MP_WARN(ao, "restoring digital output succeeded.\n");
|
||||
reset(ao);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int wrote = mp_ring_write(p->buffer, output_samples, num_bytes);
|
||||
audio_resume(ao);
|
||||
|
||||
return wrote / ao->sstride;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = AudioOutputUnitStart(p->audio_unit);
|
||||
CHECK_CA_WARN("can't start audio unit");
|
||||
audio_pause(ao);
|
||||
mp_ring_reset(p->buffer);
|
||||
}
|
||||
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
return mp_ring_available(p->buffer) / ao->sstride;
|
||||
}
|
||||
|
||||
static float get_delay(struct ao *ao)
|
||||
{
|
||||
// FIXME: should also report the delay of coreaudio itself (hardware +
|
||||
// internal buffers)
|
||||
struct priv *p = ao->priv;
|
||||
return mp_ring_buffered(p->buffer) / (float)ao->bps;
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
AudioOutputUnitStop(p->audio_unit);
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
OSStatus err = noErr;
|
||||
|
||||
if (!p->is_digital) {
|
||||
AudioOutputUnitStop(p->audio_unit);
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
} else {
|
||||
struct priv_d *d = p->digital;
|
||||
|
||||
void *changed = (void *) &(d->stream_asbd_changed);
|
||||
err = ca_disable_device_listener(p->device, changed);
|
||||
CHECK_CA_WARN("can't remove device listener, this may cause a crash");
|
||||
|
||||
err = AudioDeviceStop(p->device, d->render_cb);
|
||||
CHECK_CA_WARN("failed to stop audio device");
|
||||
|
||||
err = AudioDeviceDestroyIOProcID(p->device, d->render_cb);
|
||||
CHECK_CA_WARN("failed to remove device render callback");
|
||||
|
||||
if (!ca_change_format(ao, d->stream, d->original_asbd))
|
||||
MP_WARN(ao, "can't revert to original device format");
|
||||
|
||||
err = ca_enable_mixing(ao, p->device, d->changed_mixing);
|
||||
CHECK_CA_WARN("can't re-enable mixing");
|
||||
|
||||
err = ca_unlock_device(p->device, &d->hog_pid);
|
||||
CHECK_CA_WARN("can't release hog mode");
|
||||
}
|
||||
}
|
||||
|
||||
// Channel Mapping functions
|
||||
static const int speaker_map[][2] = {
|
||||
{ kAudioChannelLabel_Left, MP_SPEAKER_ID_FL },
|
||||
{ kAudioChannelLabel_Right, MP_SPEAKER_ID_FR },
|
||||
{ kAudioChannelLabel_Center, MP_SPEAKER_ID_FC },
|
||||
{ kAudioChannelLabel_LFEScreen, MP_SPEAKER_ID_LFE },
|
||||
{ kAudioChannelLabel_LeftSurround, MP_SPEAKER_ID_BL },
|
||||
{ kAudioChannelLabel_RightSurround, MP_SPEAKER_ID_BR },
|
||||
{ kAudioChannelLabel_LeftCenter, MP_SPEAKER_ID_FLC },
|
||||
{ kAudioChannelLabel_RightCenter, MP_SPEAKER_ID_FRC },
|
||||
{ kAudioChannelLabel_CenterSurround, MP_SPEAKER_ID_BC },
|
||||
{ kAudioChannelLabel_LeftSurroundDirect, MP_SPEAKER_ID_SL },
|
||||
{ kAudioChannelLabel_RightSurroundDirect, MP_SPEAKER_ID_SR },
|
||||
{ kAudioChannelLabel_TopCenterSurround, MP_SPEAKER_ID_TC },
|
||||
{ kAudioChannelLabel_VerticalHeightLeft, MP_SPEAKER_ID_TFL },
|
||||
{ kAudioChannelLabel_VerticalHeightCenter, MP_SPEAKER_ID_TFC },
|
||||
{ kAudioChannelLabel_VerticalHeightRight, MP_SPEAKER_ID_TFR },
|
||||
{ kAudioChannelLabel_TopBackLeft, MP_SPEAKER_ID_TBL },
|
||||
{ kAudioChannelLabel_TopBackCenter, MP_SPEAKER_ID_TBC },
|
||||
{ kAudioChannelLabel_TopBackRight, MP_SPEAKER_ID_TBR },
|
||||
|
||||
// unofficial extensions
|
||||
{ kAudioChannelLabel_RearSurroundLeft, MP_SPEAKER_ID_SDL },
|
||||
{ kAudioChannelLabel_RearSurroundRight, MP_SPEAKER_ID_SDR },
|
||||
{ kAudioChannelLabel_LeftWide, MP_SPEAKER_ID_WL },
|
||||
{ kAudioChannelLabel_RightWide, MP_SPEAKER_ID_WR },
|
||||
{ kAudioChannelLabel_LFE2, MP_SPEAKER_ID_LFE2 },
|
||||
|
||||
{ kAudioChannelLabel_HeadphonesLeft, MP_SPEAKER_ID_DL },
|
||||
{ kAudioChannelLabel_HeadphonesRight, MP_SPEAKER_ID_DR },
|
||||
|
||||
{ kAudioChannelLabel_Unknown, -1 },
|
||||
};
|
||||
|
||||
static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
for (int i = 0; speaker_map[i][1] >= 0; i++)
|
||||
if (speaker_map[i][0] == label)
|
||||
return speaker_map[i][1];
|
||||
return -1;
|
||||
}
|
||||
struct priv *p = ao->priv;
|
||||
OSErr err = noErr;
|
||||
|
||||
static void ca_log_layout(struct ao *ao, AudioChannelLayout *layout)
|
||||
{
|
||||
if (!mp_msg_test(ao->log, MSGL_V))
|
||||
if (p->paused)
|
||||
return;
|
||||
|
||||
AudioChannelDescription *descs = layout->mChannelDescriptions;
|
||||
|
||||
MP_VERBOSE(ao, "layout: tag: <%d>, bitmap: <%d>, "
|
||||
"descriptions <%d>\n",
|
||||
layout->mChannelLayoutTag,
|
||||
layout->mChannelBitmap,
|
||||
layout->mNumberChannelDescriptions);
|
||||
|
||||
for (int i = 0; i < layout->mNumberChannelDescriptions; i++) {
|
||||
AudioChannelDescription d = descs[i];
|
||||
MP_VERBOSE(ao, " - description %d: label <%d, %d>, flags: <%u>, "
|
||||
"coords: <%f, %f, %f>\n", i,
|
||||
d.mChannelLabel,
|
||||
ca_label_to_mp_speaker_id(d.mChannelLabel),
|
||||
d.mChannelFlags,
|
||||
d.mCoordinates[0],
|
||||
d.mCoordinates[1],
|
||||
d.mCoordinates[2]);
|
||||
if (!p->is_digital) {
|
||||
err = AudioOutputUnitStop(p->audio_unit);
|
||||
CHECK_CA_WARN("can't stop audio unit");
|
||||
} else {
|
||||
struct priv_d *d = p->digital;
|
||||
err = AudioDeviceStop(p->device, d->render_cb);
|
||||
CHECK_CA_WARN("can't stop digital device");
|
||||
}
|
||||
|
||||
p->paused = true;
|
||||
}
|
||||
|
||||
bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
struct mp_chmap *chmap)
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
AudioChannelLayoutTag tag = layout->mChannelLayoutTag;
|
||||
uint32_t layout_size = sizeof(layout);
|
||||
OSStatus err;
|
||||
struct priv *p = ao->priv;
|
||||
OSErr err = noErr;
|
||||
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
|
||||
err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(uint32_t),
|
||||
&layout->mChannelBitmap,
|
||||
&layout_size,
|
||||
layout);
|
||||
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions");
|
||||
} else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
|
||||
err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag),
|
||||
&layout->mChannelLayoutTag,
|
||||
&layout_size,
|
||||
layout);
|
||||
CHECK_CA_ERROR("failed to convert channel tag to descriptions");
|
||||
if (!p->paused)
|
||||
return;
|
||||
|
||||
if (!p->is_digital) {
|
||||
err = AudioOutputUnitStart(p->audio_unit);
|
||||
CHECK_CA_WARN("can't start audio unit");
|
||||
} else {
|
||||
struct priv_d *d = p->digital;
|
||||
err = AudioDeviceStart(p->device, d->render_cb);
|
||||
CHECK_CA_WARN("can't start digital device");
|
||||
}
|
||||
|
||||
ca_log_layout(ao, layout);
|
||||
|
||||
// If the channel layout uses channel descriptions, from my
|
||||
// experiments there are there three possibile cases:
|
||||
// * The description has a label kAudioChannelLabel_Unknown:
|
||||
// Can't do anything about this (looks like non surround
|
||||
// layouts are like this).
|
||||
// * The description uses positional information: this in
|
||||
// theory could be used but one would have to map spatial
|
||||
// positions to labels which is not really feasible.
|
||||
// * The description has a well known label which can be mapped
|
||||
// to the waveextensible definition: this is the kind of
|
||||
// descriptions we process here.
|
||||
|
||||
for (int n = 0; n < layout->mNumberChannelDescriptions; n++) {
|
||||
AudioChannelLabel label = layout->mChannelDescriptions[n].mChannelLabel;
|
||||
uint8_t speaker = ca_label_to_mp_speaker_id(label);
|
||||
if (label == kAudioChannelLabel_Unknown)
|
||||
continue;
|
||||
if (speaker < 0) {
|
||||
MP_VERBOSE(ao, "channel label=%d unusable to build channel "
|
||||
"bitmap, skipping layout\n", label);
|
||||
} else {
|
||||
chmap->speaker[n] = speaker;
|
||||
chmap->num = n + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return chmap->num > 0;
|
||||
coreaudio_error:
|
||||
ca_log_layout(ao, layout);
|
||||
return false;
|
||||
p->paused = false;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_coreaudio = {
|
||||
.description = "CoreAudio AudioUnit",
|
||||
.description = "CoreAudio (OS X Audio Output)",
|
||||
.name = "coreaudio",
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.play = play,
|
||||
.control = control,
|
||||
.pause = stop,
|
||||
.resume = start,
|
||||
.get_space = get_space,
|
||||
.get_delay = get_delay,
|
||||
.reset = reset,
|
||||
.pause = audio_pause,
|
||||
.resume = audio_resume,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INT("device_id", opt_device_id, 0, OPTDEF_INT(-1)),
|
||||
|
||||
@@ -1,683 +0,0 @@
|
||||
/*
|
||||
* CoreAudio audio output driver for Mac OS X
|
||||
*
|
||||
* original copyright (C) Timothy J. Wood - Aug 2000
|
||||
* ported to MPlayer libao2 by Dan Christiansen
|
||||
*
|
||||
* Chris Roccati
|
||||
* Stefano Pigozzi
|
||||
*
|
||||
* The S/PDIF part of the code is based on the auhal audio output
|
||||
* module from VideoLAN:
|
||||
* Copyright (c) 2006 Derk-Jan Hartman <hartman at videolan dot org>
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* along with MPlayer; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* The MacOS X CoreAudio framework doesn't mesh as simply as some
|
||||
* simpler frameworks do. This is due to the fact that CoreAudio pulls
|
||||
* audio samples rather than having them pushed at it (which is nice
|
||||
* when you are wanting to do good buffering of audio).
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "options/m_option.h"
|
||||
#include "misc/ring.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
|
||||
static void audio_pause(struct ao *ao);
|
||||
static void audio_resume(struct ao *ao);
|
||||
static void reset(struct ao *ao);
|
||||
|
||||
static bool ca_format_is_digital(AudioStreamBasicDescription asbd)
|
||||
{
|
||||
switch (asbd.mFormatID)
|
||||
case 'IAC3':
|
||||
case 'iac3':
|
||||
case kAudioFormat60958AC3:
|
||||
case kAudioFormatAC3:
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ca_stream_supports_digital(struct ao *ao, AudioStreamID stream)
|
||||
{
|
||||
AudioStreamRangedDescription *formats = NULL;
|
||||
size_t n_formats;
|
||||
|
||||
OSStatus err =
|
||||
CA_GET_ARY(stream, kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
&formats, &n_formats);
|
||||
|
||||
CHECK_CA_ERROR("Could not get number of stream formats.");
|
||||
|
||||
for (int i = 0; i < n_formats; i++) {
|
||||
AudioStreamBasicDescription asbd = formats[i].mFormat;
|
||||
ca_print_asbd(ao, "supported format:", &(asbd));
|
||||
if (ca_format_is_digital(asbd)) {
|
||||
talloc_free(formats);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(formats);
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ca_device_supports_digital(struct ao *ao, AudioDeviceID device)
|
||||
{
|
||||
AudioStreamID *streams = NULL;
|
||||
size_t n_streams;
|
||||
|
||||
/* Retrieve all the output streams. */
|
||||
OSStatus err =
|
||||
CA_GET_ARY_O(device, kAudioDevicePropertyStreams, &streams, &n_streams);
|
||||
|
||||
CHECK_CA_ERROR("could not get number of streams.");
|
||||
|
||||
for (int i = 0; i < n_streams; i++) {
|
||||
if (ca_stream_supports_digital(ao, streams[i])) {
|
||||
talloc_free(streams);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
static OSStatus ca_property_listener(
|
||||
AudioObjectPropertySelector selector,
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
void *talloc_ctx = talloc_new(NULL);
|
||||
|
||||
for (int i = 0; i < n_addresses; i++) {
|
||||
if (addresses[i].mSelector == selector) {
|
||||
if (data) *(volatile int *)data = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
talloc_free(talloc_ctx);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_stream_listener(
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
return ca_property_listener(kAudioStreamPropertyPhysicalFormat,
|
||||
object, n_addresses, addresses, data);
|
||||
}
|
||||
|
||||
static OSStatus ca_device_listener(
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
return ca_property_listener(kAudioDevicePropertyDeviceHasChanged,
|
||||
object, n_addresses, addresses, data);
|
||||
}
|
||||
|
||||
static OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid) {
|
||||
*pid = getpid();
|
||||
OSStatus err = CA_SET(device, kAudioDevicePropertyHogMode, pid);
|
||||
if (err != noErr)
|
||||
*pid = -1;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid) {
|
||||
if (*pid == getpid()) {
|
||||
*pid = -1;
|
||||
return CA_SET(device, kAudioDevicePropertyHogMode, &pid);
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_change_mixing(struct ao *ao, AudioDeviceID device,
|
||||
uint32_t val, bool *changed) {
|
||||
*changed = false;
|
||||
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertySupportsMixing,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
if (AudioObjectHasProperty(device, &p_addr)) {
|
||||
OSStatus err;
|
||||
Boolean writeable = 0;
|
||||
err = CA_SETTABLE(device, kAudioDevicePropertySupportsMixing,
|
||||
&writeable);
|
||||
|
||||
if (!CHECK_CA_WARN("can't tell if mixing property is settable")) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!writeable)
|
||||
return noErr;
|
||||
|
||||
err = CA_SET(device, kAudioDevicePropertySupportsMixing, &val);
|
||||
if (err != noErr)
|
||||
return err;
|
||||
|
||||
if (!CHECK_CA_WARN("can't set mix mode")) {
|
||||
return err;
|
||||
}
|
||||
|
||||
*changed = true;
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_disable_mixing(struct ao *ao,
|
||||
AudioDeviceID device, bool *changed) {
|
||||
return ca_change_mixing(ao, device, 0, changed);
|
||||
}
|
||||
|
||||
static OSStatus ca_enable_mixing(struct ao *ao,
|
||||
AudioDeviceID device, bool changed) {
|
||||
if (changed) {
|
||||
bool dont_care = false;
|
||||
return ca_change_mixing(ao, device, 1, &dont_care);
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_change_device_listening(AudioDeviceID device,
|
||||
void *flag, bool enabled)
|
||||
{
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertyDeviceHasChanged,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
if (enabled) {
|
||||
return AudioObjectAddPropertyListener(
|
||||
device, &p_addr, ca_device_listener, flag);
|
||||
} else {
|
||||
return AudioObjectRemovePropertyListener(
|
||||
device, &p_addr, ca_device_listener, flag);
|
||||
}
|
||||
}
|
||||
|
||||
static OSStatus ca_enable_device_listener(AudioDeviceID device, void *flag) {
|
||||
return ca_change_device_listening(device, flag, true);
|
||||
}
|
||||
|
||||
static OSStatus ca_disable_device_listener(AudioDeviceID device, void *flag) {
|
||||
return ca_change_device_listening(device, flag, false);
|
||||
}
|
||||
|
||||
static bool ca_change_format(struct ao *ao, AudioStreamID stream,
|
||||
AudioStreamBasicDescription change_format)
|
||||
{
|
||||
OSStatus err = noErr;
|
||||
AudioObjectPropertyAddress p_addr;
|
||||
volatile int stream_format_changed = 0;
|
||||
|
||||
ca_print_asbd(ao, "setting stream format:", &change_format);
|
||||
|
||||
/* Install the callback. */
|
||||
p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioStreamPropertyPhysicalFormat,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
err = AudioObjectAddPropertyListener(stream, &p_addr, ca_stream_listener,
|
||||
(void *)&stream_format_changed);
|
||||
if (!CHECK_CA_WARN("can't add property listener during format change")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Change the format. */
|
||||
err = CA_SET(stream, kAudioStreamPropertyPhysicalFormat, &change_format);
|
||||
if (!CHECK_CA_WARN("error changing physical format")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The AudioStreamSetProperty is not only asynchronious,
|
||||
* it is also not Atomic, in its behaviour.
|
||||
* Therefore we check 5 times before we really give up. */
|
||||
bool format_set = false;
|
||||
for (int i = 0; !format_set && i < 5; i++) {
|
||||
for (int j = 0; !stream_format_changed && j < 50; j++)
|
||||
mp_sleep_us(10000);
|
||||
|
||||
if (stream_format_changed) {
|
||||
stream_format_changed = 0;
|
||||
} else {
|
||||
MP_VERBOSE(ao, "reached timeout\n");
|
||||
}
|
||||
|
||||
AudioStreamBasicDescription actual_format;
|
||||
err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &actual_format);
|
||||
|
||||
ca_print_asbd(ao, "actual format in use:", &actual_format);
|
||||
if (actual_format.mSampleRate == change_format.mSampleRate &&
|
||||
actual_format.mFormatID == change_format.mFormatID &&
|
||||
actual_format.mFramesPerPacket == change_format.mFramesPerPacket) {
|
||||
format_set = true;
|
||||
}
|
||||
}
|
||||
|
||||
err = AudioObjectRemovePropertyListener(stream, &p_addr, ca_stream_listener,
|
||||
(void *)&stream_format_changed);
|
||||
|
||||
if (!CHECK_CA_WARN("can't remove property listener")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return format_set;
|
||||
}
|
||||
|
||||
|
||||
struct priv {
|
||||
AudioDeviceID device; // selected device
|
||||
|
||||
bool paused;
|
||||
|
||||
struct mp_ring *buffer;
|
||||
|
||||
// digital render callback
|
||||
AudioDeviceIOProcID render_cb;
|
||||
|
||||
// pid set for hog mode, (-1) means that hog mode on the device was
|
||||
// released. hog mode is exclusive access to a device
|
||||
pid_t hog_pid;
|
||||
|
||||
// stream selected for digital playback by the detection in init
|
||||
AudioStreamID stream;
|
||||
|
||||
// stream index in an AudioBufferList
|
||||
int stream_idx;
|
||||
|
||||
// format we changed the stream to: for the digital case each application
|
||||
// sets the stream format for a device to what it needs
|
||||
AudioStreamBasicDescription stream_asbd;
|
||||
AudioStreamBasicDescription original_asbd;
|
||||
|
||||
bool changed_mixing;
|
||||
int stream_asbd_changed;
|
||||
bool muted;
|
||||
|
||||
// options
|
||||
int opt_device_id;
|
||||
int opt_list;
|
||||
};
|
||||
|
||||
static int get_ring_size(struct ao *ao)
|
||||
{
|
||||
return af_fmt_seconds_to_bytes(
|
||||
ao->format, 0.5, ao->channels.num, ao->samplerate);
|
||||
}
|
||||
|
||||
static OSStatus render_cb_digital(
|
||||
AudioDeviceID device, const AudioTimeStamp *ts,
|
||||
const void *in_data, const AudioTimeStamp *in_ts,
|
||||
AudioBufferList *out_data, const AudioTimeStamp *out_ts, void *ctx)
|
||||
{
|
||||
struct ao *ao = ctx;
|
||||
struct priv *p = ao->priv;
|
||||
AudioBuffer buf = out_data->mBuffers[p->stream_idx];
|
||||
int requested = buf.mDataByteSize;
|
||||
|
||||
if (p->muted)
|
||||
mp_ring_drain(p->buffer, requested);
|
||||
else
|
||||
mp_ring_read(p->buffer, buf.mData, requested);
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
ao_control_vol_t *control_vol;
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
control_vol = (ao_control_vol_t *)arg;
|
||||
// Digital output has no volume adjust.
|
||||
int digitalvol = p->muted ? 0 : 100;
|
||||
*control_vol = (ao_control_vol_t) {
|
||||
.left = digitalvol, .right = digitalvol,
|
||||
};
|
||||
return CONTROL_TRUE;
|
||||
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
control_vol = (ao_control_vol_t *)arg;
|
||||
// Digital output can not set volume. Here we have to return true
|
||||
// to make mixer forget it. Else mixer will add a soft filter,
|
||||
// that's not we expected and the filter not support ac3 stream
|
||||
// will cause mplayer die.
|
||||
|
||||
// Although not support set volume, but at least we support mute.
|
||||
// MPlayer set mute by set volume to zero, we handle it.
|
||||
if (control_vol->left == 0 && control_vol->right == 0)
|
||||
p->muted = true;
|
||||
else
|
||||
p->muted = false;
|
||||
return CONTROL_TRUE;
|
||||
|
||||
} // end switch
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd);
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (p->opt_list) ca_print_device_list(ao);
|
||||
|
||||
OSStatus err = ca_select_device(ao, p->opt_device_id, &p->device);
|
||||
CHECK_CA_ERROR("failed to select device");
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
bool supports_digital = false;
|
||||
/* Probe whether device support S/PDIF stream output if input is AC3 stream. */
|
||||
if (AF_FORMAT_IS_AC3(ao->format)) {
|
||||
if (ca_device_supports_digital(ao, p->device))
|
||||
supports_digital = true;
|
||||
}
|
||||
|
||||
if (!supports_digital) {
|
||||
MP_ERR(ao, "selected device doesn't support digital formats\n");
|
||||
goto coreaudio_error;
|
||||
} // closes if (!supports_digital)
|
||||
|
||||
// Build ASBD for the input format
|
||||
AudioStreamBasicDescription asbd;
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
|
||||
return init_digital(ao, asbd);
|
||||
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = noErr;
|
||||
|
||||
uint32_t is_alive = 1;
|
||||
err = CA_GET(p->device, kAudioDevicePropertyDeviceIsAlive, &is_alive);
|
||||
CHECK_CA_WARN("could not check whether device is alive");
|
||||
|
||||
if (!is_alive)
|
||||
MP_WARN(ao , "device is not alive\n");
|
||||
|
||||
err = ca_lock_device(p->device, &p->hog_pid);
|
||||
CHECK_CA_WARN("failed to set hogmode");
|
||||
|
||||
err = ca_disable_mixing(ao, p->device, &p->changed_mixing);
|
||||
CHECK_CA_WARN("failed to disable mixing");
|
||||
|
||||
AudioStreamID *streams;
|
||||
size_t n_streams;
|
||||
|
||||
/* Get a list of all the streams on this device. */
|
||||
err = CA_GET_ARY_O(p->device, kAudioDevicePropertyStreams,
|
||||
&streams, &n_streams);
|
||||
|
||||
CHECK_CA_ERROR("could not get number of streams");
|
||||
|
||||
for (int i = 0; i < n_streams && p->stream_idx < 0; i++) {
|
||||
bool digital = ca_stream_supports_digital(ao, streams[i]);
|
||||
|
||||
if (digital) {
|
||||
err = CA_GET(streams[i], kAudioStreamPropertyPhysicalFormat,
|
||||
&p->original_asbd);
|
||||
if (!CHECK_CA_WARN("could not get stream's physical format to "
|
||||
"revert to, getting the next one"))
|
||||
continue;
|
||||
|
||||
AudioStreamRangedDescription *formats;
|
||||
size_t n_formats;
|
||||
|
||||
err = CA_GET_ARY(streams[i],
|
||||
kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
&formats, &n_formats);
|
||||
|
||||
if (!CHECK_CA_WARN("could not get number of stream formats"))
|
||||
continue; // try next one
|
||||
|
||||
int req_rate_format = -1;
|
||||
int max_rate_format = -1;
|
||||
|
||||
p->stream = streams[i];
|
||||
p->stream_idx = i;
|
||||
|
||||
for (int j = 0; j < n_formats; j++)
|
||||
if (ca_format_is_digital(formats[j].mFormat)) {
|
||||
// select the digital format that has exactly the same
|
||||
// samplerate. If an exact match cannot be found, select
|
||||
// the format with highest samplerate as backup.
|
||||
if (formats[j].mFormat.mSampleRate == asbd.mSampleRate) {
|
||||
req_rate_format = j;
|
||||
break;
|
||||
} else if (max_rate_format < 0 ||
|
||||
formats[j].mFormat.mSampleRate >
|
||||
formats[max_rate_format].mFormat.mSampleRate)
|
||||
max_rate_format = j;
|
||||
}
|
||||
|
||||
if (req_rate_format >= 0)
|
||||
p->stream_asbd = formats[req_rate_format].mFormat;
|
||||
else
|
||||
p->stream_asbd = formats[max_rate_format].mFormat;
|
||||
|
||||
talloc_free(formats);
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
if (p->stream_idx < 0) {
|
||||
MP_WARN(ao , "can't find any digital output stream format\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
if (!ca_change_format(ao, p->stream, p->stream_asbd))
|
||||
goto coreaudio_error;
|
||||
|
||||
void *changed = (void *) &(p->stream_asbd_changed);
|
||||
err = ca_enable_device_listener(p->device, changed);
|
||||
CHECK_CA_ERROR("cannot install format change listener during init");
|
||||
|
||||
#if 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;
|
||||
ao->bps = ao->samplerate *
|
||||
(p->stream_asbd.mBytesPerPacket /
|
||||
p->stream_asbd.mFramesPerPacket);
|
||||
|
||||
p->buffer = mp_ring_new(p, get_ring_size(ao));
|
||||
|
||||
err = AudioDeviceCreateIOProcID(p->device,
|
||||
(AudioDeviceIOProc)render_cb_digital,
|
||||
(void *)ao,
|
||||
&p->render_cb);
|
||||
|
||||
CHECK_CA_ERROR("failed to register digital render callback");
|
||||
|
||||
reset(ao);
|
||||
|
||||
return CONTROL_TRUE;
|
||||
|
||||
coreaudio_error:
|
||||
err = ca_unlock_device(p->device, &p->hog_pid);
|
||||
CHECK_CA_WARN("can't release hog mode");
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
void *output_samples = data[0];
|
||||
int num_bytes = samples * ao->sstride;
|
||||
|
||||
// Check whether we need to reset the digital output stream.
|
||||
if (p->stream_asbd_changed) {
|
||||
p->stream_asbd_changed = 0;
|
||||
if (ca_stream_supports_digital(ao, p->stream)) {
|
||||
if (!ca_change_format(ao, p->stream, p->stream_asbd)) {
|
||||
MP_WARN(ao , "can't restore digital output\n");
|
||||
} else {
|
||||
MP_WARN(ao, "restoring digital output succeeded.\n");
|
||||
reset(ao);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int wrote = mp_ring_write(p->buffer, output_samples, num_bytes);
|
||||
audio_resume(ao);
|
||||
|
||||
return wrote / ao->sstride;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
audio_pause(ao);
|
||||
mp_ring_reset(p->buffer);
|
||||
}
|
||||
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
return mp_ring_available(p->buffer) / ao->sstride;
|
||||
}
|
||||
|
||||
static float get_delay(struct ao *ao)
|
||||
{
|
||||
// FIXME: should also report the delay of coreaudio itself (hardware +
|
||||
// internal buffers)
|
||||
struct priv *p = ao->priv;
|
||||
return mp_ring_buffered(p->buffer) / (float)ao->bps;
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = noErr;
|
||||
|
||||
void *changed = (void *) &(p->stream_asbd_changed);
|
||||
err = ca_disable_device_listener(p->device, changed);
|
||||
CHECK_CA_WARN("can't remove device listener, this may cause a crash");
|
||||
|
||||
err = AudioDeviceStop(p->device, p->render_cb);
|
||||
CHECK_CA_WARN("failed to stop audio device");
|
||||
|
||||
err = AudioDeviceDestroyIOProcID(p->device, p->render_cb);
|
||||
CHECK_CA_WARN("failed to remove device render callback");
|
||||
|
||||
if (!ca_change_format(ao, p->stream, p->original_asbd))
|
||||
MP_WARN(ao, "can't revert to original device format");
|
||||
|
||||
err = ca_enable_mixing(ao, p->device, p->changed_mixing);
|
||||
CHECK_CA_WARN("can't re-enable mixing");
|
||||
|
||||
err = ca_unlock_device(p->device, &p->hog_pid);
|
||||
CHECK_CA_WARN("can't release hog mode");
|
||||
}
|
||||
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (p->paused)
|
||||
return;
|
||||
|
||||
OSStatus err = AudioDeviceStop(p->device, p->render_cb);
|
||||
CHECK_CA_WARN("can't stop digital device");
|
||||
|
||||
p->paused = true;
|
||||
}
|
||||
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (!p->paused)
|
||||
return;
|
||||
|
||||
OSStatus err = AudioDeviceStart(p->device, p->render_cb);
|
||||
CHECK_CA_WARN("can't start digital device");
|
||||
|
||||
p->paused = false;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_coreaudio_exclusive = {
|
||||
.description = "CoreAudio Exclusive Mode",
|
||||
.name = "coreaudio_exclusive",
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.play = play,
|
||||
.control = control,
|
||||
.get_space = get_space,
|
||||
.get_delay = get_delay,
|
||||
.reset = reset,
|
||||
.pause = audio_pause,
|
||||
.resume = audio_resume,
|
||||
.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}
|
||||
},
|
||||
};
|
||||
@@ -37,7 +37,6 @@ OSStatus ca_set(AudioObjectID id, ca_scope scope, ca_sel selector,
|
||||
|
||||
#define CA_GET(id, sel, data) ca_get(id, CA_GLOBAL, sel, sizeof(*(data)), data)
|
||||
#define CA_SET(id, sel, data) ca_set(id, CA_GLOBAL, sel, sizeof(*(data)), data)
|
||||
#define CA_GET_O(id, sel, data) ca_get(id, CA_OUTPUT, sel, sizeof(*(data)), data)
|
||||
|
||||
OSStatus ca_get_ary(AudioObjectID id, ca_scope scope, ca_sel selector,
|
||||
uint32_t element_size, void **data, size_t *elements);
|
||||
|
||||
+374
-90
@@ -25,75 +25,12 @@
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
void ca_print_device_list(struct ao *ao)
|
||||
{
|
||||
char *help = talloc_strdup(NULL, "Available output devices:\n");
|
||||
|
||||
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++) {
|
||||
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:
|
||||
MP_INFO(ao, "%s", help);
|
||||
talloc_free(help);
|
||||
}
|
||||
|
||||
OSStatus ca_select_device(struct ao *ao, int selection, AudioDeviceID *device)
|
||||
{
|
||||
OSStatus err = noErr;
|
||||
*device = 0;
|
||||
if (selection < 0) {
|
||||
// device not set by user, get the default one
|
||||
err = CA_GET(kAudioObjectSystemObject,
|
||||
kAudioHardwarePropertyDefaultOutputDevice,
|
||||
device);
|
||||
CHECK_CA_ERROR("could not get default audio device");
|
||||
} else {
|
||||
*device = selection;
|
||||
}
|
||||
|
||||
if (mp_msg_test(ao->log, MSGL_V)) {
|
||||
char *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",
|
||||
name, *device);
|
||||
|
||||
talloc_free(name);
|
||||
}
|
||||
|
||||
coreaudio_error:
|
||||
return err;
|
||||
}
|
||||
|
||||
char *fourcc_repr(void *talloc_ctx, uint32_t code)
|
||||
{
|
||||
// Extract FourCC letters from the uint32_t and finde out if it's a valid
|
||||
// code that is made of letters.
|
||||
unsigned char fcc[4] = {
|
||||
char fcc[4] = {
|
||||
(code >> 24) & 0xFF,
|
||||
(code >> 16) & 0xFF,
|
||||
(code >> 8) & 0xFF,
|
||||
@@ -102,7 +39,7 @@ char *fourcc_repr(void *talloc_ctx, uint32_t code)
|
||||
|
||||
bool valid_fourcc = true;
|
||||
for (int i = 0; i < 4; i++)
|
||||
if (fcc[i] >= 32 && fcc[i] < 128)
|
||||
if (!isprint(fcc[i]))
|
||||
valid_fourcc = false;
|
||||
|
||||
char *repr;
|
||||
@@ -126,31 +63,6 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
|
||||
return false;
|
||||
}
|
||||
|
||||
void ca_fill_asbd(struct ao *ao, AudioStreamBasicDescription *asbd)
|
||||
{
|
||||
asbd->mSampleRate = ao->samplerate;
|
||||
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_POINT_MASK) == AF_FORMAT_F)
|
||||
asbd->mFormatFlags |= kAudioFormatFlagIsFloat;
|
||||
|
||||
if ((ao->format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_SI)
|
||||
asbd->mFormatFlags |= kAudioFormatFlagIsSignedInteger;
|
||||
|
||||
if ((ao->format & AF_FORMAT_END_MASK) == AF_FORMAT_BE)
|
||||
asbd->mFormatFlags |= kAudioFormatFlagIsBigEndian;
|
||||
|
||||
asbd->mFramesPerPacket = 1;
|
||||
asbd->mBytesPerPacket = asbd->mBytesPerFrame =
|
||||
asbd->mFramesPerPacket * asbd->mChannelsPerFrame *
|
||||
(asbd->mBitsPerChannel / 8);
|
||||
}
|
||||
|
||||
void ca_print_asbd(struct ao *ao, const char *description,
|
||||
const AudioStreamBasicDescription *asbd)
|
||||
{
|
||||
@@ -175,3 +87,375 @@ void ca_print_asbd(struct ao *ao, const char *description,
|
||||
talloc_free(format);
|
||||
}
|
||||
|
||||
bool ca_format_is_digital(AudioStreamBasicDescription asbd)
|
||||
{
|
||||
switch (asbd.mFormatID)
|
||||
case 'IAC3':
|
||||
case 'iac3':
|
||||
case kAudioFormat60958AC3:
|
||||
case kAudioFormatAC3:
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ca_stream_supports_digital(struct ao *ao, AudioStreamID stream)
|
||||
{
|
||||
AudioStreamRangedDescription *formats = NULL;
|
||||
size_t n_formats;
|
||||
|
||||
OSStatus err =
|
||||
CA_GET_ARY(stream, kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
&formats, &n_formats);
|
||||
|
||||
CHECK_CA_ERROR("Could not get number of stream formats.");
|
||||
|
||||
for (int i = 0; i < n_formats; i++) {
|
||||
AudioStreamBasicDescription asbd = formats[i].mFormat;
|
||||
ca_print_asbd(ao, "supported format:", &(asbd));
|
||||
if (ca_format_is_digital(asbd)) {
|
||||
talloc_free(formats);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(formats);
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ca_device_supports_digital(struct ao *ao, AudioDeviceID device)
|
||||
{
|
||||
AudioStreamID *streams = NULL;
|
||||
size_t n_streams;
|
||||
|
||||
/* Retrieve all the output streams. */
|
||||
OSStatus err =
|
||||
CA_GET_ARY_O(device, kAudioDevicePropertyStreams, &streams, &n_streams);
|
||||
|
||||
CHECK_CA_ERROR("could not get number of streams.");
|
||||
|
||||
for (int i = 0; i < n_streams; i++) {
|
||||
if (ca_stream_supports_digital(ao, streams[i])) {
|
||||
talloc_free(streams);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
OSStatus ca_property_listener(AudioObjectPropertySelector selector,
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
void *talloc_ctx = talloc_new(NULL);
|
||||
|
||||
for (int i = 0; i < n_addresses; i++) {
|
||||
if (addresses[i].mSelector == selector) {
|
||||
if (data) *(volatile int *)data = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
talloc_free(talloc_ctx);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSStatus ca_stream_listener(AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
return ca_property_listener(kAudioStreamPropertyPhysicalFormat,
|
||||
object, n_addresses, addresses, data);
|
||||
}
|
||||
|
||||
OSStatus ca_device_listener(AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
return ca_property_listener(kAudioDevicePropertyDeviceHasChanged,
|
||||
object, n_addresses, addresses, data);
|
||||
}
|
||||
|
||||
OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid) {
|
||||
*pid = getpid();
|
||||
OSStatus err = CA_SET(device, kAudioDevicePropertyHogMode, pid);
|
||||
if (err != noErr)
|
||||
*pid = -1;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid) {
|
||||
if (*pid == getpid()) {
|
||||
*pid = -1;
|
||||
return CA_SET(device, kAudioDevicePropertyHogMode, &pid);
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_change_mixing(struct ao *ao, AudioDeviceID device,
|
||||
uint32_t val, bool *changed) {
|
||||
*changed = false;
|
||||
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertySupportsMixing,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
if (AudioObjectHasProperty(device, &p_addr)) {
|
||||
OSStatus err;
|
||||
Boolean writeable = 0;
|
||||
err = CA_SETTABLE(device, kAudioDevicePropertySupportsMixing,
|
||||
&writeable);
|
||||
|
||||
if (!CHECK_CA_WARN("can't tell if mixing property is settable")) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!writeable)
|
||||
return noErr;
|
||||
|
||||
err = CA_SET(device, kAudioDevicePropertySupportsMixing, &val);
|
||||
if (err != noErr)
|
||||
return err;
|
||||
|
||||
if (!CHECK_CA_WARN("can't set mix mode")) {
|
||||
return err;
|
||||
}
|
||||
|
||||
*changed = true;
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSStatus ca_disable_mixing(struct ao *ao, AudioDeviceID device, bool *changed) {
|
||||
return ca_change_mixing(ao, device, 0, changed);
|
||||
}
|
||||
|
||||
OSStatus ca_enable_mixing(struct ao *ao, AudioDeviceID device, bool changed) {
|
||||
if (changed) {
|
||||
bool dont_care = false;
|
||||
return ca_change_mixing(ao, device, 1, &dont_care);
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_change_device_listening(AudioDeviceID device,
|
||||
void *flag, bool enabled)
|
||||
{
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertyDeviceHasChanged,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
if (enabled) {
|
||||
return AudioObjectAddPropertyListener(
|
||||
device, &p_addr, ca_device_listener, flag);
|
||||
} else {
|
||||
return AudioObjectRemovePropertyListener(
|
||||
device, &p_addr, ca_device_listener, flag);
|
||||
}
|
||||
}
|
||||
|
||||
OSStatus ca_enable_device_listener(AudioDeviceID device, void *flag) {
|
||||
return ca_change_device_listening(device, flag, true);
|
||||
}
|
||||
|
||||
OSStatus ca_disable_device_listener(AudioDeviceID device, void *flag) {
|
||||
return ca_change_device_listening(device, flag, false);
|
||||
}
|
||||
|
||||
bool ca_change_format(struct ao *ao, AudioStreamID stream,
|
||||
AudioStreamBasicDescription change_format)
|
||||
{
|
||||
OSStatus err = noErr;
|
||||
AudioObjectPropertyAddress p_addr;
|
||||
volatile int stream_format_changed = 0;
|
||||
|
||||
ca_print_asbd(ao, "setting stream format:", &change_format);
|
||||
|
||||
/* Install the callback. */
|
||||
p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioStreamPropertyPhysicalFormat,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
err = AudioObjectAddPropertyListener(stream, &p_addr, ca_stream_listener,
|
||||
(void *)&stream_format_changed);
|
||||
if (!CHECK_CA_WARN("can't add property listener during format change")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Change the format. */
|
||||
err = CA_SET(stream, kAudioStreamPropertyPhysicalFormat, &change_format);
|
||||
if (!CHECK_CA_WARN("error changing physical format")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The AudioStreamSetProperty is not only asynchronious,
|
||||
* it is also not Atomic, in its behaviour.
|
||||
* Therefore we check 5 times before we really give up. */
|
||||
bool format_set = false;
|
||||
for (int i = 0; !format_set && i < 5; i++) {
|
||||
for (int j = 0; !stream_format_changed && j < 50; j++)
|
||||
mp_sleep_us(10000);
|
||||
|
||||
if (stream_format_changed) {
|
||||
stream_format_changed = 0;
|
||||
} else {
|
||||
MP_VERBOSE(ao, "reached timeout\n");
|
||||
}
|
||||
|
||||
AudioStreamBasicDescription actual_format;
|
||||
err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &actual_format);
|
||||
|
||||
ca_print_asbd(ao, "actual format in use:", &actual_format);
|
||||
if (actual_format.mSampleRate == change_format.mSampleRate &&
|
||||
actual_format.mFormatID == change_format.mFormatID &&
|
||||
actual_format.mFramesPerPacket == change_format.mFramesPerPacket) {
|
||||
format_set = true;
|
||||
}
|
||||
}
|
||||
|
||||
err = AudioObjectRemovePropertyListener(stream, &p_addr, ca_stream_listener,
|
||||
(void *)&stream_format_changed);
|
||||
|
||||
if (!CHECK_CA_WARN("can't remove property listener")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return format_set;
|
||||
}
|
||||
|
||||
static const int speaker_map[][2] = {
|
||||
{ kAudioChannelLabel_Left, MP_SPEAKER_ID_FL },
|
||||
{ kAudioChannelLabel_Right, MP_SPEAKER_ID_FR },
|
||||
{ kAudioChannelLabel_Center, MP_SPEAKER_ID_FC },
|
||||
{ kAudioChannelLabel_LFEScreen, MP_SPEAKER_ID_LFE },
|
||||
{ kAudioChannelLabel_LeftSurround, MP_SPEAKER_ID_BL },
|
||||
{ kAudioChannelLabel_RightSurround, MP_SPEAKER_ID_BR },
|
||||
{ kAudioChannelLabel_LeftCenter, MP_SPEAKER_ID_FLC },
|
||||
{ kAudioChannelLabel_RightCenter, MP_SPEAKER_ID_FRC },
|
||||
{ kAudioChannelLabel_CenterSurround, MP_SPEAKER_ID_BC },
|
||||
{ kAudioChannelLabel_LeftSurroundDirect, MP_SPEAKER_ID_SL },
|
||||
{ kAudioChannelLabel_RightSurroundDirect, MP_SPEAKER_ID_SR },
|
||||
{ kAudioChannelLabel_TopCenterSurround, MP_SPEAKER_ID_TC },
|
||||
{ kAudioChannelLabel_VerticalHeightLeft, MP_SPEAKER_ID_TFL },
|
||||
{ kAudioChannelLabel_VerticalHeightCenter, MP_SPEAKER_ID_TFC },
|
||||
{ kAudioChannelLabel_VerticalHeightRight, MP_SPEAKER_ID_TFR },
|
||||
{ kAudioChannelLabel_TopBackLeft, MP_SPEAKER_ID_TBL },
|
||||
{ kAudioChannelLabel_TopBackCenter, MP_SPEAKER_ID_TBC },
|
||||
{ kAudioChannelLabel_TopBackRight, MP_SPEAKER_ID_TBR },
|
||||
|
||||
// unofficial extensions
|
||||
{ kAudioChannelLabel_RearSurroundLeft, MP_SPEAKER_ID_SDL },
|
||||
{ kAudioChannelLabel_RearSurroundRight, MP_SPEAKER_ID_SDR },
|
||||
{ kAudioChannelLabel_LeftWide, MP_SPEAKER_ID_WL },
|
||||
{ kAudioChannelLabel_RightWide, MP_SPEAKER_ID_WR },
|
||||
{ kAudioChannelLabel_LFE2, MP_SPEAKER_ID_LFE2 },
|
||||
|
||||
{ kAudioChannelLabel_HeadphonesLeft, MP_SPEAKER_ID_DL },
|
||||
{ kAudioChannelLabel_HeadphonesRight, MP_SPEAKER_ID_DR },
|
||||
|
||||
{ kAudioChannelLabel_Unknown, -1 },
|
||||
};
|
||||
|
||||
static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
{
|
||||
for (int i = 0; speaker_map[i][1] >= 0; i++)
|
||||
if (speaker_map[i][0] == label)
|
||||
return speaker_map[i][1];
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ca_log_layout(struct ao *ao, AudioChannelLayout *layout)
|
||||
{
|
||||
if (!mp_msg_test(ao->log, MSGL_V))
|
||||
return;
|
||||
|
||||
AudioChannelDescription *descs = layout->mChannelDescriptions;
|
||||
|
||||
MP_VERBOSE(ao, "layout: tag: <%d>, bitmap: <%d>, "
|
||||
"descriptions <%d>\n",
|
||||
layout->mChannelLayoutTag,
|
||||
layout->mChannelBitmap,
|
||||
layout->mNumberChannelDescriptions);
|
||||
|
||||
for (int i = 0; i < layout->mNumberChannelDescriptions; i++) {
|
||||
AudioChannelDescription d = descs[i];
|
||||
MP_VERBOSE(ao, " - description %d: label <%d, %d>, flags: <%u>, "
|
||||
"coords: <%f, %f, %f>\n", i,
|
||||
d.mChannelLabel,
|
||||
ca_label_to_mp_speaker_id(d.mChannelLabel),
|
||||
d.mChannelFlags,
|
||||
d.mCoordinates[0],
|
||||
d.mCoordinates[1],
|
||||
d.mCoordinates[2]);
|
||||
}
|
||||
}
|
||||
|
||||
bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
struct mp_chmap *chmap)
|
||||
{
|
||||
AudioChannelLayoutTag tag = layout->mChannelLayoutTag;
|
||||
uint32_t layout_size = sizeof(layout);
|
||||
OSStatus err;
|
||||
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
|
||||
err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(uint32_t),
|
||||
&layout->mChannelBitmap,
|
||||
&layout_size,
|
||||
layout);
|
||||
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions");
|
||||
} else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
|
||||
err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag),
|
||||
&layout->mChannelLayoutTag,
|
||||
&layout_size,
|
||||
layout);
|
||||
CHECK_CA_ERROR("failed to convert channel tag to descriptions");
|
||||
}
|
||||
|
||||
ca_log_layout(ao, layout);
|
||||
|
||||
// If the channel layout uses channel descriptions, from my
|
||||
// experiments there are there three possibile cases:
|
||||
// * The description has a label kAudioChannelLabel_Unknown:
|
||||
// Can't do anything about this (looks like non surround
|
||||
// layouts are like this).
|
||||
// * The description uses positional information: this in
|
||||
// theory could be used but one would have to map spatial
|
||||
// positions to labels which is not really feasible.
|
||||
// * The description has a well known label which can be mapped
|
||||
// to the waveextensible definition: this is the kind of
|
||||
// descriptions we process here.
|
||||
|
||||
for (int n = 0; n < layout->mNumberChannelDescriptions; n++) {
|
||||
AudioChannelLabel label = layout->mChannelDescriptions[n].mChannelLabel;
|
||||
uint8_t speaker = ca_label_to_mp_speaker_id(label);
|
||||
if (label == kAudioChannelLabel_Unknown)
|
||||
continue;
|
||||
if (speaker < 0) {
|
||||
MP_VERBOSE(ao, "channel label=%d unusable to build channel "
|
||||
"bitmap, skipping layout\n", label);
|
||||
} else {
|
||||
chmap->speaker[n] = speaker;
|
||||
chmap->num = n + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return chmap->num > 0;
|
||||
coreaudio_error:
|
||||
ca_log_layout(ao, layout);
|
||||
return false;
|
||||
}
|
||||
@@ -46,11 +46,38 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message);
|
||||
if (err != noErr) goto label; \
|
||||
} while (0)
|
||||
|
||||
void ca_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,
|
||||
const AudioStreamBasicDescription *asbd);
|
||||
|
||||
bool ca_format_is_digital(AudioStreamBasicDescription asbd);
|
||||
bool ca_stream_supports_digital(struct ao *ao, AudioStreamID stream);
|
||||
bool ca_device_supports_digital(struct ao *ao, AudioDeviceID device);
|
||||
|
||||
OSStatus ca_property_listener(AudioObjectPropertySelector selector,
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data);
|
||||
|
||||
OSStatus ca_stream_listener(AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data);
|
||||
|
||||
OSStatus ca_device_listener(AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data);
|
||||
|
||||
OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid);
|
||||
OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid);
|
||||
OSStatus ca_disable_mixing(struct ao *ao, AudioDeviceID device, bool *changed);
|
||||
OSStatus ca_enable_mixing(struct ao *ao, AudioDeviceID device, bool changed);
|
||||
|
||||
OSStatus ca_enable_device_listener(AudioDeviceID device, void *flag);
|
||||
OSStatus ca_disable_device_listener(AudioDeviceID device, void *flag);
|
||||
|
||||
bool ca_change_format(struct ao *ao, AudioStreamID stream,
|
||||
AudioStreamBasicDescription change_format);
|
||||
|
||||
bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
struct mp_chmap *chmap);
|
||||
|
||||
#endif /* MPV_COREAUDIO_UTILS_H */
|
||||
@@ -403,7 +403,7 @@ static int init(struct ao *ao)
|
||||
ao->samplerate = rate;
|
||||
ao->format = format;
|
||||
ao->bps = ao->channels.num * rate * af_fmt2bps(format);
|
||||
int buffersize = ao->bps * 0.100; // space for 100ms
|
||||
int buffersize = ao->bps; // space for 1 sec
|
||||
MP_VERBOSE(ao, "Samplerate:%iHz Channels:%i Format:%s\n", rate,
|
||||
ao->channels.num, af_fmt_to_str(format));
|
||||
MP_VERBOSE(ao, "Buffersize:%d bytes (%d msec)\n",
|
||||
|
||||
+10
-12
@@ -71,7 +71,7 @@ static void select_format(struct ao *ao, AVCodec *codec)
|
||||
{
|
||||
int fmt = af_from_avformat(*sampleformat);
|
||||
if (!fmt) {
|
||||
MP_WARN(ao, "unsupported lavc format %s\n",
|
||||
MP_WARN(ao, "unsupported lavc format %s",
|
||||
av_get_sample_fmt_name(*sampleformat));
|
||||
continue;
|
||||
}
|
||||
@@ -114,11 +114,12 @@ static int init(struct ao *ao)
|
||||
|
||||
codec = encode_lavc_get_codec(ao->encode_lavc_ctx, ac->stream);
|
||||
|
||||
// TODO: Remove this redundancy with encode_lavc_alloc_stream also
|
||||
// setting the time base.
|
||||
// Using codec->time_bvase is deprecated, but needed for older lavf.
|
||||
ac->stream->time_base.num = 1;
|
||||
ac->stream->time_base.den = ao->samplerate;
|
||||
// ac->stream->time_base.num = 1;
|
||||
// ac->stream->time_base.den = ao->samplerate;
|
||||
// doing this breaks mpeg2ts in ffmpeg
|
||||
// which doesn't properly force the time base to be 90000
|
||||
// furthermore, ffmpeg.c doesn't do this either and works
|
||||
|
||||
ac->stream->codec->time_base.num = 1;
|
||||
ac->stream->codec->time_base.den = ao->samplerate;
|
||||
|
||||
@@ -341,7 +342,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
}
|
||||
|
||||
double pts = ectx->last_audio_in_pts;
|
||||
pts += ectx->samples_since_last_pts / (double)ao->samplerate;
|
||||
pts += ectx->samples_since_last_pts / ao->samplerate;
|
||||
ectx->samples_since_last_pts += samples;
|
||||
|
||||
size_t num_planes = af_fmt_is_planar(ao->format) ? ao->channels.num : 1;
|
||||
|
||||
@@ -449,10 +451,6 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
}
|
||||
|
||||
talloc_free(tempdata);
|
||||
|
||||
int taken = FFMIN(bufpos, orig_samples);
|
||||
ectx->samples_since_last_pts += taken;
|
||||
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
if (flags & AOPLAY_FINAL_CHUNK) {
|
||||
@@ -465,7 +463,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
}
|
||||
}
|
||||
|
||||
return taken;
|
||||
return FFMIN(bufpos, orig_samples);
|
||||
}
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
|
||||
+2
-1
@@ -90,7 +90,7 @@ static int init(struct ao *ao)
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
mp_chmap_from_channels(&ao->channels, 2);
|
||||
return -1;
|
||||
|
||||
priv->latency = priv->latency_sec * ao->samplerate;
|
||||
|
||||
@@ -210,6 +210,7 @@ const struct ao_driver audio_out_null = {
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.bufferlen = 0.2,
|
||||
.latency_sec = 0.5,
|
||||
.outburst = 256,
|
||||
.speed = 1,
|
||||
},
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "options/options.h"
|
||||
|
||||
+26
-42
@@ -53,6 +53,7 @@ struct priv {
|
||||
// temporary during control()
|
||||
struct pa_sink_input_info pi;
|
||||
|
||||
bool broken_pause;
|
||||
int retval;
|
||||
|
||||
// for wakeup handling
|
||||
@@ -63,7 +64,6 @@ struct priv {
|
||||
char *cfg_host;
|
||||
char *cfg_sink;
|
||||
int cfg_buffer;
|
||||
int cfg_latency_hacks;
|
||||
};
|
||||
|
||||
#define GENERIC_ERR_MSG(str) \
|
||||
@@ -113,7 +113,7 @@ static void stream_request_cb(pa_stream *s, size_t length, void *userdata)
|
||||
pa_threaded_mainloop_signal(priv->mainloop, 0);
|
||||
}
|
||||
|
||||
static int wait_audio(struct ao *ao, pthread_mutex_t *lock)
|
||||
static int wait(struct ao *ao, pthread_mutex_t *lock)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
// We don't use this mutex, because pulse like to call stream_request_cb
|
||||
@@ -280,12 +280,26 @@ static int init(struct ao *ao)
|
||||
struct priv *priv = ao->priv;
|
||||
char *host = priv->cfg_host && priv->cfg_host[0] ? priv->cfg_host : NULL;
|
||||
char *sink = priv->cfg_sink && priv->cfg_sink[0] ? priv->cfg_sink : NULL;
|
||||
const char *version = pa_get_library_version();
|
||||
|
||||
pthread_mutex_init(&priv->wakeup_lock, NULL);
|
||||
pthread_cond_init(&priv->wakeup, NULL);
|
||||
|
||||
ao->per_application_mixer = true;
|
||||
|
||||
priv->broken_pause = false;
|
||||
/* not sure which versions are affected, assume 0.9.11* to 0.9.14*
|
||||
* known bad: 0.9.14, 0.9.13
|
||||
* known good: 0.9.9, 0.9.10, 0.9.15
|
||||
* To test: pause, wait ca. 5 seconds, framestep and see if MPlayer
|
||||
* hangs somewhen. */
|
||||
if (strncmp(version, "0.9.1", 5) == 0 && version[5] >= '1'
|
||||
&& version[5] <= '4') {
|
||||
MP_WARN(ao, "working around probably broken pause functionality,\n"
|
||||
" see http://www.pulseaudio.org/ticket/440\n");
|
||||
priv->broken_pause = true;
|
||||
}
|
||||
|
||||
if (!(priv->mainloop = pa_threaded_mainloop_new())) {
|
||||
MP_ERR(ao, "Failed to allocate main loop\n");
|
||||
goto fail;
|
||||
@@ -343,8 +357,6 @@ 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,
|
||||
PULSE_CLIENT_NAME);
|
||||
|
||||
if (!(priv->stream = pa_stream_new_with_proplist(priv->context,
|
||||
"audio stream", &ss,
|
||||
@@ -366,13 +378,8 @@ static int init(struct ao *ao)
|
||||
.minreq = -1,
|
||||
.fragsize = -1,
|
||||
};
|
||||
|
||||
int flags = PA_STREAM_NOT_MONOTONIC;
|
||||
if (!priv->cfg_latency_hacks)
|
||||
flags |= PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE;
|
||||
|
||||
if (pa_stream_connect_playback(priv->stream, sink, &bufattr,
|
||||
flags, NULL, NULL) < 0)
|
||||
PA_STREAM_NOT_MONOTONIC, NULL, NULL) < 0)
|
||||
goto unlock_and_fail;
|
||||
|
||||
/* Wait until the stream is ready */
|
||||
@@ -456,6 +463,12 @@ static void pause(struct ao *ao)
|
||||
// Resume the audio stream by uncorking it on the server
|
||||
static void resume(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
/* Without this, certain versions will cause an infinite hang because
|
||||
* pa_stream_writable_size returns 0 always.
|
||||
* Note that this workaround causes A-V desync after pause. */
|
||||
if (priv->broken_pause)
|
||||
reset(ao);
|
||||
cork(ao, false);
|
||||
}
|
||||
|
||||
@@ -469,7 +482,8 @@ static int get_space(struct ao *ao)
|
||||
return space / ao->sstride;
|
||||
}
|
||||
|
||||
static float get_delay_hackfixed(struct ao *ao)
|
||||
// Return the current latency in seconds
|
||||
static float get_delay(struct ao *ao)
|
||||
{
|
||||
/* This code basically does what pa_stream_get_latency() _should_
|
||||
* do, but doesn't due to multiple known bugs in PulseAudio (at
|
||||
@@ -524,34 +538,6 @@ static float get_delay_hackfixed(struct ao *ao)
|
||||
return latency / 1e6;
|
||||
}
|
||||
|
||||
static float get_delay_pulse(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
pa_usec_t latency = (pa_usec_t) -1;
|
||||
pa_threaded_mainloop_lock(priv->mainloop);
|
||||
while (pa_stream_get_latency(priv->stream, &latency, NULL) < 0) {
|
||||
if (pa_context_errno(priv->context) != PA_ERR_NODATA) {
|
||||
GENERIC_ERR_MSG("pa_stream_get_latency() failed");
|
||||
break;
|
||||
}
|
||||
/* Wait until latency data is available again */
|
||||
pa_threaded_mainloop_wait(priv->mainloop);
|
||||
}
|
||||
pa_threaded_mainloop_unlock(priv->mainloop);
|
||||
return latency == (pa_usec_t) -1 ? 0 : latency / 1000000.0;
|
||||
}
|
||||
|
||||
// Return the current latency in seconds
|
||||
static float get_delay(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
if (priv->cfg_latency_hacks) {
|
||||
return get_delay_hackfixed(ao);
|
||||
} else {
|
||||
return get_delay_pulse(ao);
|
||||
}
|
||||
}
|
||||
|
||||
/* A callback function that is called when the
|
||||
* pa_context_get_sink_input_info() operation completes. Saves the
|
||||
* volume field of the specified structure to the global variable volume.
|
||||
@@ -676,18 +662,16 @@ const struct ao_driver audio_out_pulse = {
|
||||
.pause = pause,
|
||||
.resume = resume,
|
||||
.drain = drain,
|
||||
.wait = wait_audio,
|
||||
.wait = wait,
|
||||
.wakeup = wakeup,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.cfg_buffer = 250,
|
||||
.cfg_latency_hacks = 1,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("host", cfg_host, 0),
|
||||
OPT_STRING("sink", cfg_sink, 0),
|
||||
OPT_CHOICE_OR_INT("buffer", cfg_buffer, 0, 1, 2000, ({"native", -1})),
|
||||
OPT_FLAG("latency-hacks", cfg_latency_hacks, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -134,8 +134,6 @@ struct ao_driver {
|
||||
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.
|
||||
@@ -160,6 +158,7 @@ struct ao_driver {
|
||||
// These functions can be called by AOs.
|
||||
|
||||
int ao_play_silence(struct ao *ao, int samples);
|
||||
void ao_wait_drain(struct ao *ao);
|
||||
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us);
|
||||
struct pollfd;
|
||||
int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds,
|
||||
|
||||
@@ -193,22 +193,6 @@ static void resume(struct ao *ao)
|
||||
ao->driver->resume(ao);
|
||||
}
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
{
|
||||
struct ao_pull_state *p = ao->api_priv;
|
||||
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);
|
||||
@@ -228,12 +212,10 @@ const struct ao_driver ao_api_pull = {
|
||||
.init = init,
|
||||
.control = control,
|
||||
.uninit = uninit,
|
||||
.drain = drain,
|
||||
.reset = reset,
|
||||
.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),
|
||||
|
||||
+10
-52
@@ -54,7 +54,6 @@ struct ao_push_state {
|
||||
bool drain;
|
||||
bool buffers_full;
|
||||
bool avoid_ao_wait;
|
||||
bool still_playing;
|
||||
bool need_wakeup;
|
||||
bool requested_data;
|
||||
bool paused;
|
||||
@@ -88,13 +87,15 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return r;
|
||||
}
|
||||
|
||||
static double unlocked_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 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");
|
||||
@@ -105,15 +106,6 @@ static double unlocked_get_delay(struct ao *ao)
|
||||
return delay;
|
||||
}
|
||||
|
||||
static float get_delay(struct ao *ao)
|
||||
{
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
pthread_mutex_lock(&p->lock);
|
||||
float delay = unlocked_get_delay(ao);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
return delay;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
@@ -122,7 +114,6 @@ static void reset(struct ao *ao)
|
||||
ao->driver->reset(ao);
|
||||
mp_audio_buffer_clear(p->buffer);
|
||||
p->paused = false;
|
||||
p->still_playing = false;
|
||||
wakeup_playthread(ao);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
}
|
||||
@@ -155,10 +146,6 @@ static void drain(struct ao *ao)
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
|
||||
pthread_mutex_lock(&p->lock);
|
||||
if (p->paused) {
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
return;
|
||||
}
|
||||
p->final_chunk = true;
|
||||
p->drain = true;
|
||||
wakeup_playthread(ao);
|
||||
@@ -167,8 +154,7 @@ static void drain(struct ao *ao)
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
if (!ao->driver->drain)
|
||||
mp_sleep_us(get_delay(ao) * 1000000);
|
||||
reset(ao);
|
||||
ao_wait_drain(ao);
|
||||
}
|
||||
|
||||
static int unlocked_get_space(struct ao *ao)
|
||||
@@ -199,15 +185,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;
|
||||
@@ -233,7 +210,6 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
p->expected_end_time = 0;
|
||||
p->final_chunk = is_final;
|
||||
p->paused = false;
|
||||
p->still_playing |= write_samples > 0;
|
||||
|
||||
// If we don't have new data, the decoder thread basically promises it
|
||||
// will send new data as soon as it's available.
|
||||
@@ -285,7 +261,6 @@ static void ao_play_data(struct ao *ao)
|
||||
// any new data (due to rounding to period boundaries).
|
||||
p->buffers_full = max >= space && r <= 0;
|
||||
p->avoid_ao_wait = (max == 0 && space > 0) || p->paused || stuck;
|
||||
p->still_playing |= r > 0;
|
||||
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);
|
||||
}
|
||||
@@ -339,23 +314,8 @@ 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) {
|
||||
timeout = unlocked_get_delay(ao);
|
||||
if (timeout < AO_EOF_DELAY)
|
||||
p->still_playing = false;
|
||||
}
|
||||
|
||||
if (!p->requested_data || (was_playing && !p->still_playing))
|
||||
mp_input_wakeup(ao->input_ctx);
|
||||
|
||||
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 {
|
||||
if (!ao->driver->wait || ao->driver->wait(ao, &p->lock) < 0) {
|
||||
// Fallback to guessing.
|
||||
@@ -405,12 +365,11 @@ static int init(struct ao *ao)
|
||||
mp_audio_buffer_reinit_fmt(p->buffer, ao->format,
|
||||
&ao->channels, ao->samplerate);
|
||||
mp_audio_buffer_preallocate_min(p->buffer, ao->buffer);
|
||||
if (pthread_create(&p->thread, NULL, playthread, ao))
|
||||
goto err;
|
||||
if (pthread_create(&p->thread, NULL, playthread, ao)) {
|
||||
ao->driver->uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
err:
|
||||
ao->driver->uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const struct ao_driver ao_api_push = {
|
||||
@@ -424,7 +383,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),
|
||||
};
|
||||
|
||||
|
||||
+4
-5
@@ -17,8 +17,8 @@
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "talloc.h"
|
||||
|
||||
#include "common/common.h"
|
||||
#include "misc/ctype.h"
|
||||
#include "bstr/bstr.h"
|
||||
|
||||
int bstrcmp(struct bstr str1, struct bstr str2)
|
||||
@@ -105,7 +104,7 @@ int bstr_find(struct bstr haystack, struct bstr needle)
|
||||
|
||||
struct bstr bstr_lstrip(struct bstr str)
|
||||
{
|
||||
while (str.len && mp_isspace(*str.start)) {
|
||||
while (str.len && isspace(*str.start)) {
|
||||
str.start++;
|
||||
str.len--;
|
||||
}
|
||||
@@ -115,7 +114,7 @@ struct bstr bstr_lstrip(struct bstr str)
|
||||
struct bstr bstr_strip(struct bstr str)
|
||||
{
|
||||
str = bstr_lstrip(str);
|
||||
while (str.len && mp_isspace(str.start[str.len - 1]))
|
||||
while (str.len && isspace(str.start[str.len - 1]))
|
||||
str.len--;
|
||||
return str;
|
||||
}
|
||||
@@ -243,7 +242,7 @@ bool bstr_eatstart(struct bstr *s, struct bstr prefix)
|
||||
void bstr_lower(struct bstr str)
|
||||
{
|
||||
for (int i = 0; i < str.len; i++)
|
||||
str.start[i] = mp_tolower(str.start[i]);
|
||||
str.start[i] = tolower(str.start[i]);
|
||||
}
|
||||
|
||||
int bstr_sscanf(struct bstr str, const char *format, ...)
|
||||
|
||||
+1
-45
@@ -19,9 +19,6 @@
|
||||
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/log.h>
|
||||
#include <libavutil/dict.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/error.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include "common/common.h"
|
||||
@@ -67,6 +64,7 @@ void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st)
|
||||
avctx->channels = st->channels;
|
||||
avctx->block_align = st->block_align;
|
||||
avctx->channel_layout = st->channel_layout;
|
||||
avctx->audio_service_type = st->audio_service_type;
|
||||
avctx->bits_per_coded_sample = st->bits_per_coded_sample;
|
||||
}
|
||||
|
||||
@@ -183,45 +181,3 @@ const char *mp_codec_from_av_codec_id(int codec_id)
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
// kv is in the format as by OPT_KEYVALUELIST(): kv[0]=key0, kv[1]=val0, ...
|
||||
// Copy them to the dict.
|
||||
void mp_set_avdict(AVDictionary **dict, char **kv)
|
||||
{
|
||||
for (int n = 0; kv && kv[n * 2]; n++)
|
||||
av_dict_set(dict, kv[n * 2 + 0], kv[n * 2 + 1], 0);
|
||||
}
|
||||
|
||||
// For use with libav* APIs that take AVDictionaries of options.
|
||||
// Print options remaining in the dict as unset.
|
||||
void mp_avdict_print_unset(struct mp_log *log, int msgl, AVDictionary *dict)
|
||||
{
|
||||
AVDictionaryEntry *t = NULL;
|
||||
while ((t = av_dict_get(dict, "", t, AV_DICT_IGNORE_SUFFIX)))
|
||||
mp_msg(log, msgl, "Could not set AVOption %s='%s'\n", t->key, t->value);
|
||||
}
|
||||
|
||||
// kv is in the format as by OPT_KEYVALUELIST(): kv[0]=key0, kv[1]=val0, ...
|
||||
// Set these options on given avobj (using av_opt_set..., meaning avobj must
|
||||
// point to a struct that has AVClass as first member).
|
||||
// Options which fail to set (error or not found) are printed to log.
|
||||
// Returns: >=0 success, <0 failed to set an option
|
||||
int mp_set_avopts(struct mp_log *log, void *avobj, char **kv)
|
||||
{
|
||||
int success = 0;
|
||||
for (int n = 0; kv && kv[n * 2]; n++) {
|
||||
char *k = kv[n * 2 + 0];
|
||||
char *v = kv[n * 2 + 1];
|
||||
int r = av_opt_set(avobj, k, v, AV_OPT_SEARCH_CHILDREN);
|
||||
if (r == AVERROR_OPTION_NOT_FOUND) {
|
||||
mp_err(log, "AVOption '%s' not found.\n", k);
|
||||
success = -1;
|
||||
} else if (r < 0) {
|
||||
char errstr[80];
|
||||
av_strerror(r, errstr, sizeof(errstr));
|
||||
mp_err(log, "Could not set AVOption %s='%s' (%s)\n", k, v, errstr);
|
||||
success = -1;
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -26,8 +26,6 @@
|
||||
|
||||
struct mp_decoder_list;
|
||||
struct demux_packet;
|
||||
struct AVDictionary;
|
||||
struct mp_log;
|
||||
|
||||
int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size);
|
||||
void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st);
|
||||
@@ -38,8 +36,5 @@ void mp_set_avcodec_threads(AVCodecContext *avctx, int threads);
|
||||
void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type);
|
||||
int mp_codec_to_av_codec_id(const char *codec);
|
||||
const char *mp_codec_from_av_codec_id(int codec_id);
|
||||
void mp_set_avdict(struct AVDictionary **dict, char **kv);
|
||||
void mp_avdict_print_unset(struct mp_log *log, int msgl, struct AVDictionary *d);
|
||||
int mp_set_avopts(struct mp_log *log, void *avobj, char **kv);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* AVOption parsing helper
|
||||
* Copyright (C) 2008 Michael Niedermayer
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
#include "av_opts.h"
|
||||
|
||||
int parse_avopts(void *v, char *str){
|
||||
char *start;
|
||||
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
start= str= strdup(str);
|
||||
|
||||
while(str && *str){
|
||||
char *next_opt, *arg;
|
||||
|
||||
next_opt= strchr(str, ',');
|
||||
if(next_opt) *next_opt++= 0;
|
||||
|
||||
arg = strchr(str, '=');
|
||||
if(arg) *arg++= 0;
|
||||
|
||||
if (av_opt_set(v, str, arg, AV_OPT_SEARCH_CHILDREN) < 0) {
|
||||
free(start);
|
||||
return -1;
|
||||
}
|
||||
str= next_opt;
|
||||
}
|
||||
|
||||
free(start);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* AVOption parsing helper
|
||||
* Copyright (C) 2008 Michael Niedermayer
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_AV_OPTS_H
|
||||
#define MPLAYER_AV_OPTS_H
|
||||
|
||||
/**
|
||||
* Parses str and sets AVOptions in v accordingly.
|
||||
*/
|
||||
int parse_avopts(void *v, char *str);
|
||||
|
||||
#endif /* MPLAYER_AV_OPTS_H */
|
||||
@@ -590,8 +590,11 @@ AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
}
|
||||
avcodec_get_context_defaults3(stream->codec, ctx->vc);
|
||||
|
||||
// Using codec->time_base is deprecated, but needed for older lavf.
|
||||
stream->time_base = ctx->timebase;
|
||||
// stream->time_base = ctx->timebase;
|
||||
// doing this breaks mpeg2ts in ffmpeg
|
||||
// which doesn't properly force the time base to be 90000
|
||||
// furthermore, ffmpeg.c doesn't do this either and works
|
||||
|
||||
stream->codec->time_base = ctx->timebase;
|
||||
|
||||
ctx->voptions = NULL;
|
||||
@@ -623,8 +626,6 @@ AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
}
|
||||
avcodec_get_context_defaults3(stream->codec, ctx->ac);
|
||||
|
||||
// Using codec->time_base is deprecated, but needed for older lavf.
|
||||
stream->time_base = ctx->timebase;
|
||||
stream->codec->time_base = ctx->timebase;
|
||||
|
||||
ctx->aoptions = NULL;
|
||||
|
||||
+1
-5
@@ -429,7 +429,7 @@ void mp_msg_update_msglevels(struct mpv_global *global)
|
||||
root->show_time = opts->msg_time;
|
||||
if (root->use_terminal) {
|
||||
root->color = opts->msg_color && isatty(fileno(stdout));
|
||||
root->termosd = isatty(fileno(stderr));
|
||||
root->termosd = !opts->slave_mode && isatty(fileno(stderr));
|
||||
}
|
||||
|
||||
talloc_free(root->msglevels);
|
||||
@@ -469,10 +469,6 @@ struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global,
|
||||
{
|
||||
struct mp_log_root *root = global->log->root;
|
||||
|
||||
#if !HAVE_ATOMICS
|
||||
return NULL;
|
||||
#endif
|
||||
|
||||
pthread_mutex_lock(&mp_msg_lock);
|
||||
|
||||
struct mp_log_buffer *buffer = talloc_ptrtype(NULL, buffer);
|
||||
|
||||
+3
-9
@@ -215,15 +215,6 @@ void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl)
|
||||
}
|
||||
}
|
||||
|
||||
void playlist_append_entries(struct playlist *pl, struct playlist *source_pl)
|
||||
{
|
||||
while (source_pl->first) {
|
||||
struct playlist_entry *e = source_pl->first;
|
||||
playlist_unlink(source_pl, e);
|
||||
playlist_add(pl, e);
|
||||
}
|
||||
}
|
||||
|
||||
// Return number of entries between list start and e.
|
||||
// Return -1 if e is not on the list, or if e is NULL.
|
||||
int playlist_entry_to_index(struct playlist *pl, struct playlist_entry *e)
|
||||
@@ -286,6 +277,9 @@ struct playlist *playlist_parse_file(const char *file, struct mpv_global *global
|
||||
if (ret && !ret->first)
|
||||
mp_warn(log, "Warning: empty playlist\n");
|
||||
|
||||
if (ret)
|
||||
playlist_add_base_path(ret, mp_dirname(file));
|
||||
|
||||
talloc_free(log);
|
||||
return ret;
|
||||
}
|
||||
@@ -74,7 +74,6 @@ void playlist_shuffle(struct playlist *pl);
|
||||
struct playlist_entry *playlist_get_next(struct playlist *pl, int direction);
|
||||
void playlist_add_base_path(struct playlist *pl, bstr base_path);
|
||||
void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl);
|
||||
void playlist_append_entries(struct playlist *pl, struct playlist *source_pl);
|
||||
|
||||
int playlist_entry_to_index(struct playlist *pl, struct playlist_entry *e);
|
||||
int playlist_entry_count(struct playlist *pl);
|
||||
|
||||
@@ -61,25 +61,6 @@ void mp_tags_clear(struct mp_tags *tags)
|
||||
talloc_free_children(tags);
|
||||
}
|
||||
|
||||
struct mp_tags *mp_tags_dup(void *tparent, struct mp_tags *tags)
|
||||
{
|
||||
struct mp_tags *new = talloc_zero(tparent, struct mp_tags);
|
||||
MP_RESIZE_ARRAY(new, new->keys, tags->num_keys);
|
||||
MP_RESIZE_ARRAY(new, new->values, tags->num_keys);
|
||||
new->num_keys = tags->num_keys;
|
||||
for (int n = 0; n < tags->num_keys; n++) {
|
||||
new->keys[n] = talloc_strdup(new, tags->keys[n]);
|
||||
new->values[n] = talloc_strdup(new, tags->values[n]);
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
void mp_tags_merge(struct mp_tags *tags, struct mp_tags *src)
|
||||
{
|
||||
for (int n = 0; n < src->num_keys; n++)
|
||||
mp_tags_set_str(tags, src->keys[n], src->values[n]);
|
||||
}
|
||||
|
||||
void mp_tags_copy_from_av_dictionary(struct mp_tags *tags,
|
||||
struct AVDictionary *av_dict)
|
||||
{
|
||||
|
||||
@@ -14,8 +14,6 @@ void mp_tags_set_bstr(struct mp_tags *tags, bstr key, bstr value);
|
||||
char *mp_tags_get_str(struct mp_tags *tags, const char *key);
|
||||
char *mp_tags_get_bstr(struct mp_tags *tags, bstr key);
|
||||
void mp_tags_clear(struct mp_tags *tags);
|
||||
struct mp_tags *mp_tags_dup(void *tparent, struct mp_tags *tags);
|
||||
void mp_tags_merge(struct mp_tags *tags, struct mp_tags *src);
|
||||
struct AVDictionary;
|
||||
void mp_tags_copy_from_av_dictionary(struct mp_tags *tags,
|
||||
struct AVDictionary *av_dict);
|
||||
|
||||
+2
-13
@@ -22,8 +22,6 @@
|
||||
#include <inttypes.h>
|
||||
#include "config.h"
|
||||
|
||||
#define HAVE_ATOMICS 1
|
||||
|
||||
#if HAVE_STDATOMIC
|
||||
#include <stdatomic.h>
|
||||
#else
|
||||
@@ -60,17 +58,8 @@ typedef struct { volatile unsigned long long v; } atomic_ullong;
|
||||
__sync_fetch_and_add(&(a)->v, b)
|
||||
|
||||
#else
|
||||
|
||||
// This is extremely wrong. The build system actually disables code that has
|
||||
// a serious dependency on working atomics, so this is barely ok.
|
||||
#define atomic_load(p) ((p)->v)
|
||||
#define atomic_store(p, val) ((p)->v = (val))
|
||||
#define atomic_fetch_add(a, b) (((a)->v += (b)) - (b))
|
||||
|
||||
#undef HAVE_ATOMICS
|
||||
#define HAVE_ATOMICS 0
|
||||
|
||||
#endif /* no atomics */
|
||||
# error "this should have been a configuration error, report a bug please"
|
||||
#endif
|
||||
|
||||
#endif /* else HAVE_STDATOMIC */
|
||||
|
||||
|
||||
+491
-684
File diff suppressed because it is too large.
Load diff
+52
-46
@@ -31,18 +31,14 @@
|
||||
#include "packet.h"
|
||||
#include "stheader.h"
|
||||
|
||||
// Maximum total size of packets queued - if larger, no new packets are read,
|
||||
// and the demuxer pretends EOF was reached.
|
||||
#define MAX_PACKS 16000
|
||||
#define MAX_PACK_BYTES (400 * 1024 * 1024)
|
||||
// Minimum total size of packets queued - the demuxer thread will read more
|
||||
// packets, until either number or total size of the packets exceed the minimum.
|
||||
// This can actually be configured with command line options.
|
||||
#define MIN_PACKS 64
|
||||
#define MIN_PACK_BYTES (5 * 1024 * 1024)
|
||||
struct MPOpts;
|
||||
|
||||
#define MAX_PACKS 4096
|
||||
#define MAX_PACK_BYTES 0x8000000 // 128 MiB
|
||||
|
||||
enum demuxer_type {
|
||||
DEMUXER_TYPE_GENERIC = 0,
|
||||
DEMUXER_TYPE_TV,
|
||||
DEMUXER_TYPE_MATROSKA,
|
||||
DEMUXER_TYPE_EDL,
|
||||
DEMUXER_TYPE_CUE,
|
||||
@@ -52,13 +48,15 @@ enum demuxer_type {
|
||||
#define DEMUXER_CTRL_NOTIMPL -1
|
||||
#define DEMUXER_CTRL_DONTKNOW 0
|
||||
#define DEMUXER_CTRL_OK 1
|
||||
#define DEMUXER_CTRL_GUESS 2
|
||||
|
||||
enum demux_ctrl {
|
||||
DEMUXER_CTRL_SWITCHED_TRACKS = 1,
|
||||
DEMUXER_CTRL_GET_TIME_LENGTH,
|
||||
DEMUXER_CTRL_GET_START_TIME,
|
||||
DEMUXER_CTRL_RESYNC,
|
||||
DEMUXER_CTRL_IDENTIFY_PROGRAM,
|
||||
DEMUXER_CTRL_STREAM_CTRL,
|
||||
DEMUXER_CTRL_STREAM_CTRL, // stupid workaround for legacy TV code
|
||||
};
|
||||
|
||||
struct demux_ctrl_stream_ctrl {
|
||||
@@ -89,13 +87,6 @@ enum demux_check {
|
||||
DEMUX_CHECK_NORMAL, // normal, safe detection
|
||||
};
|
||||
|
||||
enum demux_event {
|
||||
DEMUX_EVENT_INIT = 1 << 0, // complete (re-)initialization
|
||||
DEMUX_EVENT_STREAMS = 1 << 1, // a stream was added
|
||||
DEMUX_EVENT_METADATA = 1 << 2, // metadata or stream_metadata changed
|
||||
DEMUX_EVENT_ALL = 0xFFFF,
|
||||
};
|
||||
|
||||
#define MAX_SH_STREAMS 256
|
||||
|
||||
struct demuxer;
|
||||
@@ -114,7 +105,7 @@ typedef struct demuxer_desc {
|
||||
// The following functions are all optional
|
||||
int (*fill_buffer)(struct demuxer *demuxer); // 0 on EOF, otherwise 1
|
||||
void (*close)(struct demuxer *demuxer);
|
||||
void (*seek)(struct demuxer *demuxer, double rel_seek_secs, int flags);
|
||||
void (*seek)(struct demuxer *demuxer, float rel_seek_secs, int flags);
|
||||
int (*control)(struct demuxer *demuxer, int cmd, void *arg);
|
||||
} demuxer_desc_t;
|
||||
|
||||
@@ -178,18 +169,21 @@ typedef struct demuxer {
|
||||
const demuxer_desc_t *desc; ///< Demuxer description structure
|
||||
const char *filetype; // format name when not identified by demuxer (libavformat)
|
||||
int64_t filepos; // input stream current pos.
|
||||
struct stream *stream;
|
||||
double stream_pts; // current stream pts, if applicable (e.g. dvd)
|
||||
char *filename; // same as stream->url
|
||||
enum demuxer_type type;
|
||||
int seekable; // flag
|
||||
double start_time;
|
||||
/* Set if using absolute seeks for small movements is OK (no pts resets
|
||||
* that would make pts ambigious, preferably supports back/forward flags */
|
||||
bool accurate_seek;
|
||||
// File format allows PTS resets (even if the current file is without)
|
||||
bool ts_resets_possible;
|
||||
|
||||
// Bitmask of DEMUX_EVENT_*
|
||||
int events;
|
||||
bool warned_queue_overflow;
|
||||
|
||||
struct sh_stream **streams;
|
||||
int num_streams;
|
||||
bool stream_autoselect;
|
||||
|
||||
struct demux_edition *editions;
|
||||
int num_editions;
|
||||
@@ -205,24 +199,19 @@ typedef struct demuxer {
|
||||
// for trivial demuxers which just read the whole file for codec to use
|
||||
struct bstr file_contents;
|
||||
|
||||
struct replaygain_data *replaygain_data;
|
||||
|
||||
// If the file is a playlist file
|
||||
struct playlist *playlist;
|
||||
|
||||
struct mp_tags *metadata;
|
||||
char *previous_metadata;
|
||||
|
||||
void *priv; // demuxer-specific internal data
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
struct mp_log *log, *glog;
|
||||
struct demuxer_params *params;
|
||||
|
||||
struct demux_internal *in; // internal to demux.c
|
||||
|
||||
// Since the demuxer can run in its own thread, and the stream is not
|
||||
// thread-safe, only the demuxer is allowed to access the stream directly.
|
||||
// You can freely use demux_stream_control() to send STREAM_CTRLs, or use
|
||||
// demux_pause() to get exclusive access to the stream.
|
||||
struct stream *stream;
|
||||
} demuxer_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -230,17 +219,27 @@ typedef struct {
|
||||
int aid, vid, sid; //audio, video and subtitle id
|
||||
} demux_program_t;
|
||||
|
||||
struct demux_packet *new_demux_packet(size_t len);
|
||||
// data must already have suitable padding
|
||||
struct demux_packet *new_demux_packet_fromdata(void *data, size_t len);
|
||||
struct demux_packet *new_demux_packet_from(void *data, size_t len);
|
||||
void demux_packet_shorten(struct demux_packet *dp, size_t len);
|
||||
void free_demux_packet(struct demux_packet *dp);
|
||||
struct demux_packet *demux_copy_packet(struct demux_packet *dp);
|
||||
|
||||
#ifndef SIZE_MAX
|
||||
#define SIZE_MAX ((size_t)-1)
|
||||
#endif
|
||||
|
||||
void free_demuxer(struct demuxer *demuxer);
|
||||
|
||||
int demux_add_packet(struct sh_stream *stream, demux_packet_t *dp);
|
||||
int demuxer_add_packet(demuxer_t *demuxer, struct sh_stream *stream,
|
||||
demux_packet_t *dp);
|
||||
|
||||
struct demux_packet *demux_read_packet(struct sh_stream *sh);
|
||||
int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt);
|
||||
bool demux_stream_is_selected(struct sh_stream *stream);
|
||||
double demux_get_next_pts(struct sh_stream *sh);
|
||||
bool demux_has_packet(struct sh_stream *sh);
|
||||
bool demux_stream_eof(struct sh_stream *sh);
|
||||
struct demux_packet *demux_read_any_packet(struct demuxer *demuxer);
|
||||
|
||||
struct sh_stream *new_sh_stream(struct demuxer *demuxer, enum stream_type type);
|
||||
|
||||
@@ -248,14 +247,11 @@ struct demuxer *demux_open(struct stream *stream, char *force_format,
|
||||
struct demuxer_params *params,
|
||||
struct mpv_global *global);
|
||||
|
||||
void demux_start_thread(struct demuxer *demuxer);
|
||||
void demux_stop_thread(struct demuxer *demuxer);
|
||||
void demux_set_wakeup_cb(struct demuxer *demuxer, void (*cb)(void *ctx), void *ctx);
|
||||
|
||||
void demux_flush(struct demuxer *demuxer);
|
||||
int demux_seek(struct demuxer *demuxer, float rel_seek_secs, int flags);
|
||||
|
||||
char *demux_info_get(struct demuxer *demuxer, const char *opt);
|
||||
bool demux_info_update(struct demuxer *demuxer);
|
||||
|
||||
int demux_control(struct demuxer *demuxer, int cmd, void *arg);
|
||||
|
||||
@@ -263,7 +259,7 @@ void demuxer_switch_track(struct demuxer *demuxer, enum stream_type type,
|
||||
struct sh_stream *stream);
|
||||
void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
|
||||
bool selected);
|
||||
void demux_set_stream_autoselect(struct demuxer *demuxer, bool autoselect);
|
||||
void demuxer_enable_autoselect(struct demuxer *demuxer);
|
||||
|
||||
void demuxer_help(struct mp_log *log);
|
||||
|
||||
@@ -273,18 +269,28 @@ int demuxer_add_chapter(struct demuxer *demuxer, struct bstr name,
|
||||
uint64_t start, uint64_t end, uint64_t demuxer_id);
|
||||
|
||||
double demuxer_get_time_length(struct demuxer *demuxer);
|
||||
double demuxer_get_start_time(struct demuxer *demuxer);
|
||||
|
||||
int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg);
|
||||
|
||||
void demux_pause(demuxer_t *demuxer);
|
||||
void demux_unpause(demuxer_t *demuxer);
|
||||
|
||||
void demux_changed(demuxer_t *demuxer, int events);
|
||||
void demux_update(demuxer_t *demuxer);
|
||||
/// Get current angle index.
|
||||
int demuxer_get_current_angle(struct demuxer *demuxer);
|
||||
/// Set angle.
|
||||
int demuxer_set_angle(struct demuxer *demuxer, int angle);
|
||||
/// Get number of angles.
|
||||
int demuxer_angles_count(struct demuxer *demuxer);
|
||||
|
||||
struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
|
||||
enum stream_type t, int id);
|
||||
|
||||
bool demuxer_stream_is_selected(struct demuxer *d, struct sh_stream *stream);
|
||||
bool demuxer_stream_has_packets_queued(struct demuxer *d, struct sh_stream *stream);
|
||||
|
||||
void demux_packet_list_sort(struct demux_packet **pkts, int num_pkts);
|
||||
void demux_packet_list_seek(struct demux_packet **pkts, int num_pkts,
|
||||
int *current, float rel_seek_secs, int flags);
|
||||
double demux_packet_list_duration(struct demux_packet **pkts, int num_pkts);
|
||||
struct demux_packet *demux_packet_list_fill(struct demux_packet **pkts,
|
||||
int num_pkts, int *current);
|
||||
|
||||
bool demux_matroska_uid_cmp(struct matroska_segment_uid *a,
|
||||
struct matroska_segment_uid *b);
|
||||
|
||||
|
||||
@@ -1,354 +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 <string.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
#include "demux.h"
|
||||
#include "stheader.h"
|
||||
|
||||
#include "video/csputils.h"
|
||||
|
||||
struct priv {
|
||||
struct demuxer *slave;
|
||||
// streams[slave_stream_index] == our_stream
|
||||
struct sh_stream **streams;
|
||||
int num_streams;
|
||||
// This contains each DVD sub stream, or NULL. Needed because DVD packets
|
||||
// can come arbitrarily late in the MPEG stream, so the slave demuxer
|
||||
// might add the streams only later.
|
||||
struct sh_stream *dvd_subs[32];
|
||||
// Used to rewrite the raw MPEG timestamps to playback time.
|
||||
double base_time; // playback display start time of current segment
|
||||
double base_dts; // packet DTS that maps to base_time
|
||||
double last_dts; // DTS of previously demuxed packet
|
||||
double seek_pts;
|
||||
bool seek_reinit; // needs reinit after seek
|
||||
};
|
||||
|
||||
// If the timestamp difference between subsequent packets is this big, assume
|
||||
// a reset. It should be big enough to account for 1. low video framerates and
|
||||
// large audio frames, and 2. bad interleaving.
|
||||
#define DTS_RESET_THRESHOLD 5.0
|
||||
|
||||
static void reselect_streams(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
for (int n = 0; n < MPMIN(p->slave->num_streams, p->num_streams); n++) {
|
||||
if (p->streams[n]) {
|
||||
demuxer_select_track(p->slave, p->slave->streams[n],
|
||||
demux_stream_is_selected(p->streams[n]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void get_disc_lang(struct stream *stream, struct sh_stream *sh)
|
||||
{
|
||||
struct stream_lang_req req = {.type = sh->type, .id = sh->demuxer_id};
|
||||
if (stream->uncached_type == STREAMTYPE_DVD && sh->type == STREAM_SUB)
|
||||
req.id = req.id & 0x1F; // mpeg ID to index
|
||||
stream_control(stream, STREAM_CTRL_GET_LANG, &req);
|
||||
if (req.name[0])
|
||||
sh->lang = talloc_strdup(sh, req.name);
|
||||
}
|
||||
|
||||
static void add_dvd_streams(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
struct stream *stream = demuxer->stream;
|
||||
if (stream->uncached_type != STREAMTYPE_DVD)
|
||||
return;
|
||||
struct stream_dvd_info_req info;
|
||||
if (stream_control(stream, STREAM_CTRL_GET_DVD_INFO, &info) > 0) {
|
||||
for (int n = 0; n < MPMIN(32, info.num_subs); n++) {
|
||||
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_SUB);
|
||||
if (!sh)
|
||||
break;
|
||||
sh->demuxer_id = n + 0x20;
|
||||
sh->codec = "dvd_subtitle";
|
||||
get_disc_lang(stream, sh);
|
||||
// p->streams _must_ match with p->slave->streams, so we can't add
|
||||
// it yet - it has to be done when the real stream appears, which
|
||||
// could be right on start, or any time later.
|
||||
p->dvd_subs[n] = sh;
|
||||
|
||||
// emulate the extradata
|
||||
struct mp_csp_params csp = MP_CSP_PARAMS_DEFAULTS;
|
||||
csp.int_bits_in = 8;
|
||||
csp.int_bits_out = 8;
|
||||
float cmatrix[3][4];
|
||||
mp_get_yuv2rgb_coeffs(&csp, cmatrix);
|
||||
|
||||
char *s = talloc_strdup(sh, "");
|
||||
s = talloc_asprintf_append(s, "palette: ");
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int color = info.palette[i];
|
||||
int c[3] = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff};
|
||||
mp_map_int_color(cmatrix, 8, c);
|
||||
color = (c[2] << 16) | (c[1] << 8) | c[0];
|
||||
|
||||
if (i != 0)
|
||||
s = talloc_asprintf_append(s, ", ");
|
||||
s = talloc_asprintf_append(s, "%06x", color);
|
||||
}
|
||||
s = talloc_asprintf_append(s, "\n");
|
||||
|
||||
sh->sub->extradata = s;
|
||||
sh->sub->extradata_len = strlen(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void add_streams(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
for (int n = p->num_streams; n < p->slave->num_streams; n++) {
|
||||
struct sh_stream *src = p->slave->streams[n];
|
||||
if (src->sub) {
|
||||
struct sh_stream *sub = NULL;
|
||||
if (src->demuxer_id >= 0x20 && src->demuxer_id <= 0x3F)
|
||||
sub = p->dvd_subs[src->demuxer_id - 0x20];
|
||||
if (sub) {
|
||||
assert(p->num_streams == n); // directly mapped
|
||||
MP_TARRAY_APPEND(p, p->streams, p->num_streams, sub);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
struct sh_stream *sh = new_sh_stream(demuxer, src->type);
|
||||
assert(p->num_streams == n); // directly mapped
|
||||
MP_TARRAY_APPEND(p, p->streams, p->num_streams, sh);
|
||||
// Copy all stream fields that might be relevant
|
||||
sh->codec = talloc_strdup(sh, src->codec);
|
||||
sh->format = src->format;
|
||||
sh->lav_headers = src->lav_headers;
|
||||
if (sh && src->video) {
|
||||
double ar;
|
||||
if (stream_control(demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar)
|
||||
== STREAM_OK)
|
||||
sh->video->aspect = ar;
|
||||
}
|
||||
if (sh && src->audio)
|
||||
sh->audio = src->audio;
|
||||
}
|
||||
reselect_streams(demuxer);
|
||||
}
|
||||
|
||||
static void d_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) {
|
||||
demux_seek(p->slave, rel_seek_secs, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
double pts = p->seek_pts;
|
||||
if (flags & SEEK_ABSOLUTE)
|
||||
pts = 0.0f;
|
||||
|
||||
if (flags & SEEK_FACTOR) {
|
||||
double tmp = 0;
|
||||
stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &tmp);
|
||||
pts += tmp * rel_seek_secs;
|
||||
} else {
|
||||
pts += rel_seek_secs;
|
||||
}
|
||||
|
||||
MP_VERBOSE(demuxer, "seek to: %f\n", pts);
|
||||
|
||||
stream_control(demuxer->stream, STREAM_CTRL_SEEK_TO_TIME, &pts);
|
||||
demux_control(p->slave, DEMUXER_CTRL_RESYNC, NULL);
|
||||
|
||||
p->seek_pts = pts;
|
||||
p->seek_reinit = true;
|
||||
}
|
||||
|
||||
static void reset_pts(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
double base;
|
||||
if (stream_control(demuxer->stream, STREAM_CTRL_GET_CURRENT_TIME, &base) < 1)
|
||||
base = 0;
|
||||
|
||||
MP_VERBOSE(demuxer, "reset to time: %f\n", base);
|
||||
|
||||
p->base_dts = p->last_dts = MP_NOPTS_VALUE;
|
||||
p->base_time = base;
|
||||
p->seek_reinit = false;
|
||||
}
|
||||
|
||||
static int d_fill_buffer(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
struct demux_packet *pkt = demux_read_any_packet(p->slave);
|
||||
if (!pkt)
|
||||
return 0;
|
||||
|
||||
demux_update(p->slave);
|
||||
|
||||
if (p->seek_reinit)
|
||||
reset_pts(demuxer);
|
||||
|
||||
add_streams(demuxer);
|
||||
if (pkt->stream >= p->num_streams) { // out of memory?
|
||||
talloc_free(pkt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct sh_stream *sh = p->streams[pkt->stream];
|
||||
if (!demux_stream_is_selected(sh)) {
|
||||
talloc_free(pkt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
MP_TRACE(demuxer, "ipts: %d %f %f\n", sh->type, pkt->pts, pkt->dts);
|
||||
|
||||
if (sh->type == STREAM_SUB) {
|
||||
if (p->base_dts == MP_NOPTS_VALUE)
|
||||
MP_WARN(demuxer, "subtitle packet along PTS reset\n");
|
||||
} else if (pkt->dts != MP_NOPTS_VALUE) {
|
||||
// Use the very first DTS to rebase the start time of the MPEG stream
|
||||
// to the playback time.
|
||||
if (p->base_dts == MP_NOPTS_VALUE)
|
||||
p->base_dts = pkt->dts;
|
||||
|
||||
if (p->last_dts == MP_NOPTS_VALUE)
|
||||
p->last_dts = pkt->dts;
|
||||
|
||||
if (fabs(p->last_dts - pkt->dts) >= DTS_RESET_THRESHOLD) {
|
||||
MP_WARN(demuxer, "PTS discontinuity: %f->%f\n", p->last_dts, pkt->dts);
|
||||
p->base_time += p->last_dts - p->base_dts;
|
||||
p->base_dts = pkt->dts - pkt->duration;
|
||||
}
|
||||
p->last_dts = pkt->dts;
|
||||
}
|
||||
|
||||
if (p->base_dts != MP_NOPTS_VALUE) {
|
||||
double delta = -p->base_dts + p->base_time;
|
||||
if (pkt->pts != MP_NOPTS_VALUE)
|
||||
pkt->pts += delta;
|
||||
if (pkt->dts != MP_NOPTS_VALUE)
|
||||
pkt->dts += delta;
|
||||
}
|
||||
|
||||
MP_TRACE(demuxer, "opts: %d %f %f\n", sh->type, pkt->pts, pkt->dts);
|
||||
|
||||
if (pkt->pts != MP_NOPTS_VALUE)
|
||||
p->seek_pts = pkt->pts;
|
||||
|
||||
demux_add_packet(sh, pkt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void add_stream_chapters(struct demuxer *demuxer)
|
||||
{
|
||||
int num = 0;
|
||||
if (stream_control(demuxer->stream, STREAM_CTRL_GET_NUM_CHAPTERS, &num) < 1)
|
||||
return;
|
||||
for (int n = 0; n < num; n++) {
|
||||
double p = n;
|
||||
if (stream_control(demuxer->stream, STREAM_CTRL_GET_CHAPTER_TIME, &p) < 1)
|
||||
continue;
|
||||
demuxer_add_chapter(demuxer, bstr0(""), p * 1e9, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static int d_open(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
struct priv *p = demuxer->priv = talloc_zero(demuxer, struct priv);
|
||||
|
||||
if (check != DEMUX_CHECK_FORCE)
|
||||
return -1;
|
||||
|
||||
char *demux = "+lavf";
|
||||
if (demuxer->stream->uncached_type == STREAMTYPE_CDDA)
|
||||
demux = "+rawaudio";
|
||||
|
||||
char *t = NULL;
|
||||
stream_control(demuxer->stream, STREAM_CTRL_GET_DISC_NAME, &t);
|
||||
if (t) {
|
||||
mp_tags_set_bstr(demuxer->metadata, bstr0("TITLE"), bstr0(t));
|
||||
talloc_free(t);
|
||||
}
|
||||
|
||||
// Initialize the playback time. We need to read _some_ data to get the
|
||||
// correct stream-layer time (at least with libdvdnav).
|
||||
stream_peek(demuxer->stream, 1);
|
||||
reset_pts(demuxer);
|
||||
|
||||
p->slave = demux_open(demuxer->stream, demux, NULL, demuxer->global);
|
||||
if (!p->slave)
|
||||
return -1;
|
||||
|
||||
// So that we don't miss initial packets of delayed subtitle streams.
|
||||
demux_set_stream_autoselect(p->slave, true);
|
||||
|
||||
// Can be seekable even if the stream isn't.
|
||||
demuxer->seekable = true;
|
||||
|
||||
add_dvd_streams(demuxer);
|
||||
add_streams(demuxer);
|
||||
add_stream_chapters(demuxer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void d_close(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
free_demuxer(p->slave);
|
||||
}
|
||||
|
||||
static int d_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case DEMUXER_CTRL_GET_TIME_LENGTH: {
|
||||
double len;
|
||||
if (stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &len) < 1)
|
||||
break;
|
||||
*(double *)arg = len;
|
||||
return DEMUXER_CTRL_OK;
|
||||
}
|
||||
case DEMUXER_CTRL_RESYNC:
|
||||
demux_flush(p->slave);
|
||||
break; // relay to slave demuxer
|
||||
case DEMUXER_CTRL_SWITCHED_TRACKS:
|
||||
reselect_streams(demuxer);
|
||||
return DEMUXER_CTRL_OK;
|
||||
}
|
||||
return demux_control(p->slave, cmd, arg);
|
||||
}
|
||||
|
||||
const demuxer_desc_t demuxer_desc_disc = {
|
||||
.name = "disc",
|
||||
.desc = "CD/DVD/BD wrapper",
|
||||
.fill_buffer = d_fill_buffer,
|
||||
.open = d_open,
|
||||
.close = d_close,
|
||||
.seek = d_seek,
|
||||
.control = d_control,
|
||||
};
|
||||
+85
-61
@@ -18,11 +18,12 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
// #include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
// #include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "config.h"
|
||||
@@ -44,6 +45,7 @@
|
||||
#include "options/options.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/tags.h"
|
||||
#include "common/av_opts.h"
|
||||
#include "common/av_common.h"
|
||||
#include "bstr/bstr.h"
|
||||
|
||||
@@ -52,7 +54,6 @@
|
||||
#include "stheader.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
|
||||
#define INITIAL_PROBE_SIZE STREAM_BUFFER_SIZE
|
||||
#define PROBE_BUF_SIZE FFMIN(STREAM_MAX_BUFFER_SIZE, 2 * 1024 * 1024)
|
||||
|
||||
@@ -70,7 +71,7 @@ struct demux_lavf_opts {
|
||||
int allow_mimetype;
|
||||
char *format;
|
||||
char *cryptokey;
|
||||
char **avopts;
|
||||
char *avopt;
|
||||
int genptsmode;
|
||||
};
|
||||
|
||||
@@ -86,7 +87,7 @@ const struct m_sub_options demux_lavf_conf = {
|
||||
OPT_STRING("cryptokey", cryptokey, 0),
|
||||
OPT_CHOICE("genpts-mode", genptsmode, 0,
|
||||
({"lavf", 1}, {"no", 0})),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
OPT_STRING("o", avopt, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct demux_lavf_opts),
|
||||
@@ -106,7 +107,6 @@ typedef struct lavf_priv {
|
||||
int num_streams;
|
||||
int cur_program;
|
||||
char *mime_type;
|
||||
bool merge_track_metadata;
|
||||
} lavf_priv_t;
|
||||
|
||||
struct format_hack {
|
||||
@@ -129,9 +129,6 @@ static const struct format_hack format_hacks[] = {
|
||||
|
||||
static const char *const format_blacklist[] = {
|
||||
"tty", // Useless non-sense, sometimes breaks MLP2 subreader.c fallback
|
||||
// Image demuxers, disabled in favor of demux_mf:
|
||||
"image2", "image2pipe",
|
||||
"bmp_pipe", "dpx_pipe", "exr_pipe", "j2k_pipe", "png_pipe", "tiff_pipe",
|
||||
0
|
||||
};
|
||||
|
||||
@@ -143,8 +140,8 @@ static int mp_read(void *opaque, uint8_t *buf, int size)
|
||||
|
||||
ret = stream_read(stream, buf, size);
|
||||
|
||||
MP_TRACE(demuxer, "%d=mp_read(%p, %p, %d), pos: %"PRId64", eof:%d\n",
|
||||
ret, stream, buf, size, stream_tell(stream), stream->eof);
|
||||
MP_DBG(demuxer, "%d=mp_read(%p, %p, %d), pos: %"PRId64", eof:%d\n",
|
||||
ret, stream, buf, size, stream_tell(stream), stream->eof);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -153,7 +150,10 @@ static int64_t mp_seek(void *opaque, int64_t pos, int whence)
|
||||
struct demuxer *demuxer = opaque;
|
||||
struct stream *stream = demuxer->stream;
|
||||
int64_t current_pos;
|
||||
MP_TRACE(demuxer, "mp_seek(%p, %"PRId64", %d)\n", stream, pos, whence);
|
||||
if (stream_manages_timeline(stream))
|
||||
return -1;
|
||||
MP_DBG(demuxer, "mp_seek(%p, %"PRId64", %d)\n",
|
||||
stream, pos, whence);
|
||||
if (whence == SEEK_END || whence == AVSEEK_SIZE) {
|
||||
int64_t end;
|
||||
if (stream_control(stream, STREAM_CTRL_GET_SIZE, &end) != STREAM_OK)
|
||||
@@ -182,15 +182,14 @@ static int64_t mp_read_seek(void *opaque, int stream_idx, int64_t ts, int flags)
|
||||
{
|
||||
struct demuxer *demuxer = opaque;
|
||||
struct stream *stream = demuxer->stream;
|
||||
struct lavf_priv *priv = demuxer->priv;
|
||||
|
||||
struct stream_avseek cmd = {
|
||||
.stream_index = stream_idx,
|
||||
.timestamp = ts,
|
||||
.flags = flags,
|
||||
};
|
||||
|
||||
int ret = stream_control(stream, STREAM_CTRL_AVSEEK, &cmd);
|
||||
return ret < 1 ? AVERROR(ENOSYS) : 0;
|
||||
AVStream *st = priv->avfc->streams[stream_idx];
|
||||
double pts = (double)ts * st->time_base.num / st->time_base.den;
|
||||
int ret = stream_control(stream, STREAM_CTRL_SEEK_TO_TIME, &pts);
|
||||
if (ret < 0)
|
||||
ret = AVERROR(ENOSYS);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void list_formats(struct demuxer *demuxer)
|
||||
@@ -265,7 +264,8 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
MP_FATAL(demuxer, "Unknown lavf format %s\n", format);
|
||||
return -1;
|
||||
}
|
||||
MP_VERBOSE(demuxer, "Forced lavf %s demuxer\n", priv->avif->long_name);
|
||||
MP_INFO(demuxer, "Forced lavf %s demuxer\n",
|
||||
priv->avif->long_name);
|
||||
goto success;
|
||||
}
|
||||
|
||||
@@ -393,13 +393,13 @@ static void select_tracks(struct demuxer *demuxer, int start)
|
||||
for (int n = start; n < priv->num_streams; n++) {
|
||||
struct sh_stream *stream = priv->streams[n];
|
||||
AVStream *st = priv->avfc->streams[n];
|
||||
bool selected = stream && demux_stream_is_selected(stream) &&
|
||||
bool selected = stream && demuxer_stream_is_selected(demuxer, stream) &&
|
||||
!stream->attached_picture;
|
||||
st->discard = selected ? AVDISCARD_DEFAULT : AVDISCARD_ALL;
|
||||
}
|
||||
}
|
||||
|
||||
static void export_replaygain(demuxer_t *demuxer, sh_audio_t *sh, AVStream *st)
|
||||
static void export_replaygain(demuxer_t *demuxer, AVStream *st)
|
||||
{
|
||||
#if HAVE_AVCODEC_REPLAYGAIN_SIDE_DATA
|
||||
for (int i = 0; i < st->nb_side_data; i++) {
|
||||
@@ -425,7 +425,7 @@ static void export_replaygain(demuxer_t *demuxer, sh_audio_t *sh, AVStream *st)
|
||||
rgain->album_peak = (av_rgain->album_peak != 0.0) ?
|
||||
av_rgain->album_peak / 100000.0f : 1.0;
|
||||
|
||||
sh->replaygain_data = rgain;
|
||||
demuxer->replaygain_data = rgain;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -454,7 +454,7 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
sh_audio->samplerate = codec->sample_rate;
|
||||
sh_audio->bitrate = codec->bit_rate;
|
||||
|
||||
export_replaygain(demuxer, sh_audio, st);
|
||||
export_replaygain(demuxer, st);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -504,7 +504,7 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
#if HAVE_AV_DISPLAYMATRIX
|
||||
uint8_t *sd = av_stream_get_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, NULL);
|
||||
if (sd)
|
||||
sh_video->rotate = -av_display_rotation_get((uint32_t *)sd);
|
||||
sh_video->rotate = av_display_rotation_get((uint32_t *)sd);
|
||||
#else
|
||||
AVDictionaryEntry *rot = av_dict_get(st->metadata, "rotate", NULL, 0);
|
||||
if (rot && rot->value) {
|
||||
@@ -519,6 +519,9 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
// This also applies to vfw-muxed mkv, but we can't detect these easily.
|
||||
sh_video->avi_dts = matches_avinputformat_name(priv, "avi");
|
||||
|
||||
MP_DBG(demuxer, "aspect= %d*%d/(%d*%d)\n",
|
||||
codec->width, codec->sample_aspect_ratio.num,
|
||||
codec->height, codec->sample_aspect_ratio.den);
|
||||
break;
|
||||
}
|
||||
case AVMEDIA_TYPE_SUBTITLE: {
|
||||
@@ -555,18 +558,11 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
AVDictionaryEntry *ftag = av_dict_get(st->metadata, "filename",
|
||||
NULL, 0);
|
||||
char *filename = ftag ? ftag->value : NULL;
|
||||
char *mimetype = NULL;
|
||||
switch (st->codec->codec_id) {
|
||||
case AV_CODEC_ID_TTF: mimetype = "application/x-truetype-font"; break;
|
||||
#if LIBAVFORMAT_VERSION_MICRO >= 100
|
||||
case AV_CODEC_ID_OTF: mimetype = "application/vnd.ms-opentype"; break;
|
||||
#endif
|
||||
}
|
||||
if (mimetype) {
|
||||
demuxer_add_attachment(demuxer, bstr0(filename), bstr0(mimetype),
|
||||
if (st->codec->codec_id == AV_CODEC_ID_TTF)
|
||||
demuxer_add_attachment(demuxer, bstr0(filename),
|
||||
bstr0("application/x-truetype-font"),
|
||||
(struct bstr){codec->extradata,
|
||||
codec->extradata_size});
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: ;
|
||||
@@ -593,7 +589,6 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
}
|
||||
|
||||
select_tracks(demuxer, i);
|
||||
demux_changed(demuxer, DEMUX_EVENT_STREAMS);
|
||||
}
|
||||
|
||||
// Add any new streams that might have been added
|
||||
@@ -607,18 +602,16 @@ static void add_new_streams(demuxer_t *demuxer)
|
||||
static void update_metadata(demuxer_t *demuxer, AVPacket *pkt)
|
||||
{
|
||||
#if HAVE_AVCODEC_METADATA_UPDATE_SIDE_DATA
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
int md_size;
|
||||
const uint8_t *md;
|
||||
md = av_packet_get_side_data(pkt, AV_PKT_DATA_METADATA_UPDATE, &md_size);
|
||||
if (md && priv->merge_track_metadata) {
|
||||
if (md) {
|
||||
AVDictionary *dict = NULL;
|
||||
av_packet_unpack_dictionary(md, md_size, &dict);
|
||||
if (dict) {
|
||||
mp_tags_clear(demuxer->metadata);
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, dict);
|
||||
av_dict_free(&dict);
|
||||
demux_changed(demuxer, DEMUX_EVENT_METADATA);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -658,7 +651,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
if (lavfdopts->probesize) {
|
||||
if (av_opt_set_int(avfc, "probesize", lavfdopts->probesize, 0) < 0)
|
||||
MP_ERR(demuxer, "couldn't set option probesize to %u\n",
|
||||
MP_ERR(demuxer, "demux_lavf, couldn't set option probesize to %u\n",
|
||||
lavfdopts->probesize);
|
||||
}
|
||||
|
||||
@@ -673,6 +666,14 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
"analyzeduration to %f\n", analyze_duration);
|
||||
}
|
||||
|
||||
if (lavfdopts->avopt) {
|
||||
if (parse_avopts(avfc, lavfdopts->avopt) < 0) {
|
||||
MP_ERR(demuxer, "Your options /%s/ look like gibberish to me pal\n",
|
||||
lavfdopts->avopt);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if ((priv->avif->flags & AVFMT_NOFILE) ||
|
||||
demuxer->stream->type == STREAMTYPE_AVDEVICE)
|
||||
{
|
||||
@@ -706,25 +707,27 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
av_dict_set(&dopts, "rtsp_transport", transport, 0);
|
||||
}
|
||||
|
||||
mp_set_avdict(&dopts, lavfdopts->avopts);
|
||||
|
||||
if (avformat_open_input(&avfc, priv->filename, priv->avif, &dopts) < 0) {
|
||||
MP_ERR(demuxer, "avformat_open_input() failed\n");
|
||||
MP_ERR(demuxer, "LAVF_header: avformat_open_input() failed\n");
|
||||
av_dict_free(&dopts);
|
||||
return -1;
|
||||
}
|
||||
|
||||
mp_avdict_print_unset(demuxer->log, MSGL_V, dopts);
|
||||
t = NULL;
|
||||
while ((t = av_dict_get(dopts, "", t, AV_DICT_IGNORE_SUFFIX))) {
|
||||
MP_VERBOSE(demuxer, "[lavf] Could not set demux option %s=%s\n",
|
||||
t->key, t->value);
|
||||
}
|
||||
av_dict_free(&dopts);
|
||||
|
||||
priv->avfc = avfc;
|
||||
if (avformat_find_stream_info(avfc, NULL) < 0) {
|
||||
MP_ERR(demuxer, "av_find_stream_info() failed\n");
|
||||
MP_ERR(demuxer, "LAVF_header: av_find_stream_info() failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
MP_VERBOSE(demuxer, "avformat_find_stream_info() finished after %"PRId64
|
||||
" bytes.\n", stream_tell(demuxer->stream));
|
||||
MP_VERBOSE(demuxer, "demux_lavf: avformat_find_stream_info() "
|
||||
"finished after %"PRId64" bytes.\n", stream_tell(demuxer->stream));
|
||||
|
||||
for (i = 0; i < avfc->nb_chapters; i++) {
|
||||
AVChapter *c = avfc->chapters[i];
|
||||
@@ -745,17 +748,26 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
// Often useful with OGG audio-only files, which have metadata in the audio
|
||||
// track metadata instead of the main metadata.
|
||||
if (demuxer->num_streams == 1) {
|
||||
priv->merge_track_metadata = true;
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
if (priv->streams[n])
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, avfc->streams[n]->metadata);
|
||||
}
|
||||
}
|
||||
|
||||
demuxer->ts_resets_possible = priv->avif->flags & AVFMT_TS_DISCONT;
|
||||
if (avfc->nb_programs) {
|
||||
int p;
|
||||
for (p = 0; p < avfc->nb_programs; p++) {
|
||||
AVProgram *program = avfc->programs[p];
|
||||
t = av_dict_get(program->metadata, "title", NULL, 0);
|
||||
MP_INFO(demuxer, "LAVF: Program %d %s\n",
|
||||
program->id, t ? t->value : "");
|
||||
MP_VERBOSE(demuxer, "PROGRAM_ID=%d\n", program->id);
|
||||
}
|
||||
}
|
||||
|
||||
demuxer->start_time = priv->avfc->start_time == AV_NOPTS_VALUE ?
|
||||
0 : (double)priv->avfc->start_time / AV_TIME_BASE;
|
||||
MP_VERBOSE(demuxer, "LAVF: build %d\n", LIBAVFORMAT_BUILD);
|
||||
|
||||
demuxer->ts_resets_possible = priv->avif->flags & AVFMT_TS_DISCONT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -769,12 +781,12 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
{
|
||||
lavf_priv_t *priv = demux->priv;
|
||||
demux_packet_t *dp;
|
||||
MP_DBG(demux, "demux_lavf_fill_buffer()\n");
|
||||
|
||||
AVPacket *pkt = talloc(NULL, AVPacket);
|
||||
int r = av_read_frame(priv->avfc, pkt);
|
||||
if (r < 0) {
|
||||
if (av_read_frame(priv->avfc, pkt) < 0) {
|
||||
talloc_free(pkt);
|
||||
return r == AVERROR(EAGAIN) ? 1 : -1; // eof
|
||||
return 0; // eof
|
||||
}
|
||||
talloc_set_destructor(pkt, destroy_avpacket);
|
||||
|
||||
@@ -785,7 +797,7 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
struct sh_stream *stream = priv->streams[pkt->stream_index];
|
||||
AVStream *st = priv->avfc->streams[pkt->stream_index];
|
||||
|
||||
if (!demux_stream_is_selected(stream)) {
|
||||
if (!demuxer_stream_is_selected(demux, stream)) {
|
||||
talloc_free(pkt);
|
||||
return 1; // don't signal EOF if skipping a packet
|
||||
}
|
||||
@@ -808,19 +820,27 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
dp->duration = pkt->convergence_duration * av_q2d(st->time_base);
|
||||
dp->pos = pkt->pos;
|
||||
dp->keyframe = pkt->flags & AV_PKT_FLAG_KEY;
|
||||
// Use only one stream for stream_pts, otherwise PTS might be jumpy.
|
||||
if (stream->type == STREAM_VIDEO) {
|
||||
double pts;
|
||||
if (stream_control(demux->stream, STREAM_CTRL_GET_CURRENT_TIME, &pts) > 0)
|
||||
dp->stream_pts = pts;
|
||||
}
|
||||
if (dp->pts != MP_NOPTS_VALUE) {
|
||||
priv->last_pts = dp->pts * AV_TIME_BASE;
|
||||
} else if (dp->dts != MP_NOPTS_VALUE) {
|
||||
priv->last_pts = dp->dts * AV_TIME_BASE;
|
||||
}
|
||||
demux_add_packet(stream, dp);
|
||||
demuxer_add_packet(demux, stream, dp);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void demux_seek_lavf(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
static void demux_seek_lavf(demuxer_t *demuxer, float rel_seek_secs, int flags)
|
||||
{
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
int avsflags = 0;
|
||||
MP_DBG(demuxer, "demux_seek_lavf(%p, %f, %d)\n",
|
||||
demuxer, rel_seek_secs, flags);
|
||||
|
||||
if (flags & SEEK_ABSOLUTE)
|
||||
priv->last_pts = 0;
|
||||
@@ -906,6 +926,11 @@ static int demux_lavf_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
*((double *)arg) = (double)priv->avfc->duration / AV_TIME_BASE;
|
||||
return DEMUXER_CTRL_OK;
|
||||
|
||||
case DEMUXER_CTRL_GET_START_TIME:
|
||||
*((double *)arg) = priv->avfc->start_time == AV_NOPTS_VALUE ?
|
||||
0 : (double)priv->avfc->start_time / AV_TIME_BASE;
|
||||
return DEMUXER_CTRL_OK;
|
||||
|
||||
case DEMUXER_CTRL_SWITCHED_TRACKS:
|
||||
{
|
||||
select_tracks(demuxer, 0);
|
||||
@@ -981,12 +1006,11 @@ redo:
|
||||
* call the new API instead of relying on av_seek_frame() to do this
|
||||
* for us.
|
||||
*/
|
||||
stream_drop_buffers(demuxer->stream);
|
||||
avio_flush(priv->avfc->pb);
|
||||
av_seek_frame(priv->avfc, 0, stream_tell(demuxer->stream),
|
||||
AVSEEK_FLAG_BYTE);
|
||||
// avio_flush() is designed for write-only streams, and does the wrong
|
||||
// thing when reading. Flush it manually instead.
|
||||
priv->avfc->pb->buf_ptr = priv->avfc->pb->buf_end;
|
||||
stream_drop_buffers(demuxer->stream);
|
||||
avio_flush(priv->avfc->pb);
|
||||
return DEMUXER_CTRL_OK;
|
||||
default:
|
||||
return DEMUXER_CTRL_NOTIMPL;
|
||||
|
||||
+3
-4
@@ -18,7 +18,6 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -37,7 +36,7 @@
|
||||
|
||||
#define MF_MAX_FILE_SIZE (1024 * 1024 * 256)
|
||||
|
||||
static void demux_seek_mf(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
static void demux_seek_mf(demuxer_t *demuxer, float rel_seek_secs, int flags)
|
||||
{
|
||||
mf_t *mf = demuxer->priv;
|
||||
int newpos = (flags & SEEK_ABSOLUTE) ? 0 : mf->curr_frame - 1;
|
||||
@@ -49,7 +48,7 @@ static void demux_seek_mf(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
if (newpos < 0)
|
||||
newpos = 0;
|
||||
if (newpos >= mf->nr_of_files)
|
||||
newpos = mf->nr_of_files;
|
||||
newpos = mf->nr_of_files - 1;
|
||||
mf->curr_frame = newpos;
|
||||
}
|
||||
|
||||
@@ -80,7 +79,7 @@ static int demux_mf_fill_buffer(demuxer_t *demuxer)
|
||||
memcpy(dp->buffer, data.start, data.len);
|
||||
dp->pts = mf->curr_frame / mf->sh->fps;
|
||||
dp->keyframe = true;
|
||||
demux_add_packet(demuxer->streams[0], dp);
|
||||
demuxer_add_packet(demuxer, demuxer->streams[0], dp);
|
||||
}
|
||||
talloc_free(data.start);
|
||||
}
|
||||
|
||||
+123
-149
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
@@ -127,13 +128,13 @@ typedef struct mkv_track {
|
||||
double ra_pts; /* previous audio timestamp */
|
||||
|
||||
/** realaudio descrambling */
|
||||
uint16_t sub_packet_size; ///< sub packet size, per stream
|
||||
uint16_t sub_packet_h; ///< number of coded frames per block
|
||||
uint32_t coded_framesize; ///< coded frame size, per stream
|
||||
uint16_t audiopk_size; ///< audio packet size
|
||||
int sub_packet_size; ///< sub packet size, per stream
|
||||
int sub_packet_h; ///< number of coded frames per block
|
||||
int coded_framesize; ///< coded frame size, per stream
|
||||
int audiopk_size; ///< audio packet size
|
||||
unsigned char *audio_buf; ///< place to store reordered audio data
|
||||
double *audio_timestamp; ///< timestamp for each audio packet
|
||||
uint16_t sub_packet_cnt; ///< number of subpacket already received
|
||||
int sub_packet_cnt; ///< number of subpacket already received
|
||||
int audio_filepos; ///< file position of first audio packet in block
|
||||
|
||||
/* generic content encoding support */
|
||||
@@ -141,7 +142,7 @@ typedef struct mkv_track {
|
||||
int num_encodings;
|
||||
|
||||
/* latest added index entry for this track */
|
||||
size_t last_index_entry;
|
||||
int last_index_entry;
|
||||
} mkv_track_t;
|
||||
|
||||
typedef struct mkv_index {
|
||||
@@ -166,7 +167,7 @@ typedef struct mkv_demuxer {
|
||||
uint64_t cluster_end;
|
||||
|
||||
mkv_index_t *indexes;
|
||||
size_t num_indexes;
|
||||
int num_indexes;
|
||||
bool index_complete;
|
||||
uint64_t deferred_cues;
|
||||
|
||||
@@ -191,6 +192,19 @@ typedef struct mkv_demuxer {
|
||||
// (Subtitle packets added before first A/V keyframe packet is found with seek.)
|
||||
#define NUM_SUB_PREROLL_PACKETS 500
|
||||
|
||||
/**
|
||||
* \brief ensures there is space for at least one additional element
|
||||
* \param array array to grow
|
||||
* \param nelem current number of elements in array
|
||||
* \param elsize size of one array element
|
||||
*/
|
||||
static void *grow_array(void *array, int nelem, size_t elsize)
|
||||
{
|
||||
if (!(nelem & 31))
|
||||
array = realloc(array, (nelem + 32) * elsize);
|
||||
return array;
|
||||
}
|
||||
|
||||
#define AAC_SYNC_EXTENSION_TYPE 0x02b7
|
||||
static int aac_get_sample_rate_index(uint32_t sample_rate)
|
||||
{
|
||||
@@ -244,12 +258,6 @@ static bstr demux_mkv_decode(struct mp_log *log, mkv_track_t *track,
|
||||
zstream.avail_out = size;
|
||||
int result;
|
||||
do {
|
||||
if (size >= INT_MAX - 4000) {
|
||||
talloc_free(dest);
|
||||
dest = NULL;
|
||||
inflateEnd(&zstream);
|
||||
goto error;
|
||||
}
|
||||
size += 4000;
|
||||
dest = talloc_realloc_size(track->parser_tmp, dest, size);
|
||||
zstream.next_out = (Bytef *) (dest + zstream.total_out);
|
||||
@@ -271,9 +279,6 @@ static bstr demux_mkv_decode(struct mp_log *log, mkv_track_t *track,
|
||||
} else if (enc->comp_algo == 2) {
|
||||
/* lzo encoded track */
|
||||
int out_avail;
|
||||
int maxlen = INT_MAX - AV_LZO_OUTPUT_PADDING;
|
||||
if (size >= maxlen / 3)
|
||||
goto error;
|
||||
int dstlen = size * 3;
|
||||
|
||||
dest = NULL;
|
||||
@@ -292,11 +297,6 @@ static bstr demux_mkv_decode(struct mp_log *log, mkv_track_t *track,
|
||||
goto error;
|
||||
}
|
||||
mp_dbg(log, "lzo decompression buffer too small.\n");
|
||||
if (dstlen >= maxlen / 2) {
|
||||
talloc_free(dest);
|
||||
dest = NULL;
|
||||
goto error;
|
||||
}
|
||||
dstlen *= 2;
|
||||
}
|
||||
size = dstlen - out_avail;
|
||||
@@ -311,8 +311,6 @@ static bstr demux_mkv_decode(struct mp_log *log, mkv_track_t *track,
|
||||
error:
|
||||
if (src != dest && src != orig_src)
|
||||
talloc_free(src);
|
||||
if (!size)
|
||||
dest = NULL;
|
||||
return (bstr){dest, size};
|
||||
}
|
||||
|
||||
@@ -435,7 +433,7 @@ static void parse_trackencodings(struct demuxer *demuxer,
|
||||
if (e.order >= ce[i].order)
|
||||
break;
|
||||
}
|
||||
ce = talloc_realloc(track, ce, mkv_content_encoding_t, n_enc + 1);
|
||||
ce = talloc_realloc_size(track, ce, (n_enc + 1) * sizeof(*ce));
|
||||
memmove(ce + i + 1, ce + i, (n_enc - i) * sizeof(*ce));
|
||||
memcpy(ce + i, &e, sizeof(e));
|
||||
}
|
||||
@@ -461,11 +459,13 @@ static void parse_trackaudio(struct demuxer *demuxer, struct mkv_track *track,
|
||||
}
|
||||
if (audio->n_bit_depth) {
|
||||
track->a_bps = audio->bit_depth;
|
||||
MP_VERBOSE(demuxer, "| + Bit depth: %u\n", track->a_bps);
|
||||
MP_VERBOSE(demuxer, "| + Bit depth: %u\n",
|
||||
track->a_bps);
|
||||
}
|
||||
if (audio->n_channels) {
|
||||
track->a_channels = audio->channels;
|
||||
MP_VERBOSE(demuxer, "| + Channels: %u\n", track->a_channels);
|
||||
MP_VERBOSE(demuxer, "| + Channels: %u\n",
|
||||
track->a_channels);
|
||||
} else {
|
||||
track->a_channels = 1;
|
||||
}
|
||||
@@ -476,33 +476,38 @@ static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track,
|
||||
{
|
||||
if (video->n_frame_rate) {
|
||||
track->v_frate = video->frame_rate;
|
||||
MP_VERBOSE(demuxer, "| + Frame rate: %f\n", track->v_frate);
|
||||
MP_VERBOSE(demuxer, "| + Frame rate: %f\n",
|
||||
track->v_frate);
|
||||
if (track->v_frate > 0)
|
||||
track->default_duration = 1 / track->v_frate;
|
||||
}
|
||||
if (video->n_display_width) {
|
||||
track->v_dwidth = video->display_width;
|
||||
track->v_dwidth_set = true;
|
||||
MP_VERBOSE(demuxer, "| + Display width: %u\n", track->v_dwidth);
|
||||
MP_VERBOSE(demuxer, "| + Display width: %u\n",
|
||||
track->v_dwidth);
|
||||
}
|
||||
if (video->n_display_height) {
|
||||
track->v_dheight = video->display_height;
|
||||
track->v_dheight_set = true;
|
||||
MP_VERBOSE(demuxer, "| + Display height: %u\n", track->v_dheight);
|
||||
MP_VERBOSE(demuxer, "| + Display height: %u\n",
|
||||
track->v_dheight);
|
||||
}
|
||||
if (video->n_pixel_width) {
|
||||
track->v_width = video->pixel_width;
|
||||
MP_VERBOSE(demuxer, "| + Pixel width: %u\n", track->v_width);
|
||||
MP_VERBOSE(demuxer, "| + Pixel width: %u\n",
|
||||
track->v_width);
|
||||
}
|
||||
if (video->n_pixel_height) {
|
||||
track->v_height = video->pixel_height;
|
||||
MP_VERBOSE(demuxer, "| + Pixel height: %u\n", track->v_height);
|
||||
MP_VERBOSE(demuxer, "| + Pixel height: %u\n",
|
||||
track->v_height);
|
||||
}
|
||||
if (video->n_colour_space && video->colour_space.len == 4) {
|
||||
uint8_t *d = (uint8_t *)&video->colour_space.start[0];
|
||||
track->colorspace = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24);
|
||||
MP_VERBOSE(demuxer, "| + Colorspace: %#x\n",
|
||||
(unsigned int)track->colorspace);
|
||||
(unsigned int)track->colorspace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,13 +525,14 @@ static void parse_trackentry(struct demuxer *demuxer,
|
||||
struct ebml_track_entry *entry)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
|
||||
struct mkv_track *track = talloc_zero(NULL, struct mkv_track);
|
||||
track->last_index_entry = (size_t)-1;
|
||||
struct mkv_track *track = talloc_zero_size(NULL, sizeof(*track));
|
||||
track->last_index_entry = -1;
|
||||
track->parser_tmp = talloc_new(track);
|
||||
|
||||
track->tnum = entry->track_number;
|
||||
if (track->tnum) {
|
||||
MP_VERBOSE(demuxer, "| + Track number: %u\n", track->tnum);
|
||||
MP_VERBOSE(demuxer, "| + Track number: %u\n",
|
||||
track->tnum);
|
||||
} else {
|
||||
MP_ERR(demuxer, "Missing track number!\n");
|
||||
}
|
||||
@@ -534,7 +540,8 @@ static void parse_trackentry(struct demuxer *demuxer,
|
||||
if (entry->n_name) {
|
||||
track->name = talloc_strndup(track, entry->name.start,
|
||||
entry->name.len);
|
||||
MP_VERBOSE(demuxer, "| + Name: %s\n", track->name);
|
||||
MP_VERBOSE(demuxer, "| + Name: %s\n",
|
||||
track->name);
|
||||
}
|
||||
|
||||
track->type = entry->track_type;
|
||||
@@ -570,31 +577,35 @@ static void parse_trackentry(struct demuxer *demuxer,
|
||||
if (!strcmp(track->codec_id, MKV_V_MSCOMP)
|
||||
|| !strcmp(track->codec_id, MKV_A_ACM))
|
||||
track->ms_compat = 1;
|
||||
MP_VERBOSE(demuxer, "| + Codec ID: %s\n", track->codec_id);
|
||||
MP_VERBOSE(demuxer, "| + Codec ID: %s\n",
|
||||
track->codec_id);
|
||||
} else {
|
||||
MP_ERR(demuxer, "Missing codec ID!\n");
|
||||
track->codec_id = "";
|
||||
}
|
||||
|
||||
if (entry->n_codec_private && entry->codec_private.len <= 0x10000000) {
|
||||
if (entry->n_codec_private) {
|
||||
int len = entry->codec_private.len;
|
||||
track->private_data = talloc_size(track, len + AV_LZO_INPUT_PADDING);
|
||||
memcpy(track->private_data, entry->codec_private.start, len);
|
||||
track->private_size = len;
|
||||
MP_VERBOSE(demuxer, "| + CodecPrivate, length %u\n", track->private_size);
|
||||
MP_VERBOSE(demuxer, "| + CodecPrivate, length %u\n",
|
||||
track->private_size);
|
||||
}
|
||||
|
||||
if (entry->n_language) {
|
||||
track->language = talloc_strndup(track, entry->language.start,
|
||||
entry->language.len);
|
||||
MP_VERBOSE(demuxer, "| + Language: %s\n", track->language);
|
||||
MP_VERBOSE(demuxer, "| + Language: %s\n",
|
||||
track->language);
|
||||
} else {
|
||||
track->language = talloc_strdup(track, "eng");
|
||||
}
|
||||
|
||||
if (entry->n_flag_default) {
|
||||
track->default_track = entry->flag_default;
|
||||
MP_VERBOSE(demuxer, "| + Default flag: %u\n", track->default_track);
|
||||
MP_VERBOSE(demuxer, "| + Default flag: %u\n",
|
||||
track->default_track);
|
||||
} else {
|
||||
track->default_track = 1;
|
||||
}
|
||||
@@ -607,7 +618,7 @@ static void parse_trackentry(struct demuxer *demuxer,
|
||||
if (!track->v_frate)
|
||||
track->v_frate = 1e9 / entry->default_duration;
|
||||
MP_VERBOSE(demuxer, "| + Default duration: %.3fms ( = %.3f fps)\n",
|
||||
entry->default_duration / 1000000.0, track->v_frate);
|
||||
entry->default_duration / 1000000.0, track->v_frate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -629,8 +640,8 @@ static int demux_mkv_read_tracks(demuxer_t *demuxer)
|
||||
if (ebml_read_element(s, &parse_ctx, &tracks, &ebml_tracks_desc) < 0)
|
||||
return -1;
|
||||
|
||||
mkv_d->tracks = talloc_zero_array(mkv_d, struct mkv_track*,
|
||||
tracks.n_track_entry);
|
||||
mkv_d->tracks = talloc_size(mkv_d,
|
||||
tracks.n_track_entry * sizeof(*mkv_d->tracks));
|
||||
for (int i = 0; i < tracks.n_track_entry; i++) {
|
||||
MP_VERBOSE(demuxer, "| + a track...\n");
|
||||
parse_trackentry(demuxer, &tracks.track_entry[i]);
|
||||
@@ -644,8 +655,8 @@ static void cue_index_add(demuxer_t *demuxer, int track_id, uint64_t filepos,
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
|
||||
|
||||
MP_TARRAY_GROW(mkv_d, mkv_d->indexes, mkv_d->num_indexes);
|
||||
|
||||
mkv_d->indexes = grow_array(mkv_d->indexes, mkv_d->num_indexes,
|
||||
sizeof(mkv_index_t));
|
||||
mkv_d->indexes[mkv_d->num_indexes].tnum = track_id;
|
||||
mkv_d->indexes[mkv_d->num_indexes].timecode = timecode;
|
||||
mkv_d->indexes[mkv_d->num_indexes].filepos = filepos;
|
||||
@@ -659,7 +670,7 @@ static void add_block_position(demuxer_t *demuxer, struct mkv_track *track,
|
||||
|
||||
if (mkv_d->index_complete || !track)
|
||||
return;
|
||||
if (track->last_index_entry != (size_t)-1) {
|
||||
if (track->last_index_entry >= 0) {
|
||||
mkv_index_t *index = &mkv_d->indexes[track->last_index_entry];
|
||||
// filepos is always the cluster position, which can contain multiple
|
||||
// blocks with different timecodes - one is enough.
|
||||
@@ -770,7 +781,8 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
|
||||
}
|
||||
if (wanted_edition >= 0 && wanted_edition < num_editions) {
|
||||
selected_edition = wanted_edition;
|
||||
MP_VERBOSE(demuxer, "User-specified edition: %d\n", selected_edition);
|
||||
MP_VERBOSE(demuxer, "User-specified edition: %d\n",
|
||||
selected_edition);
|
||||
} else {
|
||||
for (int i = 0; i < num_editions; i++) {
|
||||
if (wanted_edition_uid &&
|
||||
@@ -799,10 +811,10 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
|
||||
int warn_level = idx == selected_edition ? MSGL_WARN : MSGL_V;
|
||||
if (editions[idx].n_edition_flag_default)
|
||||
MP_VERBOSE(demuxer, "Default edition flag: %"PRIu64
|
||||
"\n", editions[idx].edition_flag_default);
|
||||
"\n", editions[idx].edition_flag_default);
|
||||
if (editions[idx].n_edition_flag_ordered)
|
||||
MP_VERBOSE(demuxer, "Ordered chapter flag: %"PRIu64
|
||||
"\n", editions[idx].edition_flag_ordered);
|
||||
"\n", editions[idx].edition_flag_ordered);
|
||||
|
||||
int chapter_count = editions[idx].n_chapter_atom;
|
||||
|
||||
@@ -819,7 +831,8 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
|
||||
struct bstr name = { "(unnamed)", 9 };
|
||||
|
||||
if (!ca->n_chapter_time_start)
|
||||
MP_MSG(demuxer, warn_level, "Chapter lacks start time\n");
|
||||
MP_MSG(demuxer, warn_level,
|
||||
"Chapter lacks start time\n");
|
||||
chapter.start = ca->chapter_time_start;
|
||||
chapter.end = ca->chapter_time_end;
|
||||
|
||||
@@ -828,7 +841,8 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
|
||||
MP_MSG(demuxer, warn_level, "Multiple chapter "
|
||||
"names not supported, picking first\n");
|
||||
if (!ca->chapter_display[0].n_chap_string)
|
||||
MP_MSG(demuxer, warn_level, "Malformed chapter name entry\n");
|
||||
MP_MSG(demuxer, warn_level, "Malformed chapter "
|
||||
"name entry\n");
|
||||
else
|
||||
name = ca->chapter_display[0].chap_string;
|
||||
}
|
||||
@@ -969,7 +983,7 @@ static int demux_mkv_read_attachments(demuxer_t *demuxer)
|
||||
struct bstr mime = attachment->file_mime_type;
|
||||
demuxer_add_attachment(demuxer, name, mime, attachment->file_data);
|
||||
MP_VERBOSE(demuxer, "Attachment: %.*s, %.*s, %zu bytes\n",
|
||||
BSTR_P(name), BSTR_P(mime), attachment->file_data.len);
|
||||
BSTR_P(name), BSTR_P(mime), attachment->file_data.len);
|
||||
}
|
||||
|
||||
talloc_free(parse_ctx.talloc_ctx);
|
||||
@@ -1227,11 +1241,6 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
}
|
||||
}
|
||||
|
||||
if (extradata_size > 0x1000000) {
|
||||
MP_WARN(demuxer, "Invalid CodecPrivate\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
sh = new_sh_stream(demuxer, STREAM_VIDEO);
|
||||
if (!sh)
|
||||
return 1;
|
||||
@@ -1456,8 +1465,6 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
if (track->private_size == 0 || track->ms_compat && !sh_a->wf->cbSize)
|
||||
goto error;
|
||||
if (!track->ms_compat) {
|
||||
if (track->private_size > 0x1000000)
|
||||
goto error;
|
||||
sh_a->wf->cbSize = track->private_size;
|
||||
sh_a->wf = talloc_realloc_size(sh_a, sh_a->wf,
|
||||
sizeof(*sh_a->wf) + sh_a->wf->cbSize);
|
||||
@@ -1492,18 +1499,10 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
src += RAPROPERTIES5_SIZE;
|
||||
}
|
||||
|
||||
if (track->audiopk_size == 0 || track->sub_packet_size == 0 ||
|
||||
track->sub_packet_h == 0 || track->coded_framesize == 0)
|
||||
goto error;
|
||||
if (track->coded_framesize > 0x40000000)
|
||||
goto error;
|
||||
|
||||
src += 3;
|
||||
if (version == 5)
|
||||
src++;
|
||||
codecdata_length = AV_RB32(src);
|
||||
if (codecdata_length < 0 || codecdata_length > 0x1000000)
|
||||
goto error;
|
||||
src += 4;
|
||||
sh_a->wf->cbSize = codecdata_length;
|
||||
sh_a->wf = talloc_realloc_size(sh_a, sh_a->wf,
|
||||
@@ -1528,9 +1527,9 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
sh_a->wf->nBlockAlign = track->coded_framesize;
|
||||
audiobuf:
|
||||
track->audio_buf =
|
||||
talloc_array_size(track, track->sub_packet_h, track->audiopk_size);
|
||||
talloc_size(demuxer, track->sub_packet_h * track->audiopk_size);
|
||||
track->audio_timestamp =
|
||||
talloc_array(track, double, track->sub_packet_h);
|
||||
talloc_size(demuxer, track->sub_packet_h * sizeof(double));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1632,9 +1631,6 @@ static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track)
|
||||
}
|
||||
}
|
||||
|
||||
if (track->private_size > 0x10000000)
|
||||
return 1;
|
||||
|
||||
bstr in = (bstr){track->private_data, track->private_size};
|
||||
struct sh_stream *sh = new_sh_stream(demuxer, STREAM_SUB);
|
||||
if (!sh)
|
||||
@@ -1658,8 +1654,10 @@ static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track)
|
||||
sh->title = talloc_strdup(sh, track->name);
|
||||
sh->default_track = track->default_track;
|
||||
|
||||
if (!subtitle_type)
|
||||
MP_ERR(demuxer, "Subtitle type '%s' is not supported.\n", track->codec_id);
|
||||
if (!subtitle_type) {
|
||||
MP_ERR(demuxer, "Subtitle type '%s' is not supported.\n",
|
||||
track->codec_id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1676,7 +1674,7 @@ static int read_ebml_header(demuxer_t *demuxer)
|
||||
return 0;
|
||||
if (ebml_master.doc_type.start == NULL) {
|
||||
MP_VERBOSE(demuxer, "File has EBML header but no doctype."
|
||||
" Assuming \"matroska\".\n");
|
||||
" Assuming \"matroska\".\n");
|
||||
} else if (bstrcmp(ebml_master.doc_type, bstr0("matroska")) != 0
|
||||
&& bstrcmp(ebml_master.doc_type, bstr0("webm")) != 0) {
|
||||
MP_DBG(demuxer, "no head found\n");
|
||||
@@ -1685,8 +1683,8 @@ static int read_ebml_header(demuxer_t *demuxer)
|
||||
}
|
||||
if (ebml_master.doc_type_read_version > 2) {
|
||||
MP_WARN(demuxer, "This looks like a Matroska file, "
|
||||
"but we don't support format version %"PRIu64"\n",
|
||||
ebml_master.doc_type_read_version);
|
||||
"but we don't support format version %"PRIu64"\n",
|
||||
ebml_master.doc_type_read_version);
|
||||
talloc_free(parse_ctx.talloc_ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -1698,7 +1696,7 @@ static int read_ebml_header(demuxer_t *demuxer)
|
||||
&& ebml_master.ebml_max_id_length != 4))
|
||||
{
|
||||
MP_WARN(demuxer, "This looks like a Matroska file, "
|
||||
"but the header has bad parameters\n");
|
||||
"but the header has bad parameters\n");
|
||||
talloc_free(parse_ctx.talloc_ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -1984,88 +1982,71 @@ static void handle_realvideo(demuxer_t *demuxer, mkv_track_t *track,
|
||||
dp->pos = mkv_d->last_filepos;
|
||||
dp->keyframe = keyframe;
|
||||
|
||||
demux_add_packet(track->stream, dp);
|
||||
demuxer_add_packet(demuxer, track->stream, dp);
|
||||
}
|
||||
|
||||
static void handle_realaudio(demuxer_t *demuxer, mkv_track_t *track,
|
||||
bstr data, bool keyframe)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
|
||||
uint16_t sps = track->sub_packet_size;
|
||||
uint16_t sph = track->sub_packet_h;
|
||||
uint32_t cfs = track->coded_framesize; // restricted to [1,0x40000000]
|
||||
uint16_t w = track->audiopk_size;
|
||||
uint16_t spc = track->sub_packet_cnt;
|
||||
int sps = track->sub_packet_size;
|
||||
int sph = track->sub_packet_h;
|
||||
int cfs = track->coded_framesize;
|
||||
int w = track->audiopk_size;
|
||||
int spc = track->sub_packet_cnt;
|
||||
uint8_t *buffer = data.start;
|
||||
uint32_t size = data.len;
|
||||
demux_packet_t *dp;
|
||||
|
||||
// track->audio_buf allocation size
|
||||
size_t audiobuf_size = sph * w;
|
||||
|
||||
if ((track->a_formattag == MP_FOURCC('2', '8', '_', '8'))
|
||||
|| (track->a_formattag == MP_FOURCC('c', 'o', 'o', 'k'))
|
||||
|| (track->a_formattag == MP_FOURCC('a', 't', 'r', 'c'))
|
||||
|| (track->a_formattag == MP_FOURCC('s', 'i', 'p', 'r')))
|
||||
{
|
||||
|| (track->a_formattag == MP_FOURCC('s', 'i', 'p', 'r'))) {
|
||||
// if(!block_bref)
|
||||
// spc = track->sub_packet_cnt = 0;
|
||||
switch (track->a_formattag) {
|
||||
case MP_FOURCC('2', '8', '_', '8'):
|
||||
for (int x = 0; x < sph / 2; x++) {
|
||||
uint64_t dst_offset = x * 2 * w + spc * (uint64_t)cfs;
|
||||
if (dst_offset + cfs > audiobuf_size)
|
||||
goto error;
|
||||
uint64_t src_offset = x * (uint64_t)cfs;
|
||||
if (src_offset + cfs > size)
|
||||
goto error;
|
||||
memcpy(track->audio_buf + dst_offset, buffer + src_offset, cfs);
|
||||
}
|
||||
for (int x = 0; x < sph / 2; x++)
|
||||
memcpy(track->audio_buf + x * 2 * w + spc * cfs,
|
||||
buffer + cfs * x, cfs);
|
||||
break;
|
||||
case MP_FOURCC('c', 'o', 'o', 'k'):
|
||||
case MP_FOURCC('a', 't', 'r', 'c'):
|
||||
for (int x = 0; x < w / sps; x++) {
|
||||
uint32_t dst_offset = sps * (sph * x + ((sph + 1) / 2) * (spc & 1)
|
||||
+ (spc >> 1));
|
||||
if (dst_offset + sps > audiobuf_size)
|
||||
goto error;
|
||||
uint32_t src_offset = sps * x;
|
||||
if (src_offset + sps > size)
|
||||
goto error;
|
||||
memcpy(track->audio_buf + dst_offset, buffer + src_offset, sps);
|
||||
}
|
||||
for (int x = 0; x < w / sps; x++)
|
||||
memcpy(track->audio_buf +
|
||||
sps * (sph * x + ((sph + 1) / 2) * (spc & 1) +
|
||||
(spc >> 1)), buffer + sps * x, sps);
|
||||
break;
|
||||
case MP_FOURCC('s', 'i', 'p', 'r'):
|
||||
if (spc * w + w > audiobuf_size || w > size)
|
||||
goto error;
|
||||
memcpy(track->audio_buf + spc * w, buffer, w);
|
||||
if (spc == sph - 1) {
|
||||
int n;
|
||||
int bs = sph * w * 2 / 96; // nibbles per subpacket
|
||||
// Perform reordering
|
||||
for (n = 0; n < 38; n++) {
|
||||
unsigned int i = bs * sipr_swaps[n][0]; // 77 max
|
||||
unsigned int o = bs * sipr_swaps[n][1]; // 95 max
|
||||
// swap nibbles of block 'i' with 'o'
|
||||
for (int j = 0; j < bs; j++) {
|
||||
if (i / 2 >= audiobuf_size || o / 2 >= audiobuf_size)
|
||||
goto error;
|
||||
int j;
|
||||
int i = bs * sipr_swaps[n][0];
|
||||
int o = bs * sipr_swaps[n][1];
|
||||
// swap nibbles of block 'i' with 'o' TODO: optimize
|
||||
for (j = 0; j < bs; j++) {
|
||||
int x = (i & 1) ?
|
||||
(track->audio_buf[i / 2] >> 4) :
|
||||
(track->audio_buf[i / 2] & 0x0F);
|
||||
(track->audio_buf[i >> 1] >> 4) :
|
||||
(track->audio_buf[i >> 1] & 0x0F);
|
||||
int y = (o & 1) ?
|
||||
(track->audio_buf[o / 2] >> 4) :
|
||||
(track->audio_buf[o / 2] & 0x0F);
|
||||
(track->audio_buf[o >> 1] >> 4) :
|
||||
(track->audio_buf[o >> 1] & 0x0F);
|
||||
if (o & 1)
|
||||
track->audio_buf[o / 2] =
|
||||
(track->audio_buf[o / 2] & 0x0F) | (x << 4);
|
||||
track->audio_buf[o >> 1] =
|
||||
(track->audio_buf[o >> 1] & 0x0F) | (x << 4);
|
||||
else
|
||||
track->audio_buf[o / 2] =
|
||||
(track->audio_buf[o / 2] & 0xF0) | x;
|
||||
track->audio_buf[o >> 1] =
|
||||
(track->audio_buf[o >> 1] & 0xF0) | x;
|
||||
if (i & 1)
|
||||
track->audio_buf[i / 2] =
|
||||
(track->audio_buf[i / 2] & 0x0F) | (y << 4);
|
||||
track->audio_buf[i >> 1] =
|
||||
(track->audio_buf[i >> 1] & 0x0F) | (y << 4);
|
||||
else
|
||||
track->audio_buf[i / 2] =
|
||||
(track->audio_buf[i / 2] & 0xF0) | y;
|
||||
track->audio_buf[i >> 1] =
|
||||
(track->audio_buf[i >> 1] & 0xF0) | y;
|
||||
++i;
|
||||
++o;
|
||||
}
|
||||
@@ -2079,11 +2060,8 @@ static void handle_realaudio(demuxer_t *demuxer, mkv_track_t *track,
|
||||
if (track->sub_packet_cnt == 0)
|
||||
track->audio_filepos = mkv_d->last_filepos;
|
||||
if (++(track->sub_packet_cnt) == sph) {
|
||||
int apk_usize = track->stream->audio->wf->nBlockAlign;
|
||||
track->sub_packet_cnt = 0;
|
||||
// apk_usize has same range as coded_framesize in worst case
|
||||
uint32_t apk_usize = track->stream->audio->wf->nBlockAlign;
|
||||
if (apk_usize > audiobuf_size)
|
||||
goto error;
|
||||
// Release all the audio packets
|
||||
for (int x = 0; x < sph * w / apk_usize; x++) {
|
||||
dp = new_demux_packet_from(track->audio_buf + x * apk_usize,
|
||||
@@ -2094,7 +2072,7 @@ static void handle_realaudio(demuxer_t *demuxer, mkv_track_t *track,
|
||||
track->audio_timestamp[x * apk_usize / w];
|
||||
dp->pos = track->audio_filepos; // all equal
|
||||
dp->keyframe = !x; // Mark first packet as keyframe
|
||||
demux_add_packet(track->stream, dp);
|
||||
demuxer_add_packet(demuxer, track->stream, dp);
|
||||
}
|
||||
}
|
||||
} else { // Not a codec that requires reordering
|
||||
@@ -2107,11 +2085,8 @@ static void handle_realaudio(demuxer_t *demuxer, mkv_track_t *track,
|
||||
|
||||
dp->pos = mkv_d->last_filepos;
|
||||
dp->keyframe = keyframe;
|
||||
demux_add_packet(track->stream, dp);
|
||||
demuxer_add_packet(demuxer, track->stream, dp);
|
||||
}
|
||||
return;
|
||||
error:
|
||||
MP_ERR(demuxer, "RealAudio decrypting error.\n");
|
||||
}
|
||||
|
||||
static void mkv_seek_reset(demuxer_t *demuxer)
|
||||
@@ -2179,9 +2154,6 @@ static int libav_parse_wavpack(mkv_track_t *track, uint8_t *src,
|
||||
if (blocksize > srclen)
|
||||
goto fail;
|
||||
|
||||
if (dstlen > 0x10000000 || blocksize > 0x10000000)
|
||||
goto fail;
|
||||
|
||||
tmp = talloc_realloc(track->parser_tmp, dst, uint8_t,
|
||||
dstlen + blocksize + 32);
|
||||
if (!tmp)
|
||||
@@ -2251,7 +2223,7 @@ static bool mkv_parse_packet(mkv_track_t *track, bstr *raw, bstr *out)
|
||||
int len = av_parser_parse2(track->av_parser, track->av_parser_codec,
|
||||
&data, &size, raw->start, raw->len,
|
||||
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
|
||||
if (len < 0 || len > 0x10000000)
|
||||
if (len < 0)
|
||||
return false;
|
||||
*raw = bstr_cut(*raw, len);
|
||||
if (size) {
|
||||
@@ -2362,7 +2334,7 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
|
||||
uint32_t lace_size[MAX_NUM_LACES];
|
||||
bool use_this_block = tc >= mkv_d->skip_to_timecode;
|
||||
|
||||
if (!demux_stream_is_selected(stream))
|
||||
if (!demuxer_stream_is_selected(demuxer, stream))
|
||||
return 0;
|
||||
|
||||
if (demux_mkv_read_block_lacing(&data, &laces, lace_size)) {
|
||||
@@ -2426,7 +2398,7 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
|
||||
if (track->ms_compat)
|
||||
MPSWAP(double, dp->pts, dp->dts);
|
||||
dp->duration = block_duration / 1e9;
|
||||
demux_add_packet(stream, dp);
|
||||
demuxer_add_packet(demuxer, stream, dp);
|
||||
}
|
||||
talloc_free_children(track->parser_tmp);
|
||||
}
|
||||
@@ -2668,7 +2640,7 @@ static struct mkv_index *seek_with_cues(struct demuxer *demuxer, int seek_id,
|
||||
min_diff = -min_diff;
|
||||
min_diff = FFMAX(min_diff, 1);
|
||||
|
||||
for (size_t i = 0; i < mkv_d->num_indexes; i++) {
|
||||
for (int i = 0; i < mkv_d->num_indexes; i++) {
|
||||
if (seek_id < 0 || mkv_d->indexes[i].tnum == seek_id) {
|
||||
int64_t diff =
|
||||
target_timecode -
|
||||
@@ -2689,7 +2661,7 @@ static struct mkv_index *seek_with_cues(struct demuxer *demuxer, int seek_id,
|
||||
uint64_t seek_pos = index->filepos;
|
||||
if (flags & SEEK_SUBPREROLL) {
|
||||
uint64_t prev_target = 0;
|
||||
for (size_t i = 0; i < mkv_d->num_indexes; i++) {
|
||||
for (int i = 0; i < mkv_d->num_indexes; i++) {
|
||||
if (seek_id < 0 || mkv_d->indexes[i].tnum == seek_id) {
|
||||
uint64_t index_pos = mkv_d->indexes[i].filepos;
|
||||
if (index_pos > prev_target && index_pos < seek_pos)
|
||||
@@ -2706,7 +2678,7 @@ static struct mkv_index *seek_with_cues(struct demuxer *demuxer, int seek_id,
|
||||
return index;
|
||||
}
|
||||
|
||||
static void demux_mkv_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
static void demux_mkv_seek(demuxer_t *demuxer, float rel_seek_secs, int flags)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
int64_t old_pos = stream_tell(demuxer->stream);
|
||||
@@ -2716,7 +2688,7 @@ static void demux_mkv_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
mkv_seek_reset(demuxer);
|
||||
for (int i = 0; i < mkv_d->num_tracks; i++) {
|
||||
mkv_track_t *track = mkv_d->tracks[i];
|
||||
if (demux_stream_is_selected(track->stream)) {
|
||||
if (demuxer_stream_is_selected(demuxer, track->stream)) {
|
||||
st_active[track->stream->type] = true;
|
||||
if (track->type == MATROSKA_TRACK_VIDEO)
|
||||
v_tnum = track->tnum;
|
||||
@@ -2773,6 +2745,7 @@ static void demux_mkv_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
stream_t *s = demuxer->stream;
|
||||
uint64_t target_filepos;
|
||||
mkv_index_t *index = NULL;
|
||||
int i;
|
||||
|
||||
read_deferred_cues(demuxer);
|
||||
|
||||
@@ -2786,7 +2759,7 @@ static void demux_mkv_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
stream_control(s, STREAM_CTRL_GET_SIZE, &size);
|
||||
|
||||
target_filepos = (uint64_t) (size * rel_seek_secs);
|
||||
for (size_t i = 0; i < mkv_d->num_indexes; i++)
|
||||
for (i = 0; i < mkv_d->num_indexes; i++)
|
||||
if (mkv_d->indexes[i].tnum == v_tnum)
|
||||
if ((index == NULL)
|
||||
|| ((mkv_d->indexes[i].filepos >= target_filepos)
|
||||
@@ -2834,6 +2807,7 @@ static void mkv_free(struct demuxer *demuxer)
|
||||
mkv_seek_reset(demuxer);
|
||||
for (int i = 0; i < mkv_d->num_tracks; i++)
|
||||
demux_mkv_free_trackentry(mkv_d->tracks[i]);
|
||||
free(mkv_d->indexes);
|
||||
}
|
||||
|
||||
const demuxer_desc_t demuxer_desc_matroska = {
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <strings.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "options/options.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
+2
-2
@@ -232,12 +232,12 @@ static int raw_fill_buffer(demuxer_t *demuxer)
|
||||
|
||||
int len = stream_read(demuxer->stream, dp->buffer, dp->len);
|
||||
demux_packet_shorten(dp, len);
|
||||
demux_add_packet(demuxer->streams[0], dp);
|
||||
demuxer_add_packet(demuxer, demuxer->streams[0], dp);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void raw_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
static void raw_seek(demuxer_t *demuxer, float rel_seek_secs, int flags)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
stream_t *s = demuxer->stream;
|
||||
|
||||
+3
-51
@@ -26,6 +26,7 @@
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/avstring.h>
|
||||
@@ -111,55 +112,6 @@ struct readline_args {
|
||||
/* Maximal length of line of a subtitle */
|
||||
#define LINE_LEN 1000
|
||||
|
||||
static double demux_packet_list_duration(struct demux_packet **pkts, int num_pkts)
|
||||
{
|
||||
if (num_pkts > 0)
|
||||
return pkts[num_pkts - 1]->pts + pkts[num_pkts - 1]->duration;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void demux_packet_list_seek(struct demux_packet **pkts, int num_pkts,
|
||||
int *current, double rel_seek_secs, int flags)
|
||||
{
|
||||
double ref_time = 0;
|
||||
if (*current >= 0 && *current < num_pkts) {
|
||||
ref_time = pkts[*current]->pts;
|
||||
} else if (*current == num_pkts && num_pkts > 0) {
|
||||
ref_time = pkts[num_pkts - 1]->pts + pkts[num_pkts - 1]->duration;
|
||||
}
|
||||
|
||||
if (flags & SEEK_ABSOLUTE)
|
||||
ref_time = 0;
|
||||
|
||||
if (flags & SEEK_FACTOR) {
|
||||
ref_time += demux_packet_list_duration(pkts, num_pkts) * rel_seek_secs;
|
||||
} else {
|
||||
ref_time += rel_seek_secs;
|
||||
}
|
||||
|
||||
// Could do binary search, but it's probably not worth the complexity.
|
||||
int last_index = 0;
|
||||
for (int n = 0; n < num_pkts; n++) {
|
||||
if (pkts[n]->pts > ref_time)
|
||||
break;
|
||||
last_index = n;
|
||||
}
|
||||
*current = last_index;
|
||||
}
|
||||
|
||||
static struct demux_packet *demux_packet_list_fill(struct demux_packet **pkts,
|
||||
int num_pkts, int *current)
|
||||
{
|
||||
if (*current < 0)
|
||||
*current = 0;
|
||||
if (*current >= num_pkts)
|
||||
return NULL;
|
||||
struct demux_packet *new = talloc(NULL, struct demux_packet);
|
||||
*new = *pkts[*current];
|
||||
*current += 1;
|
||||
return new;
|
||||
}
|
||||
|
||||
static int eol(char p) {
|
||||
return p=='\r' || p=='\n' || p=='\0';
|
||||
}
|
||||
@@ -800,10 +752,10 @@ static int d_fill_buffer(struct demuxer *demuxer)
|
||||
struct priv *p = demuxer->priv;
|
||||
struct demux_packet *dp = demux_packet_list_fill(p->pkts, p->num_pkts,
|
||||
&p->current);
|
||||
return demux_add_packet(p->sh, dp);
|
||||
return demuxer_add_packet(demuxer, p->sh, dp);
|
||||
}
|
||||
|
||||
static void d_seek(struct demuxer *demuxer, double secs, int flags)
|
||||
static void d_seek(struct demuxer *demuxer, float secs, int flags)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
demux_packet_list_seek(p->pkts, p->num_pkts, &p->current, secs, flags);
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/options.h"
|
||||
|
||||
#include "demux.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "video/img_fourcc.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
#include "stream/tv.h"
|
||||
|
||||
static int demux_open_tv(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
tvi_handle_t *tvh;
|
||||
sh_video_t *sh_video;
|
||||
sh_audio_t *sh_audio = NULL;
|
||||
const tvi_functions_t *funcs;
|
||||
|
||||
if (check > DEMUX_CHECK_REQUEST || demuxer->stream->type != STREAMTYPE_TV)
|
||||
return -1;
|
||||
|
||||
tv_param_t *params = m_sub_options_copy(demuxer, &tv_params_conf,
|
||||
demuxer->opts->tv_params);
|
||||
struct tv_stream_params *sparams = demuxer->stream->priv;
|
||||
if (sparams->channel && sparams->channel[0]) {
|
||||
talloc_free(params->channel);
|
||||
params->channel = talloc_strdup(NULL, sparams->channel);
|
||||
}
|
||||
if (sparams->input >= 0)
|
||||
params->input = sparams->input;
|
||||
|
||||
assert(demuxer->priv==NULL);
|
||||
if(!(tvh=tv_begin(params, demuxer->log))) return -1;
|
||||
if (!tvh->functions->init(tvh->priv)) return -1;
|
||||
|
||||
tvh->demuxer = demuxer;
|
||||
|
||||
if (!open_tv(tvh)){
|
||||
tv_uninit(tvh);
|
||||
return -1;
|
||||
}
|
||||
funcs = tvh->functions;
|
||||
demuxer->priv=tvh;
|
||||
|
||||
struct sh_stream *sh_v = new_sh_stream(demuxer, STREAM_VIDEO);
|
||||
sh_video = sh_v->video;
|
||||
|
||||
/* get IMAGE FORMAT */
|
||||
int fourcc;
|
||||
funcs->control(tvh->priv, TVI_CONTROL_VID_GET_FORMAT, &fourcc);
|
||||
if (fourcc == MP_FOURCC_MJPEG) {
|
||||
sh_v->codec = "mjpeg";
|
||||
} else {
|
||||
sh_v->codec = "rawvideo";
|
||||
sh_v->format = fourcc;
|
||||
}
|
||||
|
||||
/* set FPS and FRAMETIME */
|
||||
|
||||
if(!sh_video->fps)
|
||||
{
|
||||
float tmp;
|
||||
if (funcs->control(tvh->priv, TVI_CONTROL_VID_GET_FPS, &tmp) != TVI_CONTROL_TRUE)
|
||||
sh_video->fps = 25.0f; /* on PAL */
|
||||
else sh_video->fps = tmp;
|
||||
}
|
||||
|
||||
if (tvh->tv_param->fps != -1.0f)
|
||||
sh_video->fps = tvh->tv_param->fps;
|
||||
|
||||
/* If playback only mode, go to immediate mode, fail silently */
|
||||
if(tvh->tv_param->immediate == 1)
|
||||
{
|
||||
funcs->control(tvh->priv, TVI_CONTROL_IMMEDIATE, 0);
|
||||
tvh->tv_param->audio = 0;
|
||||
}
|
||||
|
||||
/* set width */
|
||||
funcs->control(tvh->priv, TVI_CONTROL_VID_GET_WIDTH, &sh_video->disp_w);
|
||||
|
||||
/* set height */
|
||||
funcs->control(tvh->priv, TVI_CONTROL_VID_GET_HEIGHT, &sh_video->disp_h);
|
||||
|
||||
demuxer->seekable = 0;
|
||||
|
||||
/* here comes audio init */
|
||||
if (tvh->tv_param->audio && funcs->control(tvh->priv, TVI_CONTROL_IS_AUDIO, 0) == TVI_CONTROL_TRUE)
|
||||
{
|
||||
int audio_format;
|
||||
|
||||
/* yeah, audio is present */
|
||||
|
||||
funcs->control(tvh->priv, TVI_CONTROL_AUD_SET_SAMPLERATE,
|
||||
&tvh->tv_param->audiorate);
|
||||
|
||||
if (funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_FORMAT, &audio_format) != TVI_CONTROL_TRUE)
|
||||
goto no_audio;
|
||||
|
||||
switch(audio_format)
|
||||
{
|
||||
case AF_FORMAT_U8:
|
||||
case AF_FORMAT_S8:
|
||||
case AF_FORMAT_U16_LE:
|
||||
case AF_FORMAT_U16_BE:
|
||||
case AF_FORMAT_S16_LE:
|
||||
case AF_FORMAT_S16_BE:
|
||||
case AF_FORMAT_S32_LE:
|
||||
case AF_FORMAT_S32_BE:
|
||||
break;
|
||||
case AF_FORMAT_MPEG2:
|
||||
default:
|
||||
MP_ERR(tvh, "Audio type '%s' unsupported!\n",
|
||||
af_fmt_to_str(audio_format));
|
||||
goto no_audio;
|
||||
}
|
||||
|
||||
struct sh_stream *sh_a = new_sh_stream(demuxer, STREAM_AUDIO);
|
||||
sh_audio = sh_a->audio;
|
||||
|
||||
funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_SAMPLERATE,
|
||||
&sh_audio->samplerate);
|
||||
int nchannels = sh_audio->channels.num;
|
||||
funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_CHANNELS,
|
||||
&nchannels);
|
||||
mp_chmap_from_channels(&sh_audio->channels, nchannels);
|
||||
|
||||
sh_a->codec = "mp-pcm";
|
||||
sh_a->format = audio_format;
|
||||
|
||||
int samplesize = af_fmt2bps(audio_format);
|
||||
int block_align = samplesize * sh_audio->channels.num;
|
||||
int bytes_per_second = sh_audio->samplerate * block_align;
|
||||
|
||||
sh_audio->bitrate = bytes_per_second * 8;
|
||||
|
||||
// emulate WF for win32 codecs:
|
||||
sh_audio->wf = talloc_zero(sh_audio, MP_WAVEFORMATEX);
|
||||
sh_audio->wf->wFormatTag = sh_a->format;
|
||||
sh_audio->wf->nChannels = sh_audio->channels.num;
|
||||
sh_audio->wf->wBitsPerSample = samplesize * 8;
|
||||
sh_audio->wf->nSamplesPerSec = sh_audio->samplerate;
|
||||
sh_audio->wf->nBlockAlign = block_align;
|
||||
sh_audio->wf->nAvgBytesPerSec = bytes_per_second;
|
||||
|
||||
MP_VERBOSE(tvh, " TV audio: %d channels, %d bits, %d Hz\n",
|
||||
sh_audio->wf->nChannels, sh_audio->wf->wBitsPerSample,
|
||||
sh_audio->wf->nSamplesPerSec);
|
||||
}
|
||||
no_audio:
|
||||
|
||||
if(!(funcs->start(tvh->priv))){
|
||||
// start failed :(
|
||||
tv_uninit(tvh);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* set color eq */
|
||||
tv_set_color_options(tvh, TV_COLOR_BRIGHTNESS, tvh->tv_param->brightness);
|
||||
tv_set_color_options(tvh, TV_COLOR_HUE, tvh->tv_param->hue);
|
||||
tv_set_color_options(tvh, TV_COLOR_SATURATION, tvh->tv_param->saturation);
|
||||
tv_set_color_options(tvh, TV_COLOR_CONTRAST, tvh->tv_param->contrast);
|
||||
|
||||
if(tvh->tv_param->gain!=-1)
|
||||
if(funcs->control(tvh->priv,TVI_CONTROL_VID_SET_GAIN,&tvh->tv_param->gain)!=TVI_CONTROL_TRUE)
|
||||
MP_WARN(tvh, "Unable to set gain control!\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void demux_close_tv(demuxer_t *demuxer)
|
||||
{
|
||||
tvi_handle_t *tvh=(tvi_handle_t*)(demuxer->priv);
|
||||
if (!tvh) return;
|
||||
tv_uninit(tvh);
|
||||
free(tvh);
|
||||
demuxer->priv=NULL;
|
||||
}
|
||||
|
||||
static int demux_tv_fill_buffer(demuxer_t *demux)
|
||||
{
|
||||
tvi_handle_t *tvh=(tvi_handle_t*)(demux->priv);
|
||||
demux_packet_t* dp;
|
||||
unsigned int len=0;
|
||||
struct sh_stream *want_audio = NULL, *want_video = NULL;
|
||||
|
||||
for (int n = 0; n < demux->num_streams; n++) {
|
||||
struct sh_stream *sh = demux->streams[n];
|
||||
if (!demux_has_packet(sh) && demux_stream_is_selected(sh)) {
|
||||
if (sh->type == STREAM_AUDIO)
|
||||
want_audio = sh;
|
||||
if (sh->type == STREAM_VIDEO)
|
||||
want_video = sh;
|
||||
}
|
||||
}
|
||||
|
||||
/* ================== ADD AUDIO PACKET =================== */
|
||||
|
||||
if (want_audio && tvh->tv_param->audio &&
|
||||
tvh->functions->control(tvh->priv,
|
||||
TVI_CONTROL_IS_AUDIO, 0) == TVI_CONTROL_TRUE)
|
||||
{
|
||||
len = tvh->functions->get_audio_framesize(tvh->priv);
|
||||
|
||||
dp=new_demux_packet(len);
|
||||
dp->keyframe = true;
|
||||
dp->pts=tvh->functions->grab_audio_frame(tvh->priv, dp->buffer,len);
|
||||
demux_add_packet(want_audio, dp);
|
||||
}
|
||||
|
||||
/* ================== ADD VIDEO PACKET =================== */
|
||||
|
||||
if (want_video && tvh->functions->control(tvh->priv,
|
||||
TVI_CONTROL_IS_VIDEO, 0) == TVI_CONTROL_TRUE)
|
||||
{
|
||||
len = tvh->functions->get_video_framesize(tvh->priv);
|
||||
dp=new_demux_packet(len);
|
||||
dp->keyframe = true;
|
||||
dp->pts=tvh->functions->grab_video_frame(tvh->priv, dp->buffer, len);
|
||||
demux_add_packet(want_video, dp);
|
||||
}
|
||||
|
||||
if (tvh->tv_param->scan) tv_scan(tvh);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int demux_tv_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
{
|
||||
tvi_handle_t *tvh=(tvi_handle_t*)(demuxer->priv);
|
||||
if (cmd != DEMUXER_CTRL_STREAM_CTRL)
|
||||
return DEMUXER_CTRL_NOTIMPL;
|
||||
struct demux_ctrl_stream_ctrl *ctrl = arg;
|
||||
ctrl->res = tv_stream_control(tvh, ctrl->ctrl, ctrl->arg);
|
||||
return DEMUXER_CTRL_OK;
|
||||
}
|
||||
|
||||
const demuxer_desc_t demuxer_desc_tv = {
|
||||
.name = "tv",
|
||||
.desc = "TV card demuxer",
|
||||
.fill_buffer = demux_tv_fill_buffer,
|
||||
.control = demux_tv_control,
|
||||
.open = demux_open_tv,
|
||||
.close = demux_close_tv,
|
||||
};
|
||||
+17
-15
@@ -37,6 +37,10 @@
|
||||
#include "compat/mpbswap.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#ifndef SIZE_MAX
|
||||
#define SIZE_MAX ((size_t)-1)
|
||||
#endif
|
||||
|
||||
// Whether the id is a known Matroska level 1 element (allowed as element on
|
||||
// global file level, after the level 0 MATROSKA_ID_SEGMENT).
|
||||
// This (intentionally) doesn't include "global" elements.
|
||||
@@ -360,7 +364,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
char *s = target;
|
||||
uint8_t *end = data + size;
|
||||
uint8_t *p = data;
|
||||
int num_elems[MAX_EBML_SUBELEMENTS] = {0};
|
||||
int num_elems[MAX_EBML_SUBELEMENTS] = {};
|
||||
while (p < end) {
|
||||
uint8_t *startp = p;
|
||||
int len;
|
||||
@@ -386,10 +390,6 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
if (type->fields[i].id == id) {
|
||||
field_idx = i;
|
||||
num_elems[i]++;
|
||||
if (num_elems[i] >= 0x70000000) {
|
||||
MP_ERR(ctx, "Too many EBML subelements.\n");
|
||||
goto other_error;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -409,7 +409,8 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
continue;
|
||||
|
||||
past_end_error:
|
||||
MP_DBG(ctx, "Subelement headers go past end of containing element\n");
|
||||
MP_DBG(ctx, "Subelement headers go "
|
||||
"past end of containing element\n");
|
||||
other_error:
|
||||
ctx->has_errors = true;
|
||||
end = startp;
|
||||
@@ -531,7 +532,8 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
subelptr = (fieldtype *) fieldptr
|
||||
GETPTR(uintptr, uint64_t);
|
||||
if (length < 1 || length > 8) {
|
||||
MP_DBG(ctx, "uint invalid length %"PRIu64"\n", length);
|
||||
MP_DBG(ctx, "uint invalid length %"PRIu64
|
||||
"\n", length);
|
||||
goto error;
|
||||
}
|
||||
*uintptr = ebml_parse_uint(data, length);
|
||||
@@ -542,7 +544,8 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
int64_t *sintptr;
|
||||
GETPTR(sintptr, int64_t);
|
||||
if (length < 1 || length > 8) {
|
||||
MP_DBG(ctx, "sint invalid length %"PRIu64"\n", length);
|
||||
MP_DBG(ctx, "sint invalid length %"PRIu64
|
||||
"\n", length);
|
||||
goto error;
|
||||
}
|
||||
*sintptr = ebml_parse_sint(data, length);
|
||||
@@ -553,7 +556,8 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
double *floatptr;
|
||||
GETPTR(floatptr, double);
|
||||
if (length != 4 && length != 8) {
|
||||
MP_DBG(ctx, "float invalid length %"PRIu64"\n", length);
|
||||
MP_DBG(ctx, "float invalid length %"PRIu64
|
||||
"\n", length);
|
||||
goto error;
|
||||
}
|
||||
*floatptr = ebml_parse_float(data, length);
|
||||
@@ -562,18 +566,16 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
|
||||
case EBML_TYPE_STR:
|
||||
case EBML_TYPE_BINARY:;
|
||||
if (length > 0x80000000) {
|
||||
MP_ERR(ctx, "Not reading overly long EBML element.\n");
|
||||
break;
|
||||
}
|
||||
struct bstr *strptr;
|
||||
GETPTR(strptr, struct bstr);
|
||||
strptr->start = data;
|
||||
strptr->len = length;
|
||||
if (ed->type == EBML_TYPE_STR)
|
||||
MP_DBG(ctx, "string \"%.*s\"\n", BSTR_P(*strptr));
|
||||
MP_DBG(ctx, "string \"%.*s\"\n",
|
||||
BSTR_P(*strptr));
|
||||
else
|
||||
MP_DBG(ctx, "binary %zd bytes\n", strptr->len);
|
||||
MP_DBG(ctx, "binary %zd bytes\n",
|
||||
strptr->len);
|
||||
break;
|
||||
|
||||
case EBML_TYPE_EBML_ID:;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
struct mp_log;
|
||||
|
||||
|
||||
/* EBML version supported */
|
||||
#define EBML_VERSION 1
|
||||
|
||||
@@ -68,6 +69,7 @@ struct ebml_parse_ctx {
|
||||
|
||||
#define EBML_ID_INVALID 0xffffffff
|
||||
|
||||
|
||||
/* matroska track types */
|
||||
#define MATROSKA_TRACK_VIDEO 0x01 /* rectangle-shaped pictures aka video */
|
||||
#define MATROSKA_TRACK_AUDIO 0x02 /* anything you can hear */
|
||||
@@ -76,8 +78,18 @@ struct ebml_parse_ctx {
|
||||
#define MATROSKA_TRACK_SUBTITLE 0x11 /* text-subtitles */
|
||||
#define MATROSKA_TRACK_CONTROL 0x20 /* control-codes for menu or other stuff*/
|
||||
|
||||
#ifndef UINT64_MAX
|
||||
#define UINT64_MAX 18446744073709551615ULL
|
||||
#endif
|
||||
|
||||
#ifndef INT64_MAX
|
||||
#define INT64_MAX 9223372036854775807LL
|
||||
#endif
|
||||
|
||||
#define EBML_UINT_INVALID UINT64_MAX
|
||||
#define EBML_INT_INVALID INT64_MAX
|
||||
#define EBML_FLOAT_INVALID -1000000000.0
|
||||
|
||||
|
||||
bool ebml_is_mkv_level1_id(uint32_t id);
|
||||
uint32_t ebml_read_id (stream_t *s);
|
||||
|
||||
+2
-2
@@ -16,6 +16,7 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -29,7 +30,6 @@
|
||||
#include "talloc.h"
|
||||
#include "common/msg.h"
|
||||
#include "stream/stream.h"
|
||||
#include "misc/ctype.h"
|
||||
#include "options/path.h"
|
||||
|
||||
#include "mf.h"
|
||||
@@ -55,7 +55,7 @@ mf_t *open_mf_pattern(void *talloc_ctx, struct mp_log *log, char *filename)
|
||||
while (fgets(fname, 512, lst_f)) {
|
||||
/* remove spaces from end of fname */
|
||||
char *t = fname + strlen(fname) - 1;
|
||||
while (t > fname && mp_isspace(*t))
|
||||
while (t > fname && isspace((unsigned char)*t))
|
||||
*(t--) = 0;
|
||||
if (!mp_path_exists(fname)) {
|
||||
mp_verbose(log, "file not found: '%s'\n", fname);
|
||||
|
||||
-122
@@ -1,122 +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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include "packet.h"
|
||||
|
||||
static void packet_destroy(void *ptr)
|
||||
{
|
||||
struct demux_packet *dp = ptr;
|
||||
talloc_free(dp->avpacket);
|
||||
av_free(dp->allocation);
|
||||
}
|
||||
|
||||
static struct demux_packet *create_packet(size_t len)
|
||||
{
|
||||
if (len > 1000000000) {
|
||||
fprintf(stderr, "Attempt to allocate demux packet over 1 GB!\n");
|
||||
abort();
|
||||
}
|
||||
struct demux_packet *dp = talloc(NULL, struct demux_packet);
|
||||
talloc_set_destructor(dp, packet_destroy);
|
||||
*dp = (struct demux_packet) {
|
||||
.len = len,
|
||||
.pts = MP_NOPTS_VALUE,
|
||||
.dts = MP_NOPTS_VALUE,
|
||||
.duration = -1,
|
||||
.pos = -1,
|
||||
.stream = -1,
|
||||
};
|
||||
return dp;
|
||||
}
|
||||
|
||||
struct demux_packet *new_demux_packet(size_t len)
|
||||
{
|
||||
struct demux_packet *dp = create_packet(len);
|
||||
dp->buffer = av_malloc(len + FF_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (!dp->buffer) {
|
||||
fprintf(stderr, "Memory allocation failure!\n");
|
||||
abort();
|
||||
}
|
||||
memset(dp->buffer + len, 0, FF_INPUT_BUFFER_PADDING_SIZE);
|
||||
dp->allocation = dp->buffer;
|
||||
return dp;
|
||||
}
|
||||
|
||||
// data must already have suitable padding, and does not copy the data
|
||||
struct demux_packet *new_demux_packet_fromdata(void *data, size_t len)
|
||||
{
|
||||
struct demux_packet *dp = create_packet(len);
|
||||
dp->buffer = data;
|
||||
return dp;
|
||||
}
|
||||
|
||||
struct demux_packet *new_demux_packet_from(void *data, size_t len)
|
||||
{
|
||||
struct demux_packet *dp = new_demux_packet(len);
|
||||
memcpy(dp->buffer, data, len);
|
||||
return dp;
|
||||
}
|
||||
|
||||
void demux_packet_shorten(struct demux_packet *dp, size_t len)
|
||||
{
|
||||
assert(len <= dp->len);
|
||||
dp->len = len;
|
||||
memset(dp->buffer + dp->len, 0, FF_INPUT_BUFFER_PADDING_SIZE);
|
||||
}
|
||||
|
||||
void free_demux_packet(struct demux_packet *dp)
|
||||
{
|
||||
talloc_free(dp);
|
||||
}
|
||||
|
||||
static void destroy_avpacket(void *pkt)
|
||||
{
|
||||
av_free_packet(pkt);
|
||||
}
|
||||
|
||||
struct demux_packet *demux_copy_packet(struct demux_packet *dp)
|
||||
{
|
||||
struct demux_packet *new = NULL;
|
||||
if (dp->avpacket) {
|
||||
assert(dp->buffer == dp->avpacket->data);
|
||||
assert(dp->len == dp->avpacket->size);
|
||||
AVPacket *newavp = talloc_zero(NULL, AVPacket);
|
||||
talloc_set_destructor(newavp, destroy_avpacket);
|
||||
av_init_packet(newavp);
|
||||
if (av_packet_ref(newavp, dp->avpacket) < 0)
|
||||
abort();
|
||||
new = new_demux_packet_fromdata(newavp->data, newavp->size);
|
||||
new->avpacket = newavp;
|
||||
}
|
||||
if (!new) {
|
||||
new = new_demux_packet(dp->len);
|
||||
memcpy(new->buffer, dp->buffer, new->len);
|
||||
}
|
||||
new->pts = dp->pts;
|
||||
new->dts = dp->dts;
|
||||
new->duration = dp->duration;
|
||||
return new;
|
||||
}
|
||||
+1
-9
@@ -20,7 +20,6 @@
|
||||
#define MPLAYER_DEMUX_PACKET_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
// Holds one packet/frame/whatever
|
||||
@@ -29,6 +28,7 @@ typedef struct demux_packet {
|
||||
double pts;
|
||||
double dts;
|
||||
double duration;
|
||||
double stream_pts;
|
||||
int64_t pos; // position in source file byte stream
|
||||
unsigned char *buffer;
|
||||
bool keyframe;
|
||||
@@ -38,12 +38,4 @@ typedef struct demux_packet {
|
||||
struct AVPacket *avpacket; // original libavformat packet (demux_lavf)
|
||||
} demux_packet_t;
|
||||
|
||||
struct demux_packet *new_demux_packet(size_t len);
|
||||
// data must already have suitable padding
|
||||
struct demux_packet *new_demux_packet_fromdata(void *data, size_t len);
|
||||
struct demux_packet *new_demux_packet_from(void *data, size_t len);
|
||||
void demux_packet_shorten(struct demux_packet *dp, size_t len);
|
||||
void free_demux_packet(struct demux_packet *dp);
|
||||
struct demux_packet *demux_copy_packet(struct demux_packet *dp);
|
||||
|
||||
#endif /* MPLAYER_DEMUX_PACKET_H */
|
||||
+1
-1
@@ -31,6 +31,7 @@ struct demuxer;
|
||||
|
||||
struct sh_stream {
|
||||
enum stream_type type;
|
||||
struct demuxer *demuxer;
|
||||
// Index into demuxer->streams.
|
||||
int index;
|
||||
// Demuxer/format specific ID. Corresponds to the stream IDs as encoded in
|
||||
@@ -72,7 +73,6 @@ typedef struct sh_audio {
|
||||
// note codec extradata may be either under "wf" or "codecdata"
|
||||
unsigned char *codecdata;
|
||||
int codecdata_len;
|
||||
struct replaygain_data *replaygain_data;
|
||||
} sh_audio_t;
|
||||
|
||||
typedef struct sh_video {
|
||||
|
||||
+5
-11
@@ -2,7 +2,7 @@
|
||||
# mpv configuration file
|
||||
#
|
||||
# Configuration files are read system-wide from /usr/local/etc/mpv.conf
|
||||
# and per-user from ~/.config/mpv/mpv.conf, where per-user settings override
|
||||
# and per-user from ~/.mpv/config, where per-user settings override
|
||||
# system-wide settings, all of which are overridden by the command line.
|
||||
#
|
||||
# Configuration file settings and the command line options use the same
|
||||
@@ -80,17 +80,11 @@
|
||||
# Use 8MB input cache for everything, even local files.
|
||||
#cache=8192
|
||||
#
|
||||
# If a seek is issued, and the target is 1024KB past the cached range, then
|
||||
# keep reading until the seek target is hit, instead of doing a real seek.
|
||||
#cache-seek-min=1024
|
||||
# Prefill 20% of the cache before starting playback.
|
||||
#cache-min=20.0
|
||||
#
|
||||
# Disable the behavior that the player will pause if the cache goes below a
|
||||
# certain fill size.
|
||||
#cache-pause-below=no
|
||||
#
|
||||
# Enable demuxer-based cache. This reads ahead a number of packets, and the
|
||||
# player won't freeze completely if network is slow during playback.
|
||||
#demuxer-thread=yes
|
||||
# Prefill 50% of the cache before restarting playback after the cache emptied.
|
||||
#cache-seek-min=50
|
||||
|
||||
# Display English subtitles if available.
|
||||
#slang=en
|
||||
|
||||
+52
-45
@@ -1,29 +1,43 @@
|
||||
# mpv keybindings
|
||||
# mpv input control file
|
||||
#
|
||||
# Location of user-defined bindings: ~/.config/mpv/input.conf
|
||||
# You are able to redefine default keyboard/joystick/mouse/LIRC bindings, or
|
||||
# add new ones here.
|
||||
# See DOCS/man/input.rst for possible commands that can be bound.
|
||||
# Also see mpv --input-cmdlist for other possible options.
|
||||
# The file should be placed in the $HOME/.mpv directory.
|
||||
#
|
||||
# mpv --input-test --force-window --idle can be used to test which commands
|
||||
# keys are bound to.
|
||||
#
|
||||
# If you wish to unbind a key, bind it to the 'ignore' command:
|
||||
# KEY ignore
|
||||
#
|
||||
# Note that merely removing default key bindings from this file won't remove
|
||||
# the default bindings mpv was compiled with, unless
|
||||
# --no-input-default-bindings
|
||||
# is specified.
|
||||
#
|
||||
# Lines starting with # are comments. Use SHARP to assign the # key.
|
||||
# Copy this file and uncomment and edit the bindings you want to change.
|
||||
#
|
||||
# List of commands and further details: DOCS/man/input.rst
|
||||
# List of special keys: --input-keylist
|
||||
# Keybindings testing mode: mpv --input-test --force-window --idle
|
||||
#
|
||||
# Use 'ignore' to unbind a key fully (e.g. 'ctrl+a ignore').
|
||||
#
|
||||
# Strings need to be quoted and escaped:
|
||||
# KEY show_text "This is a single backslash: \\ and a quote: \" !"
|
||||
#
|
||||
# You can use modifier-key combinations like Shift+Left or Ctrl+Alt+x with
|
||||
# the modifiers Shift, Ctrl, Alt and Meta (may not work on the terminal).
|
||||
#
|
||||
# The default keybindings are hardcoded into the mpv binary.
|
||||
# You can disable them completely with: --no-input-default-bindings
|
||||
# modifiers Shift, Ctrl, Alt and Meta, but note that currently reading
|
||||
# key combinations is only supported through the video windows of certain
|
||||
# output drivers (not in output windows of other drivers or in a terminal).
|
||||
|
||||
# All lines in this file are commented. If you want to remap a key, copy the
|
||||
# file to ~/.mpv/input.conf, and uncomment and edit the binding you want to
|
||||
# change.
|
||||
|
||||
# Developer note:
|
||||
# On compilation, this file is baked into the mpv binary, and all lines are
|
||||
# uncommented (unless '#' is followed by a space) - thus this file defines the
|
||||
# default key bindings.
|
||||
# etc/input.conf from the mpv source tree is baked into the mpv binary, with
|
||||
# all entries magically uncommented and parsed on initialization. Thus, this
|
||||
# file defines the mpv default key bindings. If this file is read as normal
|
||||
# config file, all lines starting with '#' are treated as comments (and this
|
||||
# file, if unedited, doesn't define any bindings).
|
||||
# Lines that have a space after the '#' are always ignored as real comments.
|
||||
|
||||
# If this is enabled, treat all the following bindings as default.
|
||||
#default-bindings start
|
||||
@@ -45,8 +59,8 @@
|
||||
#AXIS_RIGHT seek -5
|
||||
|
||||
## Seek units are in seconds, but note that these are limited by keyframes
|
||||
#RIGHT seek 5
|
||||
#LEFT seek -5
|
||||
#RIGHT seek 10
|
||||
#LEFT seek -10
|
||||
#UP seek 60
|
||||
#DOWN seek -60
|
||||
# Do smaller, always exact (non-keyframe-limited), seeks with shift.
|
||||
@@ -55,10 +69,10 @@
|
||||
#Shift+LEFT no-osd seek -1 - exact
|
||||
#Shift+UP no-osd seek 5 - exact
|
||||
#Shift+DOWN no-osd seek -5 - exact
|
||||
#PGUP add chapter 1 # skip to next chapter
|
||||
#PGDWN add chapter -1 # skip to previous chapter
|
||||
#Shift+PGUP seek 600
|
||||
#Shift+PGDWN seek -600
|
||||
#PGUP seek 600
|
||||
#PGDWN seek -600
|
||||
#+ add audio-delay 0.100 # this changes audio/video sync
|
||||
#- add audio-delay -0.100
|
||||
#[ multiply speed 0.9091 # scale playback speed
|
||||
#] multiply speed 1.1
|
||||
#{ multiply speed 0.5
|
||||
@@ -76,18 +90,17 @@
|
||||
#> playlist_next # skip to next file
|
||||
#ENTER playlist_next force # skip to next file or quit
|
||||
#< playlist_prev # skip to previous file
|
||||
#O osd # cycle through OSD mode
|
||||
#o show_progress
|
||||
#P show_progress
|
||||
#o osd # cycle through OSD mode
|
||||
#I show_text "${filename}" # display filename in osd
|
||||
#P show_progress
|
||||
#z add sub-delay -0.1 # subtract 100 ms delay from subs
|
||||
#x add sub-delay +0.1 # add
|
||||
#ctrl++ add audio-delay 0.100 # this changes audio/video sync
|
||||
#ctrl+- add audio-delay -0.100
|
||||
#9 add volume -1
|
||||
#/ add volume -1
|
||||
#0 add volume 1
|
||||
#* add volume 1
|
||||
#( add balance -0.1 # adjust audio balance in favor of left
|
||||
#) add balance 0.1 # right
|
||||
#m cycle mute
|
||||
#1 add contrast -1
|
||||
#2 add contrast 1
|
||||
@@ -98,8 +111,10 @@
|
||||
#7 add saturation -1
|
||||
#8 add saturation 1
|
||||
#d cycle framedrop # cycle through framedrop modes
|
||||
# toggle deinterlacer (automatically inserts or removes required filter)
|
||||
# toggle deinterlacer; requires either vdpau output, -vf yadif or kerndeint
|
||||
#D cycle deinterlace
|
||||
#c cycle colormatrix
|
||||
# Next 3 currently only work with --no-ass
|
||||
#r add sub-pos -1 # move subtitles up
|
||||
#t add sub-pos +1 # down
|
||||
#v cycle sub-visibility
|
||||
@@ -107,8 +122,10 @@
|
||||
#V cycle ass-vsfilter-aspect-compat
|
||||
#j cycle sub # cycle through subtitles
|
||||
#J cycle sub down # ...backwards
|
||||
#F cycle sub-forced-only
|
||||
#SHARP cycle audio # switch audio streams
|
||||
#_ cycle video
|
||||
#TAB cycle program
|
||||
#T cycle ontop # toggle video window ontop of other windows
|
||||
#f cycle fullscreen # toggle fullscreen
|
||||
#s screenshot # take a screenshot
|
||||
@@ -131,7 +148,14 @@
|
||||
#MUTE cycle mute
|
||||
#CLOSE_WIN quit
|
||||
#CLOSE_WIN {encode} quit
|
||||
#! add chapter -1 # skip to previous chapter
|
||||
#@ add chapter 1 # next
|
||||
#E cycle edition # next edition
|
||||
#A cycle angle
|
||||
#U stop
|
||||
|
||||
#l cycle quvi-format 1
|
||||
#L cycle quvi-format -1
|
||||
|
||||
# Apple Remote section
|
||||
#AR_PLAY cycle pause
|
||||
@@ -181,12 +205,6 @@
|
||||
#h cycle tv-channel -1 # previous channel
|
||||
#k cycle tv-channel +1 # next channel
|
||||
|
||||
#
|
||||
# Legacy bindings (may or may not be removed in the future)
|
||||
#
|
||||
#! add chapter -1 # skip to previous chapter
|
||||
#@ add chapter 1 # next
|
||||
|
||||
#
|
||||
# Not assigned by default
|
||||
# (not an exhaustive list of unbound commands)
|
||||
@@ -197,14 +215,3 @@
|
||||
# ? sub_step -1 # immediately display next subtitle
|
||||
# ? sub_step +1 # previous
|
||||
# ? cycle_values window-scale 0.5 2 1 # switch between 1/2, 2x, unresized window size
|
||||
# ? cycle colormatrix
|
||||
# ? add audio-delay 0.100 # this changes audio/video sync
|
||||
# ? add audio-delay -0.100
|
||||
# ? cycle angle # switch DVD/Bluray angle
|
||||
# ? add balance -0.1 # adjust audio balance in favor of left
|
||||
# ? add balance 0.1 # right
|
||||
# ? cycle sub-forced-only # toggle DVD forced subs
|
||||
# ? cycle program # cycle transport stream programs
|
||||
# ? stop # stop playback (quit or enter idle mode)
|
||||
# ? cycle quvi-format 1 # cycle streaming video quality (semi-broken)
|
||||
# ? cycle quvi-format -1
|
||||
@@ -1,153 +0,0 @@
|
||||
##
|
||||
## MPlayer-style key bindings
|
||||
##
|
||||
## Save it as ~/.config/input.conf to use it.
|
||||
##
|
||||
## Generally, it's recommended to use this as reference-only.
|
||||
##
|
||||
|
||||
RIGHT seek +10
|
||||
LEFT seek -10
|
||||
DOWN seek -60
|
||||
UP seek +60
|
||||
PGUP seek 600
|
||||
PGDWN seek -600
|
||||
m cycle mute
|
||||
# cycle audio # switch audio streams
|
||||
+ add audio-delay 0.100
|
||||
= add audio-delay 0.100
|
||||
- add audio-delay -0.100
|
||||
[ multiply speed 0.9091 # scale playback speed
|
||||
] multiply speed 1.1
|
||||
{ multiply speed 0.5
|
||||
} multiply speed 2.0
|
||||
BS set speed 1.0 # reset speed to normal
|
||||
q quit
|
||||
ESC quit
|
||||
ENTER playlist_next force # skip to next file
|
||||
p cycle pause
|
||||
. frame_step # advance one frame and pause
|
||||
SPACE cycle pause
|
||||
HOME set playlist-pos 1 # not the same as MPlayer
|
||||
#END pt_up_step -1
|
||||
> playlist_next # skip to next file
|
||||
< playlist_prev # previous
|
||||
#INS alt_src_step 1
|
||||
#DEL alt_src_step -1
|
||||
o osd
|
||||
I show_text "${filename}" # display filename in osd
|
||||
P show_progress
|
||||
z add sub-delay -0.1 # subtract 100 ms delay from subs
|
||||
x add sub-delay +0.1 # add
|
||||
9 add volume -1
|
||||
/ add volume -1
|
||||
0 add volume 1
|
||||
* add volume 1
|
||||
1 add contrast -1
|
||||
2 add contrast 1
|
||||
3 add brightness -1
|
||||
4 add brightness 1
|
||||
5 add hue -1
|
||||
6 add hue 1
|
||||
7 add saturation -1
|
||||
8 add saturation 1
|
||||
( add balance -0.1 # adjust audio balance in favor of left
|
||||
) add balance +0.1 # right
|
||||
d cycle framedrop
|
||||
D cycle deinterlace # toggle deinterlacer (auto-inserted filter)
|
||||
r add sub-pos -1 # move subtitles up
|
||||
t add sub-pos +1 # down
|
||||
#? sub_step +1 # immediately display next subtitle
|
||||
#? sub_step -1 # previous
|
||||
#? add sub-scale +0.1 # increase subtitle font size
|
||||
#? add sub-scale -0.1 # decrease subtitle font size
|
||||
f cycle fullscreen
|
||||
T cycle ontop # toggle video window ontop of other windows
|
||||
w add panscan -0.1 # zoom out with -panscan 0 -fs
|
||||
e add panscan +0.1 # in
|
||||
c cycle stream-capture # save (and append) file/stream to stream.dump with -capture
|
||||
s screenshot # take a screenshot (if you want PNG, use "--screenshot-format=png")
|
||||
S screenshot - each-frame # S will take a png screenshot of every frame
|
||||
|
||||
h cycle tv-channel 1
|
||||
l cycle tv-channel -1
|
||||
n cycle tv-norm
|
||||
#b tv_step_chanlist
|
||||
|
||||
##
|
||||
## Joystick section
|
||||
## WARNING: joystick support has to be explicitly enabled at
|
||||
## compiletime with --enable-joystick
|
||||
##
|
||||
|
||||
JOY_RIGHT seek 10
|
||||
JOY_LEFT seek -10
|
||||
JOY_UP seek 60
|
||||
JOY_DOWN seek -60
|
||||
JOY_BTN0 cycle pause
|
||||
JOY_BTN1 osd
|
||||
JOY_BTN2 add volume 1
|
||||
JOY_BTN3 add volume -1
|
||||
#JOY_BTN4 set_menu main
|
||||
|
||||
##
|
||||
## Apple Remote section
|
||||
##
|
||||
## To use OSD menu with Apple Remote, set key AR_MENU to any OSD menu command,
|
||||
## or just comment out the 'AR_MENU osd' line and uncomment the line after it.
|
||||
##
|
||||
|
||||
AR_PLAY cycle pause
|
||||
AR_PLAY_HOLD quit
|
||||
AR_NEXT seek 30
|
||||
AR_NEXT_HOLD seek 120
|
||||
AR_PREV seek -10
|
||||
AR_PREV_HOLD seek -120
|
||||
#AR_MENU menu up
|
||||
#AR_MENU menu cancel
|
||||
AR_MENU_HOLD cycle mute
|
||||
AR_VUP add volume 1
|
||||
AR_VDOWN add volume -1
|
||||
|
||||
##
|
||||
## DVD and Bluray menus
|
||||
##
|
||||
## Unlike MPlayer, input doesn't require blocking normal keys when no menu
|
||||
## is active. The "discnav-menu" input section is active only when a menu
|
||||
## is shown, while "discnav" is always active when a DVD/Bluray is played.
|
||||
##
|
||||
## The bindings were adjusted according to this.
|
||||
##
|
||||
|
||||
UP {discnav-menu} discnav up # DVDNav UP
|
||||
DOWN {discnav-menu} discnav down # DVDNav DOWN
|
||||
LEFT {discnav-menu} discnav left # DVDNav LEFT
|
||||
RIGHT {discnav-menu} discnav right # DVDNav RIGHT
|
||||
ESC {discnav} discnav menu # DVDNav MENU
|
||||
ENTER {discnav-menu} discnav select # DVDNav SELECT (ok)
|
||||
BS {discnav-menu} discnav prev # DVDNav PREVIOUS menu (in the order chapter->title->root)
|
||||
|
||||
AR_VUP {discnav-menu} discnav up # DVDNav UP
|
||||
AR_VDOWN {discnav-menu} discnav down # DVDNav DOWN
|
||||
AR_PREV {discnav-menu} discnav left # DVDNav LEFT
|
||||
AR_NEXT {discnav-menu} discnav right # DVDNav RIGHT
|
||||
AR_MENU {discnav} discnav menu # DVDNav MENU
|
||||
AR_PLAY {discnav-menu} discnav select # DVDNav SELECT (ok)
|
||||
|
||||
#? add chapter -1 # skip to previous dvd chapter
|
||||
#? add chapter +1 # next
|
||||
|
||||
##
|
||||
## Advanced seek
|
||||
## Uncomment the following lines to be able to seek to n% of the media with
|
||||
## the Fx keys.
|
||||
##
|
||||
#F1 seek 10 absolute-percent
|
||||
#F2 seek 20 absolute-percent
|
||||
#F3 seek 30 absolute-percent
|
||||
#F4 seek 40 absolute-percent
|
||||
#F5 seek 50 absolute-percent
|
||||
#F6 seek 60 absolute-percent
|
||||
#F7 seek 70 absolute-percent
|
||||
#F8 seek 80 absolute-percent
|
||||
#F9 seek 90 absolute-percent
|
||||
+1
-1
@@ -23,5 +23,5 @@ Exec=mpv --no-terminal --force-window -- %U
|
||||
Terminal=false
|
||||
NoDisplay=true
|
||||
Categories=AudioVideo;Audio;Video;Player;TV;
|
||||
MimeType=application/ogg;application/x-ogg;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/m4a;audio/x-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/x-mpeg;audio/mpegurl;audio/x-mpegurl;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/scpls;audio/x-scpls;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-pls;audio/x-wav;video/mpeg;video/x-mpeg;video/x-mpeg2;video/mp4;video/msvideo;video/x-msvideo;video/quicktime;video/vnd.rn-realvideo;video/x-ms-afs;video/x-ms-asf;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/x-fli;video/x-flv;video/x-theora;video/x-matroska;video/webm;audio/x-flac;audio/x-vorbis+ogg;video/x-ogm+ogg;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;video/mp2t;audio/flac;
|
||||
MimeType=application/ogg;application/x-ogg;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/m4a;audio/x-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/x-mpeg;audio/mpegurl;audio/x-mpegurl;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/scpls;audio/x-scpls;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-pls;audio/x-wav;video/mpeg;video/x-mpeg;video/x-mpeg2;video/mp4;video/msvideo;video/x-msvideo;video/quicktime;video/vnd.rn-realvideo;video/x-ms-afs;video/x-ms-asf;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/x-fli;video/x-flv;video/x-theora;video/x-matroska;video/webm;audio/x-flac;audio/x-vorbis+ogg;video/x-ogm+ogg;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;video/mp2t;
|
||||
X-KDE-Protocols=ftp,http,https,mms,rtmp,rtsp,sftp,smb
|
||||
+1
-2
@@ -111,8 +111,7 @@ const struct mp_cmd_def mp_cmds[] = {
|
||||
{ MP_CMD_LOADFILE, "loadfile", {
|
||||
ARG_STRING,
|
||||
OARG_CHOICE(0, ({"replace", 0}, {"0", 0},
|
||||
{"append", 1}, {"1", 1},
|
||||
{"append-play", 2})),
|
||||
{"append", 1}, {"1", 1})),
|
||||
OPT_KEYVALUELIST(ARG(str_list), MP_CMD_OPT_ARG),
|
||||
}},
|
||||
{ MP_CMD_LOADLIST, "loadlist", {
|
||||
|
||||
+1
-1
@@ -306,7 +306,7 @@ struct mp_cmd *mp_input_parse_cmd_strv(struct mp_log *log, int def_flags,
|
||||
bstr args[MP_CMD_MAX_ARGS];
|
||||
int num = 0;
|
||||
for (; argv[num]; num++) {
|
||||
if (num >= MP_CMD_MAX_ARGS) {
|
||||
if (num > MP_CMD_MAX_ARGS) {
|
||||
mp_err(log, "%s: too many arguments.\n", location);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+28
-71
@@ -28,6 +28,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <fcntl.h>
|
||||
#include <ctype.h>
|
||||
#include <pthread.h>
|
||||
#include <assert.h>
|
||||
|
||||
@@ -122,7 +123,6 @@ struct input_ctx {
|
||||
bool mainthread_set;
|
||||
struct mp_log *log;
|
||||
struct mpv_global *global;
|
||||
struct input_opts *opts;
|
||||
|
||||
bool using_alt_gr;
|
||||
bool using_ar;
|
||||
@@ -160,9 +160,6 @@ struct input_ctx {
|
||||
// Unlike mouse_x/y, this can be used to resolve mouse click bindings.
|
||||
int mouse_vo_x, mouse_vo_y;
|
||||
|
||||
bool mouse_mangle, mouse_src_mangle;
|
||||
struct mp_rect mouse_src, mouse_dst;
|
||||
|
||||
bool test;
|
||||
|
||||
bool default_bindings;
|
||||
@@ -173,6 +170,10 @@ struct input_ctx {
|
||||
struct active_section active_sections[MAX_ACTIVE_SECTIONS];
|
||||
int num_active_sections;
|
||||
|
||||
// Used to track whether we managed to read something while checking
|
||||
// events sources. If yes, the sources may have more queued.
|
||||
bool got_new_events;
|
||||
|
||||
unsigned int mouse_event_counter;
|
||||
|
||||
struct input_fd fds[MP_MAX_FDS];
|
||||
@@ -180,7 +181,6 @@ struct input_ctx {
|
||||
|
||||
struct cmd_queue cmd_queue;
|
||||
|
||||
bool need_wakeup;
|
||||
bool in_select;
|
||||
int wakeup_pipe[2];
|
||||
};
|
||||
@@ -207,7 +207,6 @@ struct input_opts {
|
||||
int use_appleremote;
|
||||
int use_media_keys;
|
||||
int default_bindings;
|
||||
int enable_mouse_movements;
|
||||
int test;
|
||||
};
|
||||
|
||||
@@ -227,7 +226,6 @@ const struct m_sub_options input_config = {
|
||||
OPT_FLAG("lirc", use_lirc, CONF_GLOBAL),
|
||||
OPT_FLAG("right-alt-gr", use_alt_gr, CONF_GLOBAL),
|
||||
OPT_INTRANGE("key-fifo-size", key_fifo_size, CONF_GLOBAL, 2, 65000),
|
||||
OPT_FLAG("cursor", enable_mouse_movements, CONF_GLOBAL),
|
||||
#if HAVE_LIRC
|
||||
OPT_STRING("lirc-conf", lirc_configfile, CONF_GLOBAL),
|
||||
#endif
|
||||
@@ -245,7 +243,6 @@ const struct m_sub_options input_config = {
|
||||
.ar_rate = 40,
|
||||
.use_lirc = 1,
|
||||
.use_alt_gr = 1,
|
||||
.enable_mouse_movements = 1,
|
||||
#if HAVE_COCOA
|
||||
.use_appleremote = 1,
|
||||
.use_media_keys = 1,
|
||||
@@ -542,7 +539,7 @@ static void update_mouse_section(struct input_ctx *ictx)
|
||||
struct mp_cmd *cmd = get_cmd_from_keys(ictx, old, MP_KEY_MOUSE_LEAVE);
|
||||
if (cmd)
|
||||
queue_add_tail(&ictx->cmd_queue, cmd);
|
||||
mp_input_wakeup(ictx);
|
||||
ictx->got_new_events = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,10 +557,9 @@ static void release_down_cmd(struct input_ctx *ictx, bool drop_current)
|
||||
if (!drop_current && ictx->current_down_cmd &&
|
||||
ictx->current_down_cmd->key_up_follows)
|
||||
{
|
||||
memset(ictx->key_history, 0, sizeof(ictx->key_history));
|
||||
ictx->current_down_cmd->key_up_follows = false;
|
||||
queue_add_tail(&ictx->cmd_queue, ictx->current_down_cmd);
|
||||
mp_input_wakeup(ictx);
|
||||
ictx->got_new_events = true;
|
||||
} else {
|
||||
talloc_free(ictx->current_down_cmd);
|
||||
}
|
||||
@@ -600,10 +596,15 @@ static struct mp_cmd *resolve_key(struct input_ctx *ictx, int code)
|
||||
{
|
||||
update_mouse_section(ictx);
|
||||
struct mp_cmd *cmd = get_cmd_from_keys(ictx, NULL, code);
|
||||
key_buf_add(ictx->key_history, code);
|
||||
if (cmd && cmd->id != MP_CMD_IGNORE && !should_drop_cmd(ictx, cmd))
|
||||
return cmd;
|
||||
if (cmd && cmd->id != MP_CMD_IGNORE) {
|
||||
memset(ictx->key_history, 0, sizeof(ictx->key_history));
|
||||
if (!should_drop_cmd(ictx, cmd))
|
||||
return cmd;
|
||||
talloc_free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
talloc_free(cmd);
|
||||
key_buf_add(ictx->key_history, code);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -631,7 +632,7 @@ static void interpret_key(struct input_ctx *ictx, int code, double scale)
|
||||
|
||||
if (MP_KEY_DEPENDS_ON_MOUSE_POS(unmod))
|
||||
ictx->mouse_event_counter++;
|
||||
mp_input_wakeup(ictx);
|
||||
ictx->got_new_events = true;
|
||||
|
||||
struct mp_cmd *cmd = NULL;
|
||||
|
||||
@@ -672,8 +673,6 @@ static void interpret_key(struct input_ctx *ictx, int code, double scale)
|
||||
return;
|
||||
}
|
||||
|
||||
memset(ictx->key_history, 0, sizeof(ictx->key_history));
|
||||
|
||||
cmd->scale = scale;
|
||||
|
||||
if (cmd->key_up_follows)
|
||||
@@ -689,14 +688,12 @@ static void mp_input_feed_key(struct input_ctx *ictx, int code, double scale)
|
||||
release_down_cmd(ictx, false);
|
||||
return;
|
||||
}
|
||||
if (!ictx->opts->enable_mouse_movements && MP_KEY_IS_MOUSE(unmod))
|
||||
return;
|
||||
if (unmod == MP_KEY_MOUSE_LEAVE) {
|
||||
update_mouse_section(ictx);
|
||||
struct mp_cmd *cmd = get_cmd_from_keys(ictx, NULL, code);
|
||||
if (cmd)
|
||||
queue_add_tail(&ictx->cmd_queue, cmd);
|
||||
mp_input_wakeup(ictx);
|
||||
ictx->got_new_events = true;
|
||||
return;
|
||||
}
|
||||
double now = mp_time_sec();
|
||||
@@ -744,50 +741,11 @@ void mp_input_put_axis(struct input_ctx *ictx, int direction, double value)
|
||||
input_unlock(ictx);
|
||||
}
|
||||
|
||||
void mp_input_set_mouse_transform(struct input_ctx *ictx, struct mp_rect *dst,
|
||||
struct mp_rect *src)
|
||||
{
|
||||
input_lock(ictx);
|
||||
ictx->mouse_mangle = dst || src;
|
||||
if (ictx->mouse_mangle) {
|
||||
ictx->mouse_dst = *dst;
|
||||
ictx->mouse_src_mangle = !!src;
|
||||
if (ictx->mouse_src_mangle)
|
||||
ictx->mouse_src = *src;
|
||||
}
|
||||
input_unlock(ictx);
|
||||
}
|
||||
|
||||
bool mp_input_mouse_enabled(struct input_ctx *ictx)
|
||||
{
|
||||
input_lock(ictx);
|
||||
bool r = ictx->opts->enable_mouse_movements;
|
||||
input_unlock(ictx);
|
||||
return r;
|
||||
}
|
||||
|
||||
void mp_input_set_mouse_pos(struct input_ctx *ictx, int x, int y)
|
||||
{
|
||||
input_lock(ictx);
|
||||
MP_DBG(ictx, "mouse move %d/%d\n", x, y);
|
||||
|
||||
if (!ictx->opts->enable_mouse_movements) {
|
||||
input_unlock(ictx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ictx->mouse_mangle) {
|
||||
struct mp_rect *src = &ictx->mouse_src;
|
||||
struct mp_rect *dst = &ictx->mouse_dst;
|
||||
x = MPCLAMP(x, dst->x0, dst->x1) - dst->x0;
|
||||
y = MPCLAMP(y, dst->y0, dst->y1) - dst->y0;
|
||||
if (ictx->mouse_src_mangle) {
|
||||
x = x * 1.0 / (dst->x1 - dst->x0) * (src->x1 - src->x0) + src->x0;
|
||||
y = y * 1.0 / (dst->y1 - dst->y0) * (src->y1 - src->y0) + src->y0;
|
||||
}
|
||||
MP_DBG(ictx, "-> %d/%d\n", x, y);
|
||||
}
|
||||
|
||||
ictx->mouse_event_counter++;
|
||||
ictx->mouse_vo_x = x;
|
||||
ictx->mouse_vo_y = y;
|
||||
@@ -811,7 +769,7 @@ void mp_input_set_mouse_pos(struct input_ctx *ictx, int x, int y)
|
||||
talloc_free(tail);
|
||||
}
|
||||
queue_add_tail(&ictx->cmd_queue, cmd);
|
||||
mp_input_wakeup(ictx);
|
||||
ictx->got_new_events = true;
|
||||
}
|
||||
}
|
||||
input_unlock(ictx);
|
||||
@@ -993,7 +951,7 @@ static void read_cmd_fd(struct input_ctx *ictx, struct input_fd *cmd_fd)
|
||||
int r;
|
||||
char *text;
|
||||
while ((r = read_cmd(cmd_fd, &text)) >= 0) {
|
||||
mp_input_wakeup(ictx);
|
||||
ictx->got_new_events = true;
|
||||
struct mp_cmd *cmd = mp_input_parse_cmd(ictx, bstr0(text), "<pipe>");
|
||||
talloc_free(text);
|
||||
if (cmd)
|
||||
@@ -1101,9 +1059,9 @@ static void read_events(struct input_ctx *ictx, int time)
|
||||
time = FFMAX(time, 0);
|
||||
|
||||
while (1) {
|
||||
if (ictx->need_wakeup)
|
||||
if (ictx->got_new_events)
|
||||
time = 0;
|
||||
ictx->need_wakeup = false;
|
||||
ictx->got_new_events = false;
|
||||
|
||||
remove_dead_fds(ictx);
|
||||
|
||||
@@ -1114,13 +1072,13 @@ static void read_events(struct input_ctx *ictx, int time)
|
||||
}
|
||||
}
|
||||
|
||||
if (ictx->need_wakeup)
|
||||
if (ictx->got_new_events)
|
||||
time = 0;
|
||||
|
||||
input_wait_read(ictx, time);
|
||||
|
||||
// Read until no new wakeups happen.
|
||||
if (!ictx->need_wakeup)
|
||||
// Read until all input FDs are empty
|
||||
if (!ictx->got_new_events)
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1128,6 +1086,7 @@ static void read_events(struct input_ctx *ictx, int time)
|
||||
int mp_input_queue_cmd(struct input_ctx *ictx, mp_cmd_t *cmd)
|
||||
{
|
||||
input_lock(ictx);
|
||||
ictx->got_new_events = true;
|
||||
if (cmd)
|
||||
queue_add_tail(&ictx->cmd_queue, cmd);
|
||||
input_unlock(ictx);
|
||||
@@ -1494,7 +1453,6 @@ static int parse_config_file(struct input_ctx *ictx, char *file, bool warn)
|
||||
MP_ERR(ictx, "Can't open input config file %s.\n", file);
|
||||
goto done;
|
||||
}
|
||||
stream_skip_bom(s);
|
||||
bstr data = stream_read_complete(s, tmp, 1000000);
|
||||
if (data.start) {
|
||||
MP_VERBOSE(ictx, "Parsing input config file %s\n", file);
|
||||
@@ -1532,7 +1490,6 @@ struct input_ctx *mp_input_init(struct mpv_global *global)
|
||||
struct input_ctx *ictx = talloc_ptrtype(NULL, ictx);
|
||||
*ictx = (struct input_ctx){
|
||||
.global = global,
|
||||
.opts = input_conf,
|
||||
.log = mp_log_new(ictx, global->log, "input"),
|
||||
.key_fifo_size = input_conf->key_fifo_size,
|
||||
.doubleclick_time = input_conf->doubleclick_time,
|
||||
@@ -1566,7 +1523,7 @@ struct input_ctx *mp_input_init(struct mpv_global *global)
|
||||
#ifndef __MINGW32__
|
||||
int ret = mp_make_wakeup_pipe(ictx->wakeup_pipe);
|
||||
if (ret < 0)
|
||||
MP_ERR(ictx, "Failed to initialize wakeup pipe.\n");
|
||||
MP_ERR(ictx, "Failed to initialize wakeup pipe: %s\n", strerror(-ret));
|
||||
else
|
||||
mp_input_add_fd(ictx, ictx->wakeup_pipe[0], true, NULL, read_wakeup,
|
||||
NULL, NULL);
|
||||
@@ -1678,7 +1635,7 @@ void mp_input_wakeup(struct input_ctx *ictx)
|
||||
{
|
||||
input_lock(ictx);
|
||||
bool send_wakeup = ictx->in_select;
|
||||
ictx->need_wakeup = true;
|
||||
ictx->got_new_events = true;
|
||||
pthread_cond_signal(&ictx->wakeup);
|
||||
input_unlock(ictx);
|
||||
// Safe without locking
|
||||
@@ -1692,7 +1649,7 @@ void mp_input_wakeup_nolock(struct input_ctx *ictx)
|
||||
write(ictx->wakeup_pipe[1], &(char){0}, 1);
|
||||
} else {
|
||||
// Not race condition free. Done for the sake of jackaudio+windows.
|
||||
ictx->need_wakeup = true;
|
||||
ictx->got_new_events = true;
|
||||
pthread_cond_signal(&ictx->wakeup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,16 +133,6 @@ void mp_input_set_mouse_pos(struct input_ctx *ictx, int x, int y);
|
||||
|
||||
void mp_input_get_mouse_pos(struct input_ctx *ictx, int *x, int *y);
|
||||
|
||||
// Return whether we want/accept mouse input.
|
||||
bool mp_input_mouse_enabled(struct input_ctx *ictx);
|
||||
|
||||
/* Make mp_input_set_mouse_pos() mangle the mouse coordinates. Hack for certain
|
||||
* VOs. dst=NULL, src=NULL reset it. src can be NULL.
|
||||
*/
|
||||
struct mp_rect;
|
||||
void mp_input_set_mouse_transform(struct input_ctx *ictx, struct mp_rect *dst,
|
||||
struct mp_rect *src);
|
||||
|
||||
// As for the cmd one you usually don't need this function.
|
||||
void mp_input_rm_key_fd(struct input_ctx *ictx, int fd);
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include "bstr/bstr.h"
|
||||
#include "common/common.h"
|
||||
|
||||
@@ -222,9 +222,6 @@
|
||||
(MP_KEY_IS_MOUSE_BTN_SINGLE(code) || MP_KEY_IS_MOUSE_BTN_DBL(code) || \
|
||||
(code) == MP_KEY_MOUSE_MOVE)
|
||||
|
||||
#define MP_KEY_IS_MOUSE(code) \
|
||||
(MP_KEY_DEPENDS_ON_MOUSE_POS(code) || (code) == MP_KEY_MOUSE_LEAVE)
|
||||
|
||||
// Emit a command even on key-up (normally key-up is ignored). This means by
|
||||
// default they binding will be triggered on key-up instead of key-down.
|
||||
// This is a fixed part of the keycode, not a modifier than can change.
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ int mp_input_lirc_init(struct input_ctx *ictx, struct mp_log *log,
|
||||
.log = log,
|
||||
.lirc_config = lirc_config,
|
||||
};
|
||||
mp_input_add_fd(ictx, lirc_sock, 1, mp_input_lirc_read, NULL, mp_input_lirc_close, ctx);
|
||||
mp_input_add_fd(ictx, lirc_sock, 0, mp_input_lirc_read, NULL, mp_input_lirc_close, ctx);
|
||||
|
||||
return lirc_sock;
|
||||
}
|
||||
|
||||
+6
-31
@@ -114,7 +114,7 @@ extern "C" {
|
||||
* handler is process-wide, and there's no proper way to share it with other
|
||||
* xlib users within the same process. This might confuse GUI toolkits.
|
||||
* - mpv uses some other libraries that are not library-safe, such as Fribidi
|
||||
* (used through libass), ALSA, FFmpeg, and possibly more.
|
||||
* (used through libass), LittleCMS, ALSA, FFmpeg, and possibly more.
|
||||
* - The FPU precision must be set at least to double precision.
|
||||
* - On Windows, mpv will call timeBeginPeriod(1).
|
||||
* - On memory exhaustion, mpv will kill the process.
|
||||
@@ -131,24 +131,6 @@ extern "C" {
|
||||
* aspect ratio of the window and the video mismatch).
|
||||
*
|
||||
* On OSX, embedding is not yet possible, because Cocoa makes this non-trivial.
|
||||
*
|
||||
* Compatibility
|
||||
* -------------
|
||||
*
|
||||
* mpv development doesn't stand still, and changes to mpv internals as well as
|
||||
* to its interface can cause compatibility issues to client API users.
|
||||
*
|
||||
* The API is versioned (see MPV_CLIENT_API_VERSION), and changes to it are
|
||||
* documented in DOCS/client-api-changes.rst. The C API itself will probably
|
||||
* remain compatible for a long time, but the functionality exposed by it
|
||||
* could change more rapidly. For example, it's possible that options are
|
||||
* renamed, or change the set of allowed values.
|
||||
*
|
||||
* Defensive programming should be used to potentially deal with the fact that
|
||||
* options, commands, and properties could disappear, change their value range,
|
||||
* or change the underlying datatypes. It might be a good idea to prefer
|
||||
* MPV_FORMAT_STRING over other types to decouple your code from potential
|
||||
* mpv changes.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -157,12 +139,8 @@ extern "C" {
|
||||
* the API becomes incompatible to previous versions, the major version
|
||||
* number is incremented. This affects only C part, and not properties and
|
||||
* options.
|
||||
*
|
||||
* You can use MPV_MAKE_VERSION() and compare the result with integer
|
||||
* relational operators (<, >, <=, >=).
|
||||
*/
|
||||
#define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL)
|
||||
#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 4)
|
||||
#define MPV_CLIENT_API_VERSION 0x00010000UL
|
||||
|
||||
/**
|
||||
* Return the MPV_CLIENT_API_VERSION the mpv source has been compiled with.
|
||||
@@ -918,10 +896,8 @@ typedef enum mpv_event_id {
|
||||
*/
|
||||
MPV_EVENT_UNPAUSE = 13,
|
||||
/**
|
||||
* Sent every time after a video frame is displayed. Note that currently,
|
||||
* this will be sent in lower frequency if there is no video, or playback
|
||||
* is paused - but that will be removed in the future, and it will be
|
||||
* restricted to video frames only.
|
||||
* Sent every time after a video frame is displayed (or in lower frequency
|
||||
* if there is no video, or playback is paused).
|
||||
*/
|
||||
MPV_EVENT_TICK = 14,
|
||||
/**
|
||||
@@ -936,8 +912,8 @@ typedef enum mpv_event_id {
|
||||
/**
|
||||
* Triggered by the script_message input command. The command uses the
|
||||
* first argument of the command as client name (see mpv_client_name()) to
|
||||
* dispatch the message, and passes along all arguments starting from the
|
||||
* second argument as strings.
|
||||
* dispatch the message, and passes along the all arguments starting from
|
||||
* the seconand argument as strings.
|
||||
* See also mpv_event and mpv_event_client_message.
|
||||
*/
|
||||
MPV_EVENT_CLIENT_MESSAGE = 16,
|
||||
@@ -984,7 +960,6 @@ typedef enum mpv_event_id {
|
||||
* Happens when the current chapter changes.
|
||||
*/
|
||||
MPV_EVENT_CHAPTER_CHANGE = 23
|
||||
// Internal note: adjust INTERNAL_EVENT_BASE when adding new events.
|
||||
} mpv_event_id;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
mpv_client_api_version
|
||||
mpv_client_name
|
||||
mpv_command
|
||||
mpv_command_async
|
||||
mpv_command_string
|
||||
mpv_create
|
||||
mpv_detach_destroy
|
||||
mpv_error_string
|
||||
mpv_event_name
|
||||
mpv_free
|
||||
mpv_free_node_contents
|
||||
mpv_get_property
|
||||
mpv_get_property_async
|
||||
mpv_get_property_osd_string
|
||||
mpv_get_property_string
|
||||
mpv_get_time_us
|
||||
mpv_get_wakeup_pipe
|
||||
mpv_initialize
|
||||
mpv_load_config_file
|
||||
mpv_observe_property
|
||||
mpv_request_event
|
||||
mpv_request_log_messages
|
||||
mpv_resume
|
||||
mpv_set_option
|
||||
mpv_set_option_string
|
||||
mpv_set_property
|
||||
mpv_set_property_async
|
||||
mpv_set_property_string
|
||||
mpv_set_wakeup_callback
|
||||
mpv_suspend
|
||||
mpv_terminate_destroy
|
||||
mpv_unobserve_property
|
||||
mpv_wait_event
|
||||
mpv_wakeup
|
||||
@@ -8,5 +8,4 @@ Description: mpv media player client library
|
||||
Version: @VERSION@
|
||||
Requires:
|
||||
Libs: -L${libdir} -lmpv
|
||||
Libs.private: @PRIV_LIBS@
|
||||
Cflags: -I${includedir}
|
||||
+4
-31
@@ -21,7 +21,6 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "config.h"
|
||||
@@ -81,24 +80,11 @@ bool mp_charset_requires_guess(const char *user_cp)
|
||||
// Note that "utf8" is the UTF-8 codepage, while "utf8:..." specifies UTF-8
|
||||
// by default, plus a codepage that is used if the input is not UTF-8.
|
||||
return bstrcasecmp0(res[0], "enca") == 0 ||
|
||||
bstrcasecmp0(res[0], "auto") == 0 ||
|
||||
bstrcasecmp0(res[0], "guess") == 0 ||
|
||||
(r > 1 && bstrcasecmp0(res[0], "utf-8") == 0) ||
|
||||
(r > 1 && bstrcasecmp0(res[0], "utf8") == 0);
|
||||
}
|
||||
|
||||
static const char *const utf_bom[3] = {"\xEF\xBB\xBF", "\xFF\xFE", "\xFE\xFF"};
|
||||
static const char *const utf_enc[3] = {"utf-8", "utf-16le", "utf-16be"};
|
||||
|
||||
static const char *ms_bom_guess(bstr buf)
|
||||
{
|
||||
for (int n = 0; n < 3; n++) {
|
||||
if (bstr_startswith0(buf, utf_bom[n]))
|
||||
return utf_enc[n];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if HAVE_ENCA
|
||||
static const char *enca_guess(struct mp_log *log, bstr buf, const char *language)
|
||||
{
|
||||
@@ -116,7 +102,8 @@ static const char *enca_guess(struct mp_log *log, bstr buf, const char *language
|
||||
detected_cp = tmp;
|
||||
enca_analyser_free(analyser);
|
||||
} else {
|
||||
mp_err(log, "ENCA doesn't know language '%s'\n", language);
|
||||
mp_err(log, "ENCA doesn't know language '%s'\n",
|
||||
language);
|
||||
size_t langcnt;
|
||||
const char **languages = enca_get_languages(&langcnt);
|
||||
mp_err(log, "ENCA supported languages:");
|
||||
@@ -156,15 +143,6 @@ const char *mp_charset_guess(struct mp_log *log, bstr buf, const char *user_cp,
|
||||
if (!mp_charset_requires_guess(user_cp))
|
||||
return user_cp;
|
||||
|
||||
bool use_auto = strcasecmp(user_cp, "auto") == 0;
|
||||
if (use_auto) {
|
||||
#if HAVE_ENCA
|
||||
user_cp = "enca";
|
||||
#else
|
||||
user_cp = "UTF-8:UTF-8-BROKEN";
|
||||
#endif
|
||||
}
|
||||
|
||||
// Do our own UTF-8 detection, because at least ENCA seems to get it
|
||||
// wrong sometimes (suggested by divVerent).
|
||||
int r = bstr_validate_utf8(buf);
|
||||
@@ -181,12 +159,6 @@ const char *mp_charset_guess(struct mp_log *log, bstr buf, const char *user_cp,
|
||||
|
||||
const char *res = NULL;
|
||||
|
||||
if (use_auto) {
|
||||
res = ms_bom_guess(buf);
|
||||
if (res)
|
||||
type = bstr0("auto");
|
||||
}
|
||||
|
||||
#if HAVE_ENCA
|
||||
if (bstrcasecmp0(type, "enca") == 0)
|
||||
res = enca_guess(log, buf, lang);
|
||||
@@ -201,7 +173,8 @@ const char *mp_charset_guess(struct mp_log *log, bstr buf, const char *user_cp,
|
||||
}
|
||||
|
||||
if (res) {
|
||||
mp_dbg(log, "%.*s detected charset: '%s'\n", BSTR_P(type), res);
|
||||
mp_dbg(log, "%.*s detected charset: '%s'\n",
|
||||
BSTR_P(type), res);
|
||||
} else {
|
||||
res = fallback;
|
||||
mp_dbg(log, "Detection with %.*s failed: fallback to %s\n",
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef MP_CTYPE_H_
|
||||
#define MP_CTYPE_H_
|
||||
|
||||
// Roughly follows C semantics, but doesn't account for EOF, allows char as
|
||||
// parameter, and is locale independent (always uses "C" locale).
|
||||
|
||||
static inline int mp_isprint(char c) { return (unsigned char)c >= 32; }
|
||||
static inline int mp_isspace(char c) { return c == ' ' || c == '\f' || c == '\n' ||
|
||||
c == '\r' || c == '\t' || c =='\v'; }
|
||||
static inline int mp_isupper(char c) { return c >= 'A' && c <= 'Z'; }
|
||||
static inline int mp_islower(char c) { return c >= 'a' && c <= 'z'; }
|
||||
static inline int mp_isdigit(char c) { return c >= '0' && c <= '9'; }
|
||||
static inline int mp_isalpha(char c) { return mp_isupper(c) || mp_islower(c); }
|
||||
static inline int mp_isalnum(char c) { return mp_isalpha(c) || mp_isdigit(c); }
|
||||
|
||||
static inline char mp_tolower(char c) { return mp_isupper(c) ? c - 'A' + 'a' : c; }
|
||||
static inline char mp_toupper(char c) { return mp_islower(c) ? c - 'a' + 'A' : c; }
|
||||
|
||||
#endif
|
||||
Loaded 100 of 218 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user