• C# 版 flvmerge:快速合并多个flv文件


    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 文件由1header和若干个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来处理。

     

  • 相关阅读:
    MyEclipse或者Eclipse内存溢出问题
    关于分布式事务、两阶段提交协议、三阶提交协议
    Linux查看CPU和内存使用情况(转)
    Linux中设定umask的作用
    Linux解决乱码问题
    Vi编辑器的使用
    Eclipse的SVN插件下载
    Netty 入门初体验
    聊聊spring的那些扩展机制
    FreeMarker入门
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3442389.html
Copyright © 2020-2023  润新知