(一)JM代码学习笔记一NAL单元的相关代码

1,NAL单元结构体

typedef struct nalu_t
{
  int       startcodeprefix_len;   //!< 起始码长度,对于参数集和图像中的第一个片,其长度为4,其它的长度是3
  unsigned  len;                   //!< NAL单元的长度,不包括起始码长度
  unsigned  max_size;              //!< NAL Unit Buffer size  NAL单元缓冲区大小
  int       forbidden_bit;         //!< should be always FALSE 禁止位,一般情况下为0.
  NaluType  nal_unit_type;         //!< NALU_TYPE_xxxx  NAL单元的类型,5个比特位
  NalRefIdc nal_reference_idc;     //!< 当前NAL单元的优先级,2个比特位,值范围为0~3,值越大,表示NAL单元越重要,越需要优先受到保护。
  byte     *buf;                   //!< contains the first byte followed by the EBSP 指向字节型的指针,可以指向起始码的第一个字节。
  uint16    lost_packets;          //!< true, if packet loss is detected
#if (MVC_EXTENSION_ENABLE)
  int       svc_extension_flag;    //!< should be always 0, for MVC
  int       non_idr_flag;          //!< 0 = current is IDR
  int       priority_id;           //!< a lower value of priority_id specifies a higher priority
  int       view_id;               //!< view identifier for the NAL unit
  int       temporal_id;           //!< temporal identifier for the NAL unit
  int       anchor_pic_flag;       //!< anchor access unit
  int       inter_view_flag;       //!< inter-view prediction enable
  int       reserved_one_bit;      //!< shall be equal to 1
#endif
} NALU_t;


2,NAL单元内存分配函数代码

/*!
 *************************************************************************************
 * \brief
 *    Allocates memory for a NALU 为一个NAL单元分配内存
 *
 * \param buffersize
 *     size of NALU buffer  NAL单元缓冲区的大小
 *
 * \return
 *    pointer to a NALU   返回的是一个指向NAL单元的指针
 *************************************************************************************
 */
//这是一个返回指针值的函数。
NALU_t *AllocNALU(int buffersize)
{
  NALU_t *n;                                           //NALU类型的指针n

  if ((n = (NALU_t*)calloc (1, sizeof (NALU_t))) == NULL)//用来存放NALU类型的数据,即为其中的一些变量开辟内存空间。
    no_mem_exit ("AllocNALU: n");//分配内存失败          //calloc函数原型为calloc(size_t n,size_t size),用来动态分配内存空间,并初始化内存为0,malloc不初始化

  n->max_size=buffersize;
  if ((n->buf = (byte*)calloc (buffersize, sizeof (byte))) == NULL)//开辟一段内存,用于存放NAL单元,类型是字节型的,将内存的首地址赋给n->buf。
  {
    free (n);//如果存放NAL单元的内存分配不成功,释放结构体NALU_t中的变量占据的内存空间。
    no_mem_exit ("AllocNALU: n->buf");
  }

  return n;
}


3,释放一个NAL单元的函数

 

/*!
 *************************************************************************************
 * \brief
 *    Frees a NALU 释放一个NALU单元
 *
 * \param n
 *    NALU to be freed  需要被释放的NAL单元
 *  先释放一个NALU占据的内存空间,再释放NALU结构体中的变量占据的内存空间
 *************************************************************************************
 */
void FreeNALU(NALU_t *n)
{
  if (n != NULL)
  {
    if (n->buf != NULL)
    {
      free(n->buf);
      n->buf=NULL;
    }
    free (n);
  }
}


4,编码端比特流结构体

struct bit_stream_enc
{
  int     buffer_size;        //!< Buffer size      缓冲区的大小,是整形
  int     byte_pos;           //!< current position in bitstream;
  int     bits_to_go;         //!< current bitcounter
  
  int     stored_byte_pos;    //!< storage for position in bitstream;
  int     stored_bits_to_go;  //!< storage for bitcounter
  int     byte_pos_skip;      //!< storage for position in bitstream;
  int     bits_to_go_skip;    //!< storage for bitcounter
  int     write_flag;         //!< Bitstream contains data and needs to be written

  byte    byte_buf;           //!< current buffer for last written byte  最后一个已经写入字节的所在的缓冲区,类型为字节型
  byte    stored_byte_buf;    //!< storage for buffer of last written byte
  byte    byte_buf_skip;      //!< current buffer for last written byte
  byte    *streamBuffer;      //!< actual buffer for written bytes

#if TRACE
  Boolean trace_enabled;
#endif
};


  以下是一段编码后的原始数据(SODB)在比特流缓冲区中的示意图

              

                                                                             图1

以下是比特流结构体中变量解释以及和图1的对应关系

   int buffer_size    缓冲区大小      可能是10个byte,也可能是20个byte,也可能是10KB…………这由内存分配决定。

   int byte_pos       当前比特流指针指向的是第几个字节       第6个 

   int bits_to_go      保证字节对齐的比特数     4个,还需要4个比特,才能保证最后一个是字节对齐的。

    以下两个变量都是储存上面最后两个变量的值的,相当于一个中间临时变量

   int     stored_byte_pos;    //!< storage for position in bitstream;   用来保存比特流中字节的位置
   int     stored_bits_to_go;  //!< storage for bitcounter                   保存保证字节对齐的比特数

  byte    byte_buf;           //!< current buffer for last written byte  最后一个字节的十进制值,范围在0~255     图1中的二进制值为1011,十进制值为11
  byte    stored_byte_buf;    //!< storage for buffer of last written byte   保存最后一个字节的十进制值
  byte    byte_buf_skip;      //!< current buffer for last written byte
  byte    *streamBuffer;      //!< actual buffer for written bytes     指向缓冲区最后一个字节的指针       图1中的 streamBuffer,它是一个指针,值是一个地址。


 下面来分析SODB封装成RBSP的函数

void SODBtoRBSP(Bitstream *currStream)
{
   //byte *dddf=NULL;
  currStream->byte_buf <<= 1;//比特流缓冲区中最后一个未对齐的字节左移1位,目的是补1
  currStream->byte_buf |= 1;//空出的比特位补1
  currStream->bits_to_go--;//离字节对齐还差bits_to_go-1个
  currStream->byte_buf <<= currStream->bits_to_go;//再左移bits_to_go个比特位,相当于补了bits_to_go个0
  currStream->streamBuffer[currStream->byte_pos++] = currStream->byte_buf;//将字节对齐的字节的值放入缓冲区中,streamBuffer右移一位
  //dddf=&currStream->streamBuffer[--currStream->byte_pos];
  currStream->bits_to_go = 8;//指针右移一位后,指向下一个字节,其中的内容为空,还未输入比特,相当于差8个比特才能字节对齐
  currStream->byte_buf = 0;//当前指针指向的字节,十进制值为0.
}


代码分析如下:

/*!
************************************************************************
*  \brief
*     This function add emulation_prevention_three_byte for all occurrences
*     of the following byte sequences in the stream
*       0x000000  -> 0x00000300
*       0x000001  -> 0x00000301
*       0x000002  -> 0x00000302
*       0x000003  -> 0x00000303
*
*  \param NaluBuffer
*            pointer to target buffer 指向封装后的EBSP缓冲区的首地址
*  \param rbsp
*            pointer to source buffer  指向原RBSP缓冲区的首地址
*  \param rbsp_size
*           Size of source          RBSP中有多少个字节,即大小
*  \return
*           Size target buffer after emulation prevention.  
*
************************************************************************
*/
//本质上:就是将一组无符号字符型数据(16进制的值),首地址为rbsp,复制到一个新的缓冲区内,
//首地址为NaluBuffer,依次复制的时候,如果遇到连续的两个字节0+一个字节0,或一个字节1,或一个字节2,或一个字节3.
//即在这三个字节的第一个字节前面插入0x03。相应的新的缓冲区大小可能会增加。
//这个函数中好的技巧是用了count来表征某个特殊值的个数。先判断个数
int RBSPtoEBSP(byte *NaluBuffer, unsigned char *rbsp, int rbsp_size)
{
  int j     = 0;//用来记录在引入防竞争机制后,缓冲区的长度
  int count = 0;//用来统计在RBSP中,字节连续为0的个数,比如 0x00 00,此时就是两个连续的0字节
  int i;

  for(i = 0; i < rbsp_size; i++)
  {
    if(count == ZEROBYTES_SHORTSTARTCODE && !(rbsp[i] & 0xFC))// 0xFC的二进制是11111100,只有当rbsp[i]=0x00,0x01,0x02,0x03,
    {                                                         //才能使(rbsp[i] & 0xFC)=0,!(rbsp[i] & 0xFC)=1,如果cout==2,
      NaluBuffer[j] = 0x03;                                  //就执行条件内的语句
      j++;                                                  //缓冲区长度加1
      count = 0;                                           //插入之后,将0字节个数归0
    }
    NaluBuffer[j] = rbsp[i];                              //将当前的字节复制到新的缓冲区中。
    if(rbsp[i] == 0x00)                                   //统计字节连续为0的个数。
      count++;
    else
      count = 0;                                         //如果当前字节不为0,0字节个数归0
    j++;
  }

  return j;
}




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先需要了解一下H.264/AVC视频编码的基本结构。H.264视频编码将视频帧分为若干个片(slice),每个片又分为若干个宏块(macroblock),宏块再分为若干个子块(sub-block)。每个子块的编码数据就是一个NAL单元,也就是网络抽象层单元。 在FFmpeg中,可以通过解码器(decoder)获取到视频帧的packet,每个packet对应一个或多个NAL单元。下面是从packet中拆分出NAL单元的完整代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <errno.h> #include <inttypes.h> #include "libavcodec/avcodec.h" #define BUFFER_SIZE 1024 * 1024 int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "Usage: %s <input_file>\n", argv[0]); exit(1); } const char *input_file = argv[1]; AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264); if (!codec) { fprintf(stderr, "Codec not found\n"); exit(1); } AVCodecContext *context = avcodec_alloc_context3(codec); if (!context) { fprintf(stderr, "Could not allocate codec context\n"); exit(1); } if (avcodec_open2(context, codec, NULL) < 0) { fprintf(stderr, "Could not open codec\n"); exit(1); } FILE *file = fopen(input_file, "rb"); if (!file) { fprintf(stderr, "Could not open input file '%s': %s\n", input_file, strerror(errno)); exit(1); } uint8_t *buffer = (uint8_t *) malloc(BUFFER_SIZE); int buffer_size = BUFFER_SIZE; AVPacket packet; av_init_packet(&packet); while (1) { int bytes_read = fread(buffer, 1, buffer_size, file); if (bytes_read < 0) { fprintf(stderr, "Error while reading input file: %s\n", strerror(errno)); exit(1); } if (bytes_read == 0) { break; // end of file } uint8_t *data = buffer; int size = bytes_read; while (size > 0) { int ret = av_parser_parse2(context->parser, context, &packet.data, &packet.size, data, size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); if (ret < 0) { fprintf(stderr, "Error while parsing\n"); exit(1); } data += ret; size -= ret; if (packet.size > 0) { printf("NAL Unit: size=%d\n", packet.size); for (int i = 0; i < packet.size; i++) { printf("%02x ", packet.data[i]); } printf("\n"); } av_packet_unref(&packet); } } free(buffer); fclose(file); avcodec_free_context(&context); return 0; } ``` 这段代码的大致流程如下: 1. 使用AVCodecContext和AVCodec打开H.264解码器。 2. 打开输入文件,并分配输入缓冲区。 3. 循环读取输入文件,每次读取一定数量的数据到缓冲区中。 4. 使用解码器的parser从缓冲区中解析出NAL单元,并输出NAL单元的大小和数据。 5. 释放解码器返回的AVPacket。 注意,这段代码只是演示了如何从H.264视频中拆分出NAL单元,并没有进行任何解码操作。如果需要对视频进行解码,还需要使用解码器的API进行解码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值