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,播放的时候就不会有花屏、马赛克的情况出现了。

  • 相关阅读:
    SQL Server profile使用技巧
    Java 十进制和十六制之间的转化(负数的处理)
    Step By Step(Lua调用C函数)
    Python、Lua和Ruby之优劣
    C++注释规范
    树莓派、 Arduino 、传统单片机开发板该如何选择?
    正则表达式
    python 读写文件
    python连接Oracle数据库
    MySQL获取Schema表名和字段信息
  • 原文地址:https://www.cnblogs.com/TSINGSEE/p/11714029.html
Copyright © 2011-2022 走看看