赞
踩
本篇是 《播放器网络视频数据读取过程详解》 的延续部分,我们回顾一下上一篇。
我们详细分析了自定义的协议是如何以静态方式、注册到 IJKPLAYER 协议profile中。
本篇分析打开自定义协议、读取数据流数据并匹配解封装函数,也即是说本篇要把私有协议、私有数据封装
的数据流模式,实现在ijkplayer中播放流程说清楚。
static int init_input(AVFormatContext *s, const char *filename,
AVDictionary **options)
{
if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, ///> 1.2. 此函数是 io_open() 打开文件或网络socket
options)) < 0)
return ret;
if (s->iformat)
return 0;
return av_probe_input_buffer2(s->pb, &s->iformat, filename, ///> 1.3 读数据流的第一帧数据
s, 0, s->format_probesize);
}
在 init_input() 函数中调用了 io_open() 函数,
ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options);
这个函数其根本是执行此函数
static int io_open_default(AVFormatContext *s, AVIOContext **pb,
const char *url, int flags, AVDictionary **options)
在该函数中调用函数
ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);
匹配通讯协议,并根据通讯建立通讯连接。前面文章已经描述此过程,再次我们简单回顾。
int ffio_open_whitelist(AVIOContext **s, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist, const char *blacklist ) { URLContext *h; int err; ///> 此函数匹配 filename 中协议类别,创建通讯对象和关联结构体初始化。 err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL); if (err < 0) return err; err = ffio_fdopen(s, h); ///> 调用 ffio_fdopen 函数,分配AVIOContext的io buffer, if (err < 0) { ffurl_close(h); return err; } return 0; } int ffio_fdopen(AVIOContext **s, URLContext *h) { AVIOInternal *internal = NULL; uint8_t *buffer = NULL; int buffer_size, max_packet_size; av_log(NULL, AV_LOG_INFO, "%s/%s(), LINE:%d \n",__FILE__, __func__, __LINE__); max_packet_size = h->max_packet_size; if (max_packet_size) { buffer_size = max_packet_size; /* no need to bufferize more than one packet */ } else { buffer_size = IO_BUFFER_SIZE; } buffer = av_malloc(buffer_size); ///> 用于预取视频数据的缓存, io buffer大小为32k if (!buffer) return AVERROR(ENOMEM); internal = av_mallocz(sizeof(*internal)); ///> AVIOInternal指针, 指向URLContext if (!internal) goto fail; internal->h = h; ///> 此函数动态设置 read_packet 指针指向的函数为 io_read_packet() 函数。 *s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, ///> 分配AVIOContext, 设置read_packet、write_packet、seek函数, internal, io_read_packet, io_write_packet, io_seek);///> 对应为io_read_packet、io_write_packet、io_see if (!*s) goto fail; av_log(NULL, AV_LOG_INFO, "%s/%s(), LINE:%d \n",__FILE__, __func__, __LINE__); (*s)->protocol_whitelist = av_strdup(h->protocol_whitelist); if (!(*s)->protocol_whitelist && h->protocol_whitelist) { avio_closep(s); goto fail; } (*s)->protocol_blacklist = av_strdup(h->protocol_blacklist); if (!(*s)->protocol_blacklist && h->protocol_blacklist) { avio_closep(s); goto fail; } (*s)->direct = h->flags & AVIO_FLAG_DIRECT; av_log(NULL, AV_LOG_INFO, "%s/%s(), LINE:%d \n",__FILE__, __func__, __LINE__); (*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL; (*s)->max_packet_size = max_packet_size; (*s)->min_packet_size = h->min_packet_size; if(h->prot) { (*s)->read_pause = io_read_pause; (*s)->read_seek = io_read_seek; if (h->prot->url_read_seek) (*s)->seekable |= AVIO_SEEKABLE_TIME; } (*s)->short_seek_get = io_short_seek; (*s)->av_class = &ff_avio_class; return 0; fail: av_log(NULL, AV_LOG_INFO, "%s/%s(), LINE:%d \n",__FILE__, __func__, __LINE__); av_freep(&internal); av_freep(&buffer); return AVERROR(ENOMEM); }
此 open_fdopen() 函数主要工作内容建立 AVIOContext 对象,并把对象关联到播放器对象上,就完成通讯协议
各部分的初始化工作,通讯链接已经建立起来。
接下来我们分析读取数据流第一帧数据 probe data 和数据流解封装的过程,在 init_input() 函数中,执行
io_open() 函数后,就调用下面这个函数。
///> av_probe_input_buffer2(s->pb, &s->iformat, filename,s, 0, s->format_probesize); ///> 1.3 读数据流的第一帧数据 int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt, const char *filename, void *logctx, unsigned int offset, unsigned int max_probe_size) { AVProbeData pd = { filename ? filename : "" }; uint8_t *buf = NULL; int ret = 0, probe_size, buf_offset = 0; int score = 0; int ret2; if (!max_probe_size) max_probe_size = PROBE_BUF_MAX; else if (max_probe_size < PROBE_BUF_MIN) { av_log(logctx, AV_LOG_ERROR, "Specified probe size value %u cannot be < %u\n", max_probe_size, PROBE_BUF_MIN); return AVERROR(EINVAL); } if (offset >= max_probe_size) return AVERROR(EINVAL); if (pb->av_class) { ///> av_class 指针执行当前 编解码类对象 uint8_t *mime_type_opt = NULL; char *semi; av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt); ///> 获取当前av_class对象的mime_type内容 pd.mime_type = (const char *)mime_type_opt; semi = pd.mime_type ? strchr(pd.mime_type, ';') : NULL; if (semi) { *semi = '\0'; } } #if 0 if (!*fmt && pb->av_class && av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type) >= 0 && mime_type) { if (!av_strcasecmp(mime_type, "audio/aacp")) { *fmt = av_find_input_format("aac"); } av_freep(&mime_type); } #endif ///> 此循环体是在 max_probe_size 范围内容,查找可以使用的解封装对象。 for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt; probe_size = FFMIN(probe_size << 1,FFMAX(max_probe_size, probe_size + 1))) { score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0; /* Read probe data.buf缓存默认大小为2k+32 */ if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0) ///> extra allocated bytes at the end of the probe buffer goto fail; if ((ret = avio_read(pb, buf + buf_offset, ///> 1.3.1 此函数最终调用 s->read_packet(s->opaque, buf, size); probe_size - buf_offset)) < 0) ///> 读取 probe_size 长度数据到 buf 中供 av_probe_input_format2() 用 { /* Fail if error was not end of file, otherwise, lower score. */ if (ret != AVERROR_EOF) goto fail; score = 0; ret = 0; /* error was end of file, nothing read */ } buf_offset += ret; if (buf_offset < offset) continue; pd.buf_size = buf_offset - offset; pd.buf = &buf[offset]; ///> 1.3.2 将buf缓存保存到AVProbeData中 memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE); /* Guess file format. 根据AVPorbeData中的数据,探测出输入视频的容器格式, 即 AVInputFormat,ffmpeg支持的AVInputFormat定义在 demuxer_list.c 中 */ *fmt = av_probe_input_format2(&pd, 1, &score); ///> 1.3.3 根据 pd.buf 中数据匹配 demuxer 格式 if (*fmt) { /* This can only be true in the last iteration. */ if (score <= AVPROBE_SCORE_RETRY) { av_log(logctx, AV_LOG_WARNING, "Format %s detected only with low score of %d, " "misdetection possible!\n", (*fmt)->name, score); } else av_log(logctx, AV_LOG_DEBUG, "Format %s probed with size=%d and score=%d\n", (*fmt)->name, probe_size, score); #if 0 FILE *f = fopen("probestat.tmp", "ab"); fprintf(f, "probe_size:%d format:%s score:%d filename:%s\n", probe_size, (*fmt)->name, score, filename); fclose(f); #endif } } if (!*fmt) ret = AVERROR_INVALIDDATA; fail: /* Rewind. Reuse probe buffer to avoid seeking. */ ret2 = ffio_rewind_with_probe_data(pb, &buf, buf_offset); ///> 1.3.4 匹配到解封装器后,把probe中数据转移 if (ret >= 0) ret = ret2; av_freep(&pd.mime_type); return ret < 0 ? ret : score; }
根据上面的代码我们知道数据流格式匹配由函数 av_probe_input_format2() 处理。
至此我们先总结一下这个逻辑:
接下来 看 ff_mov_demuxer 解封装器是如何实现,此源码路径: libavformat / mov.c
#define OFFSET(x) offsetof(MOVContext, x) #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM static const AVOption mov_options[] = { {"use_absolute_path", "allow using absolute path when opening alias, this is a possible security issue", OFFSET(use_absolute_path), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, FLAGS}, {"seek_streams_individually", "Seek each stream individually to the to the closest point", OFFSET(seek_individually), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, FLAGS}, {"ignore_editlist", "Ignore the edit list atom.", OFFSET(ignore_editlist), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, FLAGS}, {"advanced_editlist", "Modify the AVIndex according to the editlists. Use this option to decode in the order specified by the edits.", OFFSET(advanced_editlist), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS}, {"ignore_chapters", "", OFFSET(ignore_chapters), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, FLAGS}, {"use_mfra_for", "use mfra for fragment timestamps", OFFSET(use_mfra_for), AV_OPT_TYPE_INT, {.i64 = FF_MOV_FLAG_MFRA_AUTO}, -1, FF_MOV_FLAG_MFRA_PTS, FLAGS, "use_mfra_for"}, {"auto", "auto", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_MFRA_AUTO}, 0, 0, FLAGS, "use_mfra_for" }, {"dts", "dts", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_MFRA_DTS}, 0, 0, FLAGS, "use_mfra_for" }, {"pts", "pts", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_MFRA_PTS}, 0, 0, FLAGS, "use_mfra_for" }, { "export_all", "Export unrecognized metadata entries", OFFSET(export_all), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, .flags = FLAGS }, { "export_xmp", "Export full XMP metadata", OFFSET(export_xmp), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, .flags = FLAGS }, { "activation_bytes", "Secret bytes for Audible AAX files", OFFSET(activation_bytes), AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_DECODING_PARAM }, { "audible_fixed_key", // extracted from libAAX_SDK.so and AAXSDKWin.dll files! "Fixed key used for handling Audible AAX files", OFFSET(audible_fixed_key), AV_OPT_TYPE_BINARY, {.str="77214d4b196a87cd520045fd20a51d67"}, .flags = AV_OPT_FLAG_DECODING_PARAM }, { "decryption_key", "The media decryption key (hex)", OFFSET(decryption_key), AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_DECODING_PARAM }, { "enable_drefs", "Enable external track support.", OFFSET(enable_drefs), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, FLAGS }, { NULL }, }; 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, }; 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", .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, };
ff_mov_demuxer 解封装器支持的格式"mov,mp4,m4a,3gp,3g2,mj2",我们先看 AVoption 定义如下:
/** * {"auto", "auto", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_MFRA_AUTO}, 0, 0, FLAGS, "use_mfra_for" }, * 上面的数据对应到结构体中,如下。 */ typedef struct AVOption { const char *name; //> "auto", const char *help; //> "auto", int offset; //> 0, enum AVOptionType type; //> AV_OPT_TYPE_CONST, union { int64_t i64; double dbl; const char *str; AVRational q; } default_val; //> {.i64 = FF_MOV_FLAG_MFRA_AUTO}, double min; //> 0, double max; //> 0, int flags; //> FLAGS, const char *unit; //> "use_mfra_for" } AVOption;
程序在 AVInputFormat ff_mov_demuxer 定义时并没有见到给成员 mime_type 赋值;
而程序 av_probe_input_format2() 中在用,我们看看是如何使用的。
我们看下面的这个结构体,前面注解很清晰是 数据格式匹配。
/**
* This structure contains the data a format has to probe a file.
*/
typedef struct AVProbeData {
const char *filename; ///> 字符串指针,与硬件平台结构有关。
unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */
int buf_size; /**< Size of buf except extra allocated bytes */
const char *mime_type; /**< mime_type, when known. */
} AVProbeData;
接着看函数 av_probe_input_format2() 函数做了什么事情呢。
///> *fmt = av_probe_input_format2(&pd, 1, &score); AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max) { int score_ret; AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret); if (score_ret > *score_max) { *score_max = score_ret; return fmt; } else return NULL; } AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret) { AVProbeData lpd = *pd; AVInputFormat *fmt1 = NULL, *fmt; int score, score_max = 0; const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE]; ///> AVPROBE_PADDING_SIZE = 32 bytes enum nodat { NO_ID3, ID3_ALMOST_GREATER_PROBE, ID3_GREATER_PROBE, ID3_GREATER_MAX_PROBE, } nodat = NO_ID3; if (!lpd.buf) lpd.buf = (unsigned char *) zerobuffer; ///> 上面读取的第一帧数据是放到pb.buf中,直接解析读取到数据流内容。 if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) { ///> 2.1 匹配ID3V2特征头 10 bytes, int id3len = ff_id3v2_tag_len(lpd.buf); if (lpd.buf_size > id3len + 16) { if (lpd.buf_size < 2LL*id3len + 16) nodat = ID3_ALMOST_GREATER_PROBE; lpd.buf += id3len; lpd.buf_size -= id3len; } else if (id3len >= PROBE_BUF_MAX) { nodat = ID3_GREATER_MAX_PROBE; } else nodat = ID3_GREATER_PROBE; } fmt = NULL; while ((fmt1 = av_iformat_next(fmt1))) { ///> 2.2 遍历解复用器列表 if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2")) continue; score = 0; if (fmt1->read_probe) { score = fmt1->read_probe(&lpd); ///> 2.3 调用解封装器的 read_probe,通过解析数据流内容,获取视频的得分值 if (score) av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size); if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) { ///> ff_mov_demuxer 解封装器扩展 "mov,mp4,m4a,3gp,3g2,mj2", switch (nodat) { ///> filename 字符中有 mp4,就可以匹配上这个解封装器。 case NO_ID3: score = FFMAX(score, 1); break; case ID3_GREATER_PROBE: case ID3_ALMOST_GREATER_PROBE: score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1); break; case ID3_GREATER_MAX_PROBE: score = FFMAX(score, AVPROBE_SCORE_EXTENSION); break; } } } else if (fmt1->extensions) { if (av_match_ext(lpd.filename, fmt1->extensions)) ///> 直接匹配扩展明 score = AVPROBE_SCORE_EXTENSION; } if (av_match_name(lpd.mime_type, fmt1->mime_type)) { ///> 匹配 mime_type 类型 if (AVPROBE_SCORE_MIME > score) { av_log(NULL, AV_LOG_DEBUG, "Probing %s score:%d increased to %d due to MIME type\n", fmt1->name, score, AVPROBE_SCORE_MIME); score = AVPROBE_SCORE_MIME; } } if (score > score_max) { score_max = score; fmt = fmt1; } else if (score == score_max) fmt = NULL; } if (nodat == ID3_GREATER_PROBE) score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max); *score_ret = score_max; return fmt; } ///> 入口参数: ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC) //ID3v2_DEFAULT_MAGIC = "ID3" ///> 此函数在 id3v2.c 中,源码路径 libavformat/id3v2.c ,此文件中主要是解封装的源码内容。 int ff_id3v2_match(const uint8_t *buf, const char *magic) { return buf[0] == magic[0] && // I buf[1] == magic[1] && // D buf[2] == magic[2] && // 3 buf[3] != 0xff && buf[4] != 0xff && (buf[6] & 0x80) == 0 && // length << 21 (buf[7] & 0x80) == 0 && // length << 14 (buf[8] & 0x80) == 0 && // length << 7 (buf[9] & 0x80) == 0; // length } ///> 计算帧数据长度。 int ff_id3v2_tag_len(const uint8_t *buf) { int len = ((buf[6] & 0x7f) << 21) + ((buf[7] & 0x7f) << 14) + ((buf[8] & 0x7f) << 7) + (buf[9] & 0x7f) + ID3v2_HEADER_SIZE; if (buf[5] & 0x10) len += ID3v2_HEADER_SIZE; return len; } ///> 2.2 遍历解封装器 /** head of registered input format linked list */ static AVInputFormat *first_iformat = NULL; /** head of registered output format linked list */ static AVOutputFormat *first_oformat = NULL; static AVInputFormat **last_iformat = &first_iformat; ///> 输入 解封装格式链表的 first_iformat 节点, 全局遍历是 AVInputFormat格式的数组集合 static AVOutputFormat **last_oformat = &first_oformat; ///> 输出 封装格式链表 first_oformat 节点 AVInputFormat *av_iformat_next(const AVInputFormat *f) { if (f) return f->next; else return first_iformat; }
我们在看看 ID3V2.3 标签头结构格式定义,如下:
--------------------------------------------------------------------
名称 字节 说明
--------------------------------------------------------------------
Header 3 ID3V2.3 标识符"ID3"的Ascii码,否则认为没有ID3V2.3
Ver 1 版本号,=03
Revision 1 副版本号,=00
flag 1 标志字节,一般没意义,=00
Size 4 标签内容长度,高位在前,不包括标签头的10个字节
---------------------------------------------------------------------
说明:
- Size 字段的计算公式如下(从左至右):
size =字节1的值×&H200000+字节2的值×&H4000+字节3的值×&H80+字节4的值
- 如果所有标签帧的总长度<标签内容长度,则须用0填满。
至此我们基本清楚匹配 解封装器的过程,函数 ff_id3v2_match() 根据 ID3v2 的格式头进行匹配,
检测数据流是否为 ID3V2 流格式,扩展名匹配成功就确认此解封装器可以使用。
总结一下:
我们还以 ID3v2 封装流格式为例,前面根据标签头已经匹配到 ff_mov_demuxer 解封装器,接下来程序从 init_input() 返回到
avformat_open_input 函数继续执行,如下:
int avformat_open_input(){ if ((ret = init_input(s, filename, &tmp)) < 0) ///> 1. 通过 filename 初始化 AVFormatContext 的 input 相关参数 goto fail; ///> 处理ID3V2的首帧数据 s->probe_score = ret; ...... 省略相关的代码 /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */ if (s->pb) ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta); ///> 此程序调用 id3v2_parse() 函数 if (id3v2_extra_meta) { if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") || !strcmp(s->iformat->name, "tta")) { if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) ///> 处理帧标识为 APIC 的第二帧数据 goto fail; if ((ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0) ///> goto fail; } else av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n"); } ff_id3v2_free_extra_meta(&id3v2_extra_meta); if ((ret = avformat_queue_attached_pictures(s)) < 0) goto fail; update_stream_avctx(s); for (i = 0; i < s->nb_streams; i++) s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id; } ///> 到此 avformat_open_input() 打开输入流函数就执行结束,程序返回至 read_thread() 线程中。 ///> 建立新流 int ff_id3v2_parse_apic(AVFormatContext *s, ID3v2ExtraMeta **extra_meta) { ID3v2ExtraMeta *cur; for (cur = *extra_meta; cur; cur = cur->next) { ID3v2ExtraMetaAPIC *apic; AVStream *st; if (strcmp(cur->tag, "APIC")) continue; apic = cur->data; if (!(st = avformat_new_stream(s, NULL))) return AVERROR(ENOMEM); st->disposition |= AV_DISPOSITION_ATTACHED_PIC; st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; st->codecpar->codec_id = apic->id; if (AV_RB64(apic->buf->data) == 0x89504e470d0a1a0a) st->codecpar->codec_id = AV_CODEC_ID_PNG; if (apic->description[0]) av_dict_set(&st->metadata, "title", apic->description, 0); av_dict_set(&st->metadata, "comment", apic->type, 0); av_init_packet(&st->attached_pic); st->attached_pic.buf = apic->buf; st->attached_pic.data = apic->buf->data; st->attached_pic.size = apic->buf->size - AV_INPUT_BUFFER_PADDING_SIZE; st->attached_pic.stream_index = st->index; st->attached_pic.flags |= AV_PKT_FLAG_KEY; apic->buf = NULL; } return 0; } ///> int ff_id3v2_parse_chapters(AVFormatContext *s, ID3v2ExtraMeta **extra_meta) { int ret = 0; ID3v2ExtraMeta *cur; AVRational time_base = {1, 1000}; ID3v2ExtraMetaCHAP **chapters = NULL; int num_chapters = 0; int i; // since extra_meta is a linked list where elements are prepended, // we need to reverse the order of chapters for (cur = *extra_meta; cur; cur = cur->next) { ID3v2ExtraMetaCHAP *chap; if (strcmp(cur->tag, "CHAP")) continue; chap = cur->data; if ((ret = av_dynarray_add_nofree(&chapters, &num_chapters, chap)) < 0) goto end; } for (i = 0; i < (num_chapters / 2); i++) { ID3v2ExtraMetaCHAP *right; int right_index; right_index = (num_chapters - 1) - i; right = chapters[right_index]; chapters[right_index] = chapters[i]; chapters[i] = right; } for (i = 0; i < num_chapters; i++) { ID3v2ExtraMetaCHAP *chap; AVChapter *chapter; chap = chapters[i]; chapter = avpriv_new_chapter(s, i, time_base, chap->start, chap->end, chap->element_id); if (!chapter) continue; if ((ret = av_dict_copy(&chapter->metadata, chap->meta, 0)) < 0) goto end; } end: av_freep(&chapters); return ret; }
我们接下来在看 ID3v2 标签帧的结构,如下:
表4:标签帧的结构 ---------------------------------------------------------- 名称 字节 说明 ---------------------------------------------------------- FrameID 4 帧标识符的Ascii码,常用标识符的意义见表5 Size 4 帧内容及编码方式的合计长度,高位在前 Flags 2 标志,只使用了6位,详见表6,一般均=0 encode 4 帧内容所用的编码方式。许多帧没有此项 帧内容 至少 1 个字节 ---------------------------------------------------------- 说明: ①Size的计算同上。 ②标签帧之间没有特殊的分隔符,要得到一个完整的标签帧内容必须先从帧头中得到帧内容长度。 ③encode 有 4 个可能值: 0:表示帧内容字符用 ISO-8859-1 编码; 1:表示帧内容字符用 UTF-16LE 编码; 2:表示帧内容字符用 UTF-16BE 编码; 3:表示帧内容字符用 UTF-8 编码(仅ID3V2.4才支持) 但经常看到的是"eng"这样的字符形式,它表示帧内容所使用的自然语言为英语。也许 D3V2 标签帧进化到现在,encode 已经用“自然语言”取代了“编码方式”。 ⑤帧内容均为字符串,常以 00 开头。 表5:标签帧标识符的意义 --------------------------------------- 名称 意义 --------------------------------------- AENC: 音频加密技术 APIC: 附加描述 COMM: 注释,相当于ID3v1的Comment COMR: 广告 ENCR: 加密方法注册 ETC0: 事件时间编码 GEOB: 常规压缩对象 GRID: 组识别注册 IPLS: 复杂类别列表 MCDI: 音乐CD标识符 MLLT: MPEG位置查找表格 OWNE: 所有权 PRIV: 私有 PCNT: 播放计数 POPM: 普通仪表 POSS: 位置同步 RBUF: 推荐缓冲区大小 RVAD: 音量调节器 RVRB: 混响 SYLT: 同步歌词或文本 SYTC: 同步节拍编码 TALB: 专辑,相当于ID3v1的Album TBPM: 每分钟节拍数 TCOM: 作曲家 TCON: 流派(风格),见表2 TCOP: 版权 TDAT: 日期 TDLY: 播放列表返录 TENC: 编码 TEXT: 歌词作者 TFLT: 文件类型 TIME: 时间 TIT1: 内容组描述 TIT2: 标题,相当于ID3v1的Title TIT3: 副标题 TKEY: 最初关键字 TLAN: 语言 TLEN: 长度 TMED: 媒体类型 TOAL: 原唱片集 TOFN: 原文件名 TOLY: 原歌词作者 TOPE: 原艺术家 TORY: 最初发行年份 TOWM: 文件所有者(许可证者) TPE1: 艺术家相当于ID3v1的Artist TPE2: 乐队 TPE3: 指挥者 TPE4: 翻译(记录员、修改员) TPOS: 作品集部分 TPUB: 发行人 TRCK: 音轨(曲号),相当于ID3v1的Track TRDA: 录制日期 TRSN: Intenet电台名称 TRSO: Intenet电台所有者 TSIZ: 大小 TSRC: ISRC(国际的标准记录代码) TSSE: 编码使用的软件(硬件设置) TYER: 年代,相当于ID3v1的Year TXXX: 年度 UFID: 唯一的文件标识符 USER: 使用条款 USLT: 歌词 WCOM: 广告信息 WCOP: 版权信息 WOAF: 官方音频文件网页 WOAR: 官方艺术家网页 WOAS: 官方音频原始资料网页 WORS: 官方互联网无线配置首页 WPAY: 付款 WPUB: 出版商官方网页 WXXX: 用户定义的URL链接 --------------------------------------- 说明: ①帧内容是数字的,都用 Ascii 字符表示。 ②有的 TCON(风格、流派)的帧内容是直接用字符串表示的,如“genre”,而有的则是用编号表示的,如“28 31 32 29”就是用字符串“(12)”表示 12 号风格,我们在解析的时候要注意。 ③TRCK(音轨)的帧内容格式是:N/M。其中,分母表示专辑中共有 M 首歌曲,分子表示专辑中的第 N 首曲。 表6:标签帧中Flags标志的意义 ---------------------------------------------------- 位址 意义 ---------------------------------------------------- 0 标签保护标志,如设置表示此帧作废 1 文件保护标志,如设置表示此帧作废 2 只读标志,如设置表示此帧不能修改 3 压缩标志,如设置表示1个字节存放2个BCD码表示数字 4 加密标志 5 组标志,如设置表示此帧和其它的某帧是一组 ----------------------------------------------------
至此总结以下:
下一篇文章 《ijkplayer 代码走读之 h264 解封装器应用详解》,通过实例再次阐述 IJKPLAYER 的解协议、解封装的
实现逻辑。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。