FFmpeg优化 苏宁PP体育视频剪切效率提升技巧
FFmpeg功能強(qiáng)大,社區(qū)活躍,在多媒體處理業(yè)務(wù)中扮演著不可或缺的角色。但沒有優(yōu)化過的FFmpeg在生產(chǎn)環(huán)境下有很多性能瓶頸,因此對其進(jìn)行優(yōu)化勢在必行。蘇寧旗下PP體育音視頻技術(shù)負(fù)責(zé)人田釗撰文分享了團(tuán)隊(duì)在處理海量視頻切割過程中遇到的挑戰(zhàn)及優(yōu)化方法。感謝OnVideo視頻創(chuàng)作云平臺(tái)聯(lián)合創(chuàng)始人、FFmpeg Maintainer劉歧對本文的技術(shù)審校。
文 / 田釗
審校 / 劉歧
一、前言
蘇寧旗下PP體育所在的直播行業(yè),每天有無數(shù)視頻原始數(shù)據(jù)需要進(jìn)行分類存儲(chǔ)、渲染處理。處理這些視頻,一個(gè)很重要的方面,就是要將長時(shí)段的直播視頻切割成不定時(shí)長,不定畫面組的短視頻,以匹配現(xiàn)代用戶碎片化的消費(fèi)時(shí)間。尤其是體育賽事直播行業(yè),在直播前的墊場片花、直播中的即時(shí)快看、直播后的全場集錦和精華鏡頭,都需要對大量的視頻作剪切/壓制處理。而且因?yàn)轶w育賽事直播行業(yè)的特殊性,對于直播中和直播后的精彩鏡頭,集錦類視頻片段,要求必須能及時(shí)處理視頻,并發(fā)布到用戶端。這對視頻的處理效率提出了非常高的要求。
在PP體育,我們在使用與業(yè)界同樣高效的設(shè)計(jì)模式和優(yōu)化方案的同時(shí),另外嘗試了換一種角度來思考這個(gè)問題,并進(jìn)行了實(shí)踐。下面我們來針對這部分的構(gòu)思和實(shí)踐中碰到的問題,來做個(gè)分享。
二、背景基礎(chǔ)知識(shí)
先簡單說一下我們對視頻在數(shù)據(jù)層面上的理解。對于視頻來說,無論是何種編碼,何種封裝格式,拆分開看,都是由音頻流和視頻流來組合而成的。從數(shù)據(jù)的最低層級(jí)往上推,會(huì)發(fā)現(xiàn)一個(gè)視頻文件會(huì)由以下幾個(gè)層面的數(shù)據(jù)組成。
1. 第一層是亂序的二進(jìn)制數(shù)據(jù)層。基本看不出來是啥數(shù)據(jù)。
2. 第二層是未經(jīng)編碼的音視頻數(shù)據(jù)層。這里就有了數(shù)據(jù)源出來的原始音頻、視頻等數(shù)據(jù)。原始音視頻流數(shù)據(jù)量很大。
3. 第三層是編碼數(shù)據(jù)層。通常音頻使用AAC編碼,視頻使用H.264/265編碼后,音視頻流數(shù)據(jù)量就已經(jīng)比較小了。
4. 第四層是封裝層。將編碼后的音視頻數(shù)據(jù)”打包“封裝成不同的封裝格式。這里就是我們通常所看到的.ts/.mp4/.flv/.mkv等視頻文件。這些文件里封裝著M路編碼的視頻流和N路編碼的音頻流。當(dāng)然也可以有其它的數(shù)據(jù)流,如字幕流,附加信息流等。
三、常規(guī)做法簡述
視頻的切割/轉(zhuǎn)碼/壓制,目前業(yè)界通常的處理方式是在云端服務(wù)器,直接通過云轉(zhuǎn)碼模塊集成的視頻剪切服務(wù)來處理。通常使用FFmpeg套件改造而成。而且部分視頻云服務(wù)廠商為提升轉(zhuǎn)碼效率,會(huì)用到云端轉(zhuǎn)碼集群。通過將完整的長段視頻先進(jìn)行切割,再將切割完的小段視頻再通過分布式集群進(jìn)行轉(zhuǎn)碼,合并,壓制操作。其中,轉(zhuǎn)碼壓制部分,由蘇寧視頻云服務(wù)提供的業(yè)界領(lǐng)先的分布式轉(zhuǎn)碼集群來完成。基礎(chǔ)的轉(zhuǎn)碼業(yè)務(wù)圖如下:
其中,轉(zhuǎn)碼部分,多數(shù)視頻云服務(wù)廠商采用了分布式轉(zhuǎn)碼服務(wù),來進(jìn)行效率優(yōu)化的提升。對于切割部分,卻不一定重視。部分方案會(huì)和轉(zhuǎn)碼模塊合并到一起,也有的廠商兩樣將分析視頻的結(jié)果列表,也利用服務(wù)器集群來進(jìn)行并發(fā)的切割操作。通常這種方案會(huì)直接使用FFmpeg套件來完成切割的動(dòng)作。所以,對視頻云廠商來說,FFmpeg套件切割視頻功能的優(yōu)化是提升切割效率的核心。各大廠商的業(yè)界大牛們?yōu)榇俗隽瞬煌膰L試,也取得了不錯(cuò)的效果。
典型的切割服務(wù),多在音視頻分層圖的第三層作數(shù)據(jù)拷貝處理,典型如下列指令:
ffmpeg?-ss?00:10:24?-i?input.mp4?-vcodec?copy?-acodec?copy?-t?00:95:27?output.mp4
此切割指令使用FFmpeg套件對視頻數(shù)據(jù)中的音視頻,按音視頻幀級(jí)數(shù)據(jù)包直接拷貝來處理。此種方式有優(yōu)點(diǎn)也有缺陷。
缺點(diǎn)在于:經(jīng)常會(huì)有比較明顯的視頻切割誤差。因?yàn)橐曨lGOP長度因素存在,經(jīng)常會(huì)出現(xiàn)起始點(diǎn)視頻幀并非關(guān)鍵幀。而FFmpeg切割程序代碼需要找到切割起始點(diǎn)的視頻關(guān)鍵幀,才能正常完成視頻幀層面的切割動(dòng)作。所以FFmpeg程序會(huì)計(jì)算查找當(dāng)前視頻幀的GOP關(guān)鍵幀后,再以此GOP關(guān)鍵幀為起始點(diǎn)來作為切割起始點(diǎn)。此種方式下會(huì)導(dǎo)致真實(shí)切割點(diǎn)與原始需求切割點(diǎn)是不一致的情況。導(dǎo)致切割出來的視頻起止點(diǎn)并不精確。
優(yōu)點(diǎn)也很明顯:因?yàn)椴粚σ丫幋a的音視頻數(shù)據(jù)進(jìn)行解碼再編碼的操作,所以效率已經(jīng)非常不錯(cuò)。并且在此基礎(chǔ)上,進(jìn)一步的優(yōu)化方案,可以將FFmpeg套件按多進(jìn)程模型來使用,利用服務(wù)器的多核性能來并行調(diào)用多個(gè)FFmpeg進(jìn)程進(jìn)行多路切割操作,縮短總體切割時(shí)間,以提升切割性能;再利用服務(wù)器集群,進(jìn)行多服務(wù)器規(guī)模并行處理,進(jìn)一步提高切割效率。
四、優(yōu)化方法與實(shí)踐
我們的優(yōu)化做法,與上述情況在原理上是一致的,但是在細(xì)節(jié)上有做了微創(chuàng)新。
首先,我們沒用使用FFmpeg套件來做核心切割功能服務(wù)。如上所述,業(yè)界通常利用FFmpeg套件切割視頻文件時(shí),是在視頻分層圖的第三層編碼數(shù)據(jù)層對視頻文件按”幀“級(jí)數(shù)據(jù)作拷貝處理。我們對生產(chǎn)環(huán)境及直播鏈路進(jìn)行梳理后發(fā)現(xiàn),視頻的數(shù)據(jù)封裝格式基本只有MP4/FLV/TS三種。而此三種封裝格式里,除MP4封裝稍復(fù)雜外,FLV/TS的封裝相對容易分析處理。所以我們大膽地嘗試了在視頻分層圖的第四層——封裝層做分析處理。將視頻切割動(dòng)作分解為對封裝數(shù)據(jù)的切分。
1. 分析視頻封裝里的詳細(xì)描述信息;
2. 根據(jù)封裝詳細(xì)描述信息,對起止切割點(diǎn)進(jìn)行計(jì)算;
3. 找到切割點(diǎn)二進(jìn)制數(shù)據(jù)起止點(diǎn);
4. 復(fù)制出起止點(diǎn)間二進(jìn)制數(shù)據(jù);
5. 重新描述起止切割點(diǎn)的封裝信息,并與復(fù)制出的二進(jìn)制數(shù)據(jù)進(jìn)行拼合。
上述操作完成后,最終得到切割后的視頻。這種操作方法,實(shí)際是將視頻文件分解為兩層,封裝層和二進(jìn)制數(shù)據(jù)層。切割工具從封裝層得到描述信息后,對視頻數(shù)據(jù)進(jìn)行最底層的二進(jìn)制數(shù)據(jù)拷貝,其中不涉及任何幀的處理。切割起始點(diǎn)與終止點(diǎn)的計(jì)算,以及拷貝數(shù)據(jù)拼合成新的視頻,是這里的關(guān)鍵。典型代碼片段如下:?
func?CopyStructureData(src?*demux.VideoStructure,?dst?*demux.VideoStructure)?{?
????copy(dst.FTYP.CompatibleBrands,?src.FTYP.CompatibleBrands)?
????copy(dst.MOOV.MVHD.Flags,?src.MOOV.MVHD.Flags)?
????copy(dst.MOOV.MVHD.Reserved,?src.MOOV.MVHD.Reserved)?
????copy(dst.MOOV.MVHD.Matrix,?src.MOOV.MVHD.Matrix)?
????copy(dst.MOOV.MVHD.PreDefined,?src.MOOV.MVHD.PreDefined)?
????for?i?:=?0;?i?<?len(src.MOOV.TRAK);?i?++?{?
????????copy(dst.MOOV.TRAK[i].TKHD.Flags,?src.MOOV.TRAK[i].TKHD.Flags)?
????????copy(dst.MOOV.TRAK[i].TKHD.Reserved1,?src.MOOV.TRAK[i].TKHD.Reserved1)?
????????copy(dst.MOOV.TRAK[i].TKHD.Reserved2,?src.MOOV.TRAK[i].TKHD.Reserved2)?
????????copy(dst.MOOV.TRAK[i].TKHD.Reserved3,?src.MOOV.TRAK[i].TKHD.Reserved3)?
????????copy(dst.MOOV.TRAK[i].TKHD.Matrix,?src.MOOV.TRAK[i].TKHD.Matrix)?
????????copy(dst.MOOV.TRAK[i].EDTS.ELST.Flags,?src.MOOV.TRAK[i].EDTS.ELST.Flags)?
????????copy(dst.MOOV.TRAK[i].EDTS.ELST.TrackDurations,?src.MOOV.TRAK[i].EDTS.ELST.TrackDurations)?
????????copy(dst.MOOV.TRAK[i].EDTS.ELST.Times,?src.MOOV.TRAK[i].EDTS.ELST.Times)?
????????copy(dst.MOOV.TRAK[i].EDTS.ELST.Speeds,?src.MOOV.TRAK[i].EDTS.ELST.Speeds)?
????????copy(dst.MOOV.TRAK[i].MDIA.MDHD.Flags,?src.MOOV.TRAK[i].MDIA.MDHD.Flags)?
????????copy(dst.MOOV.TRAK[i].MDIA.HDLR.ComponentName,?src.MOOV.TRAK[i].MDIA.HDLR.ComponentName)?
????????copy(dst.MOOV.TRAK[i].MDIA.MINF.SMHD.Flags,?src.MOOV.TRAK[i].MDIA.MINF.SMHD.Flags)?
????????copy(dst.MOOV.TRAK[i].MDIA.MINF.SMHD.Balance,?src.MOOV.TRAK[i].MDIA.MINF.SMHD.Balance)?
????????copy(dst.MOOV.TRAK[i].MDIA.MINF.SMHD.Reserved,?src.MOOV.TRAK[i].MDIA.MINF.SMHD.Reserved)?
????????copy(dst.MOOV.TRAK[i].MDIA.MINF.VMHD.Flags,?src.MOOV.TRAK[i].MDIA.MINF.VMHD.Flags)?
????????......?
????}?
}
//?生成片段視頻文件?
func?Generate(clipVideo?*demux.VideoStructure,?videoSampleOffsets,?audioSampleOffsets?*SampleOffsets,?videoSampleIndexRange,?audioSampleIndexRange?*SampleIndexRange,?clipPath,?clipPrefix?string,?clipTime?*ClipTime,?videoFilePath?string)?{?
????//?拼接完整路徑名稱?
????clipVideoPath?:=?fmt.Sprintf("%s%s%d-%d.mp4",?clipPath,?clipPrefix,?clipTime.Start,?clipTime.Stop)?
????//?創(chuàng)建文件寫入對象?
????writer,?err?:=?NewFileWriter(clipVideoPath)?
????if?err?!=?nil?{?
????????fmt.Print(err)?
????????return?
????}?
????defer?writer.Close()?
????writeFTYP(writer,?clipVideo)?
????writeFREE(writer,?clipVideo)?
????writeMDAT(writer,?clipVideo,?videoSampleOffsets,?audioSampleOffsets,?videoSampleIndexRange,?audioSampleIndexRange,?videoFilePath)?
????writeMOOV(writer,?clipVideo)?
????writeMVHD(writer,?clipVideo)?
????for?_,?track?:=?range?clipVideo.MOOV.TRAK?{?
????????if?track.MDIA.HDLR.ComponentSubtype?==?"vide"?{?
????????????writeTRAK(writer,?&track,?true)?
????????}?
????????if?track.MDIA.HDLR.ComponentSubtype?==?"soun"?{?
????????????writeTRAK(writer,?&track,?false)?
????????}?
????}?
}?
//?從原視頻結(jié)構(gòu)體中取出片段幀的偏移,再從原視頻中拷貝幀數(shù)據(jù)到片段視頻?
func?writeMDAT(writer?*FileWriter,?clipVideo?*demux.VideoStructure,?videoSampleOffsets,?audioSampleOffsets?*SampleOffsets,?videoSampleIndexRange,?audioSampleIndexRange?*SampleIndexRange,?videoFilePath?string)?{?
????//?重算音視頻幀總長度?
????sampleTotalSize?:=?uint32(0)?
????for?_,?track?:=?range?clipVideo.MOOV.TRAK?{?
????????for?_,?sampleSize?:=?range?track.MDIA.MINF.STBL.STSZ.SampleSize?{?
????????????sampleTotalSize?+=?sampleSize?
????????}?
????}?
????writer.WriteUint32BE(8?+?sampleTotalSize)?
????writer.WriteString("mdat")?
????//?從原視頻中拷貝幀數(shù)據(jù)?
????reader,?err?:=?NewRawReader(videoFilePath)?
????if?err?!=?nil?{?
????????fmt.Print(err)?
????????return?
????}?
????//?視頻數(shù)據(jù)如果連續(xù),則合并長度,減少讀取次數(shù)?
????currentOffset?:=?int64(0)?
????currentLength?:=?int64(0)?
????for?index,?offset?:=?range?videoSampleOffsets.Offset[videoSampleIndexRange.Start:videoSampleIndexRange.Stop]?{?
????????for?_,?track?:=?range?clipVideo.MOOV.TRAK?{?
????????????//?視頻track?
????????????if?track.MDIA.HDLR.ComponentSubtype?==?"vide"?{?
????????????????if?currentOffset?==?0?{?
????????????????????currentOffset?=?int64(offset)?
????????????????????currentLength?=?int64(track.MDIA.MINF.STBL.STSZ.SampleSize[index])?
????????????????}?
????????????????//?如果內(nèi)存是連續(xù)的則合并長度待最后一次性讀取?
????????????????if?index+1?<=?videoSampleIndexRange.Stop-videoSampleIndexRange.Start?&&?uint64(track.MDIA.MINF.STBL.STSZ.SampleSize[index])+offset?==?videoSampleOffsets.Offset[index+1]?{?
????????????????????if?currentOffset?>?0?{?
????????????????????????currentLength?+=?int64(track.MDIA.MINF.STBL.STSZ.SampleSize[index])?
????????????????????}?
????????????????}?else?{?
????????????????????sampleContent?:=?reader.ReadBytesAt(currentLength,?currentOffset)?
????????????????????writer.WriteBytes(sampleContent)?
????????????????????currentOffset?=?0?
????????????????????currentLength?=?0?
????????????????}?
????????????????break?
????????????}?
????????}?
????}?
????//?音頻數(shù)據(jù)如果連續(xù),則合并長度,減少讀取次數(shù)?
????currentOffset?=?int64(0)?
????currentLength?=?int64(0)?
????//多音軌視頻,某音軌長度不足造成越界,直接補(bǔ)0?
????if?audioSampleIndexRange.Start?>?len(audioSampleOffsets.Offset)?||?audioSampleIndexRange.Stop?>?len(audioSampleOffsets.Offset)?{?
????????log.Println("Current?audio?track?length?not?enough?to?fit?the?cut?range!")?
????????for?_,?track?:=?range?clipVideo.MOOV.TRAK?{?
????????????if?track.MDIA.HDLR.ComponentSubtype?==?"soun"?{?
????????????????for?_,?sampleSize?:=?range?track.MDIA.MINF.STBL.STSZ.SampleSize?{?
????????????????????buf?:=?make([]byte,?sampleSize)?
????????????????????writer.WriteBytes(buf)?
????????????????}?
????????????}?
????????}?
????????return?
????}?
????for?index,?offset?:=?range?audioSampleOffsets.Offset[audioSampleIndexRange.Start:audioSampleIndexRange.Stop]?{?
????????for?_,?track?:=?range?clipVideo.MOOV.TRAK?{?
????????????//?音頻track?
????????????if?track.MDIA.HDLR.ComponentSubtype?==?"soun"?{?
????????????????if?currentOffset?==?0?{?
????????????????????currentOffset?=?int64(offset)?
????????????????????currentLength?=?int64(track.MDIA.MINF.STBL.STSZ.SampleSize[index])?
????????????????}?
????????????????//?如果內(nèi)存是連續(xù)的則合并長度待最后一次性讀取?
????????????????if?index+1?<=?audioSampleIndexRange.Stop-audioSampleIndexRange.Start?&&?uint64(track.MDIA.MINF.STBL.STSZ.SampleSize[index])+offset?==?audioSampleOffsets.Offset[index+1]?{?
????????????????????if?currentOffset?>?0?{?
????????????????????????currentLength?+=?int64(track.MDIA.MINF.STBL.STSZ.SampleSize[index])?
????????????????????}?
????????????????}?else?{?
????????????????????sampleContent?:=?reader.ReadBytesAt(currentLength,?currentOffset)?
????????????????????writer.WriteBytes(sampleContent)?
????????????????????currentOffset?=?0?
????????????????????currentLength?=?0?
????????????????}?
????????????????break?
????????????}?
????????}?
????}?
}
這樣就模擬了最原始的數(shù)據(jù)拷貝動(dòng)作。實(shí)際應(yīng)用效果對比看,優(yōu)化后的切割方式,比使用FFmpeg套件,效率提升了近2倍。這是對切割操作思路的一種轉(zhuǎn)換。
但是,這并不是優(yōu)化的結(jié)束。我們前面談到業(yè)界通行做法,都用到了服務(wù)器的多核處理。多核優(yōu)化利用了機(jī)器的最大性能,是最基本的優(yōu)化方式。那么,我們能不能在這方面再考慮入手呢?
是的,我們又在編程語言上微創(chuàng)新了一下。巧合的是,我們當(dāng)時(shí)正在準(zhǔn)備用Golang來做長鏈接系統(tǒng)的服務(wù)。程序員靈光乍現(xiàn),用Golang實(shí)現(xiàn)了上述操作邏輯,順便開了“一些” goroutine來做復(fù)制切割數(shù)據(jù)的動(dòng)作。把每個(gè)goroutine模擬成一個(gè)FFmpeg切割進(jìn)程,這樣在同一臺(tái)服務(wù)器上,每個(gè)內(nèi)核線程上就運(yùn)行著多個(gè)"goroutine形式的FFmpeg"切割JOB。簡略的主流程代碼如下:
func?main()?{?
//?解碼源視頻?
????rawVideo?:=?new(demux.VideoStructure)?
????demux.Demux(rawVideo,?videoFilePath)?
????//?并發(fā)編碼多個(gè)片段視頻?
????start?=?time.Now()?
????pool?:=?util.NewRoutinePool(len(clipTimes))?
????for?_,?clipTime?:=?range?clipTimes?{?
????????go?func(clipTime?*remux.ClipTime)?{?
????????????pool.AddOne()?
????????????defer?pool.DelOne()?
????????????//?編碼片段視頻?
????????????clipVideo?:=?new(demux.VideoStructure)?
????????????videoOffsets,?audioOffsets,?videoRange,?audioRange,?err?:=?remux.Remux(rawVideo,?clipVideo,?clipTime)?
????????????//?導(dǎo)出片段視頻?
????????????remux.Generate(clipVideo,videoOffsets,audioOffsets,videoRange,audioRange,clipPath,?clipPrefix,?clipTime,?videoFilePath)?
????????}(clipTime)?
????}?
????pool.Wait()?
}
經(jīng)過此番轉(zhuǎn)換后,一臺(tái)服務(wù)器上的剪切視頻操作,就從FFmpeg切割方案的“單進(jìn)程/M線程”轉(zhuǎn)換成“M線程xN協(xié)程"模式。(M為CPU內(nèi)核數(shù),N為單內(nèi)核上的goroutine數(shù))
在編程語言層面上的”誤打誤撞“并發(fā)處理后,切割效率又得到了進(jìn)一步的提升。經(jīng)過效果對比驗(yàn)證,比使用FFmpeg套件的單進(jìn)程方式,效率提升了20~80倍。最終影響整個(gè)切割效率,成為瓶頸的,是硬盤的IO性能。
在此基礎(chǔ)上,將單臺(tái)服務(wù)器擴(kuò)展至分布式服務(wù)集群。這樣的視頻切割JOB集群,帶來的是超高效率的視頻切割處理流程。
五、存在的問題
方案經(jīng)過優(yōu)化后,在視頻切割方面,已經(jīng)將效率提高了至少10倍以上。但同時(shí)優(yōu)化過程中也有一些問題呈現(xiàn)出來。
1. 首先,就是適配的視頻封裝格式單一的問題。因?yàn)槲覀兊臄?shù)據(jù)源比較單一,基本是MP4封裝格式,所以在初期,切割程序只需要解析MP4封裝格式相關(guān)定義字段即可。不過網(wǎng)絡(luò)上視頻流媒體格式非常豐富,即使常用的也有4、5種。對此,我們后續(xù)添加了對另2種比較常見的FLV與TS封裝格式的支持,滿足了業(yè)務(wù)的正常需求。但是,仍然與FFmpeg套件的廣泛適用性相去甚遠(yuǎn)。畢竟FFmpeg積累這么些年兼容了幾乎所有的媒體格式,這也是用FFmpeg套件被廣泛選擇,且相對更簡潔易用的原因。
2. 另外,在實(shí)際計(jì)算起止切割點(diǎn)時(shí),往往會(huì)出現(xiàn)當(dāng)前切割點(diǎn)的時(shí)間上并不是關(guān)鍵幀,導(dǎo)致部分?jǐn)?shù)據(jù)無法被正確解碼的問題。對此,我們也做了簡單的處理:對于切割點(diǎn)上非關(guān)鍵幀的情況,我們的程序會(huì)自動(dòng)往前/往后找到上一個(gè)/下一個(gè)關(guān)鍵幀的時(shí)間點(diǎn),并以此時(shí)間點(diǎn)為基準(zhǔn),重新計(jì)算數(shù)據(jù)后再行切割。這樣才能保證所有切割出來的視頻是確定能被解碼的。經(jīng)過測試,對切割效率的影響幾乎可以忽略不計(jì)。并且,我們也正在著手進(jìn)行優(yōu)化的“補(bǔ)幀”形式的精確起止點(diǎn)方案。
3. 還有,視頻媒體源文件非標(biāo)的處理問題。實(shí)際生產(chǎn)過程中,經(jīng)常會(huì)發(fā)現(xiàn)數(shù)據(jù)源提供的視頻文件里,有1路以上的音頻流,而且經(jīng)常性出現(xiàn)幾路音頻流中,都是無效的錯(cuò)誤數(shù)據(jù)。這種情況在實(shí)際生產(chǎn)中會(huì)影響到數(shù)據(jù)切割后的音視頻同步出錯(cuò),導(dǎo)致無法切割成功,或者播放失敗。我們對不同的情況進(jìn)行分析后,找到幾種思路/模式來解決:
(1)分析并保留正確的音頻流數(shù)據(jù)。這對部分非現(xiàn)場錄制的視頻文件比較有效,絕大多數(shù)PGC生產(chǎn)的視頻文件均可適用此模式。
(2)切割拷貝數(shù)據(jù)時(shí)不包括音頻流數(shù)據(jù)。這意味著切割后的視頻沒有聲音。大多數(shù)賽事直播現(xiàn)場錄制的視頻可應(yīng)用此模式。
(3)對于無法分析正確且不能丟棄原始音頻流數(shù)據(jù)的文件,作“降級(jí)”處理,改用FFmpeg套件接手切割工作,保證生產(chǎn)出正確的視頻文件。
六、分析與小結(jié)
從解決方案的拆分模塊角度看,任何環(huán)節(jié)的優(yōu)化提升都是對整個(gè)方案的效率有積極的促進(jìn)作用。故而,我們對整個(gè)視頻剪切流程進(jìn)行梳理劃分。整理出視頻數(shù)據(jù)切割操作中的不同模塊。
優(yōu)化方案的核心思路,主要是對數(shù)據(jù)處理模塊進(jìn)行效率提升。其關(guān)鍵點(diǎn)在于:
1. 單個(gè)剪切需求轉(zhuǎn)換為數(shù)據(jù)拷貝的JOB。
2. JOB由進(jìn)程轉(zhuǎn)換為協(xié)程化處理。
3. 集群分布式處理JOB列表。
雖然在實(shí)際生產(chǎn)使用過程中,仍然不斷有出現(xiàn)或大或小的坑,但是這都不影響我們在追求更高生產(chǎn)效率的路上繼續(xù)前行。只要能提升效率,任何微小的創(chuàng)新都在我們的持續(xù)不懈的優(yōu)化范圍之中,這也正是蘇寧的造極精神的體現(xiàn)。
總結(jié)
以上是生活随笔為你收集整理的FFmpeg优化 苏宁PP体育视频剪切效率提升技巧的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 即构科技金健忠:回顾20年音视频技术演进
- 下一篇: 吴晓然:实时通信需要Codec和网络模块