FFmpeg的入门实践系列五(编程入门之属性查看)-LMLPHP

欢迎诸位来阅读在下的博文~
在这里,在下会不定期发表一些浅薄的知识和经验,望诸位能与在下多多交流,共同努力

前期博客

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 的指针,用于日志处理、调试和其他全局设置。
  • iformatoformat:分别指向输入格式和输出格式结构体。用于定义当前上下文是用于输入还是输出。
  • priv_data:格式特定的私有数据,通常由特定的输入或输出格式使用。
  • pbAVIOContext 用于管理输入输出操作的上下文,比如读写文件或网络数据。
  • 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

输出结果:
FFmpeg的入门实践系列五(编程入门之属性查看)-LMLPHP

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

输出结果:
FFmpeg的入门实践系列五(编程入门之属性查看)-LMLPHP

二、AVStream结构体

数据流对应FFmpeg的AVStream结构,调用avformat_new_stream函数会给音视频文件创建指定编码器的数据流。FFmpeg支持的数据流类型见表如下:
当然,以下是一个Markdown格式的表格,展示了FFmpeg支持的数据流类型及其对应的数值:

AVFormatContext结构体中的stream字段就是AVStream的指针数组,里面放的是AVStream的流对象,有视频流、音频流等。AVStream结构体的常见字段如下:

  • index:当前数据流的索引
  • start_time:当前数据流的开始播放时间戳
  • nb_frames:当前数据流包含的数据帧个数
  • duration:当前数据流的结束播放时间戳
  • codecpar:一个指向 AVCodecParameters 结构的指针,包含与流的解码器相关的参数,如编码器类型、比特率、采样率、分辨率等

详细实例请看上面的实例2。

记得一提的是,每个音视频文件都有若干数据流,每个数据流也都有相应的编解码器,诸位可使用avcodec_find_decoder和avcodec_find_encoder分别查找,但是一般来说,编解码器的两个函数接口返回的值是一样的,比如都是H264。这里可以灵活使用。

以下是常见的编解码器与编码标准的对应关系:

实例:查看数据流的编解码器的参数

对于音视频的数据流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

输出结果:
FFmpeg的入门实践系列五(编程入门之属性查看)-LMLPHP

附件

编译代码要连接各种库,着实麻烦。为了方便大家编译代码,在下提供了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
)

至此,结束~
FFmpeg的入门实践系列五(编程入门之属性查看)-LMLPHP
望诸位不忘三连支持一下~

08-25 19:08