- Added APV decoder wrapper - Changes in project configuration file and libavcodec Makefile - Added documentation for APV decoder wrapper
Signed-off-by: Dawid Kozinski <d.kozin...@samsung.com> --- configure | 1 + doc/decoders.texi | 27 ++ libavcodec/Makefile | 1 + libavcodec/libapvdec.c | 560 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 589 insertions(+) create mode 100644 libavcodec/libapvdec.c diff --git a/configure b/configure index fa125d383d..8c4a829035 100755 --- a/configure +++ b/configure @@ -3562,6 +3562,7 @@ libaom_av1_decoder_deps="libaom" libaom_av1_encoder_deps="libaom" libaom_av1_encoder_select="extract_extradata_bsf dovi_rpuenc" libapv_encoder_deps="liboapv" +libapv_decoder_deps="liboapv" libaribb24_decoder_deps="libaribb24" libaribcaption_decoder_deps="libaribcaption" libcelt_decoder_deps="libcelt" diff --git a/doc/decoders.texi b/doc/decoders.texi index 17bb361ffa..ffe576db77 100644 --- a/doc/decoders.texi +++ b/doc/decoders.texi @@ -397,6 +397,33 @@ without this library. @chapter Subtitles Decoders @c man begin SUBTILES DECODERS +@section liboapv + +Advanced Professional Video codec decoder wrapper. + +This decoder requires the presence of the liboapv headers and library +during configuration. You need to explicitly configure the build with +@option{--enable-liboapv}. + +@float NOTE +Many liboapv decoder options are mapped to FFmpeg global codec options, +while unique decoder options are provided through private options. +Additionally the apv-params private options allows one to pass a list +of key=value tuples as accepted by the liboapv @code{parse_apv_params} function. +@end float + +The apv project website is at @url{https://github.com/AcademySoftwareFoundation/openapv}. + +@subsection Options + +The following options are supported by the liboapv wrapper. +The apv-equivalent options or values are listed in parentheses for easy migration. + +@float NOTE +To get a more accurate and extensive documentation of the liboapv options, +invoke the command @code{apv_app_dec --help} or consult the liboapv documentation. +@end float + @section libaribb24 ARIB STD-B24 caption decoder. diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 7350f4ce07..d172e554ae 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -1130,6 +1130,7 @@ OBJS-$(CONFIG_PCM_MULAW_AT_ENCODER) += audiotoolboxenc.o OBJS-$(CONFIG_LIBAOM_AV1_DECODER) += libaomdec.o libaom.o OBJS-$(CONFIG_LIBAOM_AV1_ENCODER) += libaomenc.o libaom.o OBJS-$(CONFIG_LIBAPV_ENCODER) += libapvenc.o apv_imgb.o +OBJS-$(CONFIG_LIBAPV_DECODER) += libapvdec.o apv_imgb.o OBJS-$(CONFIG_LIBARIBB24_DECODER) += libaribb24.o ass.o OBJS-$(CONFIG_LIBARIBCAPTION_DECODER) += libaribcaption.o ass.o OBJS-$(CONFIG_LIBCELT_DECODER) += libcelt_dec.o diff --git a/libavcodec/libapvdec.c b/libavcodec/libapvdec.c new file mode 100644 index 0000000000..8bf2a12605 --- /dev/null +++ b/libavcodec/libapvdec.c @@ -0,0 +1,560 @@ +/* + * APV (Advanced Professional Video codec) decoding using APV codec library (liboapv) + * + * Copyright (C) 2025 Dawid Kozinski <d.kozin...@samsung.com> + * + * 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 <float.h> +#include <stdlib.h> + + +#include <oapv/oapv.h> + +#include "libavutil/internal.h" +#include "libavutil/common.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavutil/pixfmt.h" +#include "libavutil/imgutils.h" +#include "libavutil/cpu.h" +#include "libavutil/container_fifo.h" + +#include "avcodec.h" +#include "internal.h" +#include "packet_internal.h" +#include "codec_internal.h" +#include "profiles.h" +#include "decode.h" +#include "apv.h" +#include "apv_imgb.h" + +/** + * The structure stores all the states associated with the instance of APV decoder + */ +typedef struct ApvDecContext { + const AVClass *class; + + oapvd_t id; // apvd instance identifier @see apvd_t.h + oapvd_cdesc_t cdsc; // decoding parameters @see apvd_t.h + + oapvm_t mid; // OAPV metadata container + + int hash; // embed picture signature (HASH) for conformance checking in decoding + + int output_depth; + int output_csp; + + struct AVContainerFifo *output_fifo; + AVFrame* frames[OAPV_MAX_NUM_FRAMES]; + + int frames_count; + int total_frames_count; + int au_count; + + AVPacket *pkt; // frame data +} ApvDecContext; + +/** + * The function populates the apvd_cdsc structure. + * apvd_cdsc contains all decoder parameters that should be initialized before its use. + * + * @param[in] avctx codec context + * @param[out] cdsc contains all decoder parameters that should be initialized before its use + * + */ +static void get_conf(AVCodecContext *avctx, oapvd_cdesc_t *cdsc) +{ + /* clear apvd_cdsc structure */ + memset(cdsc, 0, sizeof(oapvd_cdesc_t)); + + /* init apvd_cdsc structure */ + cdsc->threads = OAPV_CDESC_THREADS_AUTO; +} + +static int set_extra_config(AVCodecContext *avctx, oapvd_t id, ApvDecContext *ctx) +{ + int ret = 0, size, value; + + if(ctx->hash) { + size = 4; + value = 1; + ret = oapvd_config(id, OAPV_CFG_SET_USE_FRM_HASH, &value, &size); + if (OAPV_FAILED(ret)) { + av_log(avctx, AV_LOG_ERROR, "Failed to set config for using frame hash\n"); + return AVERROR_EXTERNAL; + } + } + return ret; +} + +/** + * @param[in] info the structure that stores information of bitstream + * @param[out] avctx codec context + * @return 0 on success, negative value on failure + */ +static int export_stream_params(const oapv_au_info_t* aui, AVCodecContext *avctx) +{ + avctx->width = aui->frm_info->w; + avctx->height = aui->frm_info->h; + + switch(aui->frm_info->cs) { + case OAPV_CS_SET(OAPV_CF_YCBCR422, 10, 0): // profile 33 + avctx->pix_fmt = AV_PIX_FMT_YUV422P10LE; + break; + case OAPV_CS_SET(OAPV_CF_YCBCR422, 10, 1): + avctx->pix_fmt = AV_PIX_FMT_YUV422P10BE; + break; + case OAPV_CS_SET(OAPV_CF_YCBCR422, 12, 0): // profile 44 + avctx->pix_fmt = AV_PIX_FMT_YUV422P12LE; + break; + case OAPV_CS_SET(OAPV_CF_YCBCR422, 12, 1): + avctx->pix_fmt = AV_PIX_FMT_YUV422P12BE; + break; + case OAPV_CS_SET(OAPV_CF_YCBCR444, 10, 0): // profile 55 + avctx->pix_fmt = AV_PIX_FMT_YUV444P10LE; + break; + case OAPV_CS_SET(OAPV_CF_YCBCR444, 10, 1): + avctx->pix_fmt = AV_PIX_FMT_YUV444P10BE; + break; + case OAPV_CS_SET(OAPV_CF_YCBCR444, 12, 0): // profile 66 + avctx->pix_fmt = AV_PIX_FMT_YUV444P12LE; + break; + case OAPV_CS_SET(OAPV_CF_YCBCR444, 12, 1): + avctx->pix_fmt = AV_PIX_FMT_YUV444P12BE; + break; + case OAPV_CS_SET(OAPV_CF_YCBCR4444, 10, 0): // profile 77 + avctx->pix_fmt = AV_PIX_FMT_YUVA444P10LE; + break; + case OAPV_CS_SET(OAPV_CF_YCBCR4444, 10, 1): + avctx->pix_fmt = AV_PIX_FMT_YUVA444P10BE; + break; + case OAPV_CS_SET(OAPV_CF_YCBCR4444, 12, 0): // profile 88 + avctx->pix_fmt = AV_PIX_FMT_YUVA444P12LE; + break; + case OAPV_CS_SET(OAPV_CF_YCBCR4444, 12, 1): + avctx->pix_fmt = AV_PIX_FMT_YUVA444P12BE; + break; + case OAPV_CS_SET(OAPV_CF_YCBCR400, 10, 0): // profile 99 + avctx->pix_fmt = AV_PIX_FMT_GRAY10LE; + break; + case OAPV_CS_SET(OAPV_CF_YCBCR400, 10, 1): + avctx->pix_fmt = AV_PIX_FMT_GRAY10BE; + break; + default: + av_log(avctx, AV_LOG_ERROR, "Unknown color space\n"); + avctx->pix_fmt = AV_PIX_FMT_NONE; + return AVERROR_INVALIDDATA; + } + + return 0; +} + +/** + * @brief Copy image in imgb to frame. + * + * @param avctx codec context + * @param[in] imgb + * @param[out] frame + * @return 0 on success, negative value on failure + */ +static int libapvd_image_copy(struct AVCodecContext *avctx, oapv_imgb_t *imgb, struct AVFrame *frame) +{ + int ret; + + if (imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR422, 10, 0) && // profile 33 + imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR422, 10, 1) && // profile 33 + imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR422, 12, 0) && // profile 44 + imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR422, 12, 1) && // profile 44 + imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR444, 10, 0) && // profile 55 + imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR444, 10, 1) && // profile 55 + imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR444, 12, 0) && // profile 66 + imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR444, 12, 1) && // profile 66 + imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR4444, 10, 0) && // profile 77 + imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR4444, 10, 1) && // profile 77 + imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR4444, 12, 0) && // profile 88 + imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR4444, 12, 1) && // profile 88 + imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR400, 10, 0) && // profile 99 + imgb->cs != OAPV_CS_SET(OAPV_CF_YCBCR400, 10, 1)) { // profile 99 + av_log(avctx, AV_LOG_ERROR, "Not supported pixel format: %s\n", av_get_pix_fmt_name(avctx->pix_fmt)); + + return AVERROR_INVALIDDATA; + } + + if (imgb->w[0] != avctx->width || imgb->h[0] != avctx->height) { // stream resolution changed + if (ff_set_dimensions(avctx, imgb->w[0], imgb->h[0]) < 0) { + av_log(avctx, AV_LOG_ERROR, "Cannot set new dimension\n"); + return AVERROR_INVALIDDATA; + } + } + + if (ret = ff_get_buffer(avctx, frame, 0) < 0) + return ret; + + av_image_copy(frame->data, frame->linesize, (const uint8_t **)imgb->a, + imgb->s, avctx->pix_fmt, + imgb->w[0], imgb->h[0]); + + return 0; +} + +/** + * Initialize decoder + * Create a decoder instance and allocate all the needed resources + * + * @param avctx codec context + * @return 0 on success, negative error code on failure + */ +static av_cold int libapvd_init(AVCodecContext *avctx) +{ + ApvDecContext *apvctx = avctx->priv_data; + oapvd_cdesc_t *cdsc = &(apvctx->cdsc); + int ret = 0; + + /* read configurations from AVCodecContext and populate the apvd_cdsc structure */ + get_conf(avctx, cdsc); + + /* create decoder instance */ + apvctx->id = oapvd_create(&(apvctx->cdsc), NULL); + if (apvctx->id == NULL) { + av_log(avctx, AV_LOG_ERROR, "Cannot create apvd decoder\n"); + return AVERROR_EXTERNAL; + } + + /* create metadata container */ + apvctx->mid = oapvm_create(&ret); + if(OAPV_FAILED(ret)) { + av_log(avctx, AV_LOG_ERROR, "ERROR: cannot create OAPV metadata container (err=%d)\n", ret); + return AVERROR_EXTERNAL; + } + + if ((ret = set_extra_config(avctx, apvctx->id, apvctx)) != 0) { + av_log(avctx, AV_LOG_ERROR, "Cannot set extra configuration\n"); + return AVERROR(EINVAL); + } + + apvctx->pkt = av_packet_alloc(); + + // Allocate an AVContainerFifo instance for AVFrames + apvctx->output_fifo = av_container_fifo_alloc_avframe(0); + if (!apvctx->output_fifo) + return AVERROR(ENOMEM); + + for (int i = 0; i < FF_ARRAY_ELEMS(apvctx->frames); i++) { + apvctx->frames[i] = NULL; + } + + apvctx->frames_count = 0; + apvctx->total_frames_count = 0; + apvctx->au_count = 0; + + return 0; +} + +/** + * Decode frame with decoupled packet/frame dataflow + * + * @param avctx codec context + * @param[out] frame decoded frame + * + * @return 0 on success, negative error code on failure + */ +static int libapvd_receive_frame(AVCodecContext *avctx, AVFrame *frame) +{ + ApvDecContext *apvctx = avctx->priv_data; + AVPacket *pkt = apvctx->pkt; + int ret = 0; + + uint8_t *bs_buf = NULL; + uint32_t bs_buf_size = 0; + + oapvd_stat_t stat; + oapv_bitb_t bitb; + oapv_frms_t ofrms; + oapv_imgb_t *imgb_w = NULL; + oapv_imgb_t *imgb_o = NULL; + oapv_frm_t *frm = NULL; + + oapv_au_info_t aui; + oapv_frm_info_t *finfo = NULL; + + AVPacket* pkt_fd; // encoded frame data + + if (av_container_fifo_can_read(apvctx->output_fifo)) + goto do_output; + + for(int i =0; i<apvctx->frames_count;i++ ) { + av_frame_unref(apvctx->frames[i]); + apvctx->frames_count = 0; + } + + // frame data (input data) + ret = ff_decode_get_packet(avctx, pkt); + if (ret < 0 && ret != AVERROR_EOF) { + av_packet_unref(pkt); + return ret; + } + + if (pkt->size <= 0) { + av_packet_unref(pkt); + return ret; + } + + memset(&ofrms, 0, sizeof(oapv_frms_t)); + memset(&aui, 0, sizeof(oapv_au_info_t)); + + pkt_fd = av_packet_clone(pkt); + av_packet_unref(pkt); + + bs_buf = pkt_fd->data + APV_AU_SIZE_PREFIX_LENGTH; + bs_buf_size = pkt_fd->size - APV_AU_SIZE_PREFIX_LENGTH; + + if (OAPV_FAILED(oapvd_info(bs_buf, bs_buf_size, &aui))) + { + av_log(avctx, AV_LOG_ERROR, "Invalid bitstream\n"); + ret = AVERROR_INVALIDDATA; + goto end; + } + + if ((ret = export_stream_params(&aui, avctx)) != 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to export stream params\n"); + goto end; + } + + /* create decoding frame buffers */ + ofrms.num_frms = aui.num_frms; + for(int i = 0; i < ofrms.num_frms; i++) { + + finfo = &aui.frm_info[i]; + + if(apvctx->output_csp == 1) { + ofrms.frm[i].imgb = apv_imgb_create(finfo->w, finfo->h, OAPV_CS_SET(OAPV_CF_PLANAR2, 10, 0), avctx); + } else { + ofrms.frm[i].imgb = apv_imgb_create(finfo->w, finfo->h, finfo->cs, avctx); + } + + if(ofrms.frm[i].imgb == NULL) { + av_log(avctx, AV_LOG_ERROR, "cannot allocate image buffer (w:%d, h:%d, cs:%d)\n", + finfo->w, finfo->h, finfo->cs); + + ret = AVERROR_INVALIDDATA; + goto end; + } + } + + if(apvctx->output_depth == 0) { + apvctx->output_depth = OAPV_CS_GET_BIT_DEPTH(finfo->cs); + } + + /* main decoding block */ + bitb.addr = bs_buf; + bitb.ssize = bs_buf_size; + memset(&stat, 0, sizeof(oapvd_stat_t)); + + ret = oapvd_decode(apvctx->id, &bitb, &ofrms, apvctx->mid, &stat); + if(OAPV_FAILED(ret)) { + av_log(avctx, AV_LOG_ERROR,"failed to decode bitstream\n"); + + ret = AVERROR_INVALIDDATA; + goto end; + } + if(stat.read != bs_buf_size) { + av_log(avctx, AV_LOG_ERROR,"\t=> different reading of bitstream (in:%d, read:%d)\n", + bs_buf_size, stat.read); + } + + /* Testing of metadata reading */ + if(apvctx->mid) { + oapvm_payload_t *pld = NULL; // metadata payload + int num_plds = 0; // number of metadata payload + + ret = oapvm_get_all(apvctx->mid, NULL, &num_plds); + + if(OAPV_FAILED(ret)) { + av_log(avctx, AV_LOG_ERROR,"failed to read metadata\n"); + + ret = AVERROR_INVALIDDATA; + goto end; + } + if(num_plds > 0) { + pld = malloc(sizeof(oapvm_payload_t) * num_plds); + ret = oapvm_get_all(apvctx->mid, pld, &num_plds); + if(OAPV_FAILED(ret)) { + av_log(avctx, AV_LOG_ERROR,"failed to read metadata\n"); + + if(pld != NULL) + free(pld); + + ret = AVERROR_INVALIDDATA; + goto end; + } + } + if(pld != NULL) + free(pld); + } + + /* Write decoded frames into AVFrame objects */ + for(int i = 0; i < ofrms.num_frms; i++) { + frm = &ofrms.frm[i]; + if(OAPV_CS_GET_BIT_DEPTH(frm->imgb->cs) != apvctx->output_depth) { + if(imgb_w == NULL) { + imgb_w = apv_imgb_create(frm->imgb->w[0], frm->imgb->h[0], + OAPV_CS_SET(OAPV_CS_GET_FORMAT(frm->imgb->cs), apvctx->output_depth, 0), avctx); + if(imgb_w == NULL) { + av_log(avctx, AV_LOG_ERROR,"cannot allocate image buffer (w:%d, h:%d, cs:%d)\n", + frm->imgb->w[0], frm->imgb->h[0], frm->imgb->cs); + + ret = AVERROR_INVALIDDATA; + goto end; + } + } + apv_imgb_cpy(imgb_w, frm->imgb, avctx); + + imgb_o = imgb_w; + } + else { + imgb_o = frm->imgb; + } + + if(apvctx->frames[i] == NULL) { + apvctx->frames[i] = av_frame_alloc(); + } + + /* Copy decoded image into AVFrame object */ + ret = libapvd_image_copy(avctx, imgb_o, apvctx->frames[i]); + if(ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Image copying error\n"); + av_frame_unref(apvctx->frames[i]); + + goto end; + } + + /* Use ff_decode_frame_props_from_pkt() to fill frame properties */ + ret = ff_decode_frame_props_from_pkt(avctx, apvctx->frames[i], pkt_fd); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "ff_decode_frame_props_from_pkt error\n"); + av_frame_unref(apvctx->frames[i]); + + goto end; + } + + if (pkt_fd->flags & AV_PKT_FLAG_KEY) { + apvctx->frames[i]->pict_type = AV_PICTURE_TYPE_I; + apvctx->frames[i]->flags |= AV_FRAME_FLAG_KEY; + } + + apvctx->frames_count++; + + /* Write the AVFrame data to the FIFO */ + ret = av_container_fifo_write(apvctx->output_fifo, apvctx->frames[i], AV_CONTAINER_FIFO_FLAG_REF); + } + apvctx->total_frames_count += apvctx->frames_count; + apvctx->au_count++; + +end: + av_packet_unref(pkt_fd); + + for(int i = 0; i < ofrms.num_frms; i++) { + if(ofrms.frm[i].imgb != NULL) { + ofrms.frm[i].imgb->release(ofrms.frm[i].imgb); + ofrms.frm[i].imgb = NULL; + } + } + if (imgb_w) { + imgb_w->release(imgb_w); + imgb_w = NULL; + } + + imgb_o = NULL; + + if (av_container_fifo_can_read(apvctx->output_fifo)) + goto do_output; + + return AVERROR(EAGAIN); + +do_output: + /* Read the next available object from the FIFO into frame */ + if (av_container_fifo_read(apvctx->output_fifo, frame, 0) >= 0) { + return 0; + } + return 0; +} + +/** + * Destroy decoder + * + * @param avctx codec context + * @return 0 on success + */ +static av_cold int libapvd_close(AVCodecContext *avctx) +{ + ApvDecContext *apvctx = avctx->priv_data; + if (apvctx->id) { + oapvd_delete(apvctx->id); + apvctx->id = NULL; + } + + if (apvctx->mid) { + oapvm_rem_all(apvctx->mid); + oapvm_delete(apvctx->mid); + apvctx->mid = NULL; + } + + av_packet_free(&apvctx->pkt); + + av_container_fifo_free(&apvctx->output_fifo); + + for (int i = 0; i < FF_ARRAY_ELEMS(apvctx->frames); i++) { + av_frame_free(&apvctx->frames[i]); + } + + return 0; +} + +#define OFFSET(x) offsetof(ApvDecContext, x) +#define VD AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM + +// Consider using following options (./ffmpeg --help encoder=liboapv) +// +static const AVOption libapvd_options[] = { + { "output_csp", "Color space", OFFSET(output_csp),AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, VD }, + { "output_depth", "Color space", OFFSET(output_depth),AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, VD }, + { NULL } +}; + +static const AVClass libapvd_class = { + .class_name = "libapvd", + .item_name = av_default_item_name, + .option = libapvd_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +const FFCodec ff_libapv_decoder = { + .p.name = "apv", + .p.long_name = NULL_IF_CONFIG_SMALL("APV / Advanced Professional Video"), + .p.type = AVMEDIA_TYPE_VIDEO, + .p.id = AV_CODEC_ID_APV, + .init = libapvd_init, + FF_CODEC_RECEIVE_FRAME_CB(libapvd_receive_frame), + .close = libapvd_close, + .priv_data_size = sizeof(ApvDecContext), + .p.priv_class = &libapvd_class, + .p.capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_OTHER_THREADS | AV_CODEC_CAP_AVOID_PROBING, + .p.wrapper_name = "libapvd", + .p.profiles = NULL_IF_CONFIG_SMALL(ff_apv_profiles), + .caps_internal = FF_CODEC_CAP_INIT_CLEANUP | FF_CODEC_CAP_NOT_INIT_THREADSAFE | FF_CODEC_CAP_SETS_PKT_DTS | FF_CODEC_CAP_SETS_FRAME_PROPS +}; -- 2.34.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-requ...@ffmpeg.org with subject "unsubscribe".