使用mp4v2实现录制mp4视频,需要准备如下信息:
1、获取mp4v2源码并编译成库文件,对于mp4v2的编译可以看前面的文章android 编译mp4v2 2.0.0生成动态库 ;
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
- 前五个字节为 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
- );
- 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
- );
视频:
- 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)
- 音频:
- sampleDuration
- 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长度+NAL数据,如果传过来的数据没有0x00000001则是纯数据
- (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 <stdio.h>
- #include <string.h>
- #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);
- }
- 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)
- 音频:
- sampleDuration
- 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长度+NAL数据,如果传过来的数据没有0x00000001则是纯数据
- (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 <stdio.h>
- #include <string.h>
- #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);
- }