diff --git a/DOCS/man/en/options.rst b/DOCS/man/en/options.rst index 18542b2e37..f28280f73f 100644 --- a/DOCS/man/en/options.rst +++ b/DOCS/man/en/options.rst @@ -1118,7 +1118,7 @@ OPTIONS :no: always use software decoding (default) :auto: see below :vdpau: requires ``--vo=vdpau`` (Linux only) - :vaapi: requires ``--vo=vaapi`` (Linux with Intel GPUs only) + :vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux with Intel GPUs only) :vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only) :vda: requires ``--vo=corevideo`` (OSX only) :crystalhd: Broadcom Crystal HD diff --git a/DOCS/man/en/vo.rst b/DOCS/man/en/vo.rst index 4590ec33f2..fd14c6a128 100644 --- a/DOCS/man/en/vo.rst +++ b/DOCS/man/en/vo.rst @@ -241,6 +241,11 @@ Available video output drivers are: Some features are available with OpenGL 3 capable graphics drivers only (or if the necessary extensions are available). + Hardware decoding over OpenGL-interop is supported to some degree. Note + that in this mode, some corner case might not be gracefully handled, and + colorspace conversion and chroma upsampling is generally in the hand of + the hardware decoder APIs. + ``lscale=`` ``bilinear`` diff --git a/Makefile b/Makefile index bed21f20e5..e1cacc0ea5 100644 --- a/Makefile +++ b/Makefile @@ -121,6 +121,7 @@ SOURCES-$(VAAPI) += video/out/vo_vaapi.c \ video/decode/vaapi.c \ video/vaapi.c SOURCES-$(VAAPI_VPP) += video/filter/vf_vavpp.c +SOURCES-$(VAAPI_GLX) += video/out/gl_hwdec_vaglx.c SOURCES-$(X11) += video/out/vo_x11.c video/out/x11_common.c SOURCES-$(XV) += video/out/vo_xv.c diff --git a/TOOLS/mpv_identify.sh b/TOOLS/mpv_identify.sh index 0c4dfc5b02..b1c17f75c2 100755 --- a/TOOLS/mpv_identify.sh +++ b/TOOLS/mpv_identify.sh @@ -104,7 +104,7 @@ for __midentify__key in $__midentify__allprops; do eval unset $__midentify__nextprefix$__midentify__key done -__midentify__output=`$MPV --playing-msg="$__midentify__propstr" --vo=null --ao=null --frames=1 --quiet "$@"` +__midentify__output=`$MPV --playing-msg="$__midentify__propstr" --vo=null --ao=null --frames=1 --quiet --no-cache --no-config "$@"` __midentify__fileindex=0 __midentify__prefix= while :; do diff --git a/configure b/configure index 2b31424daa..8de1ba52e8 100755 --- a/configure +++ b/configure @@ -1863,6 +1863,8 @@ echores "$_vdpau" echocheck "VAAPI" _vaapi_vpp=no def_vaapi_vpp='#define HAVE_VAAPI_VPP 0' +_vaapi_glx=no +def_vaapi_glx='#define HAVE_VAAPI_GLX 0' if test "$_vaapi" = auto && test "$_x11" = yes ; then _vaapi=no if test "$_dl" = yes ; then @@ -1886,7 +1888,13 @@ if test "$_vaapi" = yes ; then _vaapi_vpp=yes def_vaapi_vpp='#define HAVE_VAAPI_VPP 1' fi - echores "$_vaapi_vpp" + echores "$_vaapi_glx" + echocheck "VAAPI GLX" + if pkg_config_add 'libva-glx >= 0.32.0' ; then + _vaapi_glx=yes + def_vaapi_glx='#define HAVE_VAAPI_GLX 1' + fi + echores "$_vaapi_glx" fi @@ -3321,6 +3329,7 @@ VDA = $_vda VDA_REFCOUNTING = $_vda_refcounting VAAPI = $_vaapi VAAPI_VPP = $_vaapi_vpp +VAAPI_GLX = $_vaapi_glx WIN32 = $_win32 X11 = $_x11 WAYLAND = $_wayland @@ -3501,6 +3510,7 @@ $def_vda $def_vda_refcounting $def_vaapi $def_vaapi_vpp +$def_vaapi_glx $def_vaapi_hwaccel $def_vm $def_x11 diff --git a/demux/demux.c b/demux/demux.c index eeb979be9d..a0164d529a 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -533,9 +533,8 @@ static struct demuxer *open_given_type(struct MPOpts *opts, .type = desc->type, .stream = stream, .stream_pts = MP_NOPTS_VALUE, - .movi_start = stream->start_pos, - .movi_end = stream->end_pos, - .seekable = 1, + .seekable = (stream->flags & MP_STREAM_SEEK) == MP_STREAM_SEEK && + stream->end_pos > 0, .accurate_seek = true, .filepos = -1, .opts = opts, @@ -566,6 +565,12 @@ static struct demuxer *open_given_type(struct MPOpts *opts, add_stream_chapters(demuxer); demuxer_sort_chapters(demuxer); demux_info_update(demuxer); + // Pretend we can seek if we can't seek, but there's a cache. + if (!demuxer->seekable && stream->uncached_stream) { + mp_msg(MSGT_DEMUXER, MSGL_WARN, + "File is not seekable, but there's a cache: enabling seeking.\n"); + demuxer->seekable = true; + } return demuxer; } @@ -634,7 +639,7 @@ void demux_flush(demuxer_t *demuxer) int demux_seek(demuxer_t *demuxer, float rel_seek_secs, int flags) { if (!demuxer->seekable) { - mp_tmsg(MSGT_SEEK, MSGL_WARN, "Cannot seek in this file.\n"); + mp_tmsg(MSGT_DEMUXER, MSGL_WARN, "Cannot seek in this file.\n"); return 0; } diff --git a/demux/demux.h b/demux/demux.h index 0c8f9cb3f8..1c8562eb63 100644 --- a/demux/demux.h +++ b/demux/demux.h @@ -166,8 +166,6 @@ typedef struct demuxer { const demuxer_desc_t *desc; ///< Demuxer description structure const char *filetype; // format name when not identified by demuxer (libavformat) int64_t filepos; // input stream current pos. - int64_t movi_start; - int64_t movi_end; struct stream *stream; double stream_pts; // current stream pts, if applicable (e.g. dvd) char *filename; // same as stream->url diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c index e80db5aed4..b2dfcb2193 100644 --- a/demux/demux_lavf.c +++ b/demux/demux_lavf.c @@ -584,9 +584,12 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check) } } - if (!(priv->avif->flags & AVFMT_NOFILE) && - demuxer->stream->type != STREAMTYPE_AVDEVICE) + if ((priv->avif->flags & AVFMT_NOFILE) || + demuxer->stream->type == STREAMTYPE_AVDEVICE) { + // This might be incorrect. + demuxer->seekable = true; + } else { void *buffer = av_malloc(lavfdopts->buffersize); if (!buffer) return -1; @@ -597,9 +600,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check) return -1; } priv->pb->read_seek = mp_read_seek; - priv->pb->seekable = demuxer->stream->end_pos - && (demuxer->stream->flags & MP_STREAM_SEEK) == MP_STREAM_SEEK - ? AVIO_SEEKABLE_NORMAL : 0; + priv->pb->seekable = demuxer->seekable ? AVIO_SEEKABLE_NORMAL : 0; avfc->pb = priv->pb; } @@ -857,12 +858,12 @@ static void demux_seek_lavf(demuxer_t *demuxer, float rel_seek_secs, int flags) avsflags = AVSEEK_FLAG_BACKWARD; if (flags & SEEK_FACTOR) { - if (demuxer->movi_end > 0 && demuxer->ts_resets_possible && + struct stream *s = demuxer->stream; + if (s->end_pos > 0 && demuxer->ts_resets_possible && !(priv->avif->flags & AVFMT_NO_BYTE_SEEK)) { avsflags |= AVSEEK_FLAG_BYTE; - priv->last_pts = (demuxer->movi_end - demuxer->movi_start) * - rel_seek_secs; + priv->last_pts = (s->end_pos - s->start_pos) * rel_seek_secs; } else if (priv->avfc->duration != 0 && priv->avfc->duration != AV_NOPTS_VALUE) { @@ -923,12 +924,12 @@ static int demux_lavf_control(demuxer_t *demuxer, int cmd, void *arg) switch (cmd) { case DEMUXER_CTRL_GET_TIME_LENGTH: if (priv->seek_by_bytes) { + struct stream *s = demuxer->stream; /* Our bitrate estimate may be better than would be used in * otherwise similar fallback code at higher level */ - if (demuxer->movi_end <= 0) + if (s->end_pos <= 0) return DEMUXER_CTRL_DONTKNOW; - *(double *)arg = (demuxer->movi_end - demuxer->movi_start) * 8 / - priv->bitrate; + *(double *)arg = (s->end_pos - s->start_pos) * 8 / priv->bitrate; return DEMUXER_CTRL_GUESS; } if (priv->avfc->duration == 0 || priv->avfc->duration == AV_NOPTS_VALUE) diff --git a/demux/demux_libass.c b/demux/demux_libass.c index d98cdbb5a4..73c8b3a5c8 100644 --- a/demux/demux_libass.c +++ b/demux/demux_libass.c @@ -100,6 +100,8 @@ static int d_check_file(struct demuxer *demuxer, enum demux_check check) sh->sub->track = track; sh->codec = "ass"; + demuxer->seekable = true; + return 0; } diff --git a/demux/demux_mf.c b/demux/demux_mf.c index 0d7b2ece8a..50888f59ad 100644 --- a/demux/demux_mf.c +++ b/demux/demux_mf.c @@ -85,7 +85,6 @@ static int demux_mf_fill_buffer(demuxer_t *demuxer) demux_packet_t *dp = new_demux_packet(data.len); memcpy(dp->buffer, data.start, data.len); dp->pts = mf->curr_frame / mf->sh->fps; - dp->pos = mf->curr_frame; dp->keyframe = true; demuxer_add_packet(demuxer, demuxer->streams[0], dp); } @@ -200,9 +199,6 @@ static int demux_open_mf(demuxer_t* demuxer, enum demux_check check) mf->curr_frame = 0; - demuxer->movi_start = 0; - demuxer->movi_end = mf->nr_of_files - 1; - // create a new video stream header struct sh_stream *sh = new_sh_stream(demuxer, STREAM_VIDEO); sh_video = sh->video; @@ -214,6 +210,7 @@ static int demux_open_mf(demuxer_t* demuxer, enum demux_check check) mf->sh = sh_video; demuxer->priv=(void*)mf; + demuxer->seekable = true; return 0; diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c index eb352e0f64..6110d25930 100644 --- a/demux/demux_mkv.c +++ b/demux/demux_mkv.c @@ -1029,7 +1029,7 @@ static int demux_mkv_read_seekhead(demuxer_t *demuxer) continue; } uint64_t pos = seek->seek_position + mkv_d->segment_start; - if (pos >= demuxer->movi_end) { + if (pos >= s->end_pos) { mp_msg(MSGT_DEMUX, MSGL_WARN, "[mkv] SeekHead position beyond " "end of file - incomplete file?\n"); continue; @@ -1844,14 +1844,6 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check) display_create_tracks(demuxer); - if (s->end_pos == 0) { - demuxer->seekable = 0; - } else { - demuxer->movi_start = s->start_pos; - demuxer->movi_end = s->end_pos; - demuxer->seekable = 1; - } - return 0; } @@ -2713,7 +2705,7 @@ static void demux_mkv_seek(demuxer_t *demuxer, float rel_seek_secs, int flags) } demux_mkv_fill_buffer(demuxer); - } else if ((demuxer->movi_end <= 0) || !(flags & SEEK_ABSOLUTE)) + } else if (!(flags & SEEK_ABSOLUTE)) mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] seek unsupported flags\n"); else { stream_t *s = demuxer->stream; @@ -2729,7 +2721,7 @@ static void demux_mkv_seek(demuxer_t *demuxer, float rel_seek_secs, int flags) return; } - target_filepos = (uint64_t) (demuxer->movi_end * rel_seek_secs); + target_filepos = (uint64_t) (s->end_pos * rel_seek_secs); for (i = 0; i < mkv_d->num_indexes; i++) if (mkv_d->indexes[i].tnum == v_tnum) if ((index == NULL) diff --git a/demux/demux_raw.c b/demux/demux_raw.c index 4d6e171ad4..01cf3574a0 100644 --- a/demux/demux_raw.c +++ b/demux/demux_raw.c @@ -100,9 +100,6 @@ static int demux_rawaudio_open(demuxer_t *demuxer, enum demux_check check) w->wBitsPerSample = 8 * samplesize; w->cbSize = 0; - demuxer->movi_start = demuxer->stream->start_pos; - demuxer->movi_end = demuxer->stream->end_pos; - struct priv *p = talloc_ptrtype(demuxer, p); demuxer->priv = p; *p = (struct priv) { @@ -185,9 +182,6 @@ static int demux_rawvideo_open(demuxer_t *demuxer, enum demux_check check) sh_video->disp_h = height; sh_video->i_bps = fps * imgsize; - demuxer->movi_start = demuxer->stream->start_pos; - demuxer->movi_end = demuxer->stream->end_pos; - struct priv *p = talloc_ptrtype(demuxer, p); demuxer->priv = p; *p = (struct priv) { @@ -207,7 +201,7 @@ static int raw_fill_buffer(demuxer_t *demuxer) return 0; struct demux_packet *dp = new_demux_packet(p->frame_size * p->read_frames); - dp->pos = stream_tell(demuxer->stream) - demuxer->movi_start; + dp->pos = stream_tell(demuxer->stream) - demuxer->stream->start_pos; dp->pts = (dp->pos / p->frame_size) / p->frame_rate; int len = stream_read(demuxer->stream, dp->buffer, dp->len); diff --git a/demux/demux_subreader.c b/demux/demux_subreader.c index cc02e61595..21bcf21614 100644 --- a/demux/demux_subreader.c +++ b/demux/demux_subreader.c @@ -1358,6 +1358,8 @@ static int d_open_file(struct demuxer *demuxer, enum demux_check check) add_sub_data(demuxer, sd); subdata_free(sd); + demuxer->seekable = true; + return 0; } diff --git a/mpvcore/player/loadfile.c b/mpvcore/player/loadfile.c index 24b81934ad..baa49f0db5 100644 --- a/mpvcore/player/loadfile.c +++ b/mpvcore/player/loadfile.c @@ -894,8 +894,9 @@ static void print_resolve_contents(struct mp_log *log, // from the given playlist pl, so the entries don't actually need to be copied. static void transfer_playlist(struct MPContext *mpctx, struct playlist *pl) { - if (mpctx->demuxer->playlist->first) { - playlist_transfer_entries(mpctx->playlist, mpctx->demuxer->playlist); + if (pl->first) { + playlist_transfer_entries(mpctx->playlist, pl); + // current entry is replaced if (mpctx->playlist->current) playlist_remove(mpctx->playlist, mpctx->playlist->current); } else { diff --git a/mpvcore/player/playloop.c b/mpvcore/player/playloop.c index 1162572946..d4db03c446 100644 --- a/mpvcore/player/playloop.c +++ b/mpvcore/player/playloop.c @@ -206,8 +206,8 @@ static void seek_reset(struct MPContext *mpctx, bool reset_ao, bool reset_ac) } // return -1 if seek failed (non-seekable stream?), 0 otherwise -static int seek(MPContext *mpctx, struct seek_params seek, - bool timeline_fallthrough) +static int mp_seek(MPContext *mpctx, struct seek_params seek, + bool timeline_fallthrough) { struct MPOpts *opts = mpctx->opts; uint64_t prev_seek_ts = mpctx->vo_pts_history_seek_ts; @@ -215,6 +215,11 @@ static int seek(MPContext *mpctx, struct seek_params seek, if (!mpctx->demuxer) return -1; + if (!mpctx->demuxer->seekable) { + MP_ERR(mpctx, "Can't seek in this file.\n"); + return -1; + } + if (mpctx->stop_play == AT_END_OF_FILE) mpctx->stop_play = KEEP_PLAYING; bool hr_seek = mpctx->demuxer->accurate_seek && opts->correct_pts; @@ -387,7 +392,7 @@ void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount, void execute_queued_seek(struct MPContext *mpctx) { if (mpctx->seek.type) { - seek(mpctx, mpctx->seek, false); + mp_seek(mpctx, mpctx->seek, false); mpctx->seek = (struct seek_params){0}; } } @@ -452,11 +457,12 @@ double get_current_pos_ratio(struct MPContext *mpctx, bool use_range) if (len > 0 && !demuxer->ts_resets_possible) { ans = MPCLAMP((pos - start) / len, 0, 1); } else { - int64_t size = (demuxer->movi_end - demuxer->movi_start); + struct stream *s = demuxer->stream; + int64_t size = s->end_pos - s->start_pos; int64_t fpos = demuxer->filepos > 0 ? demuxer->filepos : stream_tell(demuxer->stream); if (size > 0) - ans = MPCLAMP((double)(fpos - demuxer->movi_start) / size, 0, 1); + ans = MPCLAMP((double)(fpos - s->start_pos) / size, 0, 1); } if (use_range) { if (mpctx->opts->play_frames > 0) @@ -812,10 +818,10 @@ static void handle_backstep(struct MPContext *mpctx) // The whole point is getting frames _before_ that PTS, // so apply an arbitrary offset. (In theory the offset // has to be large enough to reach the previous frame.) - seek(mpctx, (struct seek_params){ - .type = MPSEEK_ABSOLUTE, - .amount = current_pts - 1.0, - }, false); + mp_seek(mpctx, (struct seek_params){ + .type = MPSEEK_ABSOLUTE, + .amount = current_pts - 1.0, + }, false); // Don't leave hr-seek mode. If all goes right, hr-seek // mode is cancelled as soon as the frame before // current_pts is found during hr-seeking. @@ -1184,10 +1190,10 @@ void run_playloop(struct MPContext *mpctx) && (opts->gapless_audio || buffered_audio < 0.05) && (!mpctx->paused || was_restart)) { if (end_is_chapter) { - seek(mpctx, (struct seek_params){ - .type = MPSEEK_ABSOLUTE, - .amount = mpctx->timeline[mpctx->timeline_part+1].start - }, true); + mp_seek(mpctx, (struct seek_params){ + .type = MPSEEK_ABSOLUTE, + .amount = mpctx->timeline[mpctx->timeline_part+1].start + }, true); } else mpctx->stop_play = AT_END_OF_FILE; sleeptime = 0; diff --git a/stream/stream.c b/stream/stream.c index fbc324563a..3844721346 100644 --- a/stream/stream.c +++ b/stream/stream.c @@ -318,8 +318,8 @@ static int open_internal(const stream_info_t *sinfo, struct stream *underlying, if (s->seek && !(s->flags & MP_STREAM_SEEK)) s->flags |= MP_STREAM_SEEK; - if (s->flags & MP_STREAM_FAST_SKIPPING) - s->flags |= MP_STREAM_SEEK_FW; + if (!(s->flags & MP_STREAM_SEEK)) + s->end_pos = 0; s->uncached_type = s->type; @@ -387,6 +387,8 @@ static int stream_reconnect(stream_t *s) #define RECONNECT_SLEEP_MS 1000 if (!s->streaming) return 0; + if (!(s->flags & MP_STREAM_SEEK_FW)) + return 0; int64_t pos = s->pos; for (int retry = 0; retry < MAX_RECONNECT_RETRIES; retry++) { mp_msg(MSGT_STREAM, MSGL_WARN, @@ -601,7 +603,7 @@ static int stream_skip_read(struct stream *s, int64_t len) static int stream_seek_unbuffered(stream_t *s, int64_t newpos) { if (newpos != s->pos) { - if (!s->seek || !(s->flags & MP_STREAM_SEEK)) { + if (newpos > s->pos && !(s->flags & MP_STREAM_SEEK_FW)) { mp_tmsg(MSGT_STREAM, MSGL_ERR, "Can not seek in this stream\n"); return 0; } @@ -628,7 +630,7 @@ static int stream_seek_long(stream_t *s, int64_t pos) s->eof = 0; if (s->mode == STREAM_WRITE) { - if (!s->seek || !s->seek(s, pos)) + if (!(s->flags & MP_STREAM_SEEK) || !s->seek(s, pos)) return 0; return 1; } @@ -640,7 +642,9 @@ static int stream_seek_long(stream_t *s, int64_t pos) mp_msg(MSGT_STREAM, MSGL_DBG3, "Seek from %" PRId64 " to %" PRId64 " (with offset %d)\n", s->pos, pos, (int)(pos - newpos)); - if (!s->seek && (s->flags & MP_STREAM_FAST_SKIPPING) && pos >= s->pos) { + if (pos >= s->pos && !(s->flags & MP_STREAM_SEEK) && + (s->flags & MP_STREAM_FAST_SKIPPING)) + { // skipping is handled by generic code below } else if (stream_seek_unbuffered(s, newpos) >= 0) { return 0; @@ -709,6 +713,8 @@ int stream_control(stream_t *s, int cmd, void *arg) void stream_update_size(stream_t *s) { + if (!(s->flags & MP_STREAM_SEEK)) + return; uint64_t size; if (stream_control(s, STREAM_CTRL_GET_SIZE, &size) == STREAM_OK) { if (size > s->end_pos) diff --git a/video/decode/dec_video.h b/video/decode/dec_video.h index 549d208f81..03b72907ef 100644 --- a/video/decode/dec_video.h +++ b/video/decode/dec_video.h @@ -47,6 +47,11 @@ int vd_control(struct sh_video *sh_video, int cmd, void *arg); struct mp_hwdec_info { struct mp_vdpau_ctx *vdpau_ctx; struct mp_vaapi_ctx *vaapi_ctx; + // Can be used to lazily load a requested API. + // api_name is e.g. "vdpau" (like the fields above, without "_ctx") + // Can be NULL, is idempotent, caller checks _ctx fields for success/access. + void (*load_api)(struct mp_hwdec_info *info, const char *api_name); + void *load_api_ctx; }; #endif /* MPLAYER_DEC_VIDEO_H */ diff --git a/video/decode/lavc.h b/video/decode/lavc.h index 9e2533cbd5..af206bc82a 100644 --- a/video/decode/lavc.h +++ b/video/decode/lavc.h @@ -86,6 +86,8 @@ bool hwdec_check_codec_support(const char *decoder, const struct hwdec_profile_entry *table); int hwdec_get_max_refs(struct lavc_ctx *ctx); +void hwdec_request_api(struct mp_hwdec_info *info, const char *api_name); + // lavc_dr1.c int mp_codec_get_buffer(AVCodecContext *s, AVFrame *frame); void mp_codec_release_buffer(AVCodecContext *s, AVFrame *frame); diff --git a/video/decode/vaapi.c b/video/decode/vaapi.c index bb2a6c1049..4603a3c5e1 100644 --- a/video/decode/vaapi.c +++ b/video/decode/vaapi.c @@ -421,6 +421,7 @@ static int init(struct lavc_ctx *ctx) static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, const char *decoder) { + hwdec_request_api(info, "vaapi"); if (!info || !info->vaapi_ctx) return HWDEC_ERR_NO_CTX; if (!hwdec_check_codec_support(decoder, profiles)) diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c index 64542be69d..c6a939ec51 100644 --- a/video/decode/vd_lavc.c +++ b/video/decode/vd_lavc.c @@ -46,6 +46,7 @@ #include "video/img_format.h" #include "video/mp_image_pool.h" #include "video/filter/vf.h" +#include "video/decode/dec_video.h" #include "demux/stheader.h" #include "demux/demux_packet.h" #include "osdep/numcores.h" @@ -195,6 +196,12 @@ int hwdec_get_max_refs(struct lavc_ctx *ctx) return ctx->avctx->codec_id == AV_CODEC_ID_H264 ? 16 : 2; } +void hwdec_request_api(struct mp_hwdec_info *info, const char *api_name) +{ + if (info && info->load_api) + info->load_api(info, api_name); +} + static int hwdec_probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, const char *decoder, const char **hw_decoder) { diff --git a/video/mp_image.c b/video/mp_image.c index 3bc76b669e..9b96bab67d 100644 --- a/video/mp_image.c +++ b/video/mp_image.c @@ -36,6 +36,8 @@ #include "memcpy_pic.h" #include "fmt-conversion.h" +#include "video/filter/vf.h" + #if HAVE_PTHREADS #include static pthread_mutex_t refcount_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -468,6 +470,26 @@ void mp_image_set_params(struct mp_image *image, image->chroma_location = params->chroma_location; } +// Set most image parameters, but not image format or size. +// Display size is used to set the PAR. +void mp_image_set_attributes(struct mp_image *image, + const struct mp_image_params *params) +{ + struct mp_image_params nparams = *params; + nparams.imgfmt = image->imgfmt; + nparams.w = image->w; + nparams.h = image->h; + if (nparams.imgfmt != params->imgfmt) + mp_image_params_guess_csp(&nparams); + if (nparams.w != params->w || nparams.h != params->h) { + if (nparams.d_w && nparams.d_h) { + vf_rescale_dsize(&nparams.d_w, &nparams.d_h, + params->w, params->h, nparams.w, nparams.h); + } + } + mp_image_set_params(image, &nparams); +} + void mp_image_set_colorspace_details(struct mp_image *image, struct mp_csp_details *csp) { diff --git a/video/mp_image.h b/video/mp_image.h index e198c4e547..f7e7353475 100644 --- a/video/mp_image.h +++ b/video/mp_image.h @@ -153,6 +153,9 @@ void mp_image_params_from_image(struct mp_image_params *params, void mp_image_set_params(struct mp_image *image, const struct mp_image_params *params); +void mp_image_set_attributes(struct mp_image *image, + const struct mp_image_params *params); + struct AVFrame; void mp_image_copy_fields_from_av_frame(struct mp_image *dst, struct AVFrame *src); diff --git a/video/out/gl_common.c b/video/out/gl_common.c index d39a09df16..f3e38a2171 100644 --- a/video/out/gl_common.c +++ b/video/out/gl_common.c @@ -1008,3 +1008,12 @@ void mp_log_source(struct mp_log *log, int lev, const char *src) src = next; } } + +extern const struct gl_hwdec_driver gl_hwdec_vaglx; + +const struct gl_hwdec_driver *mpgl_hwdec_drivers[] = { +#if HAVE_VAAPI_GLX + &gl_hwdec_vaglx, +#endif + NULL +}; diff --git a/video/out/gl_common.h b/video/out/gl_common.h index 41d30bb001..4722f3d52e 100644 --- a/video/out/gl_common.h +++ b/video/out/gl_common.h @@ -164,6 +164,45 @@ void mpgl_set_backend_w32(MPGLContext *ctx); void mpgl_set_backend_x11(MPGLContext *ctx); void mpgl_set_backend_wayland(MPGLContext *ctx); +struct mp_hwdec_info; + +struct gl_hwdec { + const struct gl_hwdec_driver *driver; + struct mp_log *log; + struct MPGLContext *mpgl; + struct mp_hwdec_info *info; + // For free use by hwdec driver + void *priv; + // hwdec backends must set this to an IMGFMT_ that has an equivalent + // internal representation in gl_video.c as the hardware texture. + // It's used to build the rendering chain, and also as screenshot format. + int converted_imgfmt; +}; + +struct gl_hwdec_driver { + // Same name as used by mp_hwdec_info->load_api() + const char *api_name; + // Test whether the given IMGFMT_ is supported. + bool (*query_format)(int imgfmt); + // Create the hwdec device. It must fill in hw->info, if applicable. + // This also must set hw->converted_imgfmt. + int (*create)(struct gl_hwdec *hw); + // Prepare for rendering video. (E.g. create textures.) + // Called on initialization, and every time the video size changes. + int (*reinit)(struct gl_hwdec *hw, int w, int h); + // Return textures that contain the given hw_image. + // Note that the caller keeps a reference to hw_image until unbind_image + // is called, so the callee doesn't need to do that. + int (*load_image)(struct gl_hwdec *hw, struct mp_image *hw_image, + GLuint *out_textures); + // Undo load_image(). The user of load_image() calls this when the textures + // are not needed anymore. + void (*unload_image)(struct gl_hwdec *hw); + void (*destroy)(struct gl_hwdec *hw); +}; + +extern const struct gl_hwdec_driver *mpgl_hwdec_drivers[]; + void *mp_getdladdr(const char *s); void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *), diff --git a/video/out/gl_hwdec_vaglx.c b/video/out/gl_hwdec_vaglx.c new file mode 100644 index 0000000000..9e1030fe49 --- /dev/null +++ b/video/out/gl_hwdec_vaglx.c @@ -0,0 +1,140 @@ +/* + * This file is part of mpv. + * + * Parts based on the MPlayer VA-API patch (see vo_vaapi.c). + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see . + */ + +#include +#include + +#include +#include + +#include "x11_common.h" +#include "gl_common.h" +#include "video/vaapi.h" +#include "video/decode/dec_video.h" + +struct priv { + struct mp_vaapi_ctx *ctx; + VADisplay *display; + GLuint gl_texture; + void *vaglx_surface; +}; + +static bool query_format(int imgfmt) +{ + return imgfmt == IMGFMT_VAAPI; +} + +static void destroy_texture(struct gl_hwdec *hw) +{ + struct priv *p = hw->priv; + VAStatus status; + + if (p->vaglx_surface) { + status = vaDestroySurfaceGLX(p->display, p->vaglx_surface); + check_va_status(status, "vaDestroySurfaceGLX()"); + p->vaglx_surface = NULL; + } + + glDeleteTextures(1, &p->gl_texture); + p->gl_texture = 0; +} + +static void destroy(struct gl_hwdec *hw) +{ + struct priv *p = hw->priv; + destroy_texture(hw); + va_destroy(p->ctx); +} + +static int create(struct gl_hwdec *hw) +{ + if (hw->info->vaapi_ctx) + return -1; + if (!hw->mpgl->vo->x11 || !glXGetCurrentContext()) + return -1; + struct priv *p = talloc_zero(hw, struct priv); + hw->priv = p; + p->display = vaGetDisplayGLX(hw->mpgl->vo->x11->display); + if (!p->display) + return -1; + p->ctx = va_initialize(p->display); + if (!p->ctx) { + vaTerminate(p->display); + return -1; + } + hw->info->vaapi_ctx = p->ctx; + hw->converted_imgfmt = IMGFMT_RGBA; + return 0; +} + +static int reinit(struct gl_hwdec *hw, int w, int h) +{ + struct priv *p = hw->priv; + GL *gl = hw->mpgl->gl; + VAStatus status; + + destroy_texture(hw); + + gl->GenTextures(1, &p->gl_texture); + gl->BindTexture(GL_TEXTURE_2D, p->gl_texture); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + gl->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); + gl->BindTexture(GL_TEXTURE_2D, 0); + + status = vaCreateSurfaceGLX(p->display, GL_TEXTURE_2D, + p->gl_texture, &p->vaglx_surface); + return check_va_status(status, "vaCreateSurfaceGLX()") ? 0 : -1; +} + +static int load_image(struct gl_hwdec *hw, struct mp_image *hw_image, + GLuint *out_textures) +{ + struct priv *p = hw->priv; + VAStatus status; + + if (!p->vaglx_surface) + return -1; + + status = vaCopySurfaceGLX(p->display, p->vaglx_surface, + va_surface_id_in_mp_image(hw_image), + va_get_colorspace_flag(hw_image->colorspace)); + if (!check_va_status(status, "vaCopySurfaceGLX()")) + return -1; + + out_textures[0] = p->gl_texture; + return 0; +} + +static void unload_image(struct gl_hwdec *hw) +{ +} + +const struct gl_hwdec_driver gl_hwdec_vaglx = { + .api_name = "vaapi", + .query_format = query_format, + .create = create, + .reinit = reinit, + .load_image = load_image, + .unload_image = unload_image, + .destroy = destroy, +}; diff --git a/video/out/gl_video.c b/video/out/gl_video.c index 3151927be6..bcab017133 100644 --- a/video/out/gl_video.c +++ b/video/out/gl_video.c @@ -114,6 +114,7 @@ struct texplane { struct video_image { struct texplane planes[4]; bool image_flipped; + struct mp_image *hwimage; // if hw decoding is active }; struct scaler { @@ -202,6 +203,9 @@ struct gl_video { int last_dither_matrix_size; float *last_dither_matrix; + struct gl_hwdec *hwdec; + bool hwdec_active; + void *scratch; }; @@ -1129,7 +1133,7 @@ static void reinit_rendering(struct gl_video *p) uninit_rendering(p); - if (!p->image.planes[0].gl_texture) + if (!p->image_format) return; for (int n = 0; n < 2; n++) @@ -1196,17 +1200,41 @@ void gl_video_set_lut3d(struct gl_video *p, struct lut3d *lut3d) debug_check_gl(p, "after 3d lut creation"); } -static void set_image_textures(struct gl_video *p, struct video_image *vimg) +static void set_image_textures(struct gl_video *p, struct video_image *vimg, + GLuint imgtex[4]) +{ + GL *gl = p->gl; + GLuint dummy[4]; + if (!imgtex) + imgtex = dummy; + + if (p->hwdec_active) { + assert(vimg->hwimage); + p->hwdec->driver->load_image(p->hwdec, vimg->hwimage, imgtex); + } else { + for (int n = 0; n < p->plane_count; n++) + imgtex[n] = vimg->planes[n].gl_texture; + } + + for (int n = 0; n < 4; n++) { + gl->ActiveTexture(GL_TEXTURE0 + n); + gl->BindTexture(GL_TEXTURE_2D, imgtex[n]); + } + gl->ActiveTexture(GL_TEXTURE0); +} + +static void unset_image_textures(struct gl_video *p) { GL *gl = p->gl; - for (int n = 0; n < p->plane_count; n++) { - struct texplane *plane = &vimg->planes[n]; - + for (int n = 0; n < 4; n++) { gl->ActiveTexture(GL_TEXTURE0 + n); - gl->BindTexture(GL_TEXTURE_2D, plane->gl_texture); + gl->BindTexture(GL_TEXTURE_2D, 0); } gl->ActiveTexture(GL_TEXTURE0); + + if (p->hwdec_active) + p->hwdec->driver->unload_image(p->hwdec); } static void init_video(struct gl_video *p) @@ -1243,21 +1271,27 @@ static void init_video(struct gl_video *p) plane->w = full_w >> p->image_desc.xs[n]; plane->h = full_h >> p->image_desc.ys[n]; - texture_size(p, plane->w, plane->h, - &plane->tex_w, &plane->tex_h); + if (p->hwdec_active) { + // We expect hwdec backends to allocate exact size + plane->tex_w = plane->w; + plane->tex_h = plane->h; + } else { + texture_size(p, plane->w, plane->h, + &plane->tex_w, &plane->tex_h); + + gl->ActiveTexture(GL_TEXTURE0 + n); + gl->GenTextures(1, &plane->gl_texture); + gl->BindTexture(GL_TEXTURE_2D, plane->gl_texture); + + gl->TexImage2D(GL_TEXTURE_2D, 0, plane->gl_internal_format, + plane->tex_w, plane->tex_h, 0, + plane->gl_format, plane->gl_type, NULL); + + default_tex_params(gl, GL_TEXTURE_2D, GL_LINEAR); + } MP_VERBOSE(p, "Texture for plane %d: %dx%d\n", n, plane->tex_w, plane->tex_h); - - gl->ActiveTexture(GL_TEXTURE0 + n); - gl->GenTextures(1, &plane->gl_texture); - gl->BindTexture(GL_TEXTURE_2D, plane->gl_texture); - - gl->TexImage2D(GL_TEXTURE_2D, 0, plane->gl_internal_format, - plane->tex_w, plane->tex_h, 0, - plane->gl_format, plane->gl_type, NULL); - - default_tex_params(gl, GL_TEXTURE_2D, GL_LINEAR); } gl->ActiveTexture(GL_TEXTURE0); @@ -1266,6 +1300,11 @@ static void init_video(struct gl_video *p) debug_check_gl(p, "after video texture creation"); + if (p->hwdec_active) { + if (p->hwdec->driver->reinit(p->hwdec, p->image_w, p->image_h) < 0) + MP_ERR(p, "Initializing hardware ddecoding video texture failed.\n"); + } + reinit_rendering(p); } @@ -1287,6 +1326,7 @@ static void uninit_video(struct gl_video *p) plane->buffer_ptr = NULL; plane->buffer_size = 0; } + mp_image_unrefp(&vimg->hwimage); fbotex_uninit(p, &p->indirect_fbo); fbotex_uninit(p, &p->scale_sep_fbo); @@ -1373,14 +1413,15 @@ void gl_video_render_frame(struct gl_video *p) // Order of processing: // [indirect -> [scale_sep ->]] final - set_image_textures(p, vimg); + GLuint imgtex[4] = {0}; + set_image_textures(p, vimg, imgtex); struct fbotex chain = { .vp_w = p->image_w, .vp_h = p->image_h, .tex_w = p->texture_w, .tex_h = p->texture_h, - .texture = vimg->planes[0].gl_texture, + .texture = imgtex[0], }; handle_pass(p, &chain, &p->indirect_fbo, p->indirect_program); @@ -1441,6 +1482,8 @@ void gl_video_render_frame(struct gl_video *p) gl->UseProgram(0); + unset_image_textures(p); + p->frames_rendered++; debug_check_gl(p, "after video rendering"); @@ -1552,16 +1595,21 @@ static bool get_image(struct gl_video *p, struct mp_image *mpi) void gl_video_upload_image(struct gl_video *p, struct mp_image *mpi) { GL *gl = p->gl; - int n; - - assert(mpi->num_planes == p->plane_count); struct video_image *vimg = &p->image; + if (p->hwdec_active) { + mp_image_setrefp(&vimg->hwimage, mpi); + p->have_image = true; + return; + } + + assert(mpi->num_planes == p->plane_count); + mp_image_t mpi2 = *mpi; bool pbo = false; if (!vimg->planes[0].buffer_ptr && get_image(p, &mpi2)) { - for (n = 0; n < p->plane_count; n++) { + for (int n = 0; n < p->plane_count; n++) { int line_bytes = mpi->plane_w[n] * p->image_desc.bytes[n]; memcpy_pic(mpi2.planes[n], mpi->planes[n], line_bytes, mpi->plane_h[n], mpi2.stride[n], mpi->stride[n]); @@ -1570,7 +1618,7 @@ void gl_video_upload_image(struct gl_video *p, struct mp_image *mpi) pbo = true; } vimg->image_flipped = mpi->stride[0] < 0; - for (n = 0; n < p->plane_count; n++) { + for (int n = 0; n < p->plane_count; n++) { struct texplane *plane = &vimg->planes[n]; void *plane_ptr = mpi->planes[n]; if (pbo) { @@ -1598,10 +1646,11 @@ struct mp_image *gl_video_download_image(struct gl_video *p) struct video_image *vimg = &p->image; - if (!p->have_image || !vimg->planes[0].gl_texture) + if (!p->have_image) return NULL; - assert(p->image_format == p->image_params.imgfmt); + set_image_textures(p, vimg, NULL); + assert(p->texture_w >= p->image_params.w); assert(p->texture_h >= p->image_params.h); @@ -1611,12 +1660,12 @@ struct mp_image *gl_video_download_image(struct gl_video *p) for (int n = 0; n < p->plane_count; n++) { struct texplane *plane = &vimg->planes[n]; gl->ActiveTexture(GL_TEXTURE0 + n); - gl->BindTexture(GL_TEXTURE_2D, plane->gl_texture); glDownloadTex(gl, GL_TEXTURE_2D, plane->gl_format, plane->gl_type, image->planes[n], image->stride[n]); } - gl->ActiveTexture(GL_TEXTURE0); - mp_image_set_params(image, &p->image_params); + mp_image_set_attributes(image, &p->image_params); + + unset_image_textures(p); return image; } @@ -1884,6 +1933,12 @@ static bool init_format(int fmt, struct gl_video *init) if (!init) init = &dummy; + init->hwdec_active = false; + if (init->hwdec && init->hwdec->driver->query_format(fmt)) { + fmt = init->hwdec->converted_imgfmt; + init->hwdec_active = true; + } + struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(fmt); if (!desc.id) return false; @@ -1985,9 +2040,10 @@ static bool init_format(int fmt, struct gl_video *init) return true; } -bool gl_video_check_format(int mp_format) +bool gl_video_check_format(struct gl_video *p, int mp_format) { - return init_format(mp_format, NULL); + struct gl_video tmp = *p; + return init_format(mp_format, &tmp); } void gl_video_config(struct gl_video *p, struct mp_image_params *params) @@ -2012,6 +2068,7 @@ void gl_video_config(struct gl_video *p, struct mp_image_params *params) p->colorspace = csp; p->have_image = false; + mp_image_unrefp(&p->image.hwimage); } void gl_video_set_output_depth(struct gl_video *p, int r, int g, int b) @@ -2132,3 +2189,9 @@ void gl_video_resize_redraw(struct gl_video *p, int w, int h) gl_video_render_frame(p); mpgl_osd_redraw_cb(p->osd, draw_osd_cb, p); } + +void gl_video_set_hwdec(struct gl_video *p, struct gl_hwdec *hwdec) +{ + p->hwdec = hwdec; + mp_image_unrefp(&p->image.hwimage); +} diff --git a/video/out/gl_video.h b/video/out/gl_video.h index dcec9f3888..cab5f0f077 100644 --- a/video/out/gl_video.h +++ b/video/out/gl_video.h @@ -56,6 +56,7 @@ struct gl_video; struct gl_video *gl_video_init(GL *gl, struct mp_log *log); void gl_video_uninit(struct gl_video *p); void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts); +bool gl_video_check_format(struct gl_video *p, int mp_format); void gl_video_config(struct gl_video *p, struct mp_image_params *params); void gl_video_set_output_depth(struct gl_video *p, int r, int g, int b); void gl_video_set_lut3d(struct gl_video *p, struct lut3d *lut3d); @@ -73,6 +74,7 @@ bool gl_video_get_equalizer(struct gl_video *p, const char *name, int *val); void gl_video_set_debug(struct gl_video *p, bool enable); void gl_video_resize_redraw(struct gl_video *p, int w, int h); -bool gl_video_check_format(int mp_format); +struct gl_hwdec; +void gl_video_set_hwdec(struct gl_video *p, struct gl_hwdec *hwdec); #endif diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c index cf1b3b3400..8572f587a4 100644 --- a/video/out/vo_opengl.c +++ b/video/out/vo_opengl.c @@ -48,6 +48,7 @@ #include "gl_osd.h" #include "filter_kernels.h" #include "video/memcpy_pic.h" +#include "video/decode/dec_video.h" #include "gl_video.h" #include "gl_lcms.h" @@ -58,6 +59,8 @@ struct gl_priv { struct gl_video *renderer; + struct gl_hwdec *hwdec; + // Options struct gl_video_opts *renderer_opts; struct mp_icc_opts *icc_opts; @@ -134,8 +137,9 @@ static void draw_image(struct vo *vo, mp_image_t *mpi) static int query_format(struct vo *vo, uint32_t format) { + struct gl_priv *p = vo->priv; int caps = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_FLIP; - if (!gl_video_check_format(format)) + if (!gl_video_check_format(p->renderer, format)) return 0; return caps; } @@ -191,6 +195,68 @@ static int reconfig(struct vo *vo, struct mp_image_params *params, int flags) return 0; } + +static void load_hwdec_driver(struct gl_priv *p, + const struct gl_hwdec_driver *drv) +{ + assert(!p->hwdec); + struct gl_hwdec *hwdec = talloc(NULL, struct gl_hwdec); + *hwdec = (struct gl_hwdec) { + .driver = drv, + .log = mp_log_new(hwdec, p->vo->log, drv->api_name), + .mpgl = p->glctx, + .info = talloc_zero(hwdec, struct mp_hwdec_info), + }; + mpgl_lock(p->glctx); + if (hwdec->driver->create(hwdec) < 0) { + mpgl_unlock(p->glctx); + talloc_free(hwdec); + MP_ERR(p->vo, "Couldn't load hwdec driver '%s'\n", drv->api_name); + return; + } + p->hwdec = hwdec; + gl_video_set_hwdec(p->renderer, p->hwdec); + mpgl_unlock(p->glctx); +} + +static void request_hwdec_api(struct mp_hwdec_info *info, const char *api_name) +{ + struct gl_priv *p = info->load_api_ctx; + // Load at most one hwdec API + if (p->hwdec) + return; + for (int n = 0; mpgl_hwdec_drivers[n]; n++) { + const struct gl_hwdec_driver *drv = mpgl_hwdec_drivers[n]; + if (api_name && strcmp(drv->api_name, api_name) == 0) { + load_hwdec_driver(p, drv); + if (p->hwdec) { + *info = *p->hwdec->info; + return; + } + } + } +} + +static void get_hwdec_info(struct gl_priv *p, struct mp_hwdec_info *info) +{ + info->load_api = request_hwdec_api; + info->load_api_ctx = p; + if (p->hwdec) + *info = *p->hwdec->info; +} + +static void unload_hwdec_driver(struct gl_priv *p) +{ + if (p->hwdec) { + mpgl_lock(p->glctx); + gl_video_set_hwdec(p->renderer, NULL); + p->hwdec->driver->destroy(p->hwdec); + talloc_free(p->hwdec); + p->hwdec = NULL; + mpgl_unlock(p->glctx); + } +} + static bool reparse_cmdline(struct gl_priv *p, char *args) { struct m_config *cfg = NULL; @@ -263,6 +329,10 @@ static int control(struct vo *vo, uint32_t request, void *data) mpgl_unlock(p->glctx); return true; } + case VOCTRL_GET_HWDEC_INFO: { + get_hwdec_info(p, data); + return true; + } case VOCTRL_REDRAW_FRAME: mpgl_lock(p->glctx); gl_video_render_frame(p->renderer); @@ -291,6 +361,7 @@ static void uninit(struct vo *vo) struct gl_priv *p = vo->priv; if (p->glctx) { + unload_hwdec_driver(p); if (p->renderer) gl_video_uninit(p->renderer); mpgl_uninit(p->glctx);