欢迎诸位来阅读在下的博文~
在这里,在下会不定期发表一些浅薄的知识和经验,望诸位能与在下多多交流,共同努力
文章目录
- 前期博客
- 参考书籍
- 一、AVFormatContext结构体
- 1. 结构定义
- 2. 字段说明
- 3.示例1(打开与关闭音视频文件)
- 4.实例2(查看音视频的信息)
- 二、AVStream结构体
- 实例:查看数据流的编解码器的参数
- 附件
前期博客
FFmpeg的入门实践系列一(环境搭建)
FFmpeg的入门实践系列二(基础知识)
FFmpeg的入门实践系列三(基础知识)
FFmpeg的入门实践系列四(AVS)
参考书籍
《FFmpeg开发实战——从零基础到短视频上线》——欧阳燊
一、AVFormatContext结构体
AVFormatContext
是 FFmpeg 中用于处理多媒体文件格式的一个核心结构体。它包含了文件的格式信息,以及与文件相关的流信息,如音频流、视频流和字幕流。以下是 AVFormatContext
的详细信息:
1. 结构定义
typedef struct AVFormatContext {
const AVClass *av_class; // AVClass提供日志处理等功能
struct AVInputFormat *iformat; // 输入格式
struct AVOutputFormat *oformat;// 输出格式
void *priv_data; // 私有数据,特定于输入或输出格式
AVIOContext *pb; // 读写数据的IO上下文
int ctx_flags; // 上下文标志
unsigned int nb_streams; // 流的数量
AVStream **streams; // 指向流的指针数组
char filename[1024]; // 文件名
// 一些时间基准和时长信息
int64_t duration; // 文件时长
int64_t bit_rate; // 比特率
unsigned int packet_size; // 数据包大小
int max_delay; // 最大延迟
// 其他与解码、时序、元数据等相关的字段
AVDictionary *metadata; // 文件元数据,如标题、作者等
// 其他字段省略...
} AVFormatContext;
2. 字段说明
av_class
:指向AVClass
的指针,用于日志处理、调试和其他全局设置。iformat
和oformat
:分别指向输入格式和输出格式结构体。用于定义当前上下文是用于输入还是输出。priv_data
:格式特定的私有数据,通常由特定的输入或输出格式使用。pb
:AVIOContext
用于管理输入输出操作的上下文,比如读写文件或网络数据。ctx_flags
:用于标志一些上下文状态,比如是否允许缺少流、是否实时流等。nb_streams
:表示文件中包含的流的数量(音频流、视频流、字幕流等)。streams
:一个指向AVStream
指针数组的指针,每个AVStream
结构体包含一个具体流的信息。filename
:文件名或 URL,用于标识输入或输出资源。duration
:表示文件的总时长,以 AV_TIME_BASE(通常是微秒)为单位。bit_rate
:文件的总比特率(包括所有流)。packet_size
:数据包的大小,主要用于输出。max_delay
:最大延迟,用于管理实时流的延迟处理。metadata
:文件的元数据,存储在一个AVDictionary
中。
3.示例1(打开与关闭音视频文件)
#include <stdio.h>
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#ifdef __cplusplus
};
#endif
int main(int argc, char** argv){
const char* filename = "./fuzhou.mp4";
if(argc > 1){
filename = argv[1];
}
AVFormatContext* fmt_ctx = NULL;
//打开音视频文件
int ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL);
if(ret < 0){
av_log(NULL, AV_LOG_ERROR, "open file:%s fail\n", filename);
return -1;
}
av_log(NULL, AV_LOG_INFO, "success open input_file %s \n", filename);
// 查找音视频文件中的流信息
ret = avformat_find_stream_info(fmt_ctx, NULL);
if(ret < 0){
av_log(NULL, AV_LOG_ERROR, "find streaminfo fail\n");
return -1;
}
av_log(NULL, AV_LOG_INFO, "success find streaminfo\n");
const AVInputFormat* iformat = fmt_ctx->iformat;
av_log(NULL, AV_LOG_INFO, "format name is %s \n", iformat->name);
av_log(NULL, AV_LOG_INFO, "format long_name is %s \n",iformat->long_name);
avformat_close_input(&fmt_ctx); //关闭音频文件
return 0;
}
编译:
gcc helloffmpeg.c -o helloffmpeg -I /usr/local/ffmpeg/include -L /usr/local/ffmpeg/lib -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lpostproc -lm
输出结果:
4.实例2(查看音视频的信息)
#include <stdio.h>
// 之所以增加__cplusplus的宏定义,是为了同时兼容gcc编译器和g++编译器
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#ifdef __cplusplus
};
#endif
int main(int argc, char **argv) {
const char *filename = "../fuzhou.mp4";
if (argc > 1) {
filename = argv[1];
}
AVFormatContext *fmt_ctx = NULL;
// 打开音视频文件
int ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Can't open file %s.\n", filename);
return -1;
}
av_log(NULL, AV_LOG_INFO, "Success open input_file %s.\n", filename);
// 查找音视频文件中的流信息
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Can't find stream information.\n");
return -1;
}
av_log(NULL, AV_LOG_INFO, "Success find stream information.\n");
// 格式化输出文件信息
av_dump_format(fmt_ctx, 0, filename, 0);
av_log(NULL, AV_LOG_INFO, "duration=%d\n", fmt_ctx->duration); // 持续时间,单位微秒
av_log(NULL, AV_LOG_INFO, "bit_rate=%d\n", fmt_ctx->bit_rate); // 比特率,单位比特每秒
av_log(NULL, AV_LOG_INFO, "nb_streams=%d\n", fmt_ctx->nb_streams); // 数据流的数量
av_log(NULL, AV_LOG_INFO, "max_streams=%d\n", fmt_ctx->max_streams); // 数据流的最大数量
// 找到视频流的索引
int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
av_log(NULL, AV_LOG_INFO, "video_index=%d\n", video_index);
if (video_index >= 0) {
AVStream *video_stream = fmt_ctx->streams[video_index];
av_log(NULL, AV_LOG_INFO, "video_stream index=%d\n", video_stream->index);
av_log(NULL, AV_LOG_INFO, "video_stream start_time=%d\n", video_stream->start_time);
av_log(NULL, AV_LOG_INFO, "video_stream nb_frames=%d\n", video_stream->nb_frames);
av_log(NULL, AV_LOG_INFO, "video_stream duration=%d\n", video_stream->duration);
}
// 找到音频流的索引
int audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
av_log(NULL, AV_LOG_INFO, "audio_index=%d\n", audio_index);
if (audio_index >= 0) {
AVStream *audio_stream = fmt_ctx->streams[audio_index];
av_log(NULL, AV_LOG_INFO, "audio_stream index=%d\n", audio_stream->index);
av_log(NULL, AV_LOG_INFO, "audio_stream start_time=%d\n", audio_stream->start_time);
av_log(NULL, AV_LOG_INFO, "audio_stream nb_frames=%d\n", audio_stream->nb_frames);
av_log(NULL, AV_LOG_INFO, "audio_stream duration=%d\n", audio_stream->duration);
}
avformat_close_input(&fmt_ctx); // 关闭音视频文件
return 0;
}
编译:
gcc look.c -o look -I /usr/local/ffmpeg/include -L /usr/local/ffmpeg/lib -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lpostproc -lm
输出结果:
二、AVStream结构体
数据流对应FFmpeg的AVStream结构,调用avformat_new_stream函数会给音视频文件创建指定编码器的数据流。FFmpeg支持的数据流类型见表如下:
当然,以下是一个Markdown格式的表格,展示了FFmpeg支持的数据流类型及其对应的数值:
数据流类型 | 对应数值 | 描述 |
---|---|---|
视频流 | 0 | 包含视频数据,支持多种编解码器,如H.264、H.265、VP8、VP9等。 |
音频流 | 1 | 包含音频数据,支持多种编解码器,如AAC、MP3、OGG、WMA等。 |
字幕流 | 2 | 包含视频的字幕或文本信息,可以是硬字幕或软字幕。 |
数据流 | 3 | 可以包含任何类型的数据,如图片、文本等。 |
附件流 | 4 | 通常包含与视频或音频相关的额外文件,如封面图片、歌词等。 |
媒体类型数量 | 5 | 表示媒体类型的数量。 |
AVFormatContext结构体中的stream字段就是AVStream的指针数组,里面放的是AVStream的流对象,有视频流、音频流等。AVStream结构体的常见字段如下:
- index:当前数据流的索引
- start_time:当前数据流的开始播放时间戳
- nb_frames:当前数据流包含的数据帧个数
- duration:当前数据流的结束播放时间戳
- codecpar:一个指向 AVCodecParameters 结构的指针,包含与流的解码器相关的参数,如编码器类型、比特率、采样率、分辨率等
详细实例请看上面的实例2。
记得一提的是,每个音视频文件都有若干数据流,每个数据流也都有相应的编解码器,诸位可使用avcodec_find_decoder和avcodec_find_encoder分别查找,但是一般来说,编解码器的两个函数接口返回的值是一样的,比如都是H264。这里可以灵活使用。
以下是常见的编解码器与编码标准的对应关系:
编解码器编号 | 对应数值 | 编解码器名称 | 采用的编码标准 |
---|---|---|---|
AV_CODEC_ID_H264 | 28 | H.264 | ITU-T H.264/AVC |
AV_CODEC_ID_H265 | 174 | H.265 | ITU-T H.265/HEVC |
AV_CODEC_ID_VP8 | 139 | VP8 | WebM Project |
AV_CODEC_ID_VP9 | 167 | VP9 | WebM Project |
AV_CODEC_ID_AAC | 86018 | AAC | ISO/IEC 13818-7 |
AV_CODEC_ID_MP3 | 86016 | MP3 | ISO/IEC 11172-3 |
AV_CODEC_ID_OGG | 86021 | OGG/VORBIS | Xiph.Org Foundation |
AV_CODEC_ID_WMAV1 | 86025 | WMAV1 | Microsoft |
AV_CODEC_ID_WMAV2 | 86026 | WMAV2 | Microsoft |
AV_CODEC_ID_AV1 | 285 | AV1 | Alliance for Open Media |
AV_CODEC_ID_FLAC | 86028 | FLAC | Xiph.Org Foundation |
AV_CODEC_ID_OPUS | 86030 | OPUS | Xiph.Org Foundation |
实例:查看数据流的编解码器的参数
对于音视频的数据流AVStream来说,FFmpeg把编解码器的编号保存在其codecpar属性的codec_id字段。调用avcodec_find_decoder函数传入编解码器的编号,即可获得编解码器的结构指针。
#include <stdio.h>
// 之所以增加__cplusplus的宏定义,是为了同时兼容gcc编译器和g++编译器
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#ifdef __cplusplus
};
#endif
int main(int argc, char** argv){
const char* filename = "./fuzhou.mp4";
if(argc > 1){
filename = argv[1];
}
AVFormatContext* fmt_ctx = NULL;
//发开音视频文件
int ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL);
if(ret < 0){
av_log(NULL, AV_LOG_ERROR, "open file fail\n");
return -1;
}
av_log(NULL, AV_LOG_INFO, "success open file\n");
ret = avformat_find_stream_info(fmt_ctx, NULL);
if(ret < 0){
av_log(NULL, AV_LOG_ERROR, "stream info is empty\n");
return -1;
}
//打印音视频文件属性
av_log(NULL, AV_LOG_INFO, "Success find stream information.\n");
av_log(NULL, AV_LOG_INFO, "duration=%d\n", fmt_ctx->duration); // 持续时间,单位微秒
av_log(NULL, AV_LOG_INFO, "nb_streams=%d\n", fmt_ctx->nb_streams); // 数据流的数量
av_log(NULL, AV_LOG_INFO, "max_streams=%d\n", fmt_ctx->max_streams); // 数据流的最大数量
av_log(NULL, AV_LOG_INFO, "video_codec_id=%d\n", fmt_ctx->video_codec_id);
av_log(NULL, AV_LOG_INFO, "audio_codec_id=%d\n", fmt_ctx->audio_codec_id);
//获取视频流的索引
int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
av_log(NULL, AV_LOG_INFO, "video_index=%d\n", video_index);
if (video_index >= 0) {
AVStream *video_stream = fmt_ctx->streams[video_index];
enum AVCodecID video_codec_id = video_stream->codecpar->codec_id;
// av_log(NULL, AV_LOG_INFO, "video_stream codec_id=%d\n", video_codec_id);
// 查找视频解码器
AVCodec *video_codec = (AVCodec*) avcodec_find_decoder(video_codec_id);
if (!video_codec) {
av_log(NULL, AV_LOG_ERROR, "video_codec not found\n");
return -1;
}
av_log(NULL, AV_LOG_INFO, "video_codec id=%d\n", video_codec->id);
av_log(NULL, AV_LOG_INFO, "video_codec name=%s\n", video_codec->name);
av_log(NULL, AV_LOG_INFO, "video_codec long_name=%s\n", video_codec->long_name);
// 下面的type字段来自AVMediaType定义,为0表示AVMEDIA_TYPE_VIDEO,为1表示AVMEDIA_TYPE_AUDIO
av_log(NULL, AV_LOG_INFO, "video_codec type=%d\n", video_codec->type);
}
//获取音频流的索引
int audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
av_log(NULL, AV_LOG_INFO, "audio_index=%d\n", audio_index);
if (audio_index >= 0) {
AVStream *audio_stream = fmt_ctx->streams[audio_index];
enum AVCodecID audio_codec_id = audio_stream->codecpar->codec_id;
// av_log(NULL, AV_LOG_INFO, "audio_stream codec_id=%d\n", audio_codec_id);
// 查找音频解码器
AVCodec *audio_codec = (AVCodec*) avcodec_find_decoder(audio_codec_id);
if (!audio_codec) {
av_log(NULL, AV_LOG_ERROR, "audio_codec not found\n");
return -1;
}
av_log(NULL, AV_LOG_INFO, "audio_codec id=%d\n", audio_codec->id);
av_log(NULL, AV_LOG_INFO, "audio_codec name=%s\n", audio_codec->name);
av_log(NULL, AV_LOG_INFO, "audio_codec long_name=%s\n", audio_codec->long_name);
av_log(NULL, AV_LOG_INFO, "audio_codec type=%d\n", audio_codec->type);
}
avformat_close_input(&fmt_ctx); // 关闭音视频文件
return 0;
}
编译:
gcc codec.c -o codec -I /usr/local/ffmpeg/include -L /usr/local/ffmpeg/lib -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lpostproc -lm
输出结果:
附件
编译代码要连接各种库,着实麻烦。为了方便大家编译代码,在下提供了CMakeLists.txt文件,只需要更改相应.c文件名和输出文件即可,诸君自取~
cmake_minimum_required(VERSION 3.10)
# 项目名称
project(Helloffmpeg)
# 设置 C 标准
set(CMAKE_C_STANDARD 99)
# 指定源文件(自行修改)
set(SRC codec.c)
# 指定头文件搜索路径
include_directories(/usr/local/ffmpeg/include)
# 指定库文件搜索路径
link_directories(/usr/local/ffmpeg/lib)
# 添加可执行文件(自行修改)
add_executable(codec ${SRC})
# 链接 FFmpeg 库(自行修改)
target_link_libraries(codec
avformat
avdevice
avfilter
avcodec
avutil
swscale
swresample
postproc
m
)
至此,结束~
望诸位不忘三连支持一下~