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

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

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

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

  1. SkeyePlayer RTSP/RTMP流媒体超低延迟播放器源码解析系列之H264一帧多NAL写MP4录像花屏问题解决方案

    接上一篇[SkeyePlayer源码解析系列之录像写MP4]之续篇,我们来讲解一下关于H264编码格式中的一帧多nal(Network Abstract Layer, 即网络抽象层),关于H264和N ...

  2. 一步步实现windows版ijkplayer系列文章之二——Ijkplayer播放器源码分析之音视频输出——视频篇...

    一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...

  3. 一步步实现windows版ijkplayer系列文章之三——Ijkplayer播放器源码分析之音视频输出——音频篇

    https://www.cnblogs.com/harlanc/p/9693983.html 目录 OpenSL ES & AudioTrack 源码分析 创建播放器音频输出对象 配置并创建音 ...

  4. Tomcat源码解析系列二:Tomcat总体架构

    Tomcat即是一个HTTP服务器,也是一个servlet容器,主要目的就是包装servlet,并对请求响应相应的servlet,纯servlet的web应用似乎很好理解Tomcat是如何装载serv ...

  5. openGauss数据库源码解析系列文章--openGauss简介(一)

    openGauss数据库是华为深度融合在数据库领域多年经验,结合企业级场景要求推出的新一代企业级开源数据库.此前,Gauss松鼠会已经发布了openGauss数据库核心技术系列文章,介绍了openGa ...

  6. Redux 源码解析系列(一) -- Redux的实现思想

    文章来源: IMweb前端社区 黄qiong(imweb.io) IMweb团队正在招聘啦,简历发至jayccchen@tencent.com Redux 其实是用来帮我们管理状态的一个框架,它暴露给 ...

  7. TiKV 源码解析系列文章(二)raft-rs proposal 示例情景分析

    作者:屈鹏 本文为 TiKV 源码解析系列的第二篇,按照计划首先将为大家介绍 TiKV 依赖的周边库 raft-rs .raft-rs 是 Raft 算法的 Rust 语言实现.Raft 是分布式领域 ...

  8. prometheus变量_TiKV 源码解析系列文章(四)Prometheus(下)

    本文为 TiKV 源码解析系列的第四篇,接上篇继续为大家介绍 rust-prometheus.上篇主要介绍了基础知识以及最基本的几个指标的内部工作机制,本篇会进一步介绍更多高级功能的实现原理. 与上篇 ...

  9. TiKV 源码解析系列 - Raft 的优化

    这篇文章转载TiDB大牛 唐刘 的博客:https://mp.weixin.qq.com/s?__biz=MzI3NDIxNTQyOQ==&mid=2247484544&idx=1&a ...

  10. 超美响应式自适应引导页带音乐播放器源码

    介绍: 超美响应式自适应引导页带音乐播放器源码,纯html源码,右键记事本修改即可~自带音乐播放器,自带自动打字特效,自带网站存活时间,自带随机ACG背景,自适应响应式网页 网盘下载地址: http: ...

最新文章

  1. GCD 容易让人迷惑的几个小问题
  2. 求助:Event ID:10021日志错误的解决方法
  3. hive内部表与外部表区别
  4. 项目: 推箱子图形化游戏 【C++ / C】
  5. 《蓝桥小记》 2018-04-01
  6. RoR开发环境搭建 RAILS install log
  7. Spring Security源码解析(一)——认证和鉴权
  8. Javascript 仿Flash 图片切换 及 Flash 图片切换
  9. python命令行进入帮助模式_Python 命令行之旅:深入 click 之选项篇
  10. sql: sql developer tunnel转接
  11. 【翻译】Windows下文件的命名
  12. Json API接口数据生成
  13. so easy(并查集)
  14. H3C路由器配置忘记密码恢复
  15. android首页广告倒计时,(安卓APP)简单的首页广告倒计时实现代码
  16. 八股文(Spring)
  17. 什么是工业AGV导航读码器?用在什么地方?
  18. 基于asp.net706酒店客户关系管理系统
  19. python用七巧板图片画个图_画一个太大而不能放进一个七巧板的图像
  20. pretraining+fine tuning

热门文章

  1. 堆积柱形图显示总数_excel堆积柱形图如何显示总数
  2. 西门子g120变频器接线图_西门子S7—1500第二十二讲(G120变频器第二篇)
  3. donet学习笔记4(validate)
  4. ARM的存储控制器以及如何使用SDRAM
  5. 《仿人机器人原理与实战》一第3章
  6. ENSP模拟AC/AP配置
  7. 商贸宝显示连接不到服务器,登录T1商贸宝就提示 服务器链接失败 请重新登录 这个怎么解决?...
  8. 02-02 逐帧动画、多组动画案例 实现小人跑步效果
  9. 基本的核方法和径向基函数简介
  10. python模拟鼠标点击脚本_python模拟鼠标点击和键盘输入的操作