h.264判断一帧的结束

3 篇文章 0 订阅
1 篇文章 0 订阅

最近上了一个项目,有些地方要抠一些细节,达到效果最优,其中有一处就是要从es流中整合出完整的帧。搜了一些资料,然后参照live555的代码做一下总结,留着以后备用。

PS: 从事流媒体开发时间不长,有哪些地方理解有误,还希望能帮我指正出来,共同进步^_^ 

首先讲一下es流的结构,看完标准发现h.264没有帧的概念,是以片(slice也有翻译成条带的)为单元的。


如下图:



参考文章:http://zhongcong386.blog.163.com/blog/static/1347278042012112762445528/

NAL_header(1Byte)+(slice_header)+(slice_data)+......+NAL_header(1Byte)+(slice_header)+(slice_data)   

slice_data= rbsp_byte=宏块(实实在在的视频数据),当然上面nal_header之后的rbsp数据格式不同类型的NAL也不同。

比如:nal_unit_type ==5 的idr slice 格式是slice_header( )+slice_data( )+rbsp_slice_trailing_bits( )。 具体的各个nal slice的格式参看标准的7.3.2.*

(nal_unit_type <= 5 && nal_unit_type > 0)的nal单元都负载的VCL数据(视频流),每个vcl类型的nal单元存储的是一个片(slice)。


接下来就是分理出完整一帧数据。假设我们已经分离出了连续的nal_unit_type =1~5的NAL。

第一种情况 没有下一个nal则表示当前的nal是当前帧的最后一部分数据。

第二种情况  nal_unit_type !=next_nal_unit_type  则表示上一帧已经结束。

第三种情况 nal_unit_type == next_nal_unit_type的时候有可能是同一帧的数据,也有可能是不同帧的数据。(如:两个PP帧在一块就有可能出现这种情况)

出现第三种情况的时候我们需要分析slice_header里面的frame_num这一个值。如果frame_num != next_frame_num 则表示当前nal已经是当前帧的最后一部分数据。

slice_header()的句法如下: PS 太多啦,有需求直接去看标准;

slice_header( ) { 
 first_mb_in_slice 2 ue(v)
 slice_type 2 ue(v)
 pic_parameter_set_id 2 ue(v)
 frame_num 2 u(v)
 if( !frame_mbs_only_flag ) {
  field_pic_flag 2 u(1)
 if( field_pic_flag )
 bottom_field_flag 2 u(1)
 }
 if( nal_unit_type = = 5 )
 idr_pic_id 2 ue(v)
 if( pic_order_cnt_type = = 0 ) {
 pic_order_cnt_lsb 2 u(v)
 if( pic_order_present_flag && !field_pic_flag )
 delta_pic_order_cnt_bottom 2 se(v)
 }
 ..................................
 ..................................
 if( num_slice_groups_minus1 > 0 && slice_group_map_type >= 3 && slice_group_map_type <= 5)
 slice_group_change_cycle 2 u(v)
}



具体live555分析的代码我摘录下来,贴在下面
fHaveSeenFirstByteOfNALUnit =true表示当前帧结束
unsigned H264VideoStreamParser::parse() {
..........................
..........................
   // If this NAL unit is a VCL NAL unit, we also scan the start of the next NAL unit, to determine whether this NAL unit
    // ends the current 'access unit'.  We need this information to figure out when to increment "fPresentationTime".
    // (RTP streamers also need to know this in order to figure out whether or not to set the "M" bit.)
    Boolean thisNALUnitEndsAccessUnit = False; // until we learn otherwise 
    if (haveSeenEOF()) {//最后一个nal肯定是帧的最后一部分了
      thisNALUnitEndsAccessUnit = True;
    } else {
      Boolean const isVCL = nal_unit_type <= 5 && nal_unit_type > 0; //Would need to include type 20 for SVC and MVC #####
      if (isVCL) {//
		  u_int8_t firstByteOfNextNALUnit;
		  testBytes(&firstByteOfNextNALUnit, 1);
		  u_int8_t next_nal_ref_idc = (firstByteOfNextNALUnit&0x60)>>5;
		  u_int8_t next_nal_unit_type = firstByteOfNextNALUnit&0x1F;
	if (next_nal_unit_type >= 6) {//nal_unit_type !=next_nal_unit_type的情况
 	// The next NAL unit is not a VCL; therefore, we assume that this NAL unit ends the current 'access unit':
	  thisNALUnitEndsAccessUnit = True;
	} else {
	  // The next NAL unit is also a VCL.  We need to examine it a little to figure out if it's a different 'access unit'.
	  // (We use many of the criteria described in section 7.4.1.2.4 of the H.264 specification.)
	  Boolean IdrPicFlag = nal_unit_type == 5;
	  Boolean next_IdrPicFlag = next_nal_unit_type == 5;
	  if (next_IdrPicFlag != IdrPicFlag) {
	    // IdrPicFlag differs in value
	    thisNALUnitEndsAccessUnit = True;
	  } else if (next_nal_ref_idc != nal_ref_idc && next_nal_ref_idc*nal_ref_idc == 0) {
	    //nal_ref_idc differs in value with one of the nal_ref_idc values being equal to 0
	    thisNALUnitEndsAccessUnit = True;
	  } else if ((nal_unit_type == 1 || nal_unit_type == 2 || nal_unit_type == 5)
		     && (next_nal_unit_type == 1 || next_nal_unit_type == 2 || next_nal_unit_type == 5)) {//这是第三种情况主要的功能在
//analyze_slice_header中
	    // Both this and the next NAL units begin with a "slice_header".
	    // Parse this (for each), to get parameters that we can compare:
	    // Current NAL unit's "slice_header":
	    unsigned frame_num, pic_parameter_set_id, idr_pic_id;
	    Boolean field_pic_flag, bottom_field_flag;
		analyze_slice_header(fStartOfFrame + fOutputStartCodeSize, fTo, nal_unit_type,
			frame_num, pic_parameter_set_id, idr_pic_id, field_pic_flag, bottom_field_flag);  
	    // Next NAL unit's "slice_header":
	    u_int8_t next_slice_header[NUM_NEXT_SLICE_HEADER_BYTES_TO_ANALYZE];
	    testBytes(next_slice_header, sizeof next_slice_header);
	    unsigned next_frame_num, next_pic_parameter_set_id, next_idr_pic_id;
	    Boolean next_field_pic_flag, next_bottom_field_flag;
		analyze_slice_header(next_slice_header, &next_slice_header[sizeof next_slice_header], next_nal_unit_type,
			next_frame_num, next_pic_parameter_set_id, next_idr_pic_id, next_field_pic_flag, next_bottom_field_flag);
	    if (next_frame_num != frame_num) {
	      // frame_num differs in value
	      thisNALUnitEndsAccessUnit = True;
	    } else if (next_pic_parameter_set_id != pic_parameter_set_id) {
	      // pic_parameter_set_id differs in value
	      thisNALUnitEndsAccessUnit = True;
	    } else if (next_field_pic_flag != field_pic_flag) {
	      // field_pic_flag differs in value
	      thisNALUnitEndsAccessUnit = True;
	    } else if (next_bottom_field_flag != bottom_field_flag) {
	      // bottom_field_flag differs in value
	      thisNALUnitEndsAccessUnit = True;
	    } else if (next_IdrPicFlag == 1 && next_idr_pic_id != idr_pic_id) {
	      // IdrPicFlag is equal to 1 for both and idr_pic_id differs in value
	      // Note: We already know that IdrPicFlag is the same for both.
	      thisNALUnitEndsAccessUnit = True;
	    }
	  }
..........................
}

先就这样,下次再补充。

 

这一篇博客有更好的方法和大家一块分享http://www.360doc.com/content/13/0913/15/13084517_314201133.shtml

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Android中,可以使用MediaCodec API来进行H.264编码。下面是一个简单的示例代码,演示如何使用MediaCodec API编码图像数据。 首先,需要创建一个MediaCodec对象并配置它的参数: ```java MediaCodec codec = MediaCodec.createEncoderByType("video/avc"); MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); Surface inputSurface = codec.createInputSurface(); ``` 其中,`width`和`height`分别是图像的宽度和高度,`bitRate`是比特率,`frameRate`是帧率,`iFrameInterval`是关键帧间隔。`COLOR_FormatSurface`表明使用Surface作为输入源。 然后,可以开始编码图像数据。这里假设已经将图像数据绘制到了Surface上: ```java codec.start(); while (!isEOS) { int inputIndex = codec.dequeueInputBuffer(-1); if (inputIndex >= 0) { ByteBuffer inputBuffer = codec.getInputBuffer(inputIndex); inputBuffer.clear(); if (/* 判断是否为最后一帧 */) { codec.queueInputBuffer(inputIndex, 0, /*图像数据大小*/, /*时间戳*/, MediaCodec.BUFFER_FLAG_END_OF_STREAM); isEOS = true; } else { // 将图像数据复制到inputBuffer中 codec.queueInputBuffer(inputIndex, 0, /*图像数据大小*/, /*时间戳*/, 0); } } int outputIndex = codec.dequeueOutputBuffer(info, TIMEOUT_US); if (outputIndex >= 0) { ByteBuffer outputBuffer = codec.getOutputBuffer(outputIndex); // 对outputBuffer中的数据进行处理,例如将它写入文件或网络 codec.releaseOutputBuffer(outputIndex, false); } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // 处理编码器输出格式变化的情况 MediaFormat newFormat = codec.getOutputFormat(); } } codec.stop(); codec.release(); ``` 在上面的代码中,使用`dequeueInputBuffer()`获取一个输入缓冲区,并将图像数据复制到该缓冲区中。然后,使用`queueInputBuffer()`将缓冲区提交给编码器进行编码。如果已经到达最后一帧,需要将`BUFFER_FLAG_END_OF_STREAM`标志设置为true,表示输入流结束。 使用`dequeueOutputBuffer()`获取一个输出缓冲区,并对该缓冲区中的数据进行处理。处理完成后,使用`releaseOutputBuffer()`将缓冲区释放,同时指示编码器是否需要渲染这一帧。 最后,使用`stop()`和`release()`方法停止和释放编码器。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值