文章目录
多媒体文件的基本概念
- 多媒体文件其实是个容器
多媒体文件(如MP4、MKV、AVI等)实际上是一个容器格式。容器的作用是将不同类型的数据(如视频、音频、字幕等)封装在一个文件中,方便管理和播放。每种容器格式都有自己的规范,定义了如何组织和存储这些不同类型的数据。
- 在容器里有很多流
在多媒体容器文件中,可以包含多个不同的流。每个流代表一种媒体数据,例如视频流、音频流、字幕流等。一个典型的多媒体文件通常至少包含一个视频流和一个音频流,但也可以包含多个视频流、多个音频流和其他类型的流(如字幕、章节信息、元数据等)。
- 每种流是由不同的编码器编码实现的
每个流的数据在存储之前需要经过编码。编码器(如H.264、AAC、MP3等)将原始的多媒体数据(如未压缩的视频和音频)转换成压缩格式,以减少存储空间和传输带宽。不同的编码器适用于不同类型的数据和使用场景。例如,视频流可能使用H.264编码器,音频流可能使用AAC编码器。
- 从流中读出的数据叫做包
在流中,数据被分成一个个的数据包(packet)。每个包包含一段编码后的多媒体数据,以及一些元数据(如时间戳、流的标识等)。在解码和播放时,播放器会从容器文件中读取这些数据包,并将其传递给相应的解码器进行解码。
- 在一个包中包含多个帧
数据包中的内容进一步细分为帧。帧是视频或音频数据的最小单位。例如,在视频流中,每一帧代表一个静止的图像,连续播放这些图像可以形成视频。在音频流中,每一帧代表一段音频采样数据。帧的数量和类型(如关键帧、预测帧等)取决于编码器的工作方式和编码参数。
相关重要的结构体
- AVFormatContext 结构体
AVFormatContext
是 FFmpeg 中用于描述多媒体文件或流的上下文结构体。它包含了文件格式、输入输出协议、文件信息以及多个流等信息。
- AVStream 结构体
AVStream
是 FFmpeg 中用于描述多媒体文件中的一个流(如视频流、音频流、字幕流等)的结构体。每个 AVStream
包含了流的编解码信息、时间基准等。
- AVPacket
AVPacket
是 FFmpeg 中用于描述存储在容器中的多媒体数据包的结构体。数据包是编码后的数据,包含一组帧。
操作数据流的基本步骤
1.解复用(Demuxing)
解复用是指从多媒体容器中提取出独立的音频、视频和其他流的过程。在FFmpeg中,解复用通过打开文件并解析文件头部信息来实现。
主要步骤
- 注册所有格式和编解码器: 使用
av_register_all()
注册FFmpeg支持的所有格式和编解码器(FFmpeg 4.x及以前版本需要,FFmpeg 5.0及以后版本不需要)。 - 打开输入文件: 使用
avformat_open_input()
打开输入文件。 - 读取文件头部信息: 使用
avformat_find_stream_info()
读取文件头部信息。
示例代码
AVFormatContext *fmt_ctx = NULL;
int ret;
// 注册所有格式和编解码器(FFmpeg 4.x及以前版本需要)
av_register_all();
// 打开输入文件
if ((ret = avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot open input file: %s\n", av_err2str(ret));
return ret;
}
// 读取文件头部信息
if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot find stream information: %s\n", av_err2str(ret));
avformat_close_input(&fmt_ctx);
return ret;
}
// 打印输入文件的信息
av_dump_format(fmt_ctx, 0, "input.mp4", 0);
2.获取流(Stream)
获取流是指从多媒体文件中提取出各个独立的流,例如音频流和视频流。每个流包含了相关的编解码信息。
主要步骤
- 查找音频和视频流: 遍历
AVFormatContext
中的流,查找音频和视频流。 - 打印流信息: 打印每个流的信息。
示例代码
AVStream *video_stream = NULL;
AVStream *audio_stream = NULL;
for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {
AVStream *stream = fmt_ctx->streams[i];
if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream = stream;
} else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audio_stream = stream;
}
}
if (!video_stream && !audio_stream) {
av_log(NULL, AV_LOG_ERROR, "No video or audio stream found\n");
avformat_close_input(&fmt_ctx);
return -1;
}
3. 读取数据包(Packet)
读取数据包是指从文件中逐个读取编码后的数据包。数据包可以包含音频、视频或其他类型的数据。
主要步骤
- 初始化数据包: 使用
av_init_packet()
初始化数据包。 - 读取数据包: 使用
av_read_frame()
读取数据包。 - 处理数据包: 根据数据包所属的流进行相应处理。
- 释放数据包: 使用
av_packet_unref()
释放数据包。
示例代码
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
if (pkt.stream_index == video_stream->index) {
// 处理视频数据包
av_log(NULL, AV_LOG_INFO, "Video Packet: PTS=%" PRId64 ", DTS=%" PRId64 ", size=%d\n",
pkt.pts, pkt.dts, pkt.size);
} else if (pkt.stream_index == audio_stream->index) {
// 处理音频数据包
av_log(NULL, AV_LOG_INFO, "Audio Packet: PTS=%" PRId64 ", DTS=%" PRId64 ", size=%d\n",
pkt.pts, pkt.dts, pkt.size);
}
av_packet_unref(&pkt);
}
4. 释放资源(Free Resources)
释放资源是指在完成数据流操作后,释放分配的所有内存和资源,以避免内存泄漏。
主要步骤
- 释放数据包: 使用
av_packet_unref()
释放每个数据包。 - 关闭输入文件: 使用
avformat_close_input()
关闭输入文件并释放AVFormatContext
。 - 释放其他资源: 释放任何其他分配的资源。
示例代码
// 释放数据包
av_packet_unref(&pkt);
// 关闭输入文件并释放AVFormatContext
avformat_close_input(&fmt_ctx);
完整示例
#include <libavformat/avformat.h>
#include <libavutil/log.h>
int main(int argc, char *argv[]) {
AVFormatContext *fmt_ctx = NULL;
AVPacket pkt;
AVStream *video_stream = NULL;
AVStream *audio_stream = NULL;
int ret;
if (argc < 2) {
fprintf(stderr, "Usage: %s <input file>\n", argv[0]);
return 1;
}
// 注册所有格式和编解码器(FFmpeg 4.x及以前版本需要)
av_register_all();
// 打开输入文件
if ((ret = avformat_open_input(&fmt_ctx, argv[1], NULL, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot open input file: %s\n", av_err2str(ret));
return ret;
}
// 读取文件头部信息
if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot find stream information: %s\n", av_err2str(ret));
avformat_close_input(&fmt_ctx);
return ret;
}
// 打印输入文件的信息
av_dump_format(fmt_ctx, 0, argv[1], 0);
// 查找音频和视频流
for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {
AVStream *stream = fmt_ctx->streams[i];
if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream = stream;
} else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audio_stream = stream;
}
}
if (!video_stream && !audio_stream) {
av_log(NULL, AV_LOG_ERROR, "No video or audio stream found\n");
avformat_close_input(&fmt_ctx);
return -1;
}
// 初始化数据包
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
// 读取数据包
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
if (pkt.stream_index == video_stream->index) {
// 处理视频数据包
av_log(NULL, AV_LOG_INFO, "Video Packet: PTS=%" PRId64 ", DTS=%" PRId64 ", size=%d\n",
pkt.pts, pkt.dts, pkt.size);
} else if (pkt.stream_index == audio_stream->index) {
// 处理音频数据包
av_log(NULL, AV_LOG_INFO, "Audio Packet: PTS=%" PRId64 ", DTS=%" PRId64 ", size=%d\n",
pkt.pts, pkt.dts, pkt.size);
}
av_packet_unref(&pkt);
}
// 关闭输入文件并释放AVFormatContext
avformat_close_input(&fmt_ctx);
return 0;
}