* [FFmpeg-devel] [PATCH] Add a Vulkan VP9 hwaccel (PR #20117)
@ 2025-08-05 6:58 Lynne
0 siblings, 0 replies; only message in thread
From: Lynne @ 2025-08-05 6:58 UTC (permalink / raw)
To: ffmpeg-devel
PR #20117 opened by Lynne
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20117
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20117.patch
This PR adds support for the VP9 Vulkan hwaccel.
The first commit uses the CBS infrastructure to parse the VP9 header. This allows us to implement a Vulkan VP9 hwaccel without adding custom parsing for a lot of extra fields that it requires.
The decoder and all hwaccels will eventually be switched to use the CBS structs and data fields to avoid double parsing, however, this is just the first step.
>From 5bf052b46f250ffa66f9e8e4b1fee040da10ac5c Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Thu, 27 Mar 2025 04:54:54 +0000
Subject: [PATCH 1/2] lavc/vp9dec: use cbs_vp9 to parse the frame header
This commit uses the CBS infrastructure to parse the VP9 header.
This allows us to implement a Vulkan VP9 hwaccel without adding custom
parsing for a lot of extra fields.
The decoder and all hwaccels will eventually be switched to use the CBS
structs and data fields to avoid double parsing, however, this is just
the first step.
---
configure | 2 +-
libavcodec/vp9.c | 40 ++++++++++++++++++++++++++++++++++++++++
libavcodec/vp9dec.h | 6 ++++++
libavcodec/vp9shared.h | 4 ++++
4 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/configure b/configure
index c0a4c3c87c..813229aa49 100755
--- a/configure
+++ b/configure
@@ -3155,7 +3155,7 @@ vp6a_decoder_select="vp6_decoder"
vp6f_decoder_select="vp6_decoder"
vp7_decoder_select="h264pred videodsp vp8dsp"
vp8_decoder_select="h264pred videodsp vp8dsp"
-vp9_decoder_select="videodsp vp9_parser vp9_superframe_split_bsf"
+vp9_decoder_select="videodsp vp9_parser cbs_vp9 vp9_superframe_split_bsf"
vvc_decoder_select="cabac cbs_h266 golomb videodsp vvc_sei"
wcmv_decoder_select="inflate_wrapper"
webp_decoder_select="vp8_decoder exif"
diff --git a/libavcodec/vp9.c b/libavcodec/vp9.c
index 141f0941b4..a385956f4f 100644
--- a/libavcodec/vp9.c
+++ b/libavcodec/vp9.c
@@ -97,6 +97,7 @@ static void vp9_tile_data_free(VP9TileData *td)
static void vp9_frame_unref(VP9Frame *f)
{
ff_progress_frame_unref(&f->tf);
+ av_refstruct_unref(&f->header_ref);
av_refstruct_unref(&f->extradata);
av_refstruct_unref(&f->hwaccel_picture_private);
f->segmentation_map = NULL;
@@ -145,6 +146,9 @@ fail:
static void vp9_frame_replace(VP9Frame *dst, const VP9Frame *src)
{
+ av_refstruct_replace(&dst->header_ref, src->header_ref);
+ dst->frame_header = src->frame_header;
+
ff_progress_frame_replace(&dst->tf, &src->tf);
av_refstruct_replace(&dst->extradata, src->extradata);
@@ -1255,6 +1259,11 @@ static av_cold int vp9_decode_free(AVCodecContext *avctx)
av_freep(&s->entries);
ff_pthread_free(s, vp9_context_offsets);
#endif
+
+ av_refstruct_unref(&s->header_ref);
+ ff_cbs_fragment_free(&s->current_frag);
+ ff_cbs_close(&s->cbc);
+
av_freep(&s->td);
return 0;
}
@@ -1557,11 +1566,27 @@ static int vp9_decode_frame(AVCodecContext *avctx, AVFrame *frame,
int size = pkt->size;
VP9Context *s = avctx->priv_data;
int ret, i, j, ref;
+ CodedBitstreamUnit *unit;
+ VP9RawFrame *rf;
+
int retain_segmap_ref = s->s.frames[REF_FRAME_SEGMAP].segmentation_map &&
(!s->s.h.segmentation.enabled || !s->s.h.segmentation.update_map);
const VP9Frame *src;
AVFrame *f;
+ ret = ff_cbs_read_packet(s->cbc, &s->current_frag, pkt);
+ if (ret < 0) {
+ ff_cbs_fragment_reset(&s->current_frag);
+ av_log(avctx, AV_LOG_ERROR, "Failed to read frame header.\n");
+ return ret;
+ }
+
+ unit = &s->current_frag.units[0];
+ rf = unit->content;
+
+ av_refstruct_replace(&s->header_ref, unit->content_ref);
+ s->frame_header = &rf->header;
+
if ((ret = decode_frame_header(avctx, data, size, &ref)) < 0) {
return ret;
} else if (ret == 0) {
@@ -1592,6 +1617,10 @@ static int vp9_decode_frame(AVCodecContext *avctx, AVFrame *frame,
vp9_frame_unref(&s->s.frames[CUR_FRAME]);
if ((ret = vp9_frame_alloc(avctx, &s->s.frames[CUR_FRAME])) < 0)
return ret;
+
+ s->s.frames[CUR_FRAME].header_ref = av_refstruct_ref(s->header_ref);
+ s->s.frames[CUR_FRAME].frame_header = s->frame_header;
+
f = s->s.frames[CUR_FRAME].tf.f;
if (s->s.h.keyframe)
f->flags |= AV_FRAME_FLAG_KEY;
@@ -1779,6 +1808,9 @@ static void vp9_decode_flush(AVCodecContext *avctx)
for (i = 0; i < 8; i++)
ff_progress_frame_unref(&s->s.refs[i]);
+ ff_cbs_fragment_reset(&s->current_frag);
+ ff_cbs_flush(s->cbc);
+
if (FF_HW_HAS_CB(avctx, flush))
FF_HW_SIMPLE_CALL(avctx, flush);
}
@@ -1791,6 +1823,10 @@ static av_cold int vp9_decode_init(AVCodecContext *avctx)
s->last_bpp = 0;
s->s.h.filter.sharpness = -1;
+ ret = ff_cbs_init(&s->cbc, AV_CODEC_ID_VP9, avctx);
+ if (ret < 0)
+ return ret;
+
#if HAVE_THREADS
if (avctx->active_thread_type & FF_THREAD_SLICE) {
ret = ff_pthread_init(s, vp9_context_offsets);
@@ -1814,6 +1850,10 @@ static int vp9_decode_update_thread_context(AVCodecContext *dst, const AVCodecCo
av_refstruct_replace(&s->frame_extradata_pool, ssrc->frame_extradata_pool);
s->frame_extradata_pool_size = ssrc->frame_extradata_pool_size;
+ av_refstruct_replace(&s->header_ref, ssrc->header_ref);
+ s->frame_header = ssrc->frame_header;
+ memcpy(s->cbc->priv_data, ssrc->cbc->priv_data, sizeof(CodedBitstreamVP9Context));
+
s->s.h.invisible = ssrc->s.h.invisible;
s->s.h.keyframe = ssrc->s.h.keyframe;
s->s.h.intraonly = ssrc->s.h.intraonly;
diff --git a/libavcodec/vp9dec.h b/libavcodec/vp9dec.h
index e41f47a82a..c3ad2bbcdb 100644
--- a/libavcodec/vp9dec.h
+++ b/libavcodec/vp9dec.h
@@ -38,6 +38,7 @@
#include "vp9dsp.h"
#include "vp9shared.h"
#include "vpx_rac.h"
+#include "cbs_vp9.h"
#define REF_INVALID_SCALE 0xFFFF
@@ -97,6 +98,11 @@ typedef struct VP9Context {
VP9SharedContext s;
VP9TileData *td;
+ CodedBitstreamContext *cbc;
+ CodedBitstreamFragment current_frag;
+ VP9RawFrame *header_ref; ///< RefStruct reference backing frame_header
+ VP9RawFrameHeader *frame_header;
+
VP9DSPContext dsp;
VideoDSPContext vdsp;
GetBitContext gb;
diff --git a/libavcodec/vp9shared.h b/libavcodec/vp9shared.h
index 8a450c26a6..d2226e0072 100644
--- a/libavcodec/vp9shared.h
+++ b/libavcodec/vp9shared.h
@@ -30,6 +30,7 @@
#include "libavutil/mem_internal.h"
#include "progressframe.h"
+#include "cbs_vp9.h"
#include "vp9.h"
enum BlockPartition {
@@ -63,6 +64,9 @@ typedef struct VP9mvrefPair {
} VP9mvrefPair;
typedef struct VP9Frame {
+ VP9RawFrame *header_ref; ///< RefStruct reference backing frame_header
+ VP9RawFrameHeader *frame_header;
+
ProgressFrame tf;
void *extradata; ///< RefStruct reference
uint8_t *segmentation_map;
--
2.49.1
>From fcf756527dd55fd723b294c0da5c7def290d8273 Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Thu, 27 Mar 2025 12:50:30 +0000
Subject: [PATCH 2/2] lavc/vp9: add Vulkan VP9 hwaccel
This commit adds a Vulkan VP9 hwaccel.
---
configure | 2 +
libavcodec/Makefile | 1 +
libavcodec/hwaccels.h | 1 +
libavcodec/vp9.c | 18 +-
libavcodec/vulkan_decode.c | 24 +++
libavcodec/vulkan_decode.h | 1 +
libavcodec/vulkan_vp9.c | 366 +++++++++++++++++++++++++++++++++++
libavutil/hwcontext_vulkan.c | 15 ++
libavutil/vulkan_functions.h | 3 +-
libavutil/vulkan_loader.h | 3 +
10 files changed, 432 insertions(+), 2 deletions(-)
create mode 100644 libavcodec/vulkan_vp9.c
diff --git a/configure b/configure
index 813229aa49..06de2a890c 100755
--- a/configure
+++ b/configure
@@ -3326,6 +3326,8 @@ vp9_vdpau_hwaccel_deps="vdpau VdpPictureInfoVP9"
vp9_vdpau_hwaccel_select="vp9_decoder"
vp9_videotoolbox_hwaccel_deps="videotoolbox"
vp9_videotoolbox_hwaccel_select="vp9_decoder"
+vp9_vulkan_hwaccel_deps="vulkan"
+vp9_vulkan_hwaccel_select="vp9_decoder"
vvc_vaapi_hwaccel_deps="vaapi VAPictureParameterBufferVVC"
vvc_vaapi_hwaccel_select="vvc_decoder"
wmv3_d3d11va_hwaccel_select="vc1_d3d11va_hwaccel"
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index f935cfbe0d..1710470ce6 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1081,6 +1081,7 @@ OBJS-$(CONFIG_VP9_NVDEC_HWACCEL) += nvdec_vp9.o
OBJS-$(CONFIG_VP9_VAAPI_HWACCEL) += vaapi_vp9.o
OBJS-$(CONFIG_VP9_VDPAU_HWACCEL) += vdpau_vp9.o
OBJS-$(CONFIG_VP9_VIDEOTOOLBOX_HWACCEL) += videotoolbox_vp9.o
+OBJS-$(CONFIG_VP9_VULKAN_HWACCEL) += vulkan_decode.o vulkan_vp9.o
OBJS-$(CONFIG_VP8_QSV_HWACCEL) += qsvdec.o
OBJS-$(CONFIG_VVC_VAAPI_HWACCEL) += vaapi_vvc.o
diff --git a/libavcodec/hwaccels.h b/libavcodec/hwaccels.h
index 0b2c725247..17fd948c15 100644
--- a/libavcodec/hwaccels.h
+++ b/libavcodec/hwaccels.h
@@ -84,6 +84,7 @@ extern const struct FFHWAccel ff_vp9_nvdec_hwaccel;
extern const struct FFHWAccel ff_vp9_vaapi_hwaccel;
extern const struct FFHWAccel ff_vp9_vdpau_hwaccel;
extern const struct FFHWAccel ff_vp9_videotoolbox_hwaccel;
+extern const struct FFHWAccel ff_vp9_vulkan_hwaccel;
extern const struct FFHWAccel ff_vvc_vaapi_hwaccel;
extern const struct FFHWAccel ff_wmv3_d3d11va_hwaccel;
extern const struct FFHWAccel ff_wmv3_d3d11va2_hwaccel;
diff --git a/libavcodec/vp9.c b/libavcodec/vp9.c
index a385956f4f..31792962b4 100644
--- a/libavcodec/vp9.c
+++ b/libavcodec/vp9.c
@@ -169,7 +169,8 @@ static int update_size(AVCodecContext *avctx, int w, int h)
CONFIG_VP9_NVDEC_HWACCEL + \
CONFIG_VP9_VAAPI_HWACCEL + \
CONFIG_VP9_VDPAU_HWACCEL + \
- CONFIG_VP9_VIDEOTOOLBOX_HWACCEL)
+ CONFIG_VP9_VIDEOTOOLBOX_HWACCEL + \
+ CONFIG_VP9_VULKAN_HWACCEL)
enum AVPixelFormat pix_fmts[HWACCEL_MAX + 2], *fmtp = pix_fmts;
VP9Context *s = avctx->priv_data;
uint8_t *p;
@@ -206,6 +207,9 @@ static int update_size(AVCodecContext *avctx, int w, int h)
#endif
#if CONFIG_VP9_VIDEOTOOLBOX_HWACCEL
*fmtp++ = AV_PIX_FMT_VIDEOTOOLBOX;
+#endif
+#if CONFIG_VP9_VULKAN_HWACCEL
+ *fmtp++ = AV_PIX_FMT_VULKAN;
#endif
break;
case AV_PIX_FMT_YUV420P12:
@@ -217,6 +221,9 @@ static int update_size(AVCodecContext *avctx, int w, int h)
#endif
#if CONFIG_VP9_VDPAU_HWACCEL
*fmtp++ = AV_PIX_FMT_VDPAU;
+#endif
+#if CONFIG_VP9_VULKAN_HWACCEL
+ *fmtp++ = AV_PIX_FMT_VULKAN;
#endif
break;
case AV_PIX_FMT_YUV444P:
@@ -224,6 +231,9 @@ static int update_size(AVCodecContext *avctx, int w, int h)
case AV_PIX_FMT_YUV444P12:
#if CONFIG_VP9_VAAPI_HWACCEL
*fmtp++ = AV_PIX_FMT_VAAPI;
+#endif
+#if CONFIG_VP9_VULKAN_HWACCEL
+ *fmtp++ = AV_PIX_FMT_VULKAN;
#endif
break;
case AV_PIX_FMT_GBRP:
@@ -231,6 +241,9 @@ static int update_size(AVCodecContext *avctx, int w, int h)
case AV_PIX_FMT_GBRP12:
#if CONFIG_VP9_VAAPI_HWACCEL
*fmtp++ = AV_PIX_FMT_VAAPI;
+#endif
+#if CONFIG_VP9_VULKAN_HWACCEL
+ *fmtp++ = AV_PIX_FMT_VULKAN;
#endif
break;
}
@@ -1919,6 +1932,9 @@ const FFCodec ff_vp9_decoder = {
#endif
#if CONFIG_VP9_VIDEOTOOLBOX_HWACCEL
HWACCEL_VIDEOTOOLBOX(vp9),
+#endif
+#if CONFIG_VP9_VULKAN_HWACCEL
+ HWACCEL_VULKAN(vp9),
#endif
NULL
},
diff --git a/libavcodec/vulkan_decode.c b/libavcodec/vulkan_decode.c
index e87aa94248..703f1bf7d6 100644
--- a/libavcodec/vulkan_decode.c
+++ b/libavcodec/vulkan_decode.c
@@ -33,6 +33,9 @@ extern const FFVulkanDecodeDescriptor ff_vk_dec_h264_desc;
#if CONFIG_HEVC_VULKAN_HWACCEL
extern const FFVulkanDecodeDescriptor ff_vk_dec_hevc_desc;
#endif
+#if CONFIG_VP9_VULKAN_HWACCEL
+extern const FFVulkanDecodeDescriptor ff_vk_dec_vp9_desc;
+#endif
#if CONFIG_AV1_VULKAN_HWACCEL
extern const FFVulkanDecodeDescriptor ff_vk_dec_av1_desc;
#endif
@@ -47,6 +50,9 @@ static const FFVulkanDecodeDescriptor *dec_descs[] = {
#if CONFIG_HEVC_VULKAN_HWACCEL
&ff_vk_dec_hevc_desc,
#endif
+#if CONFIG_VP9_VULKAN_HWACCEL
+ &ff_vk_dec_vp9_desc,
+#endif
#if CONFIG_AV1_VULKAN_HWACCEL
&ff_vk_dec_av1_desc,
#endif
@@ -71,6 +77,7 @@ static const VkVideoProfileInfoKHR *get_video_profile(FFVulkanDecodeShared *ctx,
VkStructureType profile_struct_type =
codec_id == AV_CODEC_ID_H264 ? VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_PROFILE_INFO_KHR :
codec_id == AV_CODEC_ID_HEVC ? VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_PROFILE_INFO_KHR :
+ codec_id == AV_CODEC_ID_VP9 ? VK_STRUCTURE_TYPE_VIDEO_DECODE_VP9_PROFILE_INFO_KHR :
codec_id == AV_CODEC_ID_AV1 ? VK_STRUCTURE_TYPE_VIDEO_DECODE_AV1_PROFILE_INFO_KHR :
VK_STRUCTURE_TYPE_MAX_ENUM;
if (profile_struct_type == VK_STRUCTURE_TYPE_MAX_ENUM)
@@ -681,6 +688,7 @@ static VkResult vulkan_setup_profile(AVCodecContext *avctx,
const FFVulkanDecodeDescriptor *vk_desc,
VkVideoDecodeH264CapabilitiesKHR *h264_caps,
VkVideoDecodeH265CapabilitiesKHR *h265_caps,
+ VkVideoDecodeVP9CapabilitiesKHR *vp9_caps,
VkVideoDecodeAV1CapabilitiesKHR *av1_caps,
VkVideoCapabilitiesKHR *caps,
VkVideoDecodeCapabilitiesKHR *dec_caps,
@@ -692,6 +700,7 @@ static VkResult vulkan_setup_profile(AVCodecContext *avctx,
VkVideoDecodeH264ProfileInfoKHR *h264_profile = &prof->h264_profile;
VkVideoDecodeH265ProfileInfoKHR *h265_profile = &prof->h265_profile;
+ VkVideoDecodeVP9ProfileInfoKHR *vp9_profile = &prof->vp9_profile;
VkVideoDecodeAV1ProfileInfoKHR *av1_profile = &prof->av1_profile;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(avctx->sw_pix_fmt);
@@ -717,6 +726,11 @@ static VkResult vulkan_setup_profile(AVCodecContext *avctx,
usage->pNext = h265_profile;
h265_profile->sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_PROFILE_INFO_KHR;
h265_profile->stdProfileIdc = cur_profile;
+ } else if (avctx->codec_id == AV_CODEC_ID_VP9) {
+ dec_caps->pNext = vp9_caps;
+ usage->pNext = vp9_profile;
+ vp9_profile->sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_VP9_PROFILE_INFO_KHR;
+ vp9_profile->stdProfile = cur_profile;
} else if (avctx->codec_id == AV_CODEC_ID_AV1) {
dec_caps->pNext = av1_caps;
usage->pNext = av1_profile;
@@ -777,6 +791,9 @@ static int vulkan_decode_get_profile(AVCodecContext *avctx, AVBufferRef *frames_
VkVideoDecodeH265CapabilitiesKHR h265_caps = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_CAPABILITIES_KHR,
};
+ VkVideoDecodeVP9CapabilitiesKHR vp9_caps = {
+ .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_VP9_CAPABILITIES_KHR,
+ };
VkVideoDecodeAV1CapabilitiesKHR av1_caps = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_AV1_CAPABILITIES_KHR,
};
@@ -797,12 +814,14 @@ static int vulkan_decode_get_profile(AVCodecContext *avctx, AVBufferRef *frames_
cur_profile = avctx->profile;
base_profile = avctx->codec_id == AV_CODEC_ID_H264 ? AV_PROFILE_H264_CONSTRAINED_BASELINE :
avctx->codec_id == AV_CODEC_ID_H265 ? AV_PROFILE_HEVC_MAIN :
+ avctx->codec_id == AV_CODEC_ID_VP9 ? STD_VIDEO_VP9_PROFILE_0 :
avctx->codec_id == AV_CODEC_ID_AV1 ? STD_VIDEO_AV1_PROFILE_MAIN :
0;
ret = vulkan_setup_profile(avctx, prof, hwctx, vk, vk_desc,
&h264_caps,
&h265_caps,
+ &vp9_caps,
&av1_caps,
caps,
dec_caps,
@@ -819,6 +838,7 @@ static int vulkan_decode_get_profile(AVCodecContext *avctx, AVBufferRef *frames_
ret = vulkan_setup_profile(avctx, prof, hwctx, vk, vk_desc,
&h264_caps,
&h265_caps,
+ &vp9_caps,
&av1_caps,
caps,
dec_caps,
@@ -845,6 +865,7 @@ static int vulkan_decode_get_profile(AVCodecContext *avctx, AVBufferRef *frames_
max_level = avctx->codec_id == AV_CODEC_ID_H264 ? ff_vk_h264_level_to_av(h264_caps.maxLevelIdc) :
avctx->codec_id == AV_CODEC_ID_H265 ? ff_vk_h265_level_to_av(h265_caps.maxLevelIdc) :
+ avctx->codec_id == AV_CODEC_ID_VP9 ? vp9_caps.maxLevel :
avctx->codec_id == AV_CODEC_ID_AV1 ? av1_caps.maxLevel :
0;
@@ -1168,6 +1189,9 @@ static int create_empty_session_parameters(AVCodecContext *avctx,
.videoSession = ctx->common.session,
};
+ if (avctx->codec_id == AV_CODEC_ID_VP9)
+ return 0;
+
ret = vk->CreateVideoSessionParametersKHR(s->hwctx->act_dev, &session_params_create,
s->hwctx->alloc, &ctx->empty_session_params);
if (ret != VK_SUCCESS) {
diff --git a/libavcodec/vulkan_decode.h b/libavcodec/vulkan_decode.h
index bf6506f280..bf51d5a170 100644
--- a/libavcodec/vulkan_decode.h
+++ b/libavcodec/vulkan_decode.h
@@ -38,6 +38,7 @@ typedef struct FFVulkanDecodeDescriptor {
typedef struct FFVulkanDecodeProfileData {
VkVideoDecodeH264ProfileInfoKHR h264_profile;
VkVideoDecodeH265ProfileInfoKHR h265_profile;
+ VkVideoDecodeVP9ProfileInfoKHR vp9_profile;
VkVideoDecodeAV1ProfileInfoKHR av1_profile;
VkVideoDecodeUsageInfoKHR usage;
VkVideoProfileInfoKHR profile;
diff --git a/libavcodec/vulkan_vp9.c b/libavcodec/vulkan_vp9.c
new file mode 100644
index 0000000000..6713ab2218
--- /dev/null
+++ b/libavcodec/vulkan_vp9.c
@@ -0,0 +1,366 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "vp9shared.h"
+
+#include "vulkan_decode.h"
+
+const FFVulkanDecodeDescriptor ff_vk_dec_vp9_desc = {
+ .codec_id = AV_CODEC_ID_VP9,
+ .decode_extension = FF_VK_EXT_VIDEO_DECODE_VP9,
+ .queue_flags = VK_QUEUE_VIDEO_DECODE_BIT_KHR,
+ .decode_op = VK_VIDEO_CODEC_OPERATION_DECODE_VP9_BIT_KHR,
+ .ext_props = {
+ .extensionName = VK_STD_VULKAN_VIDEO_CODEC_VP9_DECODE_EXTENSION_NAME,
+ .specVersion = VK_STD_VULKAN_VIDEO_CODEC_VP9_DECODE_SPEC_VERSION,
+ },
+};
+
+typedef struct VP9VulkanDecodePicture {
+ FFVulkanDecodePicture vp;
+
+ /* TODO: investigate if this can be removed to make decoding completely
+ * independent. */
+ FFVulkanDecodeContext *dec;
+
+ /* Current picture */
+ StdVideoVP9ColorConfig color_config;
+ StdVideoVP9Segmentation segmentation;
+ StdVideoVP9LoopFilter loop_filter;
+ StdVideoDecodeVP9PictureInfo std_pic_info;
+ VkVideoDecodeVP9PictureInfoKHR vp9_pic_info;
+
+ const VP9Frame *ref_src[8];
+
+ uint8_t frame_id_set;
+ uint8_t frame_id;
+ uint8_t ref_frame_sign_bias_mask;
+} VP9VulkanDecodePicture;
+
+static int vk_vp9_fill_pict(AVCodecContext *avctx, const VP9Frame **ref_src,
+ VkVideoReferenceSlotInfoKHR *ref_slot, /* Main structure */
+ VkVideoPictureResourceInfoKHR *ref, /* Goes in ^ */
+ const VP9Frame *pic, int is_current)
+{
+ FFVulkanDecodeContext *dec = avctx->internal->hwaccel_priv_data;
+ FFVulkanDecodeShared *ctx = dec->shared_ctx;
+ VP9VulkanDecodePicture *hp = pic->hwaccel_picture_private;
+ FFVulkanDecodePicture *vkpic = &hp->vp;
+
+ int err = ff_vk_decode_prepare_frame(dec, pic->tf.f, vkpic, is_current,
+ dec->dedicated_dpb);
+ if (err < 0)
+ return err;
+
+ *ref = (VkVideoPictureResourceInfoKHR) {
+ .sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR,
+ .codedOffset = (VkOffset2D){ 0, 0 },
+ .codedExtent = (VkExtent2D){ pic->tf.f->width, pic->tf.f->height },
+ .baseArrayLayer = (dec->dedicated_dpb && ctx->common.layered_dpb) ?
+ hp->frame_id : 0,
+ .imageViewBinding = vkpic->view.ref[0],
+ };
+
+ *ref_slot = (VkVideoReferenceSlotInfoKHR) {
+ .sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
+ .slotIndex = hp->frame_id,
+ .pPictureResource = ref,
+ };
+
+ if (ref_src)
+ *ref_src = pic;
+
+ return 0;
+}
+
+static enum StdVideoVP9InterpolationFilter remap_interp(uint8_t is_filter_switchable,
+ uint8_t raw_interpolation_filter_type)
+{
+ static const enum StdVideoVP9InterpolationFilter remap[] = {
+ STD_VIDEO_VP9_INTERPOLATION_FILTER_EIGHTTAP_SMOOTH,
+ STD_VIDEO_VP9_INTERPOLATION_FILTER_EIGHTTAP,
+ STD_VIDEO_VP9_INTERPOLATION_FILTER_EIGHTTAP_SHARP,
+ STD_VIDEO_VP9_INTERPOLATION_FILTER_BILINEAR,
+ };
+ if (is_filter_switchable)
+ return STD_VIDEO_VP9_INTERPOLATION_FILTER_SWITCHABLE;
+ return remap[raw_interpolation_filter_type];
+}
+
+static int vk_vp9_start_frame(AVCodecContext *avctx,
+ av_unused const AVBufferRef *buffer_ref,
+ av_unused const uint8_t *buffer,
+ av_unused uint32_t size)
+{
+ int err;
+ int ref_count = 0;
+ const VP9SharedContext *s = avctx->priv_data;
+
+ const VP9Frame *pic = &s->frames[CUR_FRAME];
+ FFVulkanDecodeContext *dec = avctx->internal->hwaccel_priv_data;
+ uint8_t profile = (pic->frame_header->profile_high_bit << 1) | pic->frame_header->profile_low_bit;
+
+ VP9VulkanDecodePicture *ap = pic->hwaccel_picture_private;
+ FFVulkanDecodePicture *vp = &ap->vp;
+
+ if (!ap->frame_id_set) {
+ unsigned slot_idx = 0;
+ for (unsigned i = 0; i < 32; i++) {
+ if (!(dec->frame_id_alloc_mask & (1 << i))) {
+ slot_idx = i;
+ break;
+ }
+ }
+ ap->frame_id = slot_idx;
+ ap->frame_id_set = 1;
+ dec->frame_id_alloc_mask |= (1 << slot_idx);
+ }
+
+ for (int i = 0; i < STD_VIDEO_VP9_REFS_PER_FRAME; i++) {
+ const int idx = pic->frame_header->ref_frame_idx[i];
+ const VP9Frame *ref_frame = &s->frames[idx];
+ VP9VulkanDecodePicture *hp = ref_frame->hwaccel_picture_private;
+ int found = 0;
+
+ if (!ref_frame->tf.f)
+ continue;
+
+ for (int j = 0; j < ref_count; j++) {
+ if (vp->ref_slots[j].slotIndex == hp->frame_id) {
+ found = 1;
+ break;
+ }
+ }
+ if (found)
+ continue;
+
+ err = vk_vp9_fill_pict(avctx, &ap->ref_src[ref_count],
+ &vp->ref_slots[ref_count], &vp->refs[ref_count],
+ ref_frame, 0);
+ if (err < 0)
+ return err;
+
+ ref_count++;
+ }
+
+ err = vk_vp9_fill_pict(avctx, NULL, &vp->ref_slot, &vp->ref,
+ pic, 1);
+ if (err < 0)
+ return err;
+
+ ap->loop_filter = (StdVideoVP9LoopFilter) {
+ .flags = (StdVideoVP9LoopFilterFlags) {
+ .loop_filter_delta_enabled = pic->frame_header->loop_filter_delta_enabled,
+ .loop_filter_delta_update = pic->frame_header->loop_filter_delta_update,
+ },
+ .loop_filter_level = pic->frame_header->loop_filter_level,
+ .loop_filter_sharpness = pic->frame_header->loop_filter_sharpness,
+ .update_ref_delta = 0x0,
+ .update_mode_delta = 0x0,
+ };
+
+ for (int i = 0; i < 2; i++)
+ ap->loop_filter.update_mode_delta |= pic->frame_header->update_mode_delta[i];
+
+ for (int i = 0; i < STD_VIDEO_VP9_MAX_REF_FRAMES; i++) {
+ ap->loop_filter.loop_filter_ref_deltas[i] = pic->frame_header->loop_filter_ref_deltas[i];
+ ap->loop_filter.update_ref_delta |= pic->frame_header->update_ref_delta[i];
+ }
+ for (int i = 0; i < STD_VIDEO_VP9_LOOP_FILTER_ADJUSTMENTS; i++)
+ ap->loop_filter.loop_filter_mode_deltas[i] = pic->frame_header->loop_filter_mode_deltas[i];
+
+ ap->segmentation = (StdVideoVP9Segmentation) {
+ .flags = (StdVideoVP9SegmentationFlags) {
+ .segmentation_update_map = pic->frame_header->segmentation_update_map,
+ .segmentation_temporal_update = pic->frame_header->segmentation_temporal_update,
+ .segmentation_update_data = pic->frame_header->segmentation_update_data,
+ .segmentation_abs_or_delta_update = pic->frame_header->segmentation_abs_or_delta_update,
+ },
+ };
+
+ for (int i = 0; i < STD_VIDEO_VP9_MAX_SEGMENTATION_TREE_PROBS; i++)
+ ap->segmentation.segmentation_tree_probs[i] = pic->frame_header->segmentation_tree_probs[i];
+ for (int i = 0; i < STD_VIDEO_VP9_MAX_SEGMENTATION_PRED_PROB; i++)
+ ap->segmentation.segmentation_pred_prob[i] = pic->frame_header->segmentation_pred_prob[i];
+ for (int i = 0; i < STD_VIDEO_VP9_MAX_SEGMENTS; i++) {
+ ap->segmentation.FeatureEnabled[i] = 0x0;
+ for (int j = 0; j < STD_VIDEO_VP9_SEG_LVL_MAX; j++) {
+ ap->segmentation.FeatureEnabled[i] |= pic->frame_header->feature_enabled[i][j];
+ ap->segmentation.FeatureData[i][j] = pic->frame_header->feature_sign[i][j] ?
+ -pic->frame_header->feature_value[i][j] :
+ +pic->frame_header->feature_value[i][j];
+ }
+ }
+
+ ap->color_config = (StdVideoVP9ColorConfig) {
+ .flags = (StdVideoVP9ColorConfigFlags) {
+ .color_range = pic->frame_header->color_range,
+ },
+ .BitDepth = profile < 2 ? 8 :
+ pic->frame_header->ten_or_twelve_bit ? 12 : 10,
+ .subsampling_x = pic->frame_header->subsampling_x,
+ .subsampling_y = pic->frame_header->subsampling_y,
+ .color_space = pic->frame_header->color_space,
+ };
+
+ ap->std_pic_info = (StdVideoDecodeVP9PictureInfo) {
+ .flags = (StdVideoDecodeVP9PictureInfoFlags) {
+ .error_resilient_mode = pic->frame_header->error_resilient_mode,
+ .intra_only = pic->frame_header->intra_only,
+ .allow_high_precision_mv = pic->frame_header->allow_high_precision_mv,
+ .refresh_frame_context = pic->frame_header->refresh_frame_context,
+ .frame_parallel_decoding_mode = pic->frame_header->frame_parallel_decoding_mode,
+ .segmentation_enabled = pic->frame_header->segmentation_enabled,
+ .show_frame = pic->frame_header->segmentation_enabled,
+ .UsePrevFrameMvs = s->h.use_last_frame_mvs,
+ },
+ .profile = profile,
+ .frame_type = pic->frame_header->frame_type,
+ .frame_context_idx = pic->frame_header->frame_context_idx,
+ .reset_frame_context = pic->frame_header->reset_frame_context,
+ .refresh_frame_flags = pic->frame_header->refresh_frame_flags,
+ .ref_frame_sign_bias_mask = 0x0,
+ .interpolation_filter = remap_interp(pic->frame_header->is_filter_switchable,
+ pic->frame_header->raw_interpolation_filter_type),
+ .base_q_idx = pic->frame_header->base_q_idx,
+ .delta_q_y_dc = pic->frame_header->delta_q_y_dc,
+ .delta_q_uv_dc = pic->frame_header->delta_q_uv_dc,
+ .delta_q_uv_ac = pic->frame_header->delta_q_uv_ac,
+ .tile_cols_log2 = pic->frame_header->tile_cols_log2,
+ .tile_rows_log2 = pic->frame_header->tile_rows_log2,
+ /* Reserved */
+ .pColorConfig = &ap->color_config,
+ .pLoopFilter = &ap->loop_filter,
+ .pSegmentation = &ap->segmentation,
+ };
+
+ for (int i = 0; i < 3; i++)
+ ap->std_pic_info.ref_frame_sign_bias_mask |= pic->frame_header->ref_frame_sign_bias[i] << i;
+
+ ap->vp9_pic_info = (VkVideoDecodeVP9PictureInfoKHR) {
+ .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_VP9_PICTURE_INFO_KHR,
+ .pStdPictureInfo = &ap->std_pic_info,
+ .uncompressedHeaderOffset = 0,
+ .compressedHeaderOffset = s->h.uncompressed_header_size,
+ .tilesOffset = s->h.uncompressed_header_size +
+ s->h.compressed_header_size,
+ };
+
+ for (int i = 0; i < STD_VIDEO_VP9_REFS_PER_FRAME; i++) {
+ const int idx = pic->frame_header->ref_frame_idx[i];
+ const VP9Frame *ref_frame = &s->frames[idx];
+ VP9VulkanDecodePicture *hp = ref_frame->hwaccel_picture_private;
+
+ if (!ref_frame->tf.f)
+ ap->vp9_pic_info.referenceNameSlotIndices[i] = -1;
+ else
+ ap->vp9_pic_info.referenceNameSlotIndices[i] = hp->frame_id;
+ }
+
+ vp->decode_info = (VkVideoDecodeInfoKHR) {
+ .sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_INFO_KHR,
+ .pNext = &ap->vp9_pic_info,
+ .flags = 0x0,
+ .pSetupReferenceSlot = &vp->ref_slot,
+ .referenceSlotCount = ref_count,
+ .pReferenceSlots = vp->ref_slots,
+ .dstPictureResource = (VkVideoPictureResourceInfoKHR) {
+ .sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR,
+ .codedOffset = (VkOffset2D){ 0, 0 },
+ .codedExtent = (VkExtent2D){ pic->tf.f->width, pic->tf.f->height },
+ .baseArrayLayer = 0,
+ .imageViewBinding = vp->view.out[0],
+ },
+ };
+
+ ap->dec = dec;
+
+ return 0;
+}
+
+static int vk_vp9_decode_slice(AVCodecContext *avctx,
+ const uint8_t *data,
+ uint32_t size)
+{
+ int err;
+ const VP9SharedContext *s = avctx->priv_data;
+ VP9VulkanDecodePicture *ap = s->frames[CUR_FRAME].hwaccel_picture_private;
+ FFVulkanDecodePicture *vp = &ap->vp;
+
+ err = ff_vk_decode_add_slice(avctx, vp, data, size, 0, NULL, NULL);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int vk_vp9_end_frame(AVCodecContext *avctx)
+{
+ const VP9SharedContext *s = avctx->priv_data;
+
+ const VP9Frame *pic = &s->frames[CUR_FRAME];
+ VP9VulkanDecodePicture *ap = pic->hwaccel_picture_private;
+ FFVulkanDecodePicture *vp = &ap->vp;
+ FFVulkanDecodePicture *rvp[STD_VIDEO_VP9_REFS_PER_FRAME] = { 0 };
+ AVFrame *rav[STD_VIDEO_VP9_REFS_PER_FRAME] = { 0 };
+
+ for (int i = 0; i < vp->decode_info.referenceSlotCount; i++) {
+ const VP9Frame *rp = ap->ref_src[i];
+ VP9VulkanDecodePicture *rhp = rp->hwaccel_picture_private;
+
+ rvp[i] = &rhp->vp;
+ rav[i] = ap->ref_src[i]->tf.f;
+ }
+
+ av_log(avctx, AV_LOG_VERBOSE, "Decoding frame, %"SIZE_SPECIFIER" bytes\n",
+ vp->slices_size);
+
+ return ff_vk_decode_frame(avctx, pic->tf.f, vp, rav, rvp);
+}
+
+static void vk_vp9_free_frame_priv(AVRefStructOpaque _hwctx, void *data)
+{
+ AVHWDeviceContext *hwctx = _hwctx.nc;
+ VP9VulkanDecodePicture *ap = data;
+
+ /* Workaround for a spec issue. */
+ if (ap->frame_id_set)
+ ap->dec->frame_id_alloc_mask &= ~(1 << ap->frame_id);
+
+ /* Free frame resources, this also destroys the session parameters. */
+ ff_vk_decode_free_frame(hwctx, &ap->vp);
+}
+
+const FFHWAccel ff_vp9_vulkan_hwaccel = {
+ .p.name = "av1_vulkan",
+ .p.type = AVMEDIA_TYPE_VIDEO,
+ .p.id = AV_CODEC_ID_VP9,
+ .p.pix_fmt = AV_PIX_FMT_VULKAN,
+ .start_frame = &vk_vp9_start_frame,
+ .decode_slice = &vk_vp9_decode_slice,
+ .end_frame = &vk_vp9_end_frame,
+ .free_frame_priv = &vk_vp9_free_frame_priv,
+ .frame_priv_data_size = sizeof(VP9VulkanDecodePicture),
+ .init = &ff_vk_decode_init,
+ .update_thread_context = &ff_vk_update_thread_context,
+ .flush = &ff_vk_decode_flush,
+ .uninit = &ff_vk_decode_uninit,
+ .frame_params = &ff_vk_frame_params,
+ .priv_data_size = sizeof(FFVulkanDecodeContext),
+ .caps_internal = HWACCEL_CAP_ASYNC_SAFE,
+};
diff --git a/libavutil/hwcontext_vulkan.c b/libavutil/hwcontext_vulkan.c
index 96f5075d64..445e308413 100644
--- a/libavutil/hwcontext_vulkan.c
+++ b/libavutil/hwcontext_vulkan.c
@@ -90,6 +90,9 @@ typedef struct VulkanDeviceFeatures {
#ifdef VK_KHR_video_maintenance2
VkPhysicalDeviceVideoMaintenance2FeaturesKHR video_maintenance_2;
#endif
+#ifdef VK_KHR_video_decode_vp9
+ VkPhysicalDeviceVideoDecodeVP9FeaturesKHR vp9_decode;
+#endif
VkPhysicalDeviceShaderObjectFeaturesEXT shader_object;
VkPhysicalDeviceCooperativeMatrixFeaturesKHR cooperative_matrix;
@@ -227,6 +230,10 @@ static void device_features_init(AVHWDeviceContext *ctx, VulkanDeviceFeatures *f
FF_VK_STRUCT_EXT(s, &feats->device, &feats->video_maintenance_2, FF_VK_EXT_VIDEO_MAINTENANCE_2,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_MAINTENANCE_2_FEATURES_KHR);
#endif
+#ifdef VK_KHR_video_decode_vp9
+ FF_VK_STRUCT_EXT(s, &feats->device, &feats->vp9_decode, FF_VK_EXT_VIDEO_DECODE_VP9,
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_DECODE_VP9_FEATURES_KHR);
+#endif
FF_VK_STRUCT_EXT(s, &feats->device, &feats->shader_object, FF_VK_EXT_SHADER_OBJECT,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT);
@@ -298,6 +305,10 @@ static void device_features_copy_needed(VulkanDeviceFeatures *dst, VulkanDeviceF
COPY_VAL(video_maintenance_2.videoMaintenance2);
#endif
+#ifdef VK_KHR_video_decode_vp9
+ COPY_VAL(vp9_decode.videoDecodeVP9);
+#endif
+
COPY_VAL(shader_object.shaderObject);
COPY_VAL(cooperative_matrix.cooperativeMatrix);
@@ -641,6 +652,9 @@ static const VulkanOptExtension optional_device_exts[] = {
{ VK_KHR_VIDEO_DECODE_H264_EXTENSION_NAME, FF_VK_EXT_VIDEO_DECODE_H264 },
{ VK_KHR_VIDEO_ENCODE_H265_EXTENSION_NAME, FF_VK_EXT_VIDEO_ENCODE_H265 },
{ VK_KHR_VIDEO_DECODE_H265_EXTENSION_NAME, FF_VK_EXT_VIDEO_DECODE_H265 },
+#ifdef VK_KHR_video_decode_vp9
+ { VK_KHR_VIDEO_DECODE_VP9_EXTENSION_NAME, FF_VK_EXT_VIDEO_DECODE_VP9 },
+#endif
{ VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME, FF_VK_EXT_VIDEO_DECODE_AV1 },
};
@@ -1545,6 +1559,7 @@ static int setup_queue_families(AVHWDeviceContext *ctx, VkDeviceCreateInfo *cd)
PICK_QF(VK_QUEUE_VIDEO_ENCODE_BIT_KHR, VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_KHR);
PICK_QF(VK_QUEUE_VIDEO_DECODE_BIT_KHR, VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR);
+ PICK_QF(VK_QUEUE_VIDEO_DECODE_BIT_KHR, VK_VIDEO_CODEC_OPERATION_DECODE_VP9_BIT_KHR);
PICK_QF(VK_QUEUE_VIDEO_DECODE_BIT_KHR, VK_VIDEO_CODEC_OPERATION_DECODE_AV1_BIT_KHR);
av_free(qf);
diff --git a/libavutil/vulkan_functions.h b/libavutil/vulkan_functions.h
index 68fa7b802d..9fd646fa4e 100644
--- a/libavutil/vulkan_functions.h
+++ b/libavutil/vulkan_functions.h
@@ -59,7 +59,8 @@ typedef uint64_t FFVulkanExtensions;
#define FF_VK_EXT_VIDEO_DECODE_QUEUE (1ULL << 40) /* VK_KHR_video_decode_queue */
#define FF_VK_EXT_VIDEO_DECODE_H264 (1ULL << 41) /* VK_KHR_video_decode_h264 */
#define FF_VK_EXT_VIDEO_DECODE_H265 (1ULL << 42) /* VK_KHR_video_decode_h265 */
-#define FF_VK_EXT_VIDEO_DECODE_AV1 (1ULL << 43) /* VK_KHR_video_decode_av1 */
+#define FF_VK_EXT_VIDEO_DECODE_VP9 (1ULL << 43) /* VK_KHR_video_decode_av1 */
+#define FF_VK_EXT_VIDEO_DECODE_AV1 (1ULL << 44) /* VK_KHR_video_decode_av1 */
#define FF_VK_EXT_VIDEO_ENCODE_QUEUE (1ULL << 50) /* VK_KHR_video_encode_queue */
#define FF_VK_EXT_VIDEO_ENCODE_H264 (1ULL << 51) /* VK_KHR_video_encode_h264 */
diff --git a/libavutil/vulkan_loader.h b/libavutil/vulkan_loader.h
index 7e805fdd4c..37a3731feb 100644
--- a/libavutil/vulkan_loader.h
+++ b/libavutil/vulkan_loader.h
@@ -76,6 +76,9 @@ static inline uint64_t ff_vk_extensions_to_mask(const char * const *extensions,
{ VK_KHR_VIDEO_DECODE_H264_EXTENSION_NAME, FF_VK_EXT_VIDEO_DECODE_H264 },
{ VK_KHR_VIDEO_ENCODE_H265_EXTENSION_NAME, FF_VK_EXT_VIDEO_ENCODE_H265 },
{ VK_KHR_VIDEO_DECODE_H265_EXTENSION_NAME, FF_VK_EXT_VIDEO_DECODE_H265 },
+#ifdef VK_KHR_video_decode_vp9
+ { VK_KHR_VIDEO_DECODE_VP9_EXTENSION_NAME, FF_VK_EXT_VIDEO_DECODE_VP9 },
+#endif
{ VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME, FF_VK_EXT_VIDEO_DECODE_AV1 },
{ VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, FF_VK_EXT_PUSH_DESCRIPTOR },
#ifdef VK_KHR_shader_expect_assume
--
2.49.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2025-08-05 6:59 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-08-05 6:58 [FFmpeg-devel] [PATCH] Add a Vulkan VP9 hwaccel (PR #20117) Lynne
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git