FFmpeg: 抽取音視頻數(shù)據(jù)


前言


上一篇博客我們聊了一下如何使用FFmpeg的命令來實(shí)現(xiàn)各種需求,從這篇博客我們將一起來看一下如何使用使用FFmpeg代碼實(shí)現(xiàn)各種需求,而這一篇博客我們主要來說一下如何使用FFmpeg抽取音視頻數(shù)據(jù)。


正文


說代碼之前,我們先用一張流程圖表明具體的操作過程。如下所示。

通過上圖我們可以知道,我們需要兩個流,一個輸出流和一個空白輸入流,我們把輸入流中的數(shù)據(jù)拷貝到輸出流中即可。但是里面所用到的函數(shù)較多,我們需要逐一分析,后面博客我們就不會對這些函數(shù)進(jìn)一步講解了。


  • 注冊所有的音視頻編解碼。(舊版中需要使用,在新版中加入會有警告。)
    av_register_all();

注:為什么在新版本中不需要通過av_register_all()來注冊所有編碼器呢?這是因?yàn)樵谛碌陌姹局校獯a器和編碼器的初始化都是通過定義全局變量來初始化的。所以我們不需要使用av_register_all()來初始化所有的編解碼。這里就不過多敘述了

具體的分析博客可以看 新版本ffmpeg源碼簡單分析:av_register_all()


  • 打開輸入流文件。
    result = avformat_open_input(&fmt_ctx, inputFilePath, NULL, NULL);

avformat_open_input 函數(shù)中,返回值類型為int,如果返回值小于0,那么就是打開失敗。第一個參數(shù)是類型為AVFormatContext的格式上下文。第二個參數(shù)是媒體的地址。第三個是輸入格式參數(shù),可以為NULL。第四個參數(shù)是選項(xiàng)參數(shù),是一個類似于Key-Value形式的結(jié)構(gòu)體。


  • 尋找到最好的輸出流下標(biāo),并且根據(jù)流的下標(biāo)取到對應(yīng)的流信息。
    best_steam_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    //拿到文件中音頻流
    inputSteam = fmt_ctx->streams[best_steam_index];

媒體中是有流的概念,一個媒體文件可能有多路流,比如視頻流,音頻流,彈幕流等等各種流。我們需要從中找出最合適的流。av_find_best_stream這個能幫助我們流文件的下標(biāo),然后我們可以從 fmt_ctx→steams 中找到我們所需要的流。

如果抽離音頻就用 AVMEDIA_TYPE_AUDIO,如果抽取視頻就用 AVMEDIA_TYPE_VIDEO,這樣就能取到對應(yīng)的流下標(biāo)。


  • 初始化輸出上下文和輸出流,以及配置一個最合適的媒體格式。
    ofmt_ctx = avformat_alloc_context();
    // 配置媒體格式
    output_fmt = av_guess_format(NULL, outFilePath, NULL);
    ofmt_ctx->oformat = output_fmt;
    // 初始化輸出流
   outSteam = avformat_new_stream(ofmt_ctx, NULL);

這一步?jīng)]有什么好說的,主要是對輸出上下文和輸出流進(jìn)行初始化操作。并且要把媒體格式初始化,通過ofmt_ctx→oformat = output_fmt配置到輸出上下文中。


  • 將輸入流的參數(shù)信息拷貝到輸出流中。
    AVCodecParameters *in_codecpar = inputSteam->codecpar;
    
    if((result = avcodec_parameters_copy(outSteam->codecpar, in_codecpar)) < 0 ){
        av_log(NULL, AV_LOG_ERROR,"拷貝編碼參數(shù)失敗");
        return result;
    }

我們首先先從輸入流中拿到參數(shù)信息,然后通過 然后通過avcodec_parameters_copy函數(shù)將參數(shù)信息拷貝到輸出流中。 由于我們不需要做音視頻的處理,所以我們只需要參數(shù)信息拷貝到輸出流中即可。


  • 初始化輸出上下文中的AVIOContext。
    if((result = avio_open(&ofmt_ctx->pb, outFilePath, AVIO_FLAG_WRITE)) < 0) {
        av_log(NULL, AV_LOG_ERROR,"不能打開輸出文件");
        return result;
    }

這一步操作主要是初始化輸出上下文中的文件IO上下文。


  • 往輸出上下文中寫入媒體頭信息。
    if (avformat_write_header(ofmt_ctx, NULL) < 0) {
        av_log(NULL, AV_LOG_ERROR,"寫入文件頭失敗");
        return result;
    }

通過avformat_write_header函數(shù)我們可以直接往輸出上下文中寫入媒體頭信息。


  • ??????循環(huán)讀取輸入上下文中每一幀數(shù)據(jù)并把它寫到輸出上下文中。??????
    while(av_read_frame(fmt_ctx, &pkt) >=0 ){
        if(pkt.stream_index == best_steam_index){
            //時(shí)間基計(jì)算,音頻pts和dts一致
            pkt.pts = av_rescale_q_rnd(pkt.pts, inputSteam->time_base, outSteam->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
            pkt.dts = pkt.pts;
            pkt.duration = av_rescale_q(pkt.duration, inputSteam->time_base, outSteam->time_base);
            pkt.pos = -1;
            pkt.stream_index = 0;
            //將包寫到輸出媒體文件
            av_interleaved_write_frame(ofmt_ctx, &pkt);
            //減少引用計(jì)數(shù),避免內(nèi)存泄漏
            av_packet_unref(&pkt);
        }
    }

這是抽取音視頻代碼中最核心的一部分,也是最后一個步驟,那么就是循環(huán)把輸入上下文中合適的包數(shù)據(jù)寫到輸出上下文中。同時(shí)要注意時(shí)間基的轉(zhuǎn)化問題。


總結(jié)


本次的博客就到這里了,歡迎各位大佬批評指導(dǎo),謝謝。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容