几个对于文件格式封装有帮助的函数
年初研究了下GIF文件格式,最近又研究了下Flash文件格式,在对文件格式进行封装的时候经常遇到很多"不规则"按bit读取的情况,而.NET框架里的流最小单位只能读到byte读取,反反复复的写些Byte转换代码实在是烦人.
而且在研究Flash格式时发现,Flash里有些结构体的信息是不定长的,它有一个由几个bit表示的长度信息,用于表示后续的每个信息的bit长度,这种不定长的结构体减小的SWF文件的大小,但是也让我在封装SWF文件格式类库时有些郁闷.
先拿个例子看看,以下是Flash中矩形区域结构体的定义:
RECT
Field Type Comment
Nbits UB[5] Bits in each rect value field
Xmin SB[Nbits] x minimum position for rect
Xmax SB[Nbits] x maximum position for rect
Ymin SB[Nbits] y minimum position for rect
Ymax SB[Nbits] y maximum position for rect
注:UB表示无符号的不定长bit值,SB表示不定长的有符号bit值
由上述定义可以看出Nbits定义了后续的值占用的bit数,而Nbits自身占用5个bit.
举个Flash文件格式文档里的例子,假设一个RECT结构体的信息如下表示: 78 00 05 5F 00 00 0F A0 00
翻译成二进制是:
0111 1000 0000 0000 0000 0101 0101 1111 0000 0000
0000 0000 0000 1111 1010 0000 0000 0000
前5个bit取出来就是:01111 -> 15
说明后续每个值占用15个bit,每个值取出来如下:
000000000000000 -> 0 = Xmin
010101011111000 -> 11000 = Xmax
000000000000000 -> 0 = Ymin
001111101000000 -> 8000 = Ymax
这个过程我在代码里怎么处理的呢?
原先(以前)的做法如下:
写了一个BytesToBin函数,把读取的Byte数组转为二进制值,这个二进制值其实是以01组成的字符串. 代码如下:
public static string BytesToBin(params byte[] bytes) { StringBuilder _result = new StringBuilder(); foreach (byte data in bytes) { byte _doc = data; if (_doc != 0) { StringBuilder _newStr = new StringBuilder(); while (_doc != 0) { _newStr.Insert(0,_doc % 2); _doc = (byte)(_doc / 2); } _result.Append(_newStr.ToString().PadLeft(8, '0')); } else { _result.Append("00000000"); } } return _result.ToString(); }
然后我使用Convert.ToInt32函数将字符串中间的几位截取并转为int值. 具体代码如下:
public void Load(BinaryReader binReader) { Nbits = (byte)(binReader.PeekChar() >> 3); int _bitCount = Nbits * 4 + 5; int _byteCount = _bitCount / 8; if (_bitCount % 8 != 0) _byteCount++; byte[] _rectInfo = binReader.ReadBytes(_byteCount); string _bitStr = Common.Math.BytesToBin(_rectInfo); Xmin = Convert.ToInt32(_bitStr.Substring(5, Nbits), 2); Xmax = Convert.ToInt32(_bitStr.Substring(Nbits + 5, Nbits), 2); Ymin = Convert.ToInt32(_bitStr.Substring(Nbits * 2 + 5, Nbits), 2); Ymax = Convert.ToInt32(_bitStr.Substring(Nbits * 3 + 5, Nbits), 2); }
这样做看起来还可以,但是好像效率不高.
于是我写了以下两个函数,用于在从byte数组中截取所需要的中间几位的值
public static UInt64 GetValueFromBits(byte[] bits, int start, int end) { if (bits == null || bits.Length == 0 || start < 0 || end > bits.Length - 1 || start > end) return 0; UInt64 _value = 0; int _length = 0; int _valueLength = end - start + 1; int _byteIndex = 0; byte _byteValue = 0; UInt64 _tempValue = 0; for (int i = 0; i < bits.Length; i++) { _byteIndex = i * 8; if (_byteIndex > end) break; if (_byteIndex + 7 < start) continue; _byteValue = bits[i]; int _byteStart = start - _byteIndex; int _byteEnd = end - _byteIndex; if (_byteStart < 0) _byteStart = 0; if (_byteEnd > 7) _byteEnd = 7; else if (_byteEnd < 0) _byteEnd = _byteIndex - end; _byteValue = (byte)(_byteValue << _byteStart); _byteValue = (byte)(_byteValue >> _byteStart); _byteValue = (byte)(_byteValue >> (7 - _byteEnd)); _length += _byteEnd - _byteStart + 1; _tempValue = _byteValue; _tempValue = _tempValue << (_valueLength - _length); _value = _value | _tempValue; } return _value; } public static UInt64[] GetValuesFromBits(byte[] bits, int start, int subLength, int valueCount) { if (bits == null || bits.Length == 0 || start < 0 || valueCount <= 0 || subLength <= 0 || start + subLength * valueCount > bits.Length) return null; UInt64[] _values = new UInt64[valueCount]; for (int i = 0; i < valueCount; i++) { _values[i] = GetValueFromBits(bits, start + i * subLength, start + subLength + i * subLength - 1); } return _values; }
GetValueFromBits是从所给的byte数组bits中截取从start位到end位的值,这个位的顺序是从左到右以0为起始索引逐一递增的. 截取的数值包括start位和end位,这点需要留意.
GetValuesFromBits是从所给的byte数组bits中从start位开始以subLength为每个值的长度截取valueCount个的值.位的顺序也是从左到右以0为起始索引逐一递增的.
新的Load方法就可以改成:
public void Load(BinaryReader binReader) { Nbits = (byte)(binReader.PeekChar() >> 3); int _bitCount = Nbits * 4 + 5; int _byteCount = _bitCount / 8; if (_bitCount % 8 != 0) _byteCount++; byte[] _rectInfo = binReader.ReadBytes(_byteCount); string _bitStr = Common.Math.BytesToBin(_rectInfo); Xmin = (int)BinMatch.GetValueFromBits(_rectInfo, 5, Nbits); Xmax = (int)BinMatch.GetValueFromBits(_rectInfo,Nbits + 5, Nbits), 2); Ymin = (int)BinMatch.GetValueFromBits(_rectInfo,Nbits * 2 + 5, Nbits), 2); Ymax = (int)BinMatch.GetValueFromBits(_rectInfo,Nbits * 3 + 5, Nbits), 2); }
以下给出我所写的用于对二进制数操作的一个类代码,里面其实就是一组我常用到的二进制取值之类的函数. 注:不好意思最近没什么时间,所以就没有写注释了,大家凑合着看吧,有问题或者建议可以留言告诉我
public class BinMath { public static string BytesToBin(params byte[] bytes) { StringBuilder _result = new StringBuilder(); foreach (byte data in bytes) { byte _doc = data; if (_doc != 0) { StringBuilder _newStr = new StringBuilder(); while (_doc != 0) { _newStr.Insert(0,_doc % 2); _doc = (byte)(_doc / 2); } _result.Append(_newStr.ToString().PadLeft(8, '0')); } else { _result.Append("00000000"); } } return _result.ToString(); } public static string UInt16sToBin(params UInt16[] integers) { StringBuilder _result = new StringBuilder(); foreach (UInt16 data in integers) { UInt16 _doc = data; if (_doc != 0) { StringBuilder _newStr = new StringBuilder(); while (_doc != 0) { _newStr.Insert(0, _doc % 2); _doc = (UInt16)(_doc / 2); } _result.Append(_newStr.ToString().PadLeft(16, '0')); } else { _result.Append("0000000000000000"); } } return _result.ToString(); } public static string Int16sToBin(params Int16[] integers) { StringBuilder _result = new StringBuilder(); foreach (Int16 data in integers) { Int16 _doc = data; if (_doc != 0) { StringBuilder _newStr = new StringBuilder(); while (_doc != 0) { _newStr.Insert(0, _doc % 2); _doc = (Int16)(_doc / 2); } _result.Append(_newStr.ToString().PadLeft(16, '0')); } else { _result.Append("0000000000000000"); } } return _result.ToString(); } public static string UInt32sToBin(params UInt32[] integers) { StringBuilder _result = new StringBuilder(); foreach (UInt32 data in integers) { UInt32 _doc = data; if (_doc != 0) { StringBuilder _newStr = new StringBuilder(); while (_doc != 0) { _newStr.Insert(0, _doc % 2); _doc = _doc / 2; } _result.Append(_newStr.ToString().PadLeft(32, '0')); } else { _result.Append("00000000000000000000000000000000"); } } return _result.ToString(); } public static string Int32sToBin(params Int32[] integers) { StringBuilder _result = new StringBuilder(); foreach (Int32 data in integers) { Int32 _doc = data; if (_doc != 0) { StringBuilder _newStr = new StringBuilder(); while (_doc != 0) { _newStr.Insert(0, _doc % 2); _doc = _doc / 2; } _result.Append(_newStr.ToString().PadLeft(32, '0')); } else { _result.Append("00000000000000000000000000000000"); } } return _result.ToString(); } public static string UInt64sToBin(params UInt64[] integers) { StringBuilder _result = new StringBuilder(); foreach (UInt64 data in integers) { UInt64 _doc = data; if (_doc != 0) { StringBuilder _newStr = new StringBuilder(); while (_doc != 0) { _newStr.Insert(0, _doc % 2); _doc = _doc / 2; } _result.Append(_newStr.ToString().PadLeft(64, '0')); } else { _result.Append("0000000000000000000000000000000000000000000000000000000000000000"); } } return _result.ToString(); } public static string Int64sToBin(params Int64[] integers) { StringBuilder _result = new StringBuilder(); foreach (Int64 data in integers) { Int64 _doc = data; if (_doc != 0) { StringBuilder _newStr = new StringBuilder(); while (_doc != 0) { _newStr.Insert(0, _doc % 2); _doc = _doc / 2; } _result.Append(_newStr.ToString().PadLeft(64, '0')); } else { _result.Append("0000000000000000000000000000000000000000000000000000000000000000"); } } return _result.ToString(); } public static Byte GetByteBits(Byte source, byte start, byte end) { if (start >= 0 && start <= 7 && end >= 0 && end <= 7) { Byte _toolNum = 0xFF; _toolNum = (Byte)(_toolNum >> start); _toolNum = (Byte)(_toolNum << start); _toolNum = (Byte)(_toolNum << (7 - end)); _toolNum = (Byte)(_toolNum >> (7 - end)); return (Byte)((source & _toolNum) >> start); } return 0; } public static UInt16 GetUInt16Bits(UInt16 source, byte start, byte end) { if (start >= 0 && start <= 15 && end >= 0 && end <= 15) { UInt16 _toolNum = 0xFFFF; _toolNum = (UInt16)(_toolNum >> start); _toolNum = (UInt16)(_toolNum << start); _toolNum = (UInt16)(_toolNum << (15 - end)); _toolNum = (UInt16)(_toolNum >> (15 - end)); return (UInt16)((source & _toolNum) >> start); } return 0; } public static UInt32 GetUInt32Bits(UInt32 source, byte start, byte end) { if (start >= 0 && start <= 31 && end >= 0 && end <= 31) { UInt32 _toolNum = 0xFFFFFFFF; _toolNum = _toolNum >> start; _toolNum = _toolNum << start; _toolNum = _toolNum << (31 - end); _toolNum = _toolNum >> (31 - end); return (source & _toolNum) >> start; } return 0; } public static UInt64 GetUInt64Bits(UInt64 source, byte start, byte end) { if (start >= 0 && start <= 63 && end >= 0 && end <= 63) { UInt64 _toolNum = 0xFFFFFFFFFFFFFFFF; _toolNum = _toolNum >> start; _toolNum = _toolNum << start; _toolNum = _toolNum << (63 - end); _toolNum = _toolNum >> (63 - end); return (source & _toolNum) >> start; } return 0; } public static UInt64 GetValueFromBits(byte[] bits, int start, int end) { if (bits == null || bits.Length == 0 || start < 0 || end > bits.Length - 1 || start > end) return 0; UInt64 _value = 0; int _length = 0; int _valueLength = end - start + 1; int _byteIndex = 0; byte _byteValue = 0; UInt64 _tempValue = 0; for (int i = 0; i < bits.Length; i++) { _byteIndex = i * 8; if (_byteIndex > end) break; if (_byteIndex + 7 < start) continue; _byteValue = bits[i]; int _byteStart = start - _byteIndex; int _byteEnd = end - _byteIndex; if (_byteStart < 0) _byteStart = 0; if (_byteEnd > 7) _byteEnd = 7; else if (_byteEnd < 0) _byteEnd = _byteIndex - end; _byteValue = (byte)(_byteValue << _byteStart); _byteValue = (byte)(_byteValue >> _byteStart); _byteValue = (byte)(_byteValue >> (7 - _byteEnd)); _length += _byteEnd - _byteStart + 1; _tempValue = _byteValue; _tempValue = _tempValue << (_valueLength - _length); _value = _value | _tempValue; } return _value; } public static UInt64[] GetValuesFromBits(byte[] bits, int start, int subLength, int valueCount) { if (bits == null || bits.Length == 0 || start < 0 || valueCount <= 0 || subLength <= 0 || start + subLength * valueCount > bits.Length) return null; UInt64[] _values = new UInt64[valueCount]; for (int i = 0; i < valueCount; i++) { _values[i] = GetValueFromBits(bits, start + i * subLength, start + subLength + i * subLength - 1); } return _values; } }