美文网首页音视频开发FFmpeg
FFmpeg 工程之路-多媒体文件处理

FFmpeg 工程之路-多媒体文件处理

作者: markfork | 来源:发表于2018-10-25 23:59 被阅读171次

    章节

    • FFmpeg 代码结构
    • FFmpeg日志系统的使用
      • c 调用 FFmpeg 库avutil 的avlog 函数->testavlog.c
      • 运行结果
    • FFmpeg 文件的删除与重命名
      • FFmpeg删除文件-avpriv_io_delete()
      • FFmpeg 重命名文件-avpriv_io_move()
    • FFmpeg处理流数据
      • 多媒体文件的基本概念
      • 几个重要的结构体
    • FFmpeg 打印音/视频信息(Meta)
      • 获取视频文件 音视频流信息代码->mediainfo.c
      • 展示音/视频信息
    • 抽取音频数据
      • 抽取音频数据涉及到的Api
      • 实战从音视频文件中抽取音频数据

    1. FFmpeg 代码结构

    FFmpeg 代码结构

    2. FFmpeg 日志系统使用

    2.1 c 调用 FFmpeg 库avutil 的avlog 函数->testavlog.c

    #include<stdio.h>
    //引入libavutil库中的 log.h 文件头
    #include<libavutil/log.h>
    // 主函数
    int main(int argc,char* argv[]){
        //设置log level 级别
        av_log_set_level(AV_LOG_DEBUG);
        //记录 Info 级别下的 av 日志
        av_log(NULL,AV_LOG_INFO,"hello world! use libavutil/log.h!\n");
    }
    

    2.2 运行结果

    output 结果

    3.FFmpeg 文件的删除与重命名

    • 删除文件Api-> avpriv_io_delete(fileName)
    • 重命名文件Api-> avpriv_io_move(src,dst)
      // src 指被命名文件、dst指命名后文件

    3.1 FFmpeg删除文件

    //引入avformat.h 头文件
    #include<libavformat/avformat.h>
    
    int main(int argc,char* argv[]){
        //定义记录操作结果变量
        int ret;
        ret = avpriv_io_delete("./testdel.txt");
        if(ret<0) {
           av_log(NULL,AV_LOG_ERROR,"Failed to delete file testdel.text!");
           return -1;
        }
        return 0;
    }
    

    运行结果
    testdel.txt 不存在时:

    testdel.txt 不存在时

    testdel.txt 存在时

    运行结果

    3.2 FFmpeg 重命名文件

    //引入avformat.h 头文件
    #include<libavformat/avformat.h>
    
    int main(int argc,char* argv[]){
        //定义记录操作结果变量
        int ret;
        //重命名 testmove000.txt 为 testmove111.txt
        ret = avpriv_io_move("testmove000.txt","testmove111.txt");
        if (ret < 0){
           av_log(NULL,AV_LOG_ERROR,"更新testmove000.txt 文件名失败!");
           return -1;
        }
    
        av_log(NULL,AV_LOG_INFO,"更新testmove000.txt 文件名成功!");
       
        ret = avpriv_io_delete("./testdel.txt");
        if(ret<0) {
           av_log(NULL,AV_LOG_ERROR,"Failed to delete file testdel.text!");
           return -1;
        }
        return 0;
    }
    

    testmove000.txt 文件不存在

    output 结果

    testmove000.txt 文件存在

    output 结果 testmove000.txt 更名为 testmove111.txt

    4.FFmpeg处理流数据

    4.1 多媒体文件的基本概念

    • 多媒体文件是个容器、音视、字母数据、
    • 在容器里有很多流(stream/track) 比如 音频流、视频流、多路音频流、
    • 每种流是由不同的编码器编码的、
    • 如视频编码 通常采用HR64 HR65编码、
    • 从流中 Stream 读取的数据称为包 Packet 多帧压缩成的包、
    • 一个包中包含着一个或多个帧 Frame 没有被压缩的数据、

    4.2 几个重要的结构体

    AVFormatContext

    格式上下文,连接多个Api的桥梁、读多媒体流的时候需要将 AVFormatContext 指针变量作为参数传递,获取相关数据。

    AVStream

    从 AVFormatContext 可以获取到AVStream->音频流、视频流

    AVPacket

    从 AVStream 可以获取到对应的 AVPacket,AVPacket 的组成内容是被压缩的帧。

    AVFrame

    从AVPacket 可以拿到具体的 1-n 个AVFrame

    5. FFmpeg操作流数据的基本步骤

    如下图所示:


    FFmpeg操作流数据基本步骤

    6.FFmpeg 打印音/视频信息(Meta)

    7.1 获取视频文件 音视频流信息代码->mediainfo.c

    #include <libavutil/log.h>
    #include <libavformat/avformat.h>
    
    //获取视频Meta信息
    int main(int argc,char* argv[]){
        int ret;
        //1.定义格式上下文(容器)指针变量
        AVFormatContext *fmt_ctx = NULL;
        av_log_set_level(AV_LOG_INFO);
        //2.注册音视频全局解码器
        av_register_all();
        //3.打开多媒体文件,注意第一个参数为指针变量的地址,而不是上下文容器 fmt_ctx 指向的上下文所存储的内存地址
        //参数含义:[指向fmt_ctx指针变量的地址,要打开的视频文件,文件格式-无值程序会根据视频文件类型后缀自动识别,命令行参数]
        ret = avformat_open_input(&fmt_ctx,"test.mp4",NULL,NULL);
        if (ret < 0) {
          av_log(NULL,AV_LOG_ERROR,"Can't open file: %s\n",av_err2str(ret));
          return -1;
        }
    
        //4.[上下文指针,index 默认值为0,文件src,0表示输入类型文件|输出流则为1]
        av_dump_format(fmt_ctx,0,"./test.mp4",0);
        //5.关闭格式上下文,注意可以从格式上下文中可以获取到对应的AVStream
        avformat_close_input(&fmt_ctx);
        return 0;
    }
    

    7.2 展示音/视频信息

    FFmpeg 打印音/视频信息(Meta)

    当前目录下音视频文件如下所示:


    音视频文件

    8.抽取音频数据

    8.1 抽取音频数据涉及到的Api

    抽取音频数据涉及到的Api
    • av_init_packet() 初始化数据包
    • av_find_best_stream() 找寻上下文中最优的流
    • av_read_frame() / av_packet_unref() 读取包数据、

    注意:在这几天阅读源码的过程中,我注意到av_read_frame()读出来的数据是 AVPacket 类型的变量,这使我很疑惑,最终我在讲师的课程中得到了答案,是因为FFmpeg的历史问题。

    8.2 实战从音视频文件中抽取音频数据

    #include <stdio.h>
    #include <libavutil/log.h>
    #include <libavformat/avformat.h>
    //定义音频播放头信息
    void adts_header(char *szAdtsHeader, int dataLen){
    
        int audio_object_type = 2;
        int sampling_frequency_index = 7;
        int channel_config = 2;
    
        int adtsLen = dataLen + 7;
    
        szAdtsHeader[0] = 0xff;         //syncword:0xfff                          高8bits
        szAdtsHeader[1] = 0xf0;         //syncword:0xfff                          低4bits
        szAdtsHeader[1] |= (0 << 3);    //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bit
        szAdtsHeader[1] |= (0 << 1);    //Layer:0                                 2bits
        szAdtsHeader[1] |= 1;           //protection absent:1                     1bit
    
        szAdtsHeader[2] = (audio_object_type - 1)<<6;            //profile:audio_object_type - 1                      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[3] |= (0 << 3);                      //copyright id bit:0                       1bit
        szAdtsHeader[3] |= (0 << 2);                      //copyright id start:0                     1bit
        szAdtsHeader[3] |= ((adtsLen & 0x1800) >> 11);           //frame length:value   高2bits
    
        szAdtsHeader[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);     //frame length:value    中间8bits
        szAdtsHeader[5] = (uint8_t)((adtsLen & 0x7) << 5);       //frame length:value    低3bits
        szAdtsHeader[5] |= 0x1f;                                 //buffer fullness:0x7ff 高5bits
        szAdtsHeader[6] = 0xfc;
    }
    
    //获取视频Meta信息 & 抽取音频数据
    int main(int argc,char* argv[]){
        int ret;
        char* src = NULL;
        char* dst = NULL;
        int len;
        //0.流的 index
        int audio_index;
        AVPacket pkt;
        //1.定义格式上下文(容器)指针变量
        AVFormatContext *fmt_ctx = NULL;
        av_log_set_level(AV_LOG_INFO);
        //2.注册音视频全局解码器
        av_register_all();
    
        //3.read two params from console ,从控制台读取输入的两个参数、param one is the read file and  param two is the audio file
        // 即从源音视频文件中抽离出来的音频文件
        if (argc < 3) {
           av_log(NULL,AV_LOG_ERROR,"the count of params should be more than three!\n");
           return -1;
        }
    
        src = argv[1];
        dst = argv[2];
    
        if(!src || !dst) {
           av_log(NULL,AV_LOG_ERROR,"src or dst is null\n");
        }
    
        //4.打开多媒体文件,注意第一个参数为指针变量的地址,而不是上下文容器 fmt_ctx 指向的上下文所存储的内存地址
        //参数含义:[指向fmt_ctx指针变量的地址,要打开的视频文件,文件格式-无值程序会根据视频文件类型后缀自动识别,命令行参数]
        ret = avformat_open_input(&fmt_ctx,src,NULL,NULL);
        if (ret < 0) {
          av_log(NULL,AV_LOG_ERROR,"Can't open file: %s\n",av_err2str(ret));
          return -1;
        }
        //打开输出文件
        FILE* dst_fd = fopen(dst,"wb");
        if(!dst_fd) {
            av_log(NULL,AV_LOG_ERROR,"can't open out file!\n");
            avformat_close_input(&fmt_ctx);//关闭格式上下文
            return -1;//打开音频输出文件出错
        }
    
        //5.[上下文指针,index 默认值为0,文件src,0表示输入类型文件|输出流则为1]
        av_dump_format(fmt_ctx,0,src,0);
        //6.获取流 stream 参数解释:格式上下文、音频类型-宏、音频流索引号、与音频流相关的视频流索引好,不知道的情况下都设置为-1、编解码器、flag、返回值是流的编号
        ret = av_find_best_stream(fmt_ctx,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
        if (ret < 0 ) {
            av_log(NULL,AV_LOG_ERROR,"Can't find the best stream@\n");
            avformat_close_input(&fmt_ctx);
            fclose(dst_fd);
            return -1;
        }
    
        audio_index = ret;
    
        //7.初始化数据包
        av_init_packet(&pkt);
    
        //7.获取音频数据 fmt_ctx -> pkt,每次读取的帧数 > 0
        while(av_read_frame(fmt_ctx,&pkt) >=0) {
           //7.1 包中的从属流的index == best stream 中的index 则将读出的packet写入文件
           if (pkt.stream_index == audio_index) {
              //每写入一个pkt,顺带写入音频播放的头部信息
              char adts_header_buf[7];
              adts_header(adts_header_buf,pkt.size);
              fwrite(adts_header_buf,1,7,dst_fd);
              //7.2 将packet当中的数据每次以1个字节的方式写 packet.data 当中size 个数据,最终数据进入 dst_fd
              len = fwrite(pkt.data,1,pkt.size,dst_fd);
              if (len != pkt.size) {
                av_log(NULL,AV_LOG_WARNING,"warning,lenth of data is not equal size of pkt");
              }
           }
           //7.3 将读取包数据的空间释放掉 即 av_read_frame 申请的空间
           av_packet_unref(&pkt);
        }
    
        //8. 关闭格式上下文,注意从格式上下文中可以获取到对应的AVStream
        avformat_close_input(&fmt_ctx);
        //9. 判断文件句柄是否仍然存在,存在则关闭
        if (dst_fd) {
           fclose(dst_fd);
        }
        return 0;
    }
    

    运行结果如下所示

    output 结果

    播放音频效果如下所示

    播放效果

    采用如下命令 ffplay test.aac 即可播放从 test.mp4 中抽取的音频文件。

    关于公众号

    精进!
    道友们,你们好。早前个人就有开设公众号的念想,今年10月终于开搞了。
    我的个人的 订阅号--T客来了;
    平时自己会总结一些后端开发相关的技术;
    最近也迷上了音视频开发相关技术;

    技术分享包括:

    • 1.FFmpeg 工程实战、
    • 2.数据库 MySQL原理与实战、
    • 3.Redis中间件、
    • 4.Nginx、Java并发编程、
    • 5.Go语言方面的技术知识与实操;


      T客来了

    点击微信图标,扫码就可以添加哦~

    完。

    相关文章

      网友评论

        本文标题:FFmpeg 工程之路-多媒体文件处理

        本文链接:https://www.haomeiwen.com/subject/bfnftqtx.html