FFmpeg API的简单实践应用

利用 FFmpeg 编译链接生成的可执行程序本身可以实现很多特定的功能,但如果我们有自己的个性化需求,想要在自己开发的项目中使用 ffmpeg 的一些功能,就需要理解并应用其已经实现好的API,以写代码的方式调用这些API来完成对媒体文件的操作。

既然是调用 FFmpeg 中实现的API,就是将其作为我们的库来使用,首先需要将 FFmpeg 安装到指定路径。具体安装步骤可以参考我之前的博客 FFMPEG编译问题记录 或者参考官方的编译指南 FFmpeg Compilation Guide。

1. CMake 编译文件配置

主要配置 FFmpeg 安装路径,包括头文件路径和链接库路径。其余的就是非常简单的项目编译配置,如编译方式、项目名称等。

CMAKE_MINIMUM_REQUIRED(VERSION 3.0) PROJECT(MYPLAYER_TEST) SET(CMAKE_BUILD_TYPE RELEASE) SET(CMAKE_CXX_STANDARD 11) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") SET(FFmpeg_DIR "/Users/phillee/ffmpeg") INCLUDE_DIRECTORIES(${FFmpeg_DIR}/ffmpeg-bin/include) LINK_DIRECTORIES(${FFmpeg_DIR}/ffmpeg-bin/lib) # Messages to show for the user MESSAGE(STATUS "** Customized settings are shown as below **") MESSAGE(STATUS "\tCMAKE BUILD TYPE: ${CMAKE_BUILD_TYPE}") MESSAGE(STATUS "\tFFmpeg include directory: ${FFmpeg_DIR}/ffmpeg-bin/include") MESSAGE(STATUS "\tFFmpeg library directory: ${FFmpeg_DIR}/ffmpeg-bin/lib") ADD_EXECUTABLE(myplayer_test main.cc) TARGET_LINK_LIBRARIES(myplayer_test avcodec avformat avutil postproc swresample swscale ) 2. 包含头文件

由于 FFmpeg 是用C99标准写成的,有些功能在 C++ 中可能无法直接编译或者使用。

不过多数情况下,在 C++ 中包含 FFmpeg 头文件还是相当直接的。

首先,显示声明头文件为 C 格式文件

extern "C" { #include <libavutil/imgutils.h> #include <libavcodec/avcodec.h> #include <libswscale/swscale.h> }

另外,如果编译时出现类似 UINT64_C was not declared in this scope 的报错,可以尝试在 CXXFLAGS 标志位中添加 -D__STDC_CONSTANT_MACROS 。

3. 简单调用API实践

第一部分是按照步骤2中的格式包含头文件,这里添加了编译时的平台限制。

#include <cstdio> #include <inttypes.h> #ifdef __APPLE__ extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "libswresample/swresample.h" #include "libavutil/pixdesc.h" } #endif

第二部分是关于从媒体流的原始数据获取数据帧和将数据帧保存到 PGM 格式文件的两个封装函数的声明,其实现放在了 main 函数后面。

static int decode_packet(AVPacket *pPacket, AVCodecContext *pCodecContext, AVFrame *pFrame); void write_to_pgm(const char* file_path, AVFrame* decoded_frame); static int decode_packet(AVPacket *pPacket, AVCodecContext *pCodecContext, AVFrame *pFrame) { int ret = avcodec_send_packet(pCodecContext, pPacket); if (ret < 0) { printf("Error while sending a packet to the decoder: %s", av_err2str(ret)); return ret; } while (ret >= 0) { ret = avcodec_receive_frame(pCodecContext, pFrame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { printf("Error while receiving a frame from the decoder: %s", av_err2str(ret)); return ret; } else if (ret >= 0) { printf("Frame %d: type=%c, size=%d bytes, format=%d, pts=%d key_frame %d [DTS %d]\n", pCodecContext->frame_number, av_get_picture_type_char(pFrame->pict_type), pFrame->pkt_size, pFrame->format, pFrame->pts, pFrame->key_frame, pFrame->coded_picture_number); char file_to_write[100]; sprintf(file_to_write, "/Users/gcxyb/phillee/misc_codes/ffmpeg_based_test/build/frame-%02d.pgm", pCodecContext->frame_number); if (pFrame->format==AV_PIX_FMT_YUV420P) { write_to_pgm(file_to_write, pFrame) } } } } void write_to_pgm(const char* file_path, AVFrame* decoded_frame) { FILE *fout = fopen(file_path, "w"); fprintf(fout, "P5\n%d %d\n%d\n", decoded_frame->width, decoded_frame->height, 255); for (int line_id=0; line_id<decoded_frame->height; ++line_id) { int ret = fwrite(decoded_frame->data[0]+line_id*decoded_frame->linesize[0], 1, decoded_frame->width, fout); if (ret < 0) exit(1); } fclose(fout); }

avcodec_send_packet 函数将原始的 AVPacket 数据包送到解码器,然后调用 avcodec_receive_frame 函数从解码器解码出 AVFrame 的帧数据信息。得到帧数据之后可以进行其他操作。

PGM 的格式非常简单,只有 ASCII 码形式的几个头部标示信息和二进制的数据,所以直接写入就可以了。

第三部分是主要流程和相应 API 的调用。

(a) 首先是 FFmpeg 的注册协议

需要用到网络的操作时应该将网络协议部分注册到 FFmpeg 框架,以便后续再去查找对应的格式。其实这里的 av_register_all 在新近的 FFmpeg 版本中被标注为 attribute_deprecated ,在后面的测试中我把该语句注释掉好像也能正常工作,如果是面向最新版本的应用应该是可以不用加了。

avformat_network_init(); av_register_all();

网络协议部分的注册是可选项,但官方建议是最好加上,防止中间出现隐式设置的开销(翻译不到位,建议看下面的原文理解)。

Do global initialization of network components. This is optional, but recommended, since it avoids the overhead of implicitly doing the setup for each session.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygfjp.html