写点什么

MediaMuxer 实用封装

用户头像
Changing Lin
关注
发布于: 21 小时前
MediaMuxer实用封装

1. MediaMuxer

MediaMuxer facilitates muxing elementary streams. Currently MediaMuxer supports MP4, Webm and 3GP file as the output. It also supports muxing B-frames in MP4 since Android Nougat.

简单来讲,可以实现 MP4、Webm、3GP 等容器文件,用于音视频文件的录制。由于 Android 原生的接口较为简介,无法满足日常开发需求,因此,对其进行简单封装,达到易用的目标。

2.代码

  • MuxerApi.kt 接口

import android.media.MediaCodecimport android.media.MediaFormatimport java.nio.ByteBuffer
interface MuxerApi {
fun start() fun stop() fun writeSampleData( trackIndex: Int, byteBuf: ByteBuffer, bufferInfo: MediaCodec.BufferInfo ) fun addTrack(format: MediaFormat): Int}
复制代码
  • AdvanceMuxerApi.kt 接口


import android.media.MediaCodecimport android.media.MediaFormatimport org.easydarwin.util.FrameInfoimport java.nio.ByteBuffer
/** * 开放给外部的录制接口 */interface AdvanceMuxerApi: MuxerApi { fun isInitTrackSuccess(): Boolean fun addTrack(format: MediaFormat, isVideo: Boolean): Int fun writeAudioFrame(outputBuffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo)
fun writeVideoFrame(outputBuffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo) fun writeVideoFrame(frameInfo: FrameInfo)}
复制代码
  • BaseMuxer.kt 抽象类

import android.media.MediaCodecimport android.media.MediaFormatimport android.media.MediaMuxerimport com.nufront.trunking.common.system.LogUtilimport java.io.Fileimport java.nio.ByteBuffer
abstract class BaseMuxer(val filePath: String, val fileName: String): AdvanceMuxerApi {
private val TAG = "BaseMuxer"
private var mediaMuxer: MediaMuxer?
init { val path = if (fileName.endsWith(".mp4")) { "$filePath/$fileName" } else "$filePath/$fileName.mp4"
LogUtil.printI(TAG, "目标录制文件名称:$path") val file = File(path) if (file.exists()) file.delete()
mediaMuxer = MediaMuxer(path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) }
override fun start() { mediaMuxer?.start() }
override fun stop() { try { mediaMuxer?.stop() mediaMuxer?.release() mediaMuxer = null }catch (e: Exception){ e.printStackTrace() LogUtil.printException(e) } }
override fun writeSampleData( trackIndex: Int, byteBuf: ByteBuffer, bufferInfo: MediaCodec.BufferInfo ) { mediaMuxer?.writeSampleData(trackIndex, byteBuf, bufferInfo) }
override fun addTrack(format: MediaFormat): Int { return mediaMuxer?.addTrack(format) ?: -1 }
}
复制代码
  • VideoMuxer.kt 视频录制实体类


import android.media.MediaCodecimport android.media.MediaFormatimport com.nufront.trunking.common.system.LogUtilimport org.easydarwin.util.FrameInfoimport java.nio.ByteBuffer
class VideoMuxer(filePath: String, fileName: String) : BaseMuxer(filePath, fileName) { private val TAG = "VideoMuxer"
private var mVideoTrackIndex = -1 private var mAudioTrackIndex = -1 private var vTimeStamp: Long = 0 // 视频时间戳 private var aTimeStamp: Long = 0 // 音频时间戳
override fun addTrack(format: MediaFormat, isVideo: Boolean): Int { val index = super.addTrack(format) if (isVideo) mVideoTrackIndex = index else mAudioTrackIndex = index return index }
/** * 是否构造音频轨道、视频轨道完毕 */ override fun isInitTrackSuccess(): Boolean{ return mVideoTrackIndex != -1 && mAudioTrackIndex != -1 }
private fun isAudioTrackSuccess(): Boolean{ return mAudioTrackIndex != -1 }
private fun isVideoTrackSuccess(): Boolean{ return mVideoTrackIndex != -1 } /** * 写入音频帧 */ @Synchronized override fun writeAudioFrame(outputBuffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo) { if (isInitTrackSuccess()){ if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) { // The codec config data was pulled out and fed to the muxer when we got // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. } else if (bufferInfo.size != 0) { // adjust the ByteBuffer values to match BufferInfo (not needed?) outputBuffer.position(bufferInfo.offset) outputBuffer.limit(bufferInfo.offset + bufferInfo.size) if (aTimeStamp != 0L) { if (bufferInfo.presentationTimeUs - aTimeStamp <= 0) { LogUtil.printE(TAG, "audio timestample goback, ignore!") return } } aTimeStamp = bufferInfo.presentationTimeUs super.writeSampleData(mAudioTrackIndex, outputBuffer, bufferInfo) }
if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { LogUtil.i(TAG, "BUFFER_FLAG_END_OF_STREAM received") } }else{ LogUtil.printE(TAG, "异常情况:writeAudioFrame方法AudioTrack未初始化完成!") } }
/** * 写入视频帧 */ @Synchronized override fun writeVideoFrame(outputBuffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo) { if (isInitTrackSuccess()){ if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) { // The codec config data was pulled out and fed to the muxer when we got // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. } else if (bufferInfo.size != 0) { // adjust the ByteBuffer values to match BufferInfo (not needed?) outputBuffer.position(bufferInfo.offset) outputBuffer.limit(bufferInfo.offset + bufferInfo.size) if (vTimeStamp != 0L) { if (bufferInfo.presentationTimeUs - vTimeStamp <= 0) { LogUtil.printE(TAG, "video timestample goback, ignore!") return } } vTimeStamp = bufferInfo.presentationTimeUs super.writeSampleData(mVideoTrackIndex, outputBuffer, bufferInfo) }
if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { LogUtil.i(TAG, "BUFFER_FLAG_END_OF_STREAM received") } }else{ LogUtil.printE(TAG, "异常情况:writeVideoFrame方法AudioTrack未初始化完成!") } }
override fun writeVideoFrame(frameInfo: FrameInfo) { val bi = MediaCodec.BufferInfo() bi.offset = frameInfo.offset bi.size = frameInfo.length val buffer = ByteBuffer.wrap(frameInfo.buffer, bi.offset, bi.size) bi.presentationTimeUs = frameInfo.stamp * 1000 // 解决录制的视频播放过快,时间戳的转换 if (frameInfo.audio){ LogUtil.printE(TAG, "错误的帧类型:${frameInfo.audio}") }else{ bi.flags = if (frameInfo.type != 1) 0 else MediaCodec.BUFFER_FLAG_KEY_FRAME writeVideoFrame(buffer, bi) } }}
复制代码


3.总结

通过几个文件的封装,可以实现对 MP4 文件的录制功能,便于后续扩展开发。

发布于: 21 小时前阅读数: 3
用户头像

Changing Lin

关注

获得机遇的手段远超于固有常规之上~ 2020.04.29 加入

我能做的,就是调整好自己的精神状态,以最佳的面貌去面对那些未曾经历过得事情,对生活充满热情和希望。

评论

发布
暂无评论
MediaMuxer实用封装