C# 版 flvmerge:快速合并多個flv文件
網上的視頻很多都是分片的 flv 文件,怎么把他們合為一體呢? GUI 工具就不考慮了,不適合批量執行,不適合在后臺運行。有沒有命令行工具或庫可以實現呢?
ffmpeg ? 提供了一個方法:
(1)先把 flv 文件轉換成 mpeg ;
(2)將多個 mpeg 文件合并成 1 個獨立的 mpeg 文件(二進制合并即可)
(3)將獨立的 mpeg 文件轉換成獨立的 flv 文件。
?網上搜到的最多的也是這種解決辦法。這種方法有兩個缺點:
(1)需要兩遍轉碼,非常耗時;
(2)轉換后的獨立的 mpeg 文件比原視頻要短一點點。
?木有辦法了,只好另尋他路。有人說有一個 flvmerge.exe? 程序可以將多個 flv 合并成一個,可惜的是俺搜了很久,都沒找到這個程序,最后還是在一款免費軟件里把這個“ flvmerge.exe ”文件給揪出來了,不幸的是,這個“ flvmerge.exe ”得不到正確的結果。
潤之同學說過,自己動手,豐衣足食。上? github? 上搜“ flvmerge ”,發現兩個項目,“ flvmerge ”和“ flvmerger ”,都是 C 寫的。前者不依賴于第三方庫,后者依賴于第三方庫,那么就從第一個開始吧。
看了看它的代碼,知道了 flv 文件合并的原理:
(1)?flv? 文件由 1 個 header 和若干個 tag 組成;
(2)?header 記錄了視頻的元數據;
(3)?tag? 是有時間戳的數據;
(4)?flv 合并的原理就是把多個文件里的 tag 組裝起來,調整各 tag 的時間戳,再在文件起始處按個頭部。
下面是我參照? flvmerge? 項目,用 linqpad 寫的一個 C# 版本的? flvmerge? 代碼:
?
1 void Main() 2 { 3 String path1 = " D:\\Videos\\Subtitle\\OutputCache\\1.flv " ; 4 String path2 = " D:\\Videos\\Subtitle\\OutputCache\\2.flv " ; 5 String path3 = " D:\\Videos\\Subtitle\\OutputCache\\3.flv " ; 6 String output = " D:\\Videos\\Subtitle\\OutputCache\\output.flv " ; 7 8 using (FileStream fs1 = new FileStream(path1, FileMode.Open)) 9 using (FileStream fs2 = new FileStream(path2, FileMode.Open)) 10 using (FileStream fs3 = new FileStream(path3, FileMode.Open)) 11 using (FileStream fsMerge = new FileStream(output, FileMode.Create)) 12 { 13 Console.WriteLine(IsFLVFile(fs1)); 14 Console.WriteLine(IsFLVFile(fs2)); 15 Console.WriteLine(IsFLVFile(fs3)); 16 17 if (IsSuitableToMerge(GetFLVFileInfo(fs1),GetFLVFileInfo(fs2)) == false 18 || IsSuitableToMerge(GetFLVFileInfo(fs1),GetFLVFileInfo(fs3)) == false ) 19 { 20 Console.WriteLine( " Video files not suitable to merge " ); 21 } 22 23 int time = Merge(fs1,fsMerge, true , 0 ); 24 time = Merge(fs2,fsMerge, false ,time); 25 time = Merge(fs3,fsMerge, false ,time); 26 Console.WriteLine( " Merge finished " ); 27 } 28 } 29 30 const int FLV_HEADER_SIZE = 9 ; 31 const int FLV_TAG_HEADER_SIZE = 11 ; 32 const int MAX_DATA_SIZE = 16777220 ; 33 34 class FLVContext 35 { 36 public byte soundFormat; 37 public byte soundRate; 38 public byte soundSize; 39 public byte soundType; 40 public byte videoCodecID; 41 } 42 43 bool IsSuitableToMerge(FLVContext flvCtx1, FLVContext flvCtx2) 44 { 45 return (flvCtx1.soundFormat == flvCtx2.soundFormat) && 46 (flvCtx1.soundRate == flvCtx2.soundRate) && 47 (flvCtx1.soundSize == flvCtx2.soundSize) && 48 (flvCtx1.soundType == flvCtx2.soundType) && 49 (flvCtx1.videoCodecID == flvCtx2.videoCodecID); 50 } 51 52 bool IsFLVFile(FileStream fs) 53 { 54 int len; 55 byte [] buf = new byte [FLV_HEADER_SIZE]; 56 fs.Position = 0 ; 57 if ( FLV_HEADER_SIZE != fs.Read(buf, 0 ,buf.Length)) 58 return false ; 59 60 if (buf[ 0 ] != ' F ' || buf[ 1 ] != ' L ' || buf[ 2 ] != ' V ' || buf[ 3 ] != 0x01 ) 61 return false ; 62 else 63 return true ; 64 } 65 66 FLVContext GetFLVFileInfo(FileStream fs) 67 { 68 bool hasAudioParams, hasVideoParams; 69 int skipSize, readLen; 70 int dataSize; 71 byte tagType; 72 byte [] tmp = new byte [FLV_TAG_HEADER_SIZE+ 1 ]; 73 if (fs == null ) return null ; 74 75 FLVContext flvCtx = new FLVContext(); 76 fs.Position = 0 ; 77 skipSize = 9 ; 78 fs.Position += skipSize; 79 hasVideoParams = hasAudioParams = false ; 80 skipSize = 4 ; 81 while (!hasVideoParams || ! hasAudioParams) 82 { 83 fs.Position += skipSize; 84 85 if (FLV_TAG_HEADER_SIZE+ 1 != fs.Read(tmp, 0 ,tmp.Length)) 86 return null ; 87 88 tagType = ( byte )(tmp[ 0 ] & 0x1f ); 89 switch (tagType) 90 { 91 case 8 : 92 flvCtx.soundFormat = ( byte )((tmp[FLV_TAG_HEADER_SIZE] & 0xf0 ) >> 4 ) ; 93 flvCtx.soundRate = ( byte )((tmp[FLV_TAG_HEADER_SIZE] & 0x0c ) >> 2 ) ; 94 flvCtx.soundSize = ( byte )((tmp[FLV_TAG_HEADER_SIZE] & 0x02 ) >> 1 ) ; 95 flvCtx.soundType = ( byte )((tmp[FLV_TAG_HEADER_SIZE] & 0x01 ) >> 0 ) ; 96 hasAudioParams = true ; 97 break ; 98 case 9 : 99 flvCtx.videoCodecID = ( byte )((tmp[FLV_TAG_HEADER_SIZE] & 0x0f )); 100 hasVideoParams = true ; 101 break ; 102 default : 103 break ; 104 } 105 106 dataSize = FromInt24StringBe(tmp[ 1 ],tmp[ 2 ],tmp[ 3 ]); 107 skipSize = dataSize - 1 + 4 ; 108 } 109 110 return flvCtx; 111 } 112 113 int FromInt24StringBe( byte b0, byte b1, byte b2) 114 { 115 return ( int )((b0<< 16 ) | (b1<< 8 ) | (b2)); 116 } 117 118 int GetTimestamp( byte b0, byte b1, byte b2, byte b3) 119 { 120 return ((b3<< 24 ) | (b0<< 16 ) | (b1<< 8 ) | (b2)); 121 } 122 123 void SetTimestamp( byte [] data, int idx, int newTimestamp) 124 { 125 data[idx + 3 ] = ( byte )(newTimestamp>> 24 ); 126 data[idx + 0 ] = ( byte )(newTimestamp>> 16 ); 127 data[idx + 1 ] = ( byte )(newTimestamp>> 8 ); 128 data[idx + 2 ] = ( byte )(newTimestamp); 129 } 130 131 int Merge(FileStream fsInput, FileStream fsMerge, bool isFirstFile, int lastTimestamp = 0 ) 132 { 133 int readLen; 134 int curTimestamp = 0 ; 135 int newTimestamp = 0 ; 136 int dataSize; 137 byte [] tmp = new byte [ 20 ]; 138 byte [] buf = new byte [MAX_DATA_SIZE]; 139 140 fsInput.Position = 0 ; 141 if (isFirstFile) 142 { 143 if (FLV_HEADER_SIZE+ 4 == (fsInput.Read(tmp, 0 ,FLV_HEADER_SIZE+ 4 ))) 144 { 145 fsMerge.Position = 0 ; 146 fsMerge.Write(tmp, 0 ,FLV_HEADER_SIZE+ 4 ); 147 } 148 } 149 else 150 { 151 fsInput.Position = FLV_HEADER_SIZE + 4 ; 152 } 153 154 while (fsInput.Read(tmp, 0 , FLV_TAG_HEADER_SIZE) > 0 ) 155 { 156 dataSize = FromInt24StringBe(tmp[ 1 ],tmp[ 2 ],tmp[ 3 ]); 157 curTimestamp = GetTimestamp(tmp[ 4 ],tmp[ 5 ],tmp[ 6 ],tmp[ 7 ]); 158 newTimestamp = curTimestamp + lastTimestamp; 159 SetTimestamp(tmp, 4 , newTimestamp); 160 fsMerge.Write(tmp, 0 ,FLV_TAG_HEADER_SIZE); 161 162 readLen = dataSize+ 4 ; 163 if (fsInput.Read(buf, 0 ,readLen) > 0 ) { 164 fsMerge.Write(buf, 0 , readLen); 165 } else { 166 goto failed; 167 } 168 } 169 170 return newTimestamp; 171 172 failed: 173 throw new Exception( " Merge Failed " ); 174 }
?
測試通過,合并速度很快 !
?
不過,這個方法有一個缺點:沒有將各個文件里的關鍵幀信息合并,這個關鍵幀信息,切分 flv 文件時很重要,合并時就沒那么重要了。如果確實需要的話,可以用? flvtool2 來處理。
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
