?1.問題描述
1.1 背景
之前基于ffmpeg
做二次開發(fā),完成常見的視頻處理功能,并用ffmpeg
命令行做兜底。在此基礎(chǔ)上,還做一個轉(zhuǎn)碼接入和調(diào)度系統(tǒng)對外提供服務(wù)。有個功能需要是這樣的:快速從指定的視頻中裁剪某一時間范圍的子視頻, 兩個要求:1. 要快,不能像轉(zhuǎn)碼一樣耗時;2.要精確,剪輯的時候能指定從哪一秒開始,到哪一秒結(jié)束。
1.2 難點(diǎn)
用ffmpeg
很容易從一個長視頻剪輯出一段小視頻。比如命令ffmpeg -i input.mp4 -ss 00:10:03 -t 00:03:00 -vcodec copy -acodec copy output.mp4
就是從input.mp4
的第10分鐘03秒開始剪輯出一個3分鐘的視頻并且保存為output.mp4
文件。參數(shù)-vcodec copy -acodec copy
就是直接拷貝原始視頻的音視頻流,不進(jìn)行編解碼。雖然上面的方法很方便,但有一個致命的缺陷:畫面在一開始會卡住(但聲音一直是正常的),幾秒后畫面才正常滾動。下面視頻是一個例子。
2.原因分析
究其原因,剪輯的開始時間落在視頻GOP
的中間位置而不是第一個I
幀。稍微了解過視頻編碼的同學(xué)應(yīng)該都聽過I
、B
、P
幀。簡單來說,I
幀是一張完整的圖像,P
幀則根據(jù)I幀
做差分編碼,B
幀根據(jù)前后的I
、P
、B
幀作差分編碼。也就是說I
幀具有完整的內(nèi)容,而P
和B
幀不具有,所以如果缺少I
幀,那么P
和B
幀是不能正常解碼的。通常來說,一個GOP
里面第一幀是I
幀,后面是若干個P
和B
幀。一個GOP
長達(dá)10秒都是有可能的。下圖是一個真實(shí)視頻的I
、B
、P
幀信息圖,紅色的表示I
幀,可以看到兩個I
幀相隔深遠(yuǎn)(實(shí)際是隔了10秒)。
從上面分析可知:剪輯的開始時間很大可能不是落在I
幀,由于缺少I
幀會使得后面的P
和B
幀無法解碼導(dǎo)致畫面卡住。上面的分析都是基于不編解碼的直接拷貝視頻內(nèi)容的,如果考慮先解碼成一張張的圖像,然后再對符合時間要求的圖像編碼,那么剪輯時間可以做到非常精準(zhǔn)。但這樣做的就是耗時過長:需求花費(fèi)大量的CPU完成編解碼操作。
3.解決方案
解決的辦法還是有的:對前面第一個符合時間要求的GOP
編解碼,而之后的GOP
內(nèi)容則直接拷貝到目標(biāo)視頻。一來,第一個GOP
的幀由于是重新編碼所以會重新分配I
幀從而能播放,二來,之后的GOP
內(nèi)容是直接拷貝的所以基本不消耗CPU,性能杠桿的。如下圖所示:
當(dāng)然這里面還是有一些坑的,下面開始填坑。
3.1 拼接
源視頻可能會驚訝:我憑本事編的碼,為什么你直接拷貝就能解碼?一般來說解碼依賴于SPS
和PPS
,而源視頻與目標(biāo)視頻的SPS
和PPS
會有所不同,因此直接拷貝是不能正確解碼的。對于mp4
文件,SPS
和PPS
一般是放到文件頭。一個文件只能有一個文件頭,也就不能存放兩個不同的SPS
和PPS
。為了能正確解碼目標(biāo)視頻必須得有源視頻的SPS
和PPS
。不能放文件頭的話,那能放哪里?能不能放到拷貝的幀的前面呢?如何放?一籌莫展、無處下手,直到有一天突然想起之前為了填一個坑,追蹤到h264_mp4toannexb
的實(shí)現(xiàn),它的作用就是將SPS
和PPS
拷貝到幀(準(zhǔn)確來說應(yīng)該是AVPacket
)的前面。來!溫習(xí)一下h264_mp4toannexb的具體實(shí)現(xiàn):在所有AVPacket
前面增加0x000001
或者0x00000001
,在I
幀的前面插入SPS
和PPS
。也就是通過h264_mp4toexannb
就能把解碼所需的SPS
和PPS
正確插入到視頻中。h264_mp4toannexb
使用起來也比較簡單,代碼如下:
AVBSFContext* initBSF(const std::string &filter_name, const AVCodecParameters *codec_par, AVRational tb)
{
const AVBitStreamFilter *filter = av_bsf_get_by_name(m_filter_name.c_str());
?
AVBSFContext *bsf_ctx = nullptr;
av_bsf_alloc(filter, &bsf_ctx);
?
avcodec_parameters_copy(bsf_ctx->par_in, codec_par);
bsf_ctx->time_base_in = tb;
?
av_bsf_init(bsf_ctx);
return bsf_ctx;
}
?
AVPacket* feedPacket(AVBSFContext *bsf_ctx, AVPacket &packet)
{
av_bsf_send_packet(bsf_ctx, packet);
?
AVPacket *dst_packet = av_packet_alloc();
av_bsf_receive_packet(bsf_ctx, dst_packet);
?
return dst_packet;
}
?
void test()
{
AVBSFContext *bsf_ctx = initBSF("h264_mp4toannexb", video_stream->codecpar, video_stream->time_base);
AVPacket *packet = readVideoPacket();
AVPacket *dst_packet = feedPacket(bsf_ctx, packet);
}
注意:編解碼第一個GOP
和原始視頻后續(xù)GOP
拼接時的時間戳要小心處理,不然視頻播放時可能會出現(xiàn)抖動現(xiàn)象。
3.2 花屏
以為就完了嗎?沒有??!你會發(fā)現(xiàn)有些視頻會在最后一秒出現(xiàn)花屏。。。。
出現(xiàn)花屏的原因其實(shí)也不難猜到:最后一幀是B
幀。由于不是所有剪輯的視頻最后一幀都是B
幀,所以花屏也不是必現(xiàn)的。知道是B
幀引起的,那解決方案也就明確了:保證最后一幀是P
幀。即使時間上稍微超一點(diǎn)(音頻流也應(yīng)該跟著視頻流稍微超一下時間)。不過呢,由于不能直接從AVPacket
判斷一個幀是否為P
幀,所以最后一個GOP
也得解碼(無需編碼)。記錄超出時間范圍后的第一個P
幀的pts
,后面拷貝GOP
的時候,拷貝到這個pts
就可以停止了。
4.總結(jié)
起初覺得問題很難解決,畢竟ffmpeg
命令行都裁剪出來的都有問題。而萬變不離其宗,從問題的原因出發(fā),一步步尋找解決方案,并將一路上碰到的問題逐一擊破。記住,明白原理才能解決問題。
原文 如何使用FFmpeg精確剪輯視頻 - 知乎?
★文末名片可以免費(fèi)領(lǐng)取音視頻開發(fā)學(xué)習(xí)資料,內(nèi)容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音視頻學(xué)習(xí)路線圖等等。
見下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓文章來源:http://www.zghlxwxcb.cn/news/detail-655318.html
?文章來源地址http://www.zghlxwxcb.cn/news/detail-655318.html
到了這里,關(guān)于如何使用FFmpeg精確剪輯視頻的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!