c#解析FLV文件

来源:互联网 发布:尚学堂白鹤翔js第二季 编辑:程序博客网 时间:2024/06/05 08:56

https://wuyuans.com/2012/09/parser-flv-using-csharp

在上一篇FLV文件格式解析中,我们对FLV的文件结构有了一定了解,现在我们就可以对FLV文件解析解析了。我这里用的是c#,只要理解了过程java、c++都是可以的。

废话少说,先上效果图:ParserFLV

1.工具类

在解析的过程中,我们会和byte做各种运算,所以我定义了一个byte工具类ByteUtils:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.IO;
  6. namespace FLVParer.Utils
  7. {
  8. class ByteUtils
  9. {
  10. public static uint ByteToUInt(byte[] bs, int length)
  11. {
  12. if (bs == null || bs.Length < length)
  13. return 0;
  14. uint rtn = 0;
  15. for (int i = 0; i < length; i++)
  16. {
  17. rtn <<= 8;
  18. rtn |= bs[i];
  19. }
  20. return rtn;
  21. }
  22. public static double ByteToDouble(byte[] bs)
  23. {
  24. if (bs == null || bs.Length < 8)
  25. return 0;
  26. byte[] b2 = new byte[8];
  27. for (int i = 0; i < 8; i++)
  28. {
  29. b2[i] = bs[7 - i];
  30. }
  31. return BitConverter.ToDouble(b2, 0);
  32. }
  33. public static short ReadUI16(Stream src)
  34. {
  35. byte[] bs = new byte[2];
  36. if (src.Read(bs, 0, 2) <= 0)
  37. return 0;
  38. return (short)((bs[0] << 8) | bs[1]);
  39. }
  40. public static uint ReadUI24(Stream src)
  41. {
  42. byte[] bs = new byte[3];
  43. if (src.Read(bs, 0, 3) <= 0)
  44. throw new IOException("Stream end.");
  45. return ByteToUInt(bs, 3);
  46. }
  47. public static uint ReadUI32(Stream src)
  48. {
  49. byte[] bs = new byte[4];
  50. if (src.Read(bs, 0, 4) <= 0)
  51. throw new IOException("Stream end.");
  52. return ByteToUInt(bs, 4);
  53. }
  54. public static string GetTime(uint time)
  55. {
  56. return (time / 60000).ToString() + ":"
  57. + (time / 1000 % 60).ToString("D2") + "."
  58. + (time % 1000).ToString("D3");
  59. }
  60. }
  61. }

2.FLV类

FLV类,主要的类,里面包括一个header和许多的tag,也就是一个FLV文件的结构:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.IO;
  6. using FLVParer.Utils;
  7. namespace FLVParer.Model
  8. {
  9. class FLV
  10. {
  11. public Header header { get; private set; }
  12. List<Tag> tags;
  13. public FLV(Stream stream)
  14. {
  15. header = new Header();
  16. header.readHeader(stream);
  17. stream.Seek(header.size, SeekOrigin.Begin);
  18. tags = new List<Tag>();
  19. while (stream.Position < stream.Length-4)
  20. {
  21. tags.Add(readTag(stream));
  22. }
  23. }
  24. private Tag readTag(Stream stream)
  25. {
  26. Tag tag = null;
  27. byte[] buf = new byte[4];
  28. stream.Read(buf, 0, 4);
  29. int type = stream.ReadByte();
  30. switch (type)
  31. {
  32. case 8:
  33. tag = new AudioTag();
  34. break;
  35. case 9:
  36. tag = new VideoTag();
  37. break;
  38. case 18:
  39. tag = new ScriptTag();
  40. break;
  41. }
  42. tag.presize = ByteUtils.ByteToUInt(buf, 4);
  43. tag.datasize = ByteUtils.ReadUI24(stream);
  44. tag.timestamp = ByteUtils.ReadUI24(stream);
  45. tag.timestamp_ex = stream.ReadByte();
  46. tag.streamid = ByteUtils.ReadUI24(stream);
  47. tag.readData(stream);
  48. return tag;
  49. }
  50. }
  51. }

2.1.Header类

Header类,保存FLV文件的头信息:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.IO;
  6. using FLVParer.Utils;
  7. namespace FLVParer.Model
  8. {
  9. class Header
  10. {
  11. public String type { get; private set; }
  12. public int version { get; private set; }
  13. public bool hasVideo { get; private set; }
  14. public bool hasAudio { get; private set; }
  15. public uint size { get; private set; }
  16. public void readHeader(Stream stream)
  17. {
  18. byte[] buf = new byte[4];
  19. stream.Read(buf, 0, 3);
  20. type = Encoding.Default.GetString(buf);
  21. stream.Read(buf, 0, 1);
  22. version = buf[0];
  23. stream.Read(buf, 0, 1);
  24. buf[0] &= 0x0f;
  25. if ((buf[0] & 0x01) == 1)
  26. {
  27. hasVideo = true;
  28. }
  29. if ((buf[0] & 0x04) == 4)
  30. {
  31. hasAudio = true;
  32. }
  33. stream.Read(buf, 0, 4);
  34. size = ByteUtils.ByteToUInt(buf, 4);
  35. }
  36. }
  37. }

2.2.Tag类

Tag类是一个抽象类,因为tag的种类有三种,为了统一管理,抽象出Tag类:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.IO;
  6. namespace FLVParer.Model
  7. {
  8. enum TagType
  9. {
  10. video,
  11. audio,
  12. Script
  13. }
  14. abstract class Tag
  15. {
  16. public TagType tagType;//tag类型
  17. public uint presize;//前一tag大小
  18. public uint datasize;//数据区大小
  19. public uint timestamp; //时间戳 单位ms
  20. public int timestamp_ex;//时间戳扩展
  21. public uint streamid;//ID
  22. public long offset;//偏移量
  23. public byte[] data;//数据
  24. //对tag进行读取
  25. public abstract void readData(Stream stream);
  26. }
  27. }

2.2.1.ScriptTag类

脚本tag类,继承自Tag类,并添加成员变量Values,用于保存脚本信息:

  1. using System.Collections.Generic;
  2. using System.Text;
  3. using System.IO;
  4. using FLVParer.Utils;
  5. namespace FLVParer.Model
  6. {
  7. class ScriptTag : Tag
  8. {
  9. public List<KeyValuePair<string, object>> Values { get; private set; }
  10. public ScriptTag()
  11. {
  12. tagType = TagType.Script;
  13. Values = new List<KeyValuePair<string, object>>();
  14. }
  15. public override void readData(Stream stream)
  16. {
  17. offset = 0;
  18. Values.Clear();
  19. byte[] bs = new byte[3];
  20. while (offset < this.datasize)
  21. {
  22. stream.Read(bs, 0, 3);
  23. if (bs[0] == 0 && bs[1] == 0 && bs[2] == 9)
  24. {
  25. offset += 3;
  26. break;
  27. }
  28. stream.Seek(-3, SeekOrigin.Current);
  29. AddElement("#" + offset, ReadElement(stream));
  30. }
  31. }
  32. private void AddElement(string key, object o)
  33. {
  34. Values.Add(new KeyValuePair<string, object>(key, o));
  35. }
  36. private object ReadElement(Stream src)
  37. {
  38. int type = src.ReadByte();
  39. offset++;
  40. switch (type)
  41. {
  42. case 0: // Number - 8
  43. return ReadDouble(src);
  44. case 1: // Boolean - 1
  45. return ReadByte(src);
  46. case 2: // String - 2+n
  47. return ReadString(src);
  48. case 3: // Object
  49. return ReadObject(src);
  50. case 4: // MovieClip
  51. return ReadString(src);
  52. case 5: // Null
  53. break;
  54. case 6: // Undefined
  55. break;
  56. case 7: // Reference - 2
  57. return ReadUShort(src);
  58. case 8: // ECMA array
  59. return ReadArray(src);
  60. case 10: // Strict array
  61. return ReadStrictArray(src);
  62. case 11: // Date - 8+2
  63. return ReadDate(src);
  64. case 12: // Long string - 4+n
  65. return ReadLongString(src);
  66. }
  67. return null;
  68. }
  69. private object ReadObject(Stream src)
  70. {
  71. byte[] bs = new byte[3];
  72. ScriptObject obj = new ScriptObject();
  73. while (offset < this.datasize)
  74. {
  75. src.Read(bs, 0, 3);
  76. if (bs[0] == 0 && bs[1] == 0 && bs[2] == 9)
  77. {
  78. offset += 3;
  79. break;
  80. }
  81. src.Seek(-3, SeekOrigin.Current);
  82. string key = ReadString(src);
  83. if (key[0] == 0)
  84. break;
  85. obj[key] = ReadElement(src);
  86. }
  87. return obj;
  88. }
  89. private double ReadDate(Stream src)
  90. {
  91. double d = ReadDouble(src);
  92. src.Seek(2, SeekOrigin.Current);
  93. offset += 2;
  94. return d;
  95. }
  96. private ScriptObject ReadArray(Stream src)
  97. {
  98. byte[] buffer = new byte[4];
  99. src.Read(buffer, 0, 4);
  100. offset += 4;
  101. uint count = ByteUtils.ByteToUInt(buffer, 4);
  102. ScriptObject array = new ScriptObject();
  103. for (uint i = 0; i < count; i++)
  104. {
  105. string key = ReadString(src);
  106. array[key] = ReadElement(src);
  107. }
  108. src.Seek(3, SeekOrigin.Current); // 00 00 09
  109. offset += 3;
  110. return array;
  111. }
  112. private ScriptArray ReadStrictArray(Stream src)
  113. {
  114. byte[] bs = new byte[4];
  115. src.Read(bs, 0, 4);
  116. offset += 4;
  117. ScriptArray array = new ScriptArray();
  118. uint count = ByteUtils.ByteToUInt(bs, 4);
  119. for (uint i = 0; i < count; i++)
  120. {
  121. array.Add(ReadElement(src));
  122. }
  123. return array;
  124. }
  125. private double ReadDouble(Stream src)
  126. {
  127. byte[] buffer = new byte[8];
  128. src.Read(buffer, 0, 8);
  129. offset += 8;
  130. return ByteUtils.ByteToDouble(buffer);
  131. }
  132. private byte ReadByte(Stream src)
  133. {
  134. offset++;
  135. return (byte)src.ReadByte();
  136. }
  137. private string ReadString(Stream src)
  138. {
  139. byte[] bs = new byte[2];
  140. src.Read(bs, 0, 2);
  141. offset += 2;
  142. int n = (int)ByteUtils.ByteToUInt(bs, 2);
  143. bs = new byte[n];
  144. src.Read(bs, 0, n);
  145. offset += n;
  146. return Encoding.ASCII.GetString(bs);
  147. }
  148. private string ReadLongString(Stream src)
  149. {
  150. byte[] bs = new byte[4];
  151. src.Read(bs, 0, 4);
  152. offset += 4;
  153. int n = (int)ByteUtils.ByteToUInt(bs, 4);
  154. bs = new byte[n];
  155. src.Read(bs, 0, n);
  156. offset += n;
  157. return Encoding.ASCII.GetString(bs);
  158. }
  159. private ushort ReadUShort(Stream src)
  160. {
  161. byte[] buffer = new byte[2];
  162. src.Read(buffer, 0, 2);
  163. offset += 2;
  164. return (ushort)ByteUtils.ByteToUInt(buffer, 2);
  165. }
  166. }
  167. public class ScriptObject
  168. {
  169. public static int indent = 0;
  170. private Dictionary<string, object> values = new Dictionary<string, object>();
  171. public object this[string key]
  172. {
  173. get
  174. {
  175. object o;
  176. values.TryGetValue(key, out o);
  177. return o;
  178. }
  179. set
  180. {
  181. if (!values.ContainsKey(key))
  182. {
  183. values.Add(key, value);
  184. }
  185. }
  186. }
  187. public override string ToString()
  188. {
  189. string str = "{\r\n";
  190. ScriptObject.indent += 2;
  191. foreach (KeyValuePair<string, object> kv in values)
  192. {
  193. str += new string(' ', ScriptObject.indent)
  194. + kv.Key + ": " + kv.Value + "\r\n";
  195. }
  196. ScriptObject.indent -= 2;
  197. //if (str.Length > 1)
  198. // str = str.Substring(0, str.Length - 1);
  199. str += "}";
  200. return str;
  201. }
  202. }
  203. public class ScriptArray
  204. {
  205. private List<object> values = new List<object>();
  206. public object this[int index]
  207. {
  208. get
  209. {
  210. if (index >= 0 && index < values.Count)
  211. return values[index];
  212. return null;
  213. }
  214. }
  215. public void Add(object o)
  216. {
  217. values.Add(o);
  218. }
  219. public override string ToString()
  220. {
  221. string str = "[";
  222. int n = 0;
  223. foreach (object o in values)
  224. {
  225. if (n % 10 == 0)
  226. str += "\r\n";
  227. n++;
  228. str += o + ",";
  229. }
  230. if (str.Length > 1)
  231. str = str.Substring(0, str.Length - 1);
  232. str += "\r\n]";
  233. return str;
  234. }
  235. }
  236. }

2.2.2.VideoTag类

视频tag类:

  1. using System.IO;
  2. namespace FLVParer.Model
  3. {
  4. class VideoTag : Tag
  5. {
  6. public int frameType;//帧类型
  7. public int encodeID;//编码ID
  8. public VideoTag()
  9. {
  10. tagType = TagType.video;
  11. }
  12. public override void readData(Stream stream)
  13. {
  14. int info = stream.ReadByte();
  15. frameType = info >> 4;
  16. encodeID = info & 0x0f;
  17. data = new byte[datasize - 1];
  18. stream.Read(data, 0, (int)datasize - 1);
  19. }
  20. }
  21. }

2.2.3.AudioTag 类

音频tag类:

  1. using System.IO;
  2. namespace FLVParer.Model
  3. {
  4. class AudioTag : Tag
  5. {
  6. public int formate;//音频格式
  7. public int rate;//采样率
  8. public int size;//采样的长度
  9. public int type;//音频类型
  10. public AudioTag()
  11. {
  12. tagType = TagType.audio;
  13. }
  14. public override void readData(Stream stream)
  15. {
  16. int info = stream.ReadByte();
  17. formate = info >> 4;
  18. rate = (info & 0x0c) >> 2;
  19. size = (info & 0x02) >> 1;
  20. type = info & 0x01;
  21. data = new byte[datasize - 1];
  22. stream.Read(data, 0, (int)datasize - 1);
  23. }
  24. }
  25. }

3.使用方法

用法很简单,new出来的时候把FLV文件的stream对象传进去就行了,比如我这样的:

  1. FLV flv = null;
  2. using (FileStream fs = new FileStream("t31_stract.flv", FileMode.Open, FileAccess.Read))
  3. {
  4. flv = new FLV(fs);
  5. }

之后就可以使用flv对象来分析当前flv的信息了。

4.总结

因为FLV文件的结构很清晰,也很简单,所以解析起来还是挺容易的。我上面的解析程序只是简单的把FLV分解了出来,如果要做FLV的修改操作的话,可以给每个类加个toStream方法,再历遍依次调用就可以写回到文件里了。

作者:wuyuan 本文来自Wuyuan's Blog 转载请注明,谢谢! 文章地址:http://wuyuans.com/2012/09/parser-flv-using-csharp

0 0
原创粉丝点击