ffmpeg源码学习笔记三

本文深入探讨了如何解析MKV文件,重点介绍了EBML语法结构和解析流程,包括读取头部信息、处理seekhead和segments,以及解析tracks、attachments和chapters等关键元素。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

9. 关于如何parse mkv

前面为了不把战线拉太长,把如何parse mkv container 内容直接跳过了

接下来还是从read_header 开始讲解

static int matroska_read_header(AVFormatContext *s)
{
    ...
    //mkv整个文件是以EBML形式存储的,如果对matroska不熟悉,可以去官网下载文档阅读
    //multimedia文件的存放,从segment开始,前面EBML可以略过
    /* The next thing is a segment. */
    /*
    首先看一下EbmlSyntax 结构体,是一个链表结构
    typedef const struct EbmlSyntax {
        //ID,EBML特殊标志符, 例如
        //#define MATROSKA_ID_TRACKS     0x1654AE6B 表示接下来存储的是track信息
        //这是matroska官方定义的标准
		uint32_t id;
        //type这是FFMPEG自己定义的,表示ebml 类型,例如 EBML_STOP 
        //表示ebml_parse函数parse到这种类型,就结束parse动作
        //对应于 res = ebml_parse(matroska, matroska_segments, matroska);返回1
        //EBML_NEST表示有嵌套.
		EbmlType type;
        //当前ebml 的size. ebml是树状结构,表示当前节点的size
		int list_elem_size;
        //距离文件头偏移量
		int data_offset;
		union {
			int64_t     i;
			uint64_t    u;
			double      f;
			const char *s;
			const struct EbmlSyntax *n;
		} def;//当前节点的值,如果
	} EbmlSyntax; 
    例如:
    static const EbmlSyntax matroska_seekhead[] = {
		{ MATROSKA_ID_SEEKENTRY, EBML_NEST, sizeof(MatroskaSeekhead), offsetof(MatroskaDemuxContext, seekhead), { .n = matroska_seekhead_entry } },
		{ 0 }
	};
	
	static const EbmlSyntax matroska_segment[] = {
		{ MATROSKA_ID_INFO,        EBML_LEVEL1, 0, 0, { .n = matroska_info } },
		{ MATROSKA_ID_TRACKS,      EBML_LEVEL1, 0, 0, { .n = matroska_tracks } },
		{ MATROSKA_ID_ATTACHMENTS, EBML_LEVEL1, 0, 0, { .n = matroska_attachments } },
		{ MATROSKA_ID_CHAPTERS,    EBML_LEVEL1, 0, 0, { .n = matroska_chapters } },
		{ MATROSKA_ID_CUES,        EBML_LEVEL1, 0, 0, { .n = matroska_index } },
		{ MATROSKA_ID_TAGS,        EBML_LEVEL1, 0, 0, { .n = matroska_tags } },
		{ MATROSKA_ID_SEEKHEAD,    EBML_LEVEL1, 0, 0, { .n = matroska_seekhead } },
		{ MATROSKA_ID_CLUSTER,     EBML_STOP },
		{ 0 }
	};
	
	static const EbmlSyntax matroska_segments[] = {
		{ MATROSKA_ID_SEGMENT, EBML_NEST, 0, 0, { .n = matroska_segment } },
		{ 0 }
	};
    */

    //获取当前读的位置,因为前面有读/parse EBML段信息,pos应该就是从segement开始位置
    pos = avio_tell(matroska->ctx->pb);
    //parse 顶层segment信息
    res = ebml_parse(matroska, matroska_segments, matroska);
    //下面循环parse segment子节点信息,也就是matroska_segment 中的信息,parse到cluster结束.
    //cluster中保存的是media data信息,是播放时候parse的.
    //ebml_parse 过程,就是填写 MatroskaDemuxContext 过程
    // try resyncing until we find a EBML_STOP type element.
    while (res != 1) {
        res = matroska_resync(matroska, pos);
        if (res < 0)
            return res;
        pos = avio_tell(matroska->ctx->pb);
        //如果遇到EBML_STOP,返回1,跳出循环.
        res = ebml_parse(matroska, matroska_segment, matroska);
    }
    //读取seekhead信息,seekhead里面保存level_1层head的索引
    /*原因是matroska 并没有要求所有的头信息都在文件最开始,
     有些重要的信息可能在cluster后面,之前parse 到cluster就结束了,导致无法拿到全部的
    level_1层信息,所以就需要通过seekhead seek 到相应位置(cluster之后)去parse 一些信息.
    static void matroska_execute_seekhead(MatroskaDemuxContext *matroska)
	{
		EbmlList *seekhead_list = &matroska->seekhead;
		int i;
	
		// we should not do any seeking in the streaming case
		if (!matroska->ctx->pb->seekable)
			return;
	
		for (i = 0; i < seekhead_list->nb_elem; i++) {
			MatroskaSeekhead *seekheads = seekhead_list->elem;
			uint32_t id  = seekheads[i].id;
			uint64_t pos = seekheads[i].pos;
	
            //从seekheaders中判断是否是lvl1层的elem,如果不是,或者parse过了,就直接跳过
			MatroskaLevel1Element *elem = matroska_find_level1_elem(matroska, id);
			if (!elem || elem->parsed)
				continue;
	
			elem->pos = pos;
	
			// defer cues parsing until we actually need cue data.
            //暂且不需要parse cue data(保存key frame data,这块数据量比较大)
			if (id == MATROSKA_ID_CUES)
				continue;
	
            //这个API就是直接跳到seekheads[i].pos位置去parse相应的信息,信息还是保存在
            //matroska中
			if (matroska_parse_seekhead_entry(matroska, pos) < 0) {
				// mark index as broken
				matroska->cues_parsing_deferred = -1;
				break;
			}
	
			elem->parsed = 1;
		}
	}
    */
    matroska_execute_seekhead(matroska);

    if (!matroska->time_scale)
        matroska->time_scale = 1000000;
    if (matroska->duration)//计算duration
        matroska->ctx->duration = matroska->duration * matroska->time_scale *
                                  1000 / AV_TIME_BASE;
    //s->metadata为NULL,在av_dict_set分配memory.
    av_dict_set(&s->metadata, "title", matroska->title, 0);
    av_dict_set(&s->metadata, "encoder", matroska->muxingapp, 0);
    ...
    
    //parse track 信息,因为前面只是件ebml 信息保存在matroska->track中,现在需要parse出
    //每个栏位的具体信息
    /*
    处理一些track信息,这里信息也蛮多的,接下来介绍
    */
    res = matroska_parse_tracks(s);
    if (res < 0)
        return res;

    //后面再parse attachments和chapter, 大部分文件都没有attachments和chapter 信息
    ...
    
}

10. matroska_parse_tracks 函数

static int matroska_parse_tracks(AVFormatContext *s)
{
    ...

    for (i = 0; i < matroska->tracks.nb_elem; i++) {
       ...

        /* Apply some sanity checks. */
        if (track->type != MATROSKA_TRACK_TYPE_VIDEO &&
            track->type != MATROSKA_TRACK_TYPE_AUDIO &&
            track->type != MATROSKA_TRACK_TYPE_SUBTITLE &&
            track->type != MATROSKA_TRACK_TYPE_METADATA) {
            av_log(matroska->ctx, AV_LOG_INFO,
                   "Unknown or unsupported track type %"PRIu64"\n",
                   track->type);
            continue;
        }
        //如果为NULL,skip
        if (!track->codec_id)
            continue;

      ...

        if (track->type == MATROSKA_TRACK_TYPE_VIDEO) {
            //如果default_duration是0,计算default_duration
            if (!track->default_duration && track->video.frame_rate > 0) {
                double default_duration = 1000000000 / track->video.frame_rate;
                if (default_duration > UINT64_MAX || default_duration < 0) {
                    av_log(matroska->ctx, AV_LOG_WARNING,
                         "Invalid frame rate %e. Cannot calculate default duration.\n",
                         track->video.frame_rate);
                } else {
                    track->default_duration = default_duration;
                }
            }
            //填宽高
            if (track->video.display_width == -1)
                track->video.display_width = track->video.pixel_width;
            if (track->video.display_height == -1)
                track->video.display_height = track->video.pixel_height;
            if (track->video.color_space.size == 4)
                fourcc = AV_RL32(track->video.color_space.data);
        } else if (track->type == MATROSKA_TRACK_TYPE_AUDIO) {
            if (!track->audio.out_samplerate)
                track->audio.out_samplerate = track->audio.samplerate;
        }

        ...
        //处理一些头解压,一般很少会将头压缩
        ...

        //之前保存在track->codec_id 是char*, 转化为ffmpeg内部的codec类型
        //char* -->enum
        for (j = 0; ff_mkv_codec_tags[j].id != AV_CODEC_ID_NONE; j++) {
            if (!strncmp(ff_mkv_codec_tags[j].str, track->codec_id,
                         strlen(ff_mkv_codec_tags[j].str))) {
                codec_id = ff_mkv_codec_tags[j].id;
                break;
            }
        }
        
        //alloc AVStream,之前有介绍
        st = track->stream = avformat_new_stream(s, NULL);
        if (!st) {
            av_free(key_id_base64);
            return AVERROR(ENOMEM);
        }

        //从codec_private信息中,parse一些信息,
        //codec_private信息存放一些decoder需要的信息,这些信息比较多,有些还搞不清楚,等以后
        //有时间再研究
        ....
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值