[FFmpeg-devel] [PATCH v18 02/10] avcodec/evc_parser: Added parser implementation for EVC format

James Almer jamrial at gmail.com
Wed Mar 29 17:57:18 EEST 2023



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;


More information about the ffmpeg-devel mailing list