1. 程式人生 > >FFmpeg 工程之路-多媒體檔案處理

FFmpeg 工程之路-多媒體檔案處理

章節

  • 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 程式碼結構

2836699-3849bc5c4352d1aa.png 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 執行結果

2836699-a6cd66902759ba98.png 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 不存在時:

2836699-486b0d328fca9b54.png testdel.txt 不存在時

testdel.txt 存在時

2836699-2c85ce5a24ea10c1.png 執行結果

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 檔案不存在

2836699-ae5fbd1c41d33b34.png output 結果

testmove000.txt 檔案存在

2836699-a0456567e0c47850.png output 結果 2836699-fa3819d7fdddc907.png 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操作流資料的基本步驟

如下圖所示:


2836699-4303cee217b50b24.png 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 展示音/視訊資訊

2836699-414f6d4790b7fcf0.png FFmpeg 列印音/視訊資訊(Meta)

當前目錄下音視訊檔案如下所示:


2836699-f0f490828a6f24a9.png 音視訊檔案

8.抽取音訊資料

8.1 抽取音訊資料涉及到的Api

2836699-b541f1c537d2f7a6.png 抽取音訊資料涉及到的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 [email protected]\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;
}

執行結果如下所示

2836699-34d86a09ee0abe5f.png output 結果

播放音訊效果如下所示

2836699-2bb41f79f41093cf.png 播放效果

採用如下命令 ffplay test.aac 即可播放從 test.mp4 中抽取的音訊檔案。

完。