前言:找了很久,很少有NDK层使用合成mp4的例程,由于我们使用的是实时流合成mp4的文件,基本所有的视频流都是在C++操作的。因此写个笔记记录一下对实时流合成mp4格式视频。
1、要使用的对象:
AMediaMuxer *mMediaMuxer = nullptr;
AMediaFormat *mMediaFormat = nullptr;
int64_t mMediaMuxerVideoFrameIndex = 0;
int muxerIndex = -1;
2、创建对象:这里需要注意的是,一定要设置好视频流的vps/sps/pps数据。否则无法生生mp4文件。其中h265只需要使用csd_0。
int MediaCodec_MuxerCreateMp4(const char *pathName) {
mMediaMuxer_fp = fopen(pathName, "wb+");// 打开新建一个文件。
if (mMediaMuxer_fp == nullptr) {
LOGCATE("MediaCodecMuxer:: Mp4 file fopen err!");
return -1;
}
mMediaMuxer_fd = fileno(mMediaMuxer_fp); // 由于muexr的原因,这里需要转换一下。
if (mMediaMuxer_fd < 0) {
perror("mp4 file err: ");
LOGCATE("MediaCodecMuxer:: Mp4 file open err! = %d", mMediaMuxer_fd);
}
// 设置mp4输出的格式。
mMediaFormat = AMediaFormat_new();
AMediaFormat_setInt32(mMediaFormat, AMEDIAFORMAT_KEY_WIDTH, RecordWidth);
AMediaFormat_setInt32(mMediaFormat, AMEDIAFORMAT_KEY_HEIGHT, RecordHeight);
AMediaFormat_setInt32(mMediaFormat, AMEDIAFORMAT_KEY_FRAME_RATE, RecordFrameRate);
if (format == H265_FORMAT) {
AMediaFormat_setString(mMediaFormat, AMEDIAFORMAT_KEY_MIME, "video/hevc");
AMediaFormat_setBuffer(mMediaFormat, AMEDIAFORMAT_KEY_CSD_0, sdp_info_date, sdp_info_size);
} else if (format == H264_FORMAT) {
AMediaFormat_setString(mMediaFormat, AMEDIAFORMAT_KEY_MIME, "video/avc");
AMediaFormat_setBuffer(mMediaFormat, AMEDIAFORMAT_KEY_CSD_0, sps_buf, sps_len);
AMediaFormat_setBuffer(mMediaFormat, AMEDIAFORMAT_KEY_CSD_1, pps_buf, pps_len);
}
mMediaMuxer = AMediaMuxer_new(mMediaMuxer_fd, AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4);// 新建一个复合输出。
muxerIndex = AMediaMuxer_addTrack(mMediaMuxer, mMediaFormat);// 添加一路视频流。
AMediaMuxer_start(mMediaMuxer);// 开启复合器输出。
mMediaMuxerVideoFrameIndex = 0;// 视频帧数初始化。
LOGCATE("MediaCodecMuxer:: Params FrameRate= %d, FrameRateTimeUs = %ld", RecordFrameRate,
FrameRateTimeUs);
// 开启录像线程。
pthread_mutex_lock(&Mutex_RecordingVideo);
Write_File_Is_First_IDR_Frame = false; //第一帧要从关键帧开始。
RecordingVideo_State = RECORDING_STATE_RECORDING;
pthread_mutex_unlock(&Mutex_RecordingVideo);
LOGCATE("MediaCodecMuxer:: Start Recording...");
return 0;
}
3、写入数据:循环写入数据即可。这里写入的数据是h264/h265数据包的。FrameRateTimeUs是按照帧率计算出来的。这个的单位是us。例如25帧的话,则FrameRateTimeUs = 1s/25 = 40000us。这里是口算的,计算错误了请评论纠正。
int MediaCodec_MuxerWriteVideoFrame(const uint8_t *data, int size) {
if (format == H265_FORMAT) {
if (data[4] == Muxer_H265_IDR || data[4] == Muxer_H265_P) {
AMediaCodecBufferInfo info;// 设置视频帧参数。
info.flags = 1;// 这里NDK没有定义buffer视频帧参数,参照java层参数这里写1
info.size = size;
info.offset = 0;
info.presentationTimeUs = FrameRateTimeUs * (mMediaMuxerVideoFrameIndex++);
AMediaMuxer_writeSampleData(mMediaMuxer, muxerIndex, data, &info);
}
} else if (format == H264_FORMAT) {
if (data[4] == Muxer_H264_IDR || data[4] == Muxer_H264_P) {
AMediaCodecBufferInfo info;
info.flags = 1;//
info.size = size;
info.offset = 0;
info.presentationTimeUs = FrameRateTimeUs * (mMediaMuxerVideoFrameIndex++);
AMediaMuxer_writeSampleData(mMediaMuxer, muxerIndex, data, &info);
}
}
return 0;
}
4、写入结束标志位,这里偷懒,直接写入一个空的数据包就可以了。如果不写这个结束包的话,保存的视频也可以播放,但是log会报一个录入音视频流没有正确结束的错误:mpeg4writer: stop() called but track is not started or stopped。
int MediaCodec_MuxerWriteEndFrame() {
uint8_t buf[5] = {0X00, 0X00, 0X00, 0X01, 0X00};
AMediaCodecBufferInfo info;
info.flags = AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
info.size = 0;
info.offset = 0;
info.presentationTimeUs = FrameRateTimeUs * (mMediaMuxerVideoFrameIndex++);
AMediaMuxer_writeSampleData(mMediaMuxer, muxerIndex, buf, &info);
return 0;
}
5、结束录像:其实也就是释放资源。其中muexr的释放,最好先stop(),再删除。
void MediaCodec_MuxerCloseMp4() {
MediaCodec_MuxerWriteEndFrame();
AMediaFormat_delete(mMediaFormat);
AMediaMuxer_stop(mMediaMuxer);
AMediaMuxer_delete(mMediaMuxer);
mMediaMuxer = nullptr;
mMediaFormat = nullptr;
fclose(mMediaMuxer_fp);
mMediaMuxer_fp = nullptr;
close(mMediaMuxer_fd);
mMediaMuxer_fd = -1;
}
另外:这里只有视频部分,因为目前处理的流只有视频。音频部分大概也是在视频轨道的基础上再加一个音频轨道,如果有大佬在此基础上增加,可以A一下我,多谢了。大概流程就是这样了。全部代码由于都是字面意思,注释就不写那么详细了。