前言
书接上回,我们比较详细的介绍了ffmpeg开发过程中会接触到的主要结构体,当然,其实还有AVFilter模块,但是对于初学者而言,忽略掉过滤器部分也无伤大雅,并不影响对于ffmpeg开发流程的主体的学习,而且AVFilter也不算是特别常用,在音视频开发中也有其他方式可以实现AVFilter的效果,因此暂时可以先忽略。
本文我们用一段相对完整,但是不算复杂的ffmpeg程序来实现我们上文提到的那些知识。
环境准备
在进行ffmpeg开发之前,一般建议大家自行获得ffmpeg源码,手动编译获得相应的动态库(dll/so),然后再正式进行c/c++开发工作。
ffmpeg可以在windows,linux系统上开发,一般推荐linux上来开发(本人用的linux环境,但也有windows环境),因为windows其实也是模拟了一些linux的环境的。
windows环境安装与编译
windows环境下主要参考这篇文章ffmpeg库编译安装及入门指南。
注意以下几点:
- 博文中作者的建议安装选项大家都尽可能安装上。
- ffmpeg源码尽可能下载最新版本。
- 编译ffmpeg库的build-ffmpeg.sh脚本替换如下
1 | shell复制代码#!/bin/sh |
主要是添加一些可debug配置,为后面调试做准备
linux环境安装与编译
在linux中就不需要安装MSYS2了,而缺的编译工具什么的按照提示使用linux的软件包管理管理工具(比如apt等)安装即可。
然后下载最新源码,libx264源码,编译过程仍然可以使用或者 ffmpeg库编译安装及入门指南中提供的编译脚本。
注意build-ffmpeg.sh脚本同样需要替换一下脚本:
1 | shell复制代码#!/bin/sh |
去掉了–prefix=xxx配置,把ffmpeg生成物推送到系统默认的环境变量的路径中,免得还需要自行配置,后面就可以直接调用和使用依赖库了。如果想自定义产物生成目录也可直接参考windows的脚本。
代码编辑器
代码编辑器可以使用 Visual Studio Code,Clion,或者其他趁手的都行。
生成产物与开发使用
编译成功之后,不仅有ffmpeg依赖库(lib文件夹)和头文件(include文件夹),还有ffmpeg,ffprobe,ffplayer这样的可执行程序,可以直接在命令行中进行调用。
在后面的开发过程中,我们至少会用到头文件和依赖库。
对于windows环境而言,为了简单起见,每新建一个工程,可以把ffmpeg生成的头文件都添加进来,然后按需调(虽然有些不环保)。
windows环境在编译时需要指定链接库,还是可以参考 ffmpeg库编译安装及入门指南;linux如果编译产物在系统默认目录中的话则不需要。
ffmpeg开发
环境安装完毕之后,正式进入正题。
我们要开发的程序的功能是,读取一个视频文件,解码音频和视频部分,并且把解码后的视频中的一帧或者几帧图保存成ppm格式。
这里主要包含到ffmpeg的解封装,解码,色彩空间转换的过程,以及对解码数据的认识。
至于ppm,它是一个未压缩的RGB图片的格式(jpg就是压缩后的图片格式),文件在操作系统中可以正常打开查看,这不是本文的重点。
函数入口
接下来我们直接看代码
1 | cpp复制代码#include <cstdio> |
上面是程序的变量和入口函数,也就是整个程序的主框架了。
从上面的注释可以比较通畅的了解程序的执行过程。从中也能找到前一篇文章中提到的许多代码片段,这里其实算是做了一个整合。
音视频配置初始化
接下来我们看看initVideo和initAudio,其实两者基本是一致的,理论上可以合并成一个函数。
1 | ini复制代码 |
根据上面的代码和注释,也能发现,关于AVStream,AVCodec,AVCodecContext的使用基本都符合前一篇文章中对于对应结构体的基本使用说明。当然这个过程中是有许多详细的参数是可以设置的,也可以把他们变得复杂一点,但是目前这不是重点。
手动配置AVFrame->data
接下来我们看看initRGBFrame的逻辑。
1 | arduino复制代码int initRGBFrame(){ |
手动创建并填充AVFrame的过程,需要首先创建AVFrame的结构体,然后申请填充 rgbFrame->data和rgbFrame->linesize这两个字段,前一篇文章中说到过,不是编解码过程中使用AVFrame需要我们手动申请这块内存。具体可以看FFmpeg开发——基础篇————AVFrame。
现在我们准备先把视频解码成YUV帧,然后把YUV帧通过libswscale转换成RGB帧。解码过程中使用AVFrame是不需要我们手动申请或填充data等字段的,但是scale转换过程自然就需要了。
解码与转换
做完了上述的准备之后,可以正式开始进行解码操作了:从数据流中读取数据到AVPacket中,然后把AVPakcet中的数据发送给解码器,接着从解码器中读取数据到AVFrame中,就获得了一个解码后的帧。
1 | scss复制代码int decodeData(AVPacket *av_packet,AVCodecContext *av_codec_ctx,int is_video) { |
这里主要涉及到两个点,解码过程和转换过程。
解码过程的API调用比较简单,也可以看AVFrame之编解码使用方式。
转换过程本质上是YUV2RGB的算法以及数据存储方式,关于前者其实在移动开发中关于视频的一些基本概念——YUV与RGB的转换介绍了相关转换原理;而数据存储方式则在文章FFmpeg开发——基础篇(一)之 AVFrame的data与linesize中有介绍到Planar和packed两种存储放在在AVFrame->data中的表现形式。了解不同的存储方式在ffmpeg中的表现形式我们才能正确的保存数据。
保存文件
然后我们最后看看数据保存过程
1 | arduino复制代码void saveRGBImage(int index){ |
ppm格式的详细信息见PPM文件格式详解
log打印的函数
1 | c复制代码char* print_log(const char *tag,int ret){ |
总结
把上述代码合并之后,就是这个程序的完整代码。
我们可以从一个视频文件中读取数据,解码,然后获取其中第一帧YUV帧,转换为RGB帧,最后把RGB帧保存为一张未压缩的图片文件。
虽然我们对音频的解码做了初始化准备配置,本来想做些其他功能,后来感觉有点多余,demo中处理视频就行了,它和video的解码过程是一致的。
本文转载自: 掘金