达达's 胡搞瞎搞工作室

此Blog搬到www.unbe.cn 去了,要找我讨论问题请到我的新Blog

几个对于文件格式封装有帮助的函数

    年初研究了下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数组中截取所需要的中间几位的值

查看详细代码


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
);
        }


一下给出我所写的用于对二进制数操作的一个类代码,里面其实就是一组我常用到的二进制取值之类的函数.
注:不好意思最近没什么时间,所以就没有写注释了,大家凑合着看吧,有问题或者建议可以留言告诉我

查看详细代码

posted on 2006-04-14 10:59  BG5SBK  阅读(2100)  评论(8)    收藏  举报

导航