博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Mp4v2实现h264+aac打包成Mp4视频文件
阅读量:4160 次
发布时间:2019-05-26

本文共 5713 字,大约阅读时间需要 19 分钟。

Mp4v2实现h264+aac打包成Mp4视频文件

2014年09月06日 ⁄ 综合 ⁄ 共 4472字 ⁄ 字号 小 中 大 ⁄ 评论关闭

 

原出处 

  

  使用mp4v2实现录制mp4视频,需要准备如下信息:

1、获取mp4v2源码并编译成库文件,对于mp4v2的编译可以看前面的文章

2、获取h264数据中的sps和pps数据,如果不会的话可以查看前面的文章 ;

3、获取音频解码信息,在调用MP4SetTrackESConfiguration使用,具体的获取方式一种通过faac获取,方法faacEncGetDecoderSpecificInfo(hEncoder,&(ASC), &(ASCLength));//得到解码信息另一种查看aac音频源码,并并对照aac的adts头格式分析: 

前五个字节为 AAC object types  LOW     2  接着4个字节为 码率index        16000      8  接着4个字节为 channels 个数                 1  应打印出的正确2进制形式为  00010 | 1000 | 0001 | 000                                                              2          8        1

所以为(20,8)

4、规定时间刻度问题,一般网上都是设置90000,这个90000是指1秒的tick数,其实也就是把1秒中分为90000份,在添加音视频的函数中

durationMP4_INVALID_DURATION

 

bool MP4WriteSample(  MP4FileHandle hFile,    MP4TrackId trackId,    u_int8_t* pBytes,    u_int32_t numBytes,    MP4Duration duration = MP4_INVALID_DURATION,    MP4Duration renderingOffset = 0,    bool isSyncSample = true   //如果是视频,此处是指当前视频帧是否是关键帧,如果是则为1,如果不是则为0     ); 其中duration这个参数是指视频帧存在的时间,在90000这个刻度上存在的时间,如果用户确定录像时的音频视频帧是按固定速率的,那么在这里可以设置MP4_INVALID_DURATION,这时mp4v2就会使用下面的函数中的时间刻度

视频: 

MP4AddH264VideoTrack(MP4FileHandle hFile,                                      uint32_t timeScale,                                      MP4Duration sampleDuration,                                      uint16_t width,                                      uint16_t height,                                      uint8_t AVCProfileIndication,                                      uint8_t profile_compat,                                      uint8_t AVCLevelIndication,                                      uint8_t sampleLenFieldSizeMinusOne)  sampleDuration为视频的固定的视频帧的显示时间,计算的方法为timeScale(90000)* during(这个值是当前视频帧的采集时间 - 上一帧的视频  采集时间)/1000,公式也可以改为timeScale(90000)/fps(码率例如20f) 音频:  MP4TrackId MP4AddAudioTrack(  MP4FileHandle hFile,    u_int32_t timeScale,    u_int32_t sampleDuration,    u_int8_t audioType = MP4_MPEG4_AUDIO_TYPE    )   sampleDuration 是音频的音频帧在时间刻度上存在的时间,这里的timeScale为音频的采样时间,例如44100,32000,计算方法给上面的视频一样的。  sampleDuration主要作用是用于音视频同步问题 5、mp4v2录制视频中h264的格式要求:长度+NAL数据,如果传过来的数据没有 则是纯数据    (1)h264流中的NAL,头四个字节是0x00000001; (2)mp4中的h264track,头四个字节要求是NAL的长度,并且是大端顺序;  (3)mp4v2很可能针对此种情况并没有做处理,所以写到mp4文件中的每个NAL头四个字节还是0x00000001.  因此如果传过来的h264数据是纯数据的话则需要如下修改:       int nalsize = frameSize;        buf[0] = (nalsize&0xff000000)>>24;        buf[1] = (nalsize&0x00ff0000)>>16;        buf[2] = (nalsize&0x0000ff00)>>8;        buf[3] = nalsize&0x000000ff;
如果头部格式有其他的,则按照上面的方式偏移到纯数据位置。 6、mp4v2录制视频中aac的格式要求:有时远程传过来的aac数据的格式为adts+aac纯数据,则需要将adts部分去掉,即需要偏移7个字节的单位  下面就开始编写如何调用mp4v2库的方法:  #include 
#include
#include "../mp4v2/mp4v2.h" #include "AppCameraShooting.h" MP4TrackId video; MP4TrackId audio; MP4FileHandle fileHandle; unsigned char sps_pps_640[17] = {0x67, 0x42, 0x40, 0x1F, 0x96 ,0x54, 0x05, 0x01, 0xED, 0x00, 0xF3, 0x9E, 0xA0, 0x68, 0xCE, 0x38, 0x80}; //存储sps和pps int video_width = 640; int video_height = 480; //视频录制的调用,实现初始化 JNIEXPORT bool JNICALL Java_com_seuic_jni_AppCameraShooting_mp4init (JNIEnv *env, jclass clz, jstring title, jint type) { const char* local_title = (*env)->GetStringUTFChars(env,title, NULL); //创建mp4文件 fileHandle = MP4Create(local_title, 0); if(fileHandle == MP4_INVALID_FILE_HANDLE) { return false; } memcpy(sps_pps, sps_pps_640, 17); video_width = 640; video_height = 480; //设置mp4文件的时间单位 MP4SetTimeScale(fileHandle, 90000); //创建视频track //根据ISO/IEC 14496-10 可知sps的第二个,第三个,第四个字节分别是 AVCProfileIndication,profile_compat,AVCLevelIndication 其中90000/20 中的20>是fps video = MP4AddH264VideoTrack(fileHandle, 90000, 90000/20, video_width, video_height, sps_pps[1], sps_pps[2], sps_pps[3], 3); if(video == MP4_INVALID_TRACK_ID) { MP4Close(fileHandle, 0); return false; } audio = MP4AddAudioTrack(fileHandle, 16000, 1024, MP4_MPEG2_AAC_LC_AUDIO_TYPE); if(audio == MP4_INVALID_TRACK_ID) { MP4Close(fileHandle, 0); return false; } //设置sps和pps MP4AddH264SequenceParameterSet(fileHandle, video, sps_pps, 13); MP4AddH264PictureParameterSet(fileHandle, video, sps_pps+13, 4); MP4SetVideoProfileLevel(fileHandle, 0x7F); MP4SetAudioProfileLevel(fileHandle, 0x02); MP4SetTrackESConfiguration(fileHandle, audio, &ubuffer[0], 2); (*env)->ReleaseStringUTFChars(env, title, local_title); return true; } //添加视频帧的方法 JNIEXPORT void JNICALL Java_com_seuic_jni_AppCameraShooting_mp4packVideo (JNIEnv *env, jclass clz, jbyteArray data, jint size, jint keyframe) { unsigned char *buf = (unsigned char *)(*env)->GetByteArrayElements(env, data, JNI_FALSE); if(video_type == 1){ int nalsize = size; buf[0] = (nalsize & 0xff000000) >> 24; buf[1] = (nalsize & 0x00ff0000) >> 16; buf[2] = (nalsize & 0x0000ff00) >> 8; buf[3] = nalsize & 0x000000ff; MP4WriteSample(fileHandle, video, buf, size, MP4_INVALID_DURATION, 0, keyframe); } (*env)->ReleaseByteArrayElements(env, data, (jbyte *)buf, 0); } //添加音频帧的方法 JNIEXPORT void JNICALL Java_com_seuic_jni_AppCameraShooting_mp4packAudio (JNIEnv *env, jclass clz, jbyteArray data, jint size) { uint8_t *bufaudio = (uint8_t *)(*env)->GetByteArrayElements(env, data, JNI_FALSE); MP4WriteSample(fileHandle, audio, &bufaudio[7], size-7, MP4_INVALID_DURATION, 0, 1); //减去7为了删除adts头部的7个字节 (*env)->ReleaseByteArrayElements(env, data, (jbyte *)bufaudio, 0); } //视频录制结束调用 JNIEXPORT void JNICALL Java_com_seuic_jni_AppCameraShooting_mp4close (JNIEnv *env, jclass clz) { MP4Close(fileHandle, 0); }

谢谢这位兄弟了。对我帮助很大。

转载地址:http://kmdxi.baihongyu.com/

你可能感兴趣的文章
又是缓存惹的祸!!!
查看>>
为什么要实现程序指令和程序数据的分离?
查看>>
我对C++ string和length方法的一个长期误解------从protobuf序列化说起(没处理好会引起数据丢失、反序列化失败哦!)
查看>>
无protobuf协议情况下的反序列化------貌似无解, 其实有解!
查看>>
make -n(仅列出命令, 但不会执行)用于调试makefile
查看>>
makefile中“-“符号的使用
查看>>
go语言如何从终端逐行读取数据?------用bufio包
查看>>
go的值类型和引用类型------重要的概念
查看>>
求二叉树中结点的最大值(所有结点的值都是正整数)
查看>>
用go的flag包来解析命令行参数
查看>>
来玩下go的http get
查看>>
队列和栈的本质区别
查看>>
matlab中inline的用法
查看>>
如何用matlab求函数的最值?
查看>>
Git从入门到放弃
查看>>
java8采用stream对集合的常用操作
查看>>
EasySwift/YXJOnePixelLine 极其方便的画出真正的一个像素的线
查看>>
Ubuntu Linux系统下apt-get命令详解
查看>>
ubuntu 16.04 下重置 MySQL 5.7 的密码(忘记密码)
查看>>
Ubuntu Navicat for MySQL安装以及破解方案
查看>>