[FFmpeg-devel] [PATCH v18 02/10] avcodec/evc_parser: Added parser implementation for EVC format
Dawid Kozinski/Multimedia (PLT) /SRPOL/Staff Engineer/Samsung Electronics
d.kozinski at samsung.com
Wed Apr 5 11:59:48 EEST 2023
> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces at ffmpeg.org> On Behalf Of James
> Almer
> Sent: środa, 29 marca 2023 16:57
> To: ffmpeg-devel at ffmpeg.org
> Subject: Re: [FFmpeg-devel] [PATCH v18 02/10] avcodec/evc_parser: Added
> parser implementation for EVC format
>
>
>
> On 3/28/2023 10:46 AM, Dawid Kozinski wrote:
> > - Added constants definitions for EVC parser
> > - Provided NAL units parsing following ISO_IEC_23094-1
> > - EVC parser registration
> >
> > Signed-off-by: Dawid Kozinski <d.kozinski at samsung.com>
> > ---
> > libavcodec/Makefile | 1 +
> > libavcodec/evc.h | 155 +++++
> > libavcodec/evc_parser.c | 1270
> +++++++++++++++++++++++++++++++++++++++
> > libavcodec/parsers.c | 1 +
> > 4 files changed, 1427 insertions(+)
> > create mode 100644 libavcodec/evc.h
> > create mode 100644 libavcodec/evc_parser.c
> >
>
> [...]
>
> > +static int parse_nal_unit(AVCodecParserContext *s, const uint8_t *buf,
> > + int buf_size, AVCodecContext *avctx) {
> > + EVCParserContext *ev = s->priv_data;
> > + int nalu_type, nalu_size;
> > + int tid;
> > + const uint8_t *data = buf;
> > + int data_size = buf_size;
> > +
> > + s->picture_structure = AV_PICTURE_STRUCTURE_FRAME;
> > + s->key_frame = -1;
> > +
> > +
> > + nalu_size = buf_size;
> > + if (nalu_size <= 0) {
> > + av_log(avctx, AV_LOG_ERROR, "Invalid NAL unit size: (%d)\n",
nalu_size);
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + // @see ISO_IEC_23094-1_2020, 7.4.2.2 NAL unit header semantic
(Table 4
> - NAL unit type codes and NAL unit type classes)
> > + // @see enum EVCNALUnitType in evc.h
> > + nalu_type = get_nalu_type(data, data_size, avctx);
> > + if (nalu_type < EVC_NOIDR_NUT || nalu_type > EVC_UNSPEC_NUT62) {
> > + av_log(avctx, AV_LOG_ERROR, "Invalid NAL unit type: (%d)\n",
> nalu_type);
> > + return AVERROR_INVALIDDATA;
> > + }
> > + ev->nalu_type = nalu_type;
> > +
> > + tid = get_temporal_id(data, data_size, avctx);
> > + if (tid < 0) {
> > + av_log(avctx, AV_LOG_ERROR, "Invalid temporial id: (%d)\n",
tid);
> > + return AVERROR_INVALIDDATA;
> > + }
> > + ev->nuh_temporal_id = tid;
> > +
> > + if (data_size < nalu_size) {
> > + av_log(avctx, AV_LOG_ERROR, "NAL unit does not fit in the data
> buffer\n");
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + data += EVC_NALU_HEADER_SIZE;
> > + data_size -= EVC_NALU_HEADER_SIZE;
> > +
> > + if (nalu_type == EVC_SPS_NUT) {
> > + EVCParserSPS *sps;
> > + int SubGopLength;
> > +
> > + sps = parse_sps(data, nalu_size, ev);
> > + if (!sps) {
> > + av_log(avctx, AV_LOG_ERROR, "SPS parsing error\n");
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + s->coded_width = sps->pic_width_in_luma_samples;
> > + s->coded_height = sps->pic_height_in_luma_samples;
> > + s->width = sps->pic_width_in_luma_samples - sps-
> >picture_crop_left_offset - sps->picture_crop_right_offset;
> > + s->height = sps->pic_height_in_luma_samples - sps-
> >picture_crop_top_offset - sps->picture_crop_bottom_offset;
> > +
> > + SubGopLength = (int)pow(2.0, sps->log2_sub_gop_length);
> > + avctx->gop_size = SubGopLength;
> > +
> > + avctx->delay = (sps->sps_max_dec_pic_buffering_minus1) ?
> > + sps->sps_max_dec_pic_buffering_minus1 - 1 : SubGopLength +
> > + sps->max_num_tid0_ref_pics - 1;
> > +
> > + if (sps->profile_idc == 1) avctx->profile =
FF_PROFILE_EVC_MAIN;
> > + else avctx->profile = FF_PROFILE_EVC_BASELINE;
> > +
> > + ev->time_base = avctx->time_base.den;
>
> This looks like a write only field. Also, avctx->time_base is no longer
used for
> decoding.
> What you can do is setting avctx->frame_rate using
> vui->num_units_in_tick and vui->time_scale if present. See h264_parser.c
>
> > +
> > + switch (sps->chroma_format_idc) {
> > + case 0: /* YCBCR400_10LE */
> > + av_log(avctx, AV_LOG_ERROR, "YCBCR400_10LE: Not supported
> chroma format\n");
> > + s->format = AV_PIX_FMT_GRAY10LE;
> > + return -1;
> > + case 1: /* YCBCR420_10LE */
> > + s->format = AV_PIX_FMT_YUV420P10LE;
> > + break;
> > + case 2: /* YCBCR422_10LE */
> > + av_log(avctx, AV_LOG_ERROR, "YCBCR422_10LE: Not supported
> chroma format\n");
> > + s->format = AV_PIX_FMT_YUV422P10LE;
> > + return -1;
> > + case 3: /* YCBCR444_10LE */
> > + av_log(avctx, AV_LOG_ERROR, "YCBCR444_10LE: Not supported
> chroma format\n");
> > + s->format = AV_PIX_FMT_YUV444P10LE;
> > + return -1;
> > + default:
> > + s->format = AV_PIX_FMT_NONE;
> > + av_log(avctx, AV_LOG_ERROR, "Unknown supported chroma
> format\n");
> > + return -1;
> > + }
> > + } else if (nalu_type == EVC_PPS_NUT) {
> > + EVCParserPPS *pps;
> > +
> > + pps = parse_pps(data, nalu_size, ev);
> > + if (!pps) {
> > + av_log(avctx, AV_LOG_ERROR, "PPS parsing error\n");
> > + return AVERROR_INVALIDDATA;
> > + }
> > + } else if (nalu_type == EVC_SEI_NUT) // Supplemental Enhancement
> Information
> > + return 0;
> > + else if (nalu_type == EVC_APS_NUT) // Adaptation parameter set
> > + return 0;
> > + else if (nalu_type == EVC_FD_NUT) /* Filler data */
> > + return 0;
>
> Use a switch() statement for this instead.
>
> > + else if (nalu_type == EVC_IDR_NUT || nalu_type == EVC_NOIDR_NUT) {
//
> Coded slice of a IDR or non-IDR picture
> > + EVCParserSliceHeader *sh;
> > + EVCParserSPS *sps;
> > + int slice_pic_parameter_set_id;
> > +
> > + sh = parse_slice_header(data, nalu_size, ev);
> > + if (!sh) {
> > + av_log(avctx, AV_LOG_ERROR, "Slice header parsing
error\n");
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + switch (sh->slice_type) {
> > + case EVC_SLICE_TYPE_B: {
> > + s->pict_type = AV_PICTURE_TYPE_B;
> > + break;
> > + }
> > + case EVC_SLICE_TYPE_P: {
> > + s->pict_type = AV_PICTURE_TYPE_P;
> > + break;
> > + }
> > + case EVC_SLICE_TYPE_I: {
> > + s->pict_type = AV_PICTURE_TYPE_I;
> > + break;
> > + }
> > + default: {
> > + s->pict_type = AV_PICTURE_TYPE_NONE;
> > + }
> > + }
> > +
> > + s->key_frame = (nalu_type == EVC_IDR_NUT) ? 1 : 0;
> > +
> > + // POC (picture order count of the current picture) derivation
> > + // @see ISO/IEC 23094-1:2020(E) 8.3.1 Decoding process for
picture
> order count
> > + slice_pic_parameter_set_id = sh->slice_pic_parameter_set_id;
> > + sps = &ev->sps[slice_pic_parameter_set_id];
> > +
> > + if (sps->sps_pocs_flag) {
> > +
> > + int PicOrderCntMsb = 0;
> > + ev->poc.prevPicOrderCntVal = ev->poc.PicOrderCntVal;
> > +
> > + if (nalu_type == EVC_IDR_NUT)
> > + PicOrderCntMsb = 0;
> > + else {
> > + int MaxPicOrderCntLsb = 1 << (sps-
> >log2_max_pic_order_cnt_lsb_minus4 + 4);
> > +
> > + int prevPicOrderCntLsb = ev->poc.PicOrderCntVal &
> (MaxPicOrderCntLsb - 1);
> > + int prevPicOrderCntMsb = ev->poc.PicOrderCntVal -
> prevPicOrderCntLsb;
> > +
> > +
> > + if ((sh->slice_pic_order_cnt_lsb < prevPicOrderCntLsb)
&&
> > + ((prevPicOrderCntLsb - sh->slice_pic_order_cnt_lsb)
>=
> (MaxPicOrderCntLsb / 2)))
> > +
> > + PicOrderCntMsb = prevPicOrderCntMsb +
MaxPicOrderCntLsb;
> > +
> > + else if ((sh->slice_pic_order_cnt_lsb >
prevPicOrderCntLsb) &&
> > + ((sh->slice_pic_order_cnt_lsb -
prevPicOrderCntLsb) >
> (MaxPicOrderCntLsb / 2)))
> > +
> > + PicOrderCntMsb = prevPicOrderCntMsb -
MaxPicOrderCntLsb;
> > +
> > + else
> > + PicOrderCntMsb = prevPicOrderCntMsb;
> > + }
> > + ev->poc.PicOrderCntVal = PicOrderCntMsb + sh-
> >slice_pic_order_cnt_lsb;
> > +
> > + } else {
> > + if (nalu_type == EVC_IDR_NUT) {
> > + ev->poc.PicOrderCntVal = 0;
> > + ev->poc.DocOffset = -1;
> > + } else {
> > + int SubGopLength = (int)pow(2.0,
sps->log2_sub_gop_length);
> > + if (tid == 0) {
> > + ev->poc.PicOrderCntVal = ev->poc.prevPicOrderCntVal
+
> SubGopLength;
> > + ev->poc.DocOffset = 0;
> > + ev->poc.prevPicOrderCntVal =
ev->poc.PicOrderCntVal;
> > + } else {
> > + int ExpectedTemporalId;
> > + int PocOffset;
> > + int prevDocOffset = ev->poc.DocOffset;
> > +
> > + ev->poc.DocOffset = (prevDocOffset + 1) %
SubGopLength;
> > + if (ev->poc.DocOffset == 0) {
> > + ev->poc.prevPicOrderCntVal += SubGopLength;
> > + ExpectedTemporalId = 0;
> > + } else
> > + ExpectedTemporalId = 1 +
(int)log2(ev->poc.DocOffset);
> > + while (tid != ExpectedTemporalId) {
> > + ev->poc.DocOffset = (ev->poc.DocOffset + 1) %
SubGopLength;
> > + if (ev->poc.DocOffset == 0)
> > + ExpectedTemporalId = 0;
> > + else
> > + ExpectedTemporalId = 1 +
(int)log2(ev->poc.DocOffset);
> > + }
> > + PocOffset = (int)(SubGopLength * ((2.0 *
ev->poc.DocOffset + 1) /
> (int)pow(2.0, tid) - 2));
> > + ev->poc.PicOrderCntVal = ev->poc.prevPicOrderCntVal
+
> PocOffset;
> > + }
> > + }
> > + }
> > +
> > + s->output_picture_number = ev->poc.PicOrderCntVal;
> > + s->key_frame = (nalu_type == EVC_IDR_NUT) ? 1 : 0;
> > +
> > + return 0;
> > + }
> > + data += (nalu_size - EVC_NALU_HEADER_SIZE);
> > + data_size -= (nalu_size - EVC_NALU_HEADER_SIZE);
>
> What does this even do? They are local variables, and this is not in a
loop.
>
> > +
> > + return 0;
> > +}
>
> [...]
>
> > +// Find the end of the current frame in the bitstream.
> > +// The end of frame is the end of Access Unit.
> > +// Function returns the position of the first byte of the next frame,
or
> END_NOT_FOUND
> > +static int evc_find_frame_end(AVCodecParserContext *s, const uint8_t
*buf,
> > + int buf_size, AVCodecContext *avctx)
> > +{
> > + EVCParserContext *ctx = s->priv_data;
> > +
> > + const uint8_t *data = buf;
> > + int data_size = buf_size;
> > +
> > + while (data_size > 0) {
> > +
> > + if (ctx->to_read == 0) {
> > + // Nothing must be read and appended to the data from
previous
> chunks.
> > + // The previous chunk of data provided the complete NALU
prefix or
> provided the complete NALU.
> > +
> > + if (ctx->nalu_prefix_assembled) // NALU prefix has been
assembled
> from previous and current chunks of incoming data
> > + ctx->nalu_prefix_assembled = 0;
> > + else { // Buffer size is not enough for buffer to store NAL
unit 4-bytes
> prefix (length)
> > + if (data_size < EVC_NALU_LENGTH_PREFIX_SIZE) {
> > + ctx->to_read = EVC_NALU_LENGTH_PREFIX_SIZE -
data_size;
> > + ctx->incomplete_nalu_prefix_read = 1;
> > + return END_NOT_FOUND;
> > + }
> > +
> > + ctx->nalu_size = read_nal_unit_length(data, data_size,
avctx);
> > + ctx->bytes_read += EVC_NALU_LENGTH_PREFIX_SIZE;
> > +
> > + data += EVC_NALU_LENGTH_PREFIX_SIZE;
> > + data_size -= EVC_NALU_LENGTH_PREFIX_SIZE;
> > + }
> > +
> > + if (data_size < ctx->nalu_size) {
> > +
> > + ctx->to_read = ctx->nalu_size - data_size;
> > + ctx->incomplete_nalu_read = 1;
> > + return END_NOT_FOUND;
> > + }
> > +
> > + // the entire NALU can be read
> > + if (parse_nal_unit(s, data, ctx->nalu_size, avctx) != 0) {
>
> NALU parsing should happen always, not only when the parser is required
> to assemble a full access unit.
>
> > + av_log(avctx, AV_LOG_ERROR, "Parsing of NAL unit
failed\n");
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + data += ctx->nalu_size;
> > + data_size -= ctx->nalu_size;
> > +
> > + ctx->bytes_read += ctx->nalu_size;
> > +
> > + if (end_of_access_unit_found(s, avctx)) {
> > +
> > + // parser should return buffer that contains complete
AU
> > + int read_bytes = ctx->bytes_read;
> > + ctx->bytes_read = 0;
> > + return read_bytes;
> > + }
> > +
> > + // go to the next iteration
> > + continue;
> > +
> > + } else {
> > + // The previous chunk of input data did not contain the
complete valid
> NALU prefix or did not contain the complete NALU.
> > + //
> > + // Missing data must be read from the current data chunk
and merged
> with the data from the previous data chunk
> > + // to assemble a complete NALU or complete NALU prefix.
> > + //
> > + // The data from the previous data chunk are stored in
pc->buf
> > +
> > + if (ctx->to_read < data_size) {
> > +
> > + if (ctx->incomplete_nalu_prefix_read == 1) {
> > +
> > + uint8_t nalu_prefix[EVC_NALU_LENGTH_PREFIX_SIZE];
> > + evc_assemble_nalu_prefix(s, data, data_size,
nalu_prefix, avctx);
> > +
> > + ctx->nalu_size = read_nal_unit_length(nalu_prefix,
> EVC_NALU_LENGTH_PREFIX_SIZE, avctx);
> > +
> > + // update variable storing amout of read bytes for
teh current AU
> > + ctx->bytes_read += ctx->to_read;
> > +
> > + // update data pointer and data size
> > + data += ctx->to_read;
> > + data_size -= ctx->to_read;
> > +
> > + // reset variable storing amount of bytes to read
from the new
> data chunk
> > + ctx->to_read = 0;
> > +
> > + ctx->incomplete_nalu_prefix_read = 0;
> > + ctx->nalu_prefix_assembled = 1;
> > +
> > + continue;
> > + }
> > + if (ctx->incomplete_nalu_read == 1) {
> > +
> > + uint8_t *nalu = (uint8_t
*)av_malloc(ctx->nalu_size);
> > +
> > + // assemble NAL unit using data from previous data
chunks (pc-
> >buffer) and the current one (data)
> > + evc_assemble_nalu(s, data, ctx->to_read, nalu,
ctx->nalu_size,
> avctx);
> > +
> > + if (parse_nal_unit(s, nalu, ctx->nalu_size, avctx)
!= 0) {
> > + av_log(avctx, AV_LOG_ERROR, "Parsing of NAL
unit failed\n");
> > + return AVERROR_INVALIDDATA;
> > + }
> > + av_free(nalu);
> > +
> > + // update variable storing amout of read bytes for
teh current AU
> > + ctx->bytes_read += ctx->nalu_size;
> > +
> > + // update data pointer and data size
> > + data += ctx->to_read;
> > + data_size -= ctx->to_read;
> > +
> > + ctx->incomplete_nalu_read = 0;
> > +
> > + if (end_of_access_unit_found(s, avctx)) {
> > +
> > + // parser should return buffer that contains
complete AU
> > + int read_bytes = ctx->to_read;
> > +
> > + ctx->to_read = 0;
> > + ctx->bytes_read = 0;
> > +
> > + return read_bytes;
> > + }
> > +
> > + // reset variable storing amount of bytes to read
from the new
> data chunk
> > + ctx->to_read = 0;
> > +
> > + continue;
> > + }
> > + } else {
> > + // needed more input data to assemble complete valid
NAL Unit
> > + ctx->to_read = ctx->to_read - data_size;
> > + return END_NOT_FOUND;
> > + }
> > + }
> > + }
> > +
> > + return END_NOT_FOUND;
> > +}
> > +
> > +static int evc_parse(AVCodecParserContext *s, AVCodecContext *avctx,
> > + const uint8_t **poutbuf, int *poutbuf_size,
> > + const uint8_t *buf, int buf_size)
> > +{
> > + int next;
> > + EVCParserContext *ev = s->priv_data;
> > + ParseContext *pc = &ev->pc;
> > +
> > + if (s->flags & PARSER_FLAG_COMPLETE_FRAMES)
> > + next = buf_size;
> > + else {
> > + next = evc_find_frame_end(s, buf, buf_size, avctx);
> > + if (ff_combine_frame(pc, next, &buf, &buf_size) < 0) {
> > + *poutbuf = NULL;
> > + *poutbuf_size = 0;
> > + return buf_size;
> > + }
> > + }
>
> Case in point, you should call the function to parse the packet's NALUs
> here, so it's done also when PARSER_FLAG_COMPLETE_FRAMES is signaled.
>
> > +
> > + // poutbuf contains just one Access Unit
> > + *poutbuf = buf;
> > + *poutbuf_size = buf_size;
> > +
> > + return next;
> > +}
> > +
> > +static int evc_parser_init(AVCodecParserContext *s)
> > +{
> > + EVCParserContext *ev = s->priv_data;
> > + ev->incomplete_nalu_prefix_read = 0;
> > +
> > + return 0;
> > +}
> > +
> > +const AVCodecParser ff_evc_parser = {
> > + .codec_ids = { AV_CODEC_ID_EVC },
> > + .priv_data_size = sizeof(EVCParserContext),
> > + .parser_init = evc_parser_init,
> > + .parser_parse = evc_parse,
> > + .parser_close = ff_parse_close,
> > +};
> > diff --git a/libavcodec/parsers.c b/libavcodec/parsers.c
> > index d355808018..2c077ec3ae 100644
> > --- a/libavcodec/parsers.c
> > +++ b/libavcodec/parsers.c
> > @@ -41,6 +41,7 @@ extern const AVCodecParser ff_dvaudio_parser;
> > extern const AVCodecParser ff_dvbsub_parser;
> > extern const AVCodecParser ff_dvdsub_parser;
> > extern const AVCodecParser ff_dvd_nav_parser;
> > +extern const AVCodecParser ff_evc_parser;
> > extern const AVCodecParser ff_flac_parser;
> > extern const AVCodecParser ff_ftr_parser;
> > extern const AVCodecParser ff_g723_1_parser;
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> https://protect2.fireeye.com/v1/url?k=e23962f4-bda25bf8-e238e9bb-
> 000babff3563-acac272a35bdd12c&q=1&e=daa29860-f1f6-44c2-9ab6-
> d68c3d42950a&u=https%3A%2F%2Fffmpeg.org%2Fmailman%2Flistinfo%2Fffmp
> eg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".
We've just uploaded new patches.
Everything you have requested has been fixed.
More information about the ffmpeg-devel
mailing list