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