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文件包含一个或多个"页",可以单独处理各页,特别适合多处理器系统的工作 ...
随机推荐
- archive成功了,但是在输出ipa时要求有账号密码
archive成功了,但是在输出ipa时要求有账号密码 可以通过命令行方式输出,绕开账号检查1.进入Windows-Organizer-Archives,找到要输出的scarchive列表项:2.右键 ...
- DIV弹出和关闭新DIV
代码用HTML+JS实现: 代码(用HTML+JS实现): <!doctype html> <html lang="UTF-8"> <head> ...
- 王爽-汇编语言-综合研究四-不使用main函数编程
(一) 研究目的 使用C语言编程,我们一定要使用main函数么? (二) 研究过程 1) 最初的程序 首先,我们编写一个不写main函数的C语言程序. 程序如下: 在编译的过程中,没有发现错误.在链接 ...
- 大前端学习笔记整理【四】LESS基础
第一次接触CSS预编译,然后对比后发现其实less的上手容易度确实比sass高不少,再加上公司项目也是使用的less.所以想想还是根据网上的各种教程,整理出来了一些比较基础的.而且比较能让我们这种初学 ...
- Android中的桌面快捷方式
一.判断是否已有快捷方式 private String getAuthorityFromPermission(Context context, String permission){ if (perm ...
- 一种模拟position: sticky;的方法
直接上代码,本例采用了jQuery来进行介绍 CSS部分: .sticky { position: fixed; top: 0; } JS部分: var stickyBar = $('.sticky- ...
- configure: error: no acceptable C compiler found in $PAT 的解决方案
configure: error: no acceptable C compiler found in $PATH See `config.log' for more details.你的机器里没有安 ...
- IP地址框
//IP地址框 // 此段代码:独立的获取本机IP地址和计算机名 WORD wVersionRequested; WSADATA wsaData; char name[255]; CString ip ...
- Java中实现PHP中的urlencode与rawurlencode
php手册中对urlencode这样说明 在java中 URLEncoder做了这样注释 也就是说java中对星号"*"是不进行编码的 也就是说URLEncoder之后还是&quo ...
- 在app中打开appStore中其他app
var str = "https://itunes.apple.com/cn/app/zhang-jiange-hao-tou-zi-ke/id402382976?mt=8"//这 ...