libavformat 是 FFmpeg 框架中的核心库之一,专门负责处理各种媒体容器格式的解复用(demuxing)和复用(muxing)操作。它为音视频数据的读取和写入提供了统一的API接口,是构建多媒体应用的基础组件。
1、libavformat概述
1.1、基本概念
libavformat(简称lavf)是FFmpeg的I/O和复用/解复用库,主要功能包括:
- 解复用(Demuxing):将媒体文件分解为音频、视频、字幕等独立流
- 复用(Muxing):将多个媒体流合并为指定容器格式的文件
- I/O模块:支持文件、TCP、HTTP等多种数据源访问协议
1.2、容器格式基础
容器格式(Container Format),也称为封装格式,是一种将音频、视频、字幕等多媒体数据以及元数据打包成单一文件的标准格式。
常见容器格式:
| 格式 | 扩展名 | 特点 | 主要用途 |
|---|---|---|---|
| MP4 | .mp4 | 广泛支持,适合流媒体 | 网络视频、移动设备 |
| MKV | .mkv | 开放标准,功能丰富 | 高清视频存储 |
| AVI | .avi | 历史悠久,兼容性好 | 传统视频编辑 |
| MOV | .mov | Apple标准,编辑友好 | 专业视频制作 |
| WebM | .webm | 开源,Web优化 | 网页视频播放 |
| TS | .ts | 传输流,适合广播 | 数字电视、直播 |
2、容器格式结构分析
2.1、通用结构模型
容器文件结构:
+------------------+
| 文件头/Header | 格式标识、基本信息
+------------------+
| 元数据/Metadata | 时长、码率、编码信息等
+------------------+
| 索引/Index | 关键帧位置、时间戳映射
+------------------+
| 数据流/Streams | 实际的音视频数据
+------------------+
| 尾部/Footer | 附加信息(可选)
+------------------+
2.2、具体格式结构
2.2.1、MP4结构(ISO Base Media File Format)
MP4文件基于Box结构:
+--------+------------------+
| ftyp | 文件类型标识 |
+--------+------------------+
| moov | 元数据容器 |
| - mvhd| 电影头部 |
| - trak| 轨道信息 |
| - udta| 用户数据 |
+--------+------------------+
| mdat | 媒体数据 |
+--------+------------------+
| free | 空闲空间(可选) |
+--------+------------------+
2.2.2、MKV结构(Matroska)
MKV使用EBML结构:
+--------+------------------+
| EBML | EBML头部 |
+--------+------------------+
| Segment| 主段 |
| - Seek| 定位信息 |
| - Info| 段信息 |
| - Tracks|轨道信息 |
| - Clusters|数据簇 |
| - Cues| 索引点 |
+--------+------------------+
2.2.3、AVI结构
AVI使用RIFF结构:
+--------+------------------+
| RIFF | RIFF头部 |
| - AVI | AVI列表 |
| - hdrl| 头部列表 |
| - movi| 数据列表 |
| - idx1| 索引(可选) |
+--------+------------------+
3、libavformat核心API
3.1、主要数据结构
3.1.1、AVFormatContext
typedef struct AVFormatContext {
const AVClass *av_class; // 日志和选项
struct AVInputFormat *iformat;// 输入格式
struct AVOutputFormat *oformat;// 输出格式
void *priv_data; // 私有数据
AVIOContext *pb; // I/O上下文
int ctx_flags; // 上下文标志
unsignedint nb_streams; // 流数量
AVStream **streams; // 流数组
char *filename; // 文件名
int64_t start_time; // 起始时间
int64_t duration; // 时长
int64_t bit_rate; // 比特率
AVDictionary *metadata; // 元数据
// ... 其他字段
} AVFormatContext;
3.1.2、AVStream
typedef struct AVStream {
int index; // 流索引
int id; // 流ID
AVCodecContext *codec; // 编解码器上下文
void *priv_data; // 私有数据
AVRational time_base; // 时间基
int64_t start_time; // 起始时间
int64_t duration; // 时长
int64_t nb_frames; // 帧数量
AVDisposition disposition; // 流属性
AVDictionary *metadata; // 流元数据
AVRational avg_frame_rate; // 平均帧率
AVRational r_frame_rate; // 实际帧率
// ... 其他字段
} AVStream;
3.2、解复用(Demuxing)流程
3.2.1、基本步骤
// 1. 注册所有格式和编解码器
av_register_all();
// 2. 打开输入文件
AVFormatContext *fmt_ctx = NULL;
if (avformat_open_input(&fmt_ctx, input_filename, NULL, NULL) < 0) {
fprintf(stderr, "无法打开输入文件\n");
return-1;
}
// 3. 读取流信息
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
fprintf(stderr, "无法找到流信息\n");
return-1;
}
// 4. 打印详细信息
av_dump_format(fmt_ctx, 0, input_filename, 0);
// 5. 查找视频流
int video_stream_index = -1;
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_index = i;
break;
}
}
// 6. 读取数据包
AVPacket pkt;
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
if (pkt.stream_index == video_stream_index) {
// 处理视频数据包
process_video_packet(&pkt);
}
av_packet_unref(&pkt);
}
// 7. 关闭输入
avformat_close_input(&fmt_ctx);
3.2.2、完整解复用示例
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <stdio.h>
int demux_video(const char *input_filename) {
AVFormatContext *fmt_ctx = NULL;
AVPacket pkt;
int ret;
// 初始化libavformat并注册所有格式
av_register_all();
// 打开输入文件
if ((ret = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL)) < 0) {
char errbuf[1024];
av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "无法打开文件 '%s': %s\n", input_filename, errbuf);
return ret;
}
// 获取流信息
if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
fprintf(stderr, "无法获取流信息\n");
goto end;
}
// 打印输入格式信息
printf("输入格式: %s\n", fmt_ctx->iformat->name);
printf("时长: %" PRId64 " 秒\n", fmt_ctx->duration / AV_TIME_BASE);
printf("比特率: %" PRId64 " bps\n", fmt_ctx->bit_rate);
printf("流数量: %d\n", fmt_ctx->nb_streams);
// 遍历所有流
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
AVStream *stream = fmt_ctx->streams[i];
AVCodecParameters *codecpar = stream->codecpar;
printf("\n流 #%d:\n", i);
printf(" 类型: %s\n", av_get_media_type_string(codecpar->codec_type));
printf(" 编解码器: %s\n", avcodec_get_name(codecpar->codec_id));
printf(" 时间基: %d/%d\n", stream->time_base.num, stream->time_base.den);
if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
printf(" 分辨率: %dx%d\n", codecpar->width, codecpar->height);
printf(" 帧率: %.2f fps\n", av_q2d(stream->avg_frame_rate));
printf(" 像素格式: %s\n", av_get_pix_fmt_name(codecpar->format));
} elseif (codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
printf(" 采样率: %d Hz\n", codecpar->sample_rate);
printf(" 声道数: %d\n", codecpar->channels);
printf(" 采样格式: %s\n", av_get_sample_fmt_name(codecpar->format));
}
// 打印元数据
AVDictionaryEntry *entry = NULL;
while ((entry = av_dict_get(stream->metadata, "", entry, AV_DICT_IGNORE_SUFFIX))) {
printf(" 元数据: %s = %s\n", entry->key, entry->value);
}
}
// 读取并处理数据包
int packet_count = 0;
while ((ret = av_read_frame(fmt_ctx, &pkt)) >= 0 && packet_count < 100) {
AVStream *stream = fmt_ctx->streams[pkt.stream_index];
printf("\n数据包 #%d:\n", packet_count);
printf(" 流索引: %d\n", pkt.stream_index);
printf(" 大小: %d 字节\n", pkt.size);
printf(" PTS: %" PRId64 "\n", pkt.pts);
printf(" DTS: %" PRId64 "\n", pkt.dts);
printf(" 时长: %" PRId64 "\n", pkt.duration);
printf(" 关键帧: %s\n", (pkt.flags & AV_PKT_FLAG_KEY) ? "是" : "否");
// 这里可以添加实际的数据处理逻辑
// process_packet_data(&pkt, stream);
av_packet_unref(&pkt);
packet_count++;
}
end:
// 清理
if (fmt_ctx) {
avformat_close_input(&fmt_ctx);
}
return ret < 0 ? ret : 0;
}
3.3、复用(Muxing)流程
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
int mux_streams(const char *output_filename) {
AVFormatContext *out_fmt_ctx = NULL;
AVStream *video_stream = NULL, *audio_stream = NULL;
AVCodecContext *video_codec_ctx = NULL, *audio_codec_ctx = NULL;
const AVCodec *video_codec = NULL, *audio_codec = NULL;
int ret;
// 分配输出格式上下文
avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, output_filename);
if (!out_fmt_ctx) {
fprintf(stderr, "无法分配输出格式上下文\n");
return-1;
}
// 查找视频编解码器(H.264)
video_codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!video_codec) {
fprintf(stderr, "找不到H.264编解码器\n");
goto end;
}
// 创建视频流
video_stream = avformat_new_stream(out_fmt_ctx, video_codec);
if (!video_stream) {
fprintf(stderr, "无法创建视频流\n");
goto end;
}
// 设置视频流参数
video_codec_ctx = avcodec_alloc_context3(video_codec);
if (!video_codec_ctx) {
fprintf(stderr, "无法分配视频编解码器上下文\n");
goto end;
}
video_codec_ctx->width = 1920;
video_codec_ctx->height = 1080;
video_codec_ctx->time_base = (AVRational){1, 30}; // 30fps
video_codec_ctx->framerate = (AVRational){30, 1};
video_codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
video_codec_ctx->bit_rate = 4000000; // 4Mbps
// 打开视频编解码器
if ((ret = avcodec_open2(video_codec_ctx, video_codec, NULL)) < 0) {
char errbuf[1024];
av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "无法打开视频编解码器: %s\n", errbuf);
goto end;
}
// 将编解码器参数复制到流
ret = avcodec_parameters_from_context(video_stream->codecpar, video_codec_ctx);
if (ret < 0) {
fprintf(stderr, "无法复制视频编解码器参数\n");
goto end;
}
// 查找音频编解码器(AAC)
audio_codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
if (!audio_codec) {
fprintf(stderr, "找不到AAC编解码器\n");
goto end;
}
// 创建音频流
audio_stream = avformat_new_stream(out_fmt_ctx, audio_codec);
if (!audio_stream) {
fprintf(stderr, "无法创建音频流\n");
goto end;
}
// 设置音频流参数
audio_codec_ctx = avcodec_alloc_context3(audio_codec);
if (!audio_codec_ctx) {
fprintf(stderr, "无法分配音频编解码器上下文\n");
goto end;
}
audio_codec_ctx->sample_rate = 48000;
audio_codec_ctx->channels = 2;
audio_codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
audio_codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
audio_codec_ctx->bit_rate = 128000; // 128kbps
audio_codec_ctx->time_base = (AVRational){1, 48000};
// 打开音频编解码器
if ((ret = avcodec_open2(audio_codec_ctx, audio_codec, NULL)) < 0) {
char errbuf[1024];
av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "无法打开音频编解码器: %s\n", errbuf);
goto end;
}
// 将编解码器参数复制到流
ret = avcodec_parameters_from_context(audio_stream->codecpar, audio_codec_ctx);
if (ret < 0) {
fprintf(stderr, "无法复制音频编解码器参数\n");
goto end;
}
// 打印输出格式信息
av_dump_format(out_fmt_ctx, 0, output_filename, 1);
// 打开输出文件
if (!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&out_fmt_ctx->pb, output_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
char errbuf[1024];
av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "无法打开输出文件: %s\n", errbuf);
goto end;
}
}
// 写入文件头
ret = avformat_write_header(out_fmt_ctx, NULL);
if (ret < 0) {
char errbuf[1024];
av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "无法写入文件头: %s\n", errbuf);
goto end;
}
// 这里应该添加实际的音视频编码和写入逻辑
// 为演示目的,我们写入一些空的数据包
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
// 写入视频帧(假设30帧,1秒视频)
for (int i = 0; i < 30; i++) {
pkt.stream_index = video_stream->index;
pkt.pts = i;
pkt.dts = i;
pkt.duration = 1;
pkt.pos = -1;
if (i % 30 == 0) { // 每秒一个关键帧
pkt.flags |= AV_PKT_FLAG_KEY;
}
ret = av_interleaved_write_frame(out_fmt_ctx, &pkt);
if (ret < 0) {
fprintf(stderr, "写入视频帧失败\n");
goto end;
}
}
// 写入音频帧(假设48000Hz,1024样本/帧,约46帧/秒)
for (int i = 0; i < 46; i++) {
pkt.stream_index = audio_stream->index;
pkt.pts = i * 1024;
pkt.dts = i * 1024;
pkt.duration = 1024;
pkt.pos = -1;
pkt.flags |= AV_PKT_FLAG_KEY;
ret = av_interleaved_write_frame(out_fmt_ctx, &pkt);
if (ret < 0) {
fprintf(stderr, "写入音频帧失败\n");
goto end;
}
}
// 写入文件尾
av_write_trailer(out_fmt_ctx);
printf("复用完成,输出文件: %s\n", output_filename);
end:
// 清理
if (video_codec_ctx) {
avcodec_close(video_codec_ctx);
avcodec_free_context(&video_codec_ctx);
}
if (audio_codec_ctx) {
avcodec_close(audio_codec_ctx);
avcodec_free_context(&audio_codec_ctx);
}
if (out_fmt_ctx) {
if (!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&out_fmt_ctx->pb);
}
avformat_free_context(out_fmt_ctx);
}
return ret < 0 ? ret : 0;
}
4、高级特性
4.1、格式探测
int probe_format(const char *filename) {
AVProbeData probe_data = {0};
uint8_t buf[4096];
FILE *f = fopen(filename, "rb");
if (!f) return-1;
// 读取文件头
probe_data.buf_size = fread(buf, 1, sizeof(buf), f);
probe_data.filename = filename;
probe_data.buf = buf;
fclose(f);
// 探测格式
AVInputFormat *fmt = av_probe_input_format(&probe_data, 1);
if (fmt) {
printf("探测到的格式: %s\n", fmt->name);
printf("格式描述: %s\n", fmt->long_name);
printf("扩展名: %s\n", fmt->extensions ? fmt->extensions : "无");
printf("MIME类型: %s\n", fmt->mime_type ? fmt->mime_type : "无");
// 检查格式能力
printf("支持的功能:\n");
if (fmt->flags & AVFMT_NOFILE) {
printf(" - 不需要文件I/O\n");
}
if (fmt->flags & AVFMT_NEEDNUMBER) {
printf(" - 需要序列号\n");
}
if (fmt->flags & AVFMT_SHOW_IDS) {
printf(" - 显示流ID\n");
}
if (fmt->flags & AVFMT_GLOBALHEADER) {
printf(" - 需要全局头部\n");
}
if (fmt->flags & AVFMT_NOTIMESTAMPS) {
printf(" - 无时间戳\n");
}
if (fmt->flags & AVFMT_GENERIC_INDEX) {
printf(" - 通用索引\n");
}
return0;
}
printf("无法识别格式\n");
return-1;
}
4.2、元数据处理
int handle_metadata(AVFormatContext *fmt_ctx) {
// 处理全局元数据
AVDictionaryEntry *entry = NULL;
printf("全局元数据:\n");
while ((entry = av_dict_get(fmt_ctx->metadata, "", entry, AV_DICT_IGNORE_SUFFIX))) {
printf(" %s = %s\n", entry->key, entry->value);
}
// 处理每个流的元数据
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
AVStream *stream = fmt_ctx->streams[i];
printf("\n流 #%d 元数据:\n", i);
entry = NULL;
while ((entry = av_dict_get(stream->metadata, "", entry, AV_DICT_IGNORE_SUFFIX))) {
printf(" %s = %s\n", entry->key, entry->value);
}
// 添加新的元数据
av_dict_set(&stream->metadata, "encoder", "libavformat_demo", 0);
}
// 设置输出元数据
av_dict_set(&fmt_ctx->metadata, "title", "Demo Video", 0);
av_dict_set(&fmt_ctx->metadata, "author", "FFmpeg Developer", 0);
av_dict_set(&fmt_ctx->metadata, "creation_time", "2024-01-01T00:00:00Z", 0);
return0;
}
4.3、错误处理和恢复
int robust_demuxing(const char *input_filename) {
AVFormatContext *fmt_ctx = NULL;
AVPacket pkt;
int ret, error_count = 0;
constint MAX_ERRORS = 10;
// 设置错误标志
AVDictionary *opts = NULL;
av_dict_set(&opts, "err_detect", "crccheck+bitstream+buffer", 0);
// 打开输入文件
ret = avformat_open_input(&fmt_ctx, input_filename, NULL, &opts);
av_dict_free(&opts);
if (ret < 0) {
char errbuf[1024];
av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "打开文件失败: %s\n", errbuf);
return ret;
}
// 读取数据包,带错误恢复
while (error_count < MAX_ERRORS) {
ret = av_read_frame(fmt_ctx, &pkt);
if (ret == AVERROR_EOF) {
printf("到达文件末尾\n");
break;
} elseif (ret == AVERROR(EAGAIN)) {
// 需要更多数据
continue;
} elseif (ret < 0) {
char errbuf[1024];
av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "读取数据包错误: %s\n", errbuf);
error_count++;
if (error_count >= MAX_ERRORS) {
fprintf(stderr, "错误次数过多,停止处理\n");
break;
}
// 尝试跳过错误部分
continue;
}
// 成功读取数据包,进行处理
printf("成功读取数据包: stream=%d, size=%d, pts=%"PRId64"\n",
pkt.stream_index, pkt.size, pkt.pts);
// 处理数据包...
av_packet_unref(&pkt);
}
avformat_close_input(&fmt_ctx);
return error_count < MAX_ERRORS ? 0 : -1;
}
5、性能优化
5.1、内存管理优化
typedef struct {
AVFormatContext *fmt_ctx;
AVPacket *packet_buffer;
int buffer_size;
int buffer_index;
} OptimizedDemuxer;
OptimizedDemuxer* create_optimized_demuxer(const char *filename) {
OptimizedDemuxer *demuxer = calloc(1, sizeof(OptimizedDemuxer));
if (!demuxer) returnNULL;
// 预分配包缓冲区
demuxer->buffer_size = 100;
demuxer->packet_buffer = calloc(demuxer->buffer_size, sizeof(AVPacket));
if (!demuxer->packet_buffer) {
free(demuxer);
returnNULL;
}
// 预分配格式上下文
demuxer->fmt_ctx = avformat_alloc_context();
if (!demuxer->fmt_ctx) {
free(demuxer->packet_buffer);
free(demuxer);
returnNULL;
}
// 设置自定义I/O缓冲区(可选)
uint8_t *io_buffer = av_malloc(32768); // 32KB缓冲区
demuxer->fmt_ctx->pb = avio_alloc_context(
io_buffer, 32768, 0, NULL, NULL, NULL, NULL
);
return demuxer;
}
5.2、多线程处理
#include <pthread.h>
typedefstruct {
AVFormatContext *fmt_ctx;
int stream_index;
AVPacket *packets;
int packet_count;
pthread_mutex_t mutex;
} ThreadSafeDemuxer;
void* demux_thread(void *arg) {
ThreadSafeDemuxer *demuxer = (ThreadSafeDemuxer*)arg;
AVPacket pkt;
while (1) {
pthread_mutex_lock(&demuxer->mutex);
int ret = av_read_frame(demuxer->fmt_ctx, &pkt);
pthread_mutex_unlock(&demuxer->mutex);
if (ret < 0) break;
if (pkt.stream_index == demuxer->stream_index) {
// 处理特定流的数据包
process_packet(&pkt);
}
av_packet_unref(&pkt);
}
returnNULL;
}
int start_multithreaded_demuxing(AVFormatContext *fmt_ctx, int num_threads) {
pthread_t *threads = calloc(num_threads, sizeof(pthread_t));
ThreadSafeDemuxer *demuxers = calloc(num_threads, sizeof(ThreadSafeDemuxer));
for (int i = 0; i < num_threads; i++) {
demuxers[i].fmt_ctx = fmt_ctx;
demuxers[i].stream_index = i % fmt_ctx->nb_streams;
pthread_mutex_init(&demuxers[i].mutex, NULL);
pthread_create(&threads[i], NULL, demux_thread, &demuxers[i]);
}
// 等待所有线程完成
for (int i = 0; i < num_threads; i++) {
pthread_join(threads[i], NULL);
pthread_mutex_destroy(&demuxers[i].mutex);
}
free(threads);
free(demuxers);
return0;
}
6、实际应用案例
6.1、媒体文件分析器
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/dict.h>
#include <stdio.h>
typedefstruct {
constchar *filename;
constchar *format_name;
int64_t duration;
int64_t bit_rate;
int nb_streams;
struct {
int index;
enum AVMediaType type;
constchar *codec_name;
int width, height;
int sample_rate;
int channels;
int64_t bit_rate;
} streams[32];
} MediaInfo;
int analyze_media_file(const char *filename, MediaInfo *info) {
AVFormatContext *fmt_ctx = NULL;
int ret;
// 初始化
memset(info, 0, sizeof(MediaInfo));
info->filename = filename;
// 打开文件
if ((ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL)) < 0) {
return ret;
}
// 获取流信息
if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
avformat_close_input(&fmt_ctx);
学习和提升音视频开发技术,推荐你加入我们的知识星球:【关键帧的音视频开发圈】

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。