diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 07a689106e4db..7f114dc8dd984 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -369,8 +369,7 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). behavior depends on the selected video output. Save the contents of the mpv window. Typically scaled, with OSD and - subtitles. The exact behavior depends on the selected video output, and - if no support is available, this will act like ``video``. + subtitles. The exact behavior depends on the selected video output. Take a screenshot each frame. Issue this command again to stop taking screenshots. Note that you should disable frame-dropping when using diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index fab91dd3d2b76..10c6ace9aecfe 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -4517,17 +4517,18 @@ Screenshot ``--screenshot-sw=`` Whether to use software rendering for screenshots (default: no). - If set to no, the screenshot will be rendered by the current VO if possible - (only vo_gpu currently). The advantage is that this will (probably) always + If set to no, the screenshot will be rendered by the current VO (only vo_gpu + or vo_gpu_next currently). The advantage is that this will (probably) always show up as in the video window, because the same code is used for rendering. But since the renderer needs to be reinitialized, this can be slow and - interrupt playback. (Unless the ``window`` mode is used with the - ``screenshot`` command.) + interrupt playback. If set to yes, the software scaler is used to convert the video to RGB (or whatever the target screenshot requires). In this case, conversion will run in a separate thread and will probably not interrupt playback. The software renderer may lack some capabilities, such as HDR rendering. + If ``window`` mode is used, the image will also be scaled in software + which may not accurately reflect the actual visible result. Software Scaler --------------- diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst index dccfa62a7c0a9..0592d9467ee5d 100644 --- a/DOCS/man/vo.rst +++ b/DOCS/man/vo.rst @@ -291,8 +291,12 @@ Available video output drivers are: or VA API hardware decoding. The driver is designed to avoid any GPU to CPU copies, and to perform scaling and color space conversion using fixed-function hardware, if available, rather than GPU shaders. This frees up GPU resources for other tasks. - Currently this driver is experimental and only works with the ``--hwdec=vaapi`` - or ``hwdec=drm`` drivers. Supported compositors : Weston and Sway. + It is highly recommended to use this VO with the appropriate ``--hwdec`` option such + as ``auto-safe``. It can still work in some circumstances without ``--hwdec`` due to + mpv's internal conversion filters, but this is not recommended as it's a needless + extra step. Correct output depends on support from your GPU, drivers, and compositor. + Weston and wlroots-based compositors like Sway and Intel GPUs are known to generally + work. ``vaapi`` Intel VA API video output driver with support for hardware decoding. Note diff --git a/audio/out/ao_pipewire.c b/audio/out/ao_pipewire.c index a7cbc866d0f06..8e93dc5341b9d 100644 --- a/audio/out/ao_pipewire.c +++ b/audio/out/ao_pipewire.c @@ -872,7 +872,7 @@ const struct ao_driver audio_out_pipewire = { .loop = NULL, .stream = NULL, .init_state = INIT_STATE_NONE, - .options.buffer_msec = 20, + .options.buffer_msec = 0, .options.volume_mode = VOLUME_MODE_CHANNEL, }, .options_prefix = "pipewire", diff --git a/filters/f_decoder_wrapper.h b/filters/f_decoder_wrapper.h index 9fa1a4f010d26..3100e6836b4ef 100644 --- a/filters/f_decoder_wrapper.h +++ b/filters/f_decoder_wrapper.h @@ -77,6 +77,7 @@ enum dec_ctrl { VDCTRL_GET_BFRAMES, // framedrop mode: 0=none, 1=standard, 2=hrseek VDCTRL_SET_FRAMEDROP, + VDCTRL_CHECK_FORCED_EOF, }; int mp_decoder_wrapper_control(struct mp_decoder_wrapper *d, diff --git a/osdep/terminal-unix.c b/osdep/terminal-unix.c index 057c6e8cae133..e03599d829b49 100644 --- a/osdep/terminal-unix.c +++ b/osdep/terminal-unix.c @@ -415,12 +415,23 @@ static void *terminal_thread(void *ptr) { .events = POLLIN, .fd = death_pipe[0] }, { .events = POLLIN, .fd = tty_in } }; - int r = polldev(fds, stdin_ok ? 2 : 1, buf.len ? ESC_TIMEOUT : INPUT_TIMEOUT); + /* + * if the process isn't in foreground process group, then on macos + * polldev() doesn't rest and gets into 100% cpu usage (see issue #11795) + * with read() returning EIO. but we shouldn't quit on EIO either since + * the process might be foregrounded later. + * + * so just avoid poll-ing tty_in when we know the process is not in the + * foreground. there's a small race window, but the timeout will take + * care of it so it's fine. + */ + bool is_fg = tcgetpgrp(tty_in) == getpgrp(); + int r = polldev(fds, stdin_ok && is_fg ? 2 : 1, buf.len ? ESC_TIMEOUT : INPUT_TIMEOUT); if (fds[0].revents) break; if (fds[1].revents) { int retval = read(tty_in, &buf.b[buf.len], BUF_LEN - buf.len); - if (!retval || (retval == -1 && errno != EINTR && errno != EAGAIN)) + if (!retval || (retval == -1 && errno != EINTR && errno != EAGAIN && errno != EIO)) break; // EOF/closed if (retval > 0) { buf.len += retval; diff --git a/player/lua/osc.lua b/player/lua/osc.lua index 0722102e94b01..e1d6fe2973e23 100644 --- a/player/lua/osc.lua +++ b/player/lua/osc.lua @@ -1142,9 +1142,8 @@ function window_controls(topbar) -- deadzone below window controls local sh_area_y0, sh_area_y1 sh_area_y0 = user_opts.barmargin - sh_area_y1 = (wc_geo.y + (wc_geo.h / 2)) + - get_align(1 - (2 * user_opts.deadzonesize), - osc_param.playresy - (wc_geo.y + (wc_geo.h / 2)), 0, 0) + sh_area_y1 = wc_geo.y + get_align(1 - (2 * user_opts.deadzonesize), + osc_param.playresy - wc_geo.y, 0, 0) add_area("showhide_wc", wc_geo.x, sh_area_y0, wc_geo.w, sh_area_y1) if topbar then @@ -1532,13 +1531,11 @@ function bar_layout(direction) if direction > 0 then -- deadzone below OSC sh_area_y0 = user_opts.barmargin - sh_area_y1 = (osc_geo.y + (osc_geo.h / 2)) + - get_align(1 - (2*user_opts.deadzonesize), - osc_param.playresy - (osc_geo.y + (osc_geo.h / 2)), 0, 0) + sh_area_y1 = osc_geo.y + get_align(1 - (2 * user_opts.deadzonesize), + osc_param.playresy - osc_geo.y, 0, 0) else -- deadzone above OSC - sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize), - osc_geo.y - (osc_geo.h / 2), 0, 0) + sh_area_y0 = get_align(-1 + (2 * user_opts.deadzonesize), osc_geo.y, 0, 0) sh_area_y1 = osc_param.playresy - user_opts.barmargin end add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1) diff --git a/player/screenshot.c b/player/screenshot.c index 30810e3d6fba1..7956c581316d2 100644 --- a/player/screenshot.c +++ b/player/screenshot.c @@ -324,11 +324,19 @@ static char *gen_fname(struct mp_cmd_ctx *cmd, const char *file_ext) } } -static void add_subs(struct MPContext *mpctx, struct mp_image *image) +static void add_osd(struct MPContext *mpctx, struct mp_image *image, int mode) { - struct mp_osd_res res = osd_res_from_image_params(&image->params); - osd_draw_on_image(mpctx->osd, res, mpctx->video_pts, - OSD_DRAW_SUB_ONLY, image); + bool window = mode == MODE_FULL_WINDOW; + struct mp_osd_res res = window ? osd_get_vo_res(mpctx->video_out->osd) : + osd_res_from_image_params(&image->params); + if (mode == MODE_SUBTITLES || window) { + osd_draw_on_image(mpctx->osd, res, mpctx->video_pts, + OSD_DRAW_SUB_ONLY, image); + } + if (window) { + osd_draw_on_image(mpctx->osd, res, mpctx->video_pts, + OSD_DRAW_OSD_ONLY, image); + } } static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode, @@ -338,36 +346,36 @@ static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode, const struct image_writer_opts *imgopts = mpctx->opts->screenshot_image_opts; if (mode == MODE_SUBTITLES && osd_get_render_subs_in_filter(mpctx->osd)) mode = 0; - bool need_add_subs = mode == MODE_SUBTITLES; if (!mpctx->video_out || !mpctx->video_out->config_ok) return NULL; vo_wait_frame(mpctx->video_out); // important for each-frame mode + bool use_sw = mpctx->opts->screenshot_sw; + bool window = mode == MODE_FULL_WINDOW; struct voctrl_screenshot ctrl = { - .scaled = mode == MODE_FULL_WINDOW, + .scaled = window, .subs = mode != 0, - .osd = mode == MODE_FULL_WINDOW, + .osd = window, .high_bit_depth = high_depth && imgopts->high_bit_depth, .native_csp = image_writer_flexible_csp(imgopts), }; - if (!mpctx->opts->screenshot_sw) + if (!use_sw) vo_control(mpctx->video_out, VOCTRL_SCREENSHOT, &ctrl); image = ctrl.res; - if (image) - need_add_subs = false; - if (!image && mode != MODE_FULL_WINDOW) - image = vo_get_current_frame(mpctx->video_out); - if (!image) { + if (!use_sw && !image && window) vo_control(mpctx->video_out, VOCTRL_SCREENSHOT_WIN, &image); - mode = MODE_FULL_WINDOW; + + if (!image) { + use_sw = true; + MP_VERBOSE(mpctx->screenshot_ctx, "Falling back to software screenshot.\n"); + image = vo_get_current_frame(mpctx->video_out); } - if (!image) - return NULL; - if (image->fmt.flags & MP_IMGFLAG_HWACCEL) { + // vo_get_current_frame() can return a hardware frame, which we have to download first. + if (image && image->fmt.flags & MP_IMGFLAG_HWACCEL) { struct mp_image *nimage = mp_image_hw_download(image, NULL); talloc_free(image); if (!nimage) @@ -375,8 +383,28 @@ static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode, image = nimage; } - if (need_add_subs) - add_subs(mpctx, image); + if (use_sw && image && window) { + struct mp_osd_res res = osd_get_vo_res(mpctx->video_out->osd); + struct mp_osd_res image_res = osd_res_from_image_params(&image->params); + if (!osd_res_equals(res, image_res)) { + struct mp_image *nimage = mp_image_alloc(image->imgfmt, res.w, res.h); + if (!nimage) { + talloc_free(image); + return NULL; + } + struct mp_sws_context *sws = mp_sws_alloc(NULL); + mp_sws_scale(sws, nimage, image); + talloc_free(image); + talloc_free(sws); + image = nimage; + } + } + + if (!image) + return NULL; + + if (use_sw && mode != 0) + add_osd(mpctx, image, mode); mp_image_params_guess_csp(&image->params); return image; } diff --git a/player/video.c b/player/video.c index 547f67c9defa1..0161929c46fb5 100644 --- a/player/video.c +++ b/player/video.c @@ -573,6 +573,16 @@ static bool check_for_hwdec_fallback(struct MPContext *mpctx) return true; } +static bool check_for_forced_eof(struct MPContext *mpctx) +{ + struct vo_chain *vo_c = mpctx->vo_chain; + struct mp_decoder_wrapper *dec = vo_c->track->dec; + + bool forced_eof = false; + mp_decoder_wrapper_control(dec, VDCTRL_CHECK_FORCED_EOF, &forced_eof); + return forced_eof; +} + /* Update avsync before a new video frame is displayed. Actually, this can be * called arbitrarily often before the actual display. * This adjusts the time of the next video frame */ @@ -1041,6 +1051,11 @@ void write_video(struct MPContext *mpctx) if (r == VD_EOF) { if (check_for_hwdec_fallback(mpctx)) return; + if (check_for_forced_eof(mpctx)) { + uninit_video_chain(mpctx); + handle_force_window(mpctx, true); + return; + } if (vo_c->filter->failed_output_conversion) goto error; diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c index 77f64aa8c5849..2106dcb56edef 100644 --- a/video/decode/vd_lavc.c +++ b/video/decode/vd_lavc.c @@ -183,6 +183,7 @@ typedef struct lavc_ctx { const char *decoder; bool hwdec_failed; bool hwdec_notified; + bool force_eof; bool intra_only; int framedrop_flags; @@ -589,7 +590,14 @@ static void select_and_set_hwdec(struct mp_filter *vd) MP_VERBOSE(vd, "Using underlying hw-decoder '%s'\n", ctx->hwdec.codec->name); } else { - MP_VERBOSE(vd, "Using software decoding.\n"); + // If software fallback is disabled and we get here, all hwdec must + // have failed. Tell the ctx to always force an eof. + if (ctx->opts->software_fallback == INT_MAX) { + MP_WARN(ctx, "Software decoding fallback is disabled.\n"); + ctx->force_eof = true; + } else { + MP_VERBOSE(vd, "Using software decoding.\n"); + } } } @@ -1167,7 +1175,7 @@ static int decode_frame(struct mp_filter *vd) vd_ffmpeg_ctx *ctx = vd->priv; AVCodecContext *avctx = ctx->avctx; - if (!avctx) + if (!avctx || ctx->force_eof) return AVERROR_EOF; prepare_decoding(vd); @@ -1317,6 +1325,10 @@ static int control(struct mp_filter *vd, enum dec_ctrl cmd, void *arg) case VDCTRL_SET_FRAMEDROP: ctx->framedrop_flags = *(int *)arg; return CONTROL_TRUE; + case VDCTRL_CHECK_FORCED_EOF: { + *(bool *)arg = ctx->force_eof; + return CONTROL_TRUE; + } case VDCTRL_GET_BFRAMES: { AVCodecContext *avctx = ctx->avctx; if (!avctx) diff --git a/video/out/vo_dmabuf_wayland.c b/video/out/vo_dmabuf_wayland.c index a7c6206f989a3..a43b0fd180e6b 100644 --- a/video/out/vo_dmabuf_wayland.c +++ b/video/out/vo_dmabuf_wayland.c @@ -648,6 +648,11 @@ static int reconfig(struct vo *vo, struct mp_image *img) { struct priv *p = vo->priv; + // If we have a supported format but no hw_subfmt, this + // is probably handle_force_window. Consider it valid. + if (is_supported_fmt(img->params.imgfmt) && img->params.hw_subfmt == IMGFMT_NONE) + goto done; + if (!drm_format_check(vo, img)) { MP_ERR(vo, "Unable to get drm format from hardware decoding!\n"); return VO_ERROR; @@ -659,6 +664,7 @@ static int reconfig(struct vo *vo, struct mp_image *img) return VO_ERROR; } +done: if (!vo_wayland_reconfig(vo)) return VO_ERROR; @@ -806,7 +812,7 @@ static int preinit(struct vo *vo) } if (p->hwdec_type == HWDEC_NONE) { - MP_ERR(vo, "No valid hardware decoding driver could be loaded!"); + MP_ERR(vo, "No valid hardware decoding driver could be loaded!\n"); goto err; } diff --git a/video/out/vo_drm.c b/video/out/vo_drm.c index 96d02c5f00c68..1128a0dd4fa0e 100644 --- a/video/out/vo_drm.c +++ b/video/out/vo_drm.c @@ -440,12 +440,7 @@ static int query_format(struct vo *vo, int format) static int control(struct vo *vo, uint32_t request, void *arg) { - struct priv *p = vo->priv; - switch (request) { - case VOCTRL_SCREENSHOT_WIN: - *(struct mp_image**)arg = mp_image_new_copy(p->cur_frame); - return VO_TRUE; case VOCTRL_SET_PANSCAN: if (vo->config_ok) reconfig(vo, vo->params); diff --git a/video/out/vo_vaapi.c b/video/out/vo_vaapi.c index 009832c734cca..09f425e26cf24 100644 --- a/video/out/vo_vaapi.c +++ b/video/out/vo_vaapi.c @@ -684,6 +684,10 @@ static void draw_osd(struct vo *vo) int rw = mp_rect_w(*rc); int rh = mp_rect_h(*rc); + // reduce width of last slice to prevent overflow + if (n == num_mod_rc - 1) + rw = w - rc->x0; + void *src = mp_image_pixel_ptr(osd, 0, rc->x0, rc->y0); void *dst = vaimg.planes[0] + rc->y0 * vaimg.stride[0] + rc->x0 * 4; @@ -788,6 +792,7 @@ static int preinit(struct vo *vo) if (!p->image_formats) goto fail; + p->mpvaapi->hwctx.hw_imgfmt = IMGFMT_VAAPI; p->pool = mp_image_pool_new(p); va_pool_set_allocator(p->pool, p->mpvaapi, VA_RT_FORMAT_YUV420);