前言
上一篇博客我們聊了一下如何使用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),謝謝。