乐趣区

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

接上一篇 [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,播放的时候就不会有花屏,马赛克的状况呈现了。

退出移动版