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需要的信息,这些信息比较多,有些还搞不清楚,等以后
//有时间再研究
....
}