FFmpeg编程FFmpeg初级开发
Posted 贺二公子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FFmpeg编程FFmpeg初级开发相关的知识,希望对你有一定的参考价值。
原文地址:https://www.cnblogs.com/ssyfj/p/14579909.html
文章目录
- FFmpeg代码结构
- 一:日志系统的使用
- 二:文件的删除与重命名
- 三:目录操作
- 四:处理流数据的基本概念
- 五:打印音/视频Meta信息
- 六:FFmpeg抽取音频数据
- 七: FFmpeg转换H264数据视频,从MP4(AVCC)格式到(AnnexB实时流)
- 八:多媒体格式转换---将MP4转成FLV格式(数据与参数不变)
- 九:音视频裁剪
FFmpeg代码结构
一:日志系统的使用
日志级别:(依次降低)
- AV_LOG_ERROR
- AV_LOG_WARNING
- AV_LOG_INFO
- AV_LOG_DEBUG
(一)日志系统编程
#include <stdio.h>
#include <libavutil/log.h>
int main(int argc,char* argv[])
av_log_set_level(AV_LOG_DEBUG);
av_log(NULL,AV_LOG_INFO,"...Hello world:%s %s\\n",argv[0],argv[1]);
return 0;
编译.c文件:
gcc 01log.c -o 01log -lavutil
运行结果:
(二)回顾gcc编译如何寻找头文件、库文件(gcc -I -L -l区别)
我们用gcc编译程序时,可能会用到 “-I”(大写i),“-L”(大写l),“-l”(小写l) 等参数,例:
gcc -o hello hello.c -I /home/hello/include -L /home/hello/lib -lworld
上面这句表示在编译hello.c时:
-I /home/hello/include : 表示将/home/hello/include目录作为第一个寻找头文件的目录,寻找的顺序是:/home/hello/include–>/usr/include–>/usr/local/include
-L /home/hello/lib : 表示将/home/hello/lib目录作为第一个寻找库文件的目录,寻找的顺序是:/home/hello/lib–>/lib–>/usr/lib–>/usr/local/lib
-lworld : 表示在上面的lib的路径中寻找libworld.so动态库文件或libworld.a静态库,同时存在时候动态库优先,
\\qquad\\qquad 如果要强制链接静态库可以用-static或直接用libword.a, gcc -o hello hello.c -I /home/hello/include -L /home/hello/lib /home/hello/lib/libworld.a
(三)linux中的动态库和静态库
1.概念和区别:
静态库就是在编译过程中一些目标文件的集合。静态库在程序链接的时候使用,链接器会将程序中使用到函数的代码从库文件中拷贝到应用程序中。一旦链接完成,在执行程序的时候就不需要静态库了。由于每个使用静态库的应用程序都需要拷贝所用函数的代码,所以静态链接的文件会比较大。
相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,而只是作些标记。然后在程序开始启动运行的时候,动态地加载所需模块,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。
2.命名:
静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称。
动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号,minor是副版本号。版本号也可以没有,一般都会建立个没有版本号的软连接文件链接到全名的库文件。
3.创建:
无论静态库还是动态库,创建都分为两步,第一步创建目标文件,第二步生产库。
1).静态库的创建:
gcc -c test.c -o test.o #生成编译文件
ar rcs libtest.a test.o #生成静态库
名字为libtest.a的静态库就生产了,其中选项:
r 表明将模块加入到静态库中;
c 表示创建静态库;
s 表示生产索引;
还有更多选项像增加、删除库中的目标文件,包括将静态库解包等可以通过man来获得。
2).动态库的创建:
gcc -fPIC -c test.c -o test.c #-fPIC 为了跨平台
gcc --share test.o -o libtest.so
4.使用:
编译链接目标程序的方法是一样的:
gcc main.c -L. -ltest -o main
-L. : 指定现在本目录下搜索库,如果没有,会到系统默认的目录下搜索,一般为/lib、/usr/lib下。
对于静态库,这个步骤之后就可以将libtest.a库删掉,因为它已经被编译进了目标程序,不再需要它了。
而对于动态库,libtest.so库只是在目标程序里做了标记,在运行程序时才会动态加载,那么从哪加载呢?
加载目录会由/etc/ld.so.conf来指定,一般默认是/lib、/usr/lib,所以要想让动态库顺利加载,你可以将库文件copy到上面的两个目录下,或者设置export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/XXX/YYY,后面为你自己动态库的目录,再或者修改/etc/ld.so.conf文件,把库所在的路径加到文件末尾,并执行ldconfig刷新。这样,加入的目录下的所有库文件都可见。
5.补充
另外还有个文件需要了解/etc/ld.so.cache,里面保存了常用的动态函数库,且会先把他们加载到内存中,因为内存的访问速度远远大于硬盘的访问速度,这样可以提高软件加载动态函数库的速度了。
最后提一点,当同一目录下既有动态库又有静态库,并且两个库的名字相同时,编译时会如何链接呢?
gcc编译时默认都是动态链接,如果要指定优先链接静态库,需要指定参数static。
6.使用案例:https://blog.csdn.net/ayz671101/article/details/101812040(重点)
(四)分析 gcc 01log.c -o 01log -lavutil
1.回顾安装FFmpeg时的配置:Fmpeg学习(一)FFmpeg安装与测试
因此,我们早就将FFmpeg动态库目录加入/etc/ld.so.conf文件中,因此-lavutil会先去FFmpeg库目录下查找
2.查看库目录
存在我们所需要的动态库文件,所以编译成功!!
二:文件的删除与重命名
(一)文件编程
#include <libavutil/log.h>
#include <libavformat/avformat.h>
int main(int argc,char* argv[])
int ret; //获取返回值状态
char* filename = "./2.txt";
av_log_set_level(AV_LOG_DEBUG); //设置日志级别
//1.移动文件测试
ret = avpriv_io_move("1.txt","3.txt");
if(ret<0)
av_log(NULL,AV_LOG_ERROR,"Failed to move\\n");
return -1;
av_log(NULL,AV_LOG_INFO,"Success to move\\n");
ret = avpriv_io_delete(filename);
if(ret<0)
av_log(NULL,AV_LOG_ERROR,"Failed to delete\\n");
return -1;
av_log(NULL,AV_LOG_INFO,"Success to delete %s\\n",filename);
return 0;
编译文件:
gcc -o fio ffmpeg_io.c -I /usr/local/ffmpeg/include -L /usr/local/ffmpeg/lib -lavutil -lavformat
注意:编译过程中我们可以不需要指定-I 但是我们必须指定-L (没有搞明白) ,虽然我们在去掉-L后也可以编译成功,但是运行会出现以下问题:
我们可以修改程序:添加av_register_all() 初始化libavformat并注册所有muxer、demuxer和协议(我们的File操作也在里面<可以自己查看源码,推荐3.0版本,太高太多东西看不到,太低和自己使用的方法不兼容)。如果不调用此函数,则可以选择希望支持的格式。
#include <libavutil/log.h>
#include <libavformat/avformat.h>
int main(int argc,char* argv[])
int ret; //获取返回值状态
char* filename = "./2.txt";
av_log_set_level(AV_LOG_DEBUG); //设置日志级别
av_register_all();
//1.移动文件测试
ret = avpriv_io_move("1.txt","3.txt");
if(ret<0)
av_log(NULL,AV_LOG_ERROR,"Failed to move\\n");
return -1;
av_log(NULL,AV_LOG_INFO,"Success to move\\n");
ret = avpriv_io_delete(filename);
if(ret<0)
av_log(NULL,AV_LOG_ERROR,"Failed to delete\\n");
return -1;
av_log(NULL,AV_LOG_INFO,"Success to delete %s\\n",filename);
return 0;
编译方案推荐第一种,虽然还没搞明白,但是兼容性看来好些,不容易出错!!!
三:目录操作
(一)重要结构体
AVIODirContext:操作目录的上下文,由avio_open_dir方法进行赋值
typedef struct URLContext
const AVClass *av_class; /**< information for av_log(). Set by url_open(). */
struct URLProtocol *prot;
void *priv_data;
char *filename; /**< specified URL */
int flags;
int max_packet_size; /**< if non zero, the stream is packetized with this max packet size */
int is_streamed; /**< true if streamed (no seek possible), default = false */
int is_connected;
AVIOInterruptCB interrupt_callback;
int64_t rw_timeout; /**< maximum time to wait for (network) read/write operation completion, in mcs */
const char *protocol_whitelist;
URLContext
typedef struct AVIODirContext
struct URLContext *url_context;
AVIODirContext;
int avio_open_dir(AVIODirContext **s, const char *url, AVDictionary **options)
URLContext *h = NULL;
AVIODirContext *ctx = NULL;
int ret;
av_assert0(s);
ctx = av_mallocz(sizeof(*ctx));
if (!ctx)
ret = AVERROR(ENOMEM);
goto fail;
if ((ret = ffurl_alloc(&h, url, AVIO_FLAG_READ, NULL)) < 0)
goto fail;
if (h->prot->url_open_dir && h->prot->url_read_dir && h->prot->url_close_dir)
if (options && h->prot->priv_data_class &&
(ret = av_opt_set_dict(h->priv_data, options)) < 0)
goto fail;
ret = h->prot->url_open_dir(h);
else
ret = AVERROR(ENOSYS);
if (ret < 0)
goto fail;
h->is_connected = 1;
ctx->url_context = h;
*s = ctx;
return 0;
fail:
av_free(ctx);
*s = NULL;
ffurl_close(h);
return ret;
AVIODirEntry:目录项。用于存放文件名、文件大小等信息
typedef struct AVIODirEntry
char *name; /**< Filename */
int type; /**< Type of the entry */
int utf8; /**< Set to 1 when name is encoded with UTF-8, 0 otherwise.
Name can be encoded with UTF-8 even though 0 is set. */
int64_t size; /**< File size in bytes, -1 if unknown. */
int64_t modification_timestamp; /**< Time of last modification in microseconds since unix
epoch, -1 if unknown. */
int64_t access_timestamp; /**< Time of last access in microseconds since unix epoch,
-1 if unknown. */
int64_t status_change_timestamp; /**< Time of last status change in microseconds since unix
epoch, -1 if unknown. */
int64_t user_id; /**< User ID of owner, -1 if unknown. */
int64_t group_id; /**< Group ID of owner, -1 if unknown. */
int64_t filemode; /**< Unix file mode, -1 if unknown. */
AVIODirEntry;
int avio_read_dir(AVIODirContext *s, AVIODirEntry **next)
URLContext *h;
int ret;
if (!s || !s->url_context)
return AVERROR(EINVAL);
h = s->url_context;
if ((ret = h->prot->url_read_dir(h, next)) < 0)
avio_free_directory_entry(next);
return ret;
(二)目录信息编程
#include <libavutil/log.h>
#include <libavformat/avformat.h>
int main(int argc,char* argv[])
int ret;
AVIODirContext* ctx=NULL; //操作目录的上下文,将由avio_open_dir赋值
AVIODirEntry* entry=NULL; //获取文件项的信息
av_log_set_level(AV_LOG_INFO);
//打开目录
ret = avio_open_dir(&ctx,"./",NULL);
if(ret<0)
av_log(NULL,AV_LOG_ERROR,"Can`t open dir:%s\\n",av_err2str(ret));
goto _fail;
//读取文件项
while(1)
ret = avio_read_dir(ctx,&entry);
if(ret<0)
av_log(NULL,AV_LOG_ERROR,"Can`t read dir:%s\\n",av_err2str(ret));
goto _fail;
if(!entry) break;
av_log(NULL,AV_LOG_INFO,"%12"PRId64" %s\\n",entry->size,entry->name); //PRId64表示打印64位数据,前面12是占位
avio_free_directory_entry(&entry); //释放使用过的空间
_fail:
avio_close_dir(&ctx);
return 0;
四:处理流数据的基本概念
(一)基本概念
多媒体文件(mp4、flv…)其实是个容器,在容器里有很多流(Stream/Track)(音频流、视频流、…没有交叉性<即便是多路音频、视频>),每种流是由不同的编码器编码的。
从流中读出的数据称为包(帧压缩后),在一个包中包含着一个或多个帧(未压缩)。
(二)几个重要的结构体
分别对应多媒体文件上下文、流、包
(三)ffmpeg 操作流数据的基本步骤
- 解复用:打开多媒体文件
- 获取文件中多路流中想要的
- 获取数据包,进行解码,对原始数据进行处理,变声、变速、滤波
- 释放相关资源
五:打印音/视频Meta信息
- av_register_all() : 初始化libavformat并注册所有muxer、demuxer和协议。(所有FFmpeg程序开始前都要去调用他)
- avformat_open_input()/avformat_close_input() : 打开、关闭多媒体文件,结合前面的结构体
- av_dump_format() : 打印多媒体meta信息
(一)多媒体文件meta数据获取
#include <libavutil/log.h>
#include <libavformat/avformat.h>
int main(int argc,char* argv[])
int ret;
AVFormatContext* fmt_ctx = NULL;
AVStream* stm = NULL;
AVPacket* pkt = NULL;
av_register_all();
av_log_set_level(AV_LOG_INFO);
ret = avformat_open_input(&fmt_ctx,"./gfxm.mp4",NULL,NULL);
if(ret<0)
av_log(NULL,AV_LOG_ERROR,"Can`t open file: %s\\n",av_err2str(ret));
return -1;
ret = avformat_find_stream_info(fmt_ctx, 0); //获取流详细信息
if(ret<0)
av_log(NULL,AV_LOG_WARNING,"Can`t get stream information!just show aac, not show aac(LC)!\\n");
av_dump_format(fmt_ctx,0,"./gfxm.mp4",0); //第一个0是流的索引值,,第二个表示输入/输出流,由于是输入文件,所以为0
//关闭上下文
avformat_close_input(&fmt_ctx);
return 0;
其中Input是我们设置的0号索引流,其中Stream表示多路流(第一路流为视频流,第二路为音频流)
1.没有加上avformat_find_stream_info时,缺少部分信息(后面数据处理时需要用到,如acc(LC),下面并没有显示LC)
2.使用avformat_find_stream_info后,显示完整信息
六:FFmpeg抽取音频数据
- av_init_packet() 初始化一个数据包结构体
- av_find_best_stream() 由四(一)可以知道在多媒体文件中有多种流,而每种流可能存在多路,该函数可以帮助找到其中最佳的一路流
- av_read_frame() 拿到流之后使用av_read_frame()获取流的数据包
- av_packet_unref() 从流中读取数据包之后,数据包就会增减引用基数,当包不用的时候,调用av_packet_unref(),将包的引用基数减 1。ffmpeg 检测到包的引用基数为0的时候,就是释放相应的资源,防止内存泄露。
补充:抽取出来的aac文件需要加adts头才能正常播放
AAC的ADTS头文件信息介绍:https://blog.csdn.net/qq_29028177/article/details/54694861(重点)
#include <libavutil/log.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#define ADTS_HEAD_LEN 7
void adts_header(char *szAdtsHeader, int dataLen)
int audio_object_type = 2; //通过av_dump_format显示音频信息或者ffplay获取多媒体文件的音频流编码acc(LC),对应表格中Object Type ID -- 2
int sampling_frequency_index = 4; //音频信息中采样率为44100 Hz 对应采样率索引0x4
int channel_config = 2; //音频信息中音频通道为双通道2
int adtsLen = dataLen + 7; //采用头长度为7字节,所以protection_absent=1 =0时为9字节,表示含有CRC校验码
szAdtsHeader[0] = 0xff; //syncword :总是0xFFF, 代表一个ADTS帧的开始, 用于同步. 高8bits
szAdtsHeader[1] = 0xf0; //syncword:0xfff 低4bits
szAdtsHeader[1] |= (0 << 3); //MPEG Version:0 : MPEG-4(mp4a),1 : MPEG-2 1bit
szAdtsHeader[1] |= (0 << 1); //Layer:0 2bits
szAdtsHeader[1] |= 1; //protection absent:1 没有CRC校验 1bit
szAdtsHeader[2] = (audio_object_type - 1)<<6; //profile=(audio_object_type - 1) 表示使用哪个级别的AAC 2bits
szAdtsHeader[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index 4bits
szAdtsHeader[2] |= (0 << 1); //private bit:0 1bit
szAdtsHeader[2] |= (channel_config & 0x04)>>2; //channel configuration:channel_config 高1bit
szAdtsHeader[3] = (channel_config & 0x03)<<6; //channel configuration:channel_config 低2bits
szAdtsHeader[3] |= (0 << 5); //original:0 1bit
szAdtsHeader[3] |= (0 << 4); //home:0 1bit ----------------固定头完结,开始可变头
szAdtsHeader[ffmpeg 开发-初级