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元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

