网站首页 编程语言 正文
avformat_open_input()用于打开输入媒体流与读取头部信息,包括本地文件、网络流、自定义缓冲区。关键流程:打开avio、探测输入流的封装格式。对应的释放方法为avformat_close_input()。
1、打开输入媒体流
avformat_open_input方法位于libavformat/utils.c,流程包括分配AVFormatContext、设置options、初始化输入流、拷贝白名单与黑名单协议、读取ID3V2参数。具体方法如下:
int avformat_open_input(AVFormatContext **ps, const char *filename,
ff_const59 AVInputFormat *fmt, AVDictionary **options)
{
AVFormatContext *s = *ps;
int i, ret = 0;
AVDictionary *tmp = NULL;
ID3v2ExtraMeta *id3v2_extra_meta = NULL;
// 分配AVFormatContext
if (!s && !(s = avformat_alloc_context()))
return AVERROR(ENOMEM);
if (!s->av_class) {
av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated\n");
return AVERROR(EINVAL);
}
if (fmt)
s->iformat = fmt;
// 拷贝options
if (options)
av_dict_copy(&tmp, *options, 0);
// 如果已经设置AVIOContext,那么flag设为自定义IO
if (s->pb) // must be before any goto fail
s->flags |= AVFMT_FLAG_CUSTOM_IO;
// 设置options
if ((ret = av_opt_set_dict(s, &tmp)) < 0)
goto fail;
if (!(s->url = av_strdup(filename ? filename : ""))) {
ret = AVERROR(ENOMEM);
goto fail;
}
// 初始化输入媒体流
if ((ret = init_input(s, filename, &tmp)) < 0)
goto fail;
s->probe_score = ret;
// 拷贝白名单协议
if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) {
s->protocol_whitelist = av_strdup(s->pb->protocol_whitelist);
if (!s->protocol_whitelist) {
ret = AVERROR(ENOMEM);
goto fail;
}
}
// 拷贝黑名单协议
if (!s->protocol_blacklist && s->pb && s->pb->protocol_blacklist) {
s->protocol_blacklist = av_strdup(s->pb->protocol_blacklist);
if (!s->protocol_blacklist) {
ret = AVERROR(ENOMEM);
goto fail;
}
}
if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) {
av_log(s, AV_LOG_ERROR, "Format not on whitelist \'%s\'\n", s->format_whitelist);
ret = AVERROR(EINVAL);
goto fail;
}
avio_skip(s->pb, s->skip_initial_bytes);
if (s->iformat->flags & AVFMT_NEEDNUMBER) {
if (!av_filename_number_test(filename)) {
ret = AVERROR(EINVAL);
goto fail;
}
}
s->duration = s->start_time = AV_NOPTS_VALUE;
/* Allocate private data. */
if (s->iformat->priv_data_size > 0) {
if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {
ret = AVERROR(ENOMEM);
goto fail;
}
if (s->iformat->priv_class) {
*(const AVClass **) s->priv_data = s->iformat->priv_class;
av_opt_set_defaults(s->priv_data);
if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
goto fail;
}
}
if (s->pb)
ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);
#if FF_API_DEMUXER_OPEN
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
#else
if (s->iformat->read_header)
#endif
if ((ret = s->iformat->read_header(s)) < 0)
goto fail;
if (!s->metadata) {
s->metadata = s->internal->id3v2_meta;
s->internal->id3v2_meta = NULL;
} else if (s->internal->id3v2_meta) {
av_log(s, AV_LOG_WARNING, "Discarding ID3 tags because more suitable tags were found.\n");
av_dict_free(&s->internal->id3v2_meta);
}
if (id3v2_extra_meta) {
if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") ||
!strcmp(s->iformat->name, "tta") || !strcmp(s->iformat->name, "wav")) {
if ((ret = ff_id3v2_parse_apic(s, id3v2_extra_meta)) < 0)
goto close;
if ((ret = ff_id3v2_parse_chapters(s, id3v2_extra_meta)) < 0)
goto close;
if ((ret = ff_id3v2_parse_priv(s, id3v2_extra_meta)) < 0)
goto close;
} 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 close;
#if FF_API_DEMUXER_OPEN
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset)
#else
if (s->pb && !s->internal->data_offset)
#endif
s->internal->data_offset = avio_tell(s->pb);
s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
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;
if (options) {
av_dict_free(options);
*options = tmp;
}
*ps = s;
return 0;
close:
if (s->iformat->read_close)
s->iformat->read_close(s);
fail:
ff_id3v2_free_extra_meta(&id3v2_extra_meta);
av_dict_free(&tmp);
if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))
avio_closep(&s->pb);
avformat_free_context(s);
*ps = NULL;
return ret;
}
2、初始化输入媒体流
核心方法是init_input(),用于打开输入媒体流、探测封装格式,代码如下:
static int init_input(AVFormatContext *s, const char *filename,
AVDictionary **options)
{
int ret;
AVProbeData pd = { filename, NULL, 0 };
int score = AVPROBE_SCORE_RETRY;
// 探测输入缓冲区
if (s->pb) {
s->flags |= AVFMT_FLAG_CUSTOM_IO;
if (!s->iformat)
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
else if (s->iformat->flags & AVFMT_NOFILE)
av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense.\n");
return 0;
}
// 探测输入格式
if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
return score;
// 打开avio
if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
return ret;
if (s->iformat)
return 0;
// 探测输入缓冲区
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
}
3、探测输入格式
av_probe_input_format2()位于libavformat/format.c,内部调用av_probe_input_format3(),而av_probe_input_format()会内部调用av_probe_input_format2()。具体调用关系如下:
ff_const59 AVInputFormat *av_probe_input_format2(ff_const59 AVProbeData *pd, int is_opened, int *score_max)
{
int score_ret;
ff_const59 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;
}
ff_const59 AVInputFormat *av_probe_input_format(ff_const59 AVProbeData *pd, int is_opened)
{
int score = 0;
return av_probe_input_format2(pd, is_opened, &score);
}
3.1 探测过程
我们主要看下av_probe_input_format3方法,探测过程分为三步:read_probe、av_match_ext、av_match_name。每一步匹配结果有个score得分,取最高分数的作为format。具体探测匹配过程如下:
ff_const59 AVInputFormat *av_probe_input_format3(ff_const59 AVProbeData *pd, int is_opened,
int *score_ret)
{
AVProbeData lpd = *pd;
const AVInputFormat *fmt1 = NULL;
ff_const59 AVInputFormat *fmt = NULL;
int score, score_max = 0;
void *i = 0;
const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];
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;
// 判断是否匹配到ID3V2,存在于mp3、aac、wav、tta
if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {
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;
}
while ((fmt1 = av_demuxer_iterate(&i))) {
if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
continue;
score = 0;
if (fmt1->read_probe) {
// 匹配格式的特定标识符,比如flv格式头部为"FLV"
score = fmt1->read_probe(&lpd);
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)) {
switch (nodat) {
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;
}
// 匹配mimetype
if (av_match_name(lpd.mime_type, fmt1->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) { // 取最高得分作为format
score_max = score;
fmt = (AVInputFormat*)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;
}
3.2 匹配分数
每步匹配有不同的分数,最高分数为100,retry为25,extension为50,mimetype为75.具体的AVProbeData结构体与得分规则如下:
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;
#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)
#define AVPROBE_SCORE_STREAM_RETRY (AVPROBE_SCORE_MAX/4-1)
#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
3.3 探测mp4格式
mp4/mov/3gp格式都在libavformat/mov.c中,read_probe指向mov_probe方法。首先探测ftyp、moov、mdat等关键box的tag,然后是hdlr、mhlr、MPEG的tag,具体的探测方法如下:
static int mov_probe(const AVProbeData *p)
{
int64_t offset = 0;
uint32_t tag;
int score = 0;
int moov_offset = -1;
for (;;) {
tag = AV_RL32(p->buf + offset + 4);
switch(tag) {
case MKTAG('m','o','o','v'):
moov_offset = offset + 4;
case MKTAG('m','d','a','t'):
case MKTAG('p','n','o','t'):
case MKTAG('u','d','t','a'):
case MKTAG('f','t','y','p'):
if (tag == MKTAG('f','t','y','p') &&
( AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ')
|| AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' ')
)) {
score = FFMAX(score, 5);
} else {
score = AVPROBE_SCORE_MAX;
}
break;
case MKTAG('e','d','i','w'): /* xdcam files have reverted first tags */
case MKTAG('w','i','d','e'):
case MKTAG('f','r','e','e'):
case MKTAG('j','u','n','k'):
case MKTAG('p','i','c','t'):
score = FFMAX(score, AVPROBE_SCORE_MAX - 5);
break;
case MKTAG(0x82,0x82,0x7f,0x7d):
case MKTAG('s','k','i','p'):
case MKTAG('u','u','i','d'):
case MKTAG('p','r','f','l'):
score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
break;
}
if (size > INT64_MAX - offset)
break;
offset += size;
}
if (score > AVPROBE_SCORE_MAX - 50 && moov_offset != -1) {
offset = moov_offset;
while (offset < (p->buf_size - 16)) {
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')) {
return 5;
} else {
/* Keep looking */
offset += 2;
}
}
}
return score;
}
4、打开avio
avio_open2方法会打开ffurl_open_whitelist()和fifo_fdopen()。具体调用链如下图所示:
4.1 open_whiltelist
其中位于avio.c的ffurl_open_whitelist()进行两步操作:ffurl_alloc和ffurl_connect,具体代码如下:
int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options,
const char *whitelist, const char* blacklist,
URLContext *parent)
{
int ret = ffurl_alloc(puc, filename, flags, int_cb);
if (ret < 0)
return ret;
......
ret = ffurl_connect(*puc, options);
if (!ret)
return 0;
fail:
ffurl_closep(puc);
return ret;
}
ffurl_alloc主要实现2个功能:查找协议和分配协议,查找过程是遍历协议数组根据scheme进行匹配。代码如下:
int ffurl_alloc(URLContext **puc, const char *filename, int flags,
const AVIOInterruptCB *int_cb)
{
const URLProtocol *p = NULL;
p = url_find_protocol(filename);
if (p)
return url_alloc_for_protocol(puc, p, filename, flags, int_cb);
*puc = NULL;
return AVERROR_PROTOCOL_NOT_FOUND;
}
ffurl_connect主要是判断用url_oepn2还是url_open来打开:
int ffurl_connect(URLContext *uc, AVDictionary **options)
{
......
int err =
uc->prot->url_open2 ? uc->prot->url_open2(uc, uc->filename, uc->flags, options) :
uc->prot->url_open(uc, uc->filename, uc->flags);
......
return 0;
}
4.2 打开fifo
调用aviobuf.c的fifo_fdopen方法打开fifo,主要是分配buffer缓冲区、分配AVIOContext:
int ffio_fdopen(AVIOContext **s, URLContext *h)
{
// malloc buffer
buffer = av_malloc(buffer_size);
if (!buffer)
return AVERROR(ENOMEM);
// malloc context
*s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h,
(int (*)(void *, uint8_t *, int)) ffurl_read,
(int (*)(void *, uint8_t *, int)) ffurl_write,
(int64_t (*)(void *, int64_t, int))ffurl_seek);
fail:
av_freep(&buffer);
return AVERROR(ENOMEM);
}
原文链接:https://blog.csdn.net/tonychan129/article/details/127032996
相关推荐
- 2022-06-18 android实现在图标上显示数字_Android
- 2023-06-18 C#中关于double.ToString()的用法_C#教程
- 2022-07-29 python中open函数对文件处理的使用教程_python
- 2022-05-04 详解Tomcat中查看JVM内存使用情况_Tomcat
- 2022-09-05 Spark repartitionAndSortWithinPartitions
- 2023-07-08 qt修改默认构建路径
- 2022-05-02 在kali上安装AWVS的图文教程_相关技巧
- 2022-06-02 Nginx虚拟主机的配置步骤过程全解_nginx
- 最近更新
-
- window11 系统安装 yarn
- 超详细win安装深度学习环境2025年最新版(
- Linux 中运行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存储小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基础操作-- 运算符,流程控制 Flo
- 1. Int 和Integer 的区别,Jav
- spring @retryable不生效的一种
- Spring Security之认证信息的处理
- Spring Security之认证过滤器
- Spring Security概述快速入门
- Spring Security之配置体系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置权
- redisson分布式锁中waittime的设
- maven:解决release错误:Artif
- restTemplate使用总结
- Spring Security之安全异常处理
- MybatisPlus优雅实现加密?
- Spring ioc容器与Bean的生命周期。
- 【探索SpringCloud】服务发现-Nac
- Spring Security之基于HttpR
- Redis 底层数据结构-简单动态字符串(SD
- arthas操作spring被代理目标对象命令
- Spring中的单例模式应用详解
- 聊聊消息队列,发送消息的4种方式
- bootspring第三方资源配置管理
- GIT同步修改后的远程分支