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 代码:
void Main()
{
String path1 = "D:\\Videos\\Subtitle\\OutputCache\\1.flv";
String path2 = "D:\\Videos\\Subtitle\\OutputCache\\2.flv";
String path3 = "D:\\Videos\\Subtitle\\OutputCache\\3.flv";
String output = "D:\\Videos\\Subtitle\\OutputCache\\output.flv"; using(FileStream fs1 = new FileStream(path1, FileMode.Open))
using(FileStream fs2 = new FileStream(path2, FileMode.Open))
using(FileStream fs3 = new FileStream(path3, FileMode.Open))
using(FileStream fsMerge = new FileStream(output, FileMode.Create))
{
Console.WriteLine(IsFLVFile(fs1));
Console.WriteLine(IsFLVFile(fs2));
Console.WriteLine(IsFLVFile(fs3)); if(IsSuitableToMerge(GetFLVFileInfo(fs1),GetFLVFileInfo(fs2)) == false
|| IsSuitableToMerge(GetFLVFileInfo(fs1),GetFLVFileInfo(fs3)) == false)
{
Console.WriteLine("Video files not suitable to merge");
} int time = Merge(fs1,fsMerge,true,);
time = Merge(fs2,fsMerge,false,time);
time = Merge(fs3,fsMerge,false,time);
Console.WriteLine("Merge finished");
}
} const int FLV_HEADER_SIZE = ;
const int FLV_TAG_HEADER_SIZE = ;
const int MAX_DATA_SIZE = ; class FLVContext
{
public byte soundFormat;
public byte soundRate;
public byte soundSize;
public byte soundType;
public byte videoCodecID;
} bool IsSuitableToMerge(FLVContext flvCtx1, FLVContext flvCtx2)
{
return (flvCtx1.soundFormat == flvCtx2.soundFormat) &&
(flvCtx1.soundRate == flvCtx2.soundRate) &&
(flvCtx1.soundSize == flvCtx2.soundSize) &&
(flvCtx1.soundType == flvCtx2.soundType) &&
(flvCtx1.videoCodecID == flvCtx2.videoCodecID);
} bool IsFLVFile(FileStream fs)
{
int len;
byte[] buf = new byte[FLV_HEADER_SIZE];
fs.Position = ;
if( FLV_HEADER_SIZE != fs.Read(buf,,buf.Length))
return false; if (buf[] != 'F' || buf[] != 'L' || buf[] != 'V' || buf[] != 0x01)
return false;
else
return true;
} FLVContext GetFLVFileInfo(FileStream fs)
{
bool hasAudioParams, hasVideoParams;
int skipSize, readLen;
int dataSize;
byte tagType;
byte[] tmp = new byte[FLV_TAG_HEADER_SIZE+];
if (fs == null) return null; FLVContext flvCtx = new FLVContext();
fs.Position = ;
skipSize = ;
fs.Position += skipSize;
hasVideoParams = hasAudioParams = false;
skipSize = ;
while (!hasVideoParams || !hasAudioParams)
{
fs.Position += skipSize; if (FLV_TAG_HEADER_SIZE+ != fs.Read(tmp,,tmp.Length))
return null; tagType = (byte)(tmp[] & 0x1f);
switch (tagType)
{
case :
flvCtx.soundFormat = (byte)((tmp[FLV_TAG_HEADER_SIZE] & 0xf0) >> ) ;
flvCtx.soundRate = (byte)((tmp[FLV_TAG_HEADER_SIZE] & 0x0c) >> ) ;
flvCtx.soundSize = (byte)((tmp[FLV_TAG_HEADER_SIZE] & 0x02) >> ) ;
flvCtx.soundType = (byte)((tmp[FLV_TAG_HEADER_SIZE] & 0x01) >> ) ;
hasAudioParams = true;
break;
case :
flvCtx.videoCodecID = (byte)((tmp[FLV_TAG_HEADER_SIZE] & 0x0f));
hasVideoParams = true;
break;
default :
break;
} dataSize = FromInt24StringBe(tmp[],tmp[],tmp[]);
skipSize = dataSize - + ;
} return flvCtx;
} int FromInt24StringBe(byte b0, byte b1, byte b2)
{
return (int)((b0<<) | (b1<<) | (b2));
} int GetTimestamp(byte b0, byte b1, byte b2, byte b3)
{
return ((b3<<) | (b0<<) | (b1<<) | (b2));
} void SetTimestamp(byte[] data, int idx, int newTimestamp)
{
data[idx + ] = (byte)(newTimestamp>>);
data[idx + ] = (byte)(newTimestamp>>);
data[idx + ] = (byte)(newTimestamp>>);
data[idx + ] = (byte)(newTimestamp);
} int Merge(FileStream fsInput, FileStream fsMerge, bool isFirstFile, int lastTimestamp = )
{
int readLen;
int curTimestamp = ;
int newTimestamp = ;
int dataSize;
byte[] tmp = new byte[];
byte[] buf = new byte[MAX_DATA_SIZE]; fsInput.Position = ;
if (isFirstFile)
{
if(FLV_HEADER_SIZE+ == (fsInput.Read(tmp,,FLV_HEADER_SIZE+)))
{
fsMerge.Position = ;
fsMerge.Write(tmp,,FLV_HEADER_SIZE+);
}
}
else
{
fsInput.Position = FLV_HEADER_SIZE + ;
} while(fsInput.Read(tmp, , FLV_TAG_HEADER_SIZE) > )
{
dataSize = FromInt24StringBe(tmp[],tmp[],tmp[]);
curTimestamp = GetTimestamp(tmp[],tmp[],tmp[],tmp[]);
newTimestamp = curTimestamp + lastTimestamp;
SetTimestamp(tmp,, newTimestamp);
fsMerge.Write(tmp,,FLV_TAG_HEADER_SIZE); readLen = dataSize+;
if (fsInput.Read(buf,,readLen) > ) {
fsMerge.Write(buf, , readLen);
} else {
goto failed;
}
} return newTimestamp; failed:
throw new Exception("Merge Failed");
}
测试通过,合并速度很快!
不过,这个方法有一个缺点:没有将各个文件里的关键帧信息合并,这个关键帧信息,切分flv文件时很重要,合并时就没那么重要了。如果确实需要的话,可以用 yamdi 来处理。
C# 版 flvmerge:快速合并多个flv文件的更多相关文章
- 批处理快速合并多分Excel文件并将指定列的数据去重复
1.批处理快速合并多个excel文件方法: 新建一个.txt文本文件,就命名为合并.txt吧. 而后开启文件,复制以下代码到文件中: @echo off E: cd xls dir copy *.cs ...
- C# 版dll 程序集合并工具
C# 版dll 程序集合并工具 最近要开发一个控件给同事用,开发中会引用一些第三方DLL,这样交给用户很不方便,希望的效果是直接交付一个DLL文件.网上找了一些资料. 1. 使用 Cost ...
- 【前端】一句命令快速合并压缩 JS、CSS
引用自:一句命令快速合并 JS.CSS 在项目开发环境下,我们会把 JS 代码尽可能模块化,方便管理和修改,这就避免不了会出现一个项目自身 JS 文件数量达到10个或者更多. 而项目上线后,会要求将所 ...
- Excel 批量快速合并相同的单元格:数据透视表、宏代码、分类汇总
Excel 批量快速合并相同的单元格 在制作Excel表格的时候,为了使得自己制作的报表更加简洁明了,方便查阅,经常需要合并很多相同的单元格,如果有几千几万条记录需要合并的话,真的会让人发疯.怎样 ...
- 使用ffmpeg批量合并flv文件
title: 使用ffmpeg批量合并flv文件 toc: false date: 2018-10-14 16:08:19 categories: methods tags: ffmpeg flv 使 ...
- 152-技巧-Power Query 快速合并文件夹中表格之自定义函数 TableXlsxCsv
152-技巧-Power Query 快速合并文件夹中表格之自定义函数 TableXlsxCsv 附件下载地址:https://jiaopengzi.com/2602.html 一.背景 在我们使用 ...
- 如何快速合并多个TXT文本内容
工作中有时候需要合并很多文本内容,例如一些推送清单之类,一个一个打开去复制粘贴的话,少量还行,如果txt文本数据量大(10+M以上)且文件数量多(成百上千),这种方式就显得很低效了.具体要求如下: ...
- 【转】打包AAC码流到FLV文件
AAC编码后数据打包到FLV很简单.1. FLV音频Tag格式 字节位置 意义0x08, ...
- C# 合并和拆分PDF文件
一.合并和拆分PDF文件的方式 PDF文件使用了工业标准的压缩算法,易于传输与储存.它还是页独立的,一个PDF文件包含一个或多个"页",可以单独处理各页,特别适合多处理器系统的工作 ...
随机推荐
- [11]APUE:(文件)记录锁
[a] 概念 建议锁:在遵循相同记录锁规则的进程间生效,通常用于保证某个程序自身多个进程间的数据一致性 强制锁:意在保证所有进程间的数据一致性,但不一定有效:如不能应对先 unlink 后建立同名副本 ...
- VS与ultraedit 正则表达式替换
ASP中把request("{param}")调用替换为requestX("{param}") VS 表达式替换(?<a>request\(&quo ...
- js时间处理函数
Date 对象的方法简介: ·Date | 返回当日的日期和时间 ·getDate | 从 Date 对象返回一个月中的某一天 (1 ~ 31) ·getDay | 从 Date 对象返回一周中 ...
- 设置代码Code高亮显示成蓝色
下面方法是让设置的关键字高亮显示,考虑到了注释与字符串的影响,所以备用,以便将来能够用到. private static void ColorizeCode(RichTextBox rtb) { st ...
- Mac:文件夹树型展示 tree
目标: 想要在MAC的Terminal中查看文件夹中所有文件的树型结构及文件夹.文件树统计. 安装方法: 1.brew安装 官网:http://brew.sh/ brew是Mac中安装软件的神器,一定 ...
- [UCSD白板题] Changing Money
Problem Introduction In this problem,you will design an algorithm for changing money optimally. Prob ...
- iscroll.js 移动端手触滚动效果。
function loaded() { var myscroll=new iScroll("wrapper",{hScrollbar:false, vScrollbar:fals ...
- 学习django之正则表达式的语法
正则表达式 正则表达式的常用语法: 1)单个字符: . 任意的一个字符 a|b 字符a或字符b [afg] a或者f或者g的一个字符 [0-4] 0 ...
- 安卓模拟器的报错This AVD's configuration is missing a kernel file!!
安卓模拟器的报错: 可能的原因是target设置问题:
- HttpServletRequest的Attribute和Parameter区别
HttpServletRequest类既有getAttribute()方法,也由getParameter()方法,这两个方法有以下的组件通过getParameter()方法来获得请求参数,例如假定we ...