c#实现flv解析详解示例
作者:
本文提供的解析程序简单的把FLV分解了出来,如果要做FLV的修改操作的话,可以给每个类加个toStream方法,再历遍依次调用就可以写回到文件里了
先上效果图:
在解析的过程中,我们会和byte做各种运算,所以我定义了一个byte工具类ByteUtils:
复制代码 代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace FLVParer.Utils
{
class ByteUtils
{
public static uint ByteToUInt(byte[] bs, int length)
{
if (bs == null || bs.Length < length)
return 0;
uint rtn = 0;
for (int i = 0; i < length; i++)
{
rtn <<= 8;
rtn |= bs[i];
}
return rtn;
}
public static double ByteToDouble(byte[] bs)
{
if (bs == null || bs.Length < 8)
return 0;
byte[] b2 = new byte[8];
for (int i = 0; i < 8; i++)
{
b2[i] = bs[7 - i];
}
return BitConverter.ToDouble(b2, 0);
}
public static short ReadUI16(Stream src)
{
byte[] bs = new byte[2];
if (src.Read(bs, 0, 2) <= 0)
return 0;
return (short)((bs[0] << 8) | bs[1]);
}
public static uint ReadUI24(Stream src)
{
byte[] bs = new byte[3];
if (src.Read(bs, 0, 3) <= 0)
throw new IOException("Stream end.");
return ByteToUInt(bs, 3);
}
public static uint ReadUI32(Stream src)
{
byte[] bs = new byte[4];
if (src.Read(bs, 0, 4) <= 0)
throw new IOException("Stream end.");
return ByteToUInt(bs, 4);
}
public static string GetTime(uint time)
{
return (time / 60000).ToString() + ":"
+ (time / 1000 % 60).ToString("D2") + "."
+ (time % 1000).ToString("D3");
}
}
}
FLV类
FLV类,主要的类,里面包括一个header和许多的tag,也就是一个FLV文件的结构:
复制代码 代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using FLVParer.Utils;
namespace FLVParer.Model
{
class FLV
{
public Header header { get; private set; }
List<Tag> tags;
public FLV(Stream stream)
{
header = new Header();
header.readHeader(stream);
stream.Seek(header.size, SeekOrigin.Begin);
tags = new List<Tag>();
while (stream.Position < stream.Length-4)
{
tags.Add(readTag(stream));
}
}
private Tag readTag(Stream stream)
{
Tag tag = null;
byte[] buf = new byte[4];
stream.Read(buf, 0, 4);
int type = stream.ReadByte();
switch (type)
{
case 8:
tag = new AudioTag();
break;
case 9:
tag = new VideoTag();
break;
case 18:
tag = new ScriptTag();
break;
}
tag.presize = ByteUtils.ByteToUInt(buf, 4);
tag.datasize = ByteUtils.ReadUI24(stream);
tag.timestamp = ByteUtils.ReadUI24(stream);
tag.timestamp_ex = stream.ReadByte();
tag.streamid = ByteUtils.ReadUI24(stream);
tag.readData(stream);
return tag;
}
}
}
Header类
Header类,保存FLV文件的头信息:
复制代码 代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using FLVParer.Utils;
namespace FLVParer.Model
{
class Header
{
public String type { get; private set; }
public int version { get; private set; }
public bool hasVideo { get; private set; }
public bool hasAudio { get; private set; }
public uint size { get; private set; }
public void readHeader(Stream stream)
{
byte[] buf = new byte[4];
stream.Read(buf, 0, 3);
type = Encoding.Default.GetString(buf);
stream.Read(buf, 0, 1);
version = buf[0];
stream.Read(buf, 0, 1);
buf[0] &= 0x0f;
if ((buf[0] & 0x01) == 1)
{
hasVideo = true;
}
if ((buf[0] & 0x04) == 4)
{
hasAudio = true;
}
stream.Read(buf, 0, 4);
size = ByteUtils.ByteToUInt(buf, 4);
}
}
}
Tag类
Tag类是一个抽象类,因为tag的种类有三种,为了统一管理,抽象出Tag类:
复制代码 代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace FLVParer.Model
{
enum TagType
{
video,
audio,
Script
}
abstract class Tag
{
public TagType tagType;//tag类型
public uint presize;//前一tag大小
public uint datasize;//数据区大小
public uint timestamp; //时间戳 单位ms
public int timestamp_ex;//时间戳扩展
public uint streamid;//ID
public long offset;//偏移量
public byte[] data;//数据
//对tag进行读取
public abstract void readData(Stream stream);
}
}
ScriptTag类
脚本tag类,继承自Tag类,并添加成员变量Values,用于保存脚本信息:
复制代码 代码如下:
using System.Collections.Generic;
using System.Text;
using System.IO;
using FLVParer.Utils;
namespace FLVParer.Model
{
class ScriptTag : Tag
{
public List<KeyValuePair<string, object>> Values { get; private set; }
public ScriptTag()
{
tagType = TagType.Script;
Values = new List<KeyValuePair<string, object>>();
}
public override void readData(Stream stream)
{
offset = 0;
Values.Clear();
byte[] bs = new byte[3];
while (offset < this.datasize)
{
stream.Read(bs, 0, 3);
if (bs[0] == 0 && bs[1] == 0 && bs[2] == 9)
{
offset += 3;
break;
}
stream.Seek(-3, SeekOrigin.Current);
AddElement("#" + offset, ReadElement(stream));
}
}
private void AddElement(string key, object o)
{
Values.Add(new KeyValuePair<string, object>(key, o));
}
private object ReadElement(Stream src)
{
int type = src.ReadByte();
offset++;
switch (type)
{
case 0: // Number - 8
return ReadDouble(src);
case 1: // Boolean - 1
return ReadByte(src);
case 2: // String - 2+n
return ReadString(src);
case 3: // Object
return ReadObject(src);
case 4: // MovieClip
return ReadString(src);
case 5: // Null
break;
case 6: // Undefined
break;
case 7: // Reference - 2
return ReadUShort(src);
case 8: // ECMA array
return ReadArray(src);
case 10: // Strict array
return ReadStrictArray(src);
case 11: // Date - 8+2
return ReadDate(src);
case 12: // Long string - 4+n
return ReadLongString(src);
}
return null;
}
private object ReadObject(Stream src)
{
byte[] bs = new byte[3];
ScriptObject obj = new ScriptObject();
while (offset < this.datasize)
{
src.Read(bs, 0, 3);
if (bs[0] == 0 && bs[1] == 0 && bs[2] == 9)
{
offset += 3;
break;
}
src.Seek(-3, SeekOrigin.Current);
string key = ReadString(src);
if (key[0] == 0)
break;
obj[key] = ReadElement(src);
}
return obj;
}
private double ReadDate(Stream src)
{
double d = ReadDouble(src);
src.Seek(2, SeekOrigin.Current);
offset += 2;
return d;
}
private ScriptObject ReadArray(Stream src)
{
byte[] buffer = new byte[4];
src.Read(buffer, 0, 4);
offset += 4;
uint count = ByteUtils.ByteToUInt(buffer, 4);
ScriptObject array = new ScriptObject();
for (uint i = 0; i < count; i++)
{
string key = ReadString(src);
array[key] = ReadElement(src);
}
src.Seek(3, SeekOrigin.Current); // 00 00 09
offset += 3;
return array;
}
private ScriptArray ReadStrictArray(Stream src)
{
byte[] bs = new byte[4];
src.Read(bs, 0, 4);
offset += 4;
ScriptArray array = new ScriptArray();
uint count = ByteUtils.ByteToUInt(bs, 4);
for (uint i = 0; i < count; i++)
{
array.Add(ReadElement(src));
}
return array;
}
private double ReadDouble(Stream src)
{
byte[] buffer = new byte[8];
src.Read(buffer, 0, 8);
offset += 8;
return ByteUtils.ByteToDouble(buffer);
}
private byte ReadByte(Stream src)
{
offset++;
return (byte)src.ReadByte();
}
private string ReadString(Stream src)
{
byte[] bs = new byte[2];
src.Read(bs, 0, 2);
offset += 2;
int n = (int)ByteUtils.ByteToUInt(bs, 2);
bs = new byte[n];
src.Read(bs, 0, n);
offset += n;
return Encoding.ASCII.GetString(bs);
}
private string ReadLongString(Stream src)
{
byte[] bs = new byte[4];
src.Read(bs, 0, 4);
offset += 4;
int n = (int)ByteUtils.ByteToUInt(bs, 4);
bs = new byte[n];
src.Read(bs, 0, n);
offset += n;
return Encoding.ASCII.GetString(bs);
}
private ushort ReadUShort(Stream src)
{
byte[] buffer = new byte[2];
src.Read(buffer, 0, 2);
offset += 2;
return (ushort)ByteUtils.ByteToUInt(buffer, 2);
}
}
public class ScriptObject
{
public static int indent = 0;
private Dictionary<string, object> values = new Dictionary<string, object>();
public object this[string key]
{
get
{
object o;
values.TryGetValue(key, out o);
return o;
}
set
{
if (!values.ContainsKey(key))
{
values.Add(key, value);
}
}
}
public override string ToString()
{
string str = "{\r\n";
ScriptObject.indent += 2;
foreach (KeyValuePair<string, object> kv in values)
{
str += new string(' ', ScriptObject.indent)
+ kv.Key + ": " + kv.Value + "\r\n";
}
ScriptObject.indent -= 2;
//if (str.Length > 1)
// str = str.Substring(0, str.Length - 1);
str += "}";
return str;
}
}
public class ScriptArray
{
private List<object> values = new List<object>();
public object this[int index]
{
get
{
if (index >= 0 && index < values.Count)
return values[index];
return null;
}
}
public void Add(object o)
{
values.Add(o);
}
public override string ToString()
{
string str = "[";
int n = 0;
foreach (object o in values)
{
if (n % 10 == 0)
str += "\r\n";
n++;
str += o + ",";
}
if (str.Length > 1)
str = str.Substring(0, str.Length - 1);
str += "\r\n]";
return str;
}
}
}
VideoTag类
视频tag类:
复制代码 代码如下:
using System.IO;
namespace FLVParer.Model
{
class VideoTag : Tag
{
public int frameType;//帧类型
public int encodeID;//编码ID
public VideoTag()
{
tagType = TagType.video;
}
public override void readData(Stream stream)
{
int info = stream.ReadByte();
frameType = info >> 4;
encodeID = info & 0x0f;
data = new byte[datasize - 1];
stream.Read(data, 0, (int)datasize - 1);
}
}
}
AudioTag 类
音频tag类:
复制代码 代码如下:
using System.IO;
namespace FLVParer.Model
{
class AudioTag : Tag
{
public int formate;//音频格式
public int rate;//采样率
public int size;//采样的长度
public int type;//音频类型
public AudioTag()
{
tagType = TagType.audio;
}
public override void readData(Stream stream)
{
int info = stream.ReadByte();
formate = info >> 4;
rate = (info & 0x0c) >> 2;
size = (info & 0x02) >> 1;
type = info & 0x01;
data = new byte[datasize - 1];
stream.Read(data, 0, (int)datasize - 1);
}
}
}
使用方法
用法很简单,new出来的时候把FLV文件的stream对象传进去就行了,比如我这样的:
复制代码 代码如下:
FLV flv = null;
using (FileStream fs = new FileStream("t31_stract.flv", FileMode.Open, FileAccess.Read))
{
flv = new FLV(fs);
}
之后就可以使用flv对象来分析当前flv的信息了。