当前位置: 首页 > news >正文

ffmpeg音视频开发从入门到精通——ffmpeg 视频数据抽取

文章目录

  • FFmpeg视频处理工具使用总结
    • 环境配置
    • 主函数与参数处理
    • 打开输入文件
    • 获取流信息
    • 分配输出文件上下文
    • 猜测输出文件格式
    • 创建视频流并设置参数
    • 打开输出文件并写入头信息
    • 读取、转换并写入帧数据
    • 写入尾信息并释放资源
    • 运行程序
    • 注意事项
    • 源代码

FFmpeg视频处理工具使用总结

环境配置

  • 在C++程序中使用FFmpeg之前,需要包含相应的头文件,并根据是否使用C++编译器,可能需要添加extern "C"块。在C++中,当包含C语言的头文件时,需要使用extern "C"来告诉编译器这是一个C语言的函数,避免C++的名称修饰导致链接错误。这个例子中,包含了FFmpeg库的头文件,用于处理多媒体文件。
  • 在C++中,当包含C语言的头文件时,需要使用extern "C"来告诉编译器这是一个C语言的函数,避免C++的名称修饰导致链接错误。
#ifdef __cplusplus
extern "C" {
#endif
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/log.h>
#ifdef __cplusplus
}
#endif

主函数与参数处理

程序入口点是main函数,它处理命令行参数并设置日志级别。

  • 检查传入的参数个数是否大于等于3,如果参数个数小于3,就输出错误信息并退出程序。

  • 将第一个参数赋值给变量src,将第二个参数赋值给变量dst,这样就得到了输入文件路径和输出文件路径。

  • 调用av_log_set_level函数设置日志级别为AV_LOG_DEBUG,这样在程序执行过程中会打印出调试级别的日志信息。

int main(int argc, char *argv[]) {// 参数检查if (argc < 3) {av_log(nullptr, AV_LOG_ERROR, "Usage: %s <input file> <output file>\n", argv[0]);exit(-1);}// 输入输出文件路径char *src = argv[1];char *dst = argv[2];// 设置日志级别av_log_set_level(AV_LOG_DEBUG);
}

打开输入文件

使用avformat_open_input打开输入文件,并检查返回值。avformat_open_input是libavformat库中的一个函数,用于打开一个输入文件并初始化输入上下文。
输入参数如下:

  1. AVFormatContext **ps:指向要初始化的AVFormatContext指针的指针。函数将分配一个AVFormatContext结构并将其指针保存在该参数指向的地址中。
  2. const char *url:输入文件的URL或文件名。
  3. AVInputFormat *fmt:强制指定要使用的输入格式。如果为NULL,则函数将根据输入文件的扩展名自动选择适当的格式。
  4. AVDictionary **options:一个指向AVDictionary结构指针的指针,用于设置差异化选项。可以使用该参数来设置例如输入缓冲区大小、超时值等选项。
  5. int (*interrupt_callback)(void):设置中断回调函数。如果输入操作需要中断,则调用此回调函数。如果为NULL,则不设置中断回调。

函数执行成功后,ps将指向已初始化的AVFormatContext结构,可以使用该结构来进行后续的输入操作。执行失败时,ps将保持不变,而函数将返回一个负值以表示错误原因。

int ret = avformat_open_input(&pFormatCtx, src, nullptr, nullptr);
if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "Could not open input file: %s\n", src);exit(-1);
}

获取流信息

调用av_find_best_stream找到最佳的视频流。
av_find_best_stream是FFmpeg库中的一个函数,用于查找最佳的流索引。它的定义如下:

int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type, int wanted_stream_nb, int related_stream, AVCodec **decoder_ret, int flags);

参数说明如下:

  1. ic:指向AVFormatContext的指针,表示输入的封装格式上下文。
  2. type:表示所需流的媒体类型,可以是AVMEDIA_TYPE_AUDIOAVMEDIA_TYPE_VIDEOAVMEDIA_TYPE_SUBTITLE
  3. wanted_stream_nb:表示所需流的索引号。
  4. related_stream:表示关联的流的索引号。通常设置为负值,表示没有关联的流。
  5. decoder_ret:指向AVCodec指针的指针,用于返回找到的解码器。
  6. flags:表示查找流的标志位,可以是AVFMT_FLAG_NOFILEAVFMT_FLAG_GENPTS等。

函数返回值为找到的流的索引号,如果未找到则返回负值错误代码。

这个函数的主要功能是根据指定的媒体类型和索引号,在输入的封装格式上下文中查找最佳的流索引。它会根据一些条件(如流的媒体类型、时间基等)进行评估,然后返回找到的最佳流索引,并通过decoder_ret参数返回对应的解码器。av_find_best_stream是FFmpeg库中的一个函数,用于查找最佳的流索引。它的定义如下:

ret = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "Failed to retrieve input stream information\n");goto _ERROR;
}

分配输出文件上下文

使用avformat_alloc_context分配输出文件的格式上下文。

avformat_alloc_context是FFmpeg中的一个函数,用于分配一个AVFormatContext结构体,并初始化其成员变量。

输入参数为void。avformat_alloc_context是FFmpeg中的一个函数,用于分配一个AVFormatContext结构体,并初始化其成员变量。

输入参数为void。

oFormatCtx = avformat_alloc_context();
if (oFormatCtx == nullptr) {av_log(nullptr, AV_LOG_ERROR, "Failed to allocate output context\n");goto _ERROR;
}

猜测输出文件格式

使用av_guess_format猜测输出文件的格式。
av_guess_format是FFmpeg库中的一个函数,用于猜测输入参数的格式。它的输入参数包括:

  1. const char *short_name:输入参数的短名称。这是一个字符串,通常是文件的扩展名或格式的简称。
  2. const char *filename:输入参数的文件名。这是一个字符串,指定要处理的文件路径。
  3. const char *mime_type:输入参数的MIME类型。这是一个字符串,指定要处理的媒体类型。

这些输入参数可根据实际需求来选择设置,也可以通过设置为NULL来忽略某些参数。根据提供的输入参数,av_guess_format函数会尝试猜测并返回最有可能的格式。

注意:在使用av_guess_format函数时,应确保FFmpeg库已正确初始化,并且所需的输入参数有效且符合可接受的格式。av_guess_format是FFmpeg库中的一个函数,用于猜测输入参数的格式。它的输入参数包括:

  1. const char *short_name:输入参数的短名称。这是一个字符串,通常是文件的扩展名或格式的简称。
  2. const char *filename:输入参数的文件名。这是一个字符串,指定要处理的文件路径。
  3. const char *mime_type:输入参数的MIME类型。这是一个字符串,指定要处理的媒体类型。

这些输入参数可根据实际需求来选择设置,也可以通过设置为NULL来忽略某些参数。根据提供的输入参数,av_guess_format函数会尝试猜测并返回最有可能的格式。

注意:在使用av_guess_format函数时,应确保FFmpeg库已正确初始化,并且所需的输入参数有效且符合可接受的格式。

outFmt = av_guess_format(nullptr, dst, nullptr);
if (outFmt == nullptr) {av_log(nullptr, AV_LOG_ERROR, "Failed to guess output format\n");goto _ERROR;
}
oFormatCtx->oformat = outFmt;

创建视频流并设置参数

为输出文件创建视频流,并复制输入视频流的参数。

outStream = avformat_new_stream(oFormatCtx, nullptr);
avcodec_parameters_copy(outStream->codecpar, pFormatCtx->streams[ret]->codecpar);
outStream->codecpar->codec_tag = 0;

打开输出文件并写入头信息

使用avio_open2打开输出文件,并使用avformat_write_header写入文件头信息。
avio_open2 是用于打开一个输入输出数据流的函数,它的输入参数包括:

  • AVIOContext **s:指向用于输入输出的 AVIOContext 结构体的指针。
  • const char *filename:输入输出的文件名或者URL地址,使用 “r” 模式打开用于输入,使用 “w” 模式打开用于输出。
  • int flags:打开文件的参数,可以是 AVIO_FLAG_READ 表示输入流、AVIO_FLAG_WRITE 表示输出流、AVIO_FLAG_READ_WRITE 表示输入输出流。
  • const AVIOInterruptCB *int_cb:输入输出中断回调函数的指针。
  • AVDictionary **options:用于传递其他选项的字典指针。可以设置 NULL 以忽略其他选项。

avformat_write_header 是用于写入文件头信息的函数,它的输入参数包括:

  • AVFormatContext *s:指向 AVFormatContext 结构体的指针,表示待写入文件的格式上下文。
  • AVDictionary **options:用于传递其他选项的字典指针。可以设置 NULL 以忽略其他选项。

这些函数用于打开输入输出数据流和写入文件头信息时,需要通过传入一些参数来配置打开的流和文件的属性和选项。avio_open2 是用于打开一个输入输出数据流的函数,它的输入参数包括:

ret = avio_open2(&oFormatCtx->pb, dst, AVIO_FLAG_WRITE, nullptr, nullptr);
if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "Failed to open output file: %s\n", dst);goto _ERROR;
}
ret = avformat_write_header(oFormatCtx, nullptr);
if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "Failed to write output file header\n");goto _ERROR;
}

读取、转换并写入帧数据

读取输入文件的视频帧,转换时间戳,并使用av_interleaved_write_frame写入输出文件。
av_interleaved_write_frame函数是FFmpeg库中的一个函数,用于将音视频数据包写入容器文件。

函数的参数如下:

  1. AVFormatContext *s: AVFormatContext结构体指针,表示容器格式上下文,用于管理容器格式相关的信息。

  2. AVPacket *pkt: AVPacket结构体指针,表示音视频数据包,包含音视频数据以及相关的参数信息。

函数的返回值为整形,表示函数的执行结果。如果返回值为0,则表示成功写入数据包;如果返回值为负值,则表示写入失败。

注意:在调用av_interleaved_write_frame函数之前,需要先调用avformat_write_header函数来写入容器文件的头部信息;在所有音视频数据包写入完成后,还需要调用av_write_trailer函数来写入容器文件的尾部信息。

此外,av_interleaved_write_frame函数只能用于写入已经交错存储的音视频数据包,如果数据包未经过交错处理,则需要使用av_write_frame函数来分别写入音频数据包和视频数据包。av_interleaved_write_frame函数是FFmpeg库中的一个函数,用于将音视频数据包写入容器文件。

while (av_read_frame(pFormatCtx, &pkt) >= 0) {if (pkt.stream_index == ret) {// 转换时间戳等pkt.pts = av_rescale_q_rnd(pkt.pts, inSteam->time_base, outStream->time_base, AV_ROUND_NEAR_INF);pkt.dts = av_rescale_q_rnd(pkt.dts, inSteam->time_base, outStream->time_base, AV_ROUND_NEAR_INF);pkt.duration = av_rescale_q(pkt.duration, inSteam->time_base, outStream->time_base);// 写入帧数据av_interleaved_write_frame(oFormatCtx, &pkt);}av_packet_unref(&pkt);
}

写入尾信息并释放资源

使用av_write_trailer写入文件尾信息,并释放所有资源。
函数av_write_trailer的参数是AVFormatContext。

AVFormatContext是一个存储了整个音视频文件信息的结构体,包含了音视频流、容器格式信息以及封装器相关的信息。

av_write_trailer函数的功能是将容器头信息写入到输出文件中,并完成文件封装的最后步骤。具体而言,它会将封装器中剩余的音视频数据写入到输出文件中,并更新容器头的相关信息,如文件大小、时长等。

在调用av_write_trailer函数之前,需要保证所有的音视频数据已经被写入到容器中,否则可能会导致文件不完整或者损坏。

调用av_write_trailer函数之后,应释放AVFormatContext相关的资源,包括关闭文件和释放相关的内存。

示例代码如下:

AVFormatContext* outputContext = nullptr;
// 初始化outputContext的代码
// ...
// 写入所有音视频数据到容器
// ...
av_write_trailer(outputContext);
// 释放相关资源
avformat_close_input(&outputContext);
avformat_free_context(outputContext);

函数av_write_trailer的参数是AVFormatContext。

AVFormatContext是一个存储了整个音视频文件信息的结构体,包含了音视频流、容器格式信息以及封装器相关的信息。

av_write_trailer函数的功能是将容器头信息写入到输出文件中,并完成文件封装的最后步骤。具体而言,它会将封装器中剩余的音视频数据写入到输出文件中,并更新容器头的相关信息,如文件大小、时长等。

在调用av_write_trailer函数之前,需要保证所有的音视频数据已经被写入到容器中,否则可能会导致文件不完整或者损坏。

调用av_write_trailer函数之后,应释放AVFormatContext相关的资源,包括关闭文件和释放相关的内存。

示例代码如下:

AVFormatContext* outputContext = nullptr;
// 初始化outputContext的代码
// ...
// 写入所有音视频数据到容器
// ...
av_write_trailer(outputContext);
// 释放相关资源
avformat_close_input(&outputContext);
avformat_free_context(outputContext);
av_write_trailer(oFormatCtx);_ERROR:
// 清理资源
if (oFormatCtx && oFormatCtx->pb) {avio_close(oFormatCtx->pb);oFormatCtx->pb = nullptr;
}
if (oFormatCtx) {avformat_free_context(oFormatCtx);oFormatCtx = nullptr;
}
if (pFormatCtx) {avformat_free_context(pFormatCtx);pFormatCtx = nullptr;
}

运行程序

程序需要传入两个参数:输入文件路径和输出文件路径。例如:

./my_ffmpeg_tool input.mp4 output.mkv

确保替换为您的实际文件名和所需的输出格式。

注意事项

  • 确保FFmpeg开发库已正确安装且可链接。
  • 检查程序输出的错误信息以进行调试。
  • 程序可能需要适当的读取和写入权限。

源代码

  • cmake 源文件
cmake_minimum_required(VERSION 3.27)
project(FFmpeg_exercise)
set(CMAKE_CXX_STANDARD 14)# 定义FFmpeg的安装路径变量
set(FFMPEG_INSTALL_DIR "/usr/local/ffmpeg")# 将FFmpeg的头文件目录添加到包含路径
include_directories(${FFMPEG_INSTALL_DIR}/include)# 定义FFmpeg库的基础名称(根据你的需要调整)
set(FFMPEG_LIBS "avcodec;avformat;avutil") # 用分号分隔库名# 寻找并链接FFmpeg库
foreach(FFMPEG_LIB ${FFMPEG_LIBS})find_library(${FFMPEG_LIB}_LIBRARY NAMES ${FFMPEG_LIB}PATHS ${FFMPEG_INSTALL_DIR}/lib NO_DEFAULT_PATH)list(APPEND FFMPEG_LIBRARIES ${${FFMPEG_LIB}_LIBRARY})
endforeach()add_executable(FFmpeg_exercise# main.cpp# extra_audic.cppextra_video.cpp)
# 链接FFmpeg库
target_link_libraries(FFmpeg_exercise ${FFMPEG_LIBRARIES})
  • cpp
//
// Created by 陈伟峰 on 2024/6/22.
//
#ifdef __cplusplus
extern "C" {
#endif
// 包含FFmpeg的头文件
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/log.h>
#ifdef __cplusplus}
#endif
#include <iostream>int main(int argc,char *argv[]){int ret {-1};int idx {-1};//1.处理一些参数char *src {nullptr};char *dst {nullptr};AVFormatContext *pFormatCtx {nullptr};AVFormatContext *oFormatCtx {nullptr};AVOutputFormat *outFmt {nullptr};AVStream *outStream {nullptr};AVStream *inSteam {nullptr};AVPacket pkt {nullptr};// 日志信息av_log_set_level(AV_LOG_DEBUG);if(argc<3){av_log(nullptr,AV_LOG_ERROR,"Usage:%s <input file> <output file>\n",argv[0]);exit(-1);}src = argv[1];dst = argv[2];// 2.打开多媒体输入文件ret = avformat_open_input(&pFormatCtx,src,nullptr,nullptr);if(ret<0){av_log(nullptr,AV_LOG_ERROR,"Could not open input file:%s\n",src);exit(-1);}// 3.获取多媒体文件信息ret = av_find_best_stream(pFormatCtx,AVMEDIA_TYPE_VIDEO,-1,-1,nullptr,0);if(ret<0){av_log(nullptr,AV_LOG_ERROR,"Failed to retrieve input stream information\n");goto _ERROR;}// 打开目的多媒体文件oFormatCtx = avformat_alloc_context();if(oFormatCtx==nullptr){av_log(nullptr,AV_LOG_ERROR,"Failed to allocate output context\n");goto _ERROR;}outFmt = av_guess_format(nullptr,dst,nullptr);if(outFmt==nullptr){av_log(nullptr,AV_LOG_ERROR,"Failed to guess output format\n");goto _ERROR;}oFormatCtx->oformat = outFmt;// 为目的文件,创建一个新的视频流outStream = avformat_new_stream(oFormatCtx,nullptr);// 设置视频参数inSteam = pFormatCtx->streams[idx];avcodec_parameters_copy(outStream->codecpar,pFormatCtx->streams[ret]->codecpar);outStream->codecpar->codec_tag = 0;// 绑定ret = avio_open2(&oFormatCtx->pb,dst,AVIO_FLAG_WRITE, nullptr, nullptr);if(ret<0){av_log(nullptr,AV_LOG_ERROR,"Failed to open output file:%s\n",dst);goto _ERROR;}// 写入头信息ret = avformat_write_header(oFormatCtx,nullptr);if(ret<0){av_log(nullptr,AV_LOG_ERROR,"Failed to write output file header\n");goto _ERROR;}// 写多媒体文件到目的文件ret = avformat_write_header(oFormatCtx,nullptr);if(ret<0){av_log(nullptr,AV_LOG_ERROR,"Failed to write output file header:%s\n", av_err2str(ret));goto _ERROR;}while(av_read_frame(pFormatCtx,&pkt)>=0) {if (pkt.stream_index == idx) {pkt.pts = av_rescale_q_rnd(pkt.pts, inSteam->time_base, outStream->time_base,(AVRounding) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));pkt.dts = av_rescale_q_rnd(pkt.dts, inSteam->time_base, outStream->time_base,(AVRounding) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));pkt.duration = av_rescale_q(pkt.duration, inSteam->time_base, outStream->time_base);pkt.stream_index = 0;pkt.pos = -1;av_interleaved_write_frame(oFormatCtx, &pkt);}av_packet_unref(&pkt);}// 写入尾信息av_write_trailer(oFormatCtx);_ERROR:if(oFormatCtx->pb){avio_close(oFormatCtx->pb);oFormatCtx->pb = nullptr;}if(oFormatCtx){avformat_free_context(oFormatCtx);oFormatCtx = nullptr;}if(pFormatCtx){avformat_free_context(pFormatCtx);pFormatCtx = nullptr;}return 0;
}
  • 执行
./main demo.mp4 demo2.h264
  • 运行
ffplay demo2.h264

image-20240622143747704

http://www.lryc.cn/news/433012.html

相关文章:

  • Node.js之文件夹的操作
  • 线程的四种操作
  • 自我指导:提升语言模型自我生成指令的能力
  • 使用Node.js实现单文件上传功能—含代码解释
  • 【机器人工具箱Robotics Toolbox开发笔记(一)】Matlab机器人工具箱简介
  • 基于 Metropolis 的朗之万算法
  • SAM2POINT:以zero-shot且快速的方式将任何 3D 视频分割为视频
  • 深入理解FastAPI的response_model:自动化数据验证与文档生成
  • 【数据结构与算法 | 灵神题单 | 删除链表篇】力扣3217, 82, 237
  • 快速失败 (fail-fast) 和安全失败 (fail-safe)
  • 【MySQL】MySQL中表的增删改查——(基础篇)(超详解)
  • 【B题第二套完整论文已出】2024数模国赛B题第二套完整论文+可运行代码参考(无偿分享)
  • 大数据之Flink(四)
  • 《Web性能权威指南》-网络技术概览-读书笔记
  • 最新版php进销存系统源码 ERP进销存专业化管理 永久免费升级更新+完整图文搭建教程
  • 【高效办公】三、两台电脑共享鼠标、键盘和文件,两台电脑当一个用的神操作!barrier
  • 智能合约系统DAPP开发
  • 宠物狗检测-目标检测数据集(包括VOC格式、YOLO格式)
  • 2.5多任务示例编程2
  • JavaWeb - 4 - Vue Ajax
  • 深入掌握Go语言中的正则表达式与字符串处理
  • Docker进入容器运行命令
  • [数据集][目标检测]机油泄漏检测数据集VOC+YOLO格式43张1类别
  • Python实现读取Excel数据详细教学版
  • 【HarmonyOS】- 内存优化
  • 【生日视频制作】保时捷车主提车交车仪式感AE模板修改文字软件生成器教程特效素材【AE模板】
  • 【自用14】C++俄罗斯方块-思路复盘3
  • ElasticSearch的DSL查询⑤(ES数据聚合、DSL语法数据聚合、RestClient数据聚合)
  • DBeaver 24.0 高阶用法
  • 外卖会员卡项目骗局揭秘,你还在做梦吗?改醒醒了