mirror of https://github.com/mpv-player/mpv
Compare commits
28 Commits
dbccbc8086
...
916fb4007c
Author | SHA1 | Date |
---|---|---|
Rui He | 916fb4007c | |
Kacper Michajłow | 69d70148c7 | |
oficsu | 3ca71b0c0e | |
oficsu | 96b34148f1 | |
nanahi | 93708a9d38 | |
Guido Cella | 2f4c550b4b | |
Kacper Michajłow | fbfc9d22c7 | |
Kacper Michajłow | 47dbc3a74e | |
Kacper Michajłow | 0b234af113 | |
rcombs | 99f1b2b7b4 | |
rcombs | 437fff9f21 | |
rcombs | aa0a9ce2ec | |
Kacper Michajłow | e3fd24496a | |
Kacper Michajłow | f55d19e846 | |
Kacper Michajłow | d8378dc226 | |
Shreesh Adiga | 4aa7588e44 | |
Shreesh Adiga | d9c5aef98d | |
ferreum | 773c5e2ae0 | |
ferreum | 190b15c827 | |
nanahi | 51e01e9772 | |
nanahi | 7f0961479a | |
Misaki Kasumi | 0f50b98457 | |
Misaki Kasumi | 82e15635a2 | |
Misaki Kasumi | b97a577fb8 | |
Misaki Kasumi | c694b4f9e8 | |
Misaki Kasumi | 338d18d695 | |
Misaki Kasumi | 0b8bfbe60d | |
Misaki Kasumi | a6a46fa89d |
|
@ -205,6 +205,30 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
cat ./build/meson-logs/testlog.txt
|
cat ./build/meson-logs/testlog.txt
|
||||||
|
|
||||||
|
linux-fuzz:
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
container:
|
||||||
|
image: "registry.opensuse.org/home/mia/images/images/mpv-ci:stable-deps"
|
||||||
|
env:
|
||||||
|
CC: "clang"
|
||||||
|
CXX: "clang++"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build with meson
|
||||||
|
id: build
|
||||||
|
run: |
|
||||||
|
meson setup build \
|
||||||
|
--werror \
|
||||||
|
-Dc_args="-Wno-error=deprecated -Wno-error=deprecated-declarations" \
|
||||||
|
-Dfuzzers=true -Dlibmpv=true -Dcplayer=false
|
||||||
|
meson compile -C build
|
||||||
|
|
||||||
|
- name: Print meson log
|
||||||
|
if: ${{ failure() && steps.build.outcome == 'failure' }}
|
||||||
|
run: |
|
||||||
|
cat ./build/meson-logs/meson-log.txt
|
||||||
|
|
||||||
linux-ffmpeg-4-4:
|
linux-ffmpeg-4-4:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
container:
|
container:
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
add `sub-text/ass-full` sub-property
|
|
@ -0,0 +1 @@
|
||||||
|
deprecate `sub-text-ass` property; add `sub-text/ass` sub-property
|
|
@ -2823,19 +2823,27 @@ Property list
|
||||||
stripped. If the subtitle is not text-based (i.e. DVD/BD subtitles), an
|
stripped. If the subtitle is not text-based (i.e. DVD/BD subtitles), an
|
||||||
empty string is returned.
|
empty string is returned.
|
||||||
|
|
||||||
``sub-text-ass``
|
This has sub-properties for different formats:
|
||||||
Like ``sub-text``, but return the text in ASS format. Text subtitles in
|
|
||||||
other formats are converted. For native ASS subtitles, events that do
|
|
||||||
not contain any text (but vector drawings etc.) are not filtered out. If
|
|
||||||
multiple events match with the current playback time, they are concatenated
|
|
||||||
with line breaks. Contains only the "Text" part of the events.
|
|
||||||
|
|
||||||
This property is not enough to render ASS subtitles correctly, because ASS
|
``sub-text/ass``
|
||||||
header and per-event metadata are not returned. You likely need to do
|
Like ``sub-text``, but return the text in ASS format. Text subtitles in
|
||||||
further filtering on the returned string to make it useful.
|
other formats are converted. For native ASS subtitles, events that do
|
||||||
|
not contain any text (but vector drawings etc.) are not filtered out. If
|
||||||
|
multiple events match with the current playback time, they are concatenated
|
||||||
|
with line breaks. Contains only the "Text" part of the events.
|
||||||
|
|
||||||
|
This property is not enough to render ASS subtitles correctly, because ASS
|
||||||
|
header and per-event metadata are not returned. Use ``/ass-full`` for that.
|
||||||
|
|
||||||
|
``sub-text/ass-full``
|
||||||
|
Like ``sub-text-ass``, but return the full event with all fields, formatted as
|
||||||
|
lines in a .ass text file. Use with ``sub-ass-extradata`` for style information.
|
||||||
|
|
||||||
|
``sub-text-ass`` (deprecated)
|
||||||
|
Deprecated alias for ``sub-text/ass``.
|
||||||
|
|
||||||
``secondary-sub-text``
|
``secondary-sub-text``
|
||||||
Same as ``sub-text``, but for the secondary subtitles.
|
Same as ``sub-text`` (with the same sub-properties), but for the secondary subtitles.
|
||||||
|
|
||||||
``sub-start``
|
``sub-start``
|
||||||
The current subtitle start time (in seconds). If there's multiple current
|
The current subtitle start time (in seconds). If there's multiple current
|
||||||
|
|
|
@ -765,12 +765,22 @@ Available mpv-only filters are:
|
||||||
read information from this filter instead.
|
read information from this filter instead.
|
||||||
|
|
||||||
``gpu=...``
|
``gpu=...``
|
||||||
Convert video to RGB using the OpenGL renderer normally used with
|
Convert video to RGB using the Vulkan or OpenGL renderer normally used with
|
||||||
``--vo=gpu``. This requires that the EGL implementation supports off-screen
|
``--vo=gpu``. In case of OpenGL, this requires that the EGL implementation
|
||||||
rendering on the default display. (This is the case with Mesa.)
|
supports off-screen rendering on the default display. (This is the case with
|
||||||
|
Mesa.)
|
||||||
|
|
||||||
Sub-options:
|
Sub-options:
|
||||||
|
|
||||||
|
``api=<type>``
|
||||||
|
The value ``type`` selects the rendering API. You can also pass
|
||||||
|
``help`` to get a complete list of compiled in backends.
|
||||||
|
|
||||||
|
egl
|
||||||
|
EGL (default if available)
|
||||||
|
vulkan
|
||||||
|
Vulkan
|
||||||
|
|
||||||
``w=<pixels>``, ``h=<pixels>``
|
``w=<pixels>``, ``h=<pixels>``
|
||||||
Size of the output in pixels (default: 0). If not positive, this will
|
Size of the output in pixels (default: 0). If not positive, this will
|
||||||
use the size of the first filtered input frame.
|
use the size of the first filtered input frame.
|
||||||
|
|
|
@ -10,6 +10,12 @@
|
||||||
To configure this script use file autoload.conf in directory script-opts (the "script-opts"
|
To configure this script use file autoload.conf in directory script-opts (the "script-opts"
|
||||||
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
|
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
|
||||||
|
|
||||||
|
Option `ignore_patterns` is a comma-separated list of patterns (see lua.org/pil/20.2.html).
|
||||||
|
Additionally to the standard lua patterns, you can also escape commas with `%`,
|
||||||
|
for example, the option `bak%,x%,,another` will be resolved as patterns `bak,x,` and `another`.
|
||||||
|
But it does not mean you need to escape all lua patterns twice,
|
||||||
|
so the option `bak%%,%.mp4,` will be resolved as two patterns `bak%%` and `%.mp4`.
|
||||||
|
|
||||||
Example configuration would be:
|
Example configuration would be:
|
||||||
|
|
||||||
disabled=no
|
disabled=no
|
||||||
|
@ -22,6 +28,7 @@ additional_audio_exts=list,of,ext
|
||||||
ignore_hidden=yes
|
ignore_hidden=yes
|
||||||
same_type=yes
|
same_type=yes
|
||||||
directory_mode=recursive
|
directory_mode=recursive
|
||||||
|
ignore_patterns=^~,^bak-,%.bak$
|
||||||
|
|
||||||
--]]
|
--]]
|
||||||
|
|
||||||
|
@ -42,7 +49,8 @@ o = {
|
||||||
additional_audio_exts = "",
|
additional_audio_exts = "",
|
||||||
ignore_hidden = true,
|
ignore_hidden = true,
|
||||||
same_type = false,
|
same_type = false,
|
||||||
directory_mode = "auto"
|
directory_mode = "auto",
|
||||||
|
ignore_patterns = ""
|
||||||
}
|
}
|
||||||
options.read_options(o, nil, function(list)
|
options.read_options(o, nil, function(list)
|
||||||
split_option_exts(list.additional_video_exts, list.additional_audio_exts, list.additional_image_exts)
|
split_option_exts(list.additional_video_exts, list.additional_audio_exts, list.additional_image_exts)
|
||||||
|
@ -67,9 +75,45 @@ function SetUnion (a,b)
|
||||||
return a
|
return a
|
||||||
end
|
end
|
||||||
|
|
||||||
function Split (s)
|
-- Returns first and last positions in string or past-to-end indices
|
||||||
|
function FindOrPastTheEnd (string, pattern, start_at)
|
||||||
|
local pos1, pos2 = string.find(string, pattern, start_at)
|
||||||
|
return pos1 or #string + 1,
|
||||||
|
pos2 or #string + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function Split (list)
|
||||||
local set = {}
|
local set = {}
|
||||||
for v in string.gmatch(s, '([^,]+)') do set[v] = true end
|
|
||||||
|
local item_pos = 1
|
||||||
|
local item = ""
|
||||||
|
|
||||||
|
while item_pos <= #list do
|
||||||
|
local pos1, pos2 = FindOrPastTheEnd(list, "%%*,", item_pos)
|
||||||
|
|
||||||
|
local pattern_length = pos2 - pos1
|
||||||
|
local is_comma_escaped = pattern_length % 2
|
||||||
|
|
||||||
|
local pos_before_escape = pos1 - 1
|
||||||
|
local item_escape_count = pattern_length - is_comma_escaped
|
||||||
|
|
||||||
|
item = item .. string.sub(list, item_pos, pos_before_escape + item_escape_count)
|
||||||
|
|
||||||
|
if is_comma_escaped == 1 then
|
||||||
|
item = item .. ","
|
||||||
|
else
|
||||||
|
set[item] = true
|
||||||
|
item = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
item_pos = pos2 + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
set[item] = true
|
||||||
|
|
||||||
|
-- exclude empty items
|
||||||
|
set[""] = nil
|
||||||
|
|
||||||
return set
|
return set
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -95,6 +139,11 @@ function split_option_exts(video, audio, image)
|
||||||
end
|
end
|
||||||
split_option_exts(true, true, true)
|
split_option_exts(true, true, true)
|
||||||
|
|
||||||
|
function split_patterns()
|
||||||
|
o.ignore_patterns = Split(o.ignore_patterns)
|
||||||
|
end
|
||||||
|
split_patterns()
|
||||||
|
|
||||||
function create_extensions()
|
function create_extensions()
|
||||||
EXTENSIONS = {}
|
EXTENSIONS = {}
|
||||||
EXTENSIONS_VIDEO = {}
|
EXTENSIONS_VIDEO = {}
|
||||||
|
@ -139,6 +188,16 @@ function get_extension(path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function is_ignored(file)
|
||||||
|
for pattern, _ in pairs(o.ignore_patterns) do
|
||||||
|
if string.match(file, pattern) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
table.filter = function(t, iter)
|
table.filter = function(t, iter)
|
||||||
for i = #t, 1, -1 do
|
for i = #t, 1, -1 do
|
||||||
if not iter(t[i]) then
|
if not iter(t[i]) then
|
||||||
|
@ -189,9 +248,14 @@ function scan_dir(path, current_file, dir_mode, separator, dir_depth, total_file
|
||||||
table.filter(files, function (v)
|
table.filter(files, function (v)
|
||||||
-- The current file could be a hidden file, ignoring it doesn't load other
|
-- The current file could be a hidden file, ignoring it doesn't load other
|
||||||
-- files from the current directory.
|
-- files from the current directory.
|
||||||
if (o.ignore_hidden and not (prefix .. v == current_file) and string.match(v, "^%.")) then
|
local current = prefix .. v == current_file
|
||||||
|
if o.ignore_hidden and not current and string.match(v, "^%.") then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
if not current and is_ignored(v) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
local ext = get_extension(v)
|
local ext = get_extension(v)
|
||||||
if ext == nil then
|
if ext == nil then
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#include "options/m_option.h"
|
#include "options/m_option.h"
|
||||||
|
|
||||||
struct priv {
|
struct priv {
|
||||||
struct mp_scaletempo2 data;
|
struct mp_scaletempo2 *data;
|
||||||
struct mp_pin *in_pin;
|
struct mp_pin *in_pin;
|
||||||
struct mp_aframe *cur_format;
|
struct mp_aframe *cur_format;
|
||||||
struct mp_aframe_pool *out_pool;
|
struct mp_aframe_pool *out_pool;
|
||||||
|
@ -29,7 +29,7 @@ static void af_scaletempo2_process(struct mp_filter *f)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
while (!p->initialized || !p->pending ||
|
while (!p->initialized || !p->pending ||
|
||||||
!mp_scaletempo2_frames_available(&p->data, p->speed))
|
!mp_scaletempo2_frames_available(p->data, p->speed))
|
||||||
{
|
{
|
||||||
bool eof = false;
|
bool eof = false;
|
||||||
if (!p->pending || !mp_aframe_get_size(p->pending)) {
|
if (!p->pending || !mp_aframe_get_size(p->pending)) {
|
||||||
|
@ -64,16 +64,16 @@ static void af_scaletempo2_process(struct mp_filter *f)
|
||||||
if (p->pending && !format_change && !p->sent_final) {
|
if (p->pending && !format_change && !p->sent_final) {
|
||||||
int frame_size = mp_aframe_get_size(p->pending);
|
int frame_size = mp_aframe_get_size(p->pending);
|
||||||
uint8_t **planes = mp_aframe_get_data_ro(p->pending);
|
uint8_t **planes = mp_aframe_get_data_ro(p->pending);
|
||||||
int read = mp_scaletempo2_fill_input_buffer(&p->data,
|
int read = mp_scaletempo2_fill_input_buffer(p->data,
|
||||||
planes, frame_size, p->speed);
|
planes, frame_size, p->speed);
|
||||||
mp_aframe_skip_samples(p->pending, read);
|
mp_aframe_skip_samples(p->pending, read);
|
||||||
}
|
}
|
||||||
if (final && p->pending && !p->sent_final) {
|
if (final && p->pending && !p->sent_final) {
|
||||||
mp_scaletempo2_set_final(&p->data);
|
mp_scaletempo2_set_final(p->data);
|
||||||
p->sent_final = true;
|
p->sent_final = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mp_scaletempo2_frames_available(&p->data, p->speed)) {
|
if (mp_scaletempo2_frames_available(p->data, p->speed)) {
|
||||||
if (eof) {
|
if (eof) {
|
||||||
mp_pin_out_repeat_eof(p->in_pin); // drain more next time
|
mp_pin_out_repeat_eof(p->in_pin); // drain more next time
|
||||||
}
|
}
|
||||||
|
@ -89,9 +89,9 @@ static void af_scaletempo2_process(struct mp_filter *f)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(p->pending);
|
assert(p->pending);
|
||||||
if (mp_scaletempo2_frames_available(&p->data, p->speed)) {
|
if (mp_scaletempo2_frames_available(p->data, p->speed)) {
|
||||||
struct mp_aframe *out = mp_aframe_new_ref(p->cur_format);
|
struct mp_aframe *out = mp_aframe_new_ref(p->cur_format);
|
||||||
int out_samples = p->data.ola_hop_size;
|
int out_samples = p->data->ola_hop_size;
|
||||||
if (mp_aframe_pool_allocate(p->out_pool, out, out_samples) < 0) {
|
if (mp_aframe_pool_allocate(p->out_pool, out, out_samples) < 0) {
|
||||||
talloc_free(out);
|
talloc_free(out);
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -101,14 +101,14 @@ static void af_scaletempo2_process(struct mp_filter *f)
|
||||||
|
|
||||||
uint8_t **planes = mp_aframe_get_data_rw(out);
|
uint8_t **planes = mp_aframe_get_data_rw(out);
|
||||||
assert(planes);
|
assert(planes);
|
||||||
assert(mp_aframe_get_planes(out) == p->data.channels);
|
assert(mp_aframe_get_planes(out) == p->data->channels);
|
||||||
|
|
||||||
out_samples = mp_scaletempo2_fill_buffer(&p->data,
|
out_samples = mp_scaletempo2_fill_buffer(p->data,
|
||||||
(float**)planes, out_samples, p->speed);
|
(float**)planes, out_samples, p->speed);
|
||||||
|
|
||||||
double pts = mp_aframe_get_pts(p->pending);
|
double pts = mp_aframe_get_pts(p->pending);
|
||||||
if (pts != MP_NOPTS_VALUE) {
|
if (pts != MP_NOPTS_VALUE) {
|
||||||
double frame_delay = mp_scaletempo2_get_latency(&p->data, p->speed)
|
double frame_delay = mp_scaletempo2_get_latency(p->data, p->speed)
|
||||||
+ out_samples * p->speed;
|
+ out_samples * p->speed;
|
||||||
mp_aframe_set_pts(out, pts - frame_delay / mp_aframe_get_effective_rate(out));
|
mp_aframe_set_pts(out, pts - frame_delay / mp_aframe_get_effective_rate(out));
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ static void af_scaletempo2_process(struct mp_filter *f)
|
||||||
|
|
||||||
// reset the filter to ensure it stops generating audio
|
// reset the filter to ensure it stops generating audio
|
||||||
// and mp_scaletempo2_frames_available returns false
|
// and mp_scaletempo2_frames_available returns false
|
||||||
mp_scaletempo2_reset(&p->data);
|
mp_scaletempo2_reset(p->data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ static bool init_scaletempo2(struct mp_filter *f)
|
||||||
p->sent_final = false;
|
p->sent_final = false;
|
||||||
mp_aframe_config_copy(p->cur_format, p->pending);
|
mp_aframe_config_copy(p->cur_format, p->pending);
|
||||||
|
|
||||||
mp_scaletempo2_init(&p->data, mp_aframe_get_channels(p->pending),
|
mp_scaletempo2_init(p->data, mp_aframe_get_channels(p->pending),
|
||||||
mp_aframe_get_rate(p->pending));
|
mp_aframe_get_rate(p->pending));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -172,7 +172,7 @@ static bool af_scaletempo2_command(struct mp_filter *f, struct mp_filter_command
|
||||||
static void af_scaletempo2_reset(struct mp_filter *f)
|
static void af_scaletempo2_reset(struct mp_filter *f)
|
||||||
{
|
{
|
||||||
struct priv *p = f->priv;
|
struct priv *p = f->priv;
|
||||||
mp_scaletempo2_reset(&p->data);
|
mp_scaletempo2_reset(p->data);
|
||||||
p->initialized = false;
|
p->initialized = false;
|
||||||
TA_FREEP(&p->pending);
|
TA_FREEP(&p->pending);
|
||||||
}
|
}
|
||||||
|
@ -180,8 +180,8 @@ static void af_scaletempo2_reset(struct mp_filter *f)
|
||||||
static void af_scaletempo2_destroy(struct mp_filter *f)
|
static void af_scaletempo2_destroy(struct mp_filter *f)
|
||||||
{
|
{
|
||||||
struct priv *p = f->priv;
|
struct priv *p = f->priv;
|
||||||
mp_scaletempo2_destroy(&p->data);
|
TA_FREEP(&p->data);
|
||||||
talloc_free(p->pending);
|
TA_FREEP(&p->pending);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct mp_filter_info af_scaletempo2_filter = {
|
static const struct mp_filter_info af_scaletempo2_filter = {
|
||||||
|
@ -206,7 +206,8 @@ static struct mp_filter *af_scaletempo2_create(
|
||||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||||
|
|
||||||
struct priv *p = f->priv;
|
struct priv *p = f->priv;
|
||||||
p->data.opts = talloc_steal(p, options);
|
p->data = talloc_zero(p, struct mp_scaletempo2);
|
||||||
|
p->data->opts = talloc_steal(p, options);
|
||||||
p->speed = 1.0;
|
p->speed = 1.0;
|
||||||
p->cur_format = talloc_steal(p, mp_aframe_create());
|
p->cur_format = talloc_steal(p, mp_aframe_create());
|
||||||
p->out_pool = mp_aframe_pool_create(p);
|
p->out_pool = mp_aframe_pool_create(p);
|
||||||
|
|
|
@ -41,19 +41,15 @@ static bool in_interval(int n, struct interval q)
|
||||||
return n >= q.lo && n <= q.hi;
|
return n >= q.lo && n <= q.hi;
|
||||||
}
|
}
|
||||||
|
|
||||||
static float **realloc_2d(float **p, int x, int y)
|
static void alloc_sample_buffer(struct mp_scaletempo2 *p, float ***ptr, size_t size)
|
||||||
{
|
{
|
||||||
float **array = realloc(p, sizeof(float*) * x + sizeof(float) * x * y);
|
talloc_free(*ptr);
|
||||||
float* data = (float*) (array + x);
|
|
||||||
for (int i = 0; i < x; ++i) {
|
|
||||||
array[i] = data + i * y;
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void zero_2d(float **a, int x, int y)
|
float **buff = talloc_array(p, float*, p->channels);
|
||||||
{
|
for (int i = 0; i < p->channels; ++i) {
|
||||||
memset(a + x, 0, sizeof(float) * x * y);
|
buff[i] = talloc_array(buff, float, size);
|
||||||
|
}
|
||||||
|
*ptr = buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void zero_2d_partial(float **a, int x, int y)
|
static void zero_2d_partial(float **a, int x, int y)
|
||||||
|
@ -480,12 +476,6 @@ static bool can_perform_wsola(struct mp_scaletempo2 *p, double playback_rate)
|
||||||
return frames_needed(p, playback_rate) <= 0;
|
return frames_needed(p, playback_rate) <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void resize_input_buffer(struct mp_scaletempo2 *p, int size)
|
|
||||||
{
|
|
||||||
p->input_buffer_size = size;
|
|
||||||
p->input_buffer = realloc_2d(p->input_buffer, p->channels, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// pad end with silence until a wsola iteration can be performed
|
// pad end with silence until a wsola iteration can be performed
|
||||||
static void add_input_buffer_final_silence(struct mp_scaletempo2 *p, double playback_rate)
|
static void add_input_buffer_final_silence(struct mp_scaletempo2 *p, double playback_rate)
|
||||||
{
|
{
|
||||||
|
@ -493,11 +483,9 @@ static void add_input_buffer_final_silence(struct mp_scaletempo2 *p, double play
|
||||||
if (needed <= 0)
|
if (needed <= 0)
|
||||||
return; // no silence needed for iteration
|
return; // no silence needed for iteration
|
||||||
|
|
||||||
int required_size = needed + p->input_buffer_frames;
|
int last_index = needed + p->input_buffer_frames - 1;
|
||||||
if (required_size > p->input_buffer_size)
|
|
||||||
resize_input_buffer(p, required_size);
|
|
||||||
|
|
||||||
for (int i = 0; i < p->channels; ++i) {
|
for (int i = 0; i < p->channels; ++i) {
|
||||||
|
MP_TARRAY_GROW(p, p->input_buffer[i], last_index);
|
||||||
float *ch_input = p->input_buffer[i];
|
float *ch_input = p->input_buffer[i];
|
||||||
for (int j = 0; j < needed; ++j) {
|
for (int j = 0; j < needed; ++j) {
|
||||||
ch_input[p->input_buffer_frames + j] = 0.0f;
|
ch_input[p->input_buffer_frames + j] = 0.0f;
|
||||||
|
@ -523,11 +511,9 @@ int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p,
|
||||||
if (read == 0)
|
if (read == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
int required_size = read + p->input_buffer_frames;
|
int last_index = read + p->input_buffer_frames - 1;
|
||||||
if (required_size > p->input_buffer_size)
|
|
||||||
resize_input_buffer(p, required_size);
|
|
||||||
|
|
||||||
for (int i = 0; i < p->channels; ++i) {
|
for (int i = 0; i < p->channels; ++i) {
|
||||||
|
MP_TARRAY_GROW(p, p->input_buffer[i], last_index);
|
||||||
memcpy(p->input_buffer[i] + p->input_buffer_frames,
|
memcpy(p->input_buffer[i] + p->input_buffer_frames,
|
||||||
planes[i], read * sizeof(float));
|
planes[i], read * sizeof(float));
|
||||||
}
|
}
|
||||||
|
@ -771,18 +757,6 @@ bool mp_scaletempo2_frames_available(struct mp_scaletempo2 *p, double playback_r
|
||||||
|| p->num_complete_frames > 0;
|
|| p->num_complete_frames > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mp_scaletempo2_destroy(struct mp_scaletempo2 *p)
|
|
||||||
{
|
|
||||||
free(p->ola_window);
|
|
||||||
free(p->transition_window);
|
|
||||||
free(p->wsola_output);
|
|
||||||
free(p->optimal_block);
|
|
||||||
free(p->search_block);
|
|
||||||
free(p->target_block);
|
|
||||||
free(p->input_buffer);
|
|
||||||
free(p->energy_candidate_blocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
void mp_scaletempo2_reset(struct mp_scaletempo2 *p)
|
void mp_scaletempo2_reset(struct mp_scaletempo2 *p)
|
||||||
{
|
{
|
||||||
p->input_buffer_frames = 0;
|
p->input_buffer_frames = 0;
|
||||||
|
@ -791,8 +765,6 @@ void mp_scaletempo2_reset(struct mp_scaletempo2 *p)
|
||||||
p->output_time = 0.0;
|
p->output_time = 0.0;
|
||||||
p->search_block_index = 0;
|
p->search_block_index = 0;
|
||||||
p->target_block_index = 0;
|
p->target_block_index = 0;
|
||||||
// Clear the queue of decoded packets.
|
|
||||||
zero_2d(p->wsola_output, p->channels, p->wsola_output_size);
|
|
||||||
p->num_complete_frames = 0;
|
p->num_complete_frames = 0;
|
||||||
p->wsola_output_started = false;
|
p->wsola_output_started = false;
|
||||||
}
|
}
|
||||||
|
@ -847,28 +819,26 @@ void mp_scaletempo2_init(struct mp_scaletempo2 *p, int channels, int rate)
|
||||||
// 1, ... |num_candidate_blocks|
|
// 1, ... |num_candidate_blocks|
|
||||||
p->search_block_center_offset = p->num_candidate_blocks / 2
|
p->search_block_center_offset = p->num_candidate_blocks / 2
|
||||||
+ (p->ola_window_size / 2 - 1);
|
+ (p->ola_window_size / 2 - 1);
|
||||||
p->ola_window = realloc(p->ola_window, sizeof(float) * p->ola_window_size);
|
MP_RESIZE_ARRAY(p, p->ola_window, p->ola_window_size);
|
||||||
get_symmetric_hanning_window(p->ola_window_size, p->ola_window);
|
get_symmetric_hanning_window(p->ola_window_size, p->ola_window);
|
||||||
p->transition_window = realloc(p->transition_window,
|
MP_RESIZE_ARRAY(p, p->transition_window, p->ola_window_size * 2);
|
||||||
sizeof(float) * p->ola_window_size * 2);
|
|
||||||
get_symmetric_hanning_window(2 * p->ola_window_size, p->transition_window);
|
get_symmetric_hanning_window(2 * p->ola_window_size, p->transition_window);
|
||||||
|
|
||||||
p->wsola_output_size = p->ola_window_size + p->ola_hop_size;
|
p->wsola_output_size = p->ola_window_size + p->ola_hop_size;
|
||||||
p->wsola_output = realloc_2d(p->wsola_output, p->channels, p->wsola_output_size);
|
alloc_sample_buffer(p, &p->wsola_output, p->wsola_output_size);
|
||||||
// Initialize for overlap-and-add of the first block.
|
|
||||||
zero_2d(p->wsola_output, p->channels, p->wsola_output_size);
|
|
||||||
|
|
||||||
// Auxiliary containers.
|
// Auxiliary containers.
|
||||||
p->optimal_block = realloc_2d(p->optimal_block, p->channels, p->ola_window_size);
|
alloc_sample_buffer(p, &p->optimal_block, p->ola_window_size);
|
||||||
p->search_block_size = p->num_candidate_blocks + (p->ola_window_size - 1);
|
p->search_block_size = p->num_candidate_blocks + (p->ola_window_size - 1);
|
||||||
p->search_block = realloc_2d(p->search_block, p->channels, p->search_block_size);
|
alloc_sample_buffer(p, &p->search_block, p->search_block_size);
|
||||||
p->target_block = realloc_2d(p->target_block, p->channels, p->ola_window_size);
|
alloc_sample_buffer(p, &p->target_block, p->ola_window_size);
|
||||||
|
|
||||||
resize_input_buffer(p, 4 * MPMAX(p->ola_window_size, p->search_block_size));
|
|
||||||
p->input_buffer_frames = 0;
|
p->input_buffer_frames = 0;
|
||||||
p->input_buffer_final_frames = 0;
|
p->input_buffer_final_frames = 0;
|
||||||
p->input_buffer_added_silence = 0;
|
p->input_buffer_added_silence = 0;
|
||||||
|
size_t initial_size = 4 * MPMAX(p->ola_window_size, p->search_block_size);
|
||||||
|
alloc_sample_buffer(p, &p->input_buffer, initial_size);
|
||||||
|
|
||||||
p->energy_candidate_blocks = realloc(p->energy_candidate_blocks,
|
MP_RESIZE_ARRAY(p, p->energy_candidate_blocks,
|
||||||
sizeof(float) * p->channels * p->num_candidate_blocks);
|
p->channels * p->num_candidate_blocks);
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,6 @@ struct mp_scaletempo2 {
|
||||||
float **target_block;
|
float **target_block;
|
||||||
// Buffered audio data.
|
// Buffered audio data.
|
||||||
float **input_buffer;
|
float **input_buffer;
|
||||||
int input_buffer_size;
|
|
||||||
int input_buffer_frames;
|
int input_buffer_frames;
|
||||||
// How many frames in |input_buffer| need to be flushed by padding with
|
// How many frames in |input_buffer| need to be flushed by padding with
|
||||||
// silence to process the final packet. While this is nonzero, the filter
|
// silence to process the final packet. While this is nonzero, the filter
|
||||||
|
|
|
@ -552,6 +552,7 @@ static MP_THREAD_VOID ao_thread(void *arg)
|
||||||
struct ao *ao = arg;
|
struct ao *ao = arg;
|
||||||
struct priv *p = ao->priv;
|
struct priv *p = ao->priv;
|
||||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||||
|
bool after_eof = false;
|
||||||
mp_thread_set_name("ao/audiotrack");
|
mp_thread_set_name("ao/audiotrack");
|
||||||
mp_mutex_lock(&p->lock);
|
mp_mutex_lock(&p->lock);
|
||||||
while (!p->thread_terminate) {
|
while (!p->thread_terminate) {
|
||||||
|
@ -564,21 +565,33 @@ static MP_THREAD_VOID ao_thread(void *arg)
|
||||||
int64_t ts = mp_time_ns();
|
int64_t ts = mp_time_ns();
|
||||||
ts += MP_TIME_S_TO_NS(read_samples / (double)(ao->samplerate));
|
ts += MP_TIME_S_TO_NS(read_samples / (double)(ao->samplerate));
|
||||||
ts += MP_TIME_S_TO_NS(AudioTrack_getLatency(ao));
|
ts += MP_TIME_S_TO_NS(AudioTrack_getLatency(ao));
|
||||||
int samples = ao_read_data_nonblocking(ao, &p->chunk, read_samples, ts);
|
bool eof;
|
||||||
int ret = AudioTrack_write(ao, samples * ao->sstride);
|
int samples = ao_read_data(ao, &p->chunk, read_samples, ts, &eof, false, false);
|
||||||
if (ret >= 0) {
|
if (samples) {
|
||||||
p->written_frames += ret / ao->sstride;
|
after_eof = false;
|
||||||
} else if (ret == AudioManager.ERROR_DEAD_OBJECT) {
|
int ret = AudioTrack_write(ao, samples * ao->sstride);
|
||||||
MP_WARN(ao, "AudioTrack.write failed with ERROR_DEAD_OBJECT. Recreating AudioTrack...\n");
|
if (ret >= 0) {
|
||||||
if (AudioTrack_Recreate(ao) < 0) {
|
p->written_frames += ret / ao->sstride;
|
||||||
MP_ERR(ao, "AudioTrack_Recreate failed\n");
|
} else if (ret == AudioManager.ERROR_DEAD_OBJECT) {
|
||||||
|
MP_WARN(ao, "AudioTrack.write failed with ERROR_DEAD_OBJECT. Recreating AudioTrack...\n");
|
||||||
|
if (AudioTrack_Recreate(ao) < 0) {
|
||||||
|
MP_ERR(ao, "AudioTrack_Recreate failed\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MP_ERR(ao, "AudioTrack.write failed with %d\n", ret);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
MP_ERR(ao, "AudioTrack.write failed with %d\n", ret);
|
if (eof) {
|
||||||
|
after_eof = true;
|
||||||
|
ao_stop_streaming(ao);
|
||||||
|
}
|
||||||
|
if (!after_eof) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
mp_cond_timedwait(&p->wakeup, &p->lock, MP_TIME_MS_TO_NS(300));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mp_cond_timedwait(&p->wakeup, &p->lock, MP_TIME_MS_TO_NS(300));
|
||||||
}
|
}
|
||||||
mp_mutex_unlock(&p->lock);
|
mp_mutex_unlock(&p->lock);
|
||||||
MP_THREAD_RETURN();
|
MP_THREAD_RETURN();
|
||||||
|
@ -815,6 +828,26 @@ static void start(struct ao *ao)
|
||||||
mp_cond_signal(&p->wakeup);
|
mp_cond_signal(&p->wakeup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool set_pause(struct ao *ao, bool paused)
|
||||||
|
{
|
||||||
|
struct priv *p = ao->priv;
|
||||||
|
if (!p->audiotrack) {
|
||||||
|
MP_ERR(ao, "AudioTrack does not exist to pause!\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||||
|
if (paused) {
|
||||||
|
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.pause);
|
||||||
|
} else {
|
||||||
|
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.play);
|
||||||
|
mp_cond_signal(&p->wakeup);
|
||||||
|
}
|
||||||
|
MP_JNI_EXCEPTION_LOG(ao);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
#define OPT_BASE_STRUCT struct priv
|
#define OPT_BASE_STRUCT struct priv
|
||||||
|
|
||||||
const struct ao_driver audio_out_audiotrack = {
|
const struct ao_driver audio_out_audiotrack = {
|
||||||
|
@ -824,6 +857,7 @@ const struct ao_driver audio_out_audiotrack = {
|
||||||
.uninit = uninit,
|
.uninit = uninit,
|
||||||
.reset = stop,
|
.reset = stop,
|
||||||
.start = start,
|
.start = start,
|
||||||
|
.set_pause = set_pause,
|
||||||
.priv_size = sizeof(struct priv),
|
.priv_size = sizeof(struct priv),
|
||||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||||
.cfg_pcm_float = 1,
|
.cfg_pcm_float = 1,
|
||||||
|
|
|
@ -97,7 +97,7 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||||
int64_t end = mp_time_ns();
|
int64_t end = mp_time_ns();
|
||||||
end += MP_TIME_S_TO_NS(p->device_latency);
|
end += MP_TIME_S_TO_NS(p->device_latency);
|
||||||
end += ca_get_latency(ts) + ca_frames_to_ns(ao, frames);
|
end += ca_get_latency(ts) + ca_frames_to_ns(ao, frames);
|
||||||
ao_read_data(ao, planes, frames, end);
|
ao_read_data(ao, planes, frames, end, NULL, true, true);
|
||||||
return noErr;
|
return noErr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,11 +76,14 @@ static void feed(struct ao *ao)
|
||||||
int64_t cur_time_mp = mp_time_ns();
|
int64_t cur_time_mp = mp_time_ns();
|
||||||
int64_t end_time_av = MPMAX(p->end_time_av, cur_time_av);
|
int64_t end_time_av = MPMAX(p->end_time_av, cur_time_av);
|
||||||
int64_t time_delta = CMTimeGetNanoseconds(CMTimeMake(request_sample_count, samplerate));
|
int64_t time_delta = CMTimeGetNanoseconds(CMTimeMake(request_sample_count, samplerate));
|
||||||
int real_sample_count = ao_read_data_nonblocking(ao, data, request_sample_count, end_time_av - cur_time_av + cur_time_mp + time_delta);
|
bool eof;
|
||||||
|
int real_sample_count = ao_read_data(ao, data, request_sample_count, end_time_av - cur_time_av + cur_time_mp + time_delta, &eof, false, true);
|
||||||
|
if (eof) {
|
||||||
|
[p->renderer stopRequestingMediaData];
|
||||||
|
ao_stop_streaming(ao);
|
||||||
|
}
|
||||||
if (real_sample_count == 0) {
|
if (real_sample_count == 0) {
|
||||||
// avoid spinning by blocking the thread
|
return;
|
||||||
mp_sleep_ns(10000000);
|
|
||||||
goto finish;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((err = CMBlockBufferCreateWithMemoryBlock(
|
if ((err = CMBlockBufferCreateWithMemoryBlock(
|
||||||
|
|
|
@ -90,7 +90,7 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||||
int64_t end = mp_time_ns();
|
int64_t end = mp_time_ns();
|
||||||
end += p->hw_latency_ns + ca_get_latency(ts) + ca_frames_to_ns(ao, frames);
|
end += p->hw_latency_ns + ca_get_latency(ts) + ca_frames_to_ns(ao, frames);
|
||||||
// don't use the returned sample count since CoreAudio always expects full frames
|
// don't use the returned sample count since CoreAudio always expects full frames
|
||||||
ao_read_data(ao, planes, frames, end);
|
ao_read_data(ao, planes, frames, end, NULL, true, true);
|
||||||
return noErr;
|
return noErr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -181,7 +181,7 @@ static OSStatus render_cb_compressed(
|
||||||
end += p->hw_latency_ns + ca_get_latency(ts)
|
end += p->hw_latency_ns + ca_get_latency(ts)
|
||||||
+ ca_frames_to_ns(ao, pseudo_frames);
|
+ ca_frames_to_ns(ao, pseudo_frames);
|
||||||
|
|
||||||
ao_read_data(ao, &buf.mData, pseudo_frames, end);
|
ao_read_data(ao, &buf.mData, pseudo_frames, end, NULL, true, true);
|
||||||
|
|
||||||
if (p->spdif_hack)
|
if (p->spdif_hack)
|
||||||
bad_hack_mygodwhy(buf.mData, pseudo_frames * ao->channels.num);
|
bad_hack_mygodwhy(buf.mData, pseudo_frames * ao->channels.num);
|
||||||
|
|
|
@ -125,7 +125,7 @@ static int process(jack_nframes_t nframes, void *arg)
|
||||||
int64_t end_time = mp_time_ns();
|
int64_t end_time = mp_time_ns();
|
||||||
end_time += MP_TIME_S_TO_NS((jack_latency + nframes) / (double)ao->samplerate);
|
end_time += MP_TIME_S_TO_NS((jack_latency + nframes) / (double)ao->samplerate);
|
||||||
|
|
||||||
ao_read_data(ao, buffers, nframes, end_time);
|
ao_read_data(ao, buffers, nframes, end_time, NULL, true, true);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@ static void buffer_callback(SLBufferQueueItf buffer_queue, void *context)
|
||||||
delay = p->frames_per_enqueue / (double)ao->samplerate;
|
delay = p->frames_per_enqueue / (double)ao->samplerate;
|
||||||
delay += p->audio_latency;
|
delay += p->audio_latency;
|
||||||
ao_read_data(ao, &p->buf, p->frames_per_enqueue,
|
ao_read_data(ao, &p->buf, p->frames_per_enqueue,
|
||||||
mp_time_ns() + MP_TIME_S_TO_NS(delay));
|
mp_time_ns() + MP_TIME_S_TO_NS(delay), NULL, true, true);
|
||||||
|
|
||||||
res = (*buffer_queue)->Enqueue(buffer_queue, p->buf, p->bytes_per_enqueue);
|
res = (*buffer_queue)->Enqueue(buffer_queue, p->buf, p->bytes_per_enqueue);
|
||||||
if (res != SL_RESULT_SUCCESS)
|
if (res != SL_RESULT_SUCCESS)
|
||||||
|
|
|
@ -206,7 +206,8 @@ static void on_process(void *userdata)
|
||||||
#endif
|
#endif
|
||||||
end_time -= pw_stream_get_nsec(p->stream) - time.now;
|
end_time -= pw_stream_get_nsec(p->stream) - time.now;
|
||||||
|
|
||||||
int samples = ao_read_data_nonblocking(ao, data, nframes, end_time);
|
bool eof;
|
||||||
|
int samples = ao_read_data(ao, data, nframes, end_time, &eof, false, false);
|
||||||
b->size = samples;
|
b->size = samples;
|
||||||
|
|
||||||
for (int i = 0; i < buf->n_datas; i++) {
|
for (int i = 0; i < buf->n_datas; i++) {
|
||||||
|
@ -217,6 +218,11 @@ static void on_process(void *userdata)
|
||||||
|
|
||||||
pw_stream_queue_buffer(p->stream, b);
|
pw_stream_queue_buffer(p->stream, b);
|
||||||
|
|
||||||
|
if (eof) {
|
||||||
|
pw_stream_flush(p->stream, true);
|
||||||
|
ao_stop_streaming(ao);
|
||||||
|
}
|
||||||
|
|
||||||
MP_TRACE(ao, "queued %d of %d samples\n", samples, nframes);
|
MP_TRACE(ao, "queued %d of %d samples\n", samples, nframes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ static void audio_callback(void *userdata, Uint8 *stream, int len)
|
||||||
// fixed latency.
|
// fixed latency.
|
||||||
double delay = 2 * len / (double)ao->bps;
|
double delay = 2 * len / (double)ao->bps;
|
||||||
|
|
||||||
ao_read_data(ao, data, len / ao->sstride, mp_time_ns() + MP_TIME_S_TO_NS(delay));
|
ao_read_data(ao, data, len / ao->sstride, mp_time_ns() + MP_TIME_S_TO_NS(delay), NULL, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void uninit(struct ao *ao)
|
static void uninit(struct ao *ao)
|
||||||
|
|
|
@ -193,17 +193,19 @@ static void thread_resume(struct ao *ao)
|
||||||
thread_unpause(ao);
|
thread_unpause(ao);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_state_and_wakeup_thread(struct ao *ao,
|
static void thread_wakeup(void *ptr)
|
||||||
enum wasapi_thread_state thread_state)
|
{
|
||||||
|
struct ao *ao = ptr;
|
||||||
|
struct wasapi_state *state = ao->priv;
|
||||||
|
SetEvent(state->hUserWake);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_thread_state(struct ao *ao,
|
||||||
|
enum wasapi_thread_state thread_state)
|
||||||
{
|
{
|
||||||
struct wasapi_state *state = ao->priv;
|
struct wasapi_state *state = ao->priv;
|
||||||
atomic_store(&state->thread_state, thread_state);
|
atomic_store(&state->thread_state, thread_state);
|
||||||
SetEvent(state->hWake);
|
thread_wakeup(ao);
|
||||||
}
|
|
||||||
|
|
||||||
static void thread_process_dispatch(void *ptr)
|
|
||||||
{
|
|
||||||
set_state_and_wakeup_thread(ptr, WASAPI_THREAD_DISPATCH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static DWORD __stdcall AudioThread(void *lpParameter)
|
static DWORD __stdcall AudioThread(void *lpParameter)
|
||||||
|
@ -220,18 +222,25 @@ static DWORD __stdcall AudioThread(void *lpParameter)
|
||||||
|
|
||||||
MP_DBG(ao, "Entering dispatch loop\n");
|
MP_DBG(ao, "Entering dispatch loop\n");
|
||||||
while (true) {
|
while (true) {
|
||||||
if (WaitForSingleObject(state->hWake, INFINITE) != WAIT_OBJECT_0)
|
HANDLE handles[] = {state->hWake, state->hUserWake};
|
||||||
MP_ERR(ao, "Unexpected return value from WaitForSingleObject\n");
|
switch (WaitForMultipleObjects(MP_ARRAY_SIZE(handles), handles, FALSE, INFINITE)) {
|
||||||
|
case WAIT_OBJECT_0:
|
||||||
|
// fill twice on under-full buffer (see comment in thread_feed)
|
||||||
|
if (thread_feed(ao) && thread_feed(ao))
|
||||||
|
MP_ERR(ao, "Unable to fill buffer fast enough\n");
|
||||||
|
continue;
|
||||||
|
case WAIT_OBJECT_0 + 1:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
MP_ERR(ao, "Unexpected return value from WaitForMultipleObjects\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mp_dispatch_queue_process(state->dispatch, 0);
|
||||||
|
|
||||||
int thread_state = atomic_load(&state->thread_state);
|
int thread_state = atomic_load(&state->thread_state);
|
||||||
switch (thread_state) {
|
switch (thread_state) {
|
||||||
case WASAPI_THREAD_FEED:
|
case WASAPI_THREAD_FEED:
|
||||||
// fill twice on under-full buffer (see comment in thread_feed)
|
|
||||||
if (thread_feed(ao) && thread_feed(ao))
|
|
||||||
MP_ERR(ao, "Unable to fill buffer fast enough\n");
|
|
||||||
break;
|
|
||||||
case WASAPI_THREAD_DISPATCH:
|
|
||||||
mp_dispatch_queue_process(state->dispatch, 0);
|
|
||||||
break;
|
break;
|
||||||
case WASAPI_THREAD_RESET:
|
case WASAPI_THREAD_RESET:
|
||||||
thread_reset(ao);
|
thread_reset(ao);
|
||||||
|
@ -267,8 +276,8 @@ static void uninit(struct ao *ao)
|
||||||
{
|
{
|
||||||
MP_DBG(ao, "Uninit wasapi\n");
|
MP_DBG(ao, "Uninit wasapi\n");
|
||||||
struct wasapi_state *state = ao->priv;
|
struct wasapi_state *state = ao->priv;
|
||||||
if (state->hWake)
|
if (state->hWake && state->hUserWake)
|
||||||
set_state_and_wakeup_thread(ao, WASAPI_THREAD_SHUTDOWN);
|
set_thread_state(ao, WASAPI_THREAD_SHUTDOWN);
|
||||||
|
|
||||||
if (state->hAudioThread &&
|
if (state->hAudioThread &&
|
||||||
WaitForSingleObject(state->hAudioThread, INFINITE) != WAIT_OBJECT_0)
|
WaitForSingleObject(state->hAudioThread, INFINITE) != WAIT_OBJECT_0)
|
||||||
|
@ -279,6 +288,7 @@ static void uninit(struct ao *ao)
|
||||||
|
|
||||||
SAFE_DESTROY(state->hInitDone, CloseHandle(state->hInitDone));
|
SAFE_DESTROY(state->hInitDone, CloseHandle(state->hInitDone));
|
||||||
SAFE_DESTROY(state->hWake, CloseHandle(state->hWake));
|
SAFE_DESTROY(state->hWake, CloseHandle(state->hWake));
|
||||||
|
SAFE_DESTROY(state->hUserWake, CloseHandle(state->hUserWake));
|
||||||
SAFE_DESTROY(state->hAudioThread,CloseHandle(state->hAudioThread));
|
SAFE_DESTROY(state->hAudioThread,CloseHandle(state->hAudioThread));
|
||||||
|
|
||||||
wasapi_change_uninit(ao);
|
wasapi_change_uninit(ao);
|
||||||
|
@ -312,14 +322,15 @@ static int init(struct ao *ao)
|
||||||
|
|
||||||
state->hInitDone = CreateEventW(NULL, FALSE, FALSE, NULL);
|
state->hInitDone = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||||
state->hWake = CreateEventW(NULL, FALSE, FALSE, NULL);
|
state->hWake = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||||
if (!state->hInitDone || !state->hWake) {
|
state->hUserWake = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||||
|
if (!state->hInitDone || !state->hWake || !state->hUserWake) {
|
||||||
MP_FATAL(ao, "Error creating events\n");
|
MP_FATAL(ao, "Error creating events\n");
|
||||||
uninit(ao);
|
uninit(ao);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
state->dispatch = mp_dispatch_create(state);
|
state->dispatch = mp_dispatch_create(state);
|
||||||
mp_dispatch_set_wakeup_fn(state->dispatch, thread_process_dispatch, ao);
|
mp_dispatch_set_wakeup_fn(state->dispatch, thread_wakeup, ao);
|
||||||
|
|
||||||
state->init_ok = false;
|
state->init_ok = false;
|
||||||
state->hAudioThread = CreateThread(NULL, 0, &AudioThread, ao, 0, NULL);
|
state->hAudioThread = CreateThread(NULL, 0, &AudioThread, ao, 0, NULL);
|
||||||
|
@ -474,17 +485,17 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||||
|
|
||||||
static void audio_reset(struct ao *ao)
|
static void audio_reset(struct ao *ao)
|
||||||
{
|
{
|
||||||
set_state_and_wakeup_thread(ao, WASAPI_THREAD_RESET);
|
set_thread_state(ao, WASAPI_THREAD_RESET);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void audio_resume(struct ao *ao)
|
static void audio_resume(struct ao *ao)
|
||||||
{
|
{
|
||||||
set_state_and_wakeup_thread(ao, WASAPI_THREAD_RESUME);
|
set_thread_state(ao, WASAPI_THREAD_RESUME);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool audio_set_pause(struct ao *ao, bool paused)
|
static bool audio_set_pause(struct ao *ao, bool paused)
|
||||||
{
|
{
|
||||||
set_state_and_wakeup_thread(ao, paused ? WASAPI_THREAD_PAUSE : WASAPI_THREAD_UNPAUSE);
|
set_thread_state(ao, paused ? WASAPI_THREAD_PAUSE : WASAPI_THREAD_UNPAUSE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,6 @@ void wasapi_change_uninit(struct ao* ao);
|
||||||
|
|
||||||
enum wasapi_thread_state {
|
enum wasapi_thread_state {
|
||||||
WASAPI_THREAD_FEED = 0,
|
WASAPI_THREAD_FEED = 0,
|
||||||
WASAPI_THREAD_DISPATCH,
|
|
||||||
WASAPI_THREAD_RESUME,
|
WASAPI_THREAD_RESUME,
|
||||||
WASAPI_THREAD_RESET,
|
WASAPI_THREAD_RESET,
|
||||||
WASAPI_THREAD_SHUTDOWN,
|
WASAPI_THREAD_SHUTDOWN,
|
||||||
|
@ -66,6 +65,7 @@ typedef struct wasapi_state {
|
||||||
HANDLE hWake; // thread wakeup event
|
HANDLE hWake; // thread wakeup event
|
||||||
atomic_int thread_state; // enum wasapi_thread_state (what to do on wakeup)
|
atomic_int thread_state; // enum wasapi_thread_state (what to do on wakeup)
|
||||||
struct mp_dispatch_queue *dispatch; // for volume/mute/session display
|
struct mp_dispatch_queue *dispatch; // for volume/mute/session display
|
||||||
|
HANDLE hUserWake; // mpv-requested wakeup event
|
||||||
|
|
||||||
// for setting the audio thread priority
|
// for setting the audio thread priority
|
||||||
HANDLE hTask;
|
HANDLE hTask;
|
||||||
|
|
|
@ -179,12 +179,12 @@ static int read_buffer(struct ao *ao, void **data, int samples, bool *eof,
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ao_read_data_locked(struct ao *ao, void **data, int samples,
|
static int ao_read_data_locked(struct ao *ao, void **data, int samples,
|
||||||
int64_t out_time_ns, bool pad_silence)
|
int64_t out_time_ns, bool *eof, bool pad_silence)
|
||||||
{
|
{
|
||||||
struct buffer_state *p = ao->buffer_state;
|
struct buffer_state *p = ao->buffer_state;
|
||||||
assert(!ao->driver->write);
|
assert(!ao->driver->write);
|
||||||
|
|
||||||
int pos = read_buffer(ao, data, samples, &(bool){0}, pad_silence);
|
int pos = read_buffer(ao, data, samples, eof, pad_silence);
|
||||||
|
|
||||||
if (pos > 0)
|
if (pos > 0)
|
||||||
p->end_time_ns = out_time_ns;
|
p->end_time_ns = out_time_ns;
|
||||||
|
@ -207,29 +207,23 @@ static int ao_read_data_locked(struct ao *ao, void **data, int samples,
|
||||||
// If this is called in paused mode, it will always return 0.
|
// If this is called in paused mode, it will always return 0.
|
||||||
// The caller should set out_time_ns to the expected delay until the last sample
|
// The caller should set out_time_ns to the expected delay until the last sample
|
||||||
// reaches the speakers, in nanoseconds, using mp_time_ns() as reference.
|
// reaches the speakers, in nanoseconds, using mp_time_ns() as reference.
|
||||||
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_ns)
|
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_ns, bool *eof, bool pad_silence, bool blocking)
|
||||||
{
|
{
|
||||||
struct buffer_state *p = ao->buffer_state;
|
struct buffer_state *p = ao->buffer_state;
|
||||||
|
|
||||||
mp_mutex_lock(&p->lock);
|
if (blocking) {
|
||||||
|
mp_mutex_lock(&p->lock);
|
||||||
|
} else if (mp_mutex_trylock(&p->lock)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int pos = ao_read_data_locked(ao, data, samples, out_time_ns, true);
|
bool eof_buf;
|
||||||
|
if (eof == NULL) {
|
||||||
|
// This is a public API. We want to reduce the cognitive burden of the caller.
|
||||||
|
eof = &eof_buf;
|
||||||
|
}
|
||||||
|
|
||||||
mp_mutex_unlock(&p->lock);
|
int pos = ao_read_data_locked(ao, data, samples, out_time_ns, eof, pad_silence);
|
||||||
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like ao_read_data() but does not block and also may return partial data.
|
|
||||||
// Callers have to check the return value.
|
|
||||||
int ao_read_data_nonblocking(struct ao *ao, void **data, int samples, int64_t out_time_ns)
|
|
||||||
{
|
|
||||||
struct buffer_state *p = ao->buffer_state;
|
|
||||||
|
|
||||||
if (mp_mutex_trylock(&p->lock))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
int pos = ao_read_data_locked(ao, data, samples, out_time_ns, false);
|
|
||||||
|
|
||||||
mp_mutex_unlock(&p->lock);
|
mp_mutex_unlock(&p->lock);
|
||||||
|
|
||||||
|
@ -245,7 +239,7 @@ int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
|
||||||
void *ndata[MP_NUM_CHANNELS] = {0};
|
void *ndata[MP_NUM_CHANNELS] = {0};
|
||||||
|
|
||||||
if (!ao_need_conversion(fmt))
|
if (!ao_need_conversion(fmt))
|
||||||
return ao_read_data(ao, data, samples, out_time_ns);
|
return ao_read_data(ao, data, samples, out_time_ns, NULL, true, true);
|
||||||
|
|
||||||
assert(ao->format == fmt->src_fmt);
|
assert(ao->format == fmt->src_fmt);
|
||||||
assert(ao->channels.num == fmt->channels);
|
assert(ao->channels.num == fmt->channels);
|
||||||
|
@ -265,7 +259,7 @@ int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
|
||||||
for (int n = 0; n < planes; n++)
|
for (int n = 0; n < planes; n++)
|
||||||
ndata[n] = p->convert_buffer + n * src_plane_size;
|
ndata[n] = p->convert_buffer + n * src_plane_size;
|
||||||
|
|
||||||
int res = ao_read_data(ao, ndata, samples, out_time_ns);
|
int res = ao_read_data(ao, ndata, samples, out_time_ns, NULL, true, true);
|
||||||
|
|
||||||
ao_convert_inplace(fmt, ndata, samples);
|
ao_convert_inplace(fmt, ndata, samples);
|
||||||
for (int n = 0; n < planes; n++)
|
for (int n = 0; n < planes; n++)
|
||||||
|
@ -274,6 +268,16 @@ int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called by pull-based AO to indicate the AO has stopped requesting more data,
|
||||||
|
// usually when EOF is got from ao_read_data().
|
||||||
|
// After this function is called, the core will call ao->driver->start() again
|
||||||
|
// when more audio data after EOF arrives.
|
||||||
|
void ao_stop_streaming(struct ao *ao)
|
||||||
|
{
|
||||||
|
struct buffer_state *p = ao->buffer_state;
|
||||||
|
p->streaming = false;
|
||||||
|
}
|
||||||
|
|
||||||
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
|
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||||
{
|
{
|
||||||
struct buffer_state *p = ao->buffer_state;
|
struct buffer_state *p = ao->buffer_state;
|
||||||
|
|
|
@ -201,9 +201,7 @@ struct ao_driver {
|
||||||
|
|
||||||
// These functions can be called by AOs.
|
// These functions can be called by AOs.
|
||||||
|
|
||||||
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_ns);
|
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_ns, bool *eof, bool pad_silence, bool blocking);
|
||||||
MP_WARN_UNUSED_RESULT
|
|
||||||
int ao_read_data_nonblocking(struct ao *ao, void **data, int samples, int64_t out_time_ns);
|
|
||||||
|
|
||||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||||
struct mp_chmap *map);
|
struct mp_chmap *map);
|
||||||
|
@ -236,4 +234,6 @@ void ao_wakeup(struct ao *ao);
|
||||||
int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
|
int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
|
||||||
void **data, int samples, int64_t out_time_ns);
|
void **data, int samples, int64_t out_time_ns);
|
||||||
|
|
||||||
|
void ao_stop_streaming(struct ao *ao);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -101,7 +101,7 @@ function _mpv_generate_arguments {
|
||||||
|
|
||||||
entry+='->files'
|
entry+='->files'
|
||||||
|
|
||||||
elif [[ $name == (ao|vo|af|vf|profile|audio-device|vulkan-device) ]]; then
|
elif [[ $desc = 'Object settings list'* || $name == (profile|audio-device|vulkan-device) ]]; then
|
||||||
|
|
||||||
entry+="->parse-help-$name"
|
entry+="->parse-help-$name"
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ const struct mp_user_filter_entry *vf_list[] = {
|
||||||
#if HAVE_D3D_HWACCEL
|
#if HAVE_D3D_HWACCEL
|
||||||
&vf_d3d11vpp,
|
&vf_d3d11vpp,
|
||||||
#endif
|
#endif
|
||||||
#if HAVE_GL && HAVE_EGL
|
#if (HAVE_GL && HAVE_EGL) || HAVE_VULKAN
|
||||||
&vf_gpu,
|
&vf_gpu,
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* This file is part of mpv.
|
||||||
|
*
|
||||||
|
* mpv is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define MPV_STRINGIFY_(X) #X
|
||||||
|
#define MPV_STRINGIFY(X) MPV_STRINGIFY_(X)
|
||||||
|
|
||||||
|
static inline void check_error(int status)
|
||||||
|
{
|
||||||
|
if (status < 0) {
|
||||||
|
fprintf(stderr, "mpv API error: %s\n", mpv_error_string(status));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool str_startswith(const char *str, size_t str_len,
|
||||||
|
const char *prefix, size_t prefix_len)
|
||||||
|
{
|
||||||
|
if (str_len < prefix_len)
|
||||||
|
return false;
|
||||||
|
return !memcmp(str, prefix, prefix_len);
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* This file is part of mpv.
|
||||||
|
*
|
||||||
|
* mpv is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <libmpv/client.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
if (size == 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
char filename[15 + 10 + 1];
|
||||||
|
sprintf(filename, "/tmp/libfuzzer.%d", getpid());
|
||||||
|
|
||||||
|
FILE *fp = fopen(filename, "wb");
|
||||||
|
if (!fp)
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
if (fwrite(data, size, 1, fp) != 1)
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
if (fclose(fp))
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
mpv_handle *ctx = mpv_create();
|
||||||
|
if (!ctx)
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
check_error(mpv_set_option_string(ctx, "vo", "null"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "ao", "null"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "ao-null-untimed", "yes"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "untimed", "yes"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "video-osd", "no"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "msg-level", "all=trace"));
|
||||||
|
|
||||||
|
check_error(mpv_initialize(ctx));
|
||||||
|
|
||||||
|
const char *cmd[] = {"loadfile", filename, NULL};
|
||||||
|
check_error(mpv_command(ctx, cmd));
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
mpv_event *event = mpv_wait_event(ctx, 10000);
|
||||||
|
if (event->event_id == MPV_EVENT_IDLE)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mpv_terminate_destroy(ctx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* This file is part of mpv.
|
||||||
|
*
|
||||||
|
* mpv is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <libmpv/client.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
if (size <= 1 || data[size - 1] != '\0')
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// Exlude data with null bytes inside
|
||||||
|
if (strlen(data) != size - 1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
#ifdef MPV_PROTO
|
||||||
|
if (!str_startswith(data, size - 1, MPV_STRINGIFY(MPV_PROTO) "://", strlen(MPV_STRINGIFY(MPV_PROTO) "://")))
|
||||||
|
return -1;
|
||||||
|
#else
|
||||||
|
// Exclude some common paths that are not useful for testing.
|
||||||
|
// Exclude -
|
||||||
|
if (size == 2 && !strncmp(data, "-", 1))
|
||||||
|
return -1;
|
||||||
|
// Exclude relative paths
|
||||||
|
if (str_startswith(data, size - 1, ".", 1))
|
||||||
|
return -1;
|
||||||
|
// Exclude absolute paths
|
||||||
|
if (str_startswith(data, size - 1, "/", 1))
|
||||||
|
return -1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
mpv_handle *ctx = mpv_create();
|
||||||
|
if (!ctx)
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
check_error(mpv_set_option_string(ctx, "vo", "null"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "ao", "null"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "ao-null-untimed", "yes"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "untimed", "yes"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "video-osd", "no"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "msg-level", "all=trace"));
|
||||||
|
|
||||||
|
check_error(mpv_initialize(ctx));
|
||||||
|
|
||||||
|
const char *cmd[] = {"loadfile", data, NULL};
|
||||||
|
check_error(mpv_command(ctx, cmd));
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
mpv_event *event = mpv_wait_event(ctx, 10000);
|
||||||
|
if (event->event_id == MPV_EVENT_IDLE)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mpv_terminate_destroy(ctx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* This file is part of mpv.
|
||||||
|
*
|
||||||
|
* mpv is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <libmpv/client.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
size_t value_len;
|
||||||
|
switch (MPV_FORMAT)
|
||||||
|
{
|
||||||
|
case MPV_FORMAT_STRING:
|
||||||
|
value_len = strnlen(data, size);
|
||||||
|
if (!value_len || value_len == size)
|
||||||
|
return -1;
|
||||||
|
value_len += 1;
|
||||||
|
break;
|
||||||
|
case MPV_FORMAT_FLAG:
|
||||||
|
value_len = sizeof(int);
|
||||||
|
break;
|
||||||
|
case MPV_FORMAT_INT64:
|
||||||
|
value_len = sizeof(int64_t);
|
||||||
|
break;
|
||||||
|
case MPV_FORMAT_DOUBLE:
|
||||||
|
value_len = sizeof(double);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
exit(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// at least two bytes for the name
|
||||||
|
if (size < value_len + 2)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
const char *name = (const char *)data + value_len;
|
||||||
|
size_t name_len = strnlen(name, size - value_len);
|
||||||
|
if (!name_len || name_len != size - value_len - 1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
mpv_handle *ctx = mpv_create();
|
||||||
|
if (!ctx)
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
#if MPV_RUN
|
||||||
|
check_error(mpv_set_option_string(ctx, "vo", "null"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "ao", "null"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "ao-null-untimed", "yes"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "untimed", "yes"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "video-osd", "no"));
|
||||||
|
check_error(mpv_set_option_string(ctx, "msg-level", "all=trace"));
|
||||||
|
|
||||||
|
check_error(mpv_initialize(ctx));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const void *value = data;
|
||||||
|
mpv_set_property(ctx, name, MPV_FORMAT, &value);
|
||||||
|
|
||||||
|
#if MPV_RUN
|
||||||
|
check_error(mpv_set_option_string(ctx, "audio-files", "av://lavfi:sine=d=0.1"));
|
||||||
|
const char *cmd[] = {"loadfile", "av://lavfi:yuvtestsrc=d=0.1", NULL};
|
||||||
|
check_error(mpv_command(ctx, cmd));
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
mpv_event *event = mpv_wait_event(ctx, 10000);
|
||||||
|
if (event->event_id == MPV_EVENT_IDLE)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
mpv_terminate_destroy(ctx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
incdir = include_directories('../')
|
||||||
|
|
||||||
|
executable('fuzzer_loadfile', 'fuzzer_loadfile.c',
|
||||||
|
include_directories: incdir, link_with: libmpv)
|
||||||
|
executable('fuzzer_loadfile_direct', 'fuzzer_loadfile_direct.c',
|
||||||
|
include_directories: incdir, link_with: libmpv)
|
||||||
|
|
||||||
|
foreach p : ['bd', 'cdda', 'dvb', 'dvd', 'edl', 'file', 'hex', 'lavf', 'memory',
|
||||||
|
'mf', 'slice', 'smb']
|
||||||
|
executable('fuzzer_protocol_' + p,
|
||||||
|
'fuzzer_loadfile_direct.c',
|
||||||
|
c_args: ['-DMPV_PROTO=' + p],
|
||||||
|
include_directories: incdir,
|
||||||
|
link_with: libmpv)
|
||||||
|
endforeach
|
||||||
|
|
||||||
|
|
||||||
|
foreach f : ['MPV_FORMAT_STRING', 'MPV_FORMAT_FLAG', 'MPV_FORMAT_INT64', 'MPV_FORMAT_DOUBLE']
|
||||||
|
foreach i : ['0', '1']
|
||||||
|
executable('fuzzer_set_property_' + f + '_' + i,
|
||||||
|
'fuzzer_set_property.c',
|
||||||
|
c_args: ['-DMPV_FORMAT=' + f, '-DMPV_RUN=' + i],
|
||||||
|
include_directories: incdir,
|
||||||
|
link_with: libmpv)
|
||||||
|
endforeach
|
||||||
|
endforeach
|
22
meson.build
22
meson.build
|
@ -379,6 +379,15 @@ pthread_debug = get_option('pthread-debug').require(
|
||||||
)
|
)
|
||||||
features += {'pthread-debug': pthread_debug.allowed()}
|
features += {'pthread-debug': pthread_debug.allowed()}
|
||||||
|
|
||||||
|
if get_option('fuzzers')
|
||||||
|
if get_option('cplayer') or not get_option('libmpv')
|
||||||
|
error('fuzzers require !cplayer and libmpv')
|
||||||
|
endif
|
||||||
|
# Adding flags manually until https://github.com/mesonbuild/meson/pull/9825
|
||||||
|
flags += ['-fsanitize=address,undefined,fuzzer', '-fno-omit-frame-pointer']
|
||||||
|
link_flags += ['-fsanitize=address,undefined,fuzzer', '-fno-omit-frame-pointer']
|
||||||
|
endif
|
||||||
|
|
||||||
add_project_arguments(flags, language: 'c')
|
add_project_arguments(flags, language: 'c')
|
||||||
add_project_arguments(['-Wno-unused-parameter'], language: 'objc')
|
add_project_arguments(['-Wno-unused-parameter'], language: 'objc')
|
||||||
add_project_link_arguments(link_flags, language: ['c', 'objc'])
|
add_project_link_arguments(link_flags, language: ['c', 'objc'])
|
||||||
|
@ -1249,7 +1258,7 @@ if features['egl'] or features['egl-android'] or features['egl-angle-win32']
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if features['gl'] and features['egl']
|
if features['gl'] and features['egl']
|
||||||
sources += files('video/filter/vf_gpu.c')
|
sources += files('video/filter/vf_gpu_egl.c')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if features['gl']
|
if features['gl']
|
||||||
|
@ -1274,7 +1283,8 @@ features += {'vulkan': vulkan.found()}
|
||||||
if features['vulkan']
|
if features['vulkan']
|
||||||
dependencies += vulkan
|
dependencies += vulkan
|
||||||
sources += files('video/out/vulkan/context.c',
|
sources += files('video/out/vulkan/context.c',
|
||||||
'video/out/vulkan/utils.c')
|
'video/out/vulkan/utils.c',
|
||||||
|
'video/filter/vf_gpu_vulkan.c')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if features['vulkan'] and features['android']
|
if features['vulkan'] and features['android']
|
||||||
|
@ -1299,6 +1309,10 @@ if features['vk-khr-display']
|
||||||
sources += files('video/out/vulkan/context_display.c')
|
sources += files('video/out/vulkan/context_display.c')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if features['vulkan'] or (features['gl'] and features['egl'])
|
||||||
|
sources += files('video/filter/vf_gpu.c')
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
# hwaccel
|
# hwaccel
|
||||||
ffnvcodec = dependency('ffnvcodec', version: '>= 11.1.5.1', required: false)
|
ffnvcodec = dependency('ffnvcodec', version: '>= 11.1.5.1', required: false)
|
||||||
|
@ -1798,6 +1812,10 @@ if get_option('tests')
|
||||||
subdir('test')
|
subdir('test')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if get_option('fuzzers')
|
||||||
|
subdir('fuzzers')
|
||||||
|
endif
|
||||||
|
|
||||||
summary({'d3d11': features['d3d11'],
|
summary({'d3d11': features['d3d11'],
|
||||||
'javascript': features['javascript'],
|
'javascript': features['javascript'],
|
||||||
'libmpv': get_option('libmpv'),
|
'libmpv': get_option('libmpv'),
|
||||||
|
|
|
@ -4,6 +4,7 @@ option('cplayer', type: 'boolean', value: true, description: 'mpv CLI player')
|
||||||
option('libmpv', type: 'boolean', value: false, description: 'libmpv library')
|
option('libmpv', type: 'boolean', value: false, description: 'libmpv library')
|
||||||
option('build-date', type: 'boolean', value: true, description: 'include compile timestamp in binary')
|
option('build-date', type: 'boolean', value: true, description: 'include compile timestamp in binary')
|
||||||
option('tests', type: 'boolean', value: false, description: 'meson unit tests')
|
option('tests', type: 'boolean', value: false, description: 'meson unit tests')
|
||||||
|
option('fuzzers', type: 'boolean', value: false, description: 'fuzzer binaries')
|
||||||
# Reminder: normally always built, but enabled by MPV_LEAK_REPORT.
|
# Reminder: normally always built, but enabled by MPV_LEAK_REPORT.
|
||||||
# Building it can be disabled only by defining NDEBUG through CFLAGS.
|
# Building it can be disabled only by defining NDEBUG through CFLAGS.
|
||||||
option('ta-leak-report', type: 'boolean', value: false, description: 'enable ta leak report by default (development only)')
|
option('ta-leak-report', type: 'boolean', value: false, description: 'enable ta leak report by default (development only)')
|
||||||
|
|
|
@ -3021,6 +3021,20 @@ static int mp_property_sub_text(void *ctx, struct m_property *prop,
|
||||||
int sub_index = def[0];
|
int sub_index = def[0];
|
||||||
int type = def[1];
|
int type = def[1];
|
||||||
|
|
||||||
|
if (action == M_PROPERTY_KEY_ACTION) {
|
||||||
|
struct m_property_action_arg *ka = arg;
|
||||||
|
|
||||||
|
if (!strcmp(ka->key, "ass"))
|
||||||
|
type = SD_TEXT_TYPE_ASS;
|
||||||
|
else if (!strcmp(ka->key, "ass-full"))
|
||||||
|
type = SD_TEXT_TYPE_ASS_FULL;
|
||||||
|
else
|
||||||
|
return M_PROPERTY_UNKNOWN;
|
||||||
|
|
||||||
|
action = ka->action;
|
||||||
|
arg = ka->arg;
|
||||||
|
}
|
||||||
|
|
||||||
struct track *track = mpctx->current_track[sub_index][STREAM_SUB];
|
struct track *track = mpctx->current_track[sub_index][STREAM_SUB];
|
||||||
struct dec_sub *sub = track ? track->d_sub : NULL;
|
struct dec_sub *sub = track ? track->d_sub : NULL;
|
||||||
double pts = mpctx->playback_pts;
|
double pts = mpctx->playback_pts;
|
||||||
|
@ -4048,8 +4062,7 @@ static const struct m_property mp_properties_base[] = {
|
||||||
.priv = (void *)&(const int[]){0, SD_TEXT_TYPE_PLAIN}},
|
.priv = (void *)&(const int[]){0, SD_TEXT_TYPE_PLAIN}},
|
||||||
{"secondary-sub-text", mp_property_sub_text,
|
{"secondary-sub-text", mp_property_sub_text,
|
||||||
.priv = (void *)&(const int[]){1, SD_TEXT_TYPE_PLAIN}},
|
.priv = (void *)&(const int[]){1, SD_TEXT_TYPE_PLAIN}},
|
||||||
{"sub-text-ass", mp_property_sub_text,
|
M_PROPERTY_DEPRECATED_ALIAS("sub-text-ass", "sub-text/ass"),
|
||||||
.priv = (void *)&(const int[]){0, SD_TEXT_TYPE_ASS}},
|
|
||||||
{"sub-start", mp_property_sub_start,
|
{"sub-start", mp_property_sub_start,
|
||||||
.priv = (void *)&(const int){0}},
|
.priv = (void *)&(const int){0}},
|
||||||
{"secondary-sub-start", mp_property_sub_start,
|
{"secondary-sub-start", mp_property_sub_start,
|
||||||
|
|
|
@ -895,18 +895,23 @@ local function add_video_out(s)
|
||||||
scale = mp.get_property_native("current-window-scale")
|
scale = mp.get_property_native("current-window-scale")
|
||||||
end
|
end
|
||||||
|
|
||||||
local r = mp.get_property_native("video-target-params")
|
local od = mp.get_property_native("osd-dimensions")
|
||||||
if not r then
|
local rt = mp.get_property_native("video-target-params")
|
||||||
local osd_dims = mp.get_property_native("osd-dimensions")
|
r = rt or {}
|
||||||
local scaled_width = osd_dims["w"] - osd_dims["ml"] - osd_dims["mr"]
|
|
||||||
local scaled_height = osd_dims["h"] - osd_dims["mt"] - osd_dims["mb"]
|
|
||||||
append_resolution(s, {w=scaled_width, h=scaled_height, s=scale},
|
|
||||||
"Resolution:")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Add window scale
|
-- Add window scale
|
||||||
r["s"] = scale
|
r["s"] = scale
|
||||||
|
r["crop-x"] = od["ml"]
|
||||||
|
r["crop-y"] = od["mt"]
|
||||||
|
r["crop-w"] = od["w"] - od["ml"] - od["mr"]
|
||||||
|
r["crop-h"] = od["h"] - od["mt"] - od["mb"]
|
||||||
|
|
||||||
|
if not rt then
|
||||||
|
r["w"] = r["crop-w"]
|
||||||
|
r["h"] = r["crop-h"]
|
||||||
|
append_resolution(s, r, "Resolution:", "w", "h", true)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
append_img_params(s, r)
|
append_img_params(s, r)
|
||||||
append_hdr(s, r, true)
|
append_hdr(s, r, true)
|
||||||
|
|
|
@ -24,6 +24,7 @@ enum sd_ctrl {
|
||||||
enum sd_text_type {
|
enum sd_text_type {
|
||||||
SD_TEXT_TYPE_PLAIN,
|
SD_TEXT_TYPE_PLAIN,
|
||||||
SD_TEXT_TYPE_ASS,
|
SD_TEXT_TYPE_ASS,
|
||||||
|
SD_TEXT_TYPE_ASS_FULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sd_times {
|
struct sd_times {
|
||||||
|
|
|
@ -115,7 +115,7 @@ static struct demux_packet *jsre_filter(struct sd_filter *ft,
|
||||||
bool drop = false;
|
bool drop = false;
|
||||||
|
|
||||||
if (ft->opts->rf_plain)
|
if (ft->opts->rf_plain)
|
||||||
sd_ass_to_plaintext(text, strlen(text), text);
|
sd_ass_to_plaintext(&text, text);
|
||||||
|
|
||||||
for (int n = 0; n < p->num_regexes; n++) {
|
for (int n = 0; n < p->num_regexes; n++) {
|
||||||
int found, err = p_regexec(p->J, n, text, &found);
|
int found, err = p_regexec(p->J, n, text, &found);
|
||||||
|
|
|
@ -64,7 +64,7 @@ static struct demux_packet *rf_filter(struct sd_filter *ft,
|
||||||
bool drop = false;
|
bool drop = false;
|
||||||
|
|
||||||
if (ft->opts->rf_plain)
|
if (ft->opts->rf_plain)
|
||||||
sd_ass_to_plaintext(text, strlen(text), text);
|
sd_ass_to_plaintext(&text, text);
|
||||||
|
|
||||||
for (int n = 0; n < p->num_regexes; n++) {
|
for (int n = 0; n < p->num_regexes; n++) {
|
||||||
int err = regexec(&p->regexes[n], text, 0, NULL, 0);
|
int err = regexec(&p->regexes[n], text, 0, NULL, 0);
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
struct lavc_conv {
|
struct lavc_conv {
|
||||||
struct mp_log *log;
|
struct mp_log *log;
|
||||||
struct mp_subtitle_opts *opts;
|
struct mp_subtitle_opts *opts;
|
||||||
|
bool styled;
|
||||||
AVCodecContext *avctx;
|
AVCodecContext *avctx;
|
||||||
AVPacket *avpkt;
|
AVPacket *avpkt;
|
||||||
AVPacket *avpkt_vtt;
|
AVPacket *avpkt_vtt;
|
||||||
|
@ -53,20 +54,6 @@ static const char *get_lavc_format(const char *format)
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable style definitions generated by the libavcodec converter.
|
|
||||||
// We always want the user defined style instead.
|
|
||||||
static void disable_styles(bstr header)
|
|
||||||
{
|
|
||||||
bstr style = bstr0("\nStyle: ");
|
|
||||||
while (header.len) {
|
|
||||||
int n = bstr_find(header, style);
|
|
||||||
if (n < 0)
|
|
||||||
break;
|
|
||||||
header.start[n + 1] = '#'; // turn into a comment
|
|
||||||
header = bstr_cut(header, n + style.len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct lavc_conv *lavc_conv_create(struct sd *sd)
|
struct lavc_conv *lavc_conv_create(struct sd *sd)
|
||||||
{
|
{
|
||||||
struct lavc_conv *priv = talloc_zero(NULL, struct lavc_conv);
|
struct lavc_conv *priv = talloc_zero(NULL, struct lavc_conv);
|
||||||
|
@ -116,7 +103,6 @@ struct lavc_conv *lavc_conv_create(struct sd *sd)
|
||||||
priv->avctx = avctx;
|
priv->avctx = avctx;
|
||||||
priv->extradata = talloc_strndup(priv, avctx->subtitle_header,
|
priv->extradata = talloc_strndup(priv, avctx->subtitle_header,
|
||||||
avctx->subtitle_header_size);
|
avctx->subtitle_header_size);
|
||||||
disable_styles(bstr0(priv->extradata));
|
|
||||||
return priv;
|
return priv;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
|
@ -260,9 +246,12 @@ char **lavc_conv_decode(struct lavc_conv *priv, struct demux_packet *packet,
|
||||||
curr_pkt = priv->avpkt_vtt;
|
curr_pkt = priv->avpkt_vtt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
priv->styled = avctx->codec_id == AV_CODEC_ID_DVB_TELETEXT;
|
||||||
|
|
||||||
if (avctx->codec_id == AV_CODEC_ID_DVB_TELETEXT) {
|
if (avctx->codec_id == AV_CODEC_ID_DVB_TELETEXT) {
|
||||||
if (!priv->opts->teletext_page) {
|
if (!priv->opts->teletext_page) {
|
||||||
av_opt_set(avctx, "txt_page", "subtitle", AV_OPT_SEARCH_CHILDREN);
|
av_opt_set(avctx, "txt_page", "subtitle", AV_OPT_SEARCH_CHILDREN);
|
||||||
|
priv->styled = false;
|
||||||
} else if (priv->opts->teletext_page == -1) {
|
} else if (priv->opts->teletext_page == -1) {
|
||||||
av_opt_set(avctx, "txt_page", "*", AV_OPT_SEARCH_CHILDREN);
|
av_opt_set(avctx, "txt_page", "*", AV_OPT_SEARCH_CHILDREN);
|
||||||
} else {
|
} else {
|
||||||
|
@ -300,6 +289,11 @@ done:
|
||||||
return priv->cur_list;
|
return priv->cur_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool lavc_conv_is_styled(struct lavc_conv *priv)
|
||||||
|
{
|
||||||
|
return priv->styled;
|
||||||
|
}
|
||||||
|
|
||||||
void lavc_conv_reset(struct lavc_conv *priv)
|
void lavc_conv_reset(struct lavc_conv *priv)
|
||||||
{
|
{
|
||||||
avcodec_flush_buffers(priv->avctx);
|
avcodec_flush_buffers(priv->avctx);
|
||||||
|
|
9
sub/sd.h
9
sub/sd.h
|
@ -55,6 +55,7 @@ struct lavc_conv *lavc_conv_create(struct sd *sd);
|
||||||
char *lavc_conv_get_extradata(struct lavc_conv *priv);
|
char *lavc_conv_get_extradata(struct lavc_conv *priv);
|
||||||
char **lavc_conv_decode(struct lavc_conv *priv, struct demux_packet *packet,
|
char **lavc_conv_decode(struct lavc_conv *priv, struct demux_packet *packet,
|
||||||
double *sub_pts, double *sub_duration);
|
double *sub_pts, double *sub_duration);
|
||||||
|
bool lavc_conv_is_styled(struct lavc_conv *priv);
|
||||||
void lavc_conv_reset(struct lavc_conv *priv);
|
void lavc_conv_reset(struct lavc_conv *priv);
|
||||||
void lavc_conv_uninit(struct lavc_conv *priv);
|
void lavc_conv_uninit(struct lavc_conv *priv);
|
||||||
|
|
||||||
|
@ -106,8 +107,10 @@ int sd_ass_fmt_offset(const char *event_format);
|
||||||
bstr sd_ass_pkt_text(struct sd_filter *ft, struct demux_packet *pkt, int offset);
|
bstr sd_ass_pkt_text(struct sd_filter *ft, struct demux_packet *pkt, int offset);
|
||||||
|
|
||||||
// convert \0-terminated "Text" (ass) content to plaintext, possibly in-place.
|
// convert \0-terminated "Text" (ass) content to plaintext, possibly in-place.
|
||||||
// result.start is out, result.len is MIN(out_siz, strlen(in)) or smaller.
|
// result.start is *out, result.len is strlen(in) or smaller.
|
||||||
// if there's room: out[result.len] is set to \0. out == in is allowed.
|
// (*out)[result.len] is always set to \0. *out == in is allowed.
|
||||||
bstr sd_ass_to_plaintext(char *out, size_t out_siz, const char *in);
|
// *out must be a talloc-allocated buffer or NULL, and will be reallocated if needed.
|
||||||
|
// *out will not be reallocated if *out == in.
|
||||||
|
bstr sd_ass_to_plaintext(char **out, const char *in);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
126
sub/sd_ass.c
126
sub/sd_ass.c
|
@ -51,7 +51,7 @@ struct sd_ass_priv {
|
||||||
bool clear_once;
|
bool clear_once;
|
||||||
struct mp_ass_packer *packer;
|
struct mp_ass_packer *packer;
|
||||||
struct sub_bitmap_copy_cache *copy_cache;
|
struct sub_bitmap_copy_cache *copy_cache;
|
||||||
char last_text[500];
|
bstr last_text;
|
||||||
struct mp_image_params video_params;
|
struct mp_image_params video_params;
|
||||||
struct mp_image_params last_params;
|
struct mp_image_params last_params;
|
||||||
struct mp_osd_res osd;
|
struct mp_osd_res osd;
|
||||||
|
@ -651,7 +651,7 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim,
|
||||||
struct mp_subtitle_opts *opts = sd->opts;
|
struct mp_subtitle_opts *opts = sd->opts;
|
||||||
struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts;
|
struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts;
|
||||||
bool no_ass = !opts->ass_enabled || shared_opts->ass_style_override[sd->order] == 5;
|
bool no_ass = !opts->ass_enabled || shared_opts->ass_style_override[sd->order] == 5;
|
||||||
bool converted = ctx->is_converted || no_ass;
|
bool converted = (ctx->is_converted && !lavc_conv_is_styled(ctx->converter)) || no_ass;
|
||||||
ASS_Track *track = no_ass ? ctx->shadow_track : ctx->ass_track;
|
ASS_Track *track = no_ass ? ctx->shadow_track : ctx->ass_track;
|
||||||
ASS_Renderer *renderer = ctx->ass_renderer;
|
ASS_Renderer *renderer = ctx->ass_renderer;
|
||||||
struct sub_bitmaps *res = &(struct sub_bitmaps){0};
|
struct sub_bitmaps *res = &(struct sub_bitmaps){0};
|
||||||
|
@ -709,30 +709,23 @@ done:
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct buf {
|
#define MAX_BUF_SIZE 1024 * 1024
|
||||||
char *start;
|
#define MIN_EXPAND_SIZE 4096
|
||||||
int size;
|
|
||||||
int len;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void append(struct buf *b, char c)
|
static void append(bstr *b, char c)
|
||||||
{
|
{
|
||||||
if (b->len < b->size) {
|
bstr_xappend(NULL, b, (bstr){&c, 1});
|
||||||
b->start[b->len] = c;
|
|
||||||
b->len++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ass_to_plaintext(struct buf *b, const char *in)
|
static void ass_to_plaintext(bstr *b, const char *in)
|
||||||
{
|
{
|
||||||
bool in_tag = false;
|
|
||||||
const char *open_tag_pos = NULL;
|
const char *open_tag_pos = NULL;
|
||||||
bool in_drawing = false;
|
bool in_drawing = false;
|
||||||
while (*in) {
|
while (*in) {
|
||||||
if (in_tag) {
|
if (open_tag_pos) {
|
||||||
if (in[0] == '}') {
|
if (in[0] == '}') {
|
||||||
in += 1;
|
in += 1;
|
||||||
in_tag = false;
|
open_tag_pos = NULL;
|
||||||
} else if (in[0] == '\\' && in[1] == 'p' && in[2] != 'o') {
|
} else if (in[0] == '\\' && in[1] == 'p' && in[2] != 'o') {
|
||||||
in += 2;
|
in += 2;
|
||||||
// Skip text between \pN and \p0 tags. A \p without a number
|
// Skip text between \pN and \p0 tags. A \p without a number
|
||||||
|
@ -756,7 +749,6 @@ static void ass_to_plaintext(struct buf *b, const char *in)
|
||||||
} else if (in[0] == '{') {
|
} else if (in[0] == '{') {
|
||||||
open_tag_pos = in;
|
open_tag_pos = in;
|
||||||
in += 1;
|
in += 1;
|
||||||
in_tag = true;
|
|
||||||
} else {
|
} else {
|
||||||
if (!in_drawing)
|
if (!in_drawing)
|
||||||
append(b, in[0]);
|
append(b, in[0]);
|
||||||
|
@ -765,65 +757,86 @@ static void ass_to_plaintext(struct buf *b, const char *in)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// A '{' without a closing '}' is always visible.
|
// A '{' without a closing '}' is always visible.
|
||||||
if (in_tag) {
|
if (open_tag_pos) {
|
||||||
while (*open_tag_pos)
|
bstr_xappend(NULL, b, bstr0(open_tag_pos));
|
||||||
append(b, *open_tag_pos++);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty string counts as whitespace. Reads s[len-1] even if there are \0s.
|
// Empty string counts as whitespace.
|
||||||
static bool is_whitespace_only(char *s, int len)
|
static bool is_whitespace_only(bstr b)
|
||||||
{
|
{
|
||||||
for (int n = 0; n < len; n++) {
|
for (int n = 0; n < b.len; n++) {
|
||||||
if (s[n] != ' ' && s[n] != '\t')
|
if (b.start[n] != ' ' && b.start[n] != '\t')
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *get_text_buf(struct sd *sd, double pts, enum sd_text_type type)
|
static bstr get_text_buf(struct sd *sd, double pts, enum sd_text_type type)
|
||||||
{
|
{
|
||||||
struct sd_ass_priv *ctx = sd->priv;
|
struct sd_ass_priv *ctx = sd->priv;
|
||||||
ASS_Track *track = ctx->ass_track;
|
ASS_Track *track = ctx->ass_track;
|
||||||
|
|
||||||
if (pts == MP_NOPTS_VALUE)
|
if (pts == MP_NOPTS_VALUE)
|
||||||
return NULL;
|
return (bstr){0};
|
||||||
long long ipts = find_timestamp(sd, pts);
|
long long ipts = find_timestamp(sd, pts);
|
||||||
|
|
||||||
struct buf b = {ctx->last_text, sizeof(ctx->last_text) - 1};
|
bstr *b = &ctx->last_text;
|
||||||
|
|
||||||
|
if (!b->start)
|
||||||
|
b->start = talloc_size(ctx, 4096);
|
||||||
|
|
||||||
|
b->len = 0;
|
||||||
|
|
||||||
for (int i = 0; i < track->n_events; ++i) {
|
for (int i = 0; i < track->n_events; ++i) {
|
||||||
ASS_Event *event = track->events + i;
|
ASS_Event *event = track->events + i;
|
||||||
if (ipts >= event->Start && ipts < event->Start + event->Duration) {
|
if (ipts >= event->Start && ipts < event->Start + event->Duration) {
|
||||||
if (event->Text) {
|
if (event->Text) {
|
||||||
int start = b.len;
|
int start = b->len;
|
||||||
if (type == SD_TEXT_TYPE_PLAIN) {
|
if (type == SD_TEXT_TYPE_PLAIN) {
|
||||||
ass_to_plaintext(&b, event->Text);
|
ass_to_plaintext(b, event->Text);
|
||||||
|
} else if (type == SD_TEXT_TYPE_ASS_FULL) {
|
||||||
|
long long s = event->Start;
|
||||||
|
long long e = s + event->Duration;
|
||||||
|
|
||||||
|
ASS_Style *style = (event->Style < 0 || event->Style >= track->n_styles) ? NULL : &track->styles[event->Style];
|
||||||
|
|
||||||
|
int sh = (s / 60 / 60 / 1000);
|
||||||
|
int sm = (s / 60 / 1000) % 60;
|
||||||
|
int ss = (s / 1000) % 60;
|
||||||
|
int sc = (s / 10) % 100;
|
||||||
|
int eh = (e / 60 / 60 / 1000);
|
||||||
|
int em = (e / 60 / 1000) % 60;
|
||||||
|
int es = (e / 1000) % 60;
|
||||||
|
int ec = (e / 10) % 100;
|
||||||
|
|
||||||
|
bstr_xappend_asprintf(NULL, b, "Dialogue: %d,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s,%s,%04d,%04d,%04d,%s,%s",
|
||||||
|
event->Layer,
|
||||||
|
sh, sm, ss, sc,
|
||||||
|
eh, em, es, ec,
|
||||||
|
(style && style->Name) ? style->Name : "", event->Name,
|
||||||
|
event->MarginL, event->MarginR, event->MarginV,
|
||||||
|
event->Effect, event->Text);
|
||||||
} else {
|
} else {
|
||||||
char *t = event->Text;
|
bstr_xappend(NULL, b, bstr0(event->Text));
|
||||||
while (*t)
|
|
||||||
append(&b, *t++);
|
|
||||||
}
|
}
|
||||||
if (is_whitespace_only(&b.start[start], b.len - start)) {
|
if (is_whitespace_only(bstr_cut(*b, start))) {
|
||||||
b.len = start;
|
b->len = start;
|
||||||
} else {
|
} else {
|
||||||
append(&b, '\n');
|
append(b, '\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b.start[b.len] = '\0';
|
bstr_eatend(b, (bstr)bstr0_lit("\n"));
|
||||||
|
|
||||||
if (b.len > 0 && b.start[b.len - 1] == '\n')
|
return *b;
|
||||||
b.start[b.len - 1] = '\0';
|
|
||||||
|
|
||||||
return ctx->last_text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *get_text(struct sd *sd, double pts, enum sd_text_type type)
|
static char *get_text(struct sd *sd, double pts, enum sd_text_type type)
|
||||||
{
|
{
|
||||||
return talloc_strdup(NULL, get_text_buf(sd, pts, type));
|
return bstrto0(NULL, get_text_buf(sd, pts, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct sd_times get_times(struct sd *sd, double pts)
|
static struct sd_times get_times(struct sd *sd, double pts)
|
||||||
|
@ -862,20 +875,26 @@ static void fill_plaintext(struct sd *sd, double pts)
|
||||||
|
|
||||||
ass_flush_events(track);
|
ass_flush_events(track);
|
||||||
|
|
||||||
char *text = get_text_buf(sd, pts, SD_TEXT_TYPE_PLAIN);
|
bstr text = get_text_buf(sd, pts, SD_TEXT_TYPE_PLAIN);
|
||||||
if (!text)
|
if (!text.len)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bstr dst = {0};
|
bstr dst = {0};
|
||||||
|
|
||||||
while (*text) {
|
while (text.len) {
|
||||||
if (*text == '{')
|
if (*text.start == '{') {
|
||||||
|
bstr_xappend(NULL, &dst, bstr0("\\{"));
|
||||||
|
text = bstr_cut(text, 1);
|
||||||
|
} else if (*text.start == '\\') {
|
||||||
bstr_xappend(NULL, &dst, bstr0("\\"));
|
bstr_xappend(NULL, &dst, bstr0("\\"));
|
||||||
bstr_xappend(NULL, &dst, (bstr){text, 1});
|
// Break ASS escapes with U+2060 WORD JOINER
|
||||||
// Break ASS escapes with U+2060 WORD JOINER
|
|
||||||
if (*text == '\\')
|
|
||||||
mp_append_utf8_bstr(NULL, &dst, 0x2060);
|
mp_append_utf8_bstr(NULL, &dst, 0x2060);
|
||||||
text++;
|
text = bstr_cut(text, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = bstrcspn(text, "{\\");
|
||||||
|
bstr_xappend(NULL, &dst, (bstr){text.start, i});
|
||||||
|
text = bstr_cut(text, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dst.start)
|
if (!dst.start)
|
||||||
|
@ -1103,11 +1122,10 @@ bstr sd_ass_pkt_text(struct sd_filter *ft, struct demux_packet *pkt, int offset)
|
||||||
return txt;
|
return txt;
|
||||||
}
|
}
|
||||||
|
|
||||||
bstr sd_ass_to_plaintext(char *out, size_t out_siz, const char *in)
|
bstr sd_ass_to_plaintext(char **out, const char *in)
|
||||||
{
|
{
|
||||||
struct buf b = {out, out_siz, 0};
|
bstr b = {*out};
|
||||||
ass_to_plaintext(&b, in);
|
ass_to_plaintext(&b, in);
|
||||||
if (b.len < out_siz)
|
*out = b.start;
|
||||||
out[b.len] = 0;
|
return b;
|
||||||
return (bstr){out, b.len};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,95 +24,49 @@
|
||||||
#include "options/options.h"
|
#include "options/options.h"
|
||||||
#include "video/out/aspect.h"
|
#include "video/out/aspect.h"
|
||||||
#include "video/out/gpu/video.h"
|
#include "video/out/gpu/video.h"
|
||||||
#include "video/out/opengl/egl_helpers.h"
|
#include "video/filter/vf_gpu.h"
|
||||||
#include "video/out/opengl/ra_gl.h"
|
|
||||||
|
|
||||||
struct offscreen_ctx {
|
extern const struct offscreen_context offscreen_vk;
|
||||||
struct mp_log *log;
|
extern const struct offscreen_context offscreen_egl;
|
||||||
struct ra *ra;
|
|
||||||
void *priv;
|
|
||||||
|
|
||||||
void (*set_context)(struct offscreen_ctx *ctx, bool enable);
|
static const struct offscreen_context *contexts[] = {
|
||||||
|
#if HAVE_EGL
|
||||||
|
&offscreen_egl,
|
||||||
|
#endif
|
||||||
|
#if HAVE_VULKAN
|
||||||
|
&offscreen_vk,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct gl_offscreen_ctx {
|
static inline OPT_STRING_VALIDATE_FUNC(offscreen_ctx_validate_api)
|
||||||
GL gl;
|
|
||||||
EGLDisplay egl_display;
|
|
||||||
EGLContext egl_context;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void gl_ctx_destroy(void *p)
|
|
||||||
{
|
{
|
||||||
struct offscreen_ctx *ctx = p;
|
struct bstr param = bstr0(*value);
|
||||||
struct gl_offscreen_ctx *gl = ctx->priv;
|
for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) {
|
||||||
|
if (bstr_equals0(param, contexts[i]->api))
|
||||||
ra_free(&ctx->ra);
|
return 1;
|
||||||
|
}
|
||||||
if (gl->egl_context)
|
return M_OPT_INVALID;
|
||||||
eglDestroyContext(gl->egl_display, gl->egl_context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gl_ctx_set_context(struct offscreen_ctx *ctx, bool enable)
|
static int offscreen_ctx_api_help(struct mp_log *log, const struct m_option *opt,
|
||||||
|
struct bstr name)
|
||||||
{
|
{
|
||||||
struct gl_offscreen_ctx *gl = ctx->priv;
|
mp_info(log, "GPU APIs (offscreen contexts):\n");
|
||||||
EGLContext c = enable ? gl->egl_context : EGL_NO_CONTEXT;
|
for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++)
|
||||||
|
mp_info(log, " %s\n", contexts[i]->api);
|
||||||
if (!eglMakeCurrent(gl->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, c))
|
return M_OPT_EXIT;
|
||||||
MP_ERR(ctx, "Could not make EGL context current.\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct offscreen_ctx *gl_offscreen_ctx_create(struct mpv_global *global,
|
static struct offscreen_ctx *offscreen_ctx_create(struct mpv_global *global,
|
||||||
struct mp_log *log)
|
struct mp_log *log,
|
||||||
|
const char *api)
|
||||||
{
|
{
|
||||||
struct offscreen_ctx *ctx = talloc_zero(NULL, struct offscreen_ctx);
|
for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) {
|
||||||
struct gl_offscreen_ctx *gl = talloc_zero(ctx, struct gl_offscreen_ctx);
|
if (api && strcmp(contexts[i]->api, api) != 0)
|
||||||
talloc_set_destructor(ctx, gl_ctx_destroy);
|
continue;
|
||||||
*ctx = (struct offscreen_ctx){
|
mp_info(log, "Creating offscreen GPU context '%s'\n", contexts[i]->api);
|
||||||
.log = log,
|
return contexts[i]->offscreen_ctx_create(global, log);
|
||||||
.priv = gl,
|
|
||||||
.set_context = gl_ctx_set_context,
|
|
||||||
};
|
|
||||||
|
|
||||||
// This appears to work with Mesa. EGL 1.5 doesn't specify what a "default
|
|
||||||
// display" is at all.
|
|
||||||
gl->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
||||||
if (!eglInitialize(gl->egl_display, NULL, NULL)) {
|
|
||||||
MP_ERR(ctx, "Could not initialize EGL.\n");
|
|
||||||
goto error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unfortunately, mpegl_create_context() is entangled with ra_ctx.
|
|
||||||
// Fortunately, it does not need much, and we can provide a stub.
|
|
||||||
struct ra_ctx ractx = {
|
|
||||||
.log = ctx->log,
|
|
||||||
.global = global,
|
|
||||||
};
|
|
||||||
EGLConfig config;
|
|
||||||
if (!mpegl_create_context(&ractx, gl->egl_display, &gl->egl_context, &config))
|
|
||||||
{
|
|
||||||
MP_ERR(ctx, "Could not create EGL context.\n");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!eglMakeCurrent(gl->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
|
||||||
gl->egl_context))
|
|
||||||
{
|
|
||||||
MP_ERR(ctx, "Could not make EGL context current.\n");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
mpegl_load_functions(&gl->gl, ctx->log);
|
|
||||||
ctx->ra = ra_create_gl(&gl->gl, ctx->log);
|
|
||||||
|
|
||||||
if (!ctx->ra)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
gl_ctx_set_context(ctx, false);
|
|
||||||
|
|
||||||
return ctx;
|
|
||||||
|
|
||||||
error:
|
|
||||||
talloc_free(ctx);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +78,7 @@ static void offscreen_ctx_set_current(struct offscreen_ctx *ctx, bool enable)
|
||||||
|
|
||||||
struct gpu_opts {
|
struct gpu_opts {
|
||||||
int w, h;
|
int w, h;
|
||||||
|
char *api;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct priv {
|
struct priv {
|
||||||
|
@ -327,7 +282,7 @@ static struct mp_filter *gpu_create(struct mp_filter *parent, void *options)
|
||||||
priv->vo_opts_cache = m_config_cache_alloc(f, f->global, &vo_sub_opts);
|
priv->vo_opts_cache = m_config_cache_alloc(f, f->global, &vo_sub_opts);
|
||||||
priv->vo_opts = priv->vo_opts_cache->opts;
|
priv->vo_opts = priv->vo_opts_cache->opts;
|
||||||
|
|
||||||
priv->ctx = gl_offscreen_ctx_create(f->global, f->log);
|
priv->ctx = offscreen_ctx_create(f->global, f->log, priv->opts->api);
|
||||||
if (!priv->ctx) {
|
if (!priv->ctx) {
|
||||||
MP_FATAL(f, "Could not create offscreen ra context.\n");
|
MP_FATAL(f, "Could not create offscreen ra context.\n");
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -368,6 +323,8 @@ const struct mp_user_filter_entry vf_gpu = {
|
||||||
.options = (const struct m_option[]){
|
.options = (const struct m_option[]){
|
||||||
{"w", OPT_INT(w)},
|
{"w", OPT_INT(w)},
|
||||||
{"h", OPT_INT(h)},
|
{"h", OPT_INT(h)},
|
||||||
|
{"api", OPT_STRING_VALIDATE(api, offscreen_ctx_validate_api),
|
||||||
|
.help = offscreen_ctx_api_help},
|
||||||
{0}
|
{0}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* This file is part of mpv.
|
||||||
|
*
|
||||||
|
* mpv is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common.h"
|
||||||
|
#include "common/global.h"
|
||||||
|
|
||||||
|
struct offscreen_ctx {
|
||||||
|
struct mp_log *log;
|
||||||
|
struct ra *ra;
|
||||||
|
void *priv;
|
||||||
|
|
||||||
|
void (*set_context)(struct offscreen_ctx *ctx, bool enable);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct offscreen_context {
|
||||||
|
const char *api;
|
||||||
|
struct offscreen_ctx *(*offscreen_ctx_create)(struct mpv_global *,
|
||||||
|
struct mp_log *);
|
||||||
|
};
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* This file is part of mpv.
|
||||||
|
*
|
||||||
|
* mpv is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common/common.h"
|
||||||
|
#include "video/filter/vf_gpu.h"
|
||||||
|
#include "video/out/opengl/egl_helpers.h"
|
||||||
|
#include "video/out/opengl/ra_gl.h"
|
||||||
|
|
||||||
|
struct gl_offscreen_ctx {
|
||||||
|
GL gl;
|
||||||
|
EGLDisplay egl_display;
|
||||||
|
EGLContext egl_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void gl_ctx_destroy(void *p)
|
||||||
|
{
|
||||||
|
struct offscreen_ctx *ctx = p;
|
||||||
|
struct gl_offscreen_ctx *gl = ctx->priv;
|
||||||
|
|
||||||
|
ra_free(&ctx->ra);
|
||||||
|
|
||||||
|
if (gl->egl_context)
|
||||||
|
eglDestroyContext(gl->egl_display, gl->egl_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gl_ctx_set_context(struct offscreen_ctx *ctx, bool enable)
|
||||||
|
{
|
||||||
|
struct gl_offscreen_ctx *gl = ctx->priv;
|
||||||
|
EGLContext c = enable ? gl->egl_context : EGL_NO_CONTEXT;
|
||||||
|
|
||||||
|
if (!eglMakeCurrent(gl->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, c))
|
||||||
|
MP_ERR(ctx, "Could not make EGL context current.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct offscreen_ctx *gl_offscreen_ctx_create(struct mpv_global *global,
|
||||||
|
struct mp_log *log)
|
||||||
|
{
|
||||||
|
struct offscreen_ctx *ctx = talloc(NULL, struct offscreen_ctx);
|
||||||
|
struct gl_offscreen_ctx *gl = talloc_zero(ctx, struct gl_offscreen_ctx);
|
||||||
|
talloc_set_destructor(ctx, gl_ctx_destroy);
|
||||||
|
*ctx = (struct offscreen_ctx){
|
||||||
|
.log = log,
|
||||||
|
.priv = gl,
|
||||||
|
.set_context = gl_ctx_set_context,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This appears to work with Mesa. EGL 1.5 doesn't specify what a "default
|
||||||
|
// display" is at all.
|
||||||
|
gl->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||||
|
if (!eglInitialize(gl->egl_display, NULL, NULL)) {
|
||||||
|
MP_ERR(ctx, "Could not initialize EGL.\n");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unfortunately, mpegl_create_context() is entangled with ra_ctx.
|
||||||
|
// Fortunately, it does not need much, and we can provide a stub.
|
||||||
|
struct ra_ctx ractx = {
|
||||||
|
.log = ctx->log,
|
||||||
|
.global = global,
|
||||||
|
};
|
||||||
|
EGLConfig config;
|
||||||
|
if (!mpegl_create_context(&ractx, gl->egl_display, &gl->egl_context, &config))
|
||||||
|
{
|
||||||
|
MP_ERR(ctx, "Could not create EGL context.\n");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eglMakeCurrent(gl->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||||
|
gl->egl_context))
|
||||||
|
{
|
||||||
|
MP_ERR(ctx, "Could not make EGL context current.\n");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
mpegl_load_functions(&gl->gl, ctx->log);
|
||||||
|
ctx->ra = ra_create_gl(&gl->gl, ctx->log);
|
||||||
|
|
||||||
|
if (!ctx->ra)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
gl_ctx_set_context(ctx, false);
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
|
||||||
|
error:
|
||||||
|
talloc_free(ctx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct offscreen_context offscreen_egl = {
|
||||||
|
.api = "egl",
|
||||||
|
.offscreen_ctx_create = gl_offscreen_ctx_create
|
||||||
|
};
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* This file is part of mpv.
|
||||||
|
*
|
||||||
|
* mpv is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "options/m_config.h"
|
||||||
|
#include "video/filter/vf_gpu.h"
|
||||||
|
#include "video/out/placebo/ra_pl.h"
|
||||||
|
#include "video/out/placebo/utils.h"
|
||||||
|
#include "video/out/vulkan/context.h"
|
||||||
|
#include "video/out/vulkan/utils.h"
|
||||||
|
|
||||||
|
struct vk_offscreen_ctx {
|
||||||
|
struct ra_ctx *ractx;
|
||||||
|
struct mpvk_ctx *vk;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const struct m_sub_options vulkan_conf;
|
||||||
|
|
||||||
|
static void vk_ctx_destroy(void *p)
|
||||||
|
{
|
||||||
|
struct offscreen_ctx *ctx = p;
|
||||||
|
struct vk_offscreen_ctx *vkctx = ctx->priv;
|
||||||
|
struct ra_ctx *ractx = vkctx->ractx;
|
||||||
|
struct mpvk_ctx *vk = vkctx->vk;
|
||||||
|
|
||||||
|
if (ractx->ra) {
|
||||||
|
pl_gpu_finish(vk->gpu);
|
||||||
|
ractx->ra->fns->destroy(ctx->ra);
|
||||||
|
ractx->ra = NULL;
|
||||||
|
ctx->ra = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk->gpu = NULL;
|
||||||
|
pl_vulkan_destroy(&vk->vulkan);
|
||||||
|
mpvk_uninit(vk);
|
||||||
|
talloc_free(vk);
|
||||||
|
talloc_free(ractx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct offscreen_ctx *vk_offscreen_ctx_create(struct mpv_global *global,
|
||||||
|
struct mp_log *log)
|
||||||
|
{
|
||||||
|
struct offscreen_ctx *ctx = talloc(NULL, struct offscreen_ctx);
|
||||||
|
talloc_set_destructor(ctx, vk_ctx_destroy);
|
||||||
|
*ctx = (struct offscreen_ctx){
|
||||||
|
.log = log,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ra_ctx *ractx = talloc_zero(ctx, struct ra_ctx);
|
||||||
|
struct mpvk_ctx *vk = talloc_zero(ctx, struct mpvk_ctx);
|
||||||
|
ractx->log = ctx->log;
|
||||||
|
ractx->global = global;
|
||||||
|
|
||||||
|
vk->pllog = mppl_log_create(ctx, log);
|
||||||
|
if (!vk->pllog)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
struct pl_vk_inst_params pl_vk_params = {0};
|
||||||
|
struct ra_ctx_opts *ctx_opts = mp_get_config_group(NULL, global, &ra_ctx_conf);
|
||||||
|
pl_vk_params.debug = ctx_opts->debug;
|
||||||
|
talloc_free(ctx_opts);
|
||||||
|
mppl_log_set_probing(vk->pllog, true);
|
||||||
|
vk->vkinst = pl_vk_inst_create(vk->pllog, &pl_vk_params);
|
||||||
|
mppl_log_set_probing(vk->pllog, false);
|
||||||
|
if (!vk->vkinst)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
struct vulkan_opts *vk_opts = mp_get_config_group(NULL, global, &vulkan_conf);
|
||||||
|
vk->vulkan = mppl_create_vulkan(vk_opts, vk->vkinst, vk->pllog, NULL);
|
||||||
|
talloc_free(vk_opts);
|
||||||
|
if (!vk->vulkan)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
vk->gpu = vk->vulkan->gpu;
|
||||||
|
ractx->ra = ra_create_pl(vk->gpu, ractx->log);
|
||||||
|
if (!ractx->ra)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
struct vk_offscreen_ctx *vkctx = talloc(ctx, struct vk_offscreen_ctx);
|
||||||
|
*vkctx = (struct vk_offscreen_ctx){
|
||||||
|
.ractx = ractx,
|
||||||
|
.vk = vk
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx->ra = ractx->ra;
|
||||||
|
ctx->priv = vkctx;
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
|
||||||
|
error:
|
||||||
|
pl_vulkan_destroy(&vk->vulkan);
|
||||||
|
mpvk_uninit(vk);
|
||||||
|
talloc_free(vk);
|
||||||
|
talloc_free(ractx);
|
||||||
|
talloc_free(ctx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct offscreen_context offscreen_vk = {
|
||||||
|
.api = "vulkan",
|
||||||
|
.offscreen_ctx_create = vk_offscreen_ctx_create
|
||||||
|
};
|
|
@ -28,7 +28,6 @@
|
||||||
#include "video/out/placebo/utils.h"
|
#include "video/out/placebo/utils.h"
|
||||||
|
|
||||||
#include "context.h"
|
#include "context.h"
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
struct vulkan_opts {
|
struct vulkan_opts {
|
||||||
char *device; // force a specific GPU
|
char *device; // force a specific GPU
|
||||||
|
@ -174,19 +173,11 @@ void ra_vk_ctx_uninit(struct ra_ctx *ctx)
|
||||||
TA_FREEP(&ctx->swapchain);
|
TA_FREEP(&ctx->swapchain);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
|
pl_vulkan mppl_create_vulkan(struct vulkan_opts *opts,
|
||||||
struct ra_vk_ctx_params params,
|
pl_vk_inst vkinst,
|
||||||
VkPresentModeKHR preferred_mode)
|
pl_log pllog,
|
||||||
|
VkSurfaceKHR surface)
|
||||||
{
|
{
|
||||||
struct ra_swapchain *sw = ctx->swapchain = talloc_zero(NULL, struct ra_swapchain);
|
|
||||||
sw->ctx = ctx;
|
|
||||||
sw->fns = &vulkan_swapchain;
|
|
||||||
|
|
||||||
struct priv *p = sw->priv = talloc_zero(sw, struct priv);
|
|
||||||
p->vk = vk;
|
|
||||||
p->params = params;
|
|
||||||
p->opts = mp_get_config_group(p, ctx->global, &vulkan_conf);
|
|
||||||
|
|
||||||
VkPhysicalDeviceFeatures2 features = {
|
VkPhysicalDeviceFeatures2 features = {
|
||||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||||||
};
|
};
|
||||||
|
@ -224,30 +215,47 @@ bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
AVUUID param_uuid = { 0 };
|
AVUUID param_uuid = { 0 };
|
||||||
bool is_uuid = p->opts->device &&
|
bool is_uuid = opts->device &&
|
||||||
av_uuid_parse(p->opts->device, param_uuid) == 0;
|
av_uuid_parse(opts->device, param_uuid) == 0;
|
||||||
|
|
||||||
assert(vk->pllog);
|
assert(pllog);
|
||||||
assert(vk->vkinst);
|
assert(vkinst);
|
||||||
struct pl_vulkan_params device_params = {
|
struct pl_vulkan_params device_params = {
|
||||||
.instance = vk->vkinst->instance,
|
.instance = vkinst->instance,
|
||||||
.get_proc_addr = vk->vkinst->get_proc_addr,
|
.get_proc_addr = vkinst->get_proc_addr,
|
||||||
.surface = vk->surface,
|
.surface = surface,
|
||||||
.async_transfer = p->opts->async_transfer,
|
.async_transfer = opts->async_transfer,
|
||||||
.async_compute = p->opts->async_compute,
|
.async_compute = opts->async_compute,
|
||||||
.queue_count = p->opts->queue_count,
|
.queue_count = opts->queue_count,
|
||||||
#if HAVE_VULKAN_INTEROP
|
#if HAVE_VULKAN_INTEROP
|
||||||
.extra_queues = VK_QUEUE_VIDEO_DECODE_BIT_KHR,
|
.extra_queues = VK_QUEUE_VIDEO_DECODE_BIT_KHR,
|
||||||
.opt_extensions = opt_extensions,
|
.opt_extensions = opt_extensions,
|
||||||
.num_opt_extensions = MP_ARRAY_SIZE(opt_extensions),
|
.num_opt_extensions = MP_ARRAY_SIZE(opt_extensions),
|
||||||
#endif
|
#endif
|
||||||
.features = &features,
|
.features = &features,
|
||||||
.device_name = is_uuid ? NULL : p->opts->device,
|
.device_name = is_uuid ? NULL : opts->device,
|
||||||
};
|
};
|
||||||
if (is_uuid)
|
if (is_uuid)
|
||||||
av_uuid_copy(device_params.device_uuid, param_uuid);
|
av_uuid_copy(device_params.device_uuid, param_uuid);
|
||||||
|
|
||||||
vk->vulkan = pl_vulkan_create(vk->pllog, &device_params);
|
return pl_vulkan_create(pllog, &device_params);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
|
||||||
|
struct ra_vk_ctx_params params,
|
||||||
|
VkPresentModeKHR preferred_mode)
|
||||||
|
{
|
||||||
|
struct ra_swapchain *sw = ctx->swapchain = talloc_zero(NULL, struct ra_swapchain);
|
||||||
|
sw->ctx = ctx;
|
||||||
|
sw->fns = &vulkan_swapchain;
|
||||||
|
|
||||||
|
struct priv *p = sw->priv = talloc_zero(sw, struct priv);
|
||||||
|
p->vk = vk;
|
||||||
|
p->params = params;
|
||||||
|
p->opts = mp_get_config_group(p, ctx->global, &vulkan_conf);
|
||||||
|
|
||||||
|
vk->vulkan = mppl_create_vulkan(p->opts, vk->vkinst, vk->pllog, vk->surface);
|
||||||
if (!vk->vulkan)
|
if (!vk->vulkan)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,12 @@ bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
|
||||||
struct ra_vk_ctx_params params,
|
struct ra_vk_ctx_params params,
|
||||||
VkPresentModeKHR preferred_mode);
|
VkPresentModeKHR preferred_mode);
|
||||||
|
|
||||||
|
// Helper for initializing mpvk_ctx->vulkan
|
||||||
|
pl_vulkan mppl_create_vulkan(struct vulkan_opts *opts,
|
||||||
|
pl_vk_inst vkinst,
|
||||||
|
pl_log pllog,
|
||||||
|
VkSurfaceKHR surface);
|
||||||
|
|
||||||
// Handles a resize request, and updates ctx->vo->dwidth/dheight
|
// Handles a resize request, and updates ctx->vo->dwidth/dheight
|
||||||
bool ra_vk_ctx_resize(struct ra_ctx *ctx, int width, int height);
|
bool ra_vk_ctx_resize(struct ra_ctx *ctx, int width, int height);
|
||||||
|
|
||||||
|
|
|
@ -2182,11 +2182,17 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
|
||||||
} else if (changed_option == &vo_opts->cursor_passthrough) {
|
} else if (changed_option == &vo_opts->cursor_passthrough) {
|
||||||
update_cursor_passthrough(w32);
|
update_cursor_passthrough(w32);
|
||||||
} else if (changed_option == &vo_opts->border ||
|
} else if (changed_option == &vo_opts->border ||
|
||||||
changed_option == &vo_opts->title_bar ||
|
changed_option == &vo_opts->title_bar)
|
||||||
changed_option == &vo_opts->show_in_taskbar)
|
|
||||||
{
|
{
|
||||||
update_window_style(w32);
|
update_window_style(w32);
|
||||||
update_window_state(w32);
|
update_window_state(w32);
|
||||||
|
} else if (changed_option == &vo_opts->show_in_taskbar) {
|
||||||
|
// This hide and show is apparently required according to the documentation:
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/shell/taskbar#managing-taskbar-buttons
|
||||||
|
ShowWindow(w32->window, SW_HIDE);
|
||||||
|
update_window_style(w32);
|
||||||
|
ShowWindow(w32->window, SW_SHOW);
|
||||||
|
update_window_state(w32);
|
||||||
} else if (changed_option == &vo_opts->window_minimized) {
|
} else if (changed_option == &vo_opts->window_minimized) {
|
||||||
update_minimized_state(w32);
|
update_minimized_state(w32);
|
||||||
} else if (changed_option == &vo_opts->window_maximized) {
|
} else if (changed_option == &vo_opts->window_maximized) {
|
||||||
|
|
Loading…
Reference in New Issue