当前位置:   article > 正文

研究Android音视频-2-MediaCodec使用:YUV码流编码为MP4的示例_android 保存yuv流为mp4文件

android 保存yuv流为mp4文件

" isAlias: ${codecInfo.isAlias} \n" +
" isSoftwareOnly: ${codecInfo.isSoftwareOnly} \n" +
" supportedTypes: ${
codecInfo.supportedTypes.map {
println(“encoder: $it”)
}
} \n" +
" isVendor: ${codecInfo.isVendor} \n" +
" isHardwareAccelerated: ${codecInfo.isHardwareAccelerated}" +
“”
)
}

codecInfos.forEach { codecInfo ->
if (!codecInfo.isEncoder)
println(
“decoder name: ${codecInfo.name} \n” +
" canonicalName: ${codecInfo.canonicalName} \n" +
" isAlias: ${codecInfo.isAlias} \n" +
" isSoftwareOnly: ${codecInfo.isSoftwareOnly} \n" +
" supportedTypes: ${
codecInfo.supportedTypes.map {
println(“decoder: $it”)
}
} \n" +
" isVendor: ${codecInfo.isVendor} \n" +
" isHardwareAccelerated: ${codecInfo.isHardwareAccelerated}" +
“”
)
}
}

/*
打印示例
“video/avc”:H.264硬件编码器
encoder name: OMX.qcom.video.encoder.avc
canonicalName: OMX.qcom.video.encoder.avc
isAlias: false
isSoftwareOnly: false
supportedTypes: [kotlin.Unit]
isVendor: true
isHardwareAccelerated: true
“video/hevc”:H.265软解编码器
encoder name: c2.android.hevc.encoder
canonicalName: c2.android.hevc.encoder
isAlias: false
isSoftwareOnly: true
supportedTypes: [kotlin.Unit]
isVendor: false
isHardwareAccelerated: false
*/

/查找指定的编解码器/
val codec = findCodec(“video/avc”, false, true)//查找H.264硬解码器
fun findCodec(
mimeType: String,
isEncoder: Boolean,
isHard: Boolean = true
): MediaCodecInfo? {
val mediaCodecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
val codecInfos = mediaCodecList.codecInfos
return codecInfos.find {
it.isEncoder == isEncoder && !it.isSoftwareOnly == isHard && hasThisCodec(
it,
mimeType
)
}
}
private fun hasThisCodec(codecInfo: MediaCodecInfo, mimeType: String): Boolean {
return codecInfo.supportedTypes.find { it.equals(mimeType) } != null
}

录制yuv文件

打开Camera,通过回调流保存原始YUV数据。简单存几秒即可,1080p下存了225帧,存储占用1080*1920*225*3/2=667.4M字节

示例工程: 地址

示例代码:

val fos = FileOutputStream(“$filesDir/test.yuv”)
cameraView = findViewById(R.id.cameraview)
cameraView.cameraParams.facing = Camera.CameraInfo.CAMERA_FACING_BACK
cameraView.cameraParams.isFilp = false
cameraView.cameraParams.isScaleWidth = true
cameraView.cameraParams.previewSize.previewWidth = 1920
cameraView.cameraParams.previewSize.previewHeight = 1080
cameraView.addPreviewFrameCallback(object : PreviewFrameCallback {
override fun analyseData(data: ByteArray): Any {
fos.write(data)
return 0
}

override fun analyseDataEnd(t: Any) {}
})
addLifecycleObserver(cameraView)

YUV视频流编码为h.264码流并通过MediaMuxer保存为mp4文件

编码流程:

  1. 查询编码队列是否空闲
  2. 将需要编码的数据复制到编码队列
  3. 查询编码完成队列是否有完成的数据
  4. 将已编码完成的数据复制到cpu内存

单线程示例代码:

//TODO YUV视频流编码为H.264/H.265码流并通过MediaMuxer保存为mp4文件
fun convertYuv2Mp4(context: Context) {
val yuvPath = “ c o n t e x t . f i l e s D i r / t e s t . y u v " v a l s a v e M p 4 P a t h = " {context.filesDir}/test.yuv" val saveMp4Path = " context.filesDir/test.yuv"valsaveMp4Path="{context.filesDir}/test.mp4”
File(saveMp4Path).deleteOnExit()

val mime = “video/avc” //若设备支持H.265也可以使用’video/hevc’编码器
val format = MediaFormat.createVideoFormat(mime, 1920, 1080)
format.setInteger(
MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
)
//widthheightframeRate*[0.1-0.2]码率控制清晰度
format.setInteger(MediaFormat.KEY_BIT_RATE, 1920 * 1080 * 3)
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30)
//每秒出一个关键帧,设置0为每帧都是关键帧
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
format.setInteger(
MediaFormat.KEY_BITRATE_MODE,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR//遵守用户设置的码率
)

//定义并启动编码器
val videoEncoder = MediaCodec.createEncoderByType(mime)
videoEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
videoEncoder.start()

// 当前编码帧信息
val bufferInfo = MediaCodec.BufferInfo()

//定义混合器:输出并保存h.264码流为mp4
val mediaMuxer =
MediaMuxer(
“${context.filesDir}/test.mp4”,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4
);
var muxerTrackIndex = -1
val byteArray = ByteArray(1920 * 1080 * 3 / 2)
var read = 0
var inputEnd = false//数据读取完毕,并且全部都加载至编码器
var pushEnd = false //数据读取完毕,并且成功发出eof信号
val presentTimeUs = System.nanoTime() / 1000

//从文件中读取yuv码流,模拟输入流
FileInputStream(“${context.filesDir}/test.yuv”).use { fis ->
loop1@ while (true) {
//step1 将需要编码的数据逐帧送往编码器
if (!inputEnd) {
//step1.1 查询编码器队列是否空闲
val inputQueueIndex = videoEncoder.dequeueInputBuffer(30);
if (inputQueueIndex > 0) {
read = fis.read(byteArray)
if (read == byteArray.size) {
//默认从Camera中保存的YUV NV21,编码后颜色成反,手动转为NV12后,颜色正常
val convertCost = measureTimeMillis {
val start = 1920 * 1080
val end = 1920 * 1080 / 4 - 1
for (i in 0…end) {
val temp = byteArray[2 * i + start]
byteArray[2 * i + start] = byteArray[2 * i + start + 1]
byteArray[2 * i + start + 1] = temp
}
}
//step1.2 将数据送往编码器,presentationTimeUs为送往编码器的跟起始值的时间差,单位为微妙
val inputBuffer =
videoEncoder.getInputBuffer(inputQueueIndex)
inputBuffer?.clear()
inputBuffer?.put(byteArray)
videoEncoder.queueInputBuffer(
inputQueueIndex,
0,
byteArray.size,
System.nanoTime() / 1000 - presentTimeUs,
0
)
} else {
inputEnd = true//文件读取结束标记
}
}
}

//step2 将结束标记传给编码器
if (inputEnd && !pushEnd) {
val inputQueueIndex = videoEncoder.dequeueInputBuffer(30);
if (inputQueueIndex > 0) {
val pts: Long = System.nanoTime() / 1000 - presentTimeUs
videoEncoder.queueInputBuffer(
inputQueueIndex,
0,
byteArray.size,
pts,
MediaCodec.BUFFER_FLAG_END_OF_STREAM
)
pushEnd = true
println(“数据输入完成,成功发出eof信号”)
}
}

//step3 从编码器中取数据,不及时取出,缓冲队列被占用,编码器将阻塞不进行编码工作
val outputQueueIndex = videoEncoder.dequeueOutputBuffer(bufferInfo, 30)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

结语

看到这篇文章的人不知道有多少是和我一样的Android程序员。

35岁,这是我们这个行业普遍的失业高发阶段,这种情况下如果还不提升自己的技能,进阶发展,我想,很可能就是本行业的职业生涯的终点了。

我们要有危机意识,切莫等到一切都成定局时才开始追悔莫及。只要有规划的,有系统地学习,进阶提升自己并不难,给自己多充一点电,你才能走的更远。

千里之行始于足下。这是上小学时,那种一元钱一个的日记本上每一页下面都印刷有的一句话,当时只觉得这句话很短,后来渐渐长大才慢慢明白这句话的真正的含义。

有了学习的想法就赶快行动起来吧,不要被其他的事情牵绊住了前行的脚步。不要等到裁员时才开始担忧,不要等到面试前一晚才开始紧张,不要等到35岁甚至更晚才开始想起来要学习要进阶。

给大家一份系统的Android学习进阶资料,希望这份资料可以给大家提供帮助。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

被其他的事情牵绊住了前行的脚步。不要等到裁员时才开始担忧,不要等到面试前一晚才开始紧张,不要等到35岁甚至更晚才开始想起来要学习要进阶。

给大家一份系统的Android学习进阶资料,希望这份资料可以给大家提供帮助。
[外链图片转存中…(img-cmWxikgl-1712020148734)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/895183
推荐阅读
相关标签
  

闽ICP备14008679号