1、写在开始之前:

最近因为新工作要维护别人留下的GB模块代码,先熟悉了流程,然后也试着封装了下ps流,结果也能通过测试正常预览了,当然,其中开发读文档的头疼,预览花屏,卡帧的事情都有遇到,当时慢慢的看文档,整理逻辑,也就都顺利解决了,下面把大致的一些流程代码贴出来分享下。既然是对接国标,自然少不了通读它的标准文档和相关的RFC文档了!具体的我就不说了,可以用百度google下的。

注意:因为是GB要求ps封装后再加上rtp头的格式来的, 所以下面代码中我也加上了rtp头,如果不需要的话,直接屏蔽代码中的rtp即可。

2、封装的重点

当我们从读缓冲区中取得一帧音视频数据的时候,封装时其实每一帧数据有且只有一个ps头和psm头,如果是I帧的话,就还多一个system头,一个或者多个pes头和rtp头,

像如果帧数据过长的话,就得进行分片,每片都会包含一个pes头,rtp负载最好长度1460,所以会进行再分包操作!所以每一个包数据至少一个rtp+databuf,每一片数据,至少有个rtp+pes+databuf,每一帧数据至少有rtp+ps+psm+pes+databuf(关键帧的话:多一个system头)

3、具体的各个封装的代码实现

首先给去一个整体的封装rtp->ps->sys->psm->pes(如果只要ps的话,则为ps->sys->psm->pes)的大致流程,

然后再一一罗列出各个部件的封装接口

  1. /***
  2. *@remark:  音视频数据的打包成ps流,并封装成rtp
  3. *@param :  pData      [in] 需要发送的音视频数据
  4. *          nFrameLen  [in] 发送数据的长度
  5. *          pPacker    [in] 数据包的一些信息,包括时间戳,rtp数据buff,发送的socket相关信息
  6. *          stream_type[in] 数据类型 0 视频 1 音频
  7. *@return:  0 success others failed
  8. */
  9. int gb28181_streampackageForH264(char *pData, int nFrameLen, Data_Info_s* pPacker, int stream_type)
  10. {
  11. char szTempPacketHead[256];
  12. int  nSizePos = 0;
  13. int  nSize = 0;
  14. char *pBuff = NULL;
  15. memset(szTempPacketHead, 0, 256);
  16. // 1 package for ps header
  17. gb28181_make_ps_header(szTempPacketHead + nSizePos, pPacker->s64CurPts);
  18. nSizePos += PS_HDR_LEN;
  19. //2 system header
  20. if( pPacker->IFrame == 1 )
  21. {
  22. // 如果是I帧的话,则添加系统头
  23. gb28181_make_sys_header(szTempPacketHead + nSizePos);
  24. nSizePos += SYS_HDR_LEN;
  25. //这个地方我是不管是I帧还是p帧都加上了map的,貌似只是I帧加也没有问题
  26. //      gb28181_make_psm_header(szTempPacketHead + nSizePos);
  27. //      nSizePos += PSM_HDR_LEN;
  28. }
  29. // psm头 (也是map)
  30. gb28181_make_psm_header(szTempPacketHead + nSizePos);
  31. nSizePos += PSM_HDR_LEN;
  32. //加上rtp发送出去,这样的话,后面的数据就只要分片分包就只有加上pes头和rtp头了
  33. if(gb28181_send_rtp_pack(szTempPacketHead, nSizePos, 0, pPacker) != 0 )
  34. return -1;
  35. // 这里向后移动是为了方便拷贝pes头
  36. //这里是为了减少后面音视频裸数据的大量拷贝浪费空间,所以这里就向后移动,在实际处理的时候,要注意地址是否越界以及覆盖等问题
  37. pBuff = pData - PES_HDR_LEN;
  38. while(nFrameLen > 0)
  39. {
  40. //每次帧的长度不要超过short类型,过了就得分片进循环行发送
  41. nSize = (nFrameLen > PS_PES_PAYLOAD_SIZE) ? PS_PES_PAYLOAD_SIZE : nFrameLen;
  42. // 添加pes头
  43. gb28181_make_pes_header(pBuff, stream_type ? 0xC0:0xE0, nSize, (pPacker->s64CurPts / 100), (pPacker->s64CurPts/300));
  44. //最后在添加rtp头并发送数据
  45. if( gb28181_send_rtp_pack(pBuff, nSize + PES_HDR_LEN, ((nSize == nFrameLen)?1:0), pPacker) != 0 )
  46. {
  47. printf("gb28181_send_pack failed!\n");
  48. return -1;
  49. }
  50. //分片后每次发送的数据移动指针操作
  51. nFrameLen -= nSize;
  52. //这里也只移动nSize,因为在while向后移动的pes头长度,正好重新填充pes头数据
  53. pBuff     += nSize;
  54. }
  55. return 0;
  56. }

上面列出来了整个打包发包的过程,接下来一个一个接口的看

  1. /***
  2. *@remark:   ps头的封装,里面的具体数据的填写已经占位,可以参考标准
  3. *@param :   pData  [in] 填充ps头数据的地址
  4. *           s64Src [in] 时间戳
  5. *@return:   0 success, others failed
  6. */
  7. int gb28181_make_ps_header(char *pData, unsigned long long s64Scr)
  8. {
  9. unsigned long long lScrExt = (s64Scr) % 100;
  10. s64Scr = s64Scr / 100;
  11. // 这里除以100是由于sdp协议返回的video的频率是90000,帧率是25帧/s,所以每次递增的量是3600,
  12. // 所以实际你应该根据你自己编码里的时间戳来处理以保证时间戳的增量为3600即可,
  13. //如果这里不对的话,就可能导致卡顿现象了
  14. bits_buffer_s   bitsBuffer;
  15. bitsBuffer.i_size = PS_HDR_LEN;
  16. bitsBuffer.i_data = 0;
  17. bitsBuffer.i_mask = 0x80; // 二进制:10000000 这里是为了后面对一个字节的每一位进行操作,避免大小端夸字节字序错乱
  18. bitsBuffer.p_data = (unsigned char *)(pData);
  19. memset(bitsBuffer.p_data, 0, PS_HDR_LEN);
  20. bits_write(&bitsBuffer, 32, 0x000001BA);            /*start codes*/
  21. bits_write(&bitsBuffer, 2,  1);                     /*marker bits '01b'*/
  22. bits_write(&bitsBuffer, 3,  (s64Scr>>30)&0x07);     /*System clock [32..30]*/
  23. bits_write(&bitsBuffer, 1,  1);                     /*marker bit*/
  24. bits_write(&bitsBuffer, 15, (s64Scr>>15)&0x7FFF);   /*System clock [29..15]*/
  25. bits_write(&bitsBuffer, 1,  1);                     /*marker bit*/
  26. bits_write(&bitsBuffer, 15, s64Scr&0x7fff);         /*System clock [29..15]*/
  27. bits_write(&bitsBuffer, 1,  1);                     /*marker bit*/
  28. bits_write(&bitsBuffer, 9,  lScrExt&0x01ff);        /*System clock [14..0]*/
  29. bits_write(&bitsBuffer, 1,  1);                     /*marker bit*/
  30. bits_write(&bitsBuffer, 22, (255)&0x3fffff);        /*bit rate(n units of 50 bytes per second.)*/
  31. bits_write(&bitsBuffer, 2,  3);                     /*marker bits '11'*/
  32. bits_write(&bitsBuffer, 5,  0x1f);                  /*reserved(reserved for future use)*/
  33. bits_write(&bitsBuffer, 3,  0);                     /*stuffing length*/
  34. return 0;
  35. }
  1. /***
  2. *@remark:   sys头的封装,里面的具体数据的填写已经占位,可以参考标准
  3. *@param :   pData  [in] 填充ps头数据的地址
  4. *@return:   0 success, others failed
  5. */
  6. int gb28181_make_sys_header(char *pData)
  7. {
  8. bits_buffer_s   bitsBuffer;
  9. bitsBuffer.i_size = SYS_HDR_LEN;
  10. bitsBuffer.i_data = 0;
  11. bitsBuffer.i_mask = 0x80;
  12. bitsBuffer.p_data = (unsigned char *)(pData);
  13. memset(bitsBuffer.p_data, 0, SYS_HDR_LEN);
  14. /*system header*/
  15. bits_write( &bitsBuffer, 32, 0x000001BB);   /*start code*/
  16. bits_write( &bitsBuffer, 16, SYS_HDR_LEN-6);/*header_length 表示次字节后面的长度,后面的相关头也是次意思*/
  17. bits_write( &bitsBuffer, 1,  1);            /*marker_bit*/
  18. bits_write( &bitsBuffer, 22, 50000);        /*rate_bound*/
  19. bits_write( &bitsBuffer, 1,  1);            /*marker_bit*/
  20. bits_write( &bitsBuffer, 6,  1);            /*audio_bound*/
  21. bits_write( &bitsBuffer, 1,  0);            /*fixed_flag */
  22. bits_write( &bitsBuffer, 1,  1);            /*CSPS_flag */
  23. bits_write( &bitsBuffer, 1,  1);            /*system_audio_lock_flag*/
  24. bits_write( &bitsBuffer, 1,  1);            /*system_video_lock_flag*/
  25. bits_write( &bitsBuffer, 1,  1);            /*marker_bit*/
  26. bits_write( &bitsBuffer, 5,  1);            /*video_bound*/
  27. bits_write( &bitsBuffer, 1,  0);            /*dif from mpeg1*/
  28. bits_write( &bitsBuffer, 7,  0x7F);         /*reserver*/
  29. /*audio stream bound*/
  30. bits_write( &bitsBuffer, 8,  0xC0);         /*stream_id*/
  31. bits_write( &bitsBuffer, 2,  3);            /*marker_bit */
  32. bits_write( &bitsBuffer, 1,  0);            /*PSTD_buffer_bound_scale*/
  33. bits_write( &bitsBuffer, 13, 512);          /*PSTD_buffer_size_bound*/
  34. /*video stream bound*/
  35. bits_write( &bitsBuffer, 8,  0xE0);         /*stream_id*/
  36. bits_write( &bitsBuffer, 2,  3);            /*marker_bit */
  37. bits_write( &bitsBuffer, 1,  1);            /*PSTD_buffer_bound_scale*/
  38. bits_write( &bitsBuffer, 13, 2048);         /*PSTD_buffer_size_bound*/
  39. return 0;
  40. }
  1. /***
  2. *@remark:   psm头的封装,里面的具体数据的填写已经占位,可以参考标准
  3. *@param :   pData  [in] 填充ps头数据的地址
  4. *@return:   0 success, others failed
  5. */
  6. int gb28181_make_psm_header(char *pData)
  7. {
  8. bits_buffer_s   bitsBuffer;
  9. bitsBuffer.i_size = PSM_HDR_LEN;
  10. bitsBuffer.i_data = 0;
  11. bitsBuffer.i_mask = 0x80;
  12. bitsBuffer.p_data = (unsigned char *)(pData);
  13. memset(bitsBuffer.p_data, 0, PS_SYS_MAP_SIZE);
  14. bits_write(&bitsBuffer, 24,0x000001);   /*start code*/
  15. bits_write(&bitsBuffer, 8, 0xBC);       /*map stream id*/
  16. bits_write(&bitsBuffer, 16,18);         /*program stream map length*/
  17. bits_write(&bitsBuffer, 1, 1);          /*current next indicator */
  18. bits_write(&bitsBuffer, 2, 3);          /*reserved*/
  19. bits_write(&bitsBuffer, 5, 0);          /*program stream map version*/
  20. bits_write(&bitsBuffer, 7, 0x7F);       /*reserved */
  21. bits_write(&bitsBuffer, 1, 1);          /*marker bit */
  22. bits_write(&bitsBuffer, 16,0);          /*programe stream info length*/
  23. bits_write(&bitsBuffer, 16, 8);         /*elementary stream map length  is*/
  24. /*audio*/
  25. bits_write(&bitsBuffer, 8, 0x90);       /*stream_type*/
  26. bits_write(&bitsBuffer, 8, 0xC0);       /*elementary_stream_id*/
  27. bits_write(&bitsBuffer, 16, 0);         /*elementary_stream_info_length is*/
  28. /*video*/
  29. bits_write(&bitsBuffer, 8, 0x1B);       /*stream_type*/
  30. bits_write(&bitsBuffer, 8, 0xE0);       /*elementary_stream_id*/
  31. bits_write(&bitsBuffer, 16, 0);         /*elementary_stream_info_length */
  32. /*crc (2e b9 0f 3d)*/
  33. bits_write(&bitsBuffer, 8, 0x45);       /*crc (24~31) bits*/
  34. bits_write(&bitsBuffer, 8, 0xBD);       /*crc (16~23) bits*/
  35. bits_write(&bitsBuffer, 8, 0xDC);       /*crc (8~15) bits*/
  36. bits_write(&bitsBuffer, 8, 0xF4);       /*crc (0~7) bits*/
  37. return 0;
  38. }
  1. /***
  2. *@remark:   pes头的封装,里面的具体数据的填写已经占位,可以参考标准
  3. *@param :   pData      [in] 填充ps头数据的地址
  4. *           stream_id  [in] 码流类型
  5. *           paylaod_len[in] 负载长度
  6. *           pts        [in] 时间戳
  7. *           dts        [in]
  8. *@return:   0 success, others failed
  9. */
  10. int gb28181_make_pes_header(char *pData, int stream_id, int payload_len, unsigned long long pts, unsigned long long dts)
  11. {
  12. bits_buffer_s   bitsBuffer;
  13. bitsBuffer.i_size = PES_HDR_LEN;
  14. bitsBuffer.i_data = 0;
  15. bitsBuffer.i_mask = 0x80;
  16. bitsBuffer.p_data = (unsigned char *)(pData);
  17. memset(bitsBuffer.p_data, 0, PES_HDR_LEN);
  18. /*system header*/
  19. bits_write( &bitsBuffer, 24,0x000001);  /*start code*/
  20. bits_write( &bitsBuffer, 8, (stream_id));   /*streamID*/
  21. bits_write( &bitsBuffer, 16,(payload_len)+13);  /*packet_len*/ //指出pes分组中数据长度和该字节后的长度和
  22. bits_write( &bitsBuffer, 2, 2 );        /*'10'*/
  23. bits_write( &bitsBuffer, 2, 0 );        /*scrambling_control*/
  24. bits_write( &bitsBuffer, 1, 0 );        /*priority*/
  25. bits_write( &bitsBuffer, 1, 0 );        /*data_alignment_indicator*/
  26. bits_write( &bitsBuffer, 1, 0 );        /*copyright*/
  27. bits_write( &bitsBuffer, 1, 0 );        /*original_or_copy*/
  28. bits_write( &bitsBuffer, 1, 1 );        /*PTS_flag*/
  29. bits_write( &bitsBuffer, 1, 1 );        /*DTS_flag*/
  30. bits_write( &bitsBuffer, 1, 0 );        /*ESCR_flag*/
  31. bits_write( &bitsBuffer, 1, 0 );        /*ES_rate_flag*/
  32. bits_write( &bitsBuffer, 1, 0 );        /*DSM_trick_mode_flag*/
  33. bits_write( &bitsBuffer, 1, 0 );        /*additional_copy_info_flag*/
  34. bits_write( &bitsBuffer, 1, 0 );        /*PES_CRC_flag*/
  35. bits_write( &bitsBuffer, 1, 0 );        /*PES_extension_flag*/
  36. bits_write( &bitsBuffer, 8, 10);        /*header_data_length*/
  37. // 指出包含在 PES 分组标题中的可选字段和任何填充字节所占用的总字节数。该字段之前
  38. //的字节指出了有无可选字段。
  39. /*PTS,DTS*/
  40. bits_write( &bitsBuffer, 4, 3 );                    /*'0011'*/
  41. bits_write( &bitsBuffer, 3, ((pts)>>30)&0x07 );     /*PTS[32..30]*/
  42. bits_write( &bitsBuffer, 1, 1 );
  43. bits_write( &bitsBuffer, 15,((pts)>>15)&0x7FFF);    /*PTS[29..15]*/
  44. bits_write( &bitsBuffer, 1, 1 );
  45. bits_write( &bitsBuffer, 15,(pts)&0x7FFF);          /*PTS[14..0]*/
  46. bits_write( &bitsBuffer, 1, 1 );
  47. bits_write( &bitsBuffer, 4, 1 );                    /*'0001'*/
  48. bits_write( &bitsBuffer, 3, ((dts)>>30)&0x07 );     /*DTS[32..30]*/
  49. bits_write( &bitsBuffer, 1, 1 );
  50. bits_write( &bitsBuffer, 15,((dts)>>15)&0x7FFF);    /*DTS[29..15]*/
  51. bits_write( &bitsBuffer, 1, 1 );
  52. bits_write( &bitsBuffer, 15,(dts)&0x7FFF);          /*DTS[14..0]*/
  53. bits_write( &bitsBuffer, 1, 1 );
  54. return 0;
  55. }
  1. /***
  2. *@remark:   rtp头的打包,并循环发送数据
  3. *@param :   pData      [in] 发送的数据地址
  4. *           nDatalen   [in] 发送数据的长度
  5. *           mark_flag  [in] mark标志位
  6. *           curpts     [in] 时间戳
  7. *           pPacker    [in] 数据包的基本信息
  8. *@return:   0 success, others failed
  9. */
  10. int gb28181_send_rtp_pack(char *databuff, int nDataLen, int mark_flag, Data_Info_s* pPacker)
  11. {
  12. int nRes = 0;
  13. int nPlayLoadLen = 0;
  14. int nSendSize    = 0;
  15. char szRtpHdr[RTP_HDR_LEN];
  16. memset(szRtpHdr, 0, RTP_HDR_LEN);
  17. if(nDataLen + RTP_HDR_LEN <= RTP_MAX_PACKET_BUFF)// 1460 pPacker指针本来有一个1460大小的buffer数据缓存
  18. {
  19. // 一帧数据发送完后,给mark标志位置1
  20. gb28181_make_rtp_header(szRtpHdr, ((mark_flag == 1 )? 1 : 0 ), ++pPacker->u16CSeq, (pPacker->s64CurPts /300), pPacker->u32Ssrc);
  21. memcpy(pPacker->szBuff, szRtpHdr, RTP_HDR_LEN);
  22. memcpy(pPacker->szBuff + RTP_HDR_LEN, databuff, nDataLen);
  23. nRet = SendDataBuff(databuff, RTP_HDR_LEN + nSendSize, pPacker);
  24. if (nRes != (RTP_HDR_LEN + nDataLen))
  25. {
  26. printf(" udp send error !\n");
  27. return -1;
  28. }
  29. }
  30. else
  31. {
  32. nPlayLoadLen = RTP_MAX_PACKET_BUFF - RTP_HDR_LEN; // 每次只能发送的数据长度 除去rtp头
  33. gb28181_make_rtp_header(pPacker->szBuff, 0, ++pPacker->u16CSeq, (pPacker->s64CurPts/100), pPacker->u32Ssrc);
  34. memcpy(pPacker->szBuff + RTP_HDR_LEN, databuff, nPlayLoadLen);
  35. nRet = SendDataBuff(databuff, RTP_HDR_LEN + nSendSize, pPacker);
  36. if (nRes != (RTP_HDR_LEN + nPlayLoadLen))
  37. {
  38. printf(" udp send error !\n");
  39. return -1;
  40. }
  41. nDataLen -= nPlayLoadLen;
  42. // databuff += (nPlayLoadLen - RTP_HDR_LEN);
  43. databuff += nPlayLoadLen; // 表明前面到数据已经发送出去
  44. databuff -= RTP_HDR_LEN; // 用来存放rtp头
  45. while(nDataLen > 0)
  46. {
  47. if(nDataLen <= nPlayLoadLen)
  48. {
  49. //一帧数据发送完,置mark标志位
  50. gb28181_make_rtp_header(databuff, mark_flag, ++pPacker->u16CSeq, (pPacker->s64CurPts/100), pPacker->u32Ssrc);
  51. nSendSize = nDataLen;
  52. }
  53. else
  54. {
  55. gb28181_make_rtp_header(databuff, 0, ++pPacker->u16CSeq, (pPacker->s64CurPts/100), pPacker->u32Ssrc);
  56. nSendSize = nPlayLoadLen;
  57. }
  58. nRet = SendDataBuff(databuff, RTP_HDR_LEN + nSendSize, pPacker);
  59. if (nRes != (RTP_HDR_LEN + nSendSize))
  60. {
  61. printf(" udp send error !\n");
  62. return -1;
  63. }
  64. nDataLen -= nSendSize;
  65. databuff += nSendSize;
  66. //因为buffer指针已经向后移动一次rtp头长度后,
  67. //所以每次循环发送rtp包时,只要向前移动裸数据到长度即可,这是buffer指针实际指向到位置是
  68. //databuff向后重复的rtp长度的裸数据到位置上
  69. }
  70. }
  71. return 0;
  72. }

还有一个很重要的宏定义,之所以把它定义成宏,是因为会频繁调用,其功能是循环将一个字节的8位按位一个一个的压入数据,防止出现夸字节的导致字序出错问题具体实现如下,其实是挪用了vlc源码中的实现过来的,这也是读源码的一个好处,能很好的利用里面比较高级而又方便的功能代码模块

  1. #define PS_HDR_LEN  14
  2. #define SYS_HDR_LEN 18
  3. #define PSM_HDR_LEN 24
  4. #define PES_HDR_LEN 19
  5. #define RTP_HDR_LEN 12
  6. /***
  7. *@remark:  讲传入的数据按地位一个一个的压入数据
  8. *@param :  buffer   [in]  压入数据的buffer
  9. *          count    [in]  需要压入数据占的位数
  10. *          bits     [in]  压入的数值
  11. */
  12. #define bits_write(buffer, count, bits)\
  13. {\
  14. bits_buffer_s *p_buffer = (buffer);\
  15. int i_count = (count);\
  16. uint64_t i_bits = (bits);\
  17. while( i_count > 0 )\
  18. {\
  19. i_count--;\
  20. if( ( i_bits >> i_count )&0x01 )\
  21. {\
  22. p_buffer->p_data[p_buffer->i_data] |= p_buffer->i_mask;\
  23. }\
  24. else\
  25. {\
  26. p_buffer->p_data[p_buffer->i_data] &= ~p_buffer->i_mask;\
  27. }\
  28. p_buffer->i_mask >>= 1;         /*操作完一个字节第一位后,操作第二位*/\
  29. if( p_buffer->i_mask == 0 )     /*循环完一个字节的8位后,重新开始下一位*/\
  30. {\
  31. p_buffer->i_data++;\
  32. p_buffer->i_mask = 0x80;\
  33. }\
  34. }\
  35. }

上面忘记贴出rtp封装头了,这次补充,如果在实际不需要rtp的话,可以直接在gb28181_send_rtp_pack函数接口中屏蔽gb28181_make_rtp_header函数接口即可,当然需要注意一点问题,就是对应的buffer指针的位置就不需要移动rtp头的长度了!

  1. int gb28181_make_rtp_header(char *pData, int marker_flag, unsigned short cseq, long long curpts, unsigned int ssrc)
  2. {
  3. bits_buffer_s   bitsBuffer;
  4. if (pData == NULL)
  5. return -1;
  6. bitsBuffer.i_size = RTP_HDR_LEN;
  7. bitsBuffer.i_data = 0;
  8. bitsBuffer.i_mask = 0x80;
  9. bitsBuffer.p_data = (unsigned char *)(pData);
  10. memset(bitsBuffer.p_data, 0, RTP_HDR_SIZE);
  11. bits_write(&bitsBuffer, 2, RTP_VERSION);    /* rtp version  */
  12. bits_write(&bitsBuffer, 1, 0);              /* rtp padding  */
  13. bits_write(&bitsBuffer, 1, 0);              /* rtp extension    */
  14. bits_write(&bitsBuffer, 4, 0);              /* rtp CSRC count */
  15. bits_write(&bitsBuffer, 1, (marker_flag));          /* rtp marker   */
  16. bits_write(&bitsBuffer, 7, 96);         /* rtp payload type*/
  17. bits_write(&bitsBuffer, 16, (cseq));            /* rtp sequence      */
  18. bits_write(&bitsBuffer, 32, (curpts));      /* rtp timestamp     */
  19. bits_write(&bitsBuffer, 32, (ssrc));        /* rtp SSRC      */
  20. return 0;
  21. }

4、封装相关心得

博主已经分步验证过rtp或者ps又或者rtp+ps封装都能正常预览。其实这个封装真心没什么理论知识说的,看看标准都知道了,只要仔细看标准一步一步的向下走,分析各个封装的各个字段,就没有什么问题了,当然在实际中也有很多小问题苦恼我很久,但是无不是因为标准没注意或者理解有误。现在我也只是简单的把自己实现的代码大致贴出来分享了,希望相关开发的少走一些弯路而已。有问题或者有更好的方法,大家可以相互交流。

关于对H264码流的PS的封装的相关代码实现的更多相关文章

  1. 关于对H264码流的TS的封装的相关代码实现

    1 写在开始之前 在前段时间有分享一个H264封装ps流到相关文章的,这次和大家分享下将H264封装成TS流到相关实现,其实也是工作工作需要.依照上篇一样,分段说明每个数据头的封装情况,当然,一样也会 ...

  2. RTP协议全解析(H264码流和PS流)(转)

    源: RTP协议全解析(H264码流和PS流)

  3. RTP协议全解析(H264码流和PS流)

    转自:http://blog.csdn.net/chen495810242/article/details/39207305 写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个 ...

  4. (转)RTP协议全解(H264码流和PS流)

    写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个比较全面的解析, 其中借鉴了很多文章,我都列在了文章最后,在此表示感谢. 互联网的发展离不开大家的无私奉献,我决定从我做起,希 ...

  5. RTP协议全解(H264码流和PS流)

    写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个比较全面的解析, 其中借鉴了很多文章,我都列在了文章最后,在此表示感谢. 互联网的发展离不开大家的无私奉献,我决定从我做起,希 ...

  6. H264码流打包分析(精华)

    H264码流打包分析 SODB 数据比特串-->最原始的编码数据 RBSP 原始字节序列载荷-->在SODB的后面填加了结尾比特(RBSP trailing bits 一个bit“1”)若 ...

  7. 从H264码流中获取视频宽高 (SPS帧) 升级篇

    之前写过 <从H264码流中获取视频宽高 (SPS帧)> . 但发现很多局限性,而且有时解出来是错误的. 所以重新去研究了. 用了 官方提供的代码库来解析. 花了点时间,从代码库里单独把解 ...

  8. 从H264码流中获取视频宽高 (SPS帧)

    获取.h264视频宽高的方法 花了2个通宵终于搞定.(后面附上完整代码) http://write.blog.csdn.net/postedit/7852406 图像的高和宽在H264的SPS帧中.在 ...

  9. H264码流解析及NALU

    ffmpeg 从mp4上提取H264的nalu http://blog.csdn.net/gavinr/article/details/7183499 639     /* bitstream fil ...

随机推荐

  1. phpStorm pycharm编辑器主题修改,自定义颜色

    新的启程 注: 本人小菜鸟一枚,内容也是从其他博客中借鉴的,谨以此作为写博客开端. phpstorm修改主题: 1. phpstorm主题下载 http://www.phpstorm-themes.c ...

  2. 【BZOJ4010】[HNOI2015]菜肴制作 拓扑排序

    [BZOJ4010][HNOI2015]菜肴制作 Description 知名美食家小 A被邀请至ATM 大酒店,为其品评菜肴. ATM 酒店为小 A 准备了 N 道菜肴,酒店按照为菜肴预估的质量从高 ...

  3. 记录-MySQL中的事件调度Event Scheduler

    下面是自己的实例 /*查询event是否开启(查询结果Off为关闭 On为开启)*/show variables like '%sche%'; /*开启/关闭命令(1开启--0关闭)*/set glo ...

  4. OLTP和OLAP

    1 OLTP和OLAP online transaction processing,联机事务处理.业务类系统主要供基层人员使用,进行一线业务操作,通常被称为联机事务处理. online analyti ...

  5. swift 一句代码补全tableView分割线

    1.swift实现分割线补全 swift一个大进步,只要设置tableView.separatorInset = UIEdgeInsets.zero即可补全分割线, 2.OC实现分割线补全 而在OC中 ...

  6. Pipeline模式(netty源码死磕6)

    精进篇:netty源码死磕6  巧夺天工--Pipeline模式揭秘 1. 巧夺天工--Pipeline模式揭秘 1.1. Pipeline模式简介 管道的发名者叫,Malcolm Douglas M ...

  7. MYSQL:基础——事务处理

    MYSQL:基础——事务处理 事物处理 1.什么是事物处理 事务处理(transaction processing)可以用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行,要么完全不执行 ...

  8. 【leetcode刷题笔记】Best Time to Buy and Sell Stock II

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  9. 3像素文本偏移bug 解决方案

    <style>.box1{ width:100px; height:50px; float:left; background:#dedede;_margin-right:-3px;}.bo ...

  10. linux下无线鼠标驱动执行流程

    操作系统: debian 7.4(linux 3.2.54) 硬件: 一个无线鼠标.一个有线鼠标.usb集线器. 从淘宝上花了15块钱买了个无线鼠标,很好奇它的驱动程序是如何执行的. 首先将usb集线 ...