Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b7d0fbab3 | ||
|
|
ec567d8a77 | ||
|
|
33489feca8 | ||
|
|
ce82d88215 | ||
|
|
d1ad0815fe | ||
|
|
fc3f1e7c54 | ||
|
|
466658ee7e | ||
|
|
cf18922d19 | ||
|
|
3c081dfd93 | ||
|
|
a135c9cffc | ||
|
|
f891b24cb5 | ||
|
|
70aa3b04f8 | ||
|
|
c24921d56b | ||
|
|
aaec2aba38 | ||
|
|
3b95dd47d5 | ||
|
|
555ecbb70e | ||
|
|
919707efb7 | ||
|
|
c1de8cdc38 | ||
|
|
0cc440f291 | ||
|
|
0524907c18 | ||
|
|
c971fefd41 | ||
|
|
9ed289ef90 | ||
|
|
f3a003e550 | ||
|
|
48c2e9d67d | ||
|
|
0ffaf653a2 | ||
|
|
76d1b430b0 | ||
|
|
ec27d573f4 | ||
|
|
72d3c5ef00 | ||
|
|
81109dcbb6 | ||
|
|
fa510bd00c | ||
|
|
c21c26472c | ||
|
|
d6e7190836 | ||
|
|
d1a46c2c32 | ||
|
|
a02f3a5dd4 | ||
|
|
17b4fb02b3 | ||
|
|
252dcdcc99 | ||
|
|
97fc4f4a85 | ||
|
|
ad6e7c31df | ||
|
|
bf6b981367 | ||
|
|
72ded5ccef | ||
|
|
0344abd67a | ||
|
|
56fefde7e2 | ||
|
|
955c6843b6 | ||
|
|
d1528e51d5 | ||
|
|
53c720d412 | ||
|
|
878f12885b | ||
|
|
0a1a5707bf | ||
|
|
dd08018e9e | ||
|
|
56d04c6570 | ||
|
|
96eb480299 | ||
|
|
14bb3d06a2 | ||
|
|
1a1ac76d2a | ||
|
|
d93a9be656 | ||
|
|
dda16ee1fb | ||
|
|
78112c8582 | ||
|
|
ff778f6d68 | ||
|
|
cee9aeaf6b | ||
|
|
eb66038d4f | ||
|
|
1ee8ce75f1 | ||
|
|
0121d00478 | ||
|
|
e157d005ba | ||
|
|
251107076b | ||
|
|
750c29766f | ||
|
|
b0b67cdf5d | ||
|
|
50f8548b2f | ||
|
|
0ab6031d76 | ||
|
|
667b968939 | ||
|
|
e1128c8b28 | ||
|
|
e3de309804 | ||
|
|
9ca312b4b1 | ||
|
|
d27d91715f | ||
|
|
0eb45343ca | ||
|
|
7e7ef2f01d | ||
|
|
76bfd5b4a2 | ||
|
|
8d414e2fe7 | ||
|
|
2483dab54d | ||
|
|
3a7a2385df | ||
|
|
3274fabeb3 | ||
|
|
e0f8d79772 | ||
|
|
da496ae2fe | ||
|
|
184426175f | ||
|
|
1f9c4f19cf | ||
|
|
8b291aff96 | ||
|
|
8d004f07a4 | ||
|
|
14a2993796 | ||
|
|
3804376ccc | ||
|
|
bddd1e9e3b | ||
|
|
9e7069fde2 | ||
|
|
94b6a1054e | ||
|
|
d714c8380a | ||
|
|
b7ba2e2c57 | ||
|
|
52de5ea6ad | ||
|
|
78caf6ae86 | ||
|
|
46c499d02a | ||
|
|
dbbde6161d | ||
|
|
a62dcdd5f4 | ||
|
|
f7124be091 | ||
|
|
6499224888 | ||
|
|
252d5b319a | ||
|
|
280251656c | ||
|
|
aa796c23db | ||
|
|
0a41c6f0ec | ||
|
|
66aa54d319 | ||
|
|
288eaacd85 | ||
|
|
ecb776f88e | ||
|
|
1c593f1497 | ||
|
|
b4804a4b26 | ||
|
|
5015759334 | ||
|
|
3c292395ca | ||
|
|
8e654d3f78 | ||
|
|
0e245b3752 | ||
|
|
6fb926a560 | ||
|
|
8f33c65fe0 | ||
|
|
beae60bcd5 | ||
|
|
29e42b78c0 | ||
|
|
54fbda2ba4 | ||
|
|
e694d67366 | ||
|
|
6890de958d | ||
|
|
c855ab5624 | ||
|
|
3ccac74de2 | ||
|
|
ee283843fa | ||
|
|
291f301c10 | ||
|
|
e448e10fd7 | ||
|
|
e72ca08554 | ||
|
|
e87f705497 | ||
|
|
93db4233af | ||
|
|
fe993a6712 | ||
|
|
69bc7e34b9 | ||
|
|
a9ed9e329c | ||
|
|
8a21cb1cd3 | ||
|
|
bd87598af9 | ||
|
|
f4d62da8f0 | ||
|
|
ae7212963e | ||
|
|
24f34c9e8e | ||
|
|
98c4ab6d13 | ||
|
|
4a07205963 | ||
|
|
7c5f41ff5f | ||
|
|
a2a2cdb5bc | ||
|
|
41bf41e416 | ||
|
|
ebb43f5176 | ||
|
|
aff6e8e8da | ||
|
|
1b7e8ff6c5 | ||
|
|
739452361f | ||
|
|
ee63c9c210 | ||
|
|
b4491c00c4 | ||
|
|
12cd48a8a9 | ||
|
|
31316e9ca0 | ||
|
|
853fe788f8 | ||
|
|
1dd7b7bddc | ||
|
|
15ef9f9ee6 | ||
|
|
ad2ab5893e | ||
|
|
144571da9b | ||
|
|
8aa8417aa3 | ||
|
|
1fa674c109 | ||
|
|
710872bc22 | ||
|
|
1ff32236fa | ||
|
|
2a7811176c | ||
|
|
2e5df94f0f | ||
|
|
0c494c238a | ||
|
|
375886c777 | ||
|
|
0aa548457f | ||
|
|
e0cb65e8ac | ||
|
|
6875e6a779 | ||
|
|
ac5538c7df | ||
|
|
0ae8aebb89 | ||
|
|
361040f9d9 | ||
|
|
d2281935b3 | ||
|
|
0a6c334b59 | ||
|
|
38b747bb67 | ||
|
|
993bee38ca | ||
|
|
0b87bf9b72 | ||
|
|
456366b63b | ||
|
|
ec356d3efe | ||
|
|
5786d07450 | ||
|
|
8d8a2045bd | ||
|
|
0e9cfa6b64 | ||
|
|
d47dff3faa | ||
|
|
b81d9c79a8 | ||
|
|
57831d52dc | ||
|
|
b14c9eb748 | ||
|
|
cb1c072534 | ||
|
|
c17ff1703a | ||
|
|
65ad85790a | ||
|
|
17cd6798a6 | ||
|
|
d8d4e18835 | ||
|
|
009dfbe3b6 | ||
|
|
2ed9370bd6 | ||
|
|
e0c005cb75 | ||
|
|
4d6c1be7b6 |
No files matched your search
@@ -32,7 +32,12 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.12.0 ---
|
||||
1.20 - deprecate "GL_MP_D3D_interfaces"/"glMPGetD3DInterface", and introduce
|
||||
"GL_MP_MPGetNativeDisplay"/"glMPGetNativeDisplay" (this is a
|
||||
backwards-compatible rename)
|
||||
--- mpv 0.11.0 ---
|
||||
--- mpv 0.10.0 ---
|
||||
1.19 - add "GL_MP_D3D_interfaces" pseudo extension to make it possible to
|
||||
use DXVA2 in OpenGL fullscreen mode in some situations
|
||||
- mpv_request_log_messages() now accepts "terminal-default" as parameter
|
||||
|
||||
@@ -1,35 +1,114 @@
|
||||
Client API examples
|
||||
===================
|
||||
# Client API examples
|
||||
|
||||
All these examples use the mpv client API through libmpv.
|
||||
|
||||
cocoa
|
||||
-----
|
||||
## Where are the docs?
|
||||
|
||||
The libmpv C API is documented directly in the header files (on normal Unix
|
||||
systems, this is in `/usr/include/mpv/client.h`.
|
||||
|
||||
libmpv merely gives you access to mpv's command interface, which is documented
|
||||
here:
|
||||
* Options (`mpv_set_option()` and friends): http://mpv.io/manual/master/#options
|
||||
* Commands (`mpv_command()` and friends): http://mpv.io/manual/master/#list-of-input-commands
|
||||
* Properties (`mpv_set_property()` and friends): http://mpv.io/manual/master/#properties
|
||||
|
||||
Essentially everything is done with them, including loading a file, retrieving
|
||||
playback progress, and so on.
|
||||
|
||||
## Methods of embedding the video window
|
||||
|
||||
All of these examples concentrate on how to integrate mpv's video renderers
|
||||
with your own GUI. This is generally the hardest part. libmpv enforces a
|
||||
somewhat efficient video output method, rather than e.g. returning a RGBA
|
||||
surface in memory for each frame. The latter would be prohibitively inefficient,
|
||||
because it would require conversion on the CPU. The goal is also not requiring
|
||||
the API users to reinvent their own video rendering/scaling/presentation
|
||||
mechanisms.
|
||||
|
||||
There are currently 2 methods of embedding video.
|
||||
|
||||
### Native window embedding
|
||||
|
||||
This uses the platform's native method of nesting multiple windows. For example,
|
||||
Linux/X11 can nest a window from a completely different process. The nested
|
||||
window can redraw contents on its own, and receive user input if the user
|
||||
interacts with this window.
|
||||
|
||||
libmpv lets you specify a parent window for its own video window via the `wid`
|
||||
option. Then libmpv will create its window with your window as parent, and
|
||||
render its video inside of it.
|
||||
|
||||
This method is highly OS-dependent. Some behavior is OS-specific. There are
|
||||
problems with focusing on X11 (the ancient X11 focus policy mismatches with
|
||||
that of modern UI toolkits - it's normally worked around, but this is not
|
||||
easily possible with raw window embedding). It seems to have stability problems
|
||||
on OSX when using the Qt toolkit.
|
||||
|
||||
### OpenGL embedding
|
||||
|
||||
This method lets you use libmpv's OpenGL renderer directly. You create an
|
||||
OpenGL context, and then use `mpv_opengl_cb_draw()` to render the video on
|
||||
each frame.
|
||||
|
||||
This is more complicated, because libmpv will work directly on your own OpenGL
|
||||
state. It's also not possible to have mpv automatically receive user input.
|
||||
You will have to simulate this with the `mouse`/`keypress`/`keydown`/`keyup`
|
||||
commands.
|
||||
|
||||
You also get much more flexibility. For example, you can actually render your
|
||||
own OSD on top of the video, something that is not possible with raw window
|
||||
embedding.
|
||||
|
||||
### Which one to use?
|
||||
|
||||
Due to the various platform-specific behavior and problems (in particular on
|
||||
OSX), OpenGL embedding is recommended. If you're not comfortable with requiring
|
||||
OpenGL, or want to support "direct" video output such as vdpau (which might
|
||||
win when it comes to performance and energy-saving), you should probably
|
||||
support both methods if possible.
|
||||
|
||||
## List of examples
|
||||
|
||||
### simple
|
||||
|
||||
Very primitive terminal-only example. Shows some most basic API usage.
|
||||
|
||||
### cocoa
|
||||
|
||||
Shows how to embed the mpv video window in Objective-C/Cocoa.
|
||||
|
||||
qt
|
||||
--
|
||||
### cocoa-openglcb
|
||||
|
||||
Similar to the cocoa sample, but shows how to integrate mpv's OpenGL renderer
|
||||
using libmpv's opengl-cb API. Since it does not require complicated interaction
|
||||
with Cocoa elements from different libraries, it's more robust.
|
||||
|
||||
### qt
|
||||
|
||||
Shows how to embed the mpv video window in Qt (using normal desktop widgets).
|
||||
|
||||
qml
|
||||
---
|
||||
### qt_opengl
|
||||
|
||||
Shows how to use mpv's OpenGL video renderer in QtQuick2 with QML.
|
||||
Shows how to use mpv's OpenGL video renderer in Qt. This uses the opengl-cb API
|
||||
for video. Since it does not rely on embedding "foreign" native Windows, it's
|
||||
usually more robust, potentially faster, and it's easier to control how your
|
||||
GUI interacts with the video. You can do your own OpenGL rendering on top of
|
||||
the video as well.
|
||||
|
||||
qml_direct
|
||||
----------
|
||||
### qml
|
||||
|
||||
Shows how to use mpv's OpenGL video renderer in QtQuick2 with QML. Uses the
|
||||
opengl-cb API for video. Since the video is a normal QML element, it's trivial
|
||||
to create OSD overlays with QML-native graphical elements as well.
|
||||
|
||||
### qml_direct
|
||||
|
||||
Alternative example, which typically avoids a FBO indirection. Might be
|
||||
slightly faster, but is less flexible and harder to use.
|
||||
slightly faster, but is less flexible and harder to use. In particular, the
|
||||
video is not a normal QML element. Uses the opengl-cb API for video.
|
||||
|
||||
sdl
|
||||
---
|
||||
### sdl
|
||||
|
||||
Show how to embed the mpv OpenGL renderer in SDL.
|
||||
|
||||
simple
|
||||
------
|
||||
|
||||
Very primitive terminal-only example. Shows some most basic API usage.
|
||||
Show how to embed the mpv OpenGL renderer in SDL. Uses the opengl-cb API for
|
||||
video.
|
||||
@@ -50,7 +50,7 @@ Item {
|
||||
text: "Make video look like on a Smart TV"
|
||||
onClicked: {
|
||||
if (checkbox.checked) {
|
||||
renderer.command(["vo_cmdline", "scale=sharpen3:scale-param1=5.0"])
|
||||
renderer.command(["vo_cmdline", "sharpen=5.0"])
|
||||
} else {
|
||||
renderer.command(["vo_cmdline", ""])
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <QApplication>
|
||||
#include "mainwindow.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
// Qt sets the locale in the QApplication constructor, but libmpv requires
|
||||
// the LC_NUMERIC category to be set to "C", so change it back.
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
MainWindow w;
|
||||
w.show();
|
||||
return a.exec();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "mainwindow.h"
|
||||
#include "mpvwidget.h"
|
||||
#include <QPushButton>
|
||||
#include <QSlider>
|
||||
#include <QLayout>
|
||||
#include <QFileDialog>
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
m_mpv = new MpvWidget(this);
|
||||
m_slider = new QSlider();
|
||||
m_slider->setOrientation(Qt::Horizontal);
|
||||
m_openBtn = new QPushButton("Open");
|
||||
m_playBtn = new QPushButton("Pause");
|
||||
QHBoxLayout *hb = new QHBoxLayout();
|
||||
hb->addWidget(m_openBtn);
|
||||
hb->addWidget(m_playBtn);
|
||||
QVBoxLayout *vl = new QVBoxLayout();
|
||||
vl->addWidget(m_mpv);
|
||||
vl->addWidget(m_slider);
|
||||
vl->addLayout(hb);
|
||||
setLayout(vl);
|
||||
connect(m_slider, SIGNAL(sliderMoved(int)), SLOT(seek(int)));
|
||||
connect(m_openBtn, SIGNAL(clicked()), SLOT(openMedia()));
|
||||
connect(m_playBtn, SIGNAL(clicked()), SLOT(pauseResume()));
|
||||
connect(m_mpv, SIGNAL(positionChanged(int)), m_slider, SLOT(setValue(int)));
|
||||
connect(m_mpv, SIGNAL(durationChanged(int)), this, SLOT(setSliderRange(int)));
|
||||
}
|
||||
|
||||
void MainWindow::openMedia()
|
||||
{
|
||||
QString file = QFileDialog::getOpenFileName(0, "Open a video");
|
||||
if (file.isEmpty())
|
||||
return;
|
||||
m_mpv->command(QStringList() << "loadfile" << file);
|
||||
}
|
||||
|
||||
void MainWindow::seek(int pos)
|
||||
{
|
||||
m_mpv->command(QVariantList() << "seek" << pos << "absolute");
|
||||
}
|
||||
|
||||
void MainWindow::pauseResume()
|
||||
{
|
||||
const bool paused = m_mpv->getProperty("pause").toBool();
|
||||
m_mpv->setProperty("pause", !paused);
|
||||
}
|
||||
|
||||
void MainWindow::setSliderRange(int duration)
|
||||
{
|
||||
const int time0 = m_mpv->getProperty("time-start").toInt();
|
||||
m_slider->setRange(time0, time0+duration);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef MainWindow_H
|
||||
#define MainWindow_H
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
class MpvWidget;
|
||||
class QSlider;
|
||||
class QPushButton;
|
||||
class MainWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
public Q_SLOTS:
|
||||
void openMedia();
|
||||
void seek(int pos);
|
||||
void pauseResume();
|
||||
private Q_SLOTS:
|
||||
void setSliderRange(int duration);
|
||||
private:
|
||||
MpvWidget *m_mpv;
|
||||
QSlider *m_slider;
|
||||
QPushButton *m_openBtn;
|
||||
QPushButton *m_playBtn;
|
||||
};
|
||||
|
||||
#endif // MainWindow_H
|
||||
@@ -0,0 +1,129 @@
|
||||
#include "mpvwidget.h"
|
||||
#include <stdexcept>
|
||||
#include <QtGui/QOpenGLContext>
|
||||
#include <QtCore/QMetaObject>
|
||||
|
||||
static void wakeup(void *ctx)
|
||||
{
|
||||
QMetaObject::invokeMethod((MpvWidget*)ctx, "on_mpv_events", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
static void *get_proc_address(void *ctx, const char *name) {
|
||||
Q_UNUSED(ctx);
|
||||
QOpenGLContext *glctx = QOpenGLContext::currentContext();
|
||||
if (!glctx)
|
||||
return NULL;
|
||||
return (void *)glctx->getProcAddress(QByteArray(name));
|
||||
}
|
||||
|
||||
MpvWidget::MpvWidget(QWidget *parent, Qt::WindowFlags f)
|
||||
: QOpenGLWidget(parent, f)
|
||||
{
|
||||
mpv = mpv::qt::Handle::FromRawHandle(mpv_create());
|
||||
if (!mpv)
|
||||
throw std::runtime_error("could not create mpv context");
|
||||
|
||||
mpv_set_option_string(mpv, "terminal", "yes");
|
||||
mpv_set_option_string(mpv, "msg-level", "all=v");
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
throw std::runtime_error("could not initialize mpv context");
|
||||
|
||||
// Make use of the MPV_SUB_API_OPENGL_CB API.
|
||||
mpv::qt::set_option_variant(mpv, "vo", "opengl-cb");
|
||||
|
||||
// Request hw decoding, just for testing.
|
||||
mpv::qt::set_option_variant(mpv, "hwdec", "auto");
|
||||
|
||||
mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
|
||||
if (!mpv_gl)
|
||||
throw std::runtime_error("OpenGL not compiled in");
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, MpvWidget::on_update, (void *)this);
|
||||
connect(this, SIGNAL(frameSwapped()), SLOT(swapped()));
|
||||
|
||||
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
|
||||
mpv_set_wakeup_callback(mpv, wakeup, this);
|
||||
}
|
||||
|
||||
MpvWidget::~MpvWidget()
|
||||
{
|
||||
makeCurrent();
|
||||
if (mpv_gl)
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, NULL, NULL);
|
||||
// Until this call is done, we need to make sure the player remains
|
||||
// alive. This is done implicitly with the mpv::qt::Handle instance
|
||||
// in this class.
|
||||
mpv_opengl_cb_uninit_gl(mpv_gl);
|
||||
}
|
||||
|
||||
void MpvWidget::command(const QVariant& params)
|
||||
{
|
||||
mpv::qt::command_variant(mpv, params);
|
||||
}
|
||||
|
||||
void MpvWidget::setProperty(const QString& name, const QVariant& value)
|
||||
{
|
||||
mpv::qt::set_property_variant(mpv, name, value);
|
||||
}
|
||||
|
||||
QVariant MpvWidget::getProperty(const QString &name) const
|
||||
{
|
||||
return mpv::qt::get_property_variant(mpv, name);
|
||||
}
|
||||
|
||||
void MpvWidget::initializeGL()
|
||||
{
|
||||
int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL);
|
||||
if (r < 0)
|
||||
throw std::runtime_error("could not initialize OpenGL");
|
||||
}
|
||||
|
||||
void MpvWidget::paintGL()
|
||||
{
|
||||
mpv_opengl_cb_draw(mpv_gl, QOpenGLContext::currentContext()->defaultFramebufferObject(), width(), -height());
|
||||
}
|
||||
|
||||
void MpvWidget::swapped()
|
||||
{
|
||||
mpv_opengl_cb_report_flip(mpv_gl, 0);
|
||||
}
|
||||
|
||||
void MpvWidget::on_mpv_events()
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
void MpvWidget::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;
|
||||
Q_EMIT positionChanged(time);
|
||||
}
|
||||
} else if (strcmp(prop->name, "duration") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double time = *(double *)prop->data;
|
||||
Q_EMIT durationChanged(time);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: ;
|
||||
// Ignore uninteresting or unknown events.
|
||||
}
|
||||
}
|
||||
|
||||
void MpvWidget::on_update(void *ctx)
|
||||
{
|
||||
QMetaObject::invokeMethod((MpvWidget*)ctx, "update");
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef PLAYERWINDOW_H
|
||||
#define PLAYERWINDOW_H
|
||||
|
||||
#include <QtWidgets/QOpenGLWidget>
|
||||
#include <mpv/client.h>
|
||||
#include <mpv/opengl_cb.h>
|
||||
#include <mpv/qthelper.hpp>
|
||||
|
||||
class MpvWidget Q_DECL_FINAL: public QOpenGLWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MpvWidget(QWidget *parent = 0, Qt::WindowFlags f = 0);
|
||||
~MpvWidget();
|
||||
void command(const QVariant& params);
|
||||
void setProperty(const QString& name, const QVariant& value);
|
||||
QVariant getProperty(const QString& name) const;
|
||||
QSize sizeHint() const { return QSize(480, 270);}
|
||||
Q_SIGNALS:
|
||||
void durationChanged(int value);
|
||||
void positionChanged(int value);
|
||||
protected:
|
||||
void initializeGL() Q_DECL_OVERRIDE;
|
||||
void paintGL() Q_DECL_OVERRIDE;
|
||||
private Q_SLOTS:
|
||||
void swapped();
|
||||
void on_mpv_events();
|
||||
private:
|
||||
void handle_mpv_event(mpv_event *event);
|
||||
static void on_update(void *ctx);
|
||||
|
||||
mpv::qt::Handle mpv;
|
||||
mpv_opengl_cb_context *mpv_gl;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // PLAYERWINDOW_H
|
||||
@@ -0,0 +1,13 @@
|
||||
CONFIG -= app_bundle
|
||||
QT += widgets
|
||||
|
||||
QT_CONFIG -= no-pkg-config
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG += mpv
|
||||
|
||||
HEADERS = \
|
||||
mpvwidget.h \
|
||||
mainwindow.h
|
||||
SOURCES = main.cpp \
|
||||
mpvwidget.cpp \
|
||||
mainwindow.cpp
|
||||
@@ -19,6 +19,17 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.12.0 ---
|
||||
- remove --use-text-osd (useless; fontconfig isn't a requirement anymore,
|
||||
and text rendering is also lazily initialized)
|
||||
- some time properties (at least "playback-time", "time-pos",
|
||||
"time-remaining", "playtime-remaining") now are unavailable if the time
|
||||
is unknown, instead of just assuming that the internal playback position
|
||||
is 0
|
||||
- add --audio-fallback-to-null option
|
||||
- replace vf_format outputlevels suboption with "video-output-levels" global
|
||||
property/option; also remove "colormatrix-output-range" property
|
||||
- vo_opengl: remove sharpen3/sharpen5 scale filters, add sharpen sub-option
|
||||
--- mpv 0.11.0 ---
|
||||
- add "af-metadata" property
|
||||
--- mpv 0.10.0 ---
|
||||
|
||||
+1
-1
@@ -164,7 +164,7 @@ Available filters are:
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``mpv --af=channels=4:[0-1,1-0,0-2,1-3] media.avi``
|
||||
``mpv --af=channels=4:[0-1,1-0,2-2,3-3] media.avi``
|
||||
Would change the number of channels to 4 and set up 4 routes that
|
||||
swap channel 0 and channel 1 and leave channel 2 and 3 intact.
|
||||
Observe that if media containing two channels were played back,
|
||||
|
||||
+2
-5
@@ -1171,9 +1171,6 @@ Property list
|
||||
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``.
|
||||
|
||||
``hr-seek`` (RW)
|
||||
See ``--hr-seek``.
|
||||
|
||||
@@ -1260,8 +1257,8 @@ Property list
|
||||
``colormatrix-input-range`` (R)
|
||||
See ``colormatrix``.
|
||||
|
||||
``colormatrix-output-range`` (R)
|
||||
See ``colormatrix``.
|
||||
``video-output-levels`` (RW)
|
||||
See ``--video-output-levels``,
|
||||
|
||||
``colormatrix-primaries`` (R)
|
||||
See ``colormatrix``.
|
||||
|
||||
+67
-36
@@ -456,16 +456,13 @@ Program Behavior
|
||||
|
||||
If the script can't do anything with an URL, it will do nothing.
|
||||
|
||||
(Note: this is the replacement for the now removed libquvi support.)
|
||||
|
||||
``--ytdl-format=<best|worst|mp4|webm|...>``
|
||||
Video format/quality that is directly passed to youtube-dl. The possible
|
||||
values are specific to the website and the video, for a given url the
|
||||
available formats can be found with the command
|
||||
``youtube-dl --list-formats URL``. See youtube-dl's documentation for
|
||||
available aliases. To use experimental DASH support for youtube, use
|
||||
``bestvideo+bestaudio``.
|
||||
(Default: ``best``)
|
||||
available aliases.
|
||||
(Default: youtube-dl's default, currently ``bestvideo+bestaudio/best``)
|
||||
|
||||
``--ytdl-raw-options=<key>=<value>[,<key>=<value>[,...]]``
|
||||
Pass arbitrary options to youtube-dl. Parameter and argument should be
|
||||
@@ -567,12 +564,11 @@ Video
|
||||
:no: always use software decoding (default)
|
||||
:auto: see below
|
||||
:vdpau: requires ``--vo=vdpau`` or ``--vo=opengl`` (Linux only)
|
||||
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux with Intel GPUs only)
|
||||
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux only)
|
||||
:vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
|
||||
:vda: requires ``--vo=opengl`` (OS X only)
|
||||
:videotoolbox: requires ``--vo=opengl`` (newer OS X only)
|
||||
:videotoolbox: requires ``--vo=opengl`` (OS X 10.8 and up only)
|
||||
:dxva2-copy: copies video back to system RAM (Windows only)
|
||||
:rpi: requires ``--vo=rpi`` (Raspberry Pi only - default if available)
|
||||
:rpi: requires ``--vo=rpi`` (Raspberry Pi only - default if available)
|
||||
|
||||
``auto`` tries to automatically enable hardware decoding using the first
|
||||
available method. This still depends what VO you are using. For example,
|
||||
@@ -581,6 +577,12 @@ Video
|
||||
work, it will always fall back to software decoding, instead of trying the
|
||||
next method (might matter on some Linux systems).
|
||||
|
||||
The ``vaapi`` mode, if used with ``--vo=opengl``, requires Mesa 11 and most
|
||||
likely works with Intel GPUs only. It also requires the opengl EGL backend
|
||||
(automatically used if available). You can also try the old GLX backend by
|
||||
forcing it with ``--vo=opengl:backend=x11``, but the vaapi/GLX interop is
|
||||
said to be slower than ``vaapi-copy``.
|
||||
|
||||
The ``vaapi-copy`` mode allows you to use vaapi with any VO. Because
|
||||
this copies the decoded video back to system RAM, it's likely less efficient
|
||||
than the ``vaapi`` mode.
|
||||
@@ -775,6 +777,26 @@ Video
|
||||
For audio-only playback, any value greater than 0 will quit playback
|
||||
immediately after initialization. The value 0 works as with video.
|
||||
|
||||
``--video-output-levels=<outputlevels>``
|
||||
RGB color levels used with YUV to RGB conversion. Normally, output devices
|
||||
such as PC monitors use full range color levels. However, some TVs and
|
||||
video monitors expect studio RGB levels. Providing full range output to a
|
||||
device expecting studio level input results in crushed blacks and whites,
|
||||
the reverse in dim gray blacks and dim whites.
|
||||
|
||||
Not all VOs support this option. Some will silently ignore it.
|
||||
|
||||
Available color ranges are:
|
||||
|
||||
:auto: automatic selection (equals to full range) (default)
|
||||
:limited: limited range (16-235 per component), studio levels
|
||||
:full: full range (0-255 per component), PC levels
|
||||
|
||||
.. note::
|
||||
|
||||
It is advisable to use your graphics driver's color range option
|
||||
instead, if available.
|
||||
|
||||
``--hwdec-codecs=<codec1,codec2,...|all>``
|
||||
Allow hardware decoding for a given list of codecs only. The special value
|
||||
``all`` always allows all codecs.
|
||||
@@ -801,6 +823,10 @@ Video
|
||||
The result is most likely broken decoding, but may also help if the
|
||||
detected or reported profiles are somehow incorrect.
|
||||
|
||||
``--vd-lavc-software-fallback=<yes|no>``
|
||||
Fallback to software decoding if the hardware-accelerated decoder fails
|
||||
(default: yes).
|
||||
|
||||
``--vd-lavc-bitexact``
|
||||
Only use bit-exact algorithms in all decoding steps (for codec testing).
|
||||
|
||||
@@ -892,6 +918,14 @@ Audio
|
||||
|
||||
Currently not implemented for most AOs.
|
||||
|
||||
``--audio-fallback-to-null=<yes|no>``
|
||||
If no audio device can be opened, behave as if ``--ao=null`` was given. This
|
||||
is useful in combination with ``--audio-device``: instead of causing an
|
||||
error if the selected device does not exist, the client API user (or a
|
||||
Lua script) could let playback continue normally, and check the
|
||||
``current-ao`` and ``audio-device-list`` properties to make high-level
|
||||
decisions about how to continue.
|
||||
|
||||
``--ao=<driver1[:suboption1[=value]:...],driver2,...[,]>``
|
||||
Specify a priority list of audio output drivers to be used. For
|
||||
interactive use one would normally specify a single one to use, but in
|
||||
@@ -2441,14 +2475,14 @@ OSD
|
||||
``--osd-duration=<time>``
|
||||
Set the duration of the OSD messages in ms (default: 1000).
|
||||
|
||||
``--osd-font=<pattern>``, ``--sub-text-font=<pattern>``
|
||||
``--osd-font=<name>``, ``--sub-text-font=<name>``
|
||||
Specify font to use for OSD and for subtitles that do not themselves
|
||||
specify a particular font. The default is ``sans-serif``.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
- ``--osd-font='Bitstream Vera Sans'``
|
||||
- ``--osd-font='Bitstream Vera Sans:style=Bold'`` (fontconfig pattern)
|
||||
- ``--osd-font='MS Comic Sans'``
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -2456,6 +2490,9 @@ OSD
|
||||
options) are ignored when ASS-subtitles are rendered, unless the
|
||||
``--no-sub-ass`` option is specified.
|
||||
|
||||
This used to support fontconfig patterns. Starting with libass 0.13.0,
|
||||
this stopped working.
|
||||
|
||||
``--osd-font-size=<size>``, ``--sub-text-font-size=<size>``
|
||||
Specify the OSD/sub font size. The unit is the size in scaled pixels at a
|
||||
window height of 720. The actual pixel size is scaled with the window
|
||||
@@ -2636,17 +2673,6 @@ OSD
|
||||
|
||||
Default: 0.
|
||||
|
||||
``--use-text-osd=<yes|no>``
|
||||
Disable text OSD rendering completely. (This includes the complete OSC as
|
||||
well.) This is mostly useful for avoiding loading fontconfig in situations
|
||||
where fontconfig does not behave well, and OSD is unused - this could for
|
||||
example allow GUI programs using libmpv to workaround fontconfig issues.
|
||||
|
||||
Note that selecting subtitles of any kind still initializes fontconfig.
|
||||
|
||||
Default: ``no``.
|
||||
|
||||
|
||||
Screenshot
|
||||
----------
|
||||
|
||||
@@ -3245,6 +3271,13 @@ Network
|
||||
Verify peer certificates when using TLS (e.g. with ``https://...``).
|
||||
(Silently fails with older FFmpeg or Libav versions.)
|
||||
|
||||
``--tls-cert-file``
|
||||
A file containing a certificate to use in the handshake with the
|
||||
peer.
|
||||
|
||||
``--tls-key-file``
|
||||
A file containing the private key for the certificate.
|
||||
|
||||
``--referrer=<string>``
|
||||
Specify a referrer path or URL for HTTP requests.
|
||||
|
||||
@@ -3430,6 +3463,11 @@ Miscellaneous
|
||||
:display-vdrop: Drop or repeat video frames to compensate desyncing
|
||||
video. (Although it should have the same effects as
|
||||
``audio``, the implementation is very different.)
|
||||
:display-adrop: Drop or repeat audio data to compensate desyncing
|
||||
video. See ``--video-sync-adrop-size``. This mode will
|
||||
cause severe audio artifacts if the real monitor
|
||||
refresh rate is too different from the reported or
|
||||
forced rate.
|
||||
:display-desync: Sync video to display, and let audio play on its own.
|
||||
:desync: Sync video according to system clock, and let audio play
|
||||
on its own.
|
||||
@@ -3461,6 +3499,13 @@ Miscellaneous
|
||||
frame dropping due to the audio "overshooting" and skipping multiple video
|
||||
frames before the sync logic can react.
|
||||
|
||||
``--video-sync-adrop-size=<value``
|
||||
For the ``--video-sync=display-adrop`` mode. This mode duplicates/drops
|
||||
audio data to keep audio in sync with video. To avoid audio artifacts on
|
||||
jitter (which would add/remove samples all the time), this is done in
|
||||
relatively large, fixed units, controlled by this option. The unit is
|
||||
seconds.
|
||||
|
||||
``--mf-fps=<value>``
|
||||
Framerate used when decoding from multiple PNG or JPEG files with ``mf://``
|
||||
(default: 1).
|
||||
@@ -3502,20 +3547,6 @@ Miscellaneous
|
||||
|
||||
.. warning:: Using realtime priority can cause system lockup.
|
||||
|
||||
``--pts-association-mode=<decode|sort|auto>``
|
||||
Select the method used to determine which container packet timestamp
|
||||
corresponds to a particular output frame from the video decoder. Normally
|
||||
you should not need to change this option.
|
||||
|
||||
:decoder: Use decoder reordering functionality. Unlike in classic MPlayer
|
||||
and mplayer2, this includes a DTS fallback. (Default.)
|
||||
:sort: Maintain a buffer of unused pts values and use the lowest value
|
||||
for the frame.
|
||||
:auto: Try to pick a working mode from the ones above automatically.
|
||||
|
||||
You can also try to use ``--no-correct-pts`` for files with completely
|
||||
broken timestamps.
|
||||
|
||||
``--force-media-title=<string>``
|
||||
Force the contents of the ``media-title`` property to this value. Useful
|
||||
for scripts which want to set a title, without overriding the user's
|
||||
|
||||
@@ -259,26 +259,6 @@ Available filters are:
|
||||
:limited: limited range (16-235 for luma, 16-240 for chroma)
|
||||
:full: full range (0-255 for both luma and chroma)
|
||||
|
||||
``<outputlevels>``
|
||||
RGB color levels used with YUV to RGB conversion. Normally, output devices
|
||||
such as PC monitors use full range color levels. However, some TVs and
|
||||
video monitors expect studio RGB levels. Providing full range output to a
|
||||
device expecting studio level input results in crushed blacks and whites,
|
||||
the reverse in dim gray blacks and dim whites.
|
||||
|
||||
The same limitations as with ``<colormatrix>`` apply.
|
||||
|
||||
Available color ranges are:
|
||||
|
||||
:auto: automatic selection (equals to full range) (default)
|
||||
:limited: limited range (16-235 per component), studio levels
|
||||
:full: full range (0-255 per component), PC levels
|
||||
|
||||
.. note::
|
||||
|
||||
It is advisable to use your graphics driver's color range option
|
||||
instead, if available.
|
||||
|
||||
``<primaries>``
|
||||
RGB primaries the source file was encoded with. Normally this should be set
|
||||
in the file header, but when playing broken or mistagged files this can be
|
||||
|
||||
+28
-19
@@ -20,13 +20,10 @@ normal driver parameters.
|
||||
|
||||
See ``--vo=help`` for a list of compiled-in video output drivers.
|
||||
|
||||
The recommended output drivers are ``--vo=vdpau`` and ``--vo=opengl-hq``.
|
||||
All other drivers are just for compatibility or special purposes.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``--vo=opengl,xv,``
|
||||
Try the ``opengl`` driver, then the ``xv`` driver, then others.
|
||||
The recommended output driver is ``--vo=opengl-hq``. All other drivers are
|
||||
for compatibility or special purposes. By default, ``--vo=opengl`` is used,
|
||||
but if that appears not to work, it fallback to other drivers (in the same
|
||||
order as listed by ``--vo=help``).
|
||||
|
||||
Available video output drivers are:
|
||||
|
||||
@@ -75,6 +72,12 @@ Available video output drivers are:
|
||||
not responding quickly enough if video FPS is close to or higher than
|
||||
the display refresh rate.
|
||||
|
||||
``x11`` (X11 only)
|
||||
Shared memory video output driver without hardware acceleration that works
|
||||
whenever X11 is present.
|
||||
|
||||
.. note:: This is a fallback only, and should not be normally used.
|
||||
|
||||
``vdpau`` (X11 only)
|
||||
Uses the VDPAU interface to display and optionally also decode video.
|
||||
Hardware decoding is used with ``--hwdec=vdpau``.
|
||||
@@ -375,10 +378,6 @@ Available video output drivers are:
|
||||
Scale parameter (``t``). Increasing this makes the result blurrier.
|
||||
Defaults to 1.
|
||||
|
||||
sharpen3, sharpen5
|
||||
Sharpening strength. Increasing this makes the image sharper but
|
||||
adds more ringing and aliasing. Defaults to 0.5.
|
||||
|
||||
oversample
|
||||
Minimum distance to an edge before interpolation is used. Setting
|
||||
this to 0 will always interpolate edges, whereas setting it to 0.5
|
||||
@@ -408,8 +407,8 @@ Available video output drivers are:
|
||||
between 0.0 and 1.0. The default value of 0.0 disables antiringing
|
||||
entirely.
|
||||
|
||||
Note that this doesn't affect the special filters ``bilinear``,
|
||||
``bicubic_fast`` or ``sharpen``.
|
||||
Note that this doesn't affect the special filters ``bilinear`` and
|
||||
``bicubic_fast``.
|
||||
|
||||
``scale-window=<window>``
|
||||
(Advanced users only) Choose a custom windowing function for the kernel.
|
||||
@@ -612,11 +611,8 @@ Available video output drivers are:
|
||||
``deband-iterations=<1..16>``
|
||||
The number of debanding steps to perform per sample. Each step reduces
|
||||
a bit more banding, but takes time to compute. Note that the strength
|
||||
of each step falls off very quickly, so high numbers are practically
|
||||
useless. (Default 4)
|
||||
|
||||
If the performance hit of debanding is too great, you can reduce this
|
||||
to 2 or 1 with marginal visual quality loss.
|
||||
of each step falls off very quickly, so high numbers (>4) are
|
||||
practically useless. (Default 1)
|
||||
|
||||
``deband-threshold=<0..4096>``
|
||||
The debanding filter's cut-off threshold. Higher numbers increase the
|
||||
@@ -626,7 +622,10 @@ Available video output drivers are:
|
||||
``deband-range=<1..64>``
|
||||
The debanding filter's initial radius. The radius increases linearly
|
||||
for each iteration. A higher radius will find more gradients, but
|
||||
a lower radius will smooth more aggressively. (Default 8)
|
||||
a lower radius will smooth more aggressively. (Default 16)
|
||||
|
||||
If you increase the ``deband-iterations``, you should probably
|
||||
decrease this to compensate.
|
||||
|
||||
``deband-grain=<0..4096>``
|
||||
Add some extra noise to the image. This significantly helps cover up
|
||||
@@ -645,6 +644,16 @@ Available video output drivers are:
|
||||
The slope of the sigmoid curve used for ``sigmoid-upscaling``, must
|
||||
be a float between 1.0 and 20.0. Defaults to 6.5 if not specified.
|
||||
|
||||
``sharpen=<value>``
|
||||
If set to a value other than 0, enable an unsharp masking filter.
|
||||
Positive values will sharpen the image (but add more ringing and
|
||||
aliasing). Negative values will blur the image. If your GPU is powerful
|
||||
enough, consider alternatives like the ``ewa_lanczossharp`` scale
|
||||
filter, or the ``scale-blur`` sub-option.
|
||||
|
||||
(This feature is the replacement for the old ``sharpen3`` and
|
||||
``sharpen5`` scalers.)
|
||||
|
||||
``glfinish``
|
||||
Call ``glFinish()`` before and after swapping buffers (default: disabled).
|
||||
Slower, but might help getting better results when doing framedropping.
|
||||
|
||||
@@ -187,7 +187,7 @@ only if discretion is required.
|
||||
[windows_compilation]: https://github.com/mpv-player/mpv/blob/master/DOCS/compile-windows.md
|
||||
[mplayer-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/mplayer-changes.rst
|
||||
|
||||
## License
|
||||
## License
|
||||
|
||||
|
||||
[GPLv2](https://github.com/mpv-player/mpv/blob/master/LICENSE)
|
||||
Mostly GPLv2 or later. See [details.](https://github.com/mpv-player/mpv/blob/master/Copyright)
|
||||
@@ -1,3 +1,91 @@
|
||||
Release 0.12.0
|
||||
==============
|
||||
|
||||
NOTE: This release changes the default format for youtube-dl and removes some
|
||||
workarounds related to MPEG DASH support. However, the required changes to
|
||||
FFmpeg code are not in any FFmpeg release yet (as of 2015-10-29), so for now,
|
||||
playing DASH streams requires using FFmpeg git master. You can work around
|
||||
this by using the --ytdl-format=best option.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- vo_opengl: support new VAAPI EGL interop (requires Mesa 11)
|
||||
- vo_opengl: vaapi: add Wayland support
|
||||
- bring back the x11 video output
|
||||
- vo_opengl: support all kinds of GBRP formats
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- video: remove VDA support (VideoToolbox is preferred)
|
||||
|
||||
Behavior
|
||||
--------
|
||||
|
||||
- vo_opengl: enable X11 EGL backend by default (disabled for NVIDIA due to driver bugs)
|
||||
- ytdl: don't override user-set format in no-video mode
|
||||
- sub: adjust behavior on mismatching video/subtitle aspect ratio
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- audio: add option for falling back to ao_null
|
||||
- options: add support for client certificate authentication
|
||||
- input: add key name for U+3000 IDEOGRAPHIC SPACE
|
||||
- player: add audio drop/duplicate mode to video-sync
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- vo_opengl: remove sharpen scalers, add sharpen sub-option
|
||||
- vo_opengl: make sw suboption work without explicit backend selection
|
||||
- command: make time properties unavailable if timestamp is unknown
|
||||
- command: do not return 0 for bitrate if unknown (make property unavailable instead)
|
||||
- vo_opengl: make the default debanding settings less excessive
|
||||
- ytdl: disable --all-subs if "sub-lang" is in raw-options
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- video: remove user-controllable PTS sorting (--pts-association-mode)
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- player: fix another --force-window bug
|
||||
- player: add wav to list of external audio file extensions
|
||||
- ao_alsa: fix failure to find any sample format
|
||||
- player: make stop command actually stop in all cases
|
||||
- audio: various fixes related to audio device hotplugging
|
||||
- vo_xv: fix crash with --wid
|
||||
- ytdl: Remove DASH hacks, use DASH by default
|
||||
- player: be slightly less prone to framedrop in display sync mode
|
||||
- sd_lavc: extend subtitle resolution if images go outside video frame
|
||||
- player: offset chapter display by start time
|
||||
- command: make bitrate properties work correctly for external tracks
|
||||
- w32_common: disable IME
|
||||
- player: fix display-sync A/V calculation on high playback speeds
|
||||
- player: fix display sync A/V difference estimation on drops
|
||||
- player: raise display sync desync tolerance
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API. A complete changelog can be seen by running
|
||||
`git log v0.11.0..v0.12.0` in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.11.0...v0.12.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.11.0..v0.12.0
|
||||
|
||||
|
||||
Release 0.11.0
|
||||
==============
|
||||
|
||||
|
||||
@@ -30,7 +30,12 @@ function add_files_at(index, files)
|
||||
end
|
||||
|
||||
function get_extension(path)
|
||||
return string.match(path, "%.([^%.]+)$" )
|
||||
match = string.match(path, "%.([^%.]+)$" )
|
||||
if match == nil then
|
||||
return "nomatch"
|
||||
else
|
||||
return match
|
||||
end
|
||||
end
|
||||
|
||||
table.filter = function(t, iter)
|
||||
|
||||
+23
-8
@@ -610,12 +610,13 @@ define_yes_no $_vdpau HAVE_VDPAU_HWACCEL
|
||||
check_pkg_config "VAAPI" $_vaapi VAAPI 'libva >= 0.32.0 libva-x11 >= 0.32.0'
|
||||
_vaapi=$(defretval)
|
||||
define_yes_no $_vaapi HAVE_VAAPI_HWACCEL
|
||||
define_yes_no $_vaapi HAVE_VAAPI_X11
|
||||
|
||||
_vaapi_wayland=no
|
||||
if test "$_vaapi" = yes ; then
|
||||
check_pkg_config "VAAPI VPP" auto VAAPI_VPP 'libva >= 0.34.0'
|
||||
else
|
||||
check_yes_no no VAAPI_VPP
|
||||
_vaapi_wayland=auto
|
||||
fi
|
||||
check_pkg_config "VAAPI Wayland" $_vaapi_wayland VAAPI_WAYLAND 'libva-wayland >= 0.34.0'
|
||||
|
||||
check_pkg_config "Xinerama" $_xinerama XINERAMA 'xinerama'
|
||||
|
||||
@@ -703,6 +704,11 @@ _vaapi_glx=no
|
||||
(test "$_gl_x11" = yes && test "$_vaapi" = yes) && _vaapi_glx=auto
|
||||
check_pkg_config "VAAPI with OpenGL/X11" $_vaapi_glx VAAPI_GLX 'libva-glx >= 0.32.0'
|
||||
|
||||
_vaapi_x_egl=no
|
||||
(test "$_gl_x11_egl" = yes && test "$_vaapi" = yes) && _vaapi_x_egl=yes
|
||||
check_yes_no $_vaapi_x_egl VAAPI_X_EGL
|
||||
check_yes_no $_vaapi_x_egl VAAPI_EGL
|
||||
|
||||
check_pkg_config "SDL 2.0" $_sdl2 SDL2 'sdl2'
|
||||
|
||||
check_statement_libs "OSS Audio" $_ossaudio OSS_AUDIO $_soundcard_header "int x = SNDCTL_DSP_SETFRAGMENT;"
|
||||
@@ -804,6 +810,18 @@ api_statement_check \
|
||||
libavutil/pixdesc.h \
|
||||
'AVComponentDescriptor d; int x = d.depth'
|
||||
|
||||
api_statement_check \
|
||||
"libavcodec 64 bit AVPacket.duration" \
|
||||
HAVE_AV_AVPACKET_INT64_DURATION \
|
||||
libavcodec/avcodec.h \
|
||||
'int x[(int)sizeof(((AVPacket){0}).duration) - 7]'
|
||||
|
||||
api_statement_check \
|
||||
"libavcodec AVSubtitleRect AVPicture removal" \
|
||||
HAVE_AV_SUBTITLE_NOPICT \
|
||||
libavcodec/avcodec.h \
|
||||
'AVSubtitleRect r = {.linesize={0}}'
|
||||
|
||||
check_pkg_config "libavfilter" $_libavfilter LIBAVFILTER 'libavfilter >= 5.0.0'
|
||||
|
||||
check_pkg_config "libavdevice" $_libavdevice LIBAVDEVICE 'libavdevice >= 55.0.0'
|
||||
@@ -926,10 +944,6 @@ cat > $TMPC << EOF
|
||||
for platforms not supported by this configure script */
|
||||
#define HAVE_BSD_FSTATFS 0
|
||||
#define HAVE_LINUX_FSTATFS 1
|
||||
#define HAVE_VDA_AV_VDA_ALLOC_CONTEXT 0
|
||||
#define HAVE_VDA_HWACCEL 0
|
||||
#define HAVE_VDA_LIBAVCODEC_REFCOUNTING 0
|
||||
#define HAVE_VDA_GL 0
|
||||
#define HAVE_GL_COCOA 0
|
||||
#define HAVE_COCOA 0
|
||||
#define HAVE_COCOA_APPLICATION 0
|
||||
@@ -958,7 +972,8 @@ cat > $TMPC << EOF
|
||||
#define HAVE_DRM 0
|
||||
#define HAVE_VIDEOTOOLBOX_HWACCEL 0
|
||||
#define HAVE_VIDEOTOOLBOX_GL 0
|
||||
#define HAVE_VIDEOTOOLBOX_VDA_GL 0
|
||||
#define HAVE_SSE4_INTRINSICS 1
|
||||
#define HAVE_C11_TLS 1
|
||||
|
||||
#ifdef __OpenBSD__
|
||||
#define DEFAULT_CDROM_DEVICE "/dev/rcd0c"
|
||||
|
||||
+5
-2
@@ -88,11 +88,13 @@ SOURCES-$(VDPAU) += video/vdpau.c video/vdpau_mixer.c \
|
||||
SOURCES-$(VDPAU_GL_X11) += video/out/opengl/hwdec_vdpau.c
|
||||
SOURCES-$(VAAPI) += video/out/vo_vaapi.c \
|
||||
video/decode/vaapi.c \
|
||||
video/filter/vf_vavpp.c \
|
||||
video/vaapi.c
|
||||
SOURCES-$(VAAPI_VPP) += video/filter/vf_vavpp.c
|
||||
SOURCES-$(VAAPI_GLX) += video/out/opengl/hwdec_vaglx.c
|
||||
SOURCES-$(VAAPI_X_EGL) += video/out/opengl/hwdec_vaegl.c
|
||||
|
||||
SOURCES-$(XV) += video/out/x11_common.c video/out/vo_xv.c
|
||||
SOURCES-$(X11) += video/out/vo_x11.c video/out/x11_common.c
|
||||
SOURCES-$(XV) += video/out/vo_xv.c
|
||||
SOURCES-$(WAYLAND) += video/out/vo_wayland.c \
|
||||
video/out/wayland_common.c \
|
||||
video/out/wayland/buffer.c \
|
||||
@@ -233,6 +235,7 @@ SOURCES = audio/audio.c \
|
||||
ta/ta_talloc.c \
|
||||
video/csputils.c \
|
||||
video/fmt-conversion.c \
|
||||
video/gpu_memcpy.c \
|
||||
video/image_writer.c \
|
||||
video/img_format.c \
|
||||
video/mp_image.c \
|
||||
|
||||
+26
-14
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import matplotlib.pyplot as plot
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
import pyqtgraph as pg
|
||||
import sys
|
||||
import re
|
||||
|
||||
@@ -38,8 +39,8 @@ Currently, the following event types are supported:
|
||||
class G:
|
||||
events = {}
|
||||
start = None
|
||||
# http://matplotlib.org/api/markers_api.html#module-matplotlib.markers
|
||||
markers = ["o", "8", "s", "p", "*", "h", "+", "x", "D"]
|
||||
markers = ["o", "s", "t", "d"]
|
||||
curveno = 0
|
||||
|
||||
def find_marker():
|
||||
if len(G.markers) == 0:
|
||||
@@ -128,20 +129,31 @@ for e, index in zip(G.sevents, range(len(G.sevents))):
|
||||
else:
|
||||
e.vals = [(x, y * (m - index) / m) for (x, y) in e.vals]
|
||||
|
||||
fig = plot.figure()
|
||||
fig.hold(True)
|
||||
pg.setConfigOption('background', 'w')
|
||||
pg.setConfigOption('foreground', 'k')
|
||||
app = QtGui.QApplication([])
|
||||
win = pg.GraphicsWindow()
|
||||
#win.resize(1500, 900)
|
||||
|
||||
ax = [None, None]
|
||||
plots = 2 if hasval else 1
|
||||
ax[0] = fig.add_subplot(plots, 1, 1)
|
||||
ax[0] = win.addPlot()
|
||||
if hasval:
|
||||
ax[1] = fig.add_subplot(plots, 1, 2, sharex=ax[0])
|
||||
legends = [[], []]
|
||||
win.nextRow()
|
||||
ax[1] = win.addPlot()
|
||||
ax[1].setXLink(ax[0])
|
||||
for cur in ax:
|
||||
if cur is not None:
|
||||
cur.addLegend(offset = (-1, 1))
|
||||
for e in G.sevents:
|
||||
cur = ax[1 if e.type == "value" else 0]
|
||||
pl, = cur.plot([x for x,y in e.vals], [y for x,y in e.vals], label=e.name)
|
||||
args = {'name': e.name,'antialias':True}
|
||||
if e.type == "event-signal":
|
||||
plot.setp(pl, marker = e.marker, linestyle = "None")
|
||||
for cur in ax:
|
||||
if cur is not None:
|
||||
cur.legend()
|
||||
plot.show()
|
||||
args['symbol'] = e.marker
|
||||
args['pen'] = None
|
||||
else:
|
||||
args['pen'] = pg.mkPen(pg.intColor(G.curveno), width=0)
|
||||
G.curveno += 1
|
||||
n = cur.plot([x for x,y in e.vals], [y for x,y in e.vals], **args)
|
||||
|
||||
QtGui.QApplication.instance().exec_()
|
||||
+7
-5
@@ -323,21 +323,23 @@ struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe)
|
||||
}
|
||||
|
||||
// If we can't handle the format (e.g. too many channels), bail out.
|
||||
if (!mp_audio_config_valid(new) || avframe->nb_extended_buf)
|
||||
if (!mp_audio_config_valid(new))
|
||||
goto fail;
|
||||
|
||||
for (int n = 0; n < AV_NUM_DATA_POINTERS; n++) {
|
||||
if (!avframe->buf[n])
|
||||
for (int n = 0; n < AV_NUM_DATA_POINTERS + avframe->nb_extended_buf; n++) {
|
||||
AVBufferRef *buf = n < AV_NUM_DATA_POINTERS ? avframe->buf[n]
|
||||
: avframe->extended_buf[n - AV_NUM_DATA_POINTERS];
|
||||
if (!buf)
|
||||
break;
|
||||
if (n >= MP_NUM_CHANNELS)
|
||||
goto fail;
|
||||
new->allocated[n] = av_buffer_ref(avframe->buf[n]);
|
||||
new->allocated[n] = av_buffer_ref(buf);
|
||||
if (!new->allocated[n])
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (int n = 0; n < new->num_planes; n++)
|
||||
new->planes[n] = avframe->data[n];
|
||||
new->planes[n] = avframe->extended_data[n];
|
||||
new->samples = avframe->nb_samples;
|
||||
|
||||
return new;
|
||||
|
||||
@@ -119,6 +119,15 @@ void mp_audio_buffer_prepend_silence(struct mp_audio_buffer *ab, int samples)
|
||||
mp_audio_fill_silence(ab->buffer, 0, samples);
|
||||
}
|
||||
|
||||
void mp_audio_buffer_duplicate(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= ab->buffer->samples);
|
||||
int oldlen = ab->buffer->samples;
|
||||
ab->buffer->samples += samples;
|
||||
mp_audio_realloc_min(ab->buffer, ab->buffer->samples);
|
||||
mp_audio_copy(ab->buffer, oldlen, ab->buffer, oldlen - samples, samples);
|
||||
}
|
||||
|
||||
// Get the start of the current readable buffer.
|
||||
void mp_audio_buffer_peek(struct mp_audio_buffer *ab, struct mp_audio *out_mpa)
|
||||
{
|
||||
|
||||
@@ -35,6 +35,7 @@ void mp_audio_buffer_get_write_buffer(struct mp_audio_buffer *ab, int minsamples
|
||||
void mp_audio_buffer_finish_write(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_append(struct mp_audio_buffer *ab, struct mp_audio *mpa);
|
||||
void mp_audio_buffer_prepend_silence(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_duplicate(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_peek(struct mp_audio_buffer *ab, struct mp_audio *out_mpa);
|
||||
void mp_audio_buffer_skip(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_clear(struct mp_audio_buffer *ab);
|
||||
|
||||
+10
-2
@@ -298,12 +298,18 @@ void mp_chmap_remove_useless_channels(struct mp_chmap *map,
|
||||
// Speakers not representable by ffmpeg/libav are dropped.
|
||||
// Warning: this ignores the order of the channels, and will return a channel
|
||||
// mask even if the order is different from libavcodec's.
|
||||
// Also, "unknown" channel maps are translated to non-sense channel
|
||||
// maps with the same number of channels.
|
||||
uint64_t mp_chmap_to_lavc_unchecked(const struct mp_chmap *src)
|
||||
{
|
||||
// lavc has no concept for unknown layouts yet, so pick a default
|
||||
struct mp_chmap t = *src;
|
||||
if (t.num > 64)
|
||||
return 0;
|
||||
// lavc has no concept for unknown layouts yet, so pick something that does
|
||||
// the job of signaling the number of channels, even if it makes no sense
|
||||
// as a proper layout.
|
||||
if (mp_chmap_is_unknown(&t))
|
||||
mp_chmap_from_channels(&t, t.num);
|
||||
return t.num == 64 ? (uint64_t)-1 : (1ULL << t.num) - 1;
|
||||
uint64_t mask = 0;
|
||||
for (int n = 0; n < t.num; n++) {
|
||||
if (t.speaker[n] < 64) // ignore MP_SPEAKER_ID_NA etc.
|
||||
@@ -359,6 +365,8 @@ bool mp_chmap_is_lavc(const struct mp_chmap *src)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Warning: for "unknown" channel maps, this returns something that may not
|
||||
// make sense. Invalid channel maps are not changed.
|
||||
void mp_chmap_reorder_to_lavc(struct mp_chmap *map)
|
||||
{
|
||||
if (!mp_chmap_is_valid(map))
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
#include <stdbool.h>
|
||||
#include "misc/bstr.h"
|
||||
|
||||
#define MP_NUM_CHANNELS 8
|
||||
#define MP_NUM_CHANNELS 16
|
||||
|
||||
// Speaker a channel can be assigned to.
|
||||
// This corresponds to WAVEFORMATEXTENSIBLE channel mask bit indexes.
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
#include "chmap_sel.h"
|
||||
|
||||
static const struct mp_chmap speaker_replacements[][2] = {
|
||||
@@ -357,3 +358,24 @@ bool mp_chmap_sel_get_def(const struct mp_chmap_sel *s, struct mp_chmap *map,
|
||||
}
|
||||
return map->num > 0;
|
||||
}
|
||||
|
||||
// Print the set of allowed channel layouts.
|
||||
void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev)
|
||||
{
|
||||
if (!mp_msg_test(log, lev))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < s->num_chmaps; i++)
|
||||
mp_msg(log, lev, " - %s\n", mp_chmap_to_str(&s->chmaps[i]));
|
||||
for (int i = 0; i < MP_SPEAKER_ID_COUNT; i++) {
|
||||
if (!s->speakers[i])
|
||||
continue;
|
||||
struct mp_chmap l = {.num = 1, .speaker = { i }};
|
||||
mp_msg(log, lev, " - #%s\n",
|
||||
i == MP_SPEAKER_ID_FC ? "fc" : mp_chmap_to_str_hr(&l));
|
||||
}
|
||||
if (s->allow_waveext)
|
||||
mp_msg(log, lev, " - waveext\n");
|
||||
if (s->allow_any)
|
||||
mp_msg(log, lev, " - anything\n");
|
||||
}
|
||||
@@ -45,4 +45,7 @@ bool mp_chmap_sel_fallback(const struct mp_chmap_sel *s, struct mp_chmap *map);
|
||||
bool mp_chmap_sel_get_def(const struct mp_chmap_sel *s, struct mp_chmap *map,
|
||||
int num);
|
||||
|
||||
struct mp_log;
|
||||
void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev);
|
||||
|
||||
#endif
|
||||
@@ -30,7 +30,7 @@ struct dec_audio {
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
bool spdif_passthrough;
|
||||
bool spdif_passthrough, spdif_failed;
|
||||
const struct ad_functions *ad_driver;
|
||||
struct sh_stream *header;
|
||||
struct af_stream *afilter;
|
||||
|
||||
+2
-1
@@ -461,7 +461,8 @@ static int af_reinit(struct af_stream *s)
|
||||
struct af_instance *aft = af->prev;
|
||||
af_remove(s, af);
|
||||
af = aft->next;
|
||||
break;
|
||||
retry++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
goto negotiate_error;
|
||||
|
||||
@@ -146,7 +146,7 @@ static void uninit(struct af_instance* af)
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
if (s) {
|
||||
av_free_packet(&s->pkt);
|
||||
av_packet_unref(&s->pkt);
|
||||
if(s->lavc_actx) {
|
||||
avcodec_close(s->lavc_actx);
|
||||
av_free(s->lavc_actx);
|
||||
|
||||
@@ -195,6 +195,10 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
if (af_to_avformat(in->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT);
|
||||
|
||||
// Removing this requires fixing AVFrame.data vs. AVFrame.extended_data
|
||||
if (in->channels.num > AV_NUM_DATA_POINTERS)
|
||||
return AF_ERROR;
|
||||
|
||||
if (!mp_chmap_is_lavc(&in->channels))
|
||||
mp_chmap_reorder_to_lavc(&in->channels); // will always work
|
||||
|
||||
@@ -211,7 +215,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
mp_chmap_from_lavc(&out_cm, l_out->channel_layout);
|
||||
mp_audio_set_channels(out, &out_cm);
|
||||
|
||||
if (!mp_audio_config_valid(out))
|
||||
if (!mp_audio_config_valid(out) || out->channels.num > AV_NUM_DATA_POINTERS)
|
||||
return AF_ERROR;
|
||||
|
||||
p->timebase_out = l_out->time_base;
|
||||
@@ -349,8 +353,6 @@ static int af_open(struct af_instance *af)
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
// Removing this requires fixing AVFrame.data vs. AVFrame.extended_data
|
||||
assert(MP_NUM_CHANNELS <= AV_NUM_DATA_POINTERS);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#define avresample_convert(ctx, out, out_planesize, out_samples, in, in_planesize, in_samples) \
|
||||
swr_convert(ctx, out, out_samples, (const uint8_t**)(in), in_samples)
|
||||
#define avresample_set_channel_mapping swr_set_channel_mapping
|
||||
#define avresample_set_compensation swr_set_compensation
|
||||
#else
|
||||
#error "config.h broken or no resampler found"
|
||||
#endif
|
||||
@@ -90,6 +91,8 @@ struct af_resample {
|
||||
int out_rate;
|
||||
int out_format;
|
||||
struct mp_chmap out_channels;
|
||||
|
||||
double missing_samples; // fractional samples not yet output
|
||||
};
|
||||
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
@@ -223,6 +226,8 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
s->out_channels= out->channels;
|
||||
s->in_channels = in->channels;
|
||||
|
||||
s->missing_samples = 0;
|
||||
|
||||
av_opt_set_int(s->avrctx, "filter_size", s->opts.filter_size, 0);
|
||||
av_opt_set_int(s->avrctx, "phase_shift", s->opts.phase_shift, 0);
|
||||
av_opt_set_int(s->avrctx, "linear_interp", s->opts.linear, 0);
|
||||
@@ -304,14 +309,15 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
av_opt_set_int(s->avrctx, "in_sample_fmt", in_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx, "out_sample_fmt", out_samplefmtp, 0);
|
||||
|
||||
// Just needs the correct number of channels.
|
||||
int fake_out_ch_layout = av_get_default_channel_layout(map_out.num);
|
||||
// Just needs the correct number of channels for deplanarization.
|
||||
struct mp_chmap fake_chmap;
|
||||
mp_chmap_set_unknown(&fake_chmap, map_out.num);
|
||||
uint64_t fake_out_ch_layout = mp_chmap_to_lavc_unchecked(&fake_chmap);
|
||||
if (!fake_out_ch_layout)
|
||||
goto error;
|
||||
|
||||
// Deplanarize if needed.
|
||||
av_opt_set_int(s->avrctx_out, "in_channel_layout", fake_out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_channel_layout", fake_out_ch_layout, 0);
|
||||
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_fmt", out_samplefmtp, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_sample_fmt", out_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_rate", s->out_rate, 0);
|
||||
@@ -386,14 +392,6 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
|
||||
s->playback_speed = *(double *)arg;
|
||||
int new_rate = rate_from_speed(s->in_rate_af, s->playback_speed);
|
||||
if (new_rate != s->in_rate && s->avrctx && af->fmt_out.format) {
|
||||
// Before reconfiguring, drain the audio that is still buffered
|
||||
// in the resampler.
|
||||
af->filter_frame(af, NULL);
|
||||
// Reinitialize resampler.
|
||||
configure_lavrr(af, &af->fmt_in, &af->fmt_out, false);
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
@@ -455,7 +453,7 @@ static void reorder_planes(struct mp_audio *mpa, int *reorder,
|
||||
}
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
static int filter_resample(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
@@ -516,6 +514,36 @@ error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
int new_rate = rate_from_speed(s->in_rate_af, s->playback_speed);
|
||||
bool need_reinit = fabs(new_rate / (double)s->in_rate - 1) > 0.01;
|
||||
|
||||
if (!need_reinit && s->avrctx) {
|
||||
double speed_factor = s->playback_speed * s->in_rate_af / s->in_rate;
|
||||
int in_samples = in ? in->samples : 0;
|
||||
double wanted_samples = in_samples / speed_factor + s->missing_samples;
|
||||
int wanted_samples_i = lrint(wanted_samples);
|
||||
s->missing_samples = wanted_samples - wanted_samples_i;
|
||||
if (avresample_set_compensation(s->avrctx,
|
||||
(wanted_samples_i - in_samples) * s->out_rate / s->in_rate,
|
||||
wanted_samples_i * s->out_rate / s->in_rate) < 0)
|
||||
need_reinit = true;
|
||||
}
|
||||
|
||||
if (need_reinit && new_rate != s->in_rate) {
|
||||
// Before reconfiguring, drain the audio that is still buffered
|
||||
// in the resampler.
|
||||
filter_resample(af, NULL);
|
||||
// Reinitialize resampler.
|
||||
configure_lavrr(af, &af->fmt_in, &af->fmt_out, false);
|
||||
}
|
||||
|
||||
return filter_resample(af, in);
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
+52
-49
@@ -240,6 +240,7 @@ static void split_ao_device(void *tmp, char *opt, char **out_ao, char **out_dev)
|
||||
}
|
||||
|
||||
struct ao *ao_init_best(struct mpv_global *global,
|
||||
bool ao_null_fallback,
|
||||
struct input_ctx *input_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels)
|
||||
@@ -248,58 +249,67 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
void *tmp = talloc_new(NULL);
|
||||
struct mp_log *log = mp_log_new(tmp, global->log, "ao");
|
||||
struct ao *ao = NULL;
|
||||
struct m_obj_settings *ao_list = opts->audio_driver_list;
|
||||
struct m_obj_settings *ao_list = NULL;
|
||||
int ao_num = 0;
|
||||
|
||||
for (int n = 0; opts->audio_driver_list && opts->audio_driver_list[n].name; n++)
|
||||
MP_TARRAY_APPEND(tmp, ao_list, ao_num, opts->audio_driver_list[n]);
|
||||
|
||||
bool forced_dev = false;
|
||||
char *pref_ao, *pref_dev;
|
||||
split_ao_device(tmp, opts->audio_device, &pref_ao, &pref_dev);
|
||||
if (!(ao_list && ao_list[0].name) && pref_ao) {
|
||||
if (!ao_num && pref_ao) {
|
||||
// Reuse the autoselection code
|
||||
ao_list = talloc_zero_array(tmp, struct m_obj_settings, 2);
|
||||
ao_list[0].name = pref_ao;
|
||||
MP_TARRAY_APPEND(tmp, ao_list, ao_num,
|
||||
(struct m_obj_settings){.name = pref_ao});
|
||||
forced_dev = true;
|
||||
}
|
||||
|
||||
if (ao_list && ao_list[0].name) {
|
||||
for (int n = 0; ao_list[n].name; n++) {
|
||||
if (strlen(ao_list[n].name) == 0)
|
||||
goto autoprobe;
|
||||
mp_verbose(log, "Trying preferred audio driver '%s'\n",
|
||||
ao_list[n].name);
|
||||
char *dev = NULL;
|
||||
if (pref_ao && strcmp(ao_list[n].name, pref_ao) == 0)
|
||||
dev = pref_dev;
|
||||
if (dev)
|
||||
mp_verbose(log, "Using preferred device '%s'\n", dev);
|
||||
ao = ao_init(false, global, input_ctx, encode_lavc_ctx,
|
||||
samplerate, format, channels, dev,
|
||||
ao_list[n].name, ao_list[n].attribs);
|
||||
if (ao)
|
||||
goto done;
|
||||
mp_err(log, "Failed to initialize audio driver '%s'\n",
|
||||
ao_list[n].name);
|
||||
if (forced_dev) {
|
||||
mp_err(log, "This audio driver/device was forced with the "
|
||||
"--audio-device option.\n"
|
||||
"Try unsetting it.\n");
|
||||
}
|
||||
bool autoprobe = ao_num == 0;
|
||||
|
||||
// Something like "--ao=a,b," means do autoprobing after a and b fail.
|
||||
if (ao_num && strlen(ao_list[ao_num - 1].name) == 0) {
|
||||
ao_num -= 1;
|
||||
autoprobe = true;
|
||||
}
|
||||
|
||||
if (autoprobe) {
|
||||
for (int n = 0; audio_out_drivers[n]; n++) {
|
||||
const struct ao_driver *driver = audio_out_drivers[n];
|
||||
if (driver == &audio_out_null)
|
||||
break;
|
||||
MP_TARRAY_APPEND(tmp, ao_list, ao_num,
|
||||
(struct m_obj_settings){.name = (char *)driver->name});
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
autoprobe: ;
|
||||
// now try the rest...
|
||||
for (int i = 0; audio_out_drivers[i]; i++) {
|
||||
const struct ao_driver *driver = audio_out_drivers[i];
|
||||
if (driver == &audio_out_null)
|
||||
break;
|
||||
ao = ao_init(true, global, input_ctx, encode_lavc_ctx, samplerate,
|
||||
format, channels, NULL, (char *)driver->name, NULL);
|
||||
if (ao_null_fallback) {
|
||||
MP_TARRAY_APPEND(tmp, ao_list, ao_num,
|
||||
(struct m_obj_settings){.name = "null"});
|
||||
}
|
||||
|
||||
for (int n = 0; n < ao_num; n++) {
|
||||
struct m_obj_settings *entry = &ao_list[n];
|
||||
bool probing = n + 1 != ao_num;
|
||||
mp_verbose(log, "Trying audio driver '%s'\n", entry->name);
|
||||
char *dev = NULL;
|
||||
if (pref_ao && pref_dev && strcmp(entry->name, pref_ao) == 0) {
|
||||
dev = pref_dev;
|
||||
mp_verbose(log, "Using preferred device '%s'\n", dev);
|
||||
}
|
||||
ao = ao_init(probing, global, input_ctx, encode_lavc_ctx,
|
||||
samplerate, format, channels, dev,
|
||||
entry->name, entry->attribs);
|
||||
if (ao)
|
||||
goto done;
|
||||
break;
|
||||
if (!probing)
|
||||
mp_err(log, "Failed to initialize audio driver '%s'\n", entry->name);
|
||||
if (dev && forced_dev) {
|
||||
mp_err(log, "This audio driver/device was forced with the "
|
||||
"--audio-device option.\nTry unsetting it.\n");
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
talloc_free(tmp);
|
||||
return ao;
|
||||
}
|
||||
@@ -411,18 +421,11 @@ void ao_hotplug_event(struct ao *ao)
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map)
|
||||
{
|
||||
if (mp_msg_test(ao->log, MSGL_DEBUG)) {
|
||||
for (int i = 0; i < s->num_chmaps; i++) {
|
||||
struct mp_chmap c = s->chmaps[i];
|
||||
MP_DBG(ao, "chmap_sel #%d: %s (%s)\n", i, mp_chmap_to_str(&c),
|
||||
mp_chmap_to_str_hr(&c));
|
||||
}
|
||||
}
|
||||
MP_VERBOSE(ao, "Channel layouts:\n");
|
||||
mp_chmal_sel_log(s, ao->log, MSGL_V);
|
||||
bool r = mp_chmap_sel_adjust(s, map);
|
||||
if (r) {
|
||||
MP_DBG(ao, "result: %s (%s)\n", mp_chmap_to_str(map),
|
||||
mp_chmap_to_str_hr(map));
|
||||
}
|
||||
if (r)
|
||||
MP_VERBOSE(ao, "result: %s\n", mp_chmap_to_str(map));
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ struct encode_lavc_context;
|
||||
struct mp_audio;
|
||||
|
||||
struct ao *ao_init_best(struct mpv_global *global,
|
||||
bool ao_null_fallback,
|
||||
struct input_ctx *input_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels);
|
||||
|
||||
+31
-10
@@ -50,6 +50,7 @@
|
||||
|
||||
struct priv {
|
||||
snd_pcm_t *alsa;
|
||||
bool device_lost;
|
||||
snd_pcm_format_t alsa_fmt;
|
||||
int can_pause;
|
||||
snd_pcm_sframes_t prepause_frames;
|
||||
@@ -83,6 +84,21 @@ struct priv {
|
||||
MP_WARN(ao, "%s: %s\n", (message), snd_strerror(err)); \
|
||||
} while (0)
|
||||
|
||||
// Common code for handling ENODEV, which happens if a device gets "lost", and
|
||||
// can't be used anymore. Returns true if alsa_err is not ENODEV.
|
||||
static bool check_device_present(struct ao *ao, int alsa_err)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (alsa_err != -ENODEV)
|
||||
return true;
|
||||
if (!p->device_lost) {
|
||||
MP_WARN(ao, "Device lost, trying to recover...\n");
|
||||
ao_request_reload(ao);
|
||||
p->device_lost = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
@@ -312,9 +328,9 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
if (mp_chmap_is_valid(&entry)) {
|
||||
if (maps[i]->type == SND_CHMAP_TYPE_VAR)
|
||||
mp_chmap_reorder_norm(&entry);
|
||||
MP_VERBOSE(ao, "Got supported channel map: %s (type %s)\n",
|
||||
mp_chmap_to_str(&entry),
|
||||
snd_pcm_chmap_type_name(maps[i]->type));
|
||||
MP_DBG(ao, "Got supported channel map: %s (type %s)\n",
|
||||
mp_chmap_to_str(&entry),
|
||||
snd_pcm_chmap_type_name(maps[i]->type));
|
||||
mp_chmap_sel_add_map(&chmap_sel, &entry);
|
||||
} else {
|
||||
char tmp[128];
|
||||
@@ -462,16 +478,20 @@ static int init_device(struct ao *ao, bool second_try)
|
||||
err = snd_pcm_hw_params_any(p->alsa, alsa_hwparams);
|
||||
CHECK_ALSA_ERROR("Unable to get initial parameters");
|
||||
|
||||
bool found_format = false;
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, try_formats);
|
||||
for (int n = 0; try_formats[n]; n++) {
|
||||
ao->format = try_formats[n];
|
||||
p->alsa_fmt = find_alsa_format(ao->format);
|
||||
if (snd_pcm_hw_params_test_format(p->alsa, alsa_hwparams, p->alsa_fmt) >= 0)
|
||||
p->alsa_fmt = find_alsa_format(try_formats[n]);
|
||||
MP_VERBOSE(ao, "trying format %s\n", af_fmt_to_str(try_formats[n]));
|
||||
if (snd_pcm_hw_params_test_format(p->alsa, alsa_hwparams, p->alsa_fmt) >= 0) {
|
||||
ao->format = try_formats[n];
|
||||
found_format = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ao->format) {
|
||||
if (!found_format) {
|
||||
MP_ERR(ao, "Can't find appropriate sample format.\n");
|
||||
goto alsa_error;
|
||||
}
|
||||
@@ -730,6 +750,8 @@ static int get_space(struct ao *ao)
|
||||
snd_pcm_status_alloca(&status);
|
||||
|
||||
err = snd_pcm_status(p->alsa, status);
|
||||
if (!check_device_present(ao, err))
|
||||
goto alsa_error;
|
||||
CHECK_ALSA_ERROR("cannot get pcm status");
|
||||
|
||||
unsigned space = snd_pcm_status_get_avail(status);
|
||||
@@ -856,9 +878,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
|
||||
if (res == -EINTR || res == -EAGAIN) { /* retry */
|
||||
res = 0;
|
||||
} else if (res == -ENODEV) {
|
||||
MP_WARN(ao, "Device lost, trying to recover...\n");
|
||||
ao_request_reload(ao);
|
||||
} else if (!check_device_present(ao, res)) {
|
||||
goto alsa_error;
|
||||
} else if (res < 0) {
|
||||
if (res == -ESTRPIPE) { /* suspend */
|
||||
resume_device(ao);
|
||||
|
||||
@@ -180,6 +180,8 @@ static void init_physical_format(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
OSErr err;
|
||||
|
||||
void *tmp = talloc_new(NULL);
|
||||
|
||||
AudioStreamBasicDescription asbd;
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
|
||||
@@ -190,6 +192,8 @@ static void init_physical_format(struct ao *ao)
|
||||
&streams, &n_streams);
|
||||
CHECK_CA_ERROR("could not get number of streams");
|
||||
|
||||
talloc_steal(tmp, streams);
|
||||
|
||||
MP_VERBOSE(ao, "Found %zd substream(s).\n", n_streams);
|
||||
|
||||
for (int i = 0; i < n_streams; i++) {
|
||||
@@ -204,6 +208,7 @@ static void init_physical_format(struct ao *ao)
|
||||
if (!CHECK_CA_WARN("could not get number of stream formats"))
|
||||
continue; // try next one
|
||||
|
||||
talloc_steal(tmp, formats);
|
||||
|
||||
uint32_t direction;
|
||||
err = CA_GET(streams[i], kAudioStreamPropertyDirection, &direction);
|
||||
@@ -232,6 +237,12 @@ static void init_physical_format(struct ao *ao)
|
||||
&p->original_asbd);
|
||||
CHECK_CA_WARN("could not get current physical stream format");
|
||||
|
||||
if (ca_asbd_equals(&p->original_asbd, &best_asbd)) {
|
||||
MP_VERBOSE(ao, "Requested format already set, not changing.\n");
|
||||
p->original_asbd.mFormatID = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ca_change_physical_format_sync(ao, streams[i], best_asbd))
|
||||
p->original_asbd = (AudioStreamBasicDescription){0};
|
||||
break;
|
||||
@@ -239,6 +250,7 @@ static void init_physical_format(struct ao *ao)
|
||||
}
|
||||
|
||||
coreaudio_error:
|
||||
talloc_free(tmp);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,9 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
|
||||
AudioChannelLayout *r;
|
||||
OSStatus err;
|
||||
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelDescriptions)
|
||||
return l;
|
||||
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
|
||||
uint32_t psize;
|
||||
err = AudioFormatGetPropertyInfo(
|
||||
@@ -109,7 +112,7 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
|
||||
kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(uint32_t), &l->mChannelBitmap, &psize, r);
|
||||
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (get)");
|
||||
} else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
|
||||
} else {
|
||||
uint32_t psize;
|
||||
err = AudioFormatGetPropertyInfo(
|
||||
kAudioFormatProperty_ChannelLayoutForTag,
|
||||
@@ -120,10 +123,11 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
|
||||
kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize, r);
|
||||
CHECK_CA_ERROR("failed to convert channel tag to descriptions (get)");
|
||||
} else {
|
||||
r = l;
|
||||
}
|
||||
|
||||
MP_VERBOSE(ao, "converted input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_V, l);
|
||||
|
||||
return r;
|
||||
coreaudio_error:
|
||||
return NULL;
|
||||
@@ -134,22 +138,20 @@ static bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
{
|
||||
void *talloc_ctx = talloc_new(NULL);
|
||||
|
||||
MP_DBG(ao, "input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_DEBUG, layout);
|
||||
MP_VERBOSE(ao, "input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_V, layout);
|
||||
|
||||
AudioChannelLayout *l = ca_layout_to_custom_layout(ao, talloc_ctx, layout);
|
||||
if (!l)
|
||||
goto coreaudio_error;
|
||||
|
||||
MP_VERBOSE(ao, "converted input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_V, l);
|
||||
|
||||
if (l->mNumberChannelDescriptions > MP_NUM_CHANNELS) {
|
||||
MP_VERBOSE(ao, "layout has too many descriptions (%u, max: %d)\n",
|
||||
(unsigned) l->mNumberChannelDescriptions, MP_NUM_CHANNELS);
|
||||
return false;
|
||||
}
|
||||
|
||||
chmap->num = l->mNumberChannelDescriptions;
|
||||
for (int n = 0; n < l->mNumberChannelDescriptions; n++) {
|
||||
AudioChannelLabel label = l->mChannelDescriptions[n].mChannelLabel;
|
||||
int speaker = ca_label_to_mp_speaker_id(label);
|
||||
@@ -157,14 +159,13 @@ static bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
MP_VERBOSE(ao, "channel label=%u unusable to build channel "
|
||||
"bitmap, skipping layout\n", (unsigned) label);
|
||||
goto coreaudio_error;
|
||||
} else {
|
||||
chmap->speaker[n] = speaker;
|
||||
chmap->num = n + 1;
|
||||
}
|
||||
chmap->speaker[n] = speaker;
|
||||
}
|
||||
|
||||
talloc_free(talloc_ctx);
|
||||
return chmap->num > 0;
|
||||
MP_VERBOSE(ao, "mp chmap: %s\n", mp_chmap_to_str(chmap));
|
||||
return mp_chmap_is_valid(chmap) && !mp_chmap_is_unknown(chmap);
|
||||
coreaudio_error:
|
||||
MP_VERBOSE(ao, "converted input channel layout (failed):\n");
|
||||
ca_log_layout(ao, MSGL_V, layout);
|
||||
@@ -234,34 +235,68 @@ coreaudio_error:
|
||||
return r;
|
||||
}
|
||||
|
||||
bool ca_init_chmap(struct ao *ao, AudioDeviceID device)
|
||||
static void ca_retrieve_layouts(struct ao *ao, struct mp_chmap_sel *s,
|
||||
AudioDeviceID device)
|
||||
{
|
||||
void *ta_ctx = talloc_new(NULL);
|
||||
|
||||
struct mp_chmap_sel chmap_sel = {.tmp = ta_ctx};
|
||||
struct mp_chmap chmap = {0};
|
||||
|
||||
mp_chmap_sel_add_map(&chmap_sel, &(struct mp_chmap)MP_CHMAP_INIT_MONO);
|
||||
struct mp_chmap chmap;
|
||||
|
||||
AudioChannelLayout *ml = ca_query_layout(ao, device, ta_ctx);
|
||||
if (ml && ca_layout_to_mp_chmap(ao, ml, &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
mp_chmap_sel_add_map(s, &chmap);
|
||||
|
||||
AudioChannelLayout *sl = ca_query_stereo_layout(ao, device, ta_ctx);
|
||||
if (sl && ca_layout_to_mp_chmap(ao, sl, &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
mp_chmap_sel_add_map(s, &chmap);
|
||||
|
||||
talloc_free(ta_ctx);
|
||||
}
|
||||
|
||||
bool ca_init_chmap(struct ao *ao, AudioDeviceID device)
|
||||
{
|
||||
struct mp_chmap_sel chmap_sel = {0};
|
||||
ca_retrieve_layouts(ao, &chmap_sel, device);
|
||||
|
||||
if (!chmap_sel.num_chmaps)
|
||||
mp_chmap_sel_add_map(&chmap_sel, &(struct mp_chmap)MP_CHMAP_INIT_STEREO);
|
||||
|
||||
mp_chmap_sel_add_map(&chmap_sel, &(struct mp_chmap)MP_CHMAP_INIT_MONO);
|
||||
|
||||
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 false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ca_get_active_chmap(struct ao *ao, AudioDeviceID device, int channel_count,
|
||||
struct mp_chmap *out_map)
|
||||
{
|
||||
// Apparently, we have to guess by looking back at the supported layouts,
|
||||
// and I haven't found a property that retrieves the actual currently
|
||||
// active channel layout.
|
||||
|
||||
struct mp_chmap_sel chmap_sel = {0};
|
||||
ca_retrieve_layouts(ao, &chmap_sel, device);
|
||||
|
||||
// Use any exact match.
|
||||
for (int n = 0; n < chmap_sel.num_chmaps; n++) {
|
||||
if (chmap_sel.chmaps[n].num == channel_count) {
|
||||
MP_VERBOSE(ao, "mismatching channels - fallback #%d\n", n);
|
||||
*out_map = chmap_sel.chmaps[n];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(ta_ctx);
|
||||
return true;
|
||||
|
||||
coreaudio_error:
|
||||
talloc_free(ta_ctx);
|
||||
return false;
|
||||
// Fall back to stereo or mono, and fill the rest with silence. (We don't
|
||||
// know what the device expects. We could use a larger default layout here,
|
||||
// but let's not.)
|
||||
mp_chmap_from_channels(out_map, MPMIN(2, channel_count));
|
||||
out_map->num = channel_count;
|
||||
for (int n = 2; n < out_map->num; n++)
|
||||
out_map->speaker[n] = MP_SPEAKER_ID_NA;
|
||||
MP_WARN(ao, "mismatching channels - falling back to %s\n",
|
||||
mp_chmap_to_str(out_map));
|
||||
}
|
||||
@@ -20,6 +20,10 @@
|
||||
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
struct mp_chmap;
|
||||
|
||||
bool ca_init_chmap(struct ao *ao, AudioDeviceID device);
|
||||
void ca_get_active_chmap(struct ao *ao, AudioDeviceID device, int channel_count,
|
||||
struct mp_chmap *out_map);
|
||||
|
||||
#endif
|
||||
@@ -300,6 +300,12 @@ static int init(struct ao *ao)
|
||||
|
||||
ca_print_asbd(ao, "virtual format", &p->stream_asbd);
|
||||
|
||||
if (p->stream_asbd.mChannelsPerFrame > MP_NUM_CHANNELS) {
|
||||
MP_ERR(ao, "unsupported number of channels: %d > %d.\n",
|
||||
p->stream_asbd.mChannelsPerFrame, MP_NUM_CHANNELS);
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
int new_format = ca_asbd_to_mp_format(&p->stream_asbd);
|
||||
|
||||
// If both old and new formats are spdif, avoid changing it due to the
|
||||
@@ -315,7 +321,10 @@ static int init(struct ao *ao)
|
||||
ao->samplerate = p->stream_asbd.mSampleRate;
|
||||
|
||||
if (ao->channels.num != p->stream_asbd.mChannelsPerFrame) {
|
||||
// We really expect that ca_init_chmap() fixes the layout to the HW's.
|
||||
ca_get_active_chmap(ao, p->device, p->stream_asbd.mChannelsPerFrame,
|
||||
&ao->channels);
|
||||
}
|
||||
if (!ao->channels.num) {
|
||||
MP_ERR(ao, "number of channels changed, and unknown channel layout!\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,9 @@ static bool ca_is_output_device(struct ao *ao, AudioDeviceID dev)
|
||||
size_t n_buffers;
|
||||
AudioBufferList *buffers;
|
||||
const ca_scope scope = kAudioDevicePropertyStreamConfiguration;
|
||||
CA_GET_ARY_O(dev, scope, &buffers, &n_buffers);
|
||||
OSStatus err = CA_GET_ARY_O(dev, scope, &buffers, &n_buffers);
|
||||
if (err != noErr)
|
||||
return false;
|
||||
talloc_free(buffers);
|
||||
return n_buffers > 0;
|
||||
}
|
||||
@@ -71,11 +73,16 @@ void ca_get_device_list(struct ao *ao, struct ao_device_list *list)
|
||||
char *name;
|
||||
char *desc;
|
||||
err = CA_GET_STR(devs[i], kAudioDevicePropertyDeviceUID, &name);
|
||||
if (err != noErr) {
|
||||
MP_VERBOSE(ao, "skipping device %d, which has no UID\n", i);
|
||||
talloc_free(ta_ctx);
|
||||
continue;
|
||||
}
|
||||
talloc_steal(ta_ctx, name);
|
||||
err = CA_GET_STR(devs[i], kAudioObjectPropertyName, &desc);
|
||||
talloc_steal(ta_ctx, desc);
|
||||
if (err != noErr)
|
||||
desc = "Unknown";
|
||||
desc = talloc_strdup(NULL, "Unknown");
|
||||
talloc_steal(ta_ctx, desc);
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc});
|
||||
talloc_free(ta_ctx);
|
||||
}
|
||||
@@ -498,6 +505,8 @@ bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &prev_format);
|
||||
CHECK_CA_ERROR("can't get current physical format");
|
||||
|
||||
ca_print_asbd(ao, "format in use before switching:", &prev_format);
|
||||
|
||||
/* Install the callback. */
|
||||
AudioObjectPropertyAddress p_addr = {
|
||||
.mSelector = kAudioStreamPropertyPhysicalFormat,
|
||||
@@ -516,7 +525,7 @@ bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
|
||||
/* The AudioStreamSetProperty is not only asynchronous,
|
||||
* it is also not Atomic, in its behaviour. */
|
||||
struct timespec timeout = mp_rel_time_to_timespec(0.5);
|
||||
struct timespec timeout = mp_rel_time_to_timespec(2.0);
|
||||
AudioStreamBasicDescription actual_format = {0};
|
||||
while (1) {
|
||||
err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &actual_format);
|
||||
|
||||
@@ -164,6 +164,9 @@ static int init(struct ao *ao)
|
||||
|
||||
ao->untimed = true;
|
||||
|
||||
if (ao->channels.num > AV_NUM_DATA_POINTERS)
|
||||
goto fail;
|
||||
|
||||
pthread_mutex_unlock(&ao->encode_lavc_ctx->lock);
|
||||
return 0;
|
||||
|
||||
|
||||
@@ -486,10 +486,8 @@ static bool search_channels(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
}
|
||||
} else {
|
||||
change_waveformat_channels(wformat, &entry);
|
||||
if (try_format_exclusive(ao, wformat)) {
|
||||
if (try_format_exclusive(ao, wformat))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &entry);
|
||||
MP_VERBOSE(ao, "%s is supported\n", mp_chmap_to_str(&entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st)
|
||||
avctx->block_align = st->block_align;
|
||||
avctx->channel_layout = st->channel_layout;
|
||||
avctx->bits_per_coded_sample = st->bits_per_coded_sample;
|
||||
avctx->has_b_frames = st->has_b_frames;
|
||||
}
|
||||
|
||||
// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it.
|
||||
|
||||
+2
-4
@@ -326,10 +326,8 @@ static void write_msg_to_buffers(struct mp_log *log, int lev, char *text)
|
||||
static void dump_stats(struct mp_log *log, int lev, char *text)
|
||||
{
|
||||
struct mp_log_root *root = log->root;
|
||||
if (lev == MSGL_STATS && root->stats_file) {
|
||||
fprintf(root->stats_file, "%"PRId64" %s #%s\n", mp_time_us(), text,
|
||||
log->verbose_prefix);
|
||||
}
|
||||
if (lev == MSGL_STATS && root->stats_file)
|
||||
fprintf(root->stats_file, "%"PRId64" %s\n", mp_time_us(), text);
|
||||
}
|
||||
|
||||
void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va)
|
||||
|
||||
+23
-9
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "misc/bstr.h"
|
||||
#include "common/common.h"
|
||||
#include "common/tags.h"
|
||||
|
||||
#include "cue.h"
|
||||
|
||||
@@ -37,6 +38,7 @@ enum cue_command {
|
||||
CUE_TRACK,
|
||||
CUE_INDEX,
|
||||
CUE_TITLE,
|
||||
CUE_PERFORMER,
|
||||
};
|
||||
|
||||
static const struct {
|
||||
@@ -51,7 +53,7 @@ static const struct {
|
||||
{ CUE_UNUSED, "CDTEXTFILE" },
|
||||
{ CUE_UNUSED, "FLAGS" },
|
||||
{ CUE_UNUSED, "ISRC" },
|
||||
{ CUE_UNUSED, "PERFORMER" },
|
||||
{ CUE_PERFORMER, "PERFORMER" },
|
||||
{ CUE_UNUSED, "POSTGAP" },
|
||||
{ CUE_UNUSED, "PREGAP" },
|
||||
{ CUE_UNUSED, "REM" },
|
||||
@@ -160,17 +162,19 @@ bool mp_probe_cue(struct bstr data)
|
||||
struct cue_file *mp_parse_cue(struct bstr data)
|
||||
{
|
||||
struct cue_file *f = talloc_zero(NULL, struct cue_file);
|
||||
f->tags = talloc_zero(f, struct mp_tags);
|
||||
|
||||
data = skip_utf8_bom(data);
|
||||
|
||||
char *filename = NULL;
|
||||
// Global metadata, and copied into new tracks.
|
||||
struct cue_track proto_track = {0};
|
||||
struct cue_track *cur_track = &proto_track;
|
||||
struct cue_track *cur_track = NULL;
|
||||
|
||||
while (data.len) {
|
||||
struct bstr param;
|
||||
switch (read_cmd(&data, ¶m)) {
|
||||
int cmd = read_cmd(&data, ¶m);
|
||||
switch (cmd) {
|
||||
case CUE_ERROR:
|
||||
talloc_free(f);
|
||||
return NULL;
|
||||
@@ -179,19 +183,29 @@ struct cue_file *mp_parse_cue(struct bstr data)
|
||||
f->num_tracks += 1;
|
||||
cur_track = &f->tracks[f->num_tracks - 1];
|
||||
*cur_track = proto_track;
|
||||
cur_track->tags = talloc_zero(f, struct mp_tags);
|
||||
break;
|
||||
}
|
||||
case CUE_TITLE:
|
||||
cur_track->title = read_quoted(f, ¶m);
|
||||
case CUE_PERFORMER: {
|
||||
static const char *metanames[] = {
|
||||
[CUE_TITLE] = "title",
|
||||
[CUE_PERFORMER] = "performer",
|
||||
};
|
||||
struct mp_tags *tags = cur_track ? cur_track->tags : f->tags;
|
||||
mp_tags_set_bstr(tags, bstr0(metanames[cmd]), param);
|
||||
break;
|
||||
}
|
||||
case CUE_INDEX: {
|
||||
int type = read_int_2(¶m);
|
||||
double time = read_time(¶m);
|
||||
if (type == 1) {
|
||||
cur_track->start = time;
|
||||
cur_track->filename = filename;
|
||||
} else if (type == 0) {
|
||||
cur_track->pregap_start = time;
|
||||
if (cur_track) {
|
||||
if (type == 1) {
|
||||
cur_track->start = time;
|
||||
cur_track->filename = filename;
|
||||
} else if (type == 0) {
|
||||
cur_track->pregap_start = time;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+2
-1
@@ -25,6 +25,7 @@
|
||||
struct cue_file {
|
||||
struct cue_track *tracks;
|
||||
int num_tracks;
|
||||
struct mp_tags *tags;
|
||||
};
|
||||
|
||||
struct cue_track {
|
||||
@@ -32,7 +33,7 @@ struct cue_track {
|
||||
double start; // corresponds to INDEX 01
|
||||
char *filename;
|
||||
int source;
|
||||
char *title;
|
||||
struct mp_tags *tags;
|
||||
};
|
||||
|
||||
bool mp_probe_cue(struct bstr data);
|
||||
|
||||
+13
-12
@@ -307,13 +307,12 @@ const char *stream_type_name(enum stream_type type)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the same value as demuxer->fill_buffer: 1 ok, 0 EOF/not selected.
|
||||
int demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
||||
void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
||||
{
|
||||
struct demux_stream *ds = stream ? stream->ds : NULL;
|
||||
if (!dp || !ds) {
|
||||
talloc_free(dp);
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
struct demux_internal *in = ds->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
@@ -330,7 +329,7 @@ int demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
||||
if (!ds->selected || in->seeking || drop) {
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
talloc_free(dp);
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
dp->stream = stream->index;
|
||||
@@ -371,7 +370,6 @@ int demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
||||
ds->in->wakeup_cb(ds->in->wakeup_cb_ctx);
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Returns true if there was "progress" (lock was released temporarily).
|
||||
@@ -430,10 +428,8 @@ static bool read_packet(struct demux_internal *in)
|
||||
pthread_mutex_lock(&in->lock);
|
||||
|
||||
if (eof) {
|
||||
for (int n = 0; n < in->d_buffer->num_streams; n++) {
|
||||
struct demux_stream *ds = in->d_buffer->streams[n]->ds;
|
||||
ds->eof = true;
|
||||
}
|
||||
for (int n = 0; n < in->d_buffer->num_streams; n++)
|
||||
in->d_buffer->streams[n]->ds->eof = true;
|
||||
// If we had EOF previously, then don't wakeup (avoids wakeup loop)
|
||||
if (!in->last_eof) {
|
||||
if (in->wakeup_cb)
|
||||
@@ -924,7 +920,8 @@ static void demux_init_cuesheet(struct demuxer *demuxer)
|
||||
if (f) {
|
||||
for (int n = 0; n < f->num_tracks; n++) {
|
||||
struct cue_track *t = &f->tracks[n];
|
||||
demuxer_add_chapter(demuxer, t->title, t->start, -1);
|
||||
int idx = demuxer_add_chapter(demuxer, "", t->start, -1);
|
||||
mp_tags_merge(demuxer->chapters[idx].metadata, t->tags);
|
||||
}
|
||||
}
|
||||
talloc_free(f);
|
||||
@@ -938,6 +935,9 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
struct demuxer_params *params,
|
||||
enum demux_check check)
|
||||
{
|
||||
if (mp_cancel_test(stream->cancel))
|
||||
return NULL;
|
||||
|
||||
struct demuxer *demuxer = talloc_ptrtype(NULL, demuxer);
|
||||
*demuxer = (struct demuxer) {
|
||||
.desc = desc,
|
||||
@@ -1392,10 +1392,11 @@ static int cached_demux_control(struct demux_internal *in, int cmd, void *arg)
|
||||
case DEMUXER_CTRL_GET_BITRATE_STATS: {
|
||||
double *rates = arg;
|
||||
for (int n = 0; n < STREAM_TYPE_COUNT; n++)
|
||||
rates[n] = 0;
|
||||
rates[n] = -1;
|
||||
for (int n = 0; n < in->d_user->num_streams; n++) {
|
||||
struct demux_stream *ds = in->d_user->streams[n]->ds;
|
||||
rates[ds->type] += MPMAX(0, ds->bitrate);
|
||||
if (ds->selected && ds->bitrate >= 0)
|
||||
rates[ds->type] = MPMAX(0, rates[ds->type]) + ds->bitrate;
|
||||
}
|
||||
return DEMUXER_CTRL_OK;
|
||||
}
|
||||
|
||||
+1
-1
@@ -242,7 +242,7 @@ typedef struct {
|
||||
void free_demuxer(struct demuxer *demuxer);
|
||||
void free_demuxer_and_stream(struct demuxer *demuxer);
|
||||
|
||||
int demux_add_packet(struct sh_stream *stream, demux_packet_t *dp);
|
||||
void demux_add_packet(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);
|
||||
|
||||
+15
-15
@@ -38,7 +38,7 @@
|
||||
#define PROBE_SIZE 512
|
||||
|
||||
struct priv {
|
||||
bstr data;
|
||||
struct cue_file *f;
|
||||
};
|
||||
|
||||
static void add_source(struct timeline *tl, struct demuxer *d)
|
||||
@@ -150,15 +150,8 @@ static void build_timeline(struct timeline *tl)
|
||||
|
||||
add_source(tl, tl->demuxer);
|
||||
|
||||
struct cue_file *f = mp_parse_cue(p->data);
|
||||
if (!f) {
|
||||
MP_ERR(tl, "CUE: error parsing input file!\n");
|
||||
goto out;
|
||||
}
|
||||
talloc_steal(ctx, f);
|
||||
|
||||
struct cue_track *tracks = f->tracks;
|
||||
size_t track_count = f->num_tracks;
|
||||
struct cue_track *tracks = p->f->tracks;
|
||||
size_t track_count = p->f->num_tracks;
|
||||
|
||||
if (track_count == 0) {
|
||||
MP_ERR(tl, "CUE: no tracks found!\n");
|
||||
@@ -224,10 +217,8 @@ static void build_timeline(struct timeline *tl)
|
||||
};
|
||||
chapters[i] = (struct demux_chapter) {
|
||||
.pts = timeline[i].start,
|
||||
.metadata = talloc_zero(tl, struct mp_tags),
|
||||
.metadata = mp_tags_dup(tl, tracks[i].tags),
|
||||
};
|
||||
// might want to include other metadata here
|
||||
mp_tags_set_str(chapters[i].metadata, "title", tracks[i].title);
|
||||
starttime += duration;
|
||||
}
|
||||
|
||||
@@ -261,9 +252,18 @@ static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
struct priv *p = talloc_zero(demuxer, struct priv);
|
||||
demuxer->priv = p;
|
||||
demuxer->fully_read = true;
|
||||
p->data = stream_read_complete(s, demuxer, 1000000);
|
||||
if (p->data.start == NULL)
|
||||
|
||||
bstr data = stream_read_complete(s, p, 1000000);
|
||||
if (data.start == NULL)
|
||||
return -1;
|
||||
p->f = mp_parse_cue(data);
|
||||
talloc_steal(p, p->f);
|
||||
if (!p->f) {
|
||||
MP_ERR(demuxer, "error parsing input file!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
mp_tags_merge(demuxer->metadata, p->f->tags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+14
-5
@@ -635,7 +635,10 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
sh->ff_index = st->index;
|
||||
sh->codec = mp_codec_from_av_codec_id(codec->codec_id);
|
||||
sh->codec_tag = codec->codec_tag;
|
||||
sh->lav_headers = codec;
|
||||
sh->lav_headers = avcodec_alloc_context3(NULL);
|
||||
if (!sh->lav_headers)
|
||||
return;
|
||||
mp_copy_lav_codec_headers(sh->lav_headers, codec);
|
||||
|
||||
if (st->disposition & AV_DISPOSITION_DEFAULT)
|
||||
sh->default_track = true;
|
||||
@@ -850,7 +853,7 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
AVPacket *pkt = &(AVPacket){0};
|
||||
int r = av_read_frame(priv->avfc, pkt);
|
||||
if (r < 0) {
|
||||
av_free_packet(pkt);
|
||||
av_packet_unref(pkt);
|
||||
if (r == AVERROR(EAGAIN))
|
||||
return 1;
|
||||
if (r == AVERROR_EOF)
|
||||
@@ -867,13 +870,13 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
AVStream *st = priv->avfc->streams[pkt->stream_index];
|
||||
|
||||
if (!demux_stream_is_selected(stream)) {
|
||||
av_free_packet(pkt);
|
||||
av_packet_unref(pkt);
|
||||
return 1; // don't signal EOF if skipping a packet
|
||||
}
|
||||
|
||||
struct demux_packet *dp = new_demux_packet_from_avpacket(pkt);
|
||||
if (!dp) {
|
||||
av_free_packet(pkt);
|
||||
av_packet_unref(pkt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -882,8 +885,10 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
if (pkt->dts != AV_NOPTS_VALUE)
|
||||
dp->dts = pkt->dts * av_q2d(st->time_base);
|
||||
dp->duration = pkt->duration * av_q2d(st->time_base);
|
||||
#if !HAVE_AV_AVPACKET_INT64_DURATION
|
||||
if (pkt->convergence_duration > 0)
|
||||
dp->duration = pkt->convergence_duration * av_q2d(st->time_base);
|
||||
#endif
|
||||
dp->pos = pkt->pos;
|
||||
dp->keyframe = pkt->flags & AV_PKT_FLAG_KEY;
|
||||
if (dp->pts != MP_NOPTS_VALUE) {
|
||||
@@ -891,7 +896,7 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
} else if (dp->dts != MP_NOPTS_VALUE) {
|
||||
priv->last_pts = dp->dts * AV_TIME_BASE;
|
||||
}
|
||||
av_free_packet(pkt);
|
||||
av_packet_unref(pkt);
|
||||
|
||||
if (priv->format_hack.clear_filepos)
|
||||
dp->pos = -1;
|
||||
@@ -1074,6 +1079,10 @@ static void demux_close_lavf(demuxer_t *demuxer)
|
||||
if (priv->pb)
|
||||
av_freep(&priv->pb->buffer);
|
||||
av_freep(&priv->pb);
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
if (priv->streams[n])
|
||||
avcodec_free_context(&priv->streams[n]->lav_headers);
|
||||
}
|
||||
talloc_free(priv);
|
||||
demuxer->priv = NULL;
|
||||
}
|
||||
|
||||
+57
-19
@@ -149,6 +149,16 @@ typedef struct mkv_index {
|
||||
uint64_t filepos; // position of the cluster which contains the packet
|
||||
} mkv_index_t;
|
||||
|
||||
struct block_info {
|
||||
uint64_t duration, discardpadding;
|
||||
bool simple, keyframe;
|
||||
uint64_t timecode;
|
||||
mkv_track_t *track;
|
||||
bstr data;
|
||||
void *alloc;
|
||||
int64_t filepos;
|
||||
};
|
||||
|
||||
typedef struct mkv_demuxer {
|
||||
int64_t segment_start, segment_end;
|
||||
|
||||
@@ -183,6 +193,8 @@ typedef struct mkv_demuxer {
|
||||
bool index_has_durations;
|
||||
|
||||
bool eof_warning;
|
||||
|
||||
struct block_info tmp_block;
|
||||
} mkv_demuxer_t;
|
||||
|
||||
#define OPT_BASE_STRUCT struct demux_mkv_opts
|
||||
@@ -190,6 +202,7 @@ struct demux_mkv_opts {
|
||||
int subtitle_preroll;
|
||||
double subtitle_preroll_secs;
|
||||
int probe_duration;
|
||||
int probe_start_time;
|
||||
int fix_timestamps;
|
||||
};
|
||||
|
||||
@@ -200,12 +213,14 @@ const struct m_sub_options demux_mkv_conf = {
|
||||
M_OPT_MIN, .min = 0),
|
||||
OPT_CHOICE("probe-video-duration", probe_duration, 0,
|
||||
({"no", 0}, {"yes", 1}, {"full", 2})),
|
||||
OPT_FLAG("probe-start-time", probe_start_time, 0),
|
||||
OPT_FLAG("fix-timestamps", fix_timestamps, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct demux_mkv_opts),
|
||||
.defaults = &(const struct demux_mkv_opts){
|
||||
.subtitle_preroll_secs = 1.0,
|
||||
.probe_start_time = 1,
|
||||
.fix_timestamps = 0,
|
||||
},
|
||||
};
|
||||
@@ -219,7 +234,9 @@ const struct m_sub_options demux_mkv_conf = {
|
||||
// (Subtitle packets added before first A/V keyframe packet is found with seek.)
|
||||
#define NUM_SUB_PREROLL_PACKETS 500
|
||||
|
||||
static void probe_last_timestamp(struct demuxer *demuxer);
|
||||
static void probe_last_timestamp(struct demuxer *demuxer, int64_t start_pos);
|
||||
static void probe_first_timestamp(struct demuxer *demuxer);
|
||||
static void free_block(struct block_info *block);
|
||||
|
||||
#define AAC_SYNC_EXTENSION_TYPE 0x02b7
|
||||
static int aac_get_sample_rate_index(uint32_t sample_rate)
|
||||
@@ -1884,8 +1901,9 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
add_coverart(demuxer);
|
||||
demuxer->allow_refresh_seeks = true;
|
||||
|
||||
probe_first_timestamp(demuxer);
|
||||
if (opts->demux_mkv->probe_duration)
|
||||
probe_last_timestamp(demuxer);
|
||||
probe_last_timestamp(demuxer, start_pos);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -2095,6 +2113,8 @@ static void mkv_seek_reset(demuxer_t *demuxer)
|
||||
}
|
||||
track->av_parser_codec = NULL;
|
||||
}
|
||||
|
||||
free_block(&mkv_d->tmp_block);
|
||||
}
|
||||
|
||||
// Copied from libavformat/matroskadec.c (FFmpeg 310f9dd / 2013-05-30)
|
||||
@@ -2268,16 +2288,6 @@ static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
|
||||
}
|
||||
}
|
||||
|
||||
struct block_info {
|
||||
uint64_t duration, discardpadding;
|
||||
bool simple, keyframe;
|
||||
uint64_t timecode;
|
||||
mkv_track_t *track;
|
||||
bstr data;
|
||||
void *alloc;
|
||||
int64_t filepos;
|
||||
};
|
||||
|
||||
static void free_block(struct block_info *block)
|
||||
{
|
||||
free(block->alloc);
|
||||
@@ -2389,8 +2399,12 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
|
||||
if (mkv_d->a_skip_to_keyframe)
|
||||
use_this_block &= keyframe;
|
||||
} else if (track->type == MATROSKA_TRACK_SUBTITLE) {
|
||||
if (!use_this_block && mkv_d->subtitle_preroll)
|
||||
use_this_block = 1;
|
||||
if (!use_this_block && mkv_d->subtitle_preroll) {
|
||||
int64_t end_time = block_info->timecode + block_info->duration;
|
||||
if (!block_info->duration)
|
||||
end_time = INT64_MAX;
|
||||
use_this_block = end_time > mkv_d->skip_to_timecode;
|
||||
}
|
||||
if (use_this_block) {
|
||||
if (mkv_d->subtitle_preroll) {
|
||||
mkv_d->subtitle_preroll--;
|
||||
@@ -2522,6 +2536,12 @@ static int read_next_block(demuxer_t *demuxer, struct block_info *block)
|
||||
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
|
||||
stream_t *s = demuxer->stream;
|
||||
|
||||
if (mkv_d->tmp_block.alloc) {
|
||||
*block = mkv_d->tmp_block;
|
||||
mkv_d->tmp_block = (struct block_info){0};
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
while (stream_tell(s) < mkv_d->cluster_end) {
|
||||
int64_t start_filepos = stream_tell(s);
|
||||
@@ -2847,10 +2867,9 @@ static void demux_mkv_seek(demuxer_t *demuxer, double rel_seek_secs, int flags)
|
||||
demux_mkv_fill_buffer(demuxer);
|
||||
}
|
||||
|
||||
static void probe_last_timestamp(struct demuxer *demuxer)
|
||||
static void probe_last_timestamp(struct demuxer *demuxer, int64_t start_pos)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
int64_t old_pos = stream_tell(demuxer->stream);
|
||||
|
||||
if (!demuxer->seekable)
|
||||
return;
|
||||
@@ -2888,10 +2907,12 @@ static void probe_last_timestamp(struct demuxer *demuxer)
|
||||
int64_t size = stream_get_size(demuxer->stream);
|
||||
stream_seek(demuxer->stream, MPMAX(size - 10 * 1024 * 1024, 0));
|
||||
if (ebml_resync_cluster(mp_null_log, demuxer->stream) < 0)
|
||||
stream_seek(demuxer->stream, old_pos); // full scan otherwise
|
||||
stream_seek(demuxer->stream, start_pos); // full scan otherwise
|
||||
}
|
||||
}
|
||||
|
||||
free_block(&mkv_d->tmp_block);
|
||||
|
||||
int64_t last_ts[STREAM_TYPE_COUNT] = {0};
|
||||
while (1) {
|
||||
struct block_info block;
|
||||
@@ -2913,12 +2934,29 @@ static void probe_last_timestamp(struct demuxer *demuxer)
|
||||
last_ts[STREAM_VIDEO] = mkv_d->cluster_tc;
|
||||
|
||||
if (last_ts[STREAM_VIDEO])
|
||||
mkv_d->duration = last_ts[STREAM_VIDEO] / 1e9;
|
||||
mkv_d->duration = last_ts[STREAM_VIDEO] / 1e9 - demuxer->start_time;
|
||||
|
||||
stream_seek(demuxer->stream, old_pos);
|
||||
stream_seek(demuxer->stream, start_pos);
|
||||
mkv_d->cluster_start = mkv_d->cluster_end = 0;
|
||||
}
|
||||
|
||||
static void probe_first_timestamp(struct demuxer *demuxer)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
|
||||
if (!demuxer->opts->demux_mkv->probe_start_time)
|
||||
return;
|
||||
|
||||
struct block_info block;
|
||||
if (read_next_block(demuxer, &block) > 0)
|
||||
mkv_d->tmp_block = block;
|
||||
|
||||
demuxer->start_time = mkv_d->cluster_tc / 1e9;
|
||||
|
||||
if (demuxer->start_time > 0)
|
||||
MP_VERBOSE(demuxer, "Start PTS: %f\n", demuxer->start_time);
|
||||
}
|
||||
|
||||
static int demux_mkv_control(demuxer_t *demuxer, int cmd, void *arg)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
|
||||
|
||||
@@ -758,6 +758,10 @@ static int d_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
if (check > DEMUX_CHECK_REQUEST)
|
||||
return -1;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
if (check != DEMUX_CHECK_FORCE)
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
if (!demuxer->params || !demuxer->params->expect_subtitle)
|
||||
return -1;
|
||||
@@ -800,7 +804,9 @@ 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);
|
||||
int have_packet = !!dp;
|
||||
demux_add_packet(p->sh, dp);
|
||||
return have_packet;
|
||||
}
|
||||
|
||||
static void d_seek(struct demuxer *demuxer, double secs, int flags)
|
||||
|
||||
@@ -36,6 +36,7 @@ struct key_name {
|
||||
static const struct key_name key_names[] = {
|
||||
{ ' ', "SPACE" },
|
||||
{ '#', "SHARP" },
|
||||
{ 0x3000, "IDEOGRAPHIC_SPACE" },
|
||||
{ MP_KEY_ENTER, "ENTER" },
|
||||
{ MP_KEY_TAB, "TAB" },
|
||||
{ MP_KEY_BACKSPACE, "BS" },
|
||||
|
||||
+20
-4
@@ -49,13 +49,29 @@ extern "C" {
|
||||
* 2) Using mpv as a library with mpv_create(). This basically allows embedding
|
||||
* mpv in other applications.
|
||||
*
|
||||
* Documentation
|
||||
* -------------
|
||||
*
|
||||
* The libmpv C API is documented directly in this header. Note that most
|
||||
* actual interaction with this player is done through
|
||||
* options/commands/properties, which can be accessed through this API.
|
||||
* Essentially everything is done with them, including loading a file,
|
||||
* retrieving playback progress, and so on.
|
||||
*
|
||||
* These are documented elsewhere:
|
||||
* * http://mpv.io/manual/master/#options
|
||||
* * http://mpv.io/manual/master/#list-of-input-commands
|
||||
* * http://mpv.io/manual/master/#properties
|
||||
*
|
||||
* You can also look at the examples in DOCS/client_api_examples/.
|
||||
*
|
||||
* Event loop
|
||||
* ----------
|
||||
*
|
||||
* In general, the API user should run an event loop in order to receive events.
|
||||
* This even loop should call mpv_wait_event(), which will return once a new
|
||||
* mpv client API is available. It should also be possible to integrate client
|
||||
* API usage in other event loops (e.g. GUI toolkits) with the
|
||||
* This event loop should call mpv_wait_event(), which will return once a new
|
||||
* mpv client API is available. It is also possible to integrate client API
|
||||
* usage in other event loops (e.g. GUI toolkits) with the
|
||||
* mpv_set_wakeup_callback() function, and then polling for events by calling
|
||||
* mpv_wait_event() with a 0 timeout.
|
||||
*
|
||||
@@ -198,7 +214,7 @@ extern "C" {
|
||||
* relational operators (<, >, <=, >=).
|
||||
*/
|
||||
#define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL)
|
||||
#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 19)
|
||||
#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 20)
|
||||
|
||||
/**
|
||||
* Return the MPV_CLIENT_API_VERSION the mpv source has been compiled with.
|
||||
|
||||
+38
-10
@@ -108,8 +108,36 @@ extern "C" {
|
||||
* context must have been uninitialized. If this doesn't happen, undefined
|
||||
* behavior will result.
|
||||
*
|
||||
* Special D3D interop considerations
|
||||
* ----------------------------------
|
||||
* Special windowing system interop considerations
|
||||
* ------------------------------------------------
|
||||
*
|
||||
* In some cases, libmpv needs to have access to the windowing system's handles.
|
||||
* This can be a pointer to a X11 "Display" for example. Usually this is needed
|
||||
* only for hardware decoding.
|
||||
*
|
||||
* You can communicate these handles to libmpv by adding a pseudo-OpenGL
|
||||
* extension "GL_MP_MPGetNativeDisplay" to the additional extension string when
|
||||
* calling mpv_opengl_cb_init_gl(). The get_proc_address callback should resolve
|
||||
* a function named "glMPGetNativeDisplay", which has the signature:
|
||||
*
|
||||
* void* GLAPIENTRY glMPGetNativeDisplay(const char* name)
|
||||
*
|
||||
* See below what names are defined. Usually, libmpv will use the native handle
|
||||
* up until mpv_opengl_cb_uninit_gl() is called. If the name is not anything
|
||||
* you know/expected, return NULL from the function.
|
||||
*
|
||||
* Windowing system interop on Linux
|
||||
* ---------------------------------
|
||||
*
|
||||
* The new VAAPI OpenGL interop requires an EGL context. EGL provides no way
|
||||
* to query the X11 Display associated to a specific EGL context, so this API
|
||||
* is used to pass it through.
|
||||
*
|
||||
* glMPGetNativeDisplay("x11") should return a X11 "Display*", which then will
|
||||
* be used to create the hardware decoder state. (On GLX, this is not needed.)
|
||||
*
|
||||
* Windowing system interop on MS win32
|
||||
* ------------------------------------
|
||||
*
|
||||
* If OpenGL switches to fullscreen, most players give it access GPU access,
|
||||
* which means DXVA2 hardware decoding in mpv won't work. This can be worked
|
||||
@@ -117,14 +145,14 @@ extern "C" {
|
||||
* create a decoder. The device can be either the real device used for display,
|
||||
* or a "blank" device created before switching to fullscreen.
|
||||
*
|
||||
* You can do this by adding "GL_MP_D3D_interfaces" to the additional extension
|
||||
* string when calling mpv_opengl_cb_init_gl(). The get_proc_address callback
|
||||
* should resolve a function named "glMPGetD3DInterface", which has the
|
||||
* signature: "void* __stdcall glMPGetD3DInterface(const char* name)". If
|
||||
* name is "IDirect3DDevice9", it should return a IDirect3DDevice9 pointer
|
||||
* (or NULL if not available). libmpv will release this interface when it is
|
||||
* done with it (usually when mpv_opengl_cb_uninit_gl() is called). New
|
||||
* interface names can be added in the future.
|
||||
* You can provide glMPGetNativeDisplay as described in the previous section.
|
||||
* If it is called with name set to "IDirect3DDevice9", it should return a
|
||||
* IDirect3DDevice9 pointer (or NULL if not available). libmpv will release
|
||||
* this interface when it is done with it.
|
||||
*
|
||||
* In previous libmpv releases, this used "GL_MP_D3D_interfaces" and
|
||||
* "glMPGetD3DInterface". This is deprecated; use glMPGetNativeDisplay instead
|
||||
* (the semantics are 100% compatible).
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
+10
-7
@@ -84,7 +84,6 @@ const struct m_opt_choice_alternatives mp_hwdec_names[] = {
|
||||
{"no", HWDEC_NONE},
|
||||
{"auto", HWDEC_AUTO},
|
||||
{"vdpau", HWDEC_VDPAU},
|
||||
{"vda", HWDEC_VDA},
|
||||
{"videotoolbox",HWDEC_VIDEOTOOLBOX},
|
||||
{"vaapi", HWDEC_VAAPI},
|
||||
{"vaapi-copy", HWDEC_VAAPI_COPY},
|
||||
@@ -186,6 +185,8 @@ const m_option_t mp_opts[] = {
|
||||
{"http", 3})),
|
||||
OPT_FLAG("tls-verify", network_tls_verify, 0),
|
||||
OPT_STRING("tls-ca-file", network_tls_ca_file, M_OPT_FILE),
|
||||
OPT_STRING("tls-cert-file", network_tls_cert_file, M_OPT_FILE),
|
||||
OPT_STRING("tls-key-file", network_tls_key_file, M_OPT_FILE),
|
||||
OPT_DOUBLE("network-timeout", network_timeout, M_OPT_MIN, .min = 0),
|
||||
|
||||
// ------------------------- demuxer options --------------------
|
||||
@@ -373,7 +374,6 @@ const m_option_t mp_opts[] = {
|
||||
OPT_FLOATRANGE("osd-bar-w", osd_bar_w, 0, 1, 100),
|
||||
OPT_FLOATRANGE("osd-bar-h", osd_bar_h, 0, 0.1, 50),
|
||||
OPT_SUBSTRUCT("osd", osd_style, osd_style_conf, 0),
|
||||
OPT_FLAG("use-text-osd", use_text_osd, CONF_GLOBAL),
|
||||
OPT_SUBSTRUCT("sub-text", sub_text_style, sub_style_conf, 0),
|
||||
OPT_FLAG("sub-clear-on-seek", sub_clear_on_seek, 0),
|
||||
|
||||
@@ -384,6 +384,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_SETTINGSLIST("ao-defaults", ao_defs, 0, &ao_obj_list),
|
||||
OPT_STRING("audio-device", audio_device, 0),
|
||||
OPT_STRING("audio-client-name", audio_client_name, 0),
|
||||
OPT_FLAG("audio-fallback-to-null", ao_null_fallback, 0),
|
||||
OPT_CHOICE("force-window", force_vo, 0,
|
||||
({"no", 0}, {"yes", 1}, {"immediate", 2})),
|
||||
OPT_FLAG("ontop", vo.ontop, M_OPT_FIXED),
|
||||
@@ -464,6 +465,8 @@ const m_option_t mp_opts[] = {
|
||||
OPT_INTRANGE("contrast", gamma_contrast, 0, -100, 100),
|
||||
OPT_INTRANGE("hue", gamma_hue, 0, -100, 100),
|
||||
OPT_INTRANGE("gamma", gamma_gamma, 0, -100, 100),
|
||||
OPT_CHOICE_C("video-output-levels", video_output_levels, 0,
|
||||
mp_csp_levels_names),
|
||||
OPT_FLAG("keepaspect", vo.keepaspect, 0),
|
||||
OPT_FLAG("keepaspect-window", vo.keepaspect_window, 0),
|
||||
|
||||
@@ -519,14 +522,13 @@ const m_option_t mp_opts[] = {
|
||||
|
||||
// a-v sync stuff:
|
||||
OPT_FLAG("correct-pts", correct_pts, 0),
|
||||
OPT_CHOICE("pts-association-mode", user_pts_assoc_mode, 0,
|
||||
({"auto", 0}, {"decoder", 1}, {"sort", 2})),
|
||||
OPT_FLAG("initial-audio-sync", initial_audio_sync, 0),
|
||||
OPT_CHOICE("video-sync", video_sync, 0,
|
||||
({"audio", VS_DEFAULT},
|
||||
{"display-resample", VS_DISP_RESAMPLE},
|
||||
{"display-resample-vdrop", VS_DISP_RESAMPLE_VDROP},
|
||||
{"display-resample-desync", VS_DISP_RESAMPLE_NONE},
|
||||
{"display-adrop", VS_DISP_ADROP},
|
||||
{"display-vdrop", VS_DISP_VDROP},
|
||||
{"display-desync", VS_DISP_NONE},
|
||||
{"desync", VS_NONE})),
|
||||
@@ -534,6 +536,8 @@ const m_option_t mp_opts[] = {
|
||||
M_OPT_MIN, .min = 0),
|
||||
OPT_DOUBLE("video-sync-max-audio-change", sync_max_audio_change,
|
||||
M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 1),
|
||||
OPT_DOUBLE("video-sync-adrop-size", sync_audio_drop_size,
|
||||
M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 1),
|
||||
OPT_CHOICE("hr-seek", hr_seek, 0,
|
||||
({"no", -1}, {"absolute", 0}, {"yes", 1}, {"always", 1})),
|
||||
OPT_FLOAT("hr-seek-demuxer-offset", hr_seek_demuxer_offset, 0),
|
||||
@@ -713,11 +717,10 @@ const struct MPOpts mp_default_opts = {
|
||||
.sub_use_margins = 1,
|
||||
.ass_scale_with_window = 0,
|
||||
.sub_scale_with_window = 1,
|
||||
.use_text_osd = 1,
|
||||
#if HAVE_LUA
|
||||
.lua_load_osc = 1,
|
||||
.lua_load_ytdl = 1,
|
||||
.lua_ytdl_format = "best",
|
||||
.lua_ytdl_format = NULL,
|
||||
.lua_ytdl_raw_options = NULL,
|
||||
#endif
|
||||
.auto_load_scripts = 1,
|
||||
@@ -728,6 +731,7 @@ const struct MPOpts mp_default_opts = {
|
||||
.hr_seek_framedrop = 1,
|
||||
.sync_max_video_change = 1,
|
||||
.sync_max_audio_change = 0.125,
|
||||
.sync_audio_drop_size = 0.020,
|
||||
.load_config = 1,
|
||||
.position_resume = 1,
|
||||
.stream_cache = {
|
||||
@@ -752,7 +756,6 @@ const struct MPOpts mp_default_opts = {
|
||||
.edition_id = -1,
|
||||
.default_max_pts_correction = -1,
|
||||
.correct_pts = 1,
|
||||
.user_pts_assoc_mode = 1,
|
||||
.initial_audio_sync = 1,
|
||||
.frame_dropping = 1,
|
||||
.term_osd = 2,
|
||||
|
||||
+5
-2
@@ -81,6 +81,7 @@ typedef struct MPOpts {
|
||||
struct m_obj_settings *audio_driver_list, *ao_defs;
|
||||
char *audio_device;
|
||||
char *audio_client_name;
|
||||
int ao_null_fallback;
|
||||
int force_vo;
|
||||
int softvol;
|
||||
float mixer_init_volume;
|
||||
@@ -103,6 +104,7 @@ typedef struct MPOpts {
|
||||
int gamma_contrast;
|
||||
int gamma_saturation;
|
||||
int gamma_hue;
|
||||
int video_output_levels;
|
||||
|
||||
int stop_screensaver;
|
||||
int cursor_autohide_delay;
|
||||
@@ -142,11 +144,11 @@ typedef struct MPOpts {
|
||||
int chapterrange[2];
|
||||
int edition_id;
|
||||
int correct_pts;
|
||||
int user_pts_assoc_mode;
|
||||
int initial_audio_sync;
|
||||
int video_sync;
|
||||
double sync_max_video_change;
|
||||
double sync_max_audio_change;
|
||||
double sync_audio_drop_size;
|
||||
int hr_seek;
|
||||
float hr_seek_demuxer_offset;
|
||||
int hr_seek_framedrop;
|
||||
@@ -233,7 +235,6 @@ typedef struct MPOpts {
|
||||
char **sub_paths;
|
||||
int sub_auto;
|
||||
int audiofile_auto;
|
||||
int use_text_osd;
|
||||
int osd_bar_visible;
|
||||
float osd_bar_align_x;
|
||||
float osd_bar_align_y;
|
||||
@@ -276,6 +277,8 @@ typedef struct MPOpts {
|
||||
char **network_http_header_fields;
|
||||
int network_tls_verify;
|
||||
char *network_tls_ca_file;
|
||||
char *network_tls_cert_file;
|
||||
char *network_tls_key_file;
|
||||
double network_timeout;
|
||||
|
||||
struct tv_params *tv_params;
|
||||
|
||||
+74
-6
@@ -29,6 +29,7 @@
|
||||
#include "common/encode.h"
|
||||
#include "options/options.h"
|
||||
#include "common/common.h"
|
||||
#include "osdep/timer.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/audio.h"
|
||||
@@ -153,6 +154,7 @@ void reset_audio_state(struct MPContext *mpctx)
|
||||
mp_audio_buffer_clear(mpctx->ao_buffer);
|
||||
mpctx->audio_status = mpctx->d_audio ? STATUS_SYNCING : STATUS_EOF;
|
||||
mpctx->delay = 0;
|
||||
mpctx->audio_stat_start = 0;
|
||||
}
|
||||
|
||||
void uninit_audio_out(struct MPContext *mpctx)
|
||||
@@ -260,11 +262,15 @@ void reinit_audio_chain(struct MPContext *mpctx)
|
||||
}
|
||||
|
||||
if (!mpctx->ao) {
|
||||
bool spdif_fallback = af_fmt_is_spdif(afs->output.format) &&
|
||||
mpctx->d_audio->spdif_passthrough;
|
||||
bool ao_null_fallback = opts->ao_null_fallback && !spdif_fallback;
|
||||
|
||||
mp_chmap_remove_useless_channels(&afs->output.channels,
|
||||
&opts->audio_output_channels);
|
||||
mp_audio_set_channels(&afs->output, &afs->output.channels);
|
||||
|
||||
mpctx->ao = ao_init_best(mpctx->global, mpctx->input,
|
||||
mpctx->ao = ao_init_best(mpctx->global, ao_null_fallback, mpctx->input,
|
||||
mpctx->encode_lavc_ctx, afs->output.rate,
|
||||
afs->output.format, afs->output.channels);
|
||||
|
||||
@@ -283,10 +289,9 @@ void reinit_audio_chain(struct MPContext *mpctx)
|
||||
|
||||
if (!mpctx->ao) {
|
||||
// If spdif was used, try to fallback to PCM.
|
||||
if (af_fmt_is_spdif(afs->output.format) &&
|
||||
mpctx->d_audio->spdif_passthrough)
|
||||
{
|
||||
if (spdif_fallback) {
|
||||
mpctx->d_audio->spdif_passthrough = false;
|
||||
mpctx->d_audio->spdif_failed = true;
|
||||
if (!audio_init_best_codec(mpctx->d_audio))
|
||||
goto init_error;
|
||||
reset_audio_state(mpctx);
|
||||
@@ -400,11 +405,31 @@ static int write_to_ao(struct MPContext *mpctx, struct mp_audio *data, int flags
|
||||
if (played > 0) {
|
||||
mpctx->shown_aframes += played;
|
||||
mpctx->delay += played / real_samplerate;
|
||||
mpctx->written_audio += played / (double)out_format.rate;
|
||||
return played;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dump_audio_stats(struct MPContext *mpctx)
|
||||
{
|
||||
if (!mp_msg_test(mpctx->log, MSGL_STATS))
|
||||
return;
|
||||
if (mpctx->audio_status != STATUS_PLAYING || !mpctx->ao || mpctx->paused) {
|
||||
mpctx->audio_stat_start = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
double delay = ao_get_delay(mpctx->ao);
|
||||
if (!mpctx->audio_stat_start) {
|
||||
mpctx->audio_stat_start = mp_time_us();
|
||||
mpctx->written_audio = delay;
|
||||
}
|
||||
double current_audio = mpctx->written_audio - delay;
|
||||
double current_time = (mp_time_us() - mpctx->audio_stat_start) / 1e6;
|
||||
MP_STATS(mpctx, "value %f ao-dev", current_audio - current_time);
|
||||
}
|
||||
|
||||
// Return the number of samples that must be skipped or prepended to reach the
|
||||
// target audio pts after a seek (for A/V sync or hr-seek).
|
||||
// Return value (*skip):
|
||||
@@ -441,7 +466,7 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
|
||||
if (mpctx->video_status < STATUS_READY)
|
||||
return false; // wait until we know a video PTS
|
||||
if (mpctx->video_next_pts != MP_NOPTS_VALUE)
|
||||
sync_pts = mpctx->video_next_pts - (opts->audio_delay - mpctx->delay);
|
||||
sync_pts = mpctx->video_next_pts - opts->audio_delay;
|
||||
} else if (mpctx->hrseek_active) {
|
||||
sync_pts = mpctx->hrseek_pts;
|
||||
}
|
||||
@@ -468,11 +493,23 @@ void fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
struct dec_audio *d_audio = mpctx->d_audio;
|
||||
|
||||
dump_audio_stats(mpctx);
|
||||
|
||||
if (mpctx->ao && ao_query_and_reset_events(mpctx->ao, AO_EVENT_RELOAD)) {
|
||||
ao_reset(mpctx->ao);
|
||||
uninit_audio_out(mpctx);
|
||||
if (d_audio)
|
||||
if (d_audio) {
|
||||
if (mpctx->d_audio->spdif_failed) {
|
||||
mpctx->d_audio->spdif_failed = false;
|
||||
mpctx->d_audio->spdif_passthrough = true;
|
||||
if (!audio_init_best_codec(mpctx->d_audio)) {
|
||||
MP_ERR(mpctx, "Error reinitializing audio.\n");
|
||||
error_on_track(mpctx, mpctx->current_track[0][STREAM_AUDIO]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mpctx->audio_status = STATUS_SYNCING;
|
||||
}
|
||||
}
|
||||
|
||||
if (!d_audio)
|
||||
@@ -516,6 +553,20 @@ void fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
playsize = MPMAX(1, playsize + skip); // silence will be prepended
|
||||
}
|
||||
|
||||
int skip_duplicate = 0; // >0: skip, <0: duplicate
|
||||
if (mpctx->display_sync_active && opts->video_sync == VS_DISP_ADROP &&
|
||||
fabs(mpctx->last_av_difference) >= opts->sync_audio_drop_size &&
|
||||
mpctx->audio_status == STATUS_PLAYING)
|
||||
{
|
||||
int samples = ceil(opts->sync_audio_drop_size * play_samplerate);
|
||||
int align = af_format_sample_alignment(out_format.format);
|
||||
samples = (samples + align / 2) / align * align;
|
||||
|
||||
skip_duplicate = mpctx->last_av_difference >= 0 ? -samples : samples;
|
||||
|
||||
playsize = MPMAX(playsize, samples);
|
||||
}
|
||||
|
||||
int status = AD_OK;
|
||||
bool working = false;
|
||||
if (playsize > mp_audio_buffer_samples(mpctx->ao_buffer)) {
|
||||
@@ -563,6 +614,21 @@ void fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
end_sync = true;
|
||||
}
|
||||
|
||||
if (skip_duplicate) {
|
||||
int max = mp_audio_buffer_samples(mpctx->ao_buffer);
|
||||
if (abs(skip_duplicate) > max)
|
||||
skip_duplicate = skip_duplicate >= 0 ? max : -max;
|
||||
mpctx->last_av_difference += skip_duplicate / play_samplerate;
|
||||
if (skip_duplicate >= 0) {
|
||||
mp_audio_buffer_skip(mpctx->ao_buffer, skip_duplicate);
|
||||
MP_STATS(mpctx, "drop-audio");
|
||||
} else {
|
||||
mp_audio_buffer_duplicate(mpctx->ao_buffer, -skip_duplicate);
|
||||
MP_STATS(mpctx, "duplicate-audio");
|
||||
}
|
||||
MP_VERBOSE(mpctx, "audio skip_duplicate=%d\n", skip_duplicate);
|
||||
}
|
||||
|
||||
if (mpctx->audio_status == STATUS_SYNCING) {
|
||||
if (end_sync)
|
||||
mpctx->audio_status = STATUS_FILLING;
|
||||
@@ -617,6 +683,8 @@ void fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
|
||||
assert(played >= 0 && played <= data.samples);
|
||||
mp_audio_buffer_skip(mpctx->ao_buffer, played);
|
||||
|
||||
dump_audio_stats(mpctx);
|
||||
|
||||
mpctx->audio_status = STATUS_PLAYING;
|
||||
if (audio_eof && !playsize) {
|
||||
mpctx->audio_status = STATUS_DRAINING;
|
||||
|
||||
+1
-1
@@ -1601,7 +1601,7 @@ static const char *const err_table[] = {
|
||||
[-MPV_ERROR_LOADING_FAILED] = "loading failed",
|
||||
[-MPV_ERROR_AO_INIT_FAILED] = "audio output initialization failed",
|
||||
[-MPV_ERROR_VO_INIT_FAILED] = "audio output initialization failed",
|
||||
[-MPV_ERROR_NOTHING_TO_PLAY] = "no audio or video data found",
|
||||
[-MPV_ERROR_NOTHING_TO_PLAY] = "no audio or video data played",
|
||||
[-MPV_ERROR_UNKNOWN_FORMAT] = "unrecognized file format",
|
||||
[-MPV_ERROR_UNSUPPORTED] = "not supported",
|
||||
[-MPV_ERROR_NOT_IMPLEMENTED] = "operation not implemented",
|
||||
|
||||
+22
-11
@@ -484,6 +484,9 @@ static int mp_property_stream_end(void *ctx, struct m_property *prop,
|
||||
// Assumes prop is the type of the actual property.
|
||||
static int property_time(int action, void *arg, double time)
|
||||
{
|
||||
if (time == MP_NOPTS_VALUE)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
const struct m_option time_type = {.type = CONF_TYPE_TIME};
|
||||
switch (action) {
|
||||
case M_PROPERTY_GET:
|
||||
@@ -652,6 +655,9 @@ static bool time_remaining(MPContext *mpctx, double *remaining)
|
||||
double len = get_time_length(mpctx);
|
||||
double playback = get_playback_time(mpctx);
|
||||
|
||||
if (playback == MP_NOPTS_VALUE)
|
||||
return false;
|
||||
|
||||
*remaining = len - playback;
|
||||
|
||||
return len >= 0;
|
||||
@@ -1611,7 +1617,7 @@ static int mp_property_audio_device(void *ctx, struct m_property *prop,
|
||||
struct ao_device_list *list = ao_hotplug_get_device_list(cmd->hotplug);
|
||||
for (int n = 0; n < list->num_devices; n++) {
|
||||
struct ao_device_desc *dev = &list->devices[n];
|
||||
if (dev->name && strcmp(dev->name, mpctx->opts->audio_device)) {
|
||||
if (dev->name && strcmp(dev->name, mpctx->opts->audio_device) == 0) {
|
||||
*(char **)arg = talloc_strdup(NULL, dev->desc ? dev->desc : "?");
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
@@ -2370,18 +2376,19 @@ static int mp_property_framedrop(void *ctx, struct m_property *prop,
|
||||
static int mp_property_video_color(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
const char *name = prop->priv ? prop->priv : prop->name;
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->d_video)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_SET: {
|
||||
if (video_set_colors(mpctx->d_video, prop->name, *(int *) arg) <= 0)
|
||||
if (video_set_colors(mpctx->d_video, name, *(int *) arg) <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
break;
|
||||
}
|
||||
case M_PROPERTY_GET:
|
||||
if (video_get_colors(mpctx->d_video, prop->name, (int *)arg) <= 0)
|
||||
if (video_get_colors(mpctx->d_video, name, (int *)arg) <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
// Write new value to option variable
|
||||
mp_property_generic_option(mpctx, prop, M_PROPERTY_SET, arg);
|
||||
@@ -2440,8 +2447,6 @@ static int property_imgparams(struct mp_image_params p, int action, void *arg)
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_names, p.colorspace))},
|
||||
{"colorlevels",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_levels_names, p.colorlevels))},
|
||||
{"outputlevels",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_levels_names, p.outputlevels))},
|
||||
{"primaries",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_prim_names, p.primaries))},
|
||||
{"gamma",
|
||||
@@ -3082,11 +3087,18 @@ static int mp_property_packet_bitrate(void *ctx, struct m_property *prop,
|
||||
int type = (uintptr_t)prop->priv & ~0x100;
|
||||
bool old = (uintptr_t)prop->priv & 0x100;
|
||||
|
||||
if (!mpctx->demuxer)
|
||||
struct demuxer *demuxer = NULL;
|
||||
if (mpctx->current_track[0][type])
|
||||
demuxer = mpctx->current_track[0][type]->demuxer;
|
||||
if (!demuxer)
|
||||
demuxer = mpctx->demuxer;
|
||||
if (!demuxer)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
double r[STREAM_TYPE_COUNT];
|
||||
if (demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_BITRATE_STATS, &r) < 1)
|
||||
if (demux_control(demuxer, DEMUXER_CTRL_GET_BITRATE_STATS, &r) < 1)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
if (r[type] < 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
// r[type] is in bytes/second -> bits
|
||||
@@ -3406,7 +3418,6 @@ static const struct m_property mp_properties[] = {
|
||||
{"demuxer-cache-idle", mp_property_demuxer_cache_idle},
|
||||
{"cache-buffering-state", mp_property_cache_buffering},
|
||||
{"paused-for-cache", mp_property_paused_for_cache},
|
||||
{"pts-association-mode", mp_property_generic_option},
|
||||
{"hr-seek", mp_property_generic_option},
|
||||
{"clock", mp_property_clock},
|
||||
{"seekable", mp_property_seekable},
|
||||
@@ -3453,6 +3464,8 @@ static const struct m_property mp_properties[] = {
|
||||
{"contrast", mp_property_video_color},
|
||||
{"saturation", mp_property_video_color},
|
||||
{"hue", mp_property_video_color},
|
||||
{"video-output-levels", mp_property_video_color,
|
||||
.priv = (void *)"output-levels"},
|
||||
{"panscan", panscan_property_helper},
|
||||
{"video-zoom", panscan_property_helper},
|
||||
{"video-align-x", panscan_property_helper},
|
||||
@@ -3563,7 +3576,6 @@ static const struct m_property mp_properties[] = {
|
||||
M_PROPERTY_ALIAS("sub", "sid"),
|
||||
M_PROPERTY_ALIAS("colormatrix", "video-params/colormatrix"),
|
||||
M_PROPERTY_ALIAS("colormatrix-input-range", "video-params/colorlevels"),
|
||||
M_PROPERTY_ALIAS("colormatrix-output-range", "video-params/outputlevels"),
|
||||
M_PROPERTY_ALIAS("colormatrix-primaries", "video-params/primaries"),
|
||||
M_PROPERTY_ALIAS("colormatrix-gamma", "video-params/gamma"),
|
||||
|
||||
@@ -3734,7 +3746,6 @@ static const struct property_osd_display {
|
||||
{ "chapter", .seek_msg = OSD_SEEK_INFO_CHAPTER_TEXT,
|
||||
.seek_bar = OSD_SEEK_INFO_BAR },
|
||||
{ "edition", .seek_msg = OSD_SEEK_INFO_EDITION },
|
||||
{ "pts-association-mode", "PTS association mode" },
|
||||
{ "hr-seek", "hr-seek" },
|
||||
{ "speed", "Speed" },
|
||||
{ "clock", "Clock" },
|
||||
@@ -4590,7 +4601,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
|
||||
case MP_CMD_STOP:
|
||||
playlist_clear(mpctx->playlist);
|
||||
if (!mpctx->stop_play)
|
||||
if (mpctx->stop_play != PT_QUIT)
|
||||
mpctx->stop_play = PT_STOP;
|
||||
break;
|
||||
|
||||
|
||||
@@ -286,10 +286,6 @@ void mp_write_watch_later_conf(struct MPContext *mpctx)
|
||||
goto exit;
|
||||
}
|
||||
|
||||
double pos = get_current_time(mpctx);
|
||||
if (pos == MP_NOPTS_VALUE)
|
||||
goto exit;
|
||||
|
||||
mp_mk_config_dir(mpctx->global, MP_WATCH_LATER_CONF);
|
||||
|
||||
conffile = mp_get_playback_resume_config_filename(mpctx, filename);
|
||||
@@ -307,7 +303,9 @@ void mp_write_watch_later_conf(struct MPContext *mpctx)
|
||||
write_name[n] = (unsigned char)filename[n] < 32 ? '_' : filename[n];
|
||||
fprintf(file, "# %s\n", write_name);
|
||||
}
|
||||
fprintf(file, "start=%f\n", pos);
|
||||
double pos = get_current_time(mpctx);
|
||||
if (pos != MP_NOPTS_VALUE)
|
||||
fprintf(file, "start=%f\n", pos);
|
||||
for (int i = 0; backup_properties[i]; i++) {
|
||||
const char *pname = backup_properties[i];
|
||||
char *val = NULL;
|
||||
|
||||
@@ -84,6 +84,7 @@ enum video_sync {
|
||||
VS_DISP_RESAMPLE,
|
||||
VS_DISP_RESAMPLE_VDROP,
|
||||
VS_DISP_RESAMPLE_NONE,
|
||||
VS_DISP_ADROP,
|
||||
VS_DISP_VDROP,
|
||||
VS_DISP_NONE,
|
||||
VS_NONE,
|
||||
@@ -92,6 +93,7 @@ enum video_sync {
|
||||
#define VS_IS_DISP(x) ((x) == VS_DISP_RESAMPLE || \
|
||||
(x) == VS_DISP_RESAMPLE_VDROP || \
|
||||
(x) == VS_DISP_RESAMPLE_NONE || \
|
||||
(x) == VS_DISP_ADROP || \
|
||||
(x) == VS_DISP_VDROP || \
|
||||
(x) == VS_DISP_NONE)
|
||||
|
||||
@@ -302,6 +304,9 @@ typedef struct MPContext {
|
||||
double last_vo_pts;
|
||||
// Video PTS, or audio PTS if video has ended.
|
||||
double playback_pts;
|
||||
// audio stats only
|
||||
int64_t audio_stat_start;
|
||||
double written_audio;
|
||||
|
||||
int last_chapter;
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ static const char *const sub_exts[] = {"utf", "utf8", "utf-8", "idx", "sub", "sr
|
||||
"js", "ass", "mks", "vtt", "sup", NULL};
|
||||
|
||||
static const char *const audio_exts[] = {"mp3", "aac", "mka", "dts", "flac",
|
||||
"ogg", "m4a", "ac3", "opus", NULL};
|
||||
"ogg", "m4a", "ac3", "opus", "wav",
|
||||
NULL};
|
||||
|
||||
static bool test_ext_list(bstr ext, const char *const *list)
|
||||
{
|
||||
|
||||
+5
-3
@@ -198,10 +198,12 @@ void update_demuxer_properties(struct MPContext *mpctx)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
struct mp_log *log = mp_log_new(NULL, mpctx->log, "!display-tags");
|
||||
if (!had_output)
|
||||
MP_INFO(mpctx, "File tags:\n");
|
||||
MP_INFO(mpctx, " %s: %s\n", info->keys[n], info->values[n]);
|
||||
mp_info(log, "File tags:\n");
|
||||
mp_info(log, " %s: %s\n", info->keys[n], info->values[n]);
|
||||
had_output = true;
|
||||
talloc_free(log);
|
||||
}
|
||||
talloc_free(mpctx->filtered_tags);
|
||||
mpctx->filtered_tags = info;
|
||||
@@ -1095,7 +1097,7 @@ reopen_file:
|
||||
}
|
||||
|
||||
open_demux_reentrant(mpctx);
|
||||
if (!mpctx->master_demuxer)
|
||||
if (!mpctx->master_demuxer || mpctx->stop_play)
|
||||
goto terminate_playback;
|
||||
mpctx->demuxer = mpctx->master_demuxer;
|
||||
|
||||
|
||||
+46
-57
@@ -3,8 +3,7 @@ local msg = require 'mp.msg'
|
||||
|
||||
local ytdl = {
|
||||
path = "youtube-dl",
|
||||
minver = "2015.02.23.1",
|
||||
vercheck = nil,
|
||||
searched = false
|
||||
}
|
||||
|
||||
local function exec(args)
|
||||
@@ -12,9 +11,9 @@ local function exec(args)
|
||||
return ret.status, ret.stdout, ret
|
||||
end
|
||||
|
||||
-- return if it was explicitly set on the command line
|
||||
-- return true if it was explicitly set on the command line
|
||||
local function option_was_set(name)
|
||||
return mp.get_property_bool("option-info/" .. name .. "/set-from-commandline",
|
||||
return mp.get_property_bool("option-info/" ..name.. "/set-from-commandline",
|
||||
false)
|
||||
end
|
||||
|
||||
@@ -51,41 +50,25 @@ local function append_rtmp_prop(props, name, value)
|
||||
return props..name.."=\""..value.."\""
|
||||
end
|
||||
|
||||
local function edl_escape(url)
|
||||
return "%" .. string.len(url) .. "%" .. url
|
||||
end
|
||||
|
||||
|
||||
mp.add_hook("on_load", 10, function ()
|
||||
local url = mp.get_property("stream-open-filename")
|
||||
|
||||
if (url:find("http://") == 1) or (url:find("https://") == 1)
|
||||
or (url:find("ytdl://") == 1) then
|
||||
|
||||
-- check version of youtube-dl if not done yet
|
||||
if (ytdl.vercheck == nil) then
|
||||
|
||||
-- check for youtube-dl in mpv's config dir
|
||||
-- check for youtube-dl in mpv's config dir
|
||||
if not (ytdl.searched) then
|
||||
local ytdl_mcd = mp.find_config_file("youtube-dl")
|
||||
if not (ytdl_mcd == nil) then
|
||||
msg.verbose("found youtube-dl at: " .. ytdl_mcd)
|
||||
ytdl.path = ytdl_mcd
|
||||
end
|
||||
|
||||
msg.debug("checking ytdl version ...")
|
||||
local es, version = exec({ytdl.path, "--version"})
|
||||
if (es < 0) then
|
||||
msg.warn("youtube-dl not found, not executable, or broken.")
|
||||
ytdl.vercheck = false
|
||||
elseif (version < ytdl.minver) then
|
||||
msg.verbose("found youtube-dl version: " .. version)
|
||||
msg.warn("Your version of youtube-dl is too old! "
|
||||
.. "You need at least version '"..ytdl.minver
|
||||
.. "', try running `youtube-dl -U`.")
|
||||
ytdl.vercheck = false
|
||||
else
|
||||
msg.verbose("found youtube-dl version: " .. version)
|
||||
ytdl.vercheck = true
|
||||
end
|
||||
end
|
||||
|
||||
if not (ytdl.vercheck) then
|
||||
return
|
||||
ytdl.searched = true
|
||||
end
|
||||
|
||||
-- strip ytdl://
|
||||
@@ -95,14 +78,18 @@ mp.add_hook("on_load", 10, function ()
|
||||
|
||||
local format = mp.get_property("options/ytdl-format")
|
||||
local raw_options = mp.get_property_native("options/ytdl-raw-options")
|
||||
local allsubs = true
|
||||
|
||||
local command = {
|
||||
ytdl.path, "--no-warnings", "-J", "--flat-playlist", "--all-subs",
|
||||
ytdl.path, "--no-warnings", "-J", "--flat-playlist",
|
||||
"--sub-format", "ass/srt/best", "--no-playlist"
|
||||
}
|
||||
|
||||
-- Checks if video option is "no", change options accordingly
|
||||
if (mp.get_property("options/vid") == "no") then
|
||||
-- Checks if video option is "no", change format accordingly,
|
||||
-- but only if user didn't explicitly set one
|
||||
if (mp.get_property("options/vid") == "no")
|
||||
and not option_was_set("ytdl-format") then
|
||||
|
||||
format = "bestaudio/best"
|
||||
msg.verbose("Video disabled. Only using audio")
|
||||
end
|
||||
@@ -117,6 +104,13 @@ mp.add_hook("on_load", 10, function ()
|
||||
if (arg ~= "") then
|
||||
table.insert(command, arg)
|
||||
end
|
||||
if (param == "sub-lang") and (arg ~= "") then
|
||||
allsubs = false
|
||||
end
|
||||
end
|
||||
|
||||
if (allsubs == true) then
|
||||
table.insert(command, "--all-subs")
|
||||
end
|
||||
table.insert(command, "--")
|
||||
table.insert(command, url)
|
||||
@@ -145,7 +139,8 @@ mp.add_hook("on_load", 10, function ()
|
||||
msg.verbose("Got direct URL")
|
||||
return
|
||||
elseif not (json["_type"] == nil)
|
||||
and ((json["_type"] == "playlist") or (json["_type"] == "multi_video")) then
|
||||
and ((json["_type"] == "playlist")
|
||||
or (json["_type"] == "multi_video")) then
|
||||
-- a playlist
|
||||
|
||||
if (#json.entries == 0) then
|
||||
@@ -159,13 +154,9 @@ mp.add_hook("on_load", 10, function ()
|
||||
and (json.entries[1]["webpage_url"] == json["webpage_url"]) then
|
||||
msg.verbose("multi-arc video detected, building EDL")
|
||||
|
||||
|
||||
local playlist = "edl://"
|
||||
for i, entry in pairs(json.entries) do
|
||||
|
||||
local urllength = string.len(entry.url)
|
||||
playlist = playlist .. "%" .. urllength .. "%" .. entry.url .. ";"
|
||||
|
||||
playlist = playlist .. edl_escape(entry.url) .. ";"
|
||||
end
|
||||
|
||||
msg.debug("EDL: " .. playlist)
|
||||
@@ -177,7 +168,8 @@ mp.add_hook("on_load", 10, function ()
|
||||
|
||||
mp.set_property("stream-open-filename", playlist)
|
||||
if not (json.title == nil) then
|
||||
mp.set_property("file-local-options/force-media-title", json.title)
|
||||
mp.set_property("file-local-options/force-media-title",
|
||||
json.title)
|
||||
end
|
||||
|
||||
else
|
||||
@@ -206,21 +198,13 @@ mp.add_hook("on_load", 10, function ()
|
||||
|
||||
-- DASH?
|
||||
if not (json["requested_formats"] == nil) then
|
||||
msg.info("Using DASH, expect inaccurate duration.")
|
||||
if not (json.duration == nil) then
|
||||
msg.info("Actual duration: " .. mp.format_time(json.duration))
|
||||
end
|
||||
|
||||
-- video url
|
||||
streamurl = json["requested_formats"][1].url
|
||||
|
||||
-- audio url
|
||||
mp.set_property("file-local-options/audio-file",
|
||||
json["requested_formats"][2].url)
|
||||
|
||||
-- workaround for slow startup (causes inaccurate duration)
|
||||
mp.set_property("file-local-options/demuxer-lavf-o",
|
||||
"fflags=+ignidx")
|
||||
mp.commandv("audio-add", json["requested_formats"][2].url,
|
||||
"select", json["requested_formats"][2]["format_note"])
|
||||
|
||||
elseif not (json.url == nil) then
|
||||
-- normal video
|
||||
@@ -251,7 +235,7 @@ mp.add_hook("on_load", 10, function ()
|
||||
end
|
||||
|
||||
if not (sub == nil) then
|
||||
mp.commandv("sub_add", sub,
|
||||
mp.commandv("sub-add", sub,
|
||||
"auto", sub_info.ext, lang)
|
||||
else
|
||||
msg.verbose("No subtitle data/url for ["..lang.."]")
|
||||
@@ -259,19 +243,24 @@ mp.add_hook("on_load", 10, function ()
|
||||
end
|
||||
end
|
||||
|
||||
-- set start and end time
|
||||
-- set start time
|
||||
if not (json.start_time == nil) then
|
||||
msg.debug("setting start to: " .. json.start_time .. " secs")
|
||||
mp.set_property("file-local-options/start",json.start_time)
|
||||
msg.debug("Setting start to: " .. json.start_time .. " secs")
|
||||
mp.set_property("file-local-options/start", json.start_time)
|
||||
end
|
||||
|
||||
-- for rtmp
|
||||
if not (json.play_path == nil) then
|
||||
local rtmp_prop = append_rtmp_prop(nil, "rtmp_tcurl", streamurl)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop, "rtmp_pageurl", json.page_url)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop, "rtmp_playpath", json.play_path)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop, "rtmp_swfverify", json.player_url)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop, "rtmp_app", json.app)
|
||||
local rtmp_prop = append_rtmp_prop(nil,
|
||||
"rtmp_tcurl", streamurl)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop,
|
||||
"rtmp_pageurl", json.page_url)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop,
|
||||
"rtmp_playpath", json.play_path)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop,
|
||||
"rtmp_swfverify", json.player_url)
|
||||
rtmp_prop = append_rtmp_prop(rtmp_prop,
|
||||
"rtmp_app", json.app)
|
||||
|
||||
mp.set_property("file-local-options/stream-lavf-o", rtmp_prop)
|
||||
end
|
||||
|
||||
+1
-1
@@ -466,7 +466,7 @@ int mp_initialize(struct MPContext *mpctx, char **options)
|
||||
if (opts->consolecontrols && cas_terminal_owner(mpctx, mpctx))
|
||||
terminal_setup_getch(mpctx->input);
|
||||
|
||||
if (handle_force_window(mpctx, false) < 0)
|
||||
if (opts->force_vo == 2 && handle_force_window(mpctx, false) < 0)
|
||||
return -1;
|
||||
|
||||
#if !defined(__MINGW32__)
|
||||
|
||||
+12
-3
@@ -54,6 +54,14 @@ static void sadd_hhmmssff(char **buf, double time, bool fractions)
|
||||
talloc_free(s);
|
||||
}
|
||||
|
||||
// If time unknown (MP_NOPTS_VALUE), use 0 instead.
|
||||
static void sadd_hhmmssff_u(char **buf, double time, bool fractions)
|
||||
{
|
||||
if (time == MP_NOPTS_VALUE)
|
||||
time = 0;
|
||||
sadd_hhmmssff(buf, time, fractions);
|
||||
}
|
||||
|
||||
static void sadd_percentage(char **buf, int percent) {
|
||||
if (percent >= 0)
|
||||
*buf = talloc_asprintf_append(*buf, " (%d%%)", percent);
|
||||
@@ -191,7 +199,7 @@ static void print_status(struct MPContext *mpctx)
|
||||
saddf(&line, ": ");
|
||||
|
||||
// Playback position
|
||||
sadd_hhmmssff(&line, get_playback_time(mpctx), mpctx->opts->osd_fractions);
|
||||
sadd_hhmmssff_u(&line, get_playback_time(mpctx), mpctx->opts->osd_fractions);
|
||||
|
||||
double len = get_time_length(mpctx);
|
||||
if (len >= 0) {
|
||||
@@ -360,7 +368,8 @@ void set_osd_bar_chapters(struct MPContext *mpctx, int type)
|
||||
} else {
|
||||
int num = get_chapter_count(mpctx);
|
||||
for (int n = 0; n < num; n++) {
|
||||
double time = chapter_start_time(mpctx, n);
|
||||
double time = chapter_start_time(mpctx, n) -
|
||||
get_start_time(mpctx);
|
||||
if (time >= 0) {
|
||||
float pos = time / len;
|
||||
MP_TARRAY_APPEND(mpctx, mpctx->osd_progbar.stops,
|
||||
@@ -429,7 +438,7 @@ static void sadd_osd_status(char **buffer, struct MPContext *mpctx, int level)
|
||||
*buffer = talloc_strdup_append(*buffer, text);
|
||||
talloc_free(text);
|
||||
} else {
|
||||
sadd_hhmmssff(buffer, get_playback_time(mpctx), fractions);
|
||||
sadd_hhmmssff_u(buffer, get_playback_time(mpctx), fractions);
|
||||
if (level == 3) {
|
||||
double len = get_time_length(mpctx);
|
||||
if (len >= 0) {
|
||||
|
||||
+12
-9
@@ -199,7 +199,8 @@ static int mp_seek(MPContext *mpctx, struct seek_params seek,
|
||||
break;
|
||||
case MPSEEK_RELATIVE:
|
||||
direction = seek.amount > 0 ? 1 : -1;
|
||||
target_time = seek.amount + get_current_time(mpctx);
|
||||
double cur = get_current_time(mpctx);
|
||||
target_time = seek.amount + (cur == MP_NOPTS_VALUE ? 0 : cur);
|
||||
break;
|
||||
case MPSEEK_FACTOR: ;
|
||||
double len = get_time_length(mpctx);
|
||||
@@ -390,18 +391,20 @@ double get_time_length(struct MPContext *mpctx)
|
||||
double get_current_time(struct MPContext *mpctx)
|
||||
{
|
||||
struct demuxer *demuxer = mpctx->demuxer;
|
||||
if (!demuxer)
|
||||
return 0;
|
||||
if (mpctx->playback_pts != MP_NOPTS_VALUE)
|
||||
return mpctx->playback_pts;
|
||||
if (mpctx->last_seek_pts != MP_NOPTS_VALUE)
|
||||
return mpctx->last_seek_pts;
|
||||
return 0;
|
||||
if (demuxer) {
|
||||
if (mpctx->playback_pts != MP_NOPTS_VALUE)
|
||||
return mpctx->playback_pts;
|
||||
if (mpctx->last_seek_pts != MP_NOPTS_VALUE)
|
||||
return mpctx->last_seek_pts;
|
||||
}
|
||||
return MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
double get_playback_time(struct MPContext *mpctx)
|
||||
{
|
||||
double cur = get_current_time(mpctx);
|
||||
if (cur == MP_NOPTS_VALUE)
|
||||
return cur;
|
||||
double start = get_start_time(mpctx);
|
||||
// During seeking, the time corresponds to the last seek time - apply some
|
||||
// cosmetics to it.
|
||||
@@ -917,7 +920,7 @@ int handle_force_window(struct MPContext *mpctx, bool force)
|
||||
.w = w, .h = h,
|
||||
.d_w = w, .d_h = h,
|
||||
};
|
||||
if (vo_reconfig(vo, &p, 0) < 0)
|
||||
if (vo_reconfig(vo, &p) < 0)
|
||||
goto err;
|
||||
vo_control(vo, VOCTRL_RESTORE_SCREENSAVER, NULL);
|
||||
vo_set_paused(vo, true);
|
||||
|
||||
+43
-23
@@ -550,6 +550,9 @@ static void adjust_sync(struct MPContext *mpctx, double v_pts, double frame_time
|
||||
change = max_change;
|
||||
mpctx->delay += change;
|
||||
mpctx->total_avsync_change += change;
|
||||
|
||||
if (mpctx->display_sync_active)
|
||||
mpctx->total_avsync_change = 0;
|
||||
}
|
||||
|
||||
// Make the frame at position 0 "known" to the playback logic. This must happen
|
||||
@@ -748,8 +751,8 @@ static void update_avsync_before_frame(struct MPContext *mpctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the A/V sync difference after a video frame has been shown.
|
||||
static void update_avsync_after_frame(struct MPContext *mpctx)
|
||||
// Update the A/V sync difference when a new video frame is being shown.
|
||||
static void update_av_diff(struct MPContext *mpctx, double offset)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
|
||||
@@ -760,13 +763,12 @@ static void update_avsync_after_frame(struct MPContext *mpctx)
|
||||
return;
|
||||
|
||||
double a_pos = playing_audio_pts(mpctx);
|
||||
if (a_pos != MP_NOPTS_VALUE && mpctx->video_pts != MP_NOPTS_VALUE) {
|
||||
mpctx->last_av_difference = a_pos - mpctx->video_pts
|
||||
+ opts->audio_delay + offset;
|
||||
}
|
||||
|
||||
mpctx->last_av_difference = a_pos - mpctx->video_pts + opts->audio_delay;
|
||||
if (mpctx->time_frame > 0)
|
||||
mpctx->last_av_difference += mpctx->time_frame * mpctx->video_speed;
|
||||
if (a_pos == MP_NOPTS_VALUE || mpctx->video_pts == MP_NOPTS_VALUE) {
|
||||
mpctx->last_av_difference = 0;
|
||||
} else if (fabs(mpctx->last_av_difference) > 0.5 && !mpctx->drop_message_shown) {
|
||||
if (fabs(mpctx->last_av_difference) > 0.5 && !mpctx->drop_message_shown) {
|
||||
MP_WARN(mpctx, "%s", av_desync_help_text);
|
||||
mpctx->drop_message_shown = true;
|
||||
}
|
||||
@@ -787,6 +789,7 @@ static void init_vo(struct MPContext *mpctx)
|
||||
video_set_colors(d_video, "saturation", opts->gamma_saturation);
|
||||
if (opts->gamma_hue != 1000)
|
||||
video_set_colors(d_video, "hue", opts->gamma_hue);
|
||||
video_set_colors(d_video, "output-levels", opts->video_output_levels);
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
|
||||
}
|
||||
@@ -911,7 +914,7 @@ static void handle_display_sync_frame(struct MPContext *mpctx,
|
||||
bool resample = mode == VS_DISP_RESAMPLE || mode == VS_DISP_RESAMPLE_VDROP ||
|
||||
mode == VS_DISP_RESAMPLE_NONE;
|
||||
bool drop = mode == VS_DISP_VDROP || mode == VS_DISP_RESAMPLE ||
|
||||
mode == VS_DISP_RESAMPLE_VDROP;
|
||||
mode == VS_DISP_ADROP || mode == VS_DISP_RESAMPLE_VDROP;
|
||||
drop &= (opts->frame_dropping & 1);
|
||||
|
||||
if (resample && using_spdif_passthrough(mpctx))
|
||||
@@ -959,10 +962,11 @@ static void handle_display_sync_frame(struct MPContext *mpctx,
|
||||
|
||||
// If we are too far ahead/behind, attempt to drop/repeat frames. In
|
||||
// particular, don't attempt to change speed for them.
|
||||
if (drop) {
|
||||
// Tolerate some desync to avoid frame dropping due to jitter.
|
||||
if (drop && fabs(av_diff) >= 0.080 && fabs(av_diff) / vsync >= 2)
|
||||
drop_repeat = -av_diff / vsync; // round towards 0
|
||||
av_diff -= drop_repeat * vsync;
|
||||
}
|
||||
|
||||
av_diff += drop_repeat * vsync;
|
||||
|
||||
if (resample) {
|
||||
double audio_factor = 1.0;
|
||||
@@ -985,7 +989,7 @@ static void handle_display_sync_frame(struct MPContext *mpctx,
|
||||
if (av_diff * -mpctx->display_sync_drift_dir >= 0)
|
||||
new = 0;
|
||||
if (fabs(av_diff) > max_drift)
|
||||
new = copysign(1, av_diff);
|
||||
new = av_diff >= 0 ? 1 : -1;
|
||||
if (mpctx->display_sync_drift_dir != new) {
|
||||
MP_VERBOSE(mpctx, "Change display sync audio drift: %d\n", new);
|
||||
mpctx->display_sync_drift_dir = new;
|
||||
@@ -1022,16 +1026,21 @@ static void handle_display_sync_frame(struct MPContext *mpctx,
|
||||
vo_increment_drop_count(vo, 1);
|
||||
|
||||
// Estimate the video position, so we can calculate a good A/V difference
|
||||
// value with update_avsync_after_frame() later. This is used to estimate
|
||||
// A/V drift.
|
||||
mpctx->time_frame = 0;
|
||||
// value below. This is used to estimate A/V drift.
|
||||
double time_left = (vo_get_next_frame_start_time(vo) - mp_time_us()) / 1e6;
|
||||
if (time_left >= 0)
|
||||
mpctx->time_frame += time_left;
|
||||
time_left = MPMAX(time_left, 0);
|
||||
// We also know that the timing is (necessarily) off, because we have to
|
||||
// align frame timings on the vsync boundaries. This is unavoidable, and
|
||||
// for the sake of the video sync calculations we pretend it's perfect.
|
||||
mpctx->time_frame -= mpctx->display_sync_error;
|
||||
time_left -= mpctx->display_sync_error;
|
||||
// Likewise, we know sync is off, but is going to be compensated.
|
||||
time_left += drop_repeat * vsync;
|
||||
|
||||
mpctx->total_avsync_change = 0;
|
||||
update_av_diff(mpctx, time_left);
|
||||
|
||||
// A bad guess, only needed when reverting to audio sync.
|
||||
mpctx->time_frame = time_left;
|
||||
|
||||
mpctx->speed_factor_v = video_speed_correction;
|
||||
|
||||
@@ -1053,6 +1062,16 @@ done:
|
||||
MPMAX(0, mpctx->display_sync_disable_counter - 1);
|
||||
}
|
||||
|
||||
static void schedule_frame(struct MPContext *mpctx, struct vo_frame *frame)
|
||||
{
|
||||
handle_display_sync_frame(mpctx, frame);
|
||||
|
||||
if (!mpctx->display_sync_active) {
|
||||
update_av_diff(mpctx, mpctx->time_frame > 0 ?
|
||||
mpctx->time_frame * mpctx->video_speed : 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the next frame duration as stored in the file.
|
||||
// frame=0 means the current frame, 1 the frame after that etc.
|
||||
// Can return -1, though usually will return a fallback if frame unavailable.
|
||||
@@ -1100,11 +1119,14 @@ void write_video(struct MPContext *mpctx, double endpts)
|
||||
return;
|
||||
|
||||
if (r == VD_EOF) {
|
||||
int prev_state = mpctx->video_status;
|
||||
mpctx->video_status =
|
||||
vo_still_displaying(vo) ? STATUS_DRAINING : STATUS_EOF;
|
||||
mpctx->delay = 0;
|
||||
mpctx->last_av_difference = 0;
|
||||
MP_DBG(mpctx, "video EOF (status=%d)\n", mpctx->video_status);
|
||||
if (prev_state != mpctx->video_status)
|
||||
mpctx->sleeptime = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1131,7 +1153,7 @@ void write_video(struct MPContext *mpctx, double endpts)
|
||||
info->name, p.w, p.h, extra, vo_format_name(p.imgfmt));
|
||||
MP_VERBOSE(mpctx, "VO: Description: %s\n", info->description);
|
||||
|
||||
int vo_r = vo_reconfig(vo, &p, 0);
|
||||
int vo_r = vo_reconfig(vo, &p);
|
||||
if (vo_r < 0) {
|
||||
mpctx->error_playing = MPV_ERROR_VO_INIT_FAILED;
|
||||
goto error;
|
||||
@@ -1175,13 +1197,11 @@ void write_video(struct MPContext *mpctx, double endpts)
|
||||
frame->duration = MPCLAMP(diff, 0, 10) * 1e6;
|
||||
}
|
||||
|
||||
handle_display_sync_frame(mpctx, frame);
|
||||
|
||||
mpctx->video_pts = mpctx->next_frames[0]->pts;
|
||||
mpctx->last_vo_pts = mpctx->video_pts;
|
||||
mpctx->playback_pts = mpctx->video_pts;
|
||||
|
||||
update_avsync_after_frame(mpctx);
|
||||
schedule_frame(mpctx, frame);
|
||||
|
||||
mpctx->osd_force_update = true;
|
||||
update_osd_msg(mpctx);
|
||||
|
||||
+4
-2
@@ -288,6 +288,8 @@ static int open_internal(const stream_info_t *sinfo, const char *url, int flags,
|
||||
s->is_network = sinfo->is_network;
|
||||
s->mode = flags & (STREAM_READ | STREAM_WRITE);
|
||||
|
||||
MP_VERBOSE(s, "Opening %s\n", url);
|
||||
|
||||
if ((s->mode & STREAM_WRITE) && !sinfo->can_write) {
|
||||
MP_VERBOSE(s, "No write access implemented.\n");
|
||||
talloc_free(s);
|
||||
@@ -328,11 +330,11 @@ static int open_internal(const stream_info_t *sinfo, const char *url, int flags,
|
||||
|
||||
s->uncached_type = s->type;
|
||||
|
||||
MP_VERBOSE(s, "Opened: %s\n", url);
|
||||
|
||||
if (s->mime_type)
|
||||
MP_VERBOSE(s, "Mime-type: '%s'\n", s->mime_type);
|
||||
|
||||
MP_VERBOSE(s, "Stream opened successfully.\n");
|
||||
|
||||
*ret = s;
|
||||
return STREAM_OK;
|
||||
}
|
||||
|
||||
@@ -168,6 +168,10 @@ void mp_setup_av_network_options(AVDictionary **dict, struct mpv_global *global,
|
||||
av_dict_set(dict, "tls_verify", opts->network_tls_verify ? "1" : "0", 0);
|
||||
if (opts->network_tls_ca_file)
|
||||
av_dict_set(dict, "ca_file", opts->network_tls_ca_file, 0);
|
||||
if (opts->network_tls_cert_file)
|
||||
av_dict_set(dict, "cert_file", opts->network_tls_cert_file, 0);
|
||||
if (opts->network_tls_key_file)
|
||||
av_dict_set(dict, "key_file", opts->network_tls_key_file, 0);
|
||||
char *cust_headers = talloc_strdup(temp, "");
|
||||
if (opts->network_referrer) {
|
||||
cust_headers = talloc_asprintf_append(cust_headers, "Referer: %s\r\n",
|
||||
@@ -242,7 +246,6 @@ static int open_f(stream_t *stream)
|
||||
talloc_free(temp);
|
||||
return STREAM_OK;
|
||||
}
|
||||
MP_VERBOSE(stream, "Opening %s\n", filename);
|
||||
|
||||
// Replace "mms://" with "mmsh://", so that most mms:// URLs just work.
|
||||
bstr b_filename = bstr0(filename);
|
||||
|
||||
@@ -44,7 +44,6 @@ void mp_ass_set_style(ASS_Style *style, double res_y,
|
||||
if (!style->FontName || strcmp(style->FontName, opts->font) != 0) {
|
||||
free(style->FontName);
|
||||
style->FontName = strdup(opts->font);
|
||||
style->treat_fontname_as_pattern = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -480,13 +480,8 @@ void osd_rescale_bitmaps(struct sub_bitmaps *imgs, int frame_w, int frame_h,
|
||||
double yscale = (double)vidh / frame_h;
|
||||
if (compensate_par < 0)
|
||||
compensate_par = xscale / yscale / res.display_par;
|
||||
if (compensate_par > 0) {
|
||||
if (compensate_par > 1.0) {
|
||||
xscale /= compensate_par;
|
||||
} else {
|
||||
yscale *= compensate_par;
|
||||
}
|
||||
}
|
||||
if (compensate_par > 0)
|
||||
xscale /= compensate_par;
|
||||
int cx = vidw / 2 - (int)(frame_w * xscale) / 2;
|
||||
int cy = vidh / 2 - (int)(frame_h * yscale) / 2;
|
||||
for (int i = 0; i < imgs->num_parts; i++) {
|
||||
|
||||
@@ -471,9 +471,6 @@ static void update_object(struct osd_state *osd, struct osd_object *obj)
|
||||
void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj,
|
||||
struct sub_bitmaps *out_imgs)
|
||||
{
|
||||
if (!osd->opts->use_text_osd)
|
||||
return;
|
||||
|
||||
if (obj->force_redraw)
|
||||
update_object(osd, obj);
|
||||
|
||||
|
||||
+21
-3
@@ -23,6 +23,8 @@
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/intreadwrite.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "talloc.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/av_common.h"
|
||||
@@ -210,12 +212,19 @@ static void decode(struct sd *sd, struct demux_packet *packet)
|
||||
continue;
|
||||
if (r->w <= 0 || r->h <= 0)
|
||||
continue;
|
||||
img->bitmap = r->pict.data[0];
|
||||
#if HAVE_AV_SUBTITLE_NOPICT
|
||||
uint8_t **data = r->data;
|
||||
int *linesize = r->linesize;
|
||||
#else
|
||||
uint8_t **data = r->pict.data;
|
||||
int *linesize = r->pict.linesize;
|
||||
#endif
|
||||
img->bitmap = data[0];
|
||||
assert(r->nb_colors > 0);
|
||||
assert(r->nb_colors * 4 <= sizeof(img->palette));
|
||||
memcpy(img->palette, r->pict.data[1], r->nb_colors * 4);
|
||||
memcpy(img->palette, data[1], r->nb_colors * 4);
|
||||
b->bitmap = img;
|
||||
b->stride = r->pict.linesize[0];
|
||||
b->stride = linesize[0];
|
||||
b->w = r->w;
|
||||
b->h = r->h;
|
||||
b->x = r->x;
|
||||
@@ -276,6 +285,15 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res d, double pts,
|
||||
d.ml = d.mr = d.mt = d.mb = 0;
|
||||
int insize[2];
|
||||
get_resolution(sd, insize);
|
||||
for (int n = 0; n < res->num_parts; n++) {
|
||||
struct sub_bitmap *p = &res->parts[n];
|
||||
if ((p->x + p->w > insize[0] || p->y + p->h > insize[1]) &&
|
||||
priv->video_params.w > insize[0] && priv->video_params.h > insize[1])
|
||||
{
|
||||
insize[0] = priv->video_params.w;
|
||||
insize[1] = priv->video_params.h;
|
||||
}
|
||||
}
|
||||
osd_rescale_bitmaps(res, insize[0], insize[1], d, video_par);
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -183,7 +183,7 @@ static int parse_webvtt(AVPacket *in, AVPacket *pkt)
|
||||
AV_PKT_DATA_WEBVTT_IDENTIFIER,
|
||||
id_len);
|
||||
if (buf == NULL) {
|
||||
av_free_packet(pkt);
|
||||
av_packet_unref(pkt);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
memcpy(buf, id, id_len);
|
||||
@@ -194,7 +194,7 @@ static int parse_webvtt(AVPacket *in, AVPacket *pkt)
|
||||
AV_PKT_DATA_WEBVTT_SETTINGS,
|
||||
settings_len);
|
||||
if (buf == NULL) {
|
||||
av_free_packet(pkt);
|
||||
av_packet_unref(pkt);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
memcpy(buf, settings, settings_len);
|
||||
@@ -202,7 +202,9 @@ static int parse_webvtt(AVPacket *in, AVPacket *pkt)
|
||||
|
||||
pkt->pts = in->pts;
|
||||
pkt->duration = in->duration;
|
||||
#if !HAVE_AV_AVPACKET_INT64_DURATION
|
||||
pkt->convergence_duration = in->convergence_duration;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -254,7 +256,7 @@ static void decode(struct sd *sd, struct demux_packet *packet)
|
||||
|
||||
done:
|
||||
avsubtitle_free(&sub);
|
||||
av_free_packet(&parsed_pkt);
|
||||
av_packet_unref(&parsed_pkt);
|
||||
}
|
||||
|
||||
static void reset(struct sd *sd)
|
||||
|
||||
+2
-1
@@ -90,6 +90,7 @@ const char *const mp_csp_equalizer_names[MP_CSP_EQ_COUNT] = {
|
||||
"hue",
|
||||
"saturation",
|
||||
"gamma",
|
||||
"output-levels",
|
||||
};
|
||||
|
||||
const struct m_opt_choice_alternatives mp_chroma_names[] = {
|
||||
@@ -709,7 +710,6 @@ void mp_csp_set_image_params(struct mp_csp_params *params,
|
||||
mp_image_params_guess_csp(&p); // ensure consistency
|
||||
params->colorspace = p.colorspace;
|
||||
params->levels_in = p.colorlevels;
|
||||
params->levels_out = p.outputlevels;
|
||||
}
|
||||
|
||||
// Copy settings from eq into params.
|
||||
@@ -721,6 +721,7 @@ void mp_csp_copy_equalizer_values(struct mp_csp_params *params,
|
||||
params->hue = eq->values[MP_CSP_EQ_HUE] / 100.0 * M_PI;
|
||||
params->saturation = (eq->values[MP_CSP_EQ_SATURATION] + 100) / 100.0;
|
||||
params->gamma = exp(log(8.0) * eq->values[MP_CSP_EQ_GAMMA] / 100.0);
|
||||
params->levels_out = eq->values[MP_CSP_EQ_OUTPUT_LEVELS];
|
||||
}
|
||||
|
||||
static int find_eq(int capabilities, const char *name)
|
||||
|
||||
+3
-1
@@ -161,6 +161,7 @@ enum mp_csp_equalizer_param {
|
||||
MP_CSP_EQ_HUE,
|
||||
MP_CSP_EQ_SATURATION,
|
||||
MP_CSP_EQ_GAMMA,
|
||||
MP_CSP_EQ_OUTPUT_LEVELS,
|
||||
MP_CSP_EQ_COUNT,
|
||||
};
|
||||
|
||||
@@ -168,7 +169,8 @@ enum mp_csp_equalizer_param {
|
||||
( (1 << MP_CSP_EQ_BRIGHTNESS) \
|
||||
| (1 << MP_CSP_EQ_CONTRAST) \
|
||||
| (1 << MP_CSP_EQ_HUE) \
|
||||
| (1 << MP_CSP_EQ_SATURATION) )
|
||||
| (1 << MP_CSP_EQ_SATURATION) \
|
||||
| (1 << MP_CSP_EQ_OUTPUT_LEVELS) )
|
||||
|
||||
#define MP_CSP_EQ_CAPS_GAMMA (1 << MP_CSP_EQ_GAMMA)
|
||||
#define MP_CSP_EQ_CAPS_BRIGHTNESS (1 << MP_CSP_EQ_BRIGHTNESS)
|
||||
|
||||
+27
-79
@@ -63,12 +63,10 @@ void video_reset_decoding(struct dec_video *d_video)
|
||||
mp_image_unrefp(&d_video->waiting_decoded_mpi);
|
||||
d_video->num_buffered_pts = 0;
|
||||
d_video->last_pts = MP_NOPTS_VALUE;
|
||||
d_video->last_packet_pdts = MP_NOPTS_VALUE;
|
||||
d_video->first_packet_pdts = MP_NOPTS_VALUE;
|
||||
d_video->decoded_pts = MP_NOPTS_VALUE;
|
||||
d_video->codec_pts = MP_NOPTS_VALUE;
|
||||
d_video->codec_dts = MP_NOPTS_VALUE;
|
||||
d_video->sorted_pts = MP_NOPTS_VALUE;
|
||||
d_video->unsorted_pts = MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
int video_vd_control(struct dec_video *d_video, int cmd, void *arg)
|
||||
@@ -210,80 +208,28 @@ bool video_init_best_codec(struct dec_video *d_video, char* video_decoders)
|
||||
return !!d_video->vd_driver;
|
||||
}
|
||||
|
||||
static void add_pts_to_sort(struct dec_video *d_video, double pts)
|
||||
static void add_avi_pts(struct dec_video *d_video, double pts)
|
||||
{
|
||||
if (pts != MP_NOPTS_VALUE) {
|
||||
int delay = -1;
|
||||
video_vd_control(d_video, VDCTRL_QUERY_UNSEEN_FRAMES, &delay);
|
||||
if (delay >= 0 && delay < d_video->num_buffered_pts)
|
||||
d_video->num_buffered_pts = delay;
|
||||
if (d_video->num_buffered_pts ==
|
||||
sizeof(d_video->buffered_pts) / sizeof(double))
|
||||
if (d_video->num_buffered_pts == MP_ARRAY_SIZE(d_video->buffered_pts)) {
|
||||
MP_ERR(d_video, "Too many buffered pts\n");
|
||||
else {
|
||||
int i, j;
|
||||
for (i = 0; i < d_video->num_buffered_pts; i++)
|
||||
if (d_video->buffered_pts[i] < pts)
|
||||
break;
|
||||
for (j = d_video->num_buffered_pts; j > i; j--)
|
||||
d_video->buffered_pts[j] = d_video->buffered_pts[j - 1];
|
||||
d_video->buffered_pts[i] = pts;
|
||||
} else {
|
||||
for (int i = d_video->num_buffered_pts; i > 0; i--)
|
||||
d_video->buffered_pts[i] = d_video->buffered_pts[i - 1];
|
||||
d_video->buffered_pts[0] = pts;
|
||||
d_video->num_buffered_pts++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if pts1 comes before pts2. pts1 can be MP_NOPTS_VALUE, but pts2
|
||||
// always has to be valid. pts1 can't be equal or larger than pts2.
|
||||
#define PTS_IS_ORDERED(pts1, pts2) \
|
||||
((pts2) != MP_NOPTS_VALUE && ((pts1) == MP_NOPTS_VALUE || ((pts1) < (pts2))))
|
||||
|
||||
static double retrieve_sorted_pts(struct dec_video *d_video, double codec_pts)
|
||||
static double retrieve_avi_pts(struct dec_video *d_video, double codec_pts)
|
||||
{
|
||||
struct MPOpts *opts = d_video->opts;
|
||||
|
||||
double sorted_pts;
|
||||
if (d_video->num_buffered_pts) {
|
||||
d_video->num_buffered_pts--;
|
||||
sorted_pts = d_video->buffered_pts[d_video->num_buffered_pts];
|
||||
} else {
|
||||
MP_ERR(d_video, "No pts value from demuxer to use for frame!\n");
|
||||
sorted_pts = MP_NOPTS_VALUE;
|
||||
return d_video->buffered_pts[d_video->num_buffered_pts];
|
||||
}
|
||||
|
||||
if (!PTS_IS_ORDERED(d_video->sorted_pts, sorted_pts))
|
||||
d_video->num_sorted_pts_problems++;
|
||||
d_video->sorted_pts = sorted_pts;
|
||||
|
||||
if (!PTS_IS_ORDERED(d_video->unsorted_pts, codec_pts))
|
||||
d_video->num_unsorted_pts_problems++;
|
||||
d_video->unsorted_pts = codec_pts;
|
||||
|
||||
if (d_video->header->video->avi_dts) {
|
||||
// Actually, they don't need to be sorted, we just reuse the buffering.
|
||||
d_video->pts_assoc_mode = 2;
|
||||
} else if (opts->user_pts_assoc_mode) {
|
||||
d_video->pts_assoc_mode = opts->user_pts_assoc_mode;
|
||||
} else if (d_video->pts_assoc_mode == 0) {
|
||||
if (codec_pts != MP_NOPTS_VALUE)
|
||||
d_video->pts_assoc_mode = 1;
|
||||
else
|
||||
d_video->pts_assoc_mode = 2;
|
||||
} else {
|
||||
int probcount1 = d_video->num_unsorted_pts_problems;
|
||||
int probcount2 = d_video->num_sorted_pts_problems;
|
||||
if (d_video->pts_assoc_mode == 2) {
|
||||
int tmp = probcount1;
|
||||
probcount1 = probcount2;
|
||||
probcount2 = tmp;
|
||||
}
|
||||
if (probcount1 >= probcount2 * 1.5 + 2) {
|
||||
d_video->pts_assoc_mode = 3 - d_video->pts_assoc_mode;
|
||||
MP_WARN(d_video, "Switching to pts association mode %d.\n",
|
||||
d_video->pts_assoc_mode);
|
||||
}
|
||||
}
|
||||
return d_video->pts_assoc_mode == 1 ? codec_pts : sorted_pts;
|
||||
MP_ERR(d_video, "No pts value from demuxer to use for frame!\n");
|
||||
return MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
struct mp_image *video_decode(struct dec_video *d_video,
|
||||
@@ -291,9 +237,7 @@ struct mp_image *video_decode(struct dec_video *d_video,
|
||||
int drop_frame)
|
||||
{
|
||||
struct MPOpts *opts = d_video->opts;
|
||||
bool sort_pts =
|
||||
(opts->user_pts_assoc_mode != 1 || d_video->header->video->avi_dts)
|
||||
&& opts->correct_pts;
|
||||
bool avi_pts = d_video->header->video->avi_dts && opts->correct_pts;
|
||||
|
||||
struct demux_packet packet_copy;
|
||||
if (packet && packet->dts == MP_NOPTS_VALUE) {
|
||||
@@ -306,15 +250,18 @@ struct mp_image *video_decode(struct dec_video *d_video,
|
||||
double pkt_dts = packet ? packet->dts : MP_NOPTS_VALUE;
|
||||
|
||||
double pkt_pdts = pkt_pts == MP_NOPTS_VALUE ? pkt_dts : pkt_pts;
|
||||
if (pkt_pdts != MP_NOPTS_VALUE)
|
||||
d_video->last_packet_pdts = pkt_pdts;
|
||||
if (pkt_pdts != MP_NOPTS_VALUE && d_video->first_packet_pdts == MP_NOPTS_VALUE)
|
||||
d_video->first_packet_pdts = pkt_pdts;
|
||||
|
||||
if (sort_pts)
|
||||
add_pts_to_sort(d_video, pkt_pdts);
|
||||
if (avi_pts)
|
||||
add_avi_pts(d_video, pkt_pdts);
|
||||
|
||||
double prev_codec_pts = d_video->codec_pts;
|
||||
double prev_codec_dts = d_video->codec_dts;
|
||||
|
||||
if (d_video->header->video->avi_dts)
|
||||
drop_frame = 0;
|
||||
|
||||
MP_STATS(d_video, "start decode video");
|
||||
|
||||
struct mp_image *mpi = d_video->vd_driver->decode(d_video, packet, drop_frame);
|
||||
@@ -332,7 +279,7 @@ struct mp_image *video_decode(struct dec_video *d_video,
|
||||
mpi->fields &= ~MP_IMGFIELD_TOP_FIRST;
|
||||
mpi->fields |= MP_IMGFIELD_INTERLACED;
|
||||
}
|
||||
|
||||
|
||||
// Note: the PTS is reordered, but the DTS is not. Both should be monotonic.
|
||||
double pts = d_video->codec_pts;
|
||||
double dts = d_video->codec_dts;
|
||||
@@ -355,20 +302,21 @@ struct mp_image *video_decode(struct dec_video *d_video,
|
||||
pts = dts;
|
||||
|
||||
// Alternative PTS determination methods
|
||||
if (sort_pts)
|
||||
pts = retrieve_sorted_pts(d_video, pts);
|
||||
if (avi_pts)
|
||||
pts = retrieve_avi_pts(d_video, pts);
|
||||
|
||||
if (!opts->correct_pts || pts == MP_NOPTS_VALUE) {
|
||||
if (opts->correct_pts && !d_video->header->missing_timestamps)
|
||||
MP_WARN(d_video, "No video PTS! Making something up.\n");
|
||||
|
||||
double frame_time = 1.0f / (d_video->fps > 0 ? d_video->fps : 25);
|
||||
double base = d_video->last_packet_pdts;
|
||||
double base = d_video->first_packet_pdts;
|
||||
pts = d_video->decoded_pts;
|
||||
if (pts == MP_NOPTS_VALUE)
|
||||
if (pts == MP_NOPTS_VALUE) {
|
||||
pts = base == MP_NOPTS_VALUE ? 0 : base;
|
||||
|
||||
pts += frame_time;
|
||||
} else {
|
||||
pts += frame_time;
|
||||
}
|
||||
}
|
||||
|
||||
if (d_video->has_broken_packet_pts < 0)
|
||||
|
||||
@@ -55,17 +55,12 @@ struct dec_video {
|
||||
double codec_dts;
|
||||
int num_codec_dts_problems;
|
||||
|
||||
// PTS sorting (obscure, non-default)
|
||||
double buffered_pts[32];
|
||||
// PTS sorting (needed for AVI-style timestamps)
|
||||
double buffered_pts[64];
|
||||
int num_buffered_pts;
|
||||
double sorted_pts;
|
||||
int num_sorted_pts_problems;
|
||||
double unsorted_pts;
|
||||
int num_unsorted_pts_problems;
|
||||
int pts_assoc_mode;
|
||||
|
||||
// PTS or DTS of packet last read
|
||||
double last_packet_pdts;
|
||||
// PTS or DTS of packet first read
|
||||
double first_packet_pdts;
|
||||
|
||||
// There was at least one packet with non-sense timestamps.
|
||||
int has_broken_packet_pts; // <0: uninitialized, 0: no problems, 1: broken
|
||||
|
||||
+5
-57
@@ -37,7 +37,6 @@
|
||||
#include "video/mp_image_pool.h"
|
||||
#include "video/hwdec.h"
|
||||
#include "video/d3d.h"
|
||||
#include "gpu_memcpy_sse4.h"
|
||||
|
||||
// A minor evil.
|
||||
#ifndef FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO
|
||||
@@ -98,9 +97,6 @@ typedef struct surface_info {
|
||||
typedef struct DXVA2Context {
|
||||
struct mp_log *log;
|
||||
|
||||
void (*copy_nv12)(struct mp_image *dest, uint8_t *src_bits,
|
||||
unsigned src_pitch, unsigned surf_height);
|
||||
|
||||
HMODULE d3dlib;
|
||||
HMODULE dxva2lib;
|
||||
|
||||
@@ -243,8 +239,8 @@ static struct mp_image *dxva2_allocate_image(struct lavc_ctx *s,
|
||||
return mp_image_new_custom_ref(&mpi, w, dxva2_release_img);
|
||||
}
|
||||
|
||||
static void copy_nv12_fallback(struct mp_image *dest, uint8_t *src_bits,
|
||||
unsigned src_pitch, unsigned surf_height)
|
||||
static void copy_nv12(struct mp_image *dest, uint8_t *src_bits,
|
||||
unsigned src_pitch, unsigned surf_height)
|
||||
{
|
||||
struct mp_image buf = {0};
|
||||
mp_image_setfmt(&buf, IMGFMT_NV12);
|
||||
@@ -254,49 +250,9 @@ static void copy_nv12_fallback(struct mp_image *dest, uint8_t *src_bits,
|
||||
buf.stride[0] = src_pitch;
|
||||
buf.planes[1] = src_bits + src_pitch * surf_height;
|
||||
buf.stride[1] = src_pitch;
|
||||
mp_image_copy(dest, &buf);
|
||||
mp_image_copy_gpu(dest, &buf);
|
||||
}
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC target("sse4.1")
|
||||
|
||||
static void copy_nv12_gpu_sse4(struct mp_image *dest, uint8_t *src_bits,
|
||||
unsigned src_pitch, unsigned surf_height)
|
||||
{
|
||||
const int lines = dest->h;
|
||||
const int stride_y = dest->stride[0];
|
||||
const int stride_uv = dest->stride[1];
|
||||
|
||||
// If the strides match, the image can be copied in one go
|
||||
if (stride_y == src_pitch && stride_uv == src_pitch) {
|
||||
const size_t size = lines * src_pitch;
|
||||
gpu_memcpy(dest->planes[0], src_bits, size);
|
||||
gpu_memcpy(dest->planes[1], src_bits + src_pitch * surf_height, size / 2);
|
||||
|
||||
} else {
|
||||
// Copy the Y plane line-by-line
|
||||
uint8_t *dest_y = dest->planes[0];
|
||||
const uint8_t *src_y = src_bits;
|
||||
const int bytes_per_line = dest->w;
|
||||
for (int i = 0; i < lines; i++) {
|
||||
gpu_memcpy(dest_y, src_y, bytes_per_line);
|
||||
dest_y += stride_y;
|
||||
src_y += src_pitch;
|
||||
}
|
||||
|
||||
// Copy the UV plane line-by-line
|
||||
uint8_t *dest_uv = dest->planes[1];
|
||||
const uint8_t *src_uv = src_bits + src_pitch * surf_height;
|
||||
for (int i = 0; i < lines / 2; i++) {
|
||||
gpu_memcpy(dest_uv, src_uv, bytes_per_line);
|
||||
dest_uv += stride_uv;
|
||||
src_uv += src_pitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma GCC pop_options
|
||||
|
||||
static struct mp_image *dxva2_retrieve_image(struct lavc_ctx *s,
|
||||
struct mp_image *img)
|
||||
{
|
||||
@@ -324,7 +280,7 @@ static struct mp_image *dxva2_retrieve_image(struct lavc_ctx *s,
|
||||
return img;
|
||||
}
|
||||
|
||||
ctx->copy_nv12(sw_img, LockedRect.pBits, LockedRect.Pitch, surfaceDesc.Height);
|
||||
copy_nv12(sw_img, LockedRect.pBits, LockedRect.Pitch, surfaceDesc.Height);
|
||||
mp_image_set_size(sw_img, img->w, img->h);
|
||||
mp_image_copy_attributes(sw_img, img);
|
||||
|
||||
@@ -408,15 +364,7 @@ static int dxva2_init(struct lavc_ctx *s)
|
||||
ctx->log = mp_log_new(s, s->log, "dxva2");
|
||||
ctx->sw_pool = talloc_steal(ctx, mp_image_pool_new(17));
|
||||
|
||||
if (av_get_cpu_flags() & AV_CPU_FLAG_SSE4) {
|
||||
// Use a memcpy implementation optimised for copying from GPU memory
|
||||
MP_DBG(ctx, "Using SSE4 memcpy\n");
|
||||
ctx->copy_nv12 = copy_nv12_gpu_sse4;
|
||||
} else {
|
||||
// Use the CRT memcpy. This can be slower than software decoding.
|
||||
MP_WARN(ctx, "Using fallback memcpy (slow)\n");
|
||||
ctx->copy_nv12 = copy_nv12_fallback;
|
||||
}
|
||||
mp_check_gpu_memcpy(ctx->log, NULL);
|
||||
|
||||
ctx->deviceHandle = INVALID_HANDLE_VALUE;
|
||||
|
||||
|
||||
+58
-13
@@ -25,7 +25,7 @@
|
||||
#include <libavcodec/vaapi.h>
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include "config.h"
|
||||
|
||||
#include "lavc.h"
|
||||
#include "common/common.h"
|
||||
@@ -53,7 +53,9 @@ struct priv {
|
||||
struct mp_log *log;
|
||||
struct mp_vaapi_ctx *ctx;
|
||||
VADisplay display;
|
||||
Display *x11_display;
|
||||
|
||||
const struct va_native_display *native_display_fns;
|
||||
void *native_display;
|
||||
|
||||
// libavcodec shared struct
|
||||
struct vaapi_context *va_context;
|
||||
@@ -65,6 +67,47 @@ struct priv {
|
||||
struct mp_image_pool *sw_pool;
|
||||
};
|
||||
|
||||
struct va_native_display {
|
||||
void (*create)(struct priv *p);
|
||||
void (*destroy)(struct priv *p);
|
||||
};
|
||||
|
||||
static const struct va_native_display disp_x11;
|
||||
|
||||
static const struct va_native_display *const native_displays[] = {
|
||||
#if HAVE_VAAPI_X11
|
||||
&disp_x11,
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
|
||||
#if HAVE_VAAPI_X11
|
||||
#include <X11/Xlib.h>
|
||||
#include <va/va_x11.h>
|
||||
|
||||
static void x11_destroy(struct priv *p)
|
||||
{
|
||||
if (p->native_display)
|
||||
XCloseDisplay(p->native_display);
|
||||
p->native_display = NULL;
|
||||
}
|
||||
|
||||
static void x11_create(struct priv *p)
|
||||
{
|
||||
p->native_display = XOpenDisplay(NULL);
|
||||
if (!p->native_display)
|
||||
return;
|
||||
p->display = vaGetDisplay(p->native_display);
|
||||
if (!p->display)
|
||||
x11_destroy(p);
|
||||
}
|
||||
|
||||
static const struct va_native_display disp_x11 = {
|
||||
.create = x11_create,
|
||||
.destroy = x11_destroy,
|
||||
};
|
||||
#endif
|
||||
|
||||
#define HAS_HEVC VA_CHECK_VERSION(0, 38, 0)
|
||||
|
||||
#define PE(av_codec_id, ff_profile, vdp_profile) \
|
||||
@@ -295,9 +338,8 @@ static void destroy_va_dummy_ctx(struct priv *p)
|
||||
va_destroy(p->ctx);
|
||||
p->ctx = NULL;
|
||||
p->display = NULL;
|
||||
if (p->x11_display)
|
||||
XCloseDisplay(p->x11_display);
|
||||
p->x11_display = NULL;
|
||||
if (p->native_display_fns)
|
||||
p->native_display_fns->destroy(p);
|
||||
}
|
||||
|
||||
// Creates a "private" VADisplay, disconnected from the VO. We just create a
|
||||
@@ -305,15 +347,18 @@ static void destroy_va_dummy_ctx(struct priv *p)
|
||||
// connection along with struct mp_hwdec_info, if we wanted.)
|
||||
static bool create_va_dummy_ctx(struct priv *p)
|
||||
{
|
||||
p->x11_display = XOpenDisplay(NULL);
|
||||
if (!p->x11_display)
|
||||
for (int n = 0; native_displays[n]; n++) {
|
||||
native_displays[n]->create(p);
|
||||
if (p->display) {
|
||||
p->native_display_fns = native_displays[n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!p->display)
|
||||
goto destroy_ctx;
|
||||
VADisplay *display = vaGetDisplay(p->x11_display);
|
||||
if (!display)
|
||||
goto destroy_ctx;
|
||||
p->ctx = va_initialize(display, p->log, true);
|
||||
p->ctx = va_initialize(p->display, p->log, true);
|
||||
if (!p->ctx) {
|
||||
vaTerminate(display);
|
||||
vaTerminate(p->display);
|
||||
goto destroy_ctx;
|
||||
}
|
||||
return true;
|
||||
@@ -334,7 +379,7 @@ static void uninit(struct lavc_ctx *ctx)
|
||||
talloc_free(p->pool);
|
||||
p->pool = NULL;
|
||||
|
||||
if (p->x11_display)
|
||||
if (p->native_display_fns)
|
||||
destroy_va_dummy_ctx(p);
|
||||
|
||||
talloc_free(p);
|
||||
|
||||
@@ -42,7 +42,6 @@ extern const vd_functions_t *const mpcodecs_vd_drivers[];
|
||||
|
||||
enum vd_ctrl {
|
||||
VDCTRL_RESET = 1, // reset decode state after seeking
|
||||
VDCTRL_QUERY_UNSEEN_FRAMES, // current decoder lag
|
||||
VDCTRL_FORCE_HWDEC_FALLBACK, // force software decoding fallback
|
||||
VDCTRL_GET_HWDEC,
|
||||
};
|
||||
|
||||
+33
-43
@@ -74,6 +74,7 @@ struct vd_lavc_params {
|
||||
int threads;
|
||||
int bitexact;
|
||||
int check_hw_profile;
|
||||
int software_fallback;
|
||||
char **avopts;
|
||||
};
|
||||
|
||||
@@ -101,6 +102,7 @@ const struct m_sub_options vd_lavc_conf = {
|
||||
OPT_INT("threads", threads, M_OPT_MIN, .min = 0),
|
||||
OPT_FLAG("bitexact", bitexact, 0),
|
||||
OPT_FLAG("check-hw-profile", check_hw_profile, 0),
|
||||
OPT_FLAG("software-fallback", software_fallback, 0),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
@@ -108,6 +110,7 @@ const struct m_sub_options vd_lavc_conf = {
|
||||
.defaults = &(const struct vd_lavc_params){
|
||||
.show_all = 0,
|
||||
.check_hw_profile = 1,
|
||||
.software_fallback = 1,
|
||||
.skip_loop_filter = AVDISCARD_DEFAULT,
|
||||
.skip_idct = AVDISCARD_DEFAULT,
|
||||
.skip_frame = AVDISCARD_DEFAULT,
|
||||
@@ -116,7 +119,6 @@ const struct m_sub_options vd_lavc_conf = {
|
||||
};
|
||||
|
||||
const struct vd_lavc_hwdec mp_vd_lavc_vdpau;
|
||||
const struct vd_lavc_hwdec mp_vd_lavc_vda;
|
||||
const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox;
|
||||
const struct vd_lavc_hwdec mp_vd_lavc_vaapi;
|
||||
const struct vd_lavc_hwdec mp_vd_lavc_vaapi_copy;
|
||||
@@ -133,9 +135,6 @@ static const struct vd_lavc_hwdec *const hwdec_list[] = {
|
||||
#if HAVE_VIDEOTOOLBOX_HWACCEL
|
||||
&mp_vd_lavc_videotoolbox,
|
||||
#endif
|
||||
#if HAVE_VDA_HWACCEL
|
||||
&mp_vd_lavc_vda,
|
||||
#endif
|
||||
#if HAVE_VAAPI_HWACCEL
|
||||
&mp_vd_lavc_vaapi,
|
||||
&mp_vd_lavc_vaapi_copy,
|
||||
@@ -194,9 +193,7 @@ const struct hwdec_profile_entry *hwdec_find_profile(
|
||||
}
|
||||
for (int n = 0; table[n].av_codec; n++) {
|
||||
if (table[n].av_codec == codec) {
|
||||
if (table[n].ff_profile == FF_PROFILE_UNKNOWN ||
|
||||
profile == FF_PROFILE_UNKNOWN ||
|
||||
table[n].ff_profile == profile ||
|
||||
if (table[n].ff_profile == profile ||
|
||||
!lavc_param->check_hw_profile)
|
||||
return &table[n];
|
||||
}
|
||||
@@ -455,7 +452,6 @@ static void uninit_avctx(struct dec_video *vd)
|
||||
MP_ERR(vd, "Could not close codec.\n");
|
||||
|
||||
av_freep(&avctx->extradata);
|
||||
av_freep(&avctx->slice_offset);
|
||||
}
|
||||
|
||||
if (ctx->hwdec && ctx->hwdec->uninit)
|
||||
@@ -526,6 +522,9 @@ static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx,
|
||||
|
||||
assert(ctx->hwdec);
|
||||
|
||||
ctx->hwdec_request_reinit |= ctx->hwdec_failed;
|
||||
ctx->hwdec_failed = false;
|
||||
|
||||
if (ctx->hwdec->image_format) {
|
||||
for (int i = 0; fmt[i] != AV_PIX_FMT_NONE; i++) {
|
||||
if (ctx->hwdec->image_format == pixfmt2imgfmt(fmt[i])) {
|
||||
@@ -568,23 +567,16 @@ static int get_buffer2_hwdec(AVCodecContext *avctx, AVFrame *pic, int flags)
|
||||
struct dec_video *vd = avctx->opaque;
|
||||
vd_ffmpeg_ctx *ctx = vd->priv;
|
||||
|
||||
if (ctx->hwdec_failed)
|
||||
return avcodec_default_get_buffer2(avctx, pic, flags);
|
||||
|
||||
/* Decoders using ffmpeg's hwaccel architecture (everything except vdpau)
|
||||
* can fall back to software decoding automatically. However, we don't
|
||||
* want that: multithreading was already disabled. ffmpeg's fallback
|
||||
* isn't really useful, and causes more trouble than it helps.
|
||||
*
|
||||
* Instead of trying to "adjust" the thread_count fields in avctx, let
|
||||
* decoding fail hard. Then decode_with_fallback() will do our own software
|
||||
* fallback. Fully reinitializing the decoder is saner, and will probably
|
||||
* save us from other weird corner cases, like having to "reroute" the
|
||||
* get_buffer callback.
|
||||
*/
|
||||
int imgfmt = pixfmt2imgfmt(pic->format);
|
||||
if (!IMGFMT_IS_HWACCEL(imgfmt) || !ctx->hwdec)
|
||||
return -1;
|
||||
ctx->hwdec_failed = true;
|
||||
|
||||
/* Hardware decoding failed, and we will trigger a proper fallback later
|
||||
* when returning from the decode call. (We are forcing complete
|
||||
* reinitialization later to reset the thread count properly.)
|
||||
*/
|
||||
if (ctx->hwdec_failed)
|
||||
return avcodec_default_get_buffer2(avctx, pic, flags);
|
||||
|
||||
// We expect it to use the exact size used to create the hw decoder in
|
||||
// get_format_hwdec(). For cropped video, this is expected to be the
|
||||
@@ -609,8 +601,8 @@ static int get_buffer2_hwdec(AVCodecContext *avctx, AVFrame *pic, int flags)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode(struct dec_video *vd, struct demux_packet *packet,
|
||||
int flags, struct mp_image **out_image)
|
||||
static void decode(struct dec_video *vd, struct demux_packet *packet,
|
||||
int flags, struct mp_image **out_image)
|
||||
{
|
||||
int got_picture = 0;
|
||||
int ret;
|
||||
@@ -619,6 +611,9 @@ static int decode(struct dec_video *vd, struct demux_packet *packet,
|
||||
struct vd_lavc_params *lavc_param = ctx->opts->vd_lavc_params;
|
||||
AVPacket pkt;
|
||||
|
||||
if (ctx->hwdec_request_reinit)
|
||||
avcodec_flush_buffers(avctx);
|
||||
|
||||
if (flags) {
|
||||
// hr-seek framedrop vs. normal framedrop
|
||||
avctx->skip_frame = flags == 2 ? AVDISCARD_NONREF : lavc_param->framedrop;
|
||||
@@ -633,18 +628,21 @@ static int decode(struct dec_video *vd, struct demux_packet *packet,
|
||||
ret = avcodec_decode_video2(avctx, ctx->pic, &got_picture, &pkt);
|
||||
hwdec_unlock(ctx);
|
||||
|
||||
if (ctx->hwdec_failed || ret < 0) {
|
||||
if (ret < 0)
|
||||
MP_WARN(vd, "Error while decoding frame!\n");
|
||||
return -1;
|
||||
if (ret < 0) {
|
||||
MP_WARN(vd, "Error while decoding frame!\n");
|
||||
if (lavc_param->software_fallback)
|
||||
ctx->hwdec_failed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->hwdec_request_reinit)
|
||||
avcodec_flush_buffers(avctx);
|
||||
if (ctx->hwdec && ctx->hwdec_failed) {
|
||||
av_frame_unref(ctx->pic);
|
||||
return;
|
||||
}
|
||||
|
||||
// Skipped frame, or delayed output due to multithreaded decoding.
|
||||
if (!got_picture)
|
||||
return 0;
|
||||
return;
|
||||
|
||||
struct mp_image_params params;
|
||||
update_image_params(vd, ctx->pic, ¶ms);
|
||||
@@ -654,7 +652,7 @@ static int decode(struct dec_video *vd, struct demux_packet *packet,
|
||||
struct mp_image *mpi = mp_image_from_av_frame(ctx->pic);
|
||||
av_frame_unref(ctx->pic);
|
||||
if (!mpi)
|
||||
return 0; // mpi==NULL, or OOM
|
||||
return;
|
||||
assert(mpi->planes[0] || mpi->planes[3]);
|
||||
mp_image_set_params(mpi, ¶ms);
|
||||
|
||||
@@ -662,7 +660,6 @@ static int decode(struct dec_video *vd, struct demux_packet *packet,
|
||||
mpi = ctx->hwdec->process_image(ctx, mpi);
|
||||
|
||||
*out_image = mp_img_swap_to_native(mpi);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct mp_image *decode_with_fallback(struct dec_video *vd,
|
||||
@@ -673,8 +670,8 @@ static struct mp_image *decode_with_fallback(struct dec_video *vd,
|
||||
return NULL;
|
||||
|
||||
struct mp_image *mpi = NULL;
|
||||
int res = decode(vd, packet, flags, &mpi);
|
||||
if (res < 0) {
|
||||
decode(vd, packet, flags, &mpi);
|
||||
if (ctx->hwdec_failed) {
|
||||
// Failed hardware decoding? Try again in software.
|
||||
if (force_fallback(vd) && ctx->avctx)
|
||||
decode(vd, packet, flags, &mpi);
|
||||
@@ -701,13 +698,6 @@ static int control(struct dec_video *vd, int cmd, void *arg)
|
||||
case VDCTRL_RESET:
|
||||
avcodec_flush_buffers(avctx);
|
||||
return CONTROL_TRUE;
|
||||
case VDCTRL_QUERY_UNSEEN_FRAMES:;
|
||||
int delay = avctx->has_b_frames;
|
||||
assert(delay >= 0);
|
||||
if (avctx->active_thread_type & FF_THREAD_FRAME)
|
||||
delay += avctx->thread_count - 1;
|
||||
*(int *)arg = delay;
|
||||
return CONTROL_TRUE;
|
||||
case VDCTRL_GET_HWDEC: {
|
||||
int hwdec = ctx->hwdec ? ctx->hwdec->type : 0;
|
||||
if (!ctx->software_fallback_decoder)
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* Copyright (c) 2013 Stefano Pigozzi <stefano.pigozzi@gmail.com>
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libavcodec/version.h>
|
||||
#include <libavcodec/vda.h>
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/msg.h"
|
||||
#include "video/mp_image.h"
|
||||
#include "video/decode/lavc.h"
|
||||
#include "config.h"
|
||||
|
||||
static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info,
|
||||
const char *decoder)
|
||||
{
|
||||
hwdec_request_api(info, "videotoolbox");
|
||||
if (!info || !info->hwctx)
|
||||
return HWDEC_ERR_NO_CTX;
|
||||
if (mp_codec_to_av_codec_id(decoder) != AV_CODEC_ID_H264)
|
||||
return HWDEC_ERR_NO_CODEC;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int init(struct lavc_ctx *ctx)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct vda_error {
|
||||
int code;
|
||||
char *reason;
|
||||
};
|
||||
|
||||
static const struct vda_error vda_errors[] = {
|
||||
{ AVERROR(ENOSYS),
|
||||
"Hardware doesn't support accelerated decoding for this stream" },
|
||||
{ AVERROR(EINVAL),
|
||||
"Invalid configuration provided to VDADecoderCreate" },
|
||||
{ AVERROR_INVALIDDATA,
|
||||
"Generic error returned by the decoder layer. The cause can range from"
|
||||
" VDADecoder finding errors in the bitstream to another application"
|
||||
" using VDA at the moment. Only one application can use VDA at a"
|
||||
" givent time." },
|
||||
{ 0, NULL },
|
||||
};
|
||||
|
||||
static void print_vda_error(struct mp_log *log, int lev, char *message,
|
||||
int error_code)
|
||||
{
|
||||
for (int n = 0; vda_errors[n].code < 0; n++)
|
||||
if (vda_errors[n].code == error_code) {
|
||||
mp_msg(log, lev, "%s: %s (%d)\n",
|
||||
message, vda_errors[n].reason, error_code);
|
||||
return;
|
||||
}
|
||||
|
||||
mp_msg(log, lev, "%s: %d\n", message, error_code);
|
||||
}
|
||||
|
||||
static int init_decoder(struct lavc_ctx *ctx, int w, int h)
|
||||
{
|
||||
av_vda_default_free(ctx->avctx);
|
||||
#if HAVE_VDA_DEFAULT_INIT2
|
||||
AVVDAContext *vdactx = av_vda_alloc_context();
|
||||
vdactx->cv_pix_fmt_type = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
|
||||
int err = av_vda_default_init2(ctx->avctx, vdactx);
|
||||
#else
|
||||
int err = av_vda_default_init(ctx->avctx);
|
||||
#endif
|
||||
|
||||
if (err < 0) {
|
||||
print_vda_error(ctx->log, MSGL_ERR, "failed to init VDA decoder", err);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void uninit(struct lavc_ctx *ctx)
|
||||
{
|
||||
if (ctx->avctx)
|
||||
av_vda_default_free(ctx->avctx);
|
||||
}
|
||||
|
||||
static struct mp_image *process_image(struct lavc_ctx *ctx, struct mp_image *img)
|
||||
{
|
||||
// Same representation. IMGFMT_VDA is only needed to select the libavcodec
|
||||
// hwaccel driver.
|
||||
if (img->imgfmt == IMGFMT_VDA)
|
||||
mp_image_setfmt(img, IMGFMT_VIDEOTOOLBOX);
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
const struct vd_lavc_hwdec mp_vd_lavc_vda = {
|
||||
.type = HWDEC_VDA,
|
||||
.image_format = IMGFMT_VDA,
|
||||
.probe = probe,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.init_decoder = init_decoder,
|
||||
.process_image = process_image,
|
||||
};
|
||||
@@ -34,7 +34,6 @@ struct vf_priv_s {
|
||||
int outfmt;
|
||||
int colormatrix;
|
||||
int colorlevels;
|
||||
int outputlevels;
|
||||
int primaries;
|
||||
int gamma;
|
||||
int chroma_location;
|
||||
@@ -89,8 +88,6 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
|
||||
out->colorspace = p->colormatrix;
|
||||
if (p->colorlevels)
|
||||
out->colorlevels = p->colorlevels;
|
||||
if (p->outputlevels)
|
||||
out->outputlevels = p->outputlevels;
|
||||
if (p->primaries)
|
||||
out->primaries = p->primaries;
|
||||
if (p->gamma)
|
||||
@@ -137,7 +134,6 @@ static const m_option_t vf_opts_fields[] = {
|
||||
OPT_IMAGEFORMAT("outfmt", outfmt, 0),
|
||||
OPT_CHOICE_C("colormatrix", colormatrix, 0, mp_csp_names),
|
||||
OPT_CHOICE_C("colorlevels", colorlevels, 0, mp_csp_levels_names),
|
||||
OPT_CHOICE_C("outputlevels", outputlevels, 0, mp_csp_levels_names),
|
||||
OPT_CHOICE_C("primaries", primaries, 0, mp_csp_prim_names),
|
||||
OPT_CHOICE_C("gamma", gamma, 0, mp_csp_trc_names),
|
||||
OPT_CHOICE_C("chroma-location", chroma_location, 0, mp_chroma_names),
|
||||
@@ -147,6 +143,7 @@ static const m_option_t vf_opts_fields[] = {
|
||||
OPT_INT("dw", dw, 0),
|
||||
OPT_INT("dh", dh, 0),
|
||||
OPT_DOUBLE("dar", dar, 0),
|
||||
OPT_REMOVED("outputlevels", "use the --video-output-levels global option"),
|
||||
{0}
|
||||
};
|
||||
|
||||
|
||||
@@ -160,9 +160,12 @@ static struct mp_image *render(struct vf_instance *vf, struct mp_image *in,
|
||||
if (!p->pipe.filters || in_id == VA_INVALID_ID)
|
||||
return NULL;
|
||||
|
||||
struct mp_image *img = mp_image_pool_get(p->pool, IMGFMT_VAAPI, in->w, in->h);
|
||||
int r_w, r_h;
|
||||
va_surface_get_uncropped_size(in, &r_w, &r_h);
|
||||
struct mp_image *img = mp_image_pool_get(p->pool, IMGFMT_VAAPI, r_w, r_h);
|
||||
if (!img)
|
||||
return NULL;
|
||||
mp_image_set_size(img, in->w, in->h);
|
||||
|
||||
bool need_end_picture = false;
|
||||
bool success = false;
|
||||
@@ -201,8 +204,8 @@ static struct mp_image *render(struct vf_instance *vf, struct mp_image *in,
|
||||
goto cleanup;
|
||||
|
||||
param->surface = in_id;
|
||||
param->surface_region = NULL;
|
||||
param->output_region = NULL;
|
||||
param->surface_region = &(VARectangle){0, 0, in->w, in->h};
|
||||
param->output_region = &(VARectangle){0, 0, img->w, img->h};
|
||||
param->output_background_color = 0;
|
||||
param->filter_flags = flags;
|
||||
param->filters = p->pipe.filters;
|
||||
|
||||
@@ -118,9 +118,6 @@ static const struct {
|
||||
#endif
|
||||
|
||||
{IMGFMT_VDPAU, AV_PIX_FMT_VDPAU},
|
||||
#if HAVE_VDA_HWACCEL
|
||||
{IMGFMT_VDA, AV_PIX_FMT_VDA},
|
||||
#endif
|
||||
#if HAVE_VIDEOTOOLBOX_HWACCEL
|
||||
{IMGFMT_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX},
|
||||
#endif
|
||||
|
||||
@@ -19,18 +19,20 @@
|
||||
* Taken from the QuickSync decoder by Eric Gur
|
||||
*/
|
||||
|
||||
#ifndef GPU_MEMCPY_SSE4_H_
|
||||
#define GPU_MEMCPY_SSE4_H_
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC target("sse4.1")
|
||||
#include <smmintrin.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "gpu_memcpy.h"
|
||||
|
||||
// gpu_memcpy is a memcpy style function that copied data very fast from a
|
||||
// GPU tiled memory (write back)
|
||||
// Performance tip: page offset (12 lsb) of both addresses should be different
|
||||
// optimally use a 2K offset between them.
|
||||
static inline void *gpu_memcpy(void *restrict d, const void *restrict s, size_t size)
|
||||
void *gpu_memcpy(void *restrict d, const void *restrict s, size_t size)
|
||||
{
|
||||
static const size_t regsInLoop = sizeof(size_t) * 2; // 8 or 16
|
||||
|
||||
@@ -131,6 +133,3 @@ static inline void *gpu_memcpy(void *restrict d, const void *restrict s, size_t
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
#pragma GCC pop_options
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef GPU_MEMCPY_SSE4_H_
|
||||
#define GPU_MEMCPY_SSE4_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
void *gpu_memcpy(void *restrict d, const void *restrict s, size_t size);
|
||||
|
||||
#endif
|
||||
@@ -10,7 +10,6 @@ enum hwdec_type {
|
||||
HWDEC_AUTO = -1,
|
||||
HWDEC_NONE = 0,
|
||||
HWDEC_VDPAU = 1,
|
||||
HWDEC_VDA = 2,
|
||||
HWDEC_VIDEOTOOLBOX = 3,
|
||||
HWDEC_VAAPI = 4,
|
||||
HWDEC_VAAPI_COPY = 5,
|
||||
|
||||
@@ -146,7 +146,7 @@ error_exit:
|
||||
avcodec_close(avctx);
|
||||
av_free(avctx);
|
||||
av_frame_free(&pic);
|
||||
av_free_packet(&pkt);
|
||||
av_packet_unref(&pkt);
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
+9
-4
@@ -37,7 +37,6 @@ static const struct mp_imgfmt_entry mp_imgfmt_list[] = {
|
||||
// not in ffmpeg
|
||||
{"vdpau_output", IMGFMT_VDPAU_OUTPUT},
|
||||
// FFmpeg names have an annoying "_vld" suffix
|
||||
{"vda", IMGFMT_VDA},
|
||||
{"videotoolbox", IMGFMT_VIDEOTOOLBOX},
|
||||
{"vaapi", IMGFMT_VAAPI},
|
||||
// names below this are not preferred over the FFmpeg names
|
||||
@@ -232,15 +231,21 @@ struct mp_imgfmt_desc mp_imgfmt_get_desc(int mpfmt)
|
||||
if (pd->flags & (AV_PIX_FMT_FLAG_PAL | AV_PIX_FMT_FLAG_PSEUDOPAL))
|
||||
desc.flags |= MP_IMGFLAG_PAL;
|
||||
|
||||
if ((desc.flags & MP_IMGFLAG_YUV) && (desc.flags & MP_IMGFLAG_BYTE_ALIGNED))
|
||||
if ((desc.flags & (MP_IMGFLAG_YUV | MP_IMGFLAG_RGB))
|
||||
&& (desc.flags & MP_IMGFLAG_BYTE_ALIGNED))
|
||||
{
|
||||
bool same_depth = true;
|
||||
for (int p = 0; p < desc.num_planes; p++) {
|
||||
same_depth &= planedepth[p] == planedepth[0] &&
|
||||
desc.bpp[p] == desc.bpp[0];
|
||||
}
|
||||
if (same_depth && pd->nb_components == desc.num_planes)
|
||||
desc.flags |= MP_IMGFLAG_YUV_P;
|
||||
if (same_depth && pd->nb_components == desc.num_planes) {
|
||||
if (desc.flags & MP_IMGFLAG_YUV) {
|
||||
desc.flags |= MP_IMGFLAG_YUV_P;
|
||||
} else {
|
||||
desc.flags |= MP_IMGFLAG_RGB_P;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int p = 0; p < desc.num_planes; p++) {
|
||||
|
||||
+3
-5
@@ -67,6 +67,8 @@
|
||||
#define MP_IMGFLAG_HWACCEL 0x10000
|
||||
// Set if the chroma resolution is lower than luma resolution. Unset for non-YUV.
|
||||
#define MP_IMGFLAG_SUBSAMPLED 0x20000
|
||||
// Like MP_IMGFLAG_YUV_P, but RGB. The planes are organized as in IMGFMT_GBRP.
|
||||
#define MP_IMGFLAG_RGB_P 0x40000
|
||||
|
||||
// Exactly one of these bits is set in mp_imgfmt_desc.flags
|
||||
#define MP_IMGFLAG_COLOR_CLASS_MASK \
|
||||
@@ -205,11 +207,7 @@ enum mp_imgfmt {
|
||||
IMGFMT_VAAPI,
|
||||
IMGFMT_DXVA2, // IDirect3DSurface9 (NV12)
|
||||
IMGFMT_MMAL, // MMAL_BUFFER_HEADER_T
|
||||
// These use the same underlying format, but FFmpeg requires us to keep
|
||||
// them separate. The VDA decoder will change the format to
|
||||
// IMGFMT_VIDEOTOOLBOX, though.
|
||||
IMGFMT_VIDEOTOOLBOX,
|
||||
IMGFMT_VDA,
|
||||
IMGFMT_VIDEOTOOLBOX, // CVPixelBufferRef
|
||||
|
||||
// Generic pass-through of AV_PIX_FMT_*. Used for formats which don't have
|
||||
// a corresponding IMGFMT_ value.
|
||||
|
||||
+64
-24
@@ -35,6 +35,7 @@
|
||||
#include "mp_image.h"
|
||||
#include "sws_utils.h"
|
||||
#include "fmt-conversion.h"
|
||||
#include "gpu_memcpy.h"
|
||||
|
||||
#include "video/filter/vf.h"
|
||||
|
||||
@@ -300,7 +301,30 @@ void mp_image_unrefp(struct mp_image **p_img)
|
||||
*p_img = NULL;
|
||||
}
|
||||
|
||||
void mp_image_copy(struct mp_image *dst, struct mp_image *src)
|
||||
typedef void *(*memcpy_fn)(void *d, const void *s, size_t size);
|
||||
|
||||
static void memcpy_pic_cb(void *dst, const void *src, int bytesPerLine, int height,
|
||||
int dstStride, int srcStride, memcpy_fn cpy)
|
||||
{
|
||||
if (bytesPerLine == dstStride && dstStride == srcStride && height) {
|
||||
if (srcStride < 0) {
|
||||
src = (uint8_t*)src + (height - 1) * srcStride;
|
||||
dst = (uint8_t*)dst + (height - 1) * dstStride;
|
||||
srcStride = -srcStride;
|
||||
}
|
||||
|
||||
cpy(dst, src, srcStride * (height - 1) + bytesPerLine);
|
||||
} else {
|
||||
for (int i = 0; i < height; i++) {
|
||||
cpy(dst, src, bytesPerLine);
|
||||
src = (uint8_t*)src + srcStride;
|
||||
dst = (uint8_t*)dst + dstStride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mp_image_copy_cb(struct mp_image *dst, struct mp_image *src,
|
||||
memcpy_fn cpy)
|
||||
{
|
||||
assert(dst->imgfmt == src->imgfmt);
|
||||
assert(dst->w == src->w && dst->h == src->h);
|
||||
@@ -308,14 +332,50 @@ void mp_image_copy(struct mp_image *dst, struct mp_image *src)
|
||||
for (int n = 0; n < dst->num_planes; n++) {
|
||||
int line_bytes = (mp_image_plane_w(dst, n) * dst->fmt.bpp[n] + 7) / 8;
|
||||
int plane_h = mp_image_plane_h(dst, n);
|
||||
memcpy_pic(dst->planes[n], src->planes[n], line_bytes, plane_h,
|
||||
dst->stride[n], src->stride[n]);
|
||||
memcpy_pic_cb(dst->planes[n], src->planes[n], line_bytes, plane_h,
|
||||
dst->stride[n], src->stride[n], cpy);
|
||||
}
|
||||
// Watch out for AV_PIX_FMT_FLAG_PSEUDOPAL retardation
|
||||
if ((dst->fmt.flags & MP_IMGFLAG_PAL) && dst->planes[1] && src->planes[1])
|
||||
memcpy(dst->planes[1], src->planes[1], MP_PALETTE_SIZE);
|
||||
}
|
||||
|
||||
void mp_image_copy(struct mp_image *dst, struct mp_image *src)
|
||||
{
|
||||
mp_image_copy_cb(dst, src, memcpy);
|
||||
}
|
||||
|
||||
void mp_image_copy_gpu(struct mp_image *dst, struct mp_image *src)
|
||||
{
|
||||
#if HAVE_SSE4_INTRINSICS
|
||||
if (av_get_cpu_flags() & AV_CPU_FLAG_SSE4) {
|
||||
mp_image_copy_cb(dst, src, gpu_memcpy);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
mp_image_copy(dst, src);
|
||||
}
|
||||
|
||||
// Helper, only for outputting some log info.
|
||||
void mp_check_gpu_memcpy(struct mp_log *log, bool *once)
|
||||
{
|
||||
if (once) {
|
||||
if (*once)
|
||||
return;
|
||||
*once = true;
|
||||
}
|
||||
|
||||
bool have_sse = false;
|
||||
#if HAVE_SSE4_INTRINSICS
|
||||
have_sse = av_get_cpu_flags() & AV_CPU_FLAG_SSE4;
|
||||
#endif
|
||||
if (have_sse) {
|
||||
mp_verbose(log, "Using SSE4 memcpy\n");
|
||||
} else {
|
||||
mp_warn(log, "Using fallback memcpy (slow)\n");
|
||||
}
|
||||
}
|
||||
|
||||
void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src)
|
||||
{
|
||||
dst->pict_type = src->pict_type;
|
||||
@@ -334,7 +394,6 @@ void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src)
|
||||
dst->params.colorspace = src->params.colorspace;
|
||||
dst->params.colorlevels = src->params.colorlevels;
|
||||
dst->params.chroma_location = src->params.chroma_location;
|
||||
dst->params.outputlevels = src->params.outputlevels;
|
||||
}
|
||||
mp_image_params_guess_csp(&dst->params); // ensure colorspace consistency
|
||||
if ((dst->fmt.flags & MP_IMGFLAG_PAL) && (src->fmt.flags & MP_IMGFLAG_PAL)) {
|
||||
@@ -431,10 +490,6 @@ char *mp_image_params_to_str_buf(char *b, size_t bs,
|
||||
m_opt_choice_str(mp_csp_levels_names, p->colorlevels));
|
||||
mp_snprintf_cat(b, bs, " CL=%s",
|
||||
m_opt_choice_str(mp_chroma_names, p->chroma_location));
|
||||
if (p->outputlevels) {
|
||||
mp_snprintf_cat(b, bs, " out=%s",
|
||||
m_opt_choice_str(mp_csp_levels_names, p->outputlevels));
|
||||
}
|
||||
if (p->rotate)
|
||||
mp_snprintf_cat(b, bs, " rot=%d", p->rotate);
|
||||
if (p->stereo_in > 0 || p->stereo_out > 0) {
|
||||
@@ -481,7 +536,6 @@ bool mp_image_params_equal(const struct mp_image_params *p1,
|
||||
p1->d_w == p2->d_w && p1->d_h == p2->d_h &&
|
||||
p1->colorspace == p2->colorspace &&
|
||||
p1->colorlevels == p2->colorlevels &&
|
||||
p1->outputlevels == p2->outputlevels &&
|
||||
p1->primaries == p2->primaries &&
|
||||
p1->gamma == p2->gamma &&
|
||||
p1->chroma_location == p2->chroma_location &&
|
||||
@@ -675,21 +729,7 @@ struct AVFrame *mp_image_to_av_frame_and_unref(struct mp_image *img)
|
||||
void memcpy_pic(void *dst, const void *src, int bytesPerLine, int height,
|
||||
int dstStride, int srcStride)
|
||||
{
|
||||
if (bytesPerLine == dstStride && dstStride == srcStride && height) {
|
||||
if (srcStride < 0) {
|
||||
src = (uint8_t*)src + (height - 1) * srcStride;
|
||||
dst = (uint8_t*)dst + (height - 1) * dstStride;
|
||||
srcStride = -srcStride;
|
||||
}
|
||||
|
||||
memcpy(dst, src, srcStride * (height - 1) + bytesPerLine);
|
||||
} else {
|
||||
for (int i = 0; i < height; i++) {
|
||||
memcpy(dst, src, bytesPerLine);
|
||||
src = (uint8_t*)src + srcStride;
|
||||
dst = (uint8_t*)dst + dstStride;
|
||||
}
|
||||
}
|
||||
memcpy_pic_cb(dst, src, bytesPerLine, height, dstStride, srcStride, memcpy);
|
||||
}
|
||||
|
||||
void memset_pic(void *dst, int fill, int bytesPerLine, int height, int stride)
|
||||
|
||||
+3
-4
@@ -46,10 +46,6 @@ struct mp_image_params {
|
||||
enum mp_csp_prim primaries;
|
||||
enum mp_csp_trc gamma;
|
||||
enum mp_chroma_location chroma_location;
|
||||
// The image should be converted to these levels. Unlike colorlevels, it
|
||||
// does not describe the current state of the image. (Somewhat similar to
|
||||
// d_w/d_h vs. w/h.)
|
||||
enum mp_csp_levels outputlevels;
|
||||
// The image should be rotated clockwise (0-359 degrees).
|
||||
int rotate;
|
||||
enum mp_stereo3d_mode stereo_in; // image is encoded with this mode
|
||||
@@ -106,6 +102,7 @@ int mp_chroma_div_up(int size, int shift);
|
||||
|
||||
struct mp_image *mp_image_alloc(int fmt, int w, int h);
|
||||
void mp_image_copy(struct mp_image *dmpi, struct mp_image *mpi);
|
||||
void mp_image_copy_gpu(struct mp_image *dst, struct mp_image *src);
|
||||
void mp_image_copy_attributes(struct mp_image *dmpi, struct mp_image *mpi);
|
||||
struct mp_image *mp_image_new_copy(struct mp_image *img);
|
||||
struct mp_image *mp_image_new_ref(struct mp_image *img);
|
||||
@@ -159,4 +156,6 @@ void memcpy_pic(void *dst, const void *src, int bytesPerLine, int height,
|
||||
void memset_pic(void *dst, int fill, int bytesPerLine, int height, int stride);
|
||||
void memset16_pic(void *dst, int fill, int unitsPerLine, int height, int stride);
|
||||
|
||||
void mp_check_gpu_memcpy(struct mp_log *log, bool *once);
|
||||
|
||||
#endif /* MPLAYER_MP_IMAGE_H */
|
||||
@@ -27,10 +27,10 @@
|
||||
struct vo;
|
||||
struct vo_cocoa_state;
|
||||
|
||||
int vo_cocoa_init(struct vo *vo);
|
||||
void vo_cocoa_init(struct vo *vo);
|
||||
void vo_cocoa_uninit(struct vo *vo);
|
||||
|
||||
int vo_cocoa_config_window(struct vo *vo, uint32_t flags);
|
||||
int vo_cocoa_config_window(struct vo *vo);
|
||||
|
||||
int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg);
|
||||
|
||||
|
||||
@@ -248,7 +248,7 @@ static void cocoa_uninit_light_sensor(struct vo_cocoa_state *s)
|
||||
}
|
||||
}
|
||||
|
||||
int vo_cocoa_init(struct vo *vo)
|
||||
void vo_cocoa_init(struct vo *vo)
|
||||
{
|
||||
struct vo_cocoa_state *s = talloc_zero(NULL, struct vo_cocoa_state);
|
||||
*s = (struct vo_cocoa_state){
|
||||
@@ -265,7 +265,10 @@ int vo_cocoa_init(struct vo *vo)
|
||||
pthread_cond_init(&s->wakeup, NULL);
|
||||
vo->cocoa = s;
|
||||
cocoa_init_light_sensor(vo);
|
||||
return 1;
|
||||
if (!s->embedded) {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
set_application_icon(NSApp);
|
||||
}
|
||||
}
|
||||
|
||||
static int vo_cocoa_set_cursor_visibility(struct vo *vo, bool *visible)
|
||||
@@ -554,7 +557,7 @@ void vo_cocoa_set_opengl_ctx(struct vo *vo, CGLContextObj ctx)
|
||||
});
|
||||
}
|
||||
|
||||
int vo_cocoa_config_window(struct vo *vo, uint32_t flags)
|
||||
int vo_cocoa_config_window(struct vo *vo)
|
||||
{
|
||||
struct vo_cocoa_state *s = vo->cocoa;
|
||||
run_on_main_thread(vo, ^{
|
||||
@@ -572,7 +575,7 @@ int vo_cocoa_config_window(struct vo *vo, uint32_t flags)
|
||||
s->old_dwidth = width;
|
||||
s->old_dheight = height;
|
||||
|
||||
if (!(flags & VOFLAG_HIDDEN) && !s->view) {
|
||||
if (!s->view) {
|
||||
create_ui(vo, &geo.win, geo.flags);
|
||||
}
|
||||
|
||||
@@ -593,11 +596,6 @@ int vo_cocoa_config_window(struct vo *vo, uint32_t flags)
|
||||
vo->dheight = s->vo_dheight = frame.size.height;
|
||||
|
||||
[s->nsgl_ctx update];
|
||||
|
||||
if (!s->embedded) {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
set_application_icon(NSApp);
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
Loaded 100 of 151 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user