FFmpeg API的简单实践应用
0. 前言
利用 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.
(b) 接着打开媒体流文件并读取基本信息
AVFormatContext *formatCtx = avformat_alloc_context();
AVCodec *pCodec = NULL;
AVCodecParameters *pCodecParam = NULL;
int video_stream_index = -1;
if (avformat_open_input(&formatCtx, argv[1], NULL, NULL) < 0) {
printf("Error: cannot open the input file!\n");
exit(1);
}
if (avformat_find_stream_info(formatCtx, NULL) < 0) {
printf("Error: fail to find stream info!\n");
exit(1);
}
函数 avformat_open_input 会根据提供的文件路径判断文件格式,然后决定使用什么样的解封装器。
而 avformat_find_stream_info 方法的作用就是把所有 Stream 流的 MetaData 信息填充好。方法内部会先查找对应的解码器,打开解码器,利用 Demuxer 中的 read_packet 函数读取一段数据进行解码,解码的信息越多分析出的流信息就越准确。
这一段代码之后的 for 循环主要是将分析的结果输出到屏幕。
(c) 解析帧数据信息
AVCodecContext *pCodecContext = avcodec_alloc_context3(pCodec);
if(avcodec_parameters_to_context(pCodecContext, pCodecParam) < 0) {
printf("Failed to fill the codec context!\n");
exit(1);
}
if(avcodec_open2(pCodecContext, pCodec, NULL) < 0) {
printf("Failed to initialize the avcodec context!\n");
exit(1);
}
AVFrame *pFrame = av_frame_alloc();
AVPacket *pPacket = av_packet_alloc();
int packet_cnt = 10;
while(av_read_frame(formatCtx, pPacket)>=0) {
if (pPacket->stream_index == video_stream_index) {
printf("AVPacket pts: %" PRId64 "\n", pPacket->pts);
if (decode_packet(pPacket, pCodecContext, pFrame)<0)
break;
if (--packet_cnt<=0)
break;
}
av_packet_unref(pPacket);
}
函数 avcodec_open2 用来打开编解码器,无论是编码过程还是解码过程都会用到。输入参数有三个,第一个是 AVCodecContext,解码过程由 FFmpeg 引擎填充。第二个参数是解码器,第三个参数一般会传递 NULL。
使用 av_read_frame 读取出来的数据是 AVPacket,在早期版本中开放给开发者的是 av_read_packet 函数,但需要开发者自己来处理 AVPacket 中的数据不能被解码器完全处理完的情况,即需要把未处理完的压缩数据缓存起来的问题,所以现在提供了该函数。对于视频流,一个AVPacket只包含一个AVFrame,最终将得到一个 AVPacket 的结构体。
main.cc 全部代码如下
#include <cstdio> // fopen, fclose, fwrite
#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
static int decode_packet(AVPacket *pPacket, AVCodecContext *pCodecContext, AVFrame *pFrame);
void write_to_pgm(const char* file_path, AVFrame* decoded_frame);
int main(const int argc, char* argv[])
{
avformat_network_init();
av_register_all();
if (argc < 2) {
printf("Please set the file path to open...\n");
exit(1);
}
printf("Open file %s...\n", argv[1]);
AVFormatContext *formatCtx = avformat_alloc_context();
AVCodec *pCodec = NULL;
AVCodecParameters *pCodecParam = NULL;
int video_stream_index = -1;
if (avformat_open_input(&formatCtx, argv[1], NULL, NULL) < 0) {
printf("Error: cannot open the input file!\n");
exit(1);
}
if (avformat_find_stream_info(formatCtx, NULL) < 0) {
printf("Error: fail to find stream info!\n");
exit(1);
}
printf("There are %d streams in the given file\n", formatCtx->nb_streams);
for (int i = 0; i < formatCtx->nb_streams; ++i) {
AVStream *stream = formatCtx->streams[i];
AVCodecParameters *pLocalCodecParam = stream->codecpar;
AVCodec *pLocalCodec = avcodec_find_decoder(pLocalCodecParam->codec_id);
if (NULL==pLocalCodec) {
printf("Error: unsupported codec found!\n");
continue;
}
if (pLocalCodecParam->codec_type == AVMEDIA_TYPE_VIDEO) {
if (-1==video_stream_index) {
video_stream_index = i;
pCodec = pLocalCodec;
pCodecParam = pLocalCodecParam;
}
printf("Info of video stream:\n");
printf("\tCodec %s ID %d bit_rate %lld kb/s\n", pLocalCodec->name, pLocalCodec->id, pLocalCodecParam->bit_rate/1024);
printf("\tAVStream->r_frame_rate: %d/%d\n", stream->r_frame_rate.num, stream->r_frame_rate.den);
printf("\tResolution=%dx%d\n", pLocalCodecParam->width, pLocalCodecParam->height);
} else if (pLocalCodecParam->codec_type == AVMEDIA_TYPE_AUDIO) {
printf("Info of audio stream:\n");
printf("\tCodec %s ID %d bit_rate %lld kb/s\n", pLocalCodec->name, pLocalCodec->id, pLocalCodecParam->bit_rate/1024);
printf("\tchannels=%d\n\tsample_rate=%d\n", pLocalCodecParam->channels, pLocalCodecParam->sample_rate);
}
printf("\tAVStream->time_base: %d/%d\n", stream->time_base.num, stream->time_base.den);
printf("\tAVStream->start_time: %" PRId64 "\n", stream->start_time);
printf("\tAVStream->duration: %" PRId64 "\n", stream->duration);
}
AVCodecContext *pCodecContext = avcodec_alloc_context3(pCodec);
if(avcodec_parameters_to_context(pCodecContext, pCodecParam) < 0) {
printf("Failed to fill the codec context!\n");
exit(1);
}
if(avcodec_open2(pCodecContext, pCodec, NULL) < 0) {
printf("Failed to initialize the avcodec context!\n");
exit(1);
}
AVFrame *pFrame = av_frame_alloc();
AVPacket *pPacket = av_packet_alloc();
int packet_cnt = 10;
while(av_read_frame(formatCtx, pPacket)>=0) {
if (pPacket->stream_index == video_stream_index) {
printf("AVPacket pts: %" PRId64 "\n", pPacket->pts);
if (decode_packet(pPacket, pCodecContext, pFrame)<0)
break;
if (--packet_cnt<=0)
break;
}
av_packet_unref(pPacket);
}
avformat_close_input(&formatCtx);
av_packet_free(&pPacket);
av_frame_free(&pFrame);
avcodec_free_context(&pCodecContext);
return 0;
}
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);
}
文件夹结构
.
├── CMakeLists.txt
└── main.cc
编译测试
$ mkdir build
$ cd build
$ cmake ..
$ make
$ ./myplayer_test /path/to/the/media/file
(全文完)
参考资料
[1] FFMPEG编译问题记录 https://www.cnblogs.com/phillee/p/13813156.html
[2] FFmpeg Compilation Guide https://trac.ffmpeg.org/wiki/CompilationGuide
[3] pgm http://netpbm.sourceforge.net/doc/pgm.html
[4] PGM example https://en.wikipedia.org/wiki/Netpbm#PGM_example
本文作者 :phillee
发表日期 :2021年4月1日
本文链接 :https://www.cnblogs.com/phillee/p/14605815.html
版权声明 :自由转载-非商用-非衍生-保持署名(创意共享3.0许可协议/CC BY-NC-SA 3.0)。转载请注明出处!
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
.outter_box { text-align: center }
.button { background-color: rgba(83, 168, 73, 1); border: none; border-radius: 6px; color: rgba(255, 255, 255, 1); padding: 9px 24px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px }
.img_box { border: none; color: rgba(255, 255, 255, 1); text-align: center; vertical-align: middle }
.demo { width: 208px; height: 260px; margin: 0 auto }
.demo img { -webkit-filter: drop-shadow(2px 2px 5px rgba(0,0,0,.5)); filter: drop-shadow(2px 2px 5px rgba(0,0,0,.5)) }
感谢您的支持

微信支付
FFmpeg API的简单实践应用的更多相关文章
- RESTful API 设计最佳实践
背景 目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个"万能"的设计标准:如何鉴权?API ...
- 学习FFmpeg API
ffmpeg是编解码的利器,用了很久,以前看过dranger 的教程,非常精彩,受益颇多,是学习ffmpeg api很好的材料.可惜的是其针对的ffmpeg版本已经比较老了,而ffmpeg的更新又很快 ...
- ****RESTful API 设计最佳实践(APP后端API设计参考典范)
http://blog.jobbole.com/41233/ 背景 目前互联网上充斥着大量的关于RESTful API(为方便,下文中“RESTful API ”简写为“API”)如何设计的文章,然而 ...
- RESTful API 设计最佳实践(转)
摘要:目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个”万能“的设计标准:如何鉴权?API格式如何?你的API ...
- RESTful API 设计最佳实践(转)
背景 目前互联网上充斥着大量的关于RESTful API(为方便,下文中“RESTful API ”简写为“API”)如何设计的文章,然而却没有一个”万能“的设计标准:如何鉴权?API 格式如何?你的 ...
- Android 设计随便说说之简单实践(合理组合)
上一篇(Android 设计随便说说之简单实践(模块划分))例举了应用商店设计来说明怎么做模块划分.模块划分主要依赖于第一是业务需求,具体是怎么样的业务.应用商店则包括两个业务,就是向用户展示appl ...
- FFmpeg资料来源简单分析:libswscale的sws_getContext()
===================================================== FFmpeg库函数的源代码的分析文章: [骨架] FFmpeg源码结构图 - 解码 FFmp ...
- 【转】学习FFmpeg API – 解码视频
ffmpeg是编解码的利器,用了很久,以前看过dranger 的教程,非常精彩,受益颇多,是学习ffmpeg api很好的材料.可惜的是其针对的ffmpeg版本已经比较老了,而ffmpeg的更新又很快 ...
- [转] 阿里研究员谷朴:API 设计最佳实践的思考
API是软件系统的核心,而软件系统的复杂度Complexity是大规模软件系统能否成功最重要的因素.但复杂度Complexity并非某一个单独的问题能完全败坏的,而是在系统设计尤其是API设计层面很多 ...
随机推荐
- Node.js require 模块加载原理 All In One
Node.js require 模块加载原理 All In One require 加载模块,搜索路径 "use strict"; /** * * @author xgqfrms ...
- holy shit CSDN
holy shit CSDN 垃圾 CSDN 到处都是垃圾文章, 无人子弟 到处都是垃圾广告,看的恶心 毫无底线,窃取别人的知识成果,毫无版权意识 垃圾爬虫,垃圾小号 ...等等 Google Sea ...
- Flutter FractionallySizedBox 设置维度比例 而不是固定的px
本周小部件 有时您的设计需要相对的维度. FractionallySizedBox允许您将子项的大小调整为总可用空间的一小部分. Scaffold( body: Center( child: Frac ...
- Flutter NotificationListener 监听列表的滚动
import 'package:flutter/material.dart'; import 'package:flutter_imagenetwork/flutter_imagenetwork.da ...
- 为什么Linux需要虚拟内存
本文转载自为什么 Linux 需要虚拟内存 导语 操作系统中的 CPU 和主内存(Main memory)都是稀缺资源,所有运行在当前操作系统的进程会共享系统中的 CPU 和内存资源,操作系统会使用 ...
- Redis 日志篇:系统高可用的杀手锏
特立独行是对的,融入圈子也是对的,重点是要想清楚自己向往怎样的生活,为此愿意付出怎样的代价. 我们通常将 Redis 作为缓存使用,提高读取响应性能,一旦 Redis 宕机,内存中的数据全部丢失,假如 ...
- 【转载】Win10彻底格式化磁盘防止数据恢复的技巧
转载地址 注意 要尽量删除数据,请在运行cipher /w时关闭其他所有应用程序. 1.如果你在格式化磁盘后想要防止数据被恢复, Format 命令,而现在只需在其后添加 /P 参数,即可用随机数据覆 ...
- base64图片显示问题
1.问题描述 小程序项目需要后端接口提供base64流的图片,对于H5的语法,前面拼接后面的代码即可: data:image/png;base64, 先看后台代码: @RestController @ ...
- CentOS7安装ElasticSearch7.9.2
1:下载 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.9.2-linux-x86_64.tar. ...
- Go的结构体
目录 结构体 一.什么是结构体? 二.结构体的声明 三.创建结构体 1.创建有名结构体 2.结构体初始化 2.1 按位置传参 2.2 按关键字传 3.创建匿名结构体 四.结构体的类型 五.结构体的默认 ...