1. 程式人生 > 程式設計 >Python實現FLV視訊拼接功能

Python實現FLV視訊拼接功能

文章摘要

本文簡單說明了FLV檔案的格式,以此為出發點,使用 Python 實現FLV視訊的拼接。

一.FLV檔案格式

關於FLV檔案格式的解析網上有諸多文章,在這裡就簡單介紹一下需要了解的部分,以便讀者更好地明白各段程式碼的功能。

FLV檔案是由檔案頭(Header)和檔案體(Body)按順序拼接而成。審查FLV內容時,以二進位制方式讀取內容。

Header:檔案頭表明了檔案的封裝格式為FLV,儲存物件為音訊、視訊或兩者。
以下為FLV檔案的Header,共 9 個位元組:

b'FLV\x01\x05\x00\x00\x00\t'
前 3 個位元組(FLV)說明這是一個FLV檔案
第 4 個位元組(\x01)為版本號,固定為 1

第 5 個位元組(\x05)表明儲存物件,需將其轉化成二進位制(00000101)檢視,左、右邊的 1 分別表示檔案含有音訊和視訊
後 4 個位元組(\x00\x00\x00\t)表示檔案頭的長度,其值固定為 9

Body:檔案體由若干個 Tag 組成,除了第一個,每個 Tag 是由頭部( 11 位元組)、主體(不定長)和尾部( 4 位元組)組成。第一個 Tag 只有尾部。

Tag 又分為 3 類,指令碼(scripts)、音訊(audio)和視訊(video)。通常第 2 個 Tag 為指令碼型別,且只有一個,後續的都是音視訊型別。

以下為指令碼 Tag 的部分,作為示例介紹一下:

頭部:b'\x12\x00\tb\x00\x00\x00\x00\x00\x00\x00'

第 1 個位元組(\x12)表示 Tag 型別,指令碼型別的對應值為 18 ,音訊為 8 ,視訊為 9
第 2-4 個位元組(\x00\tb)表示 Tag 主體的長度,此處為 2402
第 5-7 個位元組(\x00\x00\x00)為時間戳,指令碼型別的時間戳通常為 0
第 8 個位元組(\x00)是時間戳的擴充套件,當前 3 個位元組不夠用時會用這個位元組當作大端
後 3 個位元組(\x00\x00\x00)是 Stream id,固定為 0

主體:指令碼 Tag 的主體包含FLV視訊的基本資訊,如時長、大小、解析度等,比較複雜,在此不作介紹

尾部:b'\x00\x00\tm'

固定 4 位元組,表示 Tag 頭部加主體的長度,即 11 + 2402 = 2413

二.FLV視訊拼接

將多個FLV視訊合成一個可以正常播放的視訊,便足夠滿足大部分的需求。因此,在接下來的拼接過程中,不會對FLV進行細緻入微的調整,達到基本要求即可。

設定閱讀器

閱讀器可以使我們很方便地讀取檔案內容。

class Reader():
  def __init__(self,content): # content (bytes):FLV檔案的二進位制內容
    self.content = content
    self.start = 0
    self.eof = False # 判斷是否已讀完全部內容
    self.length = len(self.content)
    
  def read(self,n=1):
    # 設定 if 語句防止過度讀取內容
    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

在這裡假設要拼接的視訊基本資訊相似,即都含有音視訊,解析度、位元速率等相同或相近。

為了生成一個可以正常播放的FLV視訊,Header 和 Tag 是必不可少的。我們可以選取第一個FLV的檔案頭寫入新建FLV中,然後依次將修改過時間戳的 Tag 寫入其中,便可達到拼接目的。

def add_flv(flv,target,videoTimeStamp,audioTimeStamp): # 修改並新增 Tag 的函式
  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 位元組轉換成整數
      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') # 將整數轉換成 3 位元組
      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): # 主函式
  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,videoTS,audioTS)

拼接

import time
since = time.time()
flvs = ['m1.flv','m2.flv','m3.flv','m4.flv'] # 視訊大小:45MB,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 秒。

總結

FLV檔案格式還是比較簡明的,對資料的要求也是比較寬鬆的,即便沒有對 Scripts 裡的引數作調整,拼接後的視訊依然能夠正常播放。

不過,拼接的視訊是有不少隱形問題,如到視訊末尾可能會出現音畫不同步( 0.5 秒左右)的現象,以及不能夠方便地分離出完整的視訊和音訊。

以上所述是小編給大家介紹的Python實現FLV視訊拼接功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對我們網站的支援!
如果你覺得本文對你有幫助,歡迎轉載,煩請註明出處,謝謝!