avformat/mov: Add support for demuxing still HEIC images

They are similar to AVIF images (both use the HEIF container).
The only additional work needed is to parse the hvcC box and put
it in the extradata.

With this patch applied, ffmpeg (when built with an HEVC decoder)
is able to decode the files in
https://github.com/nokiatech/heif/tree/gh-pages/content/images

Also add a couple of fate tests with samples from
https://github.com/nokiatech/heif_conformance/tree/master/conformance_files

Partially fixes trac ticket #6521.

Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com>
Signed-off-by: James Almer <jamrial@gmail.com>
This commit is contained in:
Vignesh Venkatasubramanian via ffmpeg-devel 2023-10-04 09:40:15 -07:00 committed by James Almer
parent bb819a4ef8
commit a0821345eb
5 changed files with 71 additions and 2 deletions

View File

@ -327,6 +327,8 @@ typedef struct MOVContext {
int64_t extent_offset;
} *avif_info;
int avif_info_size;
int64_t hvcC_offset;
int hvcC_size;
int interleaved_read;
} MOVContext;

View File

@ -1233,7 +1233,8 @@ static int mov_read_ftyp(MOVContext *c, AVIOContext *pb, MOVAtom atom)
c->isom = 1;
av_log(c->fc, AV_LOG_DEBUG, "ISO: File Type Major Brand: %.4s\n",(char *)&type);
av_dict_set(&c->fc->metadata, "major_brand", type, 0);
c->is_still_picture_avif = !strncmp(type, "avif", 4);
c->is_still_picture_avif = !strncmp(type, "avif", 4) ||
!strncmp(type, "mif1", 4);
minor_ver = avio_rb32(pb); /* minor version */
av_dict_set_int(&c->fc->metadata, "minor_version", minor_ver, 0);
@ -4940,6 +4941,19 @@ static int avif_add_stream(MOVContext *c, int item_id)
st->priv_data = sc;
st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
st->codecpar->codec_id = AV_CODEC_ID_AV1;
if (c->hvcC_offset >= 0) {
int ret;
int64_t pos = avio_tell(c->fc->pb);
st->codecpar->codec_id = AV_CODEC_ID_HEVC;
if (avio_seek(c->fc->pb, c->hvcC_offset, SEEK_SET) != c->hvcC_offset) {
av_log(c->fc, AV_LOG_ERROR, "Failed to seek to hvcC data.\n");
return AVERROR_UNKNOWN;
}
ret = ff_get_extradata(c->fc, st->codecpar, c->fc->pb, c->hvcC_size);
if (ret < 0)
return ret;
avio_seek(c->fc->pb, pos, SEEK_SET);
}
sc->ffindex = st->index;
c->trak_index = st->index;
st->avg_frame_rate.num = st->avg_frame_rate.den = 1;
@ -4982,6 +4996,8 @@ static int avif_add_stream(MOVContext *c, int item_id)
static int mov_read_meta(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
c->hvcC_offset = -1;
c->hvcC_size = 0;
while (atom.size > 8) {
uint32_t tag;
if (avio_feof(pb))
@ -7859,6 +7875,28 @@ static int mov_read_iloc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
return atom.size;
}
static int mov_read_iprp(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
int size = avio_rb32(pb);
if (avio_rl32(pb) != MKTAG('i','p','c','o'))
return AVERROR_INVALIDDATA;
size -= 8;
while (size > 0) {
int sub_size, sub_type;
sub_size = avio_rb32(pb);
sub_type = avio_rl32(pb);
sub_size -= 8;
size -= sub_size + 8;
if (sub_type == MKTAG('h','v','c','C')) {
c->hvcC_offset = avio_tell(pb);
c->hvcC_size = sub_size;
break;
}
avio_skip(pb, sub_size);
}
return atom.size;
}
static const MOVParseTableEntry mov_default_parse_table[] = {
{ MKTAG('A','C','L','R'), mov_read_aclr },
{ MKTAG('A','P','R','G'), mov_read_avid },
@ -7966,6 +8004,7 @@ static const MOVParseTableEntry mov_default_parse_table[] = {
{ MKTAG('p','c','m','C'), mov_read_pcmc }, /* PCM configuration box */
{ MKTAG('p','i','t','m'), mov_read_pitm },
{ MKTAG('e','v','c','C'), mov_read_glbl },
{ MKTAG('i','p','r','p'), mov_read_iprp },
{ 0, NULL }
};
@ -9335,7 +9374,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,heic,heif",
.flags_internal = FF_FMT_INIT_CLEANUP,
.read_probe = mov_probe,
.read_header = mov_read_header,

View File

@ -20,6 +20,8 @@ FATE_MOV = fate-mov-3elist \
fate-mov-pcm-remux \
fate-mov-avif-demux-still-image-1-item \
fate-mov-avif-demux-still-image-multiple-items \
fate-mov-heic-demux-still-image-1-item \
fate-mov-heic-demux-still-image-multiple-items \
FATE_MOV_FFPROBE = fate-mov-neg-firstpts-discard \
fate-mov-neg-firstpts-discard-vorbis \
@ -148,6 +150,10 @@ fate-mov-avif-demux-still-image-1-item: CMD = framemd5 -i $(TARGET_SAMPLES)/avif
# parsed.
fate-mov-avif-demux-still-image-multiple-items: CMD = framemd5 -i $(TARGET_SAMPLES)/avif/still_image_exif.avif -c:v copy
fate-mov-heic-demux-still-image-1-item: CMD = framemd5 -i $(TARGET_SAMPLES)/heif-conformance/C002.heic -c:v copy
fate-mov-heic-demux-still-image-multiple-items: CMD = framemd5 -i $(TARGET_SAMPLES)/heif-conformance/C003.heic -c:v copy
# Resulting remux should have:
# 1. first audio stream with AV_DISPOSITION_HEARING_IMPAIRED
# 2. second audio stream with AV_DISPOSITION_VISUAL_IMPAIRED | DESCRIPTIONS

View File

@ -0,0 +1,11 @@
#format: frame checksums
#version: 2
#hash: MD5
#extradata 0, 100, 5444bf01e03182c73ae957179d560f4d
#tb 0: 1/1
#media_type 0: video
#codec_id 0: hevc
#dimensions 0: 1280x720
#sar 0: 0/1
#stream#, dts, pts, duration, size, hash
0, 0, 0, 1, 111554, 03ceabfab39afd2e2e796b9362111f32

View File

@ -0,0 +1,11 @@
#format: frame checksums
#version: 2
#hash: MD5
#extradata 0, 100, 5444bf01e03182c73ae957179d560f4d
#tb 0: 1/1
#media_type 0: video
#codec_id 0: hevc
#dimensions 0: 1280x720
#sar 0: 0/1
#stream#, dts, pts, duration, size, hash
0, 0, 0, 1, 111554, 03ceabfab39afd2e2e796b9362111f32