SkeyePlayer RTSP播放器源码解析系列之H264一帧多NAL写MP4录像花屏问题解决方案

SkeyePlayer RTSP播放器源码解析系列之H264一帧多NAL写MP4录像花屏问题解决方案接上一篇[SkeyePlayer源码解析系列之录像写MP4]之续篇,我们来讲解一下关于H264编码格式中的一帧多nal(NetworkAbstractLayer,即网络抽象层),关于H264和NAL,这里引用一段话来科普一下:【转】在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。因此我们平时的每帧数据就是一个NAL单元

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

接上一篇[SkeyePlayer源码解析系列之录像写MP4]之续篇,我们来讲解一下关于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单元的长度,否则就会出现视频解码只能播放头部一小部分,其他部分全部花屏的情况,如下图所示:

这里写图片描述

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

int SkeyeMP4Writer::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;
(这段代码是不是看起来很2B?其实不然,我就喜欢这样写,简单,直接,明了,一眼就能看懂!)

将NAL长度拷贝到AVC的缓冲区内,紧接着数据部分拷贝:
memcpy(pRealData+nRealDataSize, pout, outlen);

最后,组装成完成的avc帧之后写入MP4,播放的时候就不会有花屏,马赛克的情况出现了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/183971.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 如何给女朋友做个树莓派复古相机?[通俗易懂]

    如何给女朋友做个树莓派复古相机?[通俗易懂]本文转载自程序员极客实验室手机拍照不够爽,带个单反又太重?试试做个树莓派复古相机,还能自己编写处理算法的那种——成本不到700元。没错,颜值很高,拍出来的照片也能打:你也可以快速上手做一个。如何制作一个树莓派复古相机目前,这部相机的代码、硬件清单、STL文件(用于3D打印)和电路图都已经开源。首先是硬件部分。这部复古相机的硬件清单如下:树莓派ZeroW(搭配microSD卡)、树莓派高清镜头模组、16mm1000万像素长焦镜头、2.2英寸TFT显

    2022年5月27日
    36
  • clion 2022.01.13激活码【中文破解版】「建议收藏」

    (clion 2022.01.13激活码)好多小伙伴总是说激活码老是失效,太麻烦,关注/收藏全栈君太难教程,2021永久激活的方法等着你。https://javaforall.net/100143.htmlIntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,上面是详细链接哦~0HKLM1UCCY-eyJsaWNlbnNlSWQiOi…

    2022年3月31日
    163
  • python爬虫 完整代码

    python爬虫 完整代码这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML图表FLowchart流程图导出与导入导出导入欢迎使用Markdown编辑器你好!这是你第一次使用Markdown编辑器所展示的欢迎页。如果你想学习如何使用Mar

    2022年6月6日
    49
  • 计算机常用编码方式有哪些_计算机网络无分类编码

    计算机常用编码方式有哪些_计算机网络无分类编码计算机常用编码方式

    2025年7月2日
    1
  • p6使用教程_p6slite使用说明书

    p6使用教程_p6slite使用说明书最近开发中用到spring-data-jpa+hibernate的持久层框架,在调试过程中,日志记录的hibernateSQL执行语句无法显示传入的参数,所以上github上搜索了一番,发现了p6spy这个框架,此框架能够无缝地拦截和记录数据库的执行语句,而不会对现有应用程序进行代码更改。下面介绍一下p6spy的简单配置和使用。源码https://github.com/p6s…

    2022年10月6日
    3
  • AWS EC2 增加多个弹性 IP[通俗易懂]

    AWS EC2 增加多个弹性 IP

    2022年2月13日
    47

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号