Compare commits

...

23 Commits

Author SHA1 Message Date
Mohammad AlSaleh f1a7a5864c
Merge 98a5bb8072 into 69d70148c7 2024-04-27 05:16:39 +02:00
Kacper Michajłow 69d70148c7 TOOLS/lua/autoload: make ignore_patterns more generic 2024-04-27 03:14:31 +02:00
oficsu 3ca71b0c0e TOOLS/lua/autoload: allow multiple ignore_patterns 2024-04-27 03:14:31 +02:00
oficsu 96b34148f1 TOOLS/lua/autoload: add ignore_pattern option
Autoload script can now exclude certain files

Why? Sometimes you want to ignore thumbnails,
auto-generated backups or just unwanted files

A non-exhaustive list of real-world examples:
- user backup files: '%.bak%.mp4$' or '^bak-'
- telegram-desktop thumbnails: '_thumb%.jpg$'
- a krita graphics editor backup suffix: '^~'

See documentation here: lua.org/pil/20.2.html
2024-04-27 03:14:31 +02:00
nanahi 93708a9d38 w32_common: fix show-in-taskbar toggling after explorer is restarted
After explorer is restarted while show-in-taskbar is false, toggling
show-in-taskbar no longer puts mpv back to the taskbar until it's
unfocused and refocused.

My guess of how this works is that the HWND of the taskbar is cached,
and setting the WS_EX_TOOLWINDOW style internally uses this value to
show/hide the taskbar button. But after explorer is restarted it no
longer works until its taskbar state needs to change (such as focusing).
Only then it realizes the HWND is no longer valid and refreshes it.

Fix this by following MS documentation on this: the window needs to be
hidden before changing the style, and be shown after that. This
unfortunately can sometimes introduce a brief window flash, but it
fixes the problem.
2024-04-27 03:02:00 +02:00
Guido Cella 2f4c550b4b zsh-completion: complete --gpu-context
This is made by possible by 96e1f1dfa5 standardizing --gpu-context's
help output. This changes the check to complete any Object settings list
so it will automatically work with future options of this kind.
2024-04-27 03:01:06 +02:00
Kacper Michajłow fbfc9d22c7 ci: add fuzzers build test 2024-04-27 02:47:47 +02:00
Kacper Michajłow 47dbc3a74e fuzzers: add new fuzzer targets
fuzzer_set_property.c:

fuzz mpv_set_property in both initialized and non-initialized state.
Useful for user provided values sanitization test. I've already seen
some memory leaks in parsing code, good to drill it.

fuzzer_loadfile.c:

mpv_command "loadfile" test. Good for testing demuxers, decoding and
playback loop. Sadly in headless mode we can't really test AO and VO,
but at least all the code around can be fuzzed. Especially our custom
demuxers like demux_mkv.

fuzzer_loadfile_direct.c:

Similar to loadfile above, but instead of saving the data to file, it
passes the fuzz input in the command. Generated protocol specific
versions (mf:// and memory:// for now) and generic one.

Nothing really complex, but good start and even those few targets should
give good coverage of the most common code paths in libmpv.
2024-04-27 02:47:47 +02:00
Kacper Michajłow 0b234af113 player/command: mark sub-text-ass as deprecated
Fixes: 437fff9f21
2024-04-27 01:23:52 +02:00
rcombs 99f1b2b7b4 player/command: add sub-text/ass-full sub-property
This is like sub-text/ass, but it returns a full ASS line, making it suitable for some more advanced scripting use-cases.
2024-04-27 01:19:56 +02:00
rcombs 437fff9f21 player/command: move sub-text-ass to a sub-property 2024-04-27 01:19:56 +02:00
rcombs aa0a9ce2ec sd_ass: allow get_text to return more than 500 bytes 2024-04-27 01:19:56 +02:00
Kacper Michajłow e3fd24496a stats.lua: show osd-dimensions property
This change displays the scaled position and size of the image before
cropping to the target rectangle. In simple terms, it shows how much
margin has been added to the image or how much of the image has been
cropped.

Note that target resolution is displayed after crop as in fact all other
pixels are discarded anyway.
2024-04-27 01:15:25 +02:00
Kacper Michajłow f55d19e846 sub/lavc_conv: don't override style of converted teletext pages
This fixes teletext pages rendering, while keeping the same default
style for subtitles and other converted formats.
2024-04-27 01:14:23 +02:00
Kacper Michajłow d8378dc226 sub/lavc_conv: don't strip ASS style header
This fixes converted subtitles that are styled.

This reverts commit 5e2c211a4e.

Most of the subtitle decoders in libavcodec sets meaningful style
values. For the rest we can conditionally strip style.
2024-04-27 01:14:23 +02:00
Shreesh Adiga 4aa7588e44 DOCS/vf: update vf_gpu options 2024-04-27 01:08:22 +02:00
Shreesh Adiga d9c5aef98d vf_gpu: vulkan and egl implementations
Abstract out EGL, and allow choosing between EGL and vulkan at runtime.
vf_gpu_egl.c contains GL specific context and creation/destroy code,
vf_gpu_vulkan.c contains Vulkan specific. This allows vf_gpu being
built in systems where EGL is not available and where Vulkan is
available.
2024-04-27 01:08:22 +02:00
ferreum 773c5e2ae0 af_scaletempo2: migrate to internals to talloc
Fixes corrupted audio after resize_input_buffer; realloc_2d did not move
data to new location. Rather than reimplementing more allocator logic,
migrate internals to use talloc and grow buffer with realloc.
2024-04-27 01:05:07 +02:00
ferreum 190b15c827 af_scaletempo2: remove redundant buffer zeroing
First iteration does not overlap with initial buffer contents any more,
so this zeroing was redundant.
2024-04-27 01:05:07 +02:00
nanahi 51e01e9772 ao_wasapi: fix player core lockup when avoiding premature buffer fills
6863eefc3d handled this situation by using
an atomic variable to express the state for which the wakeup is caused
by AO control, and the dispatch queue is only processed at this state.
However, this can cause permanent lockup of the player core when the
following happens:

- AO control sets the thread state to WASAPI_THREAD_DISPATCH, and
  sets the wakeup handle.
- WASAPI thread reads the WASAPI_THREAD_DISPATCH state and processes
  the dispatch queue.
- Another AO control happens. A dispatch item is enqueued, and the
  state stays at WASAPI_THREAD_DISPATCH.
- WASAPI thread resets the thread state to WASAPI_THREAD_FEED since
  the state has not changed.
- WaitForSingleObject() returns in the WASAPI thread, sees this state,
  and does not process the dispatch queue.
- The player core locks permanently because it is waiting for the dispatch
  to be processed.

This has been experimentally verified on a system under high contention:
The easiest way to trigger this lockup is to continuously hold down "i",
which rapidly issues AO get volume/mute controls.

To properly handle this, use separate handles for system and user wakeup
requests. Only feed audio when woke up by system and only process the
dispatch queue when woke up by user.

Fixes: 6863eefc3d
2024-04-27 00:59:09 +02:00
nanahi 7f0961479a Revert "ao_wasapi: address premature buffer fills in exclusive mode"
This reverts commit 6863eefc3d.
2024-04-27 00:59:09 +02:00
Dudemanguy 76367dae35 m_config_core: fix forced option notification with m_config_cache
bc28f7693d originally added this, but the
implementation isn't correct and causes excessive notifications when
writing to other options which may have bad behavior in some some
circumstances. Fix this by reworking the implementation for force
options so that the timestamps of the option update compared instead.
Whenever an option gets changed, the internal timestamp in the cache is
always incremented. For a special force option, we can save this
timestamp internally as well.

Because cache_check_update is always checked before potentially sending
an option notification, we know that if the internal timestamp is equal
to the timestamp saved by a force update option, it must have been
previously written. Thus, the notification can be sent. This lets
options like geometry work repeatedly but without constantly sending
notifications. Fixes #13954.
2024-04-26 17:42:52 +00:00
Mohammad AlSaleh 98a5bb8072 stream: don't try to read from all-sparse/no-data files
```
 dd if=/dev/zero of=/tmp/10g.empty bs=1 seek=10G count=0
 dd if=/dev/zero of=/tmp/10m.empty bs=1 seek=10M count=0
 time mpv /tmp/10{g,m}.empty
 ```

 I keep files with the name format `${name}-${hash}.${ext}.empty`
 around, where the original is removed, and a sparse file with
 the size of the original is created instead.

 A lot of time is wasted on such files when going through
 playlists/directories that include some of them.

 This admittedly may not be that common of a use-case.

Signed-off-by: Mohammad AlSaleh <CE.Mohammad.AlSaleh@gmail.com>
2024-04-06 04:27:28 +03:00
37 changed files with 1029 additions and 326 deletions

View File

@ -205,6 +205,30 @@ jobs:
run: |
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:
runs-on: ubuntu-22.04
container:

View File

@ -0,0 +1 @@
add `sub-text/ass-full` sub-property

View File

@ -0,0 +1 @@
deprecate `sub-text-ass` property; add `sub-text/ass` sub-property

View File

@ -2823,19 +2823,27 @@ Property list
stripped. If the subtitle is not text-based (i.e. DVD/BD subtitles), an
empty string is returned.
``sub-text-ass``
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 has sub-properties for different formats:
This property is not enough to render ASS subtitles correctly, because ASS
header and per-event metadata are not returned. You likely need to do
further filtering on the returned string to make it useful.
``sub-text/ass``
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
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``
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``
The current subtitle start time (in seconds). If there's multiple current

View File

@ -765,12 +765,22 @@ Available mpv-only filters are:
read information from this filter instead.
``gpu=...``
Convert video to RGB using the OpenGL renderer normally used with
``--vo=gpu``. This requires that the EGL implementation supports off-screen
rendering on the default display. (This is the case with Mesa.)
Convert video to RGB using the Vulkan or OpenGL renderer normally used with
``--vo=gpu``. In case of OpenGL, this requires that the EGL implementation
supports off-screen rendering on the default display. (This is the case with
Mesa.)
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>``
Size of the output in pixels (default: 0). If not positive, this will
use the size of the first filtered input frame.

View File

@ -10,6 +10,12 @@
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/).
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:
disabled=no
@ -22,6 +28,7 @@ additional_audio_exts=list,of,ext
ignore_hidden=yes
same_type=yes
directory_mode=recursive
ignore_patterns=^~,^bak-,%.bak$
--]]
@ -42,7 +49,8 @@ o = {
additional_audio_exts = "",
ignore_hidden = true,
same_type = false,
directory_mode = "auto"
directory_mode = "auto",
ignore_patterns = ""
}
options.read_options(o, nil, function(list)
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
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 = {}
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
end
@ -95,6 +139,11 @@ function split_option_exts(video, audio, image)
end
split_option_exts(true, true, true)
function split_patterns()
o.ignore_patterns = Split(o.ignore_patterns)
end
split_patterns()
function create_extensions()
EXTENSIONS = {}
EXTENSIONS_VIDEO = {}
@ -139,6 +188,16 @@ function get_extension(path)
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)
for i = #t, 1, -1 do
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)
-- The current file could be a hidden file, ignoring it doesn't load other
-- 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
end
if not current and is_ignored(v) then
return false
end
local ext = get_extension(v)
if ext == nil then
return false

View File

@ -8,7 +8,7 @@
#include "options/m_option.h"
struct priv {
struct mp_scaletempo2 data;
struct mp_scaletempo2 *data;
struct mp_pin *in_pin;
struct mp_aframe *cur_format;
struct mp_aframe_pool *out_pool;
@ -29,7 +29,7 @@ static void af_scaletempo2_process(struct mp_filter *f)
return;
while (!p->initialized || !p->pending ||
!mp_scaletempo2_frames_available(&p->data, p->speed))
!mp_scaletempo2_frames_available(p->data, p->speed))
{
bool eof = false;
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) {
int frame_size = mp_aframe_get_size(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);
mp_aframe_skip_samples(p->pending, read);
}
if (final && p->pending && !p->sent_final) {
mp_scaletempo2_set_final(&p->data);
mp_scaletempo2_set_final(p->data);
p->sent_final = true;
}
if (mp_scaletempo2_frames_available(&p->data, p->speed)) {
if (mp_scaletempo2_frames_available(p->data, p->speed)) {
if (eof) {
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);
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);
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) {
talloc_free(out);
goto error;
@ -101,14 +101,14 @@ static void af_scaletempo2_process(struct mp_filter *f)
uint8_t **planes = mp_aframe_get_data_rw(out);
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);
double pts = mp_aframe_get_pts(p->pending);
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;
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
// 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;
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));
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)
{
struct priv *p = f->priv;
mp_scaletempo2_reset(&p->data);
mp_scaletempo2_reset(p->data);
p->initialized = false;
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)
{
struct priv *p = f->priv;
mp_scaletempo2_destroy(&p->data);
talloc_free(p->pending);
TA_FREEP(&p->data);
TA_FREEP(&p->pending);
}
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");
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->cur_format = talloc_steal(p, mp_aframe_create());
p->out_pool = mp_aframe_pool_create(p);

View File

@ -41,19 +41,15 @@ static bool in_interval(int n, struct interval q)
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);
float* data = (float*) (array + x);
for (int i = 0; i < x; ++i) {
array[i] = data + i * y;
}
return array;
}
talloc_free(*ptr);
static void zero_2d(float **a, int x, int y)
{
memset(a + x, 0, sizeof(float) * x * y);
float **buff = talloc_array(p, float*, p->channels);
for (int i = 0; i < p->channels; ++i) {
buff[i] = talloc_array(buff, float, size);
}
*ptr = buff;
}
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;
}
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
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)
return; // no silence needed for iteration
int required_size = needed + p->input_buffer_frames;
if (required_size > p->input_buffer_size)
resize_input_buffer(p, required_size);
int last_index = needed + p->input_buffer_frames - 1;
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];
for (int j = 0; j < needed; ++j) {
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)
return 0;
int required_size = read + p->input_buffer_frames;
if (required_size > p->input_buffer_size)
resize_input_buffer(p, required_size);
int last_index = read + p->input_buffer_frames - 1;
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,
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;
}
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)
{
p->input_buffer_frames = 0;
@ -791,8 +765,6 @@ void mp_scaletempo2_reset(struct mp_scaletempo2 *p)
p->output_time = 0.0;
p->search_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->wsola_output_started = false;
}
@ -847,28 +819,26 @@ void mp_scaletempo2_init(struct mp_scaletempo2 *p, int channels, int rate)
// 1, ... |num_candidate_blocks|
p->search_block_center_offset = p->num_candidate_blocks / 2
+ (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);
p->transition_window = realloc(p->transition_window,
sizeof(float) * p->ola_window_size * 2);
MP_RESIZE_ARRAY(p, p->transition_window, p->ola_window_size * 2);
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 = realloc_2d(p->wsola_output, p->channels, p->wsola_output_size);
// Initialize for overlap-and-add of the first block.
zero_2d(p->wsola_output, p->channels, p->wsola_output_size);
alloc_sample_buffer(p, &p->wsola_output, p->wsola_output_size);
// 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 = realloc_2d(p->search_block, p->channels, p->search_block_size);
p->target_block = realloc_2d(p->target_block, p->channels, p->ola_window_size);
alloc_sample_buffer(p, &p->search_block, p->search_block_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_final_frames = 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,
sizeof(float) * p->channels * p->num_candidate_blocks);
MP_RESIZE_ARRAY(p, p->energy_candidate_blocks,
p->channels * p->num_candidate_blocks);
}

View File

@ -110,7 +110,6 @@ struct mp_scaletempo2 {
float **target_block;
// Buffered audio data.
float **input_buffer;
int input_buffer_size;
int input_buffer_frames;
// 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

View File

@ -193,17 +193,19 @@ static void thread_resume(struct ao *ao)
thread_unpause(ao);
}
static void set_state_and_wakeup_thread(struct ao *ao,
enum wasapi_thread_state thread_state)
static void thread_wakeup(void *ptr)
{
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;
atomic_store(&state->thread_state, thread_state);
SetEvent(state->hWake);
}
static void thread_process_dispatch(void *ptr)
{
set_state_and_wakeup_thread(ptr, WASAPI_THREAD_DISPATCH);
thread_wakeup(ao);
}
static DWORD __stdcall AudioThread(void *lpParameter)
@ -220,18 +222,25 @@ static DWORD __stdcall AudioThread(void *lpParameter)
MP_DBG(ao, "Entering dispatch loop\n");
while (true) {
if (WaitForSingleObject(state->hWake, INFINITE) != WAIT_OBJECT_0)
MP_ERR(ao, "Unexpected return value from WaitForSingleObject\n");
HANDLE handles[] = {state->hWake, state->hUserWake};
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);
switch (thread_state) {
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;
case WASAPI_THREAD_RESET:
thread_reset(ao);
@ -267,8 +276,8 @@ static void uninit(struct ao *ao)
{
MP_DBG(ao, "Uninit wasapi\n");
struct wasapi_state *state = ao->priv;
if (state->hWake)
set_state_and_wakeup_thread(ao, WASAPI_THREAD_SHUTDOWN);
if (state->hWake && state->hUserWake)
set_thread_state(ao, WASAPI_THREAD_SHUTDOWN);
if (state->hAudioThread &&
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->hWake, CloseHandle(state->hWake));
SAFE_DESTROY(state->hUserWake, CloseHandle(state->hUserWake));
SAFE_DESTROY(state->hAudioThread,CloseHandle(state->hAudioThread));
wasapi_change_uninit(ao);
@ -312,14 +322,15 @@ static int init(struct ao *ao)
state->hInitDone = 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");
uninit(ao);
return -1;
}
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->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)
{
set_state_and_wakeup_thread(ao, WASAPI_THREAD_RESET);
set_thread_state(ao, WASAPI_THREAD_RESET);
}
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)
{
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;
}

View File

@ -48,7 +48,6 @@ void wasapi_change_uninit(struct ao* ao);
enum wasapi_thread_state {
WASAPI_THREAD_FEED = 0,
WASAPI_THREAD_DISPATCH,
WASAPI_THREAD_RESUME,
WASAPI_THREAD_RESET,
WASAPI_THREAD_SHUTDOWN,
@ -66,6 +65,7 @@ typedef struct wasapi_state {
HANDLE hWake; // thread wakeup event
atomic_int thread_state; // enum wasapi_thread_state (what to do on wakeup)
struct mp_dispatch_queue *dispatch; // for volume/mute/session display
HANDLE hUserWake; // mpv-requested wakeup event
// for setting the audio thread priority
HANDLE hTask;

View File

@ -101,7 +101,7 @@ function _mpv_generate_arguments {
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"

View File

@ -94,7 +94,7 @@ const struct mp_user_filter_entry *vf_list[] = {
#if HAVE_D3D_HWACCEL
&vf_d3d11vpp,
#endif
#if HAVE_GL && HAVE_EGL
#if (HAVE_GL && HAVE_EGL) || HAVE_VULKAN
&vf_gpu,
#endif
};

42
fuzzers/common.h Normal file
View File

@ -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);
}

71
fuzzers/fuzzer_loadfile.c Normal file
View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

26
fuzzers/meson.build Normal file
View File

@ -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

View File

@ -379,6 +379,15 @@ pthread_debug = get_option('pthread-debug').require(
)
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(['-Wno-unused-parameter'], language: 'objc')
add_project_link_arguments(link_flags, language: ['c', 'objc'])
@ -427,6 +436,9 @@ endif
features += {'ppoll': cc.has_function('ppoll', args: '-D_GNU_SOURCE',
prefix: '#include <poll.h>')}
features += {'seek-data': cc.has_header_symbol('errno.h', 'ENXIO') and
cc.has_header_symbol('unistd.h', 'SEEK_DATA', prefix: '#define _GNU_SOURCE')}
cd_devices = {
'windows': 'D:',
'cygwin': 'D:',
@ -1249,7 +1261,7 @@ if features['egl'] or features['egl-android'] or features['egl-angle-win32']
endif
if features['gl'] and features['egl']
sources += files('video/filter/vf_gpu.c')
sources += files('video/filter/vf_gpu_egl.c')
endif
if features['gl']
@ -1274,7 +1286,8 @@ features += {'vulkan': vulkan.found()}
if features['vulkan']
dependencies += vulkan
sources += files('video/out/vulkan/context.c',
'video/out/vulkan/utils.c')
'video/out/vulkan/utils.c',
'video/filter/vf_gpu_vulkan.c')
endif
if features['vulkan'] and features['android']
@ -1299,6 +1312,10 @@ if features['vk-khr-display']
sources += files('video/out/vulkan/context_display.c')
endif
if features['vulkan'] or (features['gl'] and features['egl'])
sources += files('video/filter/vf_gpu.c')
endif
# hwaccel
ffnvcodec = dependency('ffnvcodec', version: '>= 11.1.5.1', required: false)
@ -1798,6 +1815,10 @@ if get_option('tests')
subdir('test')
endif
if get_option('fuzzers')
subdir('fuzzers')
endif
summary({'d3d11': features['d3d11'],
'javascript': features['javascript'],
'libmpv': get_option('libmpv'),

View File

@ -4,6 +4,7 @@ option('cplayer', type: 'boolean', value: true, description: 'mpv CLI player')
option('libmpv', type: 'boolean', value: false, description: 'libmpv library')
option('build-date', type: 'boolean', value: true, description: 'include compile timestamp in binary')
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.
# 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)')

View File

@ -99,12 +99,17 @@ struct config_cache {
void *wakeup_cb_ctx;
};
struct force_update {
char *name;
uint64_t ts;
};
// Per m_config_data state for each m_config_group.
struct m_group_data {
char *udata; // pointer to group user option struct
uint64_t ts; // timestamp of the data copy
char **force_updates; // track if any opt in group was written with force_update
int force_updates_len;
char *udata; // pointer to group user option struct
uint64_t ts; // timestamp of the data copy
struct force_update **force_update; // tracks opts that are written with force update
int force_update_len;
};
static void add_sub_group(struct m_config_shadow *shadow, const char *name_prefix,
@ -590,23 +595,30 @@ struct m_config_cache *m_config_cache_alloc(void *ta_parent,
return m_config_cache_from_shadow(ta_parent, global->config, group);
}
static void clear_force_update_list(struct m_group_data *gsrc)
static void append_force_update(struct m_config_cache *cache, struct m_group_data *gdata,
const char *opt_name)
{
int index = 0;
while (index < gsrc->force_updates_len) {
TA_FREEP(&gsrc->force_updates[index]);
++index;
for (int i = 0; i < gdata->force_update_len; ++i) {
if (strcmp(opt_name, gdata->force_update[i]->name) == 0) {
gdata->force_update[i]->ts = gdata->ts;
return;
}
}
gsrc->force_updates_len = 0;
struct force_update *new_update = talloc_zero(cache, struct force_update);
new_update->name = talloc_strdup(cache, opt_name);
new_update->ts = gdata->ts;
MP_TARRAY_APPEND(cache, gdata->force_update, gdata->force_update_len, new_update);
}
static bool check_force_update_list(struct m_group_data *gsrc, const char *opt_name)
static bool check_force_update(struct m_group_data *gdata, const char *opt_name,
uint64_t timestamp)
{
int index = 0;
while (index < gsrc->force_updates_len) {
if (strcmp(opt_name, gsrc->force_updates[index]) == 0)
for (int i = 0; i < gdata->force_update_len; ++i) {
if ((strcmp(opt_name, gdata->force_update[i]->name) == 0) &&
gdata->force_update[i]->ts == timestamp)
{
return true;
++index;
}
}
return false;
}
@ -637,8 +649,8 @@ static void update_next_option(struct m_config_cache *cache, void **p_opt)
if (opt->offset >= 0 && opt->type->size) {
bool opt_equal = m_option_equal(opt, ddst, dsrc);
bool force_update = opt->force_update && gsrc->force_updates_len &&
check_force_update_list(gsrc, opt->name);
bool force_update = opt->force_update &&
check_force_update(gsrc, opt->name, in->ts);
if (!opt_equal || force_update) {
uint64_t ch = get_opt_change_mask(dst->shadow,
in->upd_group, dst->group_index, opt);
@ -666,8 +678,6 @@ static void update_next_option(struct m_config_cache *cache, void **p_opt)
}
gdst->ts = gsrc->ts;
} else {
clear_force_update_list(gsrc);
}
in->upd_group++;
@ -791,10 +801,8 @@ bool m_config_cache_write_opt(struct m_config_cache *cache, void *ptr)
}
}
if (opt->force_update) {
MP_TARRAY_APPEND(shadow, gsrc->force_updates, gsrc->force_updates_len,
talloc_strdup(shadow, opt->name));
}
if (opt->force_update)
append_force_update(cache, gsrc, opt->name);
mp_mutex_unlock(&shadow->lock);

View File

@ -3021,6 +3021,20 @@ static int mp_property_sub_text(void *ctx, struct m_property *prop,
int sub_index = def[0];
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 dec_sub *sub = track ? track->d_sub : NULL;
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}},
{"secondary-sub-text", mp_property_sub_text,
.priv = (void *)&(const int[]){1, SD_TEXT_TYPE_PLAIN}},
{"sub-text-ass", mp_property_sub_text,
.priv = (void *)&(const int[]){0, SD_TEXT_TYPE_ASS}},
M_PROPERTY_DEPRECATED_ALIAS("sub-text-ass", "sub-text/ass"),
{"sub-start", mp_property_sub_start,
.priv = (void *)&(const int){0}},
{"secondary-sub-start", mp_property_sub_start,

View File

@ -895,18 +895,23 @@ local function add_video_out(s)
scale = mp.get_property_native("current-window-scale")
end
local r = mp.get_property_native("video-target-params")
if not r then
local osd_dims = mp.get_property_native("osd-dimensions")
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
local od = mp.get_property_native("osd-dimensions")
local rt = mp.get_property_native("video-target-params")
r = rt or {}
-- Add window 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_hdr(s, r, true)

View File

@ -332,6 +332,17 @@ static int open_f(stream_t *stream, const struct stream_open_args *args)
setmode(p->fd, O_BINARY);
#endif
#if HAVE_SEEK_DATA
if (stream->mode == STREAM_READ) {
off_t first_data = lseek(p->fd, 0, SEEK_DATA);
if (first_data == (off_t)-1 && errno == ENXIO) {
MP_ERR(stream, "File is empty or all sparse (has no data).\n");
s_close(stream);
return STREAM_ERROR;
}
}
#endif
off_t len = lseek(p->fd, 0, SEEK_END);
lseek(p->fd, 0, SEEK_SET);
if (len != (off_t)-1) {

View File

@ -24,6 +24,7 @@ enum sd_ctrl {
enum sd_text_type {
SD_TEXT_TYPE_PLAIN,
SD_TEXT_TYPE_ASS,
SD_TEXT_TYPE_ASS_FULL,
};
struct sd_times {

View File

@ -115,7 +115,7 @@ static struct demux_packet *jsre_filter(struct sd_filter *ft,
bool drop = false;
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++) {
int found, err = p_regexec(p->J, n, text, &found);

View File

@ -64,7 +64,7 @@ static struct demux_packet *rf_filter(struct sd_filter *ft,
bool drop = false;
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++) {
int err = regexec(&p->regexes[n], text, 0, NULL, 0);

View File

@ -33,6 +33,7 @@
struct lavc_conv {
struct mp_log *log;
struct mp_subtitle_opts *opts;
bool styled;
AVCodecContext *avctx;
AVPacket *avpkt;
AVPacket *avpkt_vtt;
@ -53,20 +54,6 @@ static const char *get_lavc_format(const char *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 *priv = talloc_zero(NULL, struct lavc_conv);
@ -116,7 +103,6 @@ struct lavc_conv *lavc_conv_create(struct sd *sd)
priv->avctx = avctx;
priv->extradata = talloc_strndup(priv, avctx->subtitle_header,
avctx->subtitle_header_size);
disable_styles(bstr0(priv->extradata));
return priv;
error:
@ -260,9 +246,12 @@ char **lavc_conv_decode(struct lavc_conv *priv, struct demux_packet *packet,
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 (!priv->opts->teletext_page) {
av_opt_set(avctx, "txt_page", "subtitle", AV_OPT_SEARCH_CHILDREN);
priv->styled = false;
} else if (priv->opts->teletext_page == -1) {
av_opt_set(avctx, "txt_page", "*", AV_OPT_SEARCH_CHILDREN);
} else {
@ -300,6 +289,11 @@ done:
return priv->cur_list;
}
bool lavc_conv_is_styled(struct lavc_conv *priv)
{
return priv->styled;
}
void lavc_conv_reset(struct lavc_conv *priv)
{
avcodec_flush_buffers(priv->avctx);

View File

@ -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_decode(struct lavc_conv *priv, struct demux_packet *packet,
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_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);
// 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.
// if there's room: out[result.len] is set to \0. out == in is allowed.
bstr sd_ass_to_plaintext(char *out, size_t out_siz, const char *in);
// result.start is *out, result.len is strlen(in) or smaller.
// (*out)[result.len] is always set to \0. *out == in is allowed.
// *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

View File

@ -51,7 +51,7 @@ struct sd_ass_priv {
bool clear_once;
struct mp_ass_packer *packer;
struct sub_bitmap_copy_cache *copy_cache;
char last_text[500];
bstr last_text;
struct mp_image_params video_params;
struct mp_image_params last_params;
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_shared_opts *shared_opts = sd->shared_opts;
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_Renderer *renderer = ctx->ass_renderer;
struct sub_bitmaps *res = &(struct sub_bitmaps){0};
@ -709,30 +709,23 @@ done:
return res;
}
struct buf {
char *start;
int size;
int len;
};
#define MAX_BUF_SIZE 1024 * 1024
#define MIN_EXPAND_SIZE 4096
static void append(struct buf *b, char c)
static void append(bstr *b, char c)
{
if (b->len < b->size) {
b->start[b->len] = c;
b->len++;
}
bstr_xappend(NULL, b, (bstr){&c, 1});
}
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;
bool in_drawing = false;
while (*in) {
if (in_tag) {
if (open_tag_pos) {
if (in[0] == '}') {
in += 1;
in_tag = false;
open_tag_pos = NULL;
} else if (in[0] == '\\' && in[1] == 'p' && in[2] != 'o') {
in += 2;
// 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] == '{') {
open_tag_pos = in;
in += 1;
in_tag = true;
} else {
if (!in_drawing)
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.
if (in_tag) {
while (*open_tag_pos)
append(b, *open_tag_pos++);
if (open_tag_pos) {
bstr_xappend(NULL, b, bstr0(open_tag_pos));
}
}
// Empty string counts as whitespace. Reads s[len-1] even if there are \0s.
static bool is_whitespace_only(char *s, int len)
// Empty string counts as whitespace.
static bool is_whitespace_only(bstr b)
{
for (int n = 0; n < len; n++) {
if (s[n] != ' ' && s[n] != '\t')
for (int n = 0; n < b.len; n++) {
if (b.start[n] != ' ' && b.start[n] != '\t')
return false;
}
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;
ASS_Track *track = ctx->ass_track;
if (pts == MP_NOPTS_VALUE)
return NULL;
return (bstr){0};
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) {
ASS_Event *event = track->events + i;
if (ipts >= event->Start && ipts < event->Start + event->Duration) {
if (event->Text) {
int start = b.len;
int start = b->len;
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 {
char *t = event->Text;
while (*t)
append(&b, *t++);
bstr_xappend(NULL, b, bstr0(event->Text));
}
if (is_whitespace_only(&b.start[start], b.len - start)) {
b.len = start;
if (is_whitespace_only(bstr_cut(*b, start))) {
b->len = start;
} 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')
b.start[b.len - 1] = '\0';
return ctx->last_text;
return *b;
}
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)
@ -862,20 +875,26 @@ static void fill_plaintext(struct sd *sd, double pts)
ass_flush_events(track);
char *text = get_text_buf(sd, pts, SD_TEXT_TYPE_PLAIN);
if (!text)
bstr text = get_text_buf(sd, pts, SD_TEXT_TYPE_PLAIN);
if (!text.len)
return;
bstr dst = {0};
while (*text) {
if (*text == '{')
while (text.len) {
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, (bstr){text, 1});
// Break ASS escapes with U+2060 WORD JOINER
if (*text == '\\')
// Break ASS escapes with U+2060 WORD JOINER
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)
@ -1103,11 +1122,10 @@ bstr sd_ass_pkt_text(struct sd_filter *ft, struct demux_packet *pkt, int offset)
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);
if (b.len < out_siz)
out[b.len] = 0;
return (bstr){out, b.len};
*out = b.start;
return b;
}

View File

@ -24,95 +24,49 @@
#include "options/options.h"
#include "video/out/aspect.h"
#include "video/out/gpu/video.h"
#include "video/out/opengl/egl_helpers.h"
#include "video/out/opengl/ra_gl.h"
#include "video/filter/vf_gpu.h"
struct offscreen_ctx {
struct mp_log *log;
struct ra *ra;
void *priv;
extern const struct offscreen_context offscreen_vk;
extern const struct offscreen_context offscreen_egl;
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 {
GL gl;
EGLDisplay egl_display;
EGLContext egl_context;
};
static void gl_ctx_destroy(void *p)
static inline OPT_STRING_VALIDATE_FUNC(offscreen_ctx_validate_api)
{
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);
struct bstr param = bstr0(*value);
for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) {
if (bstr_equals0(param, contexts[i]->api))
return 1;
}
return M_OPT_INVALID;
}
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;
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");
mp_info(log, "GPU APIs (offscreen contexts):\n");
for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++)
mp_info(log, " %s\n", contexts[i]->api);
return M_OPT_EXIT;
}
static struct offscreen_ctx *gl_offscreen_ctx_create(struct mpv_global *global,
struct mp_log *log)
static struct offscreen_ctx *offscreen_ctx_create(struct mpv_global *global,
struct mp_log *log,
const char *api)
{
struct offscreen_ctx *ctx = talloc_zero(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;
for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) {
if (api && strcmp(contexts[i]->api, api) != 0)
continue;
mp_info(log, "Creating offscreen GPU context '%s'\n", contexts[i]->api);
return contexts[i]->offscreen_ctx_create(global, log);
}
// 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;
}
@ -124,6 +78,7 @@ static void offscreen_ctx_set_current(struct offscreen_ctx *ctx, bool enable)
struct gpu_opts {
int w, h;
char *api;
};
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 = 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) {
MP_FATAL(f, "Could not create offscreen ra context.\n");
goto error;
@ -368,6 +323,8 @@ const struct mp_user_filter_entry vf_gpu = {
.options = (const struct m_option[]){
{"w", OPT_INT(w)},
{"h", OPT_INT(h)},
{"api", OPT_STRING_VALIDATE(api, offscreen_ctx_validate_api),
.help = offscreen_ctx_api_help},
{0}
},
},

35
video/filter/vf_gpu.h Normal file
View File

@ -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 *);
};

107
video/filter/vf_gpu_egl.c Normal file
View File

@ -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
};

View File

@ -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
};

View File

@ -28,7 +28,6 @@
#include "video/out/placebo/utils.h"
#include "context.h"
#include "utils.h"
struct vulkan_opts {
char *device; // force a specific GPU
@ -174,19 +173,11 @@ void ra_vk_ctx_uninit(struct ra_ctx *ctx)
TA_FREEP(&ctx->swapchain);
}
bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
struct ra_vk_ctx_params params,
VkPresentModeKHR preferred_mode)
pl_vulkan mppl_create_vulkan(struct vulkan_opts *opts,
pl_vk_inst vkinst,
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 = {
.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
AVUUID param_uuid = { 0 };
bool is_uuid = p->opts->device &&
av_uuid_parse(p->opts->device, param_uuid) == 0;
bool is_uuid = opts->device &&
av_uuid_parse(opts->device, param_uuid) == 0;
assert(vk->pllog);
assert(vk->vkinst);
assert(pllog);
assert(vkinst);
struct pl_vulkan_params device_params = {
.instance = vk->vkinst->instance,
.get_proc_addr = vk->vkinst->get_proc_addr,
.surface = vk->surface,
.async_transfer = p->opts->async_transfer,
.async_compute = p->opts->async_compute,
.queue_count = p->opts->queue_count,
.instance = vkinst->instance,
.get_proc_addr = vkinst->get_proc_addr,
.surface = surface,
.async_transfer = opts->async_transfer,
.async_compute = opts->async_compute,
.queue_count = opts->queue_count,
#if HAVE_VULKAN_INTEROP
.extra_queues = VK_QUEUE_VIDEO_DECODE_BIT_KHR,
.opt_extensions = opt_extensions,
.num_opt_extensions = MP_ARRAY_SIZE(opt_extensions),
#endif
.features = &features,
.device_name = is_uuid ? NULL : p->opts->device,
.device_name = is_uuid ? NULL : opts->device,
};
if (is_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)
goto error;

View File

@ -21,6 +21,12 @@ bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
struct ra_vk_ctx_params params,
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
bool ra_vk_ctx_resize(struct ra_ctx *ctx, int width, int height);

View File

@ -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) {
update_cursor_passthrough(w32);
} else if (changed_option == &vo_opts->border ||
changed_option == &vo_opts->title_bar ||
changed_option == &vo_opts->show_in_taskbar)
changed_option == &vo_opts->title_bar)
{
update_window_style(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) {
update_minimized_state(w32);
} else if (changed_option == &vo_opts->window_maximized) {