登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

面包会有的

... ...

 
 
 

日志

 
 

[转]x264中bs.h文件部分函数解读  

2011-08-24 22:18:24|  分类: H264 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
本文结合写入、读取码流函数介绍哥伦布编码方式。
1 写入码流函数bs_write:
1) static inline void bs_write( bs_t *s, int i_count, uint32_t i_bits )
该函数的作用是:向s里写入i_bits流的后i_count位,s以字节为单位,所以够8个位就写下个(注意i_bits的单位是uint32_t)。

static inline void bs_write( bs_t *s, int i_count, uint32_t i_bits )
{
    while( i_count > 0 )
    {
        if( s->p >= s->p_end )
        {
            break;
        }
        i_count--;
        if( ( i_bits >> i_count )&0x01 )
        {
            *s->p |= 1 << ( s->i_left - 1 );
        }
        else
        {
            *s->p &= ~( 1 << ( s->i_left - 1 ) );
        }
        s->i_left--;
        if( s->i_left == 0 )
        {
            s->p++;
            s->i_left = 8;
        }
    }
}
函数功能:
i_count是循环的次数即要写入的位数,i_bits是要编码的数,i_left是当前空闲码流的位数即记录当前字节的空余位数。将i_bits编为i_count位的码流,每次循环,i_count和i_left都会减1,i_count和i_left并不一定相等。当i_left==0时,s->p指针指向下一个码流单元,i_left更新为8。
 
函数流程:
首先判断i_count是否大于0,如果是,则判断s->p是否大于s->p_end,若是则终止,否则,可以写入码流。这个条件是在判断是否有空闲的存储空间供新的码流写入。
若可以写码流,则i_count--,表明可以进行写一位的操作。注意,写i_bits是逐位写入的。
if( ( i_bits >> i_count )&0x01 )是用来判断当前要写入的i_bits位是0还是1,从而选择不同的算法来写入这个码流。如果当前要写入的是0,则选择*s->p &= ~( 1 << ( s->i_left - 1 ) )来把这个0写入码流;如果当前要写入的是1,则选择*s->p |= 1 << ( s->i_left - 1 )来把这个1写入码流。
  
写完一位码流后,初始的i_left被新写入的bit占了一位,所以i_left的值-1.
   这时判断I_left是否等于0,如果i_left还大于0,表示当前的存储单元中还有剩余的空间供码流写入,则直接进入下一次循环。如果i_left==0时,表示当前存储单元已被写满,所以s->p指针指向下一个存储单元,i_left更新为8。这时再进入下一循环。
  
在进入下一循环的时候,先判断i_count的值,如果非零,程序继续;如果为0,表示码流已经全部写入,程序终止。

注:该函数是采用一个while循环,一次写入一个位,循环i_count次将i_bits的前i_count位写入s中。

2)static inline void bs_write1( bs_t *s, uint32_t i_bits )
该函数的作用是:将i_bits流的后1位写入s。

static inline void bs_write1( bs_t *s, uint32_t i_bits )
{
    if( s->p < s->p_end )
    {   
        if( i_bits&0x01 )
        {
            *s->p |= 1 <<( s->i_left-1);
        }
        else
        {
            *s->p &= ~( 1 << (s->i_left-1) );
        }
         s->i_left--;
        if( s->i_left == 0 )
        {
            s->p++;
            s->i_left = 8;
        }
    }
}
 
bs_write1()相当于bs_write(bs_t *s,1, uint32_t i_bits) 就是只写入1 bit码流。

函数流程:
首先判断s->p是否大于s->p_end,若是则终止,否则,可以写入码流。
if(  i_bits &0x01 )是用来判断当前要写入的i_bits位是0还是1,如果当前要写入的是1,则选择*s->p |= 1 <<( s->i_left-1)来把这个1写入码流;如果当前要写入的是0,则选择            *s->p &= ~( 1 << (s->i_left-1) )来把这个0写入码流。
  
写完一位码流后,初始的i_left被新写入的bit占了一位,所以i_left的值-1.
这时判断I_left是否等于0,如果i_left==0时,表示当前存储单元已被写满,所以s->p指针指向下一个存储单元,i_left更新为8。这时再进入下一循环。
  
注:上面两个write函数是从网上找的,一次写入一个位数,先判断要写入的位是0或1,再直接写入0或1,比较繁琐但是便于理解,故此附上以供参考。以下介绍的都是来自x264中的源码。

3)static inline void bs_write( bs_t *s, int i_count, uint32_t i_bits )
该函数的作用是:向s里写入i_bits流的后i_count位。

static inline void bs_write( bs_t *s, int i_count, uint32_t i_bits )
{
    if( s->p >= s->p_end - 4 )
        return;
    while( i_count > 0 )
    {
        if( i_count < 32 )
            i_bits &= (1<<i_count)-1;
        if( i_count < s->i_left )
        {
            *s->p = (*s->p << i_count) | i_bits;
            s->i_left -= i_count;
            break;
        }
        else
        {
            *s->p = (*s->p << s->i_left) | (i_bits >> (i_count - s->i_left));
            i_count -= s->i_left;
            s->p++;
            s->i_left = 8;
        }
    }
}

流程简介:
首先判断是否有空闲的存储空间供新的码流写入,若剩余不足4个字节的空间,则认为空间不够,直接返回(uint32_t是32位,要4个字节的存储空间); i_count是否大于0,如果是,则可以写入码流。这个条件是在判断是否该开始或继续写入码流。

若可以写码流,若i_count<32,则i_bits &= (1<<i_count)-1,将不需要写入的位置0。注意,该函数写i_bits不是逐位写入的。
如果要写入的位数i_count < 本字节空余的位数s->i_left,则一次性将i_count位写入:             *s->p = (*s->p << i_count) | i_bits且s->i_left -= i_count,然后完成写入break;
否则,则先写入i_left位:*s->p = (*s->p << s->i_left) | (i_bits >> (i_count - s->i_left)),i_count -= s->i_left(要写入的数减少),s->p++(进入s的下一个字节),i_left重新置位为8。
while循环直至写入i_count位。

注:该函数同1)小节中介绍的函数作用一样,不过效率要高。后面函数流程就不再详细介绍了,只说明大概思路。

4)static inline void bs_write1( bs_t *s, uint32_t i_bit )
该函数的作用是:将i_bits流的后1位写入s。

static inline void bs_write1( bs_t *s, uint32_t i_bit )
{
    if( s->p < s->p_end )
    {
        *s->p <<= 1;
        *s->p |= i_bit;
        s->i_left--;
        if( s->i_left == 0 )
        {
            s->p++;
            s->i_left = 8;
        }
    }
}

思路:直接写入*s->p <<= 1;
             *s->p |= i_bit;

2 读出码流函数bs_read
1)static inline uint32_t bs_read( bs_t *s, int i_count )
该函数的作用是:从s中读出i_count位,并将其做为uint32_t类型返回。

static inline uint32_t bs_read( bs_t *s, int i_count )
{
     static uint32_t i_mask[33] ={0x00,
                                  0x01,      0x03,      0x07,      0x0f,
                                  0x1f,      0x3f,      0x7f,      0xff,
                                  0x1ff,     0x3ff,     0x7ff,     0xfff,
                                  0x1fff,    0x3fff,    0x7fff,    0xffff,
                                  0x1ffff,   0x3ffff,   0x7ffff,   0xfffff,
                                  0x1fffff,  0x3fffff,  0x7fffff,  0xffffff,
                                  0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff,
                                  0x1fffffff,0x3fffffff,0x7fffffff,0xffffffff};
    int      i_shr;
    uint32_t i_result = 0;

    while( i_count > 0 )
    {
        if( s->p >= s->p_end )
        {
            break;
        }

        if( ( i_shr = s->i_left - i_count ) >= 0 )
        {
            /* more in the buffer than requested */
            i_result |= ( *s->p >> i_shr )&i_mask[i_count];
            s->i_left -= i_count;
            if( s->i_left == 0 )
            {
                s->p++;
                s->i_left = 8;
            }
            return( i_result );
        }
        else
        {
            /* less in the buffer than requested */
           i_result |= (*s->p&i_mask[s->i_left]) << -i_shr;
           i_count  -= s->i_left;
           s->p++;
           s->i_left = 8;
        }
    }
    return( i_result );
}

思路:若i_count>0且s流并未结束,则开始或继续读取码流;
     若s当前字节中剩余位数大于等于要读取的位数i_count,则直接读取;
     若s当前字节中剩余位数小于要读取的位数i_count,则读取剩余位,进入s下一字节继续读取。
注:写入s时,i_left表示s当前字节还没被写入的位,若一个新的字节,则i_left=8;
    读取s时,i_left表示s当前字节还没被读取的位,若一个新的字节,则i_left=8。
    注意两者的区别和联系。

2)static inline uint32_t bs_read1( bs_t *s )
该函数的作用是:从s中读出1位,并将其做为uint32_t类型返回。

static inline uint32_t bs_read1( bs_t *s )
{
    if( s->p < s->p_end )
    {
        unsigned int i_result;

        s->i_left--;
        i_result = ( *s->p >> s->i_left )&0x01;
        if( s->i_left == 0 )
        {
            s->p++;
            s->i_left = 8;
        }
        return i_result;
    }

    return 0;
}

思路:若s流并未结束,则读取一位。

3 指数哥伦布编码ue(v)
1)static inline void bs_write_ue( bs_t *s, unsigned int val )
该函数的作用是:将val以哥伦布编码ue(v)方式编码并写入s中。

static inline void bs_write_ue( bs_t *s, unsigned int val )
{
    int i_size = 0;
    static const int i_size0_255[256] =
    {
        1,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
        6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
        7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
        7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
        8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
        8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
        8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
        8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8
    };

    if( val == 0 )
    {
        bs_write1( s, 1 );
    }
    else
    {
        unsigned int tmp = ++val;

        if( tmp >= 0x00010000 )//2^16=256*256
        {
            i_size += 16;
            tmp >>= 16;
        }
        if( tmp >= 0x100 )//2^8=256
        {
            i_size += 8;
            tmp >>= 8;
        }
        i_size += i_size0_255[tmp];

        bs_write( s, 2 * i_size - 1, val );
    }
}

思路:当val=0时,写入1;
      当val!=0时:该函数主要是通过i_size0_255[256]表计算出一个i_size,然后写入val的2*i_size-1位,注意
     tmp=++val,实际写入的val比传入的val大1。
例:when val=9 ,val!=0, so tmp=10;
tmp<0x00010000 and tmp<0x100, so i_size=0+i_size0_255[10]=4;
tmp=10=00001010  and 2*i_size-1=7 ,so bs_write(s,7,00001010);
由上所述:当val=9时,写入s的字串为:0001010

2)static inline int bs_read_ue( bs_t *s )
该函数的作用是:从s中解码并读出一个语法元素值。

static inline int bs_read_ue( bs_t *s )

{
    int i = 0;

    while( bs_read1( s ) == 0 && s->p < s->p_end && i < 32 )
    {
        i++;
    }
    return( ( 1 << i) - 1 + bs_read( s, i ) );
}

思路:从s的当前位读取并计数,直至读取到1为止;
while( bs_read1( s ) == 0 && s->p < s->p_end && i < 32 )这个循环用i记录了s当前位置到1为止的0的个数,并丢弃读到的第一个1;
返回2^i-1+bs_read(s,i)。
例:当s字节中存放的是0001010时,1前有3个0,所以i=3;
返回的是:2^i-1+bs_read(s,i)即:8-1+010=9

3)static inline int bs_size_ue( unsigned int val )
该函数的作用是:计算val经过ue(v)编码后所对应的位。

static inline int bs_size_ue( unsigned int val )
{
    static const int i_size0_254[255] =
    {
        1, 3, 3, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 7,
        9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
        11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
        11,11,11,11,11,11,11,11,11,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
        13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
        13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
        13,13,13,13,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
        15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
        15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
        15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
        15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
        15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15
    };

    if( val < 255 )
    {
        return i_size0_254[val];
    }
    else
    {
        int i_size = 0;

        val++;

        if( val >= 0x10000 )
        {
            i_size += 32;
            val = (val >> 16) - 1;
        }
        if( val >= 0x100 )
        {
            i_size += 16;
            val = (val >> 8) - 1;
        }
        return i_size0_254[val] + i_size;
    }
}

思路:其实就是计算bs_write_ue( bs_t *s, unsigned int val )函数中的2*i_size-1的值。

4 指数哥伦布编码se(v)
1)static inline void bs_write_se( bs_t *s, int val )
该函数的作用是:通过ue(v)编码实现se(v)编码的写入。

static inline void bs_write_se( bs_t *s, int val )
{
    bs_write_ue( s, val <= 0 ? -val * 2 : val * 2 - 1);
}

思路:se(v)表示有符号指数哥伦布编码,当val<=0时,codenum=-val*2;否则,codenum=val*2-1。
     然后,bs_write_ue(s,codenum);
注:在标准中的codenum只是一个假设的中间值,就像在函数中设置的一个变量一样,val才是要得到的语法元素值。
    在ue(v)编码中,val=codenum,标准中也这样描述:“如果语法元素编码为ue(v),语法元素值等于codeNum。”
    在se(v)编码中,val与codenum的关系如下:当val<=0时,codenum=-val*2;否则,codenum=val*2-1(参看标准9.1.1小节中的表9-3)。


2)static inline int bs_read_se( bs_t *s )
该函数的作用是:通过ue(v)编码实现se(v)编码的读取。

static inline int bs_read_se( bs_t *s )
{
    int val = bs_read_ue( s );

    return val&0x01 ? (val+1)/2 : -(val/2);
}

思路:直接bs_read_ue( s )读取出来的实际上是codenum的值,因为“当val<=0时,codenum=-val*2;否则,codenum=val*2-1。
”,所以当codenum为奇数即 codenum&0x01>0 时,val=(codenum+1)/2,否则val=-(codenum/2)。


3)static inline int bs_size_se( int val )
该函数的作用是:通过ue(v)编码计算位数的方式实现se(v)编码的位数计算。

static inline int bs_size_se( int val )
{
    return bs_size_ue( val <= 0 ? -val * 2 : val * 2 - 1);
}
注:原理同ue(v)。

5 指数哥伦布编码te(v)
1)static inline void bs_write_te( bs_t *s, int x, int val )
该函数的作用是:通过ue(v)编码实现te(v)编码的写入。

static inline void bs_write_te( bs_t *s, int x, int val )
{
    if( x == 1 )
    {
        bs_write1( s, 1&~val );
    }
    else if( x > 1 )
    {
        bs_write_ue( s, val );
    }
}

思路:当x=1时,将val最后一位的取反,然后写入;
      当x>1时,编码方式同ue(v)。
注:x表示语法元素的范围。注意参考标准中关于te(v)与ue(v)关系的描述。
   

2)static inline int bs_read_te( bs_t *s, int x )
该函数的作用是:通过ue(v)编码实现te(v)编码的读取。

static inline int bs_read_te( bs_t *s, int x )
{
    if( x == 1 )
    {
        return 1 - bs_read1( s );
    }
    else if( x > 1 )
    {
        return bs_read_ue( s );
    }
    return 0;
}

思路:当x=1时,直接读出一位,然后取反;
      当x>1时,读取方式同ue(v)。


3)static inline int bs_size_te( int x, int val )
该函数的作用是:通过ue(v)编码计算位数的方式实现te(v)编码的位数计算。

static inline int bs_size_te( int x, int val )
{
    if( x == 1 )
    {
        return 1;
    }
    else if( x > 1 )
    {
        return bs_size_ue( val );
    }
    return 0;
}

思路:当x=1时,直接为1;
     当x>1时,同ue(v)。

  评论这张
 
阅读(1458)| 评论(0)

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018