MediaMuxer 实用封装
发布于: 21 小时前
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.MediaCodec
import android.media.MediaFormat
import 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.MediaCodec
import android.media.MediaFormat
import org.easydarwin.util.FrameInfo
import 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.MediaCodec
import android.media.MediaFormat
import android.media.MediaMuxer
import com.nufront.trunking.common.system.LogUtil
import java.io.File
import 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.MediaCodec
import android.media.MediaFormat
import com.nufront.trunking.common.system.LogUtil
import org.easydarwin.util.FrameInfo
import 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
版权声明: 本文为 InfoQ 作者【Changing Lin】的原创文章。
原文链接:【http://xie.infoq.cn/article/0c8f799cee592dc0b58e51bcc】。文章转载请联系作者。
Changing Lin
关注
获得机遇的手段远超于固有常规之上~ 2020.04.29 加入
我能做的,就是调整好自己的精神状态,以最佳的面貌去面对那些未曾经历过得事情,对生活充满热情和希望。
评论