文章摘要
本文簡單說明了FLV文件的格式,以此為出發(fā)點,使用 Python 實現(xiàn)FLV視頻的拼接。
一.FLV文件格式
關(guān)于FLV文件格式的解析網(wǎng)上有諸多文章,在這里就簡單介紹一下需要了解的部分,以便讀者更好地明白各段代碼的功能。
FLV文件是由文件頭(Header)和文件體(Body)按順序拼接而成。審查FLV內(nèi)容時,以二進(jìn)制方式讀取內(nèi)容。
Header:文件頭表明了文件的封裝格式為FLV,存儲對象為音頻、視頻或兩者。
以下為FLV文件的Header,共 9 個字節(jié):
b'FLV\x01\x05\x00\x00\x00\t'
前 3 個字節(jié)(FLV)說明這是一個FLV文件
第 4 個字節(jié)(\x01)為版本號,固定為 1
第 5 個字節(jié)(\x05)表明存儲對象,需將其轉(zhuǎn)化成二進(jìn)制(00000101)查看,左、右邊的 1 分別表示文件含有音頻和視頻
后 4 個字節(jié)(\x00\x00\x00\t)表示文件頭的長度,其值固定為 9
Body:文件體由若干個 Tag 組成,除了第一個,每個 Tag 是由頭部( 11 字節(jié))、主體(不定長)和尾部( 4 字節(jié))組成。第一個 Tag 只有尾部。
Tag 又分為 3 類,腳本(scripts)、音頻(audio)和視頻(video)。通常第 2 個 Tag 為腳本類型,且只有一個,后續(xù)的都是音視頻類型。
以下為腳本 Tag 的部分,作為示例介紹一下:
頭部:b'\x12\x00\tb\x00\x00\x00\x00\x00\x00\x00'
第 1 個字節(jié)(\x12)表示 Tag 類型,腳本類型的對應(yīng)值為 18 ,音頻為 8 ,視頻為 9
第 2-4 個字節(jié)(\x00\tb)表示 Tag 主體的長度,此處為 2402
第 5-7 個字節(jié)(\x00\x00\x00)為時間戳,腳本類型的時間戳通常為 0
第 8 個字節(jié)(\x00)是時間戳的擴(kuò)展,當(dāng)前 3 個字節(jié)不夠用時會用這個字節(jié)當(dāng)作大端
后 3 個字節(jié)(\x00\x00\x00)是 Stream id,固定為 0
主體:腳本 Tag 的主體包含F(xiàn)LV視頻的基本信息,如時長、大小、分辨率等,比較復(fù)雜,在此不作介紹
尾部:b'\x00\x00\tm'
固定 4 字節(jié),表示 Tag 頭部加主體的長度,即 11 + 2402 = 2413
二.FLV視頻拼接
將多個FLV視頻合成一個可以正常播放的視頻,便足夠滿足大部分的需求。因此,在接下來的拼接過程中,不會對FLV進(jìn)行細(xì)致入微的調(diào)整,達(dá)到基本要求即可。
設(shè)置閱讀器
閱讀器可以使我們很方便地讀取文件內(nèi)容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class Reader(): def __init__( self , content): # content (bytes):FLV文件的二進(jìn)制內(nèi)容 self .content = content self .start = 0 self .eof = False # 判斷是否已讀完全部內(nèi)容 self .length = len ( self .content) def read( self , n = 1 ): # 設(shè)置 if 語句防止過度讀取內(nèi)容 if self .length > ( self .start + n): out = self .content[ self .start: self .start + n] self .start + = n else : out = self .content[ self .start:] self .eof = True return out |
向新建FLV文件寫入 Header 和 Tag
在這里假設(shè)要拼接的視頻基本信息相似,即都含有音視頻,分辨率、碼率等相同或相近。
為了生成一個可以正常播放的FLV視頻,Header 和 Tag 是必不可少的。我們可以選取第一個FLV的文件頭寫入新建FLV中,然后依次將修改過時間戳的 Tag 寫入其中,便可達(dá)到拼接目的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
def add_flv(flv, target, videoTimeStamp, audioTimeStamp): # 修改并添加 Tag 的函數(shù) with open (flv, 'rb' ) as f: content = f.read() reader = Reader(content) header = reader.read( 13 ) with open (target, 'ab' ) as f: while not reader.eof: # 一直讀取直到讀完,此時 reader.eof = True dataType = reader.read( 1 ) dataSize = reader.read( 3 ) timeStamp = int .from_bytes(reader.read( 3 ), 'big' ) # 將 3 字節(jié)轉(zhuǎn)換成整數(shù) headerRemained = reader.read( 4 ) if dataType = = b '\t' : # 視頻 timeStamp + = videoTimeStamp videoTS = timeStamp if dataType = = b '\x08' : # 音頻 timeStamp + = audioTimeStamp audioTS = timeStamp timeStamp = timeStamp.to_bytes( 3 , 'big' ) # 將整數(shù)轉(zhuǎn)換成 3 字節(jié) tagHeader = dataType + dataSize + timeStamp + headerRemained tagData_andSize = reader.read( int .from_bytes(dataSize, 'big' ) + 4 ) f.write(tagHeader) f.write(tagData_andSize) return videoTS, audioTS def merge_flv(flvs, target): # 主函數(shù) videoTS = 0 audioTS = 0 for i, flv in enumerate (flvs): with open (flv, 'rb' ) as f: content = f.read() reader = Reader(content) header = reader.read( 13 ) # flvHeader + tagSize0 if i = = 0 : # 寫入第 1 個FLV視頻的文件頭 with open (target, 'wb' ) as f: f.write(header) videoTS, audioTS = add_flv(flv, target, videoTS, audioTS) |
拼接
1
2
3
4
5
6
7
8
|
import time since = time.time() flvs = [ 'm1.flv' , 'm2.flv' , 'm3.flv' , 'm4.flv' ] # 視頻大?。?5MB,20MB,59MB,54MB target = 't.flv' merge_flv(flvs, target) end = time.time() print ( 'Merging flvs takes {:.2f} s' . format (end - since)) # Merging flvs takes 0.88 s |
可以看到,拼接 4 個共 178MB視頻用時 0.88 秒。
總結(jié)
FLV文件格式還是比較簡明的,對數(shù)據(jù)的要求也是比較寬松的,即便沒有對 Scripts 里的參數(shù)作調(diào)整,拼接后的視頻依然能夠正常播放。
不過,拼接的視頻是有不少隱形問題,如到視頻末尾可能會出現(xiàn)音畫不同步( 0.5 秒左右)的現(xiàn)象,以及不能夠方便地分離出完整的視頻和音頻。
以上所述是小編給大家介紹的Python實現(xiàn)FLV視頻拼接功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對服務(wù)器之家網(wǎng)站的支持!
如果你覺得本文對你有幫助,歡迎轉(zhuǎn)載,煩請注明出處,謝謝!
原文鏈接:https://blog.csdn.net/Okery/article/details/104054228