zoukankan      html  css  js  c++  java
  • 低延时高RTSP兼容的EasyPlayerRTSPwin解决H.264一帧多个nal单元录像花屏问题方案

    EasyPlayer-RTSP-win解决H264一帧多个nal单元录像花屏问题

    我们来讲解一下关于H264编码格式中的一帧多nal(Network Abstract Layer, 即网络抽象层),关于H264和NAL,这里引用一段话来科普一下:

    【转】 在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。因此我们平时的每帧数据就是一个NAL单元(SPS与PPS除外)。在实际的H264数据帧中,往往帧前面带有00 00 00 01 或 00 00 01分隔符,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧……

    一般情况下,一个H264帧直接以00 00 00 01开头作为一个NAL作为网络传输单元,而在有些H264的编码器则编码出来的H264帧包含了多个NAL,这个时候每个分片的NAL(注意是分片的)则是是以00 00 01开头作为网络传输单元,经过分片的NAL数据量更小,从而更加方便进行网络;但是,我们在接收到带有多个NAL的H264帧的时候进行写MP4则不能简单是只通过将头部的00 00 00 01标志转换从AVC的长度标识,而需要将所有的00 00 00 01和00 00 01都需要转换成该NAL单元的长度,否则就会出现视频解码只能播放头部一小部分,其他部分全部花屏的情况,如下图所示:

    说了这么多,大家是否明白了呢,如果不明白的(文字描述比较虚),我们直接看EasyPlayer代码实现:

    int EasyMP4Writer::WriteMp4File(unsigned char* pdata, int datasize, bool keyframe, long nTimestamp, int nWidth, int nHeight)
    {
    	if (nTimestamp==0||(pdata==NULL)||datasize<=0)
    	{
    		return -1;
    	}
    	int inlen=datasize;
    	unsigned char*pin=pdata;
    	int outlen=0;
    	unsigned char*pout=NULL;
    	bool bend = false;
    
    	int datalen=0;
    	bool bSPSOrPPS = false;
    
    	int iOutLen = datasize;
    
    	unsigned char* pRealData = new unsigned char[datasize<<1];
    	int nRealDataSize = 0;
    	memset(pRealData,0x00, datasize+4);
    	do 
    	{
    		int nal_start = 0;
    		int nal_end = 0;
    		outlen = find_nal_unit(pin,inlen, &nal_start, &nal_end );
    		if (outlen<=0)
    		{
    			break;
    		}
    		pout = pin+nal_start;
    		if(pout!=NULL)
    		{
    			unsigned char naltype = ( pout[0] & 0x1F);
    
    			if (naltype==0x07)//0x67
    			{
    // 				m_psps=pout;
    // 				m_spslen=outlen;
    				//pout[0] = 0x67;
    				if(m_bwritevideoinfo==false)
    				{
    					m_psps = new unsigned char[outlen];
    					memcpy(m_psps, pout, outlen);
    					m_spslen=outlen;
    				}
    				bSPSOrPPS = true;
    			}
    			else if (naltype==0x08)//0x68
    			{
    				// 				m_ppps=pout;
    				// 				m_ppslen=outlen;
    				//pout[0] = 0x68;
    				if(m_bwritevideoinfo==false)
    				{
    					m_ppps = new unsigned char[outlen];//outlen
    					memcpy(m_ppps, pout, outlen);
    					m_ppslen = outlen;
    				}
    				bSPSOrPPS = true;
    			}
    // 			else if (pout[0] == 0x06)//SEI
    // 			{
    // 
    // 			}
    //			else
    			{
    				memcpy(pRealData+nRealDataSize, &outlen, 4);
    				//写入头4个字节==nal内容的长度(H264数据的长度)
    				unsigned char byte0 = pRealData[nRealDataSize+3];
    				unsigned char byte1 = pRealData[nRealDataSize+2];
    				pRealData[nRealDataSize+3] = pRealData[nRealDataSize+0];
    				pRealData[nRealDataSize+2] = pRealData[nRealDataSize+1];
    				pRealData[nRealDataSize+1] = byte1;
    				pRealData[nRealDataSize+0] = byte0;
    
    				nRealDataSize += 4;
    
    				memcpy(pRealData+nRealDataSize, pout, outlen);
    				nRealDataSize += outlen;
    			}
    
    			inlen=inlen-outlen-(pout-pin);
    			pin=pout+outlen;
    		}
    	} while (bend!=true);
    
    	if (m_bwritevideoinfo==false&&m_ppps&&m_psps)
    	{
    		// PPS末尾的0过滤,否则VLC可能播放不出来 [12/22/2015 Dingshuai]
    		int nPPSSize = m_ppslen;
    		int nZeroCount = 0;
    		for (int nI = nPPSSize-1; nI>=0; nI--)
    		{
    			if (m_ppps[nI] == 0x00)
    			{
    				nZeroCount++;
    			}
    			else
    			{
    				break;
    			}
    		}
    		m_ppslen = m_ppslen-nZeroCount;
    		WriteH264SPSandPPS(m_psps,m_spslen,m_ppps,m_ppslen,nWidth,nHeight);
    		m_bwritevideoinfo = true;
    	}
    	if (m_bwritevideoinfo==false||nRealDataSize<=0 )
    	{
    		return 0;//获取sps pps失败
    	}
    
    // 	if(/*bSPSOrPPS*/pout[0]==0x67 || pout[0]==0x68)
    // 	{
    // 		return 0;
    // 	}
    	WriteH264Frame(pRealData, nRealDataSize, keyframe, nTimestamp);//左移4单位,加上数据长度头?
    
    	if (pRealData)
    	{	
    		delete []pRealData;
    		pRealData = NULL;
    	}
    
    	return true;
    }
    
    

    其中find_nal_unit()函数是从H264帧中分析出以00 00 00 01和00 00 01开头的NAL单元,然后直接填充成该NAL单元的长度,注意字节顺序为大端顺序:
    //写入头4个字节==nal内容的长度(H264数据的长度)

    unsigned char byte0 = pRealData[nRealDataSize+3];
    unsigned char byte1 = pRealData[nRealDataSize+2];
    pRealData[nRealDataSize+3] = pRealData[nRealDataSize+0];
    pRealData[nRealDataSize+2] = pRealData[nRealDataSize+1];
    pRealData[nRealDataSize+1] = byte1;
    pRealData[nRealDataSize+0] = byte0;

    将NAL长度拷贝到AVC的缓冲区内,紧接着数据部分拷贝:
    memcpy(pRealData+nRealDataSize, pout, outlen);
    最后,组装成完成的avc帧之后写入MP4,播放的时候就不会有花屏、马赛克的情况出现了。

  • 相关阅读:
    flume sink两种类型 file_rool 自定义sing com.mycomm.MySink even if there is only one event, the event has to be sent in an array
    为什么引入进程20年后,又引入线程?
    As of Flume 1.4.0, Avro is the default RPC protocol.
    Google Protocol Buffer 的使用和原理
    Log4j 2
    统一日志 统一订单
    网站行为跟踪 Website Activity Tracking Log Aggregation 日志聚合 In comparison to log-centric systems like Scribe or Flume
    Percolator
    友盟吴磊:移动大数据平台的架构、实践与数据增值
    Twitter的RPC框架Finagle简介
  • 原文地址:https://www.cnblogs.com/TSINGSEE/p/11714029.html
Copyright © 2011-2022 走看看