FFmpeg代碼實(shí)現(xiàn)抽取音頻峦萎、視頻數(shù)據(jù)

今天開(kāi)始擼代碼屡久,首先使用FFmpeg的API抽取一個(gè)MP4文件的音頻數(shù)據(jù)。

IDE

應(yīng)該是第一次在Mac上做C/C++開(kāi)發(fā)爱榔,糾結(jié)過(guò)后選擇使用CLion 開(kāi)發(fā)被环。CLion是 JetBrains下專(zhuān)門(mén)用來(lái)開(kāi)發(fā)C/C++的IDE,已經(jīng)用習(xí)慣了Android studio和IntelliJ IDEA 详幽,所以CLion用起來(lái)還是很順手的蛤售。

在新建一個(gè)C項(xiàng)目后,需要把FFmpeg的庫(kù)導(dǎo)入才能正常運(yùn)行妒潭。我們修改項(xiàng)目的CMakeLists.txt文件悴能。

抽取音頻AAC數(shù)據(jù)

其實(shí)我們要做的主要就是一個(gè)文件的操作,把一個(gè)文件打開(kāi)雳灾,從里面拿出它的一部分?jǐn)?shù)據(jù)漠酿,再把這部分?jǐn)?shù)據(jù)放到另一個(gè)文件中保存。

定義參數(shù)
#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avformat.h>

//上下文
AVFormatContext *fmt_ctx = NULL;
AVFormatContext *ofmt_ctx = NULL;

//支持各種各樣的輸出文件格式谎亩,MP4炒嘲,F(xiàn)LV,3GP等等
AVOutputFormat *output_fmt = NULL;

//輸入流
AVStream *in_stream = NULL;

//輸出流
AVStream *out_stream = NULL;

//存儲(chǔ)壓縮數(shù)據(jù)
AVPacket packet;

//要拷貝的流
int audio_stream_index = -1;

1.打開(kāi)輸入文件匈庭,提取參數(shù)

//打開(kāi)輸入文件夫凸,關(guān)于輸入文件的所有就保存到fmt_ctx中了
err_code = avformat_open_input(&fmt_ctx, src_fileName, NULL, NULL);

if (err_code < 0) {
    av_log(NULL, AV_LOG_ERROR, "cant open file:%s\n", av_err2str(err_code));
    return -1;
}

if(fmt_ctx->nb_streams<2){
      //流數(shù)小于2,說(shuō)明這個(gè)文件音頻阱持、視頻流這兩條都不能保證夭拌,輸入文件有錯(cuò)誤 
      av_log(NULL, AV_LOG_ERROR, "輸入文件錯(cuò)誤,流不足2條\n");
      exit(1);
 }

 //拿到文件中音頻流
 in_stream = fmt_ctx->streams[1];
 //參數(shù)信息
 AVCodecParameters *in_codecpar = in_stream->codecpar;

//找到最好的音頻流
audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if(audio_stream_index < 0){
        av_log(NULL, AV_LOG_DEBUG, "尋找最好音頻流失敗,請(qǐng)檢查輸入文件鸽扁!\n");
        return AVERROR(EINVAL);
}

2.準(zhǔn)備輸出文件蒜绽,輸出流

// 輸出上下文
ofmt_ctx = avformat_alloc_context();

//根據(jù)目標(biāo)文件名生成最適合的輸出容器
output_fmt = av_guess_format(NULL,dst_fileName,NULL);
if(!output_fmt){
    av_log(NULL, AV_LOG_DEBUG, "根據(jù)目標(biāo)生成輸出容器失敗桶现!\n");
    exit(1);
}

ofmt_ctx->oformat = output_fmt;

//新建輸出流
 out_stream = avformat_new_stream(ofmt_ctx, NULL);
 if(!out_stream){
      av_log(NULL, AV_LOG_DEBUG, "創(chuàng)建輸出流失敹阊拧!\n");
      exit(1);
 }

3. 數(shù)據(jù)拷貝

3.1 參數(shù)信息

// 將參數(shù)信息拷貝到輸出流中骡和,我們只是抽取音頻流相赁,并不做音頻處理,所以這里只是Copy
if((err_code = avcodec_parameters_copy(out_stream->codecpar, in_codecpar)) < 0 ){
    av_strerror(err_code, errors, ERROR_STR_SIZE);
    av_log(NULL, AV_LOG_ERROR,"拷貝編碼參數(shù)失斘坑凇噪生!, %d(%s)\n",
           err_code, errors);
}

3.2 初始化AVIOContext

//初始化AVIOContext,文件操作由它完成
if((err_code = avio_open(&ofmt_ctx->pb, dst_fileName, AVIO_FLAG_WRITE)) < 0) {
    av_strerror(err_code, errors, 1024);
    av_log(NULL, AV_LOG_DEBUG, "Could not open file %s, %d(%s)\n",
           dst_fileName,
           err_code,
           errors);
    exit(1);
}

3.3 開(kāi)始拷貝


//初始化 AVPacket, 我們從文件中讀出的數(shù)據(jù)會(huì)暫存在其中
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;

// 寫(xiě)頭部信息
if (avformat_write_header(ofmt_ctx, NULL) < 0) {
    av_log(NULL, AV_LOG_DEBUG, "Error occurred when opening output file");
    exit(1);
}


//每讀出一幀數(shù)據(jù)
while(av_read_frame(fmt_ctx, &packet) >=0 ){
    if(packet.stream_index == audio_stream_index){
        //時(shí)間基計(jì)算东囚,音頻pts和dts一致
        packet.pts = av_rescale_q_rnd(packet.pts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
        packet.dts = packet.pts;
        packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base);
        packet.pos = -1;
        packet.stream_index = 0;
        //將包寫(xiě)到輸出媒體文件
        av_interleaved_write_frame(ofmt_ctx, &packet);
        //減少引用計(jì)數(shù)跺嗽,避免內(nèi)存泄漏
        av_packet_unref(&packet);
    }
}

//寫(xiě)尾部信息
av_write_trailer(ofmt_ctx);

//最后別忘了釋放內(nèi)存
avformat_close_input(&fmt_ctx);
avio_close(ofmt_ctx->pb);

執(zhí)行

./MyC /Users/david/Desktop/1080p.mov /Users/david/Desktop/test.aac

抽取視頻數(shù)據(jù)

抽取視頻信息并保存在文件中的流程甚至代碼和上面抽取音頻基本一致。

//拿到文件中音頻流 或者 視頻流页藻,所有流都在streams數(shù)組中
 in_stream = fmt_ctx->streams[1];

//找到最好的視頻流
video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);

packet.dts = av_rescale_q_rnd(packet.dts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));

基本上就是一些參數(shù)的改變桨嫁,所有流程和代碼保持不變,就可以把一個(gè)音視頻文件中的視頻數(shù)據(jù)抽取出來(lái)了份帐,mp4璃吧、H264等格式隨便,就是這么簡(jiǎn)單废境。畜挨。。

完整代碼

//
// Created by david on 2019/5/5.
//

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>

#define ERROR_STR_SIZE 1024

int main(int argc, char *argv[]) {
    int err_code;
    char errors[ERROR_STR_SIZE];

    char *src_filename = NULL;
    char *dst_filename = NULL;

    int audio_stream_index;

    //上下文
    AVFormatContext *fmt_ctx = NULL;
    AVFormatContext *ofmt_ctx = NULL;

    //支持各種各樣的輸出文件格式噩凹,MP4巴元,F(xiàn)LV,3GP等等
    AVOutputFormat *output_fmt = NULL;

    AVStream *in_stream = NULL;
    AVStream *out_stream = NULL;

    AVPacket pkt;

    av_log_set_level(AV_LOG_DEBUG);

    if (argc < 3) {
        av_log(NULL, AV_LOG_DEBUG, "argc < 3驮宴!\n");
        return -1;
    }

    src_filename = argv[1];
    dst_filename = argv[2];

    if (src_filename == NULL || dst_filename == NULL) {
        av_log(NULL, AV_LOG_DEBUG, "src or dts file is null!\n");
        return -1;
    }


    if ((err_code = avformat_open_input(&fmt_ctx, src_filename, NULL, NULL)) < 0) {
        av_strerror(err_code, errors, 1024);
        av_log(NULL, AV_LOG_DEBUG, "打開(kāi)輸入文件失敗: %s, %d(%s)\n",
               src_filename,
               err_code,
               errors);
        return -1;
    }

    if ((err_code = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
        av_strerror(err_code, errors, 1024);
        av_log(NULL, AV_LOG_DEBUG, "failed to find stream info: %s, %d(%s)\n",
               src_filename,
               err_code,
               errors);
        return -1;
    }

    av_dump_format(fmt_ctx, 0, src_filename, 0);

    if (fmt_ctx->nb_streams < 2) {
        //流數(shù)小于2逮刨,說(shuō)明這個(gè)文件音頻、視頻流這兩條都不能保證堵泽,輸入文件有錯(cuò)誤
        av_log(NULL, AV_LOG_ERROR, "輸入文件錯(cuò)誤修己,流不足2條\n");
        exit(1);
    }

    //拿到文件中音頻流
    /**只需要修改這里AVMEDIA_TYPE_VIDEO參數(shù)**/
    audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO /*AVMEDIA_TYPE_AUDIO*/, -1, -1, NULL, 0);
    if (audio_stream_index < 0) {
        av_log(NULL, AV_LOG_DEBUG, " 獲取音頻流失敗%s,%s\n",
               av_get_media_type_string(AVMEDIA_TYPE_AUDIO),
               src_filename);
        return AVERROR(EINVAL);
    }

    in_stream = fmt_ctx->streams[audio_stream_index];
    //參數(shù)信息
    AVCodecParameters *in_codecpar = in_stream->codecpar;


    // 輸出上下文
    ofmt_ctx = avformat_alloc_context();

    //根據(jù)目標(biāo)文件名生成最適合的輸出容器
    output_fmt = av_guess_format(NULL, dst_filename, NULL);
    if (!output_fmt) {
        av_log(NULL, AV_LOG_DEBUG, "根據(jù)目標(biāo)生成輸出容器失敗迎罗!\n");
        exit(1);
    }

    ofmt_ctx->oformat = output_fmt;

    //新建輸出流
    out_stream = avformat_new_stream(ofmt_ctx, NULL);
    if (!out_stream) {
        av_log(NULL, AV_LOG_DEBUG, "創(chuàng)建輸出流失敳欠摺!\n");
        exit(1);
    }

    // 將參數(shù)信息拷貝到輸出流中纹安,我們只是抽取音頻流尤辱,并不做音頻處理砂豌,所以這里只是Copy
    if ((err_code = avcodec_parameters_copy(out_stream->codecpar, in_codecpar)) < 0) {
        av_strerror(err_code, errors, ERROR_STR_SIZE);
        av_log(NULL, AV_LOG_ERROR,
               "拷貝編碼參數(shù)失敗啥刻!, %d(%s)\n",
               err_code, errors);
    }

    out_stream->codecpar->codec_tag = 0;

    //初始化AVIOContext,文件操作由它完成
    if ((err_code = avio_open(&ofmt_ctx->pb, dst_filename, AVIO_FLAG_WRITE)) < 0) {
        av_strerror(err_code, errors, 1024);
        av_log(NULL, AV_LOG_DEBUG, "文件打開(kāi)失敗 %s, %d(%s)\n",
               dst_filename,
               err_code,
               errors);
        exit(1);
    }



    av_dump_format(ofmt_ctx, 0, dst_filename, 1);


    //初始化 AVPacket奸鸯, 我們從文件中讀出的數(shù)據(jù)會(huì)暫存在其中
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;


    // 寫(xiě)頭部信息
    if (avformat_write_header(ofmt_ctx, NULL) < 0) {
        av_log(NULL, AV_LOG_DEBUG, "寫(xiě)入頭部信息失斶湫Α可帽!");
        exit(1);
    }

    //每讀出一幀數(shù)據(jù)
    while (av_read_frame(fmt_ctx, &pkt) >= 0) {
        if (pkt.stream_index == audio_stream_index) {
            pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base,
                                       (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,
                                       (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));

            pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
            pkt.pos = -1;
            pkt.stream_index = 0;
            //將包寫(xiě)到輸出媒體文件
            av_interleaved_write_frame(ofmt_ctx, &pkt);
            //減少引用計(jì)數(shù),避免內(nèi)存泄漏
            av_packet_unref(&pkt);
        }
    }

    //寫(xiě)尾部信息
    av_write_trailer(ofmt_ctx);

    //最后別忘了釋放內(nèi)存
    avformat_close_input(&fmt_ctx);
    avio_close(ofmt_ctx->pb);

    return 0;
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窗怒,一起剝皮案震驚了整個(gè)濱河市映跟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扬虚,老刑警劉巖努隙,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異辜昵,居然都是意外死亡荸镊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)堪置,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)躬存,“玉大人,你說(shuō)我怎么就攤上這事舀锨×胫蓿” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵坎匿,是天一觀的道長(zhǎng)盾剩。 經(jīng)常有香客問(wèn)我,道長(zhǎng)替蔬,這世上最難降的妖魔是什么告私? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮承桥,結(jié)果婚禮上德挣,老公的妹妹穿的比我還像新娘。我一直安慰自己快毛,他們只是感情好格嗅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著唠帝,像睡著了一般屯掖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上襟衰,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天贴铜,我揣著相機(jī)與錄音,去河邊找鬼。 笑死绍坝,一個(gè)胖子當(dāng)著我的面吹牛徘意,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播轩褐,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼椎咧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了把介?” 一聲冷哼從身側(cè)響起勤讽,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拗踢,沒(méi)想到半個(gè)月后脚牍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡巢墅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年诸狭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片君纫。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡驯遇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出庵芭,到底是詐尸還是另有隱情妹懒,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布双吆,位于F島的核電站眨唬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏好乐。R本人自食惡果不足惜匾竿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蔚万。 院中可真熱鬧岭妖,春花似錦、人聲如沸反璃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)淮蜈。三九已至斋攀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梧田,已是汗流浹背淳蔼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工侧蘸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鹉梨。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓讳癌,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親存皂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晌坤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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