diff mbox series

[FFmpeg-devel,2/2] Provided support for MPEG-5 EVC (Essential Video Coding) codec

Message ID 002001d8a589$2272a170$6757e450$@samsung.com
State New
Headers show
Series [FFmpeg-devel,1/2] Provided support for MPEG-5 EVC (Essential Video Coding) codec | expand

Checks

Context Check Description
yinshiyou/configure_loongarch64 warning Failed to apply patch
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Dawid Kozinski Aug. 1, 2022, 9:29 a.m. UTC
- Added muxer for EVC format (MP4, raw)
- Added demuxer for EVC format (MP4)
- Added evc extension to the list of extensions for ff_mov_demuxer

Signed-off-by: Dawid Kozinski <d.kozinski@samsung.com>
---
 doc/muxers.texi          |   6 ++
 libavformat/Makefile     |   2 +
 libavformat/allformats.c |   2 +
 libavformat/evcdec.c     | 142 +++++++++++++++++++++++++++++++++++++++
 libavformat/isom_tags.c  |   2 +
 libavformat/mov.c        |   2 +-
 libavformat/movenc.c     |  29 +++++++-
 libavformat/rawenc.c     |  13 ++++
 8 files changed, 196 insertions(+), 2 deletions(-)
 create mode 100644 libavformat/evcdec.c

Comments

James Almer Aug. 1, 2022, 2:04 p.m. UTC | #1
On 8/1/2022 6:29 AM, Dawid Kozinski wrote:
> diff --git a/libavformat/movenc.c b/libavformat/movenc.c
> index 5608afde42..d0b094c30d 100644
> --- a/libavformat/movenc.c
> +++ b/libavformat/movenc.c
> @@ -1392,6 +1392,16 @@ static int mov_write_hvcc_tag(AVIOContext *pb, MOVTrack *track)
>       return update_size(pb, pos);
>   }
>   
> +static int mov_write_evcc_tag(AVIOContext *pb, MOVTrack *track)
> +{
> +    int64_t pos = avio_tell(pb);
> +
> +    avio_wb32(pb, 0);
> +    ffio_wfourcc(pb, "evcC");

Is that it? That's all this atom contains? No structure containing a 
global SPS/PPS?
Is there a spec anywhere defining this?

> +
> +    return update_size(pb, pos);
> +}
> +
>   /* also used by all avid codecs (dv, imx, meridien) and their variants */
>   static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track)
>   {
> @@ -1641,6 +1651,16 @@ static int mov_get_h264_codec_tag(AVFormatContext *s, MOVTrack *track)
>       return tag;
>   }
>   
> +static int mov_get_evc_codec_tag(AVFormatContext *s, MOVTrack *track)
> +{
> +    int tag = track->par->codec_tag;
> +
> +    if (!tag)
> +        tag = MKTAG('e', 'v', 'c', 'i');
> +
> +    return tag;
> +}
> +
>   static const struct {
>       enum AVPixelFormat pix_fmt;
>       uint32_t tag;
> @@ -1722,6 +1742,8 @@ static unsigned int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track)
>               tag = mov_get_mpeg2_xdcam_codec_tag(s, track);
>           else if (track->par->codec_id == AV_CODEC_ID_H264)
>               tag = mov_get_h264_codec_tag(s, track);
> +        else if (track->par->codec_id == AV_CODEC_ID_EVC)
> +            tag = mov_get_evc_codec_tag(s, track);
>           else if (track->par->codec_id == AV_CODEC_ID_DNXHD)
>               tag = mov_get_dnxhd_codec_tag(s, track);
>           else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) {
> @@ -2280,6 +2302,9 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex
>           mov_write_avcc_tag(pb, track);
>           if (track->mode == MODE_IPOD)
>               mov_write_uuid_tag_ipod(pb);
> +    }
> +    else if (track->par->codec_id ==AV_CODEC_ID_EVC) {
> +        mov_write_evcc_tag(pb, track);
>       } else if (track->par->codec_id == AV_CODEC_ID_VP9) {
>           mov_write_vpcc_tag(mov->fc, pb, track);
>       } else if (track->par->codec_id == AV_CODEC_ID_AV1) {
> @@ -6030,7 +6055,8 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
>       if ((par->codec_id == AV_CODEC_ID_DNXHD ||
>            par->codec_id == AV_CODEC_ID_H264 ||
>            par->codec_id == AV_CODEC_ID_HEVC ||
> -         par->codec_id == AV_CODEC_ID_TRUEHD) && !trk->vos_len &&
> +         par->codec_id == AV_CODEC_ID_TRUEHD ||
> +         par->codec_id == AV_CODEC_ID_EVC) && !trk->vos_len &&

Why are you copying the first muxed packet's data (in the absence of 
extradata) into vos_data if you're seemingly doing nothing with it? I'd 
expect that's what the evcC tag should contain.

>            !TAG_IS_AVCI(trk->tag)) {
>           /* copy frame to create needed atoms */
>           trk->vos_len  = size;
> @@ -7689,6 +7715,7 @@ static const AVCodecTag codec_mp4_tags[] = {
>       { AV_CODEC_ID_H264,            MKTAG('a', 'v', 'c', '3') },
>       { AV_CODEC_ID_HEVC,            MKTAG('h', 'e', 'v', '1') },
>       { AV_CODEC_ID_HEVC,            MKTAG('h', 'v', 'c', '1') },
> +    { AV_CODEC_ID_EVC,             MKTAG('e', 'v', 'c', '1') },
>       { AV_CODEC_ID_MPEG2VIDEO,      MKTAG('m', 'p', '4', 'v') },
>       { AV_CODEC_ID_MPEG1VIDEO,      MKTAG('m', 'p', '4', 'v') },
>       { AV_CODEC_ID_MJPEG,           MKTAG('m', 'p', '4', 'v') },
Michael Niedermayer Aug. 1, 2022, 7:30 p.m. UTC | #2
On Mon, Aug 01, 2022 at 11:29:01AM +0200, Dawid Kozinski wrote:
[...]

> +static int get_nalu_type(const uint8_t *bits, int bits_size)
> +{
> +    int unit_type_plus1 = 0;
> +
> +    if(bits_size >= EVC_NAL_HEADER_SIZE) {
> +        unsigned char *p = (unsigned char *)bits;
> +        // forbidden_zero_bit
> +        if ((p[0] & 0x80) != 0) {
> +            av_log(NULL, AV_LOG_ERROR, "Cannot get bitstream information. Malformed bitstream.\n");
> +            return -1;
> +        }
> +
> +        // nal_unit_type
> +        unit_type_plus1 = (p[0] >> 1) & 0x3F;
> +    }
> +
> +    return unit_type_plus1 - 1;
> +}
> +

> +static uint32_t read_nal_unit_length(const uint8_t *bits, int bits_size)
> +{
> +    uint32_t nalu_len = 0;
> +
> +    if(bits_size >= EVC_NAL_UNIT_LENGTH_BYTE) {
> +
> +        int t = 0;
> +        unsigned char *p = (unsigned char *)bits;
> +
> +        for(int i=0; i<EVC_NAL_UNIT_LENGTH_BYTE; i++) {
> +            t = (t << 8) | p[i];
> +        }
> +
> +        nalu_len = t;
> +        if(nalu_len == 0) {
> +            av_log(NULL, AV_LOG_ERROR, "Invalid bitstream size!\n");

These av_log() are a problem as they are in probing code
probing code would be run on all kinds of input, most not EVC so "errors"
are common while at the same time they are not real errors. That would
make probing very noisy

thx

[...]
Dawid Kozinski Aug. 3, 2022, 1:13 p.m. UTC | #3
-----Original Message-----
From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of James Almer
Sent: Monday, August 1, 2022 4:05 PM
To: ffmpeg-devel@ffmpeg.org
Subject: Re: [FFmpeg-devel] [PATCH 2/2] Provided support for MPEG-5 EVC (Essential Video Coding) codec

On 8/1/2022 6:29 AM, Dawid Kozinski wrote:
> diff --git a/libavformat/movenc.c b/libavformat/movenc.c
> index 5608afde42..d0b094c30d 100644
> --- a/libavformat/movenc.c
> +++ b/libavformat/movenc.c
> @@ -1392,6 +1392,16 @@ static int mov_write_hvcc_tag(AVIOContext *pb, MOVTrack *track)
>       return update_size(pb, pos);
>   }
>   
> +static int mov_write_evcc_tag(AVIOContext *pb, MOVTrack *track)
> +{
> +    int64_t pos = avio_tell(pb);
> +
> +    avio_wb32(pb, 0);
> +    ffio_wfourcc(pb, "evcC");

Is that it? That's all this atom contains? No structure containing a 
global SPS/PPS?
Is there a spec anywhere defining this?

####################################### 
David:
----------------------------------------------------------------
It seems that we need some information on how to do it in accordance with the rules of art. 
We have currently provided a minimalist version.
#######################################
> +
> +    return update_size(pb, pos);
> +}
> +
>   /* also used by all avid codecs (dv, imx, meridien) and their variants */
>   static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track)
>   {
> @@ -1641,6 +1651,16 @@ static int mov_get_h264_codec_tag(AVFormatContext *s, MOVTrack *track)
>       return tag;
>   }
>   
> +static int mov_get_evc_codec_tag(AVFormatContext *s, MOVTrack *track)
> +{
> +    int tag = track->par->codec_tag;
> +
> +    if (!tag)
> +        tag = MKTAG('e', 'v', 'c', 'i');
> +
> +    return tag;
> +}
> +
>   static const struct {
>       enum AVPixelFormat pix_fmt;
>       uint32_t tag;
> @@ -1722,6 +1742,8 @@ static unsigned int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track)
>               tag = mov_get_mpeg2_xdcam_codec_tag(s, track);
>           else if (track->par->codec_id == AV_CODEC_ID_H264)
>               tag = mov_get_h264_codec_tag(s, track);
> +        else if (track->par->codec_id == AV_CODEC_ID_EVC)
> +            tag = mov_get_evc_codec_tag(s, track);
>           else if (track->par->codec_id == AV_CODEC_ID_DNXHD)
>               tag = mov_get_dnxhd_codec_tag(s, track);
>           else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) {
> @@ -2280,6 +2302,9 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex
>           mov_write_avcc_tag(pb, track);
>           if (track->mode == MODE_IPOD)
>               mov_write_uuid_tag_ipod(pb);
> +    }
> +    else if (track->par->codec_id ==AV_CODEC_ID_EVC) {
> +        mov_write_evcc_tag(pb, track);
>       } else if (track->par->codec_id == AV_CODEC_ID_VP9) {
>           mov_write_vpcc_tag(mov->fc, pb, track);
>       } else if (track->par->codec_id == AV_CODEC_ID_AV1) {
> @@ -6030,7 +6055,8 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
>       if ((par->codec_id == AV_CODEC_ID_DNXHD ||
>            par->codec_id == AV_CODEC_ID_H264 ||
>            par->codec_id == AV_CODEC_ID_HEVC ||
> -         par->codec_id == AV_CODEC_ID_TRUEHD) && !trk->vos_len &&
> +         par->codec_id == AV_CODEC_ID_TRUEHD ||
> +         par->codec_id == AV_CODEC_ID_EVC) && !trk->vos_len &&

Why are you copying the first muxed packet's data (in the absence of 
extradata) into vos_data if you're seemingly doing nothing with it? I'd 
expect that's what the evcC tag should contain.

####################################### 
David:
----------------------------------------------------------------
It seems that we need some information on how to do it in accordance with the rules of art. 

I guess, that our minimalistic version is not enough and should be extended, but as I mentioned we need know-how, a piece of knowledge on how to do it, and what should be done.
#######################################

>            !TAG_IS_AVCI(trk->tag)) {
>           /* copy frame to create needed atoms */
>           trk->vos_len  = size;
> @@ -7689,6 +7715,7 @@ static const AVCodecTag codec_mp4_tags[] = {
>       { AV_CODEC_ID_H264,            MKTAG('a', 'v', 'c', '3') },
>       { AV_CODEC_ID_HEVC,            MKTAG('h', 'e', 'v', '1') },
>       { AV_CODEC_ID_HEVC,            MKTAG('h', 'v', 'c', '1') },
> +    { AV_CODEC_ID_EVC,             MKTAG('e', 'v', 'c', '1') },
>       { AV_CODEC_ID_MPEG2VIDEO,      MKTAG('m', 'p', '4', 'v') },
>       { AV_CODEC_ID_MPEG1VIDEO,      MKTAG('m', 'p', '4', 'v') },
>       { AV_CODEC_ID_MJPEG,           MKTAG('m', 'p', '4', 'v') },
Dawid Kozinski Aug. 8, 2022, 12:46 p.m. UTC | #4
I just uploaded new patches that were made based on your requests and suggestions.

-----Original Message-----
From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of Michael Niedermayer
Sent: Monday, August 1, 2022 9:31 PM
To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
Subject: Re: [FFmpeg-devel] [PATCH 2/2] Provided support for MPEG-5 EVC (Essential Video Coding) codec

On Mon, Aug 01, 2022 at 11:29:01AM +0200, Dawid Kozinski wrote:
[...]

> +static int get_nalu_type(const uint8_t *bits, int bits_size)
> +{
> +    int unit_type_plus1 = 0;
> +
> +    if(bits_size >= EVC_NAL_HEADER_SIZE) {
> +        unsigned char *p = (unsigned char *)bits;
> +        // forbidden_zero_bit
> +        if ((p[0] & 0x80) != 0) {
> +            av_log(NULL, AV_LOG_ERROR, "Cannot get bitstream information. Malformed bitstream.\n");
> +            return -1;
> +        }
> +
> +        // nal_unit_type
> +        unit_type_plus1 = (p[0] >> 1) & 0x3F;
> +    }
> +
> +    return unit_type_plus1 - 1;
> +}
> +

> +static uint32_t read_nal_unit_length(const uint8_t *bits, int bits_size)
> +{
> +    uint32_t nalu_len = 0;
> +
> +    if(bits_size >= EVC_NAL_UNIT_LENGTH_BYTE) {
> +
> +        int t = 0;
> +        unsigned char *p = (unsigned char *)bits;
> +
> +        for(int i=0; i<EVC_NAL_UNIT_LENGTH_BYTE; i++) {
> +            t = (t << 8) | p[i];
> +        }
> +
> +        nalu_len = t;
> +        if(nalu_len == 0) {
> +            av_log(NULL, AV_LOG_ERROR, "Invalid bitstream size!\n");

These av_log() are a problem as they are in probing code
probing code would be run on all kinds of input, most not EVC so "errors"
are common while at the same time they are not real errors. That would
make probing very noisy
#######
DONE
#######

thx

[...]
Dawid Kozinski Aug. 8, 2022, 12:47 p.m. UTC | #5
I just uploaded new patches that were made based on your requests and suggestions.
Please find below my comments.

-----Original Message-----
From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of James Almer
Sent: Monday, August 1, 2022 4:05 PM
To: ffmpeg-devel@ffmpeg.org
Subject: Re: [FFmpeg-devel] [PATCH 2/2] Provided support for MPEG-5 EVC (Essential Video Coding) codec

On 8/1/2022 6:29 AM, Dawid Kozinski wrote:
> diff --git a/libavformat/movenc.c b/libavformat/movenc.c
> index 5608afde42..d0b094c30d 100644
> --- a/libavformat/movenc.c
> +++ b/libavformat/movenc.c
> @@ -1392,6 +1392,16 @@ static int mov_write_hvcc_tag(AVIOContext *pb, MOVTrack *track)
>       return update_size(pb, pos);
>   }
>   
> +static int mov_write_evcc_tag(AVIOContext *pb, MOVTrack *track)
> +{
> +    int64_t pos = avio_tell(pb);
> +
> +    avio_wb32(pb, 0);
> +    ffio_wfourcc(pb, "evcC");

Is that it? That's all this atom contains? No structure containing a 
global SPS/PPS?
Is there a spec anywhere defining this?
############# 
DONE 
#############

> +
> +    return update_size(pb, pos);
> +}
> +
>   /* also used by all avid codecs (dv, imx, meridien) and their variants */
>   static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track)
>   {
> @@ -1641,6 +1651,16 @@ static int mov_get_h264_codec_tag(AVFormatContext *s, MOVTrack *track)
>       return tag;
>   }
>   
> +static int mov_get_evc_codec_tag(AVFormatContext *s, MOVTrack *track)
> +{
> +    int tag = track->par->codec_tag;
> +
> +    if (!tag)
> +        tag = MKTAG('e', 'v', 'c', 'i');
> +
> +    return tag;
> +}
> +
>   static const struct {
>       enum AVPixelFormat pix_fmt;
>       uint32_t tag;
> @@ -1722,6 +1742,8 @@ static unsigned int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track)
>               tag = mov_get_mpeg2_xdcam_codec_tag(s, track);
>           else if (track->par->codec_id == AV_CODEC_ID_H264)
>               tag = mov_get_h264_codec_tag(s, track);
> +        else if (track->par->codec_id == AV_CODEC_ID_EVC)
> +            tag = mov_get_evc_codec_tag(s, track);
>           else if (track->par->codec_id == AV_CODEC_ID_DNXHD)
>               tag = mov_get_dnxhd_codec_tag(s, track);
>           else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) {
> @@ -2280,6 +2302,9 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex
>           mov_write_avcc_tag(pb, track);
>           if (track->mode == MODE_IPOD)
>               mov_write_uuid_tag_ipod(pb);
> +    }
> +    else if (track->par->codec_id ==AV_CODEC_ID_EVC) {
> +        mov_write_evcc_tag(pb, track);
>       } else if (track->par->codec_id == AV_CODEC_ID_VP9) {
>           mov_write_vpcc_tag(mov->fc, pb, track);
>       } else if (track->par->codec_id == AV_CODEC_ID_AV1) {
> @@ -6030,7 +6055,8 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
>       if ((par->codec_id == AV_CODEC_ID_DNXHD ||
>            par->codec_id == AV_CODEC_ID_H264 ||
>            par->codec_id == AV_CODEC_ID_HEVC ||
> -         par->codec_id == AV_CODEC_ID_TRUEHD) && !trk->vos_len &&
> +         par->codec_id == AV_CODEC_ID_TRUEHD ||
> +         par->codec_id == AV_CODEC_ID_EVC) && !trk->vos_len &&

Why are you copying the first muxed packet's data (in the absence of 
extradata) into vos_data if you're seemingly doing nothing with it? I'd 
expect that's what the evcC tag should contain.
############# 
DONE 
#############
>            !TAG_IS_AVCI(trk->tag)) {
>           /* copy frame to create needed atoms */
>           trk->vos_len  = size;
> @@ -7689,6 +7715,7 @@ static const AVCodecTag codec_mp4_tags[] = {
>       { AV_CODEC_ID_H264,            MKTAG('a', 'v', 'c', '3') },
>       { AV_CODEC_ID_HEVC,            MKTAG('h', 'e', 'v', '1') },
>       { AV_CODEC_ID_HEVC,            MKTAG('h', 'v', 'c', '1') },
> +    { AV_CODEC_ID_EVC,             MKTAG('e', 'v', 'c', '1') },
>       { AV_CODEC_ID_MPEG2VIDEO,      MKTAG('m', 'p', '4', 'v') },
>       { AV_CODEC_ID_MPEG1VIDEO,      MKTAG('m', 'p', '4', 'v') },
>       { AV_CODEC_ID_MJPEG,           MKTAG('m', 'p', '4', 'v') },
diff mbox series

Patch

diff --git a/doc/muxers.texi b/doc/muxers.texi
index b2f4326aae..08ab20c09e 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -2124,6 +2124,12 @@  DTS Coherent Acoustics (DCA) audio.
 
 Dolby Digital Plus, also known as Enhanced AC-3, audio.
 
+@subsection evc
+
+MPEG-5 Essential Video Coding (EVC) / EVC / MPEG-5 Part 1 EVC video.
+
+Extensions: evc
+
 @subsection g722
 
 ITU-T G.722 audio.
diff --git a/libavformat/Makefile b/libavformat/Makefile
index e420384355..8d01c89731 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -249,6 +249,8 @@  OBJS-$(CONFIG_HCOM_DEMUXER)              += hcom.o pcm.o
 OBJS-$(CONFIG_HDS_MUXER)                 += hdsenc.o
 OBJS-$(CONFIG_HEVC_DEMUXER)              += hevcdec.o rawdec.o
 OBJS-$(CONFIG_HEVC_MUXER)                += rawenc.o
+OBJS-$(CONFIG_EVC_DEMUXER)               += evcdec.o rawdec.o
+OBJS-$(CONFIG_EVC_MUXER)                 += rawenc.o
 OBJS-$(CONFIG_HLS_DEMUXER)               += hls.o hls_sample_encryption.o
 OBJS-$(CONFIG_HLS_MUXER)                 += hlsenc.o hlsplaylist.o avc.o
 OBJS-$(CONFIG_HNM_DEMUXER)               += hnm.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index ae4479fb7a..dfce49d149 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -148,6 +148,8 @@  extern const AVInputFormat  ff_ea_cdata_demuxer;
 extern const AVInputFormat  ff_eac3_demuxer;
 extern const AVOutputFormat ff_eac3_muxer;
 extern const AVInputFormat  ff_epaf_demuxer;
+extern const AVInputFormat  ff_evc_demuxer;
+extern const AVOutputFormat ff_evc_muxer;
 extern const AVOutputFormat ff_f4v_muxer;
 extern const AVInputFormat  ff_ffmetadata_demuxer;
 extern const AVOutputFormat ff_ffmetadata_muxer;
diff --git a/libavformat/evcdec.c b/libavformat/evcdec.c
new file mode 100644
index 0000000000..58dc671d24
--- /dev/null
+++ b/libavformat/evcdec.c
@@ -0,0 +1,142 @@ 
+/*
+ * RAW EVC video demuxer
+ *
+ * Copyright (c) 2021 Dawid Kozinski <d.kozinski@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 "libavcodec/get_bits.h"
+#include "libavcodec/golomb.h"
+#include "libavcodec/internal.h"
+
+#include "rawdec.h"
+#include "avformat.h"
+
+// The length field that indicates the length in bytes of the following NAL unit is configured to be of 4 bytes
+#define EVC_NAL_UNIT_LENGTH_BYTE        (4)  /* byte */
+
+#define EVC_NAL_HEADER_SIZE             (2)  /* byte */
+#define MAX_SPS_CNT                     (16) /* defined value in EVC standard */
+
+// NALU types
+// @see ISO_IEC_23094-1_2020 7.4.2.2 NAL unit header semantics
+//
+#define EVC_NUT_NONIDR                  (0)  /* Coded slice of a non-IDR picture */
+#define EVC_NUT_IDR                     (1)  /* Coded slice of an IDR picture */
+#define EVC_NUT_SPS                     (24) /* Sequence parameter set */
+#define EVC_NUT_PPS                     (25) /* Picture paremeter set */
+#define EVC_NUT_APS                     (26) /* Adaptation parameter set */
+#define EVC_NUT_FD                      (27) /* Filler data */
+#define EVC_NUT_SEI                     (28) /* Supplemental enhancement information */
+
+typedef struct EVCParserContext {
+    int got_sps;
+    int got_pps;
+    int got_idr;
+    int got_nonidr;
+} EVCParserContext;
+
+static int get_nalu_type(const uint8_t *bits, int bits_size)
+{
+    int unit_type_plus1 = 0;
+
+    if(bits_size >= EVC_NAL_HEADER_SIZE) {
+        unsigned char *p = (unsigned char *)bits;
+        // forbidden_zero_bit
+        if ((p[0] & 0x80) != 0) {
+            av_log(NULL, AV_LOG_ERROR, "Cannot get bitstream information. Malformed bitstream.\n");
+            return -1;
+        }
+
+        // nal_unit_type
+        unit_type_plus1 = (p[0] >> 1) & 0x3F;
+    }
+
+    return unit_type_plus1 - 1;
+}
+
+static uint32_t read_nal_unit_length(const uint8_t *bits, int bits_size)
+{
+    uint32_t nalu_len = 0;
+
+    if(bits_size >= EVC_NAL_UNIT_LENGTH_BYTE) {
+
+        int t = 0;
+        unsigned char *p = (unsigned char *)bits;
+
+        for(int i=0; i<EVC_NAL_UNIT_LENGTH_BYTE; i++) {
+            t = (t << 8) | p[i];
+        }
+
+        nalu_len = t;
+        if(nalu_len == 0) {
+            av_log(NULL, AV_LOG_ERROR, "Invalid bitstream size!\n");
+            return 0;
+        }
+    }
+
+    return nalu_len;
+}
+
+static int parse_nal_units(const AVProbeData *p, EVCParserContext *ev)
+{
+    int nalu_type;
+    size_t nalu_size;
+    unsigned char *bits = (unsigned char *)p->buf;
+    int bytes_to_read = p->buf_size;
+
+    while(bytes_to_read > EVC_NAL_UNIT_LENGTH_BYTE) {
+
+        nalu_size = read_nal_unit_length(bits, EVC_NAL_UNIT_LENGTH_BYTE);
+        if(nalu_size == 0) break;
+
+        bits += EVC_NAL_UNIT_LENGTH_BYTE;
+        bytes_to_read -= EVC_NAL_UNIT_LENGTH_BYTE;
+
+        if(bytes_to_read < nalu_size) break;
+
+        nalu_type = get_nalu_type(bits, bytes_to_read);
+
+        bits += nalu_size;
+        bytes_to_read -= nalu_size;
+
+        if (nalu_type == EVC_NUT_SPS)
+            ev->got_sps++;
+        else if (nalu_type == EVC_NUT_PPS)
+            ev->got_pps++;
+        else if (nalu_type == EVC_NUT_IDR )
+            ev->got_idr++;
+        else if (nalu_type == EVC_NUT_NONIDR)
+            ev->got_nonidr++;
+    }
+
+    return 0;
+}
+
+static int evc_probe(const AVProbeData *p)
+{
+    EVCParserContext ev = {0};
+    int ret = parse_nal_units(p, &ev);
+
+    if (ret == 0 && ev.got_sps && ev.got_pps && (ev.got_idr || ev.got_nonidr > 3))
+        return AVPROBE_SCORE_EXTENSION + 1;  // 1 more than .mpg
+
+    return 0;
+}
+
+FF_DEF_RAWVIDEO_DEMUXER(evc, "raw EVC video", evc_probe, "evc", AV_CODEC_ID_EVC)
diff --git a/libavformat/isom_tags.c b/libavformat/isom_tags.c
index c5fd7987f6..cd7ec7903a 100644
--- a/libavformat/isom_tags.c
+++ b/libavformat/isom_tags.c
@@ -147,6 +147,8 @@  const AVCodecTag ff_codec_movvideo_tags[] = {
     { AV_CODEC_ID_H264, MKTAG('d', 'v', 'a', '1') }, /* AVC-based Dolby Vision derived from avc1 */
     { AV_CODEC_ID_H264, MKTAG('d', 'v', 'a', 'v') }, /* AVC-based Dolby Vision derived from avc3 */
 
+    { AV_CODEC_ID_EVC,  MKTAG('e', 'v', 'c', '1') }, /* EVC/MPEG-5 */
+
     { AV_CODEC_ID_VP8,  MKTAG('v', 'p', '0', '8') }, /* VP8 */
     { AV_CODEC_ID_VP9,  MKTAG('v', 'p', '0', '9') }, /* VP9 */
     { AV_CODEC_ID_AV1,  MKTAG('a', 'v', '0', '1') }, /* AV1 */
diff --git a/libavformat/mov.c b/libavformat/mov.c
index a09a762d91..5effe45289 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -9061,7 +9061,7 @@  const AVInputFormat ff_mov_demuxer = {
     .long_name      = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),
     .priv_class     = &mov_class,
     .priv_data_size = sizeof(MOVContext),
-    .extensions     = "mov,mp4,m4a,3gp,3g2,mj2,psp,m4b,ism,ismv,isma,f4v,avif",
+    .extensions     = "mov,mp4,m4a,3gp,3g2,mj2,psp,m4b,ism,ismv,isma,f4v,avif,evc",
     .flags_internal = FF_FMT_INIT_CLEANUP,
     .read_probe     = mov_probe,
     .read_header    = mov_read_header,
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 5608afde42..d0b094c30d 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -1392,6 +1392,16 @@  static int mov_write_hvcc_tag(AVIOContext *pb, MOVTrack *track)
     return update_size(pb, pos);
 }
 
+static int mov_write_evcc_tag(AVIOContext *pb, MOVTrack *track)
+{
+    int64_t pos = avio_tell(pb);
+
+    avio_wb32(pb, 0);
+    ffio_wfourcc(pb, "evcC");
+
+    return update_size(pb, pos);
+}
+
 /* also used by all avid codecs (dv, imx, meridien) and their variants */
 static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track)
 {
@@ -1641,6 +1651,16 @@  static int mov_get_h264_codec_tag(AVFormatContext *s, MOVTrack *track)
     return tag;
 }
 
+static int mov_get_evc_codec_tag(AVFormatContext *s, MOVTrack *track)
+{
+    int tag = track->par->codec_tag;
+
+    if (!tag)
+        tag = MKTAG('e', 'v', 'c', 'i');
+
+    return tag;
+}
+
 static const struct {
     enum AVPixelFormat pix_fmt;
     uint32_t tag;
@@ -1722,6 +1742,8 @@  static unsigned int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track)
             tag = mov_get_mpeg2_xdcam_codec_tag(s, track);
         else if (track->par->codec_id == AV_CODEC_ID_H264)
             tag = mov_get_h264_codec_tag(s, track);
+        else if (track->par->codec_id == AV_CODEC_ID_EVC)
+            tag = mov_get_evc_codec_tag(s, track);
         else if (track->par->codec_id == AV_CODEC_ID_DNXHD)
             tag = mov_get_dnxhd_codec_tag(s, track);
         else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) {
@@ -2280,6 +2302,9 @@  static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex
         mov_write_avcc_tag(pb, track);
         if (track->mode == MODE_IPOD)
             mov_write_uuid_tag_ipod(pb);
+    }
+    else if (track->par->codec_id ==AV_CODEC_ID_EVC) {
+        mov_write_evcc_tag(pb, track);
     } else if (track->par->codec_id == AV_CODEC_ID_VP9) {
         mov_write_vpcc_tag(mov->fc, pb, track);
     } else if (track->par->codec_id == AV_CODEC_ID_AV1) {
@@ -6030,7 +6055,8 @@  int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
     if ((par->codec_id == AV_CODEC_ID_DNXHD ||
          par->codec_id == AV_CODEC_ID_H264 ||
          par->codec_id == AV_CODEC_ID_HEVC ||
-         par->codec_id == AV_CODEC_ID_TRUEHD) && !trk->vos_len &&
+         par->codec_id == AV_CODEC_ID_TRUEHD ||
+         par->codec_id == AV_CODEC_ID_EVC) && !trk->vos_len &&
          !TAG_IS_AVCI(trk->tag)) {
         /* copy frame to create needed atoms */
         trk->vos_len  = size;
@@ -7689,6 +7715,7 @@  static const AVCodecTag codec_mp4_tags[] = {
     { AV_CODEC_ID_H264,            MKTAG('a', 'v', 'c', '3') },
     { AV_CODEC_ID_HEVC,            MKTAG('h', 'e', 'v', '1') },
     { AV_CODEC_ID_HEVC,            MKTAG('h', 'v', 'c', '1') },
+    { AV_CODEC_ID_EVC,             MKTAG('e', 'v', 'c', '1') },
     { AV_CODEC_ID_MPEG2VIDEO,      MKTAG('m', 'p', '4', 'v') },
     { AV_CODEC_ID_MPEG1VIDEO,      MKTAG('m', 'p', '4', 'v') },
     { AV_CODEC_ID_MJPEG,           MKTAG('m', 'p', '4', 'v') },
diff --git a/libavformat/rawenc.c b/libavformat/rawenc.c
index 26099cb1c1..48dedfa375 100644
--- a/libavformat/rawenc.c
+++ b/libavformat/rawenc.c
@@ -401,6 +401,19 @@  const AVOutputFormat ff_hevc_muxer = {
 };
 #endif
 
+#if CONFIG_EVC_MUXER
+AVOutputFormat ff_evc_muxer = {
+    .name              = "evc",
+    .long_name         = NULL_IF_CONFIG_SMALL("raw EVC video"),
+    .extensions        = "evc",
+    .audio_codec       = AV_CODEC_ID_NONE,
+    .video_codec       = AV_CODEC_ID_EVC,
+    .write_header      = force_one_stream,
+    .write_packet      = ff_raw_write_packet,
+    .flags             = AVFMT_NOTIMESTAMPS,
+};
+#endif
+
 #if CONFIG_M4V_MUXER
 const AVOutputFormat ff_m4v_muxer = {
     .name              = "m4v",