文章目录
- 多媒体文件的基本概念
- 相关重要的结构体
- 操作数据流的基本步骤
- 1.解复用(Demuxing)
- 2.获取流(Stream)
- 3. 读取数据包(Packet)
- 4. 释放资源(Free Resources)
- 完整示例
多媒体文件的基本概念
- 多媒体文件其实是个容器
多媒体文件(如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);}// 关闭输入文件并释放AVFormatContextavformat_close_input(&fmt_ctx);return 0;
}