H264 AAC G711 封装成MP4

这里有三种方法:
1.利用iOS的AVAssetWritter
2.FFmpeg
3.MP4V2

本文使用第三种:

本地h264和aac封装成MP4:

1.读取h264和aac文件的数据
NSString *h264FilePath = [[NSBundle mainBundle] pathForResource:@"文件名" ofType:@".h264"];
NSData *h264Data = [NSData dataWithContentsOfFile:h264FilePath];
//获取音频数据的代码同理
2.切割数据

这里需要考虑你读取到的数据是音频和视频分开的还是合在一起的.
对于合在一起的情况,根据公司定义好的规则把音视频数据切割.对于分开的,按照音视频的规则切割即可,由于合在一起的不好公开,以下贴上音视频分开的情况

//视频
//按照NALU切割视频数据,NALU一般以 0x00 0x00 0x00 0x01 或者 0x00 0x00 0x01分隔开
uint8_t *videoData = (uint8_t*)[h264Data bytes];
int j = 0;
    int lastJ = 0;
    while (j < h264Data.length ) {
        if (videoData[j] == 0x00 &&
            videoData[j + 1] == 0x00 &&
            videoData[j + 2] == 0x00 &&
            videoData[j + 3] == 0x01) {
            if (j > 0) {
                int frame_size = j - lastJ;
                NSData *buff = [NSData dataWithBytes:&videoData[lastJ] length:frame_size];
                lastJ = j;
                [mp4v2Tool addVideoData:buff];
            }
        }else if (j == h264Data.length - 1) {
            int frame_size = j - lastJ;
            NSData *buff = [NSData dataWithBytes:&videoData[lastJ] length:frame_size];
            lastJ = j;
            [mp4v2Tool addVideoData:buff];
        }
        j++;
    }
//音频
uint8_t *voiceData = (uint8_t*)[aacData bytes];
    j = 0;
    lastJ = 0;
    while (j < aacData.length) {
        if (voiceData[j] == 0xff &&
            (voiceData[j + 1] & 0xf0) == 0xf0) {
            if (j > 0) {
                //0xfff判断AAC头
                int frame_size = j - lastJ;
                if (frame_size > 7) {
                    NSData *buff = [NSData dataWithBytes:&voiceData[lastJ] length:frame_size];
//                    NSLog(@"%@",buff);
                    lastJ = j;
                    [mp4v2Tool addAudioData:buff];
                }
            }
        }else if (j == aacData.length - 1) {
            int frame_size = j - lastJ;
            if (frame_size > 7) {
                NSData *buff = [NSData dataWithBytes:&voiceData[lastJ] length:frame_size];
                //                    NSLog(@"%@",buff);
                lastJ = j;
                [mp4v2Tool addAudioData:buff];
            }
        }
        j++;
    }
3.创建MP4文件及设置相关参数
/*
     功能:创建MP4文件句柄。
     返回:MP4文件句柄。
     参数:fileName 要录制的MP4文件名;flags 创建文件类型,如果要创建普通文件用默认值0就可以,如要录制大于4G的MP4文件此处要设置MP4_CREATE_64BIT_DATA。
     */
    m_mp4FHandle = MP4Create(strFilePath);
//m_vTimeScale一般为9000;
MP4SetTimeScale(m_mp4FHandle, m_vTimeScale);
//对于g711
//alaw format
            /*
             初始化添加音频为PCM
             默认采样率是20ms
             */
            m_aTrackId = MP4AddALawAudioTrack(m_mp4FHandle, audioSampleRate);
            MP4SetTrackIntegerProperty(m_mp4FHandle, m_aTrackId, "mdia.minf.stbl.stsd.alaw.channels", 1);
            MP4SetTrackIntegerProperty(m_mp4FHandle, m_aTrackId, "mdia.minf.stbl.stsd.alaw.sampleSize", 8);
4.写入视频数据
根据切割到的nalu获取相关数据
typedef struct _MP4ENC_NaluUnit
{
    int frameType; //帧类型
    int frameLen;  //nalu长度,不包括00 00 00 01
    unsigned char *pframeBuf;   //不包括00 00 00 01
}MP4ENC_NaluUnit;
static int ReadOneNaluFromBuf(const unsigned char *buffer,
                              unsigned int nBufferSize,
                              unsigned int offSet,
                              MP4ENC_NaluUnit &nalu)
{
    unsigned int i = offSet;
    while(i < nBufferSize)
    {
        if(buffer[i++] == 0x00 && buffer[i] == 0x00 && buffer[i+1] == 0x00 && buffer[i+2] == 0x01)
        {
            unsigned int pos = i+3;
            unsigned int iEnd = i+3;
            unsigned int posEnd = 0;
            while (pos < nBufferSize)
            {
                if(buffer[pos++] == 0x00 && buffer[pos] == 0x00 && buffer[pos+1] == 0x00 && buffer[pos+2] == 0x01)
                {
                    posEnd = pos+3;
                    break;
                }
                posEnd = pos;
            }
            if(posEnd == nBufferSize)
            {
                nalu.frameLen = posEnd-iEnd;
            }
            else
            {
                nalu.frameLen = (posEnd - 4) - iEnd;
            }

            nalu.frameType = buffer[iEnd]&0x1f;
            nalu.pframeBuf = (unsigned char*)&buffer[iEnd];
            return (nalu.frameLen+iEnd-offSet);
        }
    }

    return 0;
}

//获取sps
if(m_bGetSpsSlice == false)
            {
                m_vTrackId = MP4AddH264VideoTrack(m_mp4FHandle,
                                                  m_vTimeScale,
                                                  m_vTimeScale/m_vFrateR,
                                                  m_vWidth,
                                                  m_vHeight,
                                                  nalu.pframeBuf[1],
                                                  nalu.pframeBuf[2],
                                                  nalu.pframeBuf[3],
                                                  3);
                if(m_vTrackId == MP4_INVALID_TRACK_ID)
                {
                    printf("add viedo trake failed.\n");
                    return -1;
                }
                
                
                MP4SetVideoProfileLevel(m_mp4FHandle, 1);
                MP4AddH264SequenceParameterSet(m_mp4FHandle, m_vTrackId, nalu.pframeBuf, nalu.frameLen);
                
                m_bGetSpsSlice = true;
            }
            

        }
//获取pps
if (nalu.frameType == 0x08) //pps
        {
            if(m_bGetPpsSlice == false)
            {
                MP4AddH264PictureParameterSet(m_mp4FHandle, m_vTrackId, nalu.pframeBuf, nalu.frameLen);
                m_bGetPpsSlice = true;
                
            }
            
        }
//写入数据
if((nalu.frameType != 0x06) && (nalu.frameType != 0x0d))
        {
            if((m_vTrackId != MP4_INVALID_TRACK_ID) && m_bGetSpsSlice && m_bGetPpsSlice && m_bRecord)
            {
                //当trackID有效,获取到sps,pps时开始写入数据
                int datalen = nalu.frameLen + 4;
                BYTE *data = new BYTE[datalen];
                
                data[0] = nalu.frameLen >> 24;
                data[1] = nalu.frameLen >> 16;
                data[2] = nalu.frameLen >> 8;
                data[3] = nalu.frameLen & 0xff;
                
                memcpy(data+4, nalu.pframeBuf, nalu.frameLen);

                if(!MP4WriteSample(m_mp4FHandle, m_vTrackId, (const uint8_t*)data, datalen,m_vTimeScale/m_vFrateR))
                {
                    printf("write a viedo failed\n");
                    delete []data;

                    return -1;
                }
                m_bGetIFrame = true;
                
                delete []data;
            }

        }
5.写入音频数据
int CMp4Encoder::WriteAudioTrack(BYTE* _aacData,int _aacSize)
{
    if(m_aTrackId == MP4_INVALID_TRACK_ID)
    {
        return -1;
    }
    
    if (!m_bGetIFrame)
    {
        return -1;
    }
    
    if(!m_bRecord)
    {
        return -1;
    }
    
    if(m_audioFormat == WAVE_FORMAT_AAC)
    {
         bool result = MP4WriteSample(m_mp4FHandle, m_aTrackId,(const uint8_t*) _aacData+7, _aacSize-7 ,1024, 0, 1);
        if (result == true) {
            printf("add success!\n");
        }else {
            printf("add failed!\n");
        }
    }
    else if (m_audioFormat == WAVE_FORMAT_G711)
    {
        MP4WriteSample(m_mp4FHandle, m_aTrackId,(const uint8_t*) _aacData, _aacSize ,MP4_INVALID_DURATION, 0, 1);
    }
   
    
    return _aacSize;
}
6.关闭
 MP4Close(m_mp4FHandle); 

MP4V2 编译iOS下使用的.a

MP4V2-iOS

下载后运行脚本即可

相关知识:

常用NAL(Network Abstract Layer)头的取值:
0x67:SPS
0x68:PPS
0x65:IDR
0x61:non-IDR Slice
0x01:B Slice
0x06:SEI
0x09:AU Delimiter

注意点:
需要先获取到sps和pps,序列参数集 SPS 作用于一系列连续的编码图像,而图像参数集 PPS 作用于编码视频序列中一个或多个独立 的图像。如果解码器没能正确接收到这两个参数集,那么其他NALU 也是无法解码的。因此它们一般在发送其它 NALU 之前发送,并且使用不同的信道或者更加可靠的传输协议(如TCP)进行传输,也可以重复传输。
视频帧率和音频采样率要设置正确,否则播放速度不正常或者音视频不同步.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容