低延时高RTSP兼容的EasyPlayer-RTSP-win解决H.264一帧多个nal单元录像花屏问题方案

本文主要是介绍低延时高RTSP兼容的EasyPlayer-RTSP-win解决H.264一帧多个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单元的长度,否则就会出现视频解码只能播放头部一小部分,其他部分全部花屏的情况,如下图所示:

1.gif

说了这么多,大家是否明白了呢,如果不明白的(文字描述比较虚),我们直接看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];//outlenmemcpy(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,播放的时候就不会有花屏,马赛克的情况出现了。

这篇关于低延时高RTSP兼容的EasyPlayer-RTSP-win解决H.264一帧多个nal单元录像花屏问题方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/930875

相关文章

SpringBoot3.X 整合 MinIO 存储原生方案

《SpringBoot3.X整合MinIO存储原生方案》本文详细介绍了SpringBoot3.X整合MinIO的原生方案,从环境搭建到核心功能实现,涵盖了文件上传、下载、删除等常用操作,并补充了... 目录SpringBoot3.X整合MinIO存储原生方案:从环境搭建到实战开发一、前言:为什么选择MinI

Java中读取YAML文件配置信息常见问题及解决方法

《Java中读取YAML文件配置信息常见问题及解决方法》:本文主要介绍Java中读取YAML文件配置信息常见问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录1 使用Spring Boot的@ConfigurationProperties2. 使用@Valu

Knife4j+Axios+Redis前后端分离架构下的 API 管理与会话方案(最新推荐)

《Knife4j+Axios+Redis前后端分离架构下的API管理与会话方案(最新推荐)》本文主要介绍了Swagger与Knife4j的配置要点、前后端对接方法以及分布式Session实现原理,... 目录一、Swagger 与 Knife4j 的深度理解及配置要点Knife4j 配置关键要点1.Spri

SQL Server配置管理器无法打开的四种解决方法

《SQLServer配置管理器无法打开的四种解决方法》本文总结了SQLServer配置管理器无法打开的四种解决方法,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录方法一:桌面图标进入方法二:运行窗口进入检查版本号对照表php方法三:查找文件路径方法四:检查 S

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

Redis出现中文乱码的问题及解决

《Redis出现中文乱码的问题及解决》:本文主要介绍Redis出现中文乱码的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 问题的产生2China编程. 问题的解决redihttp://www.chinasem.cns数据进制问题的解决中文乱码问题解决总结

SQLite3 在嵌入式C环境中存储音频/视频文件的最优方案

《SQLite3在嵌入式C环境中存储音频/视频文件的最优方案》本文探讨了SQLite3在嵌入式C环境中存储音视频文件的优化方案,推荐采用文件路径存储结合元数据管理,兼顾效率与资源限制,小文件可使用B... 目录SQLite3 在嵌入式C环境中存储音频/视频文件的专业方案一、存储策略选择1. 直接存储 vs

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决

Springboot如何正确使用AOP问题

《Springboot如何正确使用AOP问题》:本文主要介绍Springboot如何正确使用AOP问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录​一、AOP概念二、切点表达式​execution表达式案例三、AOP通知四、springboot中使用AOP导出