目录
前言
avio_reading是关于对音视频流内存读取操作的应用实例,将文件中获取的数据流映射到内存再进行读取解析。(刚开始学习这块,主要作为学习记录,可能会存在很多问题)
一、源码
官方示例源码
如果使用c++需要注意一定要对头文件进行 extern"C"
#include <stdio.h>
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavutil/file.h"
struct buffer_data {
uint8_t* ptr;
size_t size; ///< size left in the buffer
};
static int read_packet(void* opaque, uint8_t* buf, int buf_size)
{
struct buffer_data* bd = (struct buffer_data*)opaque;
buf_size = FFMIN(buf_size, bd->size);
if (!buf_size)
return AVERROR_EOF;
printf("ptr:%p size:%zu\n", bd->ptr, bd->size);
/* copy internal buffer data to buf */
memcpy(buf, bd->ptr, buf_size);
bd->ptr += buf_size;
bd->size -= buf_size;
return buf_size;
}
int main()
{
AVFormatContext* fmt_ctx = NULL; //IO上下文格式
AVIOContext* avio_ctx = NULL; // 字节流上下文
uint8_t* buffer = NULL, * avio_ctx_buffer = NULL;
size_t buffer_size, avio_ctx_buffer_size = 4096;
char* input_filename = NULL;
int ret = 0;
struct buffer_data bd = { 0 };
input_filename = "test.264";
/* slurp file content into buffer 读取所有数据*/
ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL);
if (ret < 0)
{
goto end;
}
/* fill opaque structure used by the AVIOContext read callback */
bd.ptr = buffer; //数据内容指针
bd.size = buffer_size; //数据字节大小
if (!(fmt_ctx = avformat_alloc_context())) {
ret = AVERROR(ENOMEM);
goto end;
}
avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
if (!avio_ctx_buffer) {
ret = AVERROR(ENOMEM);
goto end;
}
avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size,
0, &bd, &read_packet, NULL, NULL);
if (!avio_ctx) {
ret = AVERROR(ENOMEM);
goto end;
}
fmt_ctx->pb = avio_ctx;
ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL); // init_input——>av_probe_input_buffer2——>avio_read——>read_packet_wrapper 中调用read_packet
if (ret < 0) {
fprintf(stderr, "Could not open input\n");
goto end;
}
//获取数据包中的流信息
ret = avformat_find_stream_info(fmt_ctx, NULL); //read_frame_internal——>ff_read_packet 中调用read_packet
if (ret < 0) {
fprintf(stderr, "Could not find stream information\n");
goto end;
}
//抛出数据流信息
av_dump_format(fmt_ctx, 0, input_filename, 0);
end:
avformat_close_input(&fmt_ctx);
/* note: the internal buffer could have changed, and be != avio_ctx_buffer */
if (avio_ctx)
av_freep(&avio_ctx->buffer);
avio_context_free(&avio_ctx);
av_file_unmap(buffer, buffer_size);
if (ret < 0) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return 1;
}
return 0;
}
二、分析
1、av_file_map函数
mmap映射
mmap 是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。
进程就可以采用指针的方式读写操作这一段内存,对文件的操作而不必再调用 read、write 等系统调用函数。
具有如下的特点:
1、mmap 提供的内存访问接口是内存地址连续的;
2、mmap 提供的内存空间是虚拟空间(虚拟内存),而不是物理空间(物理内存),因此可以分配远远大于物理内存大小的虚拟空间(例如 8G 内存主机分配 100G 的 mmap 内存空间);
3、mmap 负责映射文件逻辑上一段连续的数据(物理上可以不连续存储)映射为连续内存,而这里的文件可以是磁盘文件、驱动假造出的文件(例如 DMA 技术)以及设备;
4、mmap 由操作系统负责管理,对同一个文件地址的映射将被所有线程共享,操作系统确保线程安全以及线程可见性;
原文链接:https://blog.csdn.net/agonie201218/article/details/123791047
//根据文件名称 读取所有的数据到bufptr,并返回字节 同时可添加日志等级与日志的上下文
int av_file_map(const char *filename, uint8_t **bufptr, size_t *size, int log_offset, void *log_ctx)
{
//日志
FileLogContext file_log_ctx = { &file_log_ctx_class, log_offset, log_ctx };
int err, fd = avpriv_open(filename, O_RDONLY); //只读的方式打开文件 (fopen)
struct stat st; //系统中的文件属性的结构 (结构体内容在后面)
av_unused void *ptr;
off_t off_size;
char errbuf[128];
*bufptr = NULL;
*size = 0;
if (fd < 0) {
err = AVERROR(errno);
av_strerror(err, errbuf, sizeof(errbuf));
av_log(&file_log_ctx, AV_LOG_ERROR, "Cannot read file '%s': %s\n", filename, errbuf);
return err;
}
//通过文件描述符 获取文件状态
if (fstat(fd, &st) < 0) {
err = AVERROR(errno);
av_strerror(err, errbuf, sizeof(errbuf));
av_log(&file_log_ctx, AV_LOG_ERROR, "Error occurred in fstat(): %s\n", errbuf);
close(fd);
return err;
}
off_size = st.st_size; //文件的字节数
//SIZE_MAX 系统最大的字节数(和系统相关)
if (off_size > SIZE_MAX) {
av_log(&file_log_ctx, AV_LOG_ERROR,
"File size for file '%s' is too big\n", filename);
close(fd);
return AVERROR(EINVAL);
}
*size = off_size;
if (!*size) {
*bufptr = NULL;
goto out;
}
//内存映射 linux
#if HAVE_MMAP
ptr = mmap(NULL, *size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
if (ptr == MAP_FAILED) {
err = AVERROR(errno);
av_strerror(err, errbuf, sizeof(errbuf));
av_log(&file_log_ctx, AV_LOG_ERROR, "Error occurred in mmap(): %s\n", errbuf);
close(fd);
*size = 0;
return err;
}
*bufptr = ptr;
//windows 系统的内存映射
#elif HAVE_MAPVIEWOFFILE
{
HANDLE mh, fh = (HANDLE)_get_osfhandle(fd);
//创建映射句柄
mh = CreateFileMapping(fh, NULL, PAGE_READONLY, 0, 0, NULL);
if (!mh) {
av_log(&file_log_ctx, AV_LOG_ERROR, "Error occurred in CreateFileMapping()\n");
close(fd);
*size = 0;
return -1;
}
//获取映射地址
ptr = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, *size);
CloseHandle(mh);
if (!ptr) {
av_log(&file_log_ctx, AV_LOG_ERROR, "Error occurred in MapViewOfFile()\n");
close(fd);
*size = 0;
return -1;
}
*bufptr = ptr;
}
#else
//通过read函数获取数据
*bufptr = av_malloc(*size);
if (!*bufptr) {
av_log(&file_log_ctx, AV_LOG_ERROR, "Memory allocation error occurred\n");
close(fd);
*size = 0;
return AVERROR(ENOMEM);
}
read(fd, *bufptr, *size);
#endif
out:
close(fd);
return 0;
}
/*
struct stat
{
dev_t st_dev; /* ID of device containing file -文件所在设备的ID*/
ino_t st_ino; /* inode number -inode节点号*/
mode_t st_mode; /* protection -保护模式?*/
nlink_t st_nlink; /* number of hard links -链向此文件的连接数(硬连接)*/
uid_t st_uid; /* user ID of owner -user id*/
gid_t st_gid; /* group ID of owner - group id*/
dev_t st_rdev; /* device ID (if special file) -设备号,针对设备文件*/
off_t st_size; /* total size, in bytes -文件大小,字节为单位*/
blksize_t st_blksize; /* blocksize for filesystem I/O -系统块的大小*/
blkcnt_t st_blocks; /* number of blocks allocated -文件所占块数*/
time_t st_atime; /* time of last access -最近存取时间*/
time_t st_mtime; /* time of last modification -最近修改时间*/
time_t st_ctime; /* time of last status change - */
};
*/
2、avformat_alloc_context函数
分配 媒体格式上下文空间
函数原型
AVFormatContext *avformat_alloc_context(void)
{
// I/O 上下文内容
AVFormatContext *ic;
//数据流相关
AVFormatInternal *internal;
ic = av_malloc(sizeof(AVFormatContext));
if (!ic) return ic;
internal = av_mallocz(sizeof(*internal));
if (!internal) {
av_free(ic);
return NULL;
}
//获取压缩数据包(音频或视频)临时的
internal->pkt = av_packet_alloc();
/*
AVPacket *av_packet_alloc(void)
{
AVPacket *pkt = av_mallocz(sizeof(AVPacket));
if (!pkt)
return pkt;
get_packet_defaults(pkt);
return pkt;
}
*/
//数据包
internal->parse_pkt = av_packet_alloc();
if (!internal->pkt || !internal->parse_pkt) {
av_packet_free(&internal->pkt);
av_packet_free(&internal->parse_pkt);
av_free(internal);
av_free(ic);
return NULL;
}
//设置默认参数
avformat_get_context_defaults(ic);
ic->internal = internal;
ic->internal->offset = AV_NOPTS_VALUE;
ic->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
ic->internal->shortest_end = AV_NOPTS_VALUE;
return ic;
3、avio_alloc_context函数
分配I/O上下文空间,必须使用avio_context_free().进行清理
函数原型
@param buffer Memory block for input/output operations via AVIOContext.
* @param write_flag Set to 1 if the buffer should be writable, 0 otherwise.
* @param opaque An opaque pointer to user-specific data.
* @param read_packet A function for refilling the buffer, may be NULL.
* @param write_packet A function for writing the buffer contents, may be NULL.
* @param seek A function for seeking to specified byte position, may be NULL.
* @return Allocated AVIOContext or NULL on failure.
//需要注意read_packet 、write_packet 这两个回调函数 函数的调用在avformat_open_input和avformat_find_stream_info中
AVIOContext *avio_alloc_context(
unsigned char *buffer,
int buffer_size,
int write_flag,
void *opaque,
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
AVIOContext *s = av_malloc(sizeof(AVIOContext));
if (!s)
return NULL;
//参数初始化 将参数赋值到 AVIOContext
ffio_init_context(s, buffer, buffer_size, write_flag, opaque,
read_packet, write_packet, seek);
/*
int ffio_init_context(AVIOContext *s,
unsigned char *buffer,
int buffer_size,
int write_flag,
void *opaque,
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
memset(s, 0, sizeof(AVIOContext));
s->buffer = buffer;
s->orig_buffer_size =
s->buffer_size = buffer_size;
s->buf_ptr = buffer;
s->buf_ptr_max = buffer;
s->opaque = opaque;
s->direct = 0;
url_resetbuf(s, write_flag ? AVIO_FLAG_WRITE : AVIO_FLAG_READ);
s->write_packet = write_packet;
s->read_packet = read_packet;
s->seek = seek;
s->pos = 0;
s->eof_reached = 0;
s->error = 0;
s->seekable = seek ? AVIO_SEEKABLE_NORMAL : 0;
s->min_packet_size = 0;
s->max_packet_size = 0;
s->update_checksum = NULL;
s->short_seek_threshold = SHORT_SEEK_THRESHOLD;
if (!read_packet && !write_flag) {
s->pos = buffer_size;
s->buf_end = s->buffer + buffer_size;
}
s->read_pause = NULL;
s->read_seek = NULL;
s->write_data_type = NULL;
s->ignore_boundary_point = 0;
s->current_type = AVIO_DATA_MARKER_UNKNOWN;
s->last_time = AV_NOPTS_VALUE;
s->short_seek_get = NULL;
s->written = 0;
return 0;
}
*/
return s;
}
4、avformat_open_input函数
Open an input stream and read the header. The codecs are not opened.
The stream must be closed with avformat_close_input().
函数原型
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options)
{
AVFormatContext *s = *ps;
int i, ret = 0;
AVDictionary *tmp = NULL;
ID3v2ExtraMeta *id3v2_extra_meta = NULL;
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 by avformat_alloc_context() and is not NULL either\n");
return AVERROR(EINVAL);
}
//判断是否有外部格式设置
if (fmt)
s->iformat = fmt;
//其他设置
if (options)
av_dict_copy(&tmp, *options, 0);
//I/O上下文
if (s->pb) // must be before any goto fail
s->flags |= AVFMT_FLAG_CUSTOM_IO; //官方自定义的I/O上下文
//参数设置不为空时生效
if ((ret = av_opt_set_dict(s, &tmp)) < 0)
goto fail;
//av_strdup 复制字符串
if (!(s->url = av_strdup(filename ? filename : ""))) {
ret = AVERROR(ENOMEM);
goto fail;
}
#if FF_API_FORMAT_FILENAME
FF_DISABLE_DEPRECATION_WARNINGS
av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
FF_ENABLE_DEPRECATION_WARNINGS
#endif
//init_input 打开文件 并进行格式探测
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;
}
}
//av_match_list :Check if a name is in a list 在列表中查找字符产
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);
/* Check filename in case an image number is expected. */
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;
}
}
/* 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);
#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;
}
/* Open input file and probe the format if necessary. */
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 and "
"will be ignored with AVFMT_NOFILE format.\n");
return 0;
}
if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score)))) //Guess the file format 探测文件格式
return score;
//io_open 回调函数 打开一个新的数据流
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);
}
5、avformat_find_stream_info函数
函数原型