FFmpeg编程FFmpeg初级开发

Posted 贺二公子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FFmpeg编程FFmpeg初级开发相关的知识,希望对你有一定的参考价值。

原文地址:https://www.cnblogs.com/ssyfj/p/14579909.html


文章目录

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 操作流数据的基本步骤

  1. 解复用:打开多媒体文件
  2. 获取文件中多路流中想要的
  3. 获取数据包,进行解码,对原始数据进行处理,变声、变速、滤波
  4. 释放相关资源

五:打印音/视频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 开发-初级

音视频处理 ffmpeg初级开发 命令行工具-实用命令

ffmpeg如何批量切割视频尾部4秒?

FFmpeg编程FFmpeg中级开发

FFmpeg基础库编程开发学习笔记——视频常见格式

FFmpeg基础库编程开发学习笔记——音频常见格式及字幕格式