- 摘要:之前在Mp4格式详解中详细描述了Mp4文件格式的具体布局方式。为了更加深入理解mp4文件格式,本文记录了ffmpeg中解封装mp4文件的基本实现。
- 关键字:
mov 、FFmpeg 、mp4
1 简介
??
2 ff_mov_demuxer
??在FFmpeg中mp4文件解封装的实现在
??
??
static const AVClass mov_class = { .class_name = "mov,mp4,m4a,3gp,3g2,mj2", .item_name = av_default_item_name, .option = mov_options, .version = LIBAVUTIL_VERSION_INT, }; const AVInputFormat ff_mov_demuxer = { .name = "mov,mp4,m4a,3gp,3g2,mj2", .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", .flags_internal = FF_FMT_INIT_CLEANUP, .read_probe = mov_probe, .read_header = mov_read_header, .read_packet = mov_read_packet, .read_close = mov_read_close, .read_seek = mov_read_seek, .flags = AVFMT_NO_BYTE_SEEK | AVFMT_SEEK_TO_PTS | AVFMT_SHOW_IDS, };
??
name :由, 隔开的格式名称;long_name :全称;priv_class :私有的选项;priv_data_size :私有数据的大小,一般为对应格式的Context,比如mov格式为MOVContext ;extensions :扩展名,能够看到mov ,```mp4````等格式公用同一个解封装器;flag_internal :内部的标志符;const char *mime_type; :, 隔开的mime_type;read_probe :探测当前文件是那个类型的文件的函数指针,在avformat_find_stream_info 用来探测当前文件是否为mp4 文件,以及相关流信息;read_header :读取格式header,初始化AVFormatContext 的函数指针,avformat_open_input 时调用,用来读取文件的基本信息;read_packet :从文件中读取一个packet的函数指针,读取未解码的数据流的函数指针,在av_read_frame 时调用;read_close :关闭流,但是不涉及对应流的释放;read_seek :seek到对应的位置,av_seek_frame 时调用来seek到对应的位置;flags; :操作文件个标志符,比如是否允许按照bytes seek等。
??解封装的基本流程与用
3 解封装具体流程
3.1 解封装涉及的结构体
??mp4解封装涉及的结构体比较多,这里挑选几个重点说下。
typedef struct MOVAtom { uint32_t type; int64_t size; /* total size (excluding the size and type fields) */ } MOVAtom;
??以及一些他描述mp4流,轨道索引等信息的结构体,比如
3.1 mov_probe
??
#define AVPROBE_SCORE_EXTENSION 50 ///< score for file extension #define AVPROBE_SCORE_MIME 75 ///< score for file mime type #define AVPROBE_SCORE_MAX 100 ///< maximum score
??
int mov_probe(int *p){ int score = 0, offset = 0, moov_offset = -1; while(1){ int64_t size = AV_RB32(p + offset); //从当前流的位置读取当前box的大小,伪代码不考虑largesize的情况 char tag[4] = AV_RL32(p + offset+ 4) //从接下来的内存中读取tag switch(tag){ case "moov":moov_offset = offset + 4; case "mdat": case "pnot": case "udta": case "ftyp": if(tag == "ftyp" && tag in ["jp2 " "jpx " "jxl "]){ score = std::max(score, 5); }else{ score = AVPROBE_SCORE_MAX; } break; case "ediw": case "wide": case "junk": case "pict": score = std::max(score , AVPROBE_SCORE_MAX - 5);break; case "skip": case "uuid": case "prfl": score = std::max(score, AVPROBE_SCORE_EXTENSION);break; } offset += size } if(score > AVPROBE_SCORE_MAX - 50 && moov_offset != -1){ /* moov atom in the header - we should make sure that this is not a * MOV-packed MPEG-PS */ offset = moov_offset; while (offset < (len(p) - 16)) { /* Sufficient space */ /* We found an actual hdlr atom */ if (AV_RL32(p->buf + offset ) == MKTAG('h','d','l','r') && AV_RL32(p->buf + offset + 8) == MKTAG('m','h','l','r') && AV_RL32(p->buf + offset + 12) == MKTAG('M','P','E','G')) { av_log(NULL, AV_LOG_WARNING, "Found media data tag MPEG indicating this is a MOV-packed MPEG-PS. "); /* We found a media handler reference atom describing an * MPEG-PS-in-MOV, return a * low score to force expanding the probe window until * mpegps_probe finds what it needs */ return 5; } else { /* Keep looking */ offset += 2; } } } return score; }
??
3.2 mov_read_header
??
??
/* check MOV header *///不断嵌套读,直到读到moov box未知 do { if (mov->moov_retry) avio_seek(pb, 0, SEEK_SET); if ((err = mov_read_default(mov, pb, atom)) < 0) { av_log(s, AV_LOG_ERROR, "error reading header "); return err; } } while ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mov->found_moov && !mov->moov_retry++);
??
static const MOVParseTableEntry mov_default_parse_table[] = { { MKTAG('A','C','L','R'), mov_read_aclr }, { MKTAG('A','P','R','G'), mov_read_avid }, { MKTAG('A','A','L','P'), mov_read_avid }, { MKTAG('A','R','E','S'), mov_read_ares }, { MKTAG('a','v','s','s'), mov_read_avss }, ... { MKTAG('m','d','c','v'), mov_read_mdcv }, { MKTAG('c','l','l','i'), mov_read_clli }, { MKTAG('d','v','c','C'), mov_read_dvcc_dvvc }, { MKTAG('d','v','v','C'), mov_read_dvcc_dvvc }, { 0, NULL } };}
??经过上面的步骤,流的基本信息已经存储在
3.3 mov_read_packet
??
??首先会调用
sample = mov_find_next_sample(s, &st); if (!sample || (mov->next_root_atom && sample->pos > mov->next_root_atom)) { if (!mov->next_root_atom) return AVERROR_EOF; if ((ret = mov_switch_root(s, mov->next_root_atom, -1)) < 0) return ret; goto retry; }
??然后就是根据标志位来判断当前packet是否要丢弃,调用
if (st->codecpar->codec_id == AV_CODEC_ID_EIA_608 && sample->size > 8) ret = get_eia608_packet(sc->pb, pkt, sample->size); else ret = av_get_packet(sc->pb, pkt, sample->size);
??最后就是填充
3.4 mov_read_seek
??
3.5 mov_read_close
??
static int mov_read_close(AVFormatContext *s) { MOVContext *mov = s->priv_data; int i, j; for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; MOVStreamContext *sc = st->priv_data; if (!sc) continue; av_freep(&sc->ctts_data); for (j = 0; j < sc->drefs_count; j++) { av_freep(&sc->drefs[j].path); av_freep(&sc->drefs[j].dir); } av_freep(&sc->drefs); sc->drefs_count = 0; if (!sc->pb_is_copied) ff_format_io_close(s, &sc->pb); //内部就是调用io_close sc->pb = NULL; av_freep(&sc->chunk_offsets); av_freep(&sc->stsc_data); av_freep(&sc->sample_sizes); av_freep(&sc->keyframes); av_freep(&sc->stts_data); av_freep(&sc->sdtp_data); av_freep(&sc->stps_data); av_freep(&sc->elst_data); av_freep(&sc->rap_group); av_freep(&sc->display_matrix); av_freep(&sc->index_ranges); if (sc->extradata) for (j = 0; j < sc->stsd_count; j++) av_free(sc->extradata[j]); av_freep(&sc->extradata); av_freep(&sc->extradata_size); mov_free_encryption_index(&sc->cenc.encryption_index); av_encryption_info_free(sc->cenc.default_encrypted_sample); av_aes_ctr_free(sc->cenc.aes_ctr); av_freep(&sc->stereo3d); av_freep(&sc->spherical); av_freep(&sc->mastering); av_freep(&sc->coll); } av_freep(&mov->dv_demux); avformat_free_context(mov->dv_fctx); mov->dv_fctx = NULL; if (mov->meta_keys) { for (i = 1; i < mov->meta_keys_count; i++) { av_freep(&mov->meta_keys[i]); } av_freep(&mov->meta_keys); } av_freep(&mov->trex_data); av_freep(&mov->bitrates); for (i = 0; i < mov->frag_index.nb_items; i++) { MOVFragmentStreamInfo *frag = mov->frag_index.item[i].stream_info; for (j = 0; j < mov->frag_index.item[i].nb_stream_info; j++) { mov_free_encryption_index(&frag[j].encryption_index); } av_freep(&mov->frag_index.item[i].stream_info); } av_freep(&mov->frag_index.item); av_freep(&mov->aes_decrypt); av_freep(&mov->chapter_tracks); return 0; }