android - Android MediaCodec recording produces a video which cannot be played on huawei
问题描述
I use MediaCodec.inputSurface
and MediaMixuer
for recording of my android view. Everything is good on the most of devices, but not on Huawei. For some reason it produces a video which cannot be played on this devices. But for some unknown reason 1 out of 10 times it generates a good video. Here's link to the broken video and to the normal video. It is also weird that both videos can be played on my mac laptop.
Our users reported the issue from multiple huawei models and I can confirm it from my phone: HUAWEI P8 lite 2017, android 7.0. Also it happens on new phones too with any android version.
Here's code of how I manage the recording:
/**
* Stages:
* 1. Draw canvas to bitmap
* 2. Take bitmap pixels and convert them to YUV
* 3. Write bitmap pixels as a frame to MediaCodec
* 4. Take mediaCodec and write to mediaMuxer to receive file
*/
class VideoEncoder(
val width: Int,
val height: Int,
val frameRate: Int,
val file: File,
val durationUs: Long,
val handler: Handler,
val videoRecordingFinished: () -> Unit,
val onError: (MediaCodec.CodecException) -> Unit
) : KoinComponent {
var mediaMuxer: MediaMuxer? = null
var videoCodec: MediaCodec? = null
var videoTrackIndex = 0
var surface: Surface? = null
val videoBufferInfo by lazy { MediaCodec.BufferInfo() }
var writingVideoFinished = false
private var currentFrame = 0
var audioEncoder: AudioEncoder? = null
var writingAudioFinished: Boolean
get() = audioEncoder?.writingAudioFinished ?: true
set(value) {
audioEncoder?.writingAudioFinished = value
}
var videoFormatInited: Boolean = false
val allFormatsInited: Boolean
get() = videoFormatInited && (audioEncoder?.audioFormatInited != false)
private val pendingVEncoderInfos = LinkedList<MediaCodec.BufferInfo>()
private val pendingVEncoderIndices = LinkedList<Int>()
val logger: KLogger by inject {
parametersOf("video-encoder")
}
private fun createVideoFormat(mimeType: String, desiredColorFormat: Int): MediaFormat {
val mediaFormat =
MediaFormat.createVideoFormat(mimeType, width, height)
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, ENCODING_VIDEO_BITRATE)
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, this.frameRate)
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, desiredColorFormat)
return mediaFormat
}
private fun findCorrectVideoFormat(): MediaFormat {
val mimeType = POSSIBLE_MIME_TYPES[0]
val desiredColorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
val mediaFormat = createVideoFormat(mimeType, desiredColorFormat)
val encoderForFormat =
MediaCodecList(MediaCodecList.REGULAR_CODECS).findEncoderForFormat(mediaFormat)
if (encoderForFormat == null) {
logger.info { "encoderForFormatIsNull!!! width = $width, height = $height" }
videoCodec = MediaCodec.createEncoderByType(mimeType)
} else {
videoCodec = MediaCodec.createByCodecName(encoderForFormat)
}
val codecInfo = videoCodec!!.codecInfo
if (codecInfo.isEncoder && codecInfo.supportedTypes.contains(mimeType) &&
codecInfo.getCapabilitiesForType(mimeType).colorFormats
.contains(desiredColorFormat)
) {
} else {
throw IllegalStateException("MediaCodec is wrong = ${codecInfo}")
}
val errorMessage = checkIsColorFormatSupported(mediaFormat, desiredColorFormat, mimeType)
if (errorMessage != null)
throw IllegalStateException(errorMessage)
return mediaFormat
}
//return error message if false
fun checkIsColorFormatSupported(
mediaFormat: MediaFormat,
desiredColorFormat: Int,
mimeType: String
): String? {
var colorFormats = videoCodec!!.codecInfo.getCapabilitiesForType(mimeType).colorFormats
var colorFormatSize = colorFormats.size
var counterColorFormat = 0
val colorFormatCorrect: Boolean
while (true) {
if (counterColorFormat >= colorFormatSize) {
colorFormatCorrect = false
break
}
if (colorFormats[counterColorFormat] == desiredColorFormat) {
colorFormatCorrect = true
break
}
++counterColorFormat
}
if (!colorFormatCorrect) {
var message = "NO COLOR FORMAT COMPATIBLE\\n$mediaFormat"
colorFormats = videoCodec!!.codecInfo.getCapabilitiesForType(mimeType).colorFormats
colorFormatSize = colorFormats.size
counterColorFormat = 0
while (counterColorFormat < colorFormatSize) {
val sb = StringBuilder()
sb.append(message)
sb.append("\\n")
sb.append(colorFormats[counterColorFormat])
message = sb.toString()
logger.debug { message }
++counterColorFormat
}
return message
}
return null
}
private fun printVideoCodecInfo() {
logger.debug {
val json = JSONObject()
json.put("codec_name", videoCodec!!.name)
json.put("codec_info_name", videoCodec!!.codecInfo.name)
json.put("codec_supported_types", videoCodec!!.codecInfo.supportedTypes)
json.put("output_width", width)
json.put("output_height", height)
json.toString()
}
}
@Throws(Exception::class)
fun initialize(videoAsyncEncoder: Boolean) {
val filePath = file.canonicalPath
val mediaFormat = findCorrectVideoFormat()
printVideoCodecInfo()
if (videoAsyncEncoder) {
videoCodec!!.setCallback(object : MediaCodec.Callback() {
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
}
override fun onOutputBufferAvailable(
codec: MediaCodec,
index: Int,
info: MediaCodec.BufferInfo
) {
pendingVEncoderIndices.add(index)
pendingVEncoderInfos.add(info)
if (allFormatsInited)
checkVideoOutputAvailable()
}
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
writingVideoFinished = true
e.printDebug()
onError.invoke(e)
}
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
onVideoFormatChanged(format)
}
}, handler)
}
videoCodec!!.configure(
mediaFormat, null, null,
MediaCodec.CONFIGURE_FLAG_ENCODE
)
surface = videoCodec!!.createInputSurface()
mediaMuxer = MediaMuxer(filePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
}
fun initAudio(
path: String,
startTimeUs: Long,
volume: Int,
audioRecordingFinished: () -> Unit
) {
audioEncoder = AudioEncoder(
mediaMuxer!!,
handler,
durationUs,
::checkFormatsInited,
audioRecordingFinished
)
audioEncoder!!.initAudio(path, startTimeUs, volume)
audioEncoder!!.startAudioCodec()
}
fun canWriteAudio() {
audioEncoder?.canWriteAudio()
}
fun getCurrentAudioTime() = audioEncoder?.getCurrentAudioTime()
private fun onVideoFormatChanged(format: MediaFormat) {
videoTrackIndex =
mediaMuxer!!.addTrack(format)
videoFormatInited = true
checkFormatsInited()
}
fun checkFormatsInited() {
if (allFormatsInited) {
mediaMuxer!!.start()
checkVideoOutputAvailable()
}
}
@Throws(IllegalStateException::class)
fun writeToMuxerSyncMode(currentFrame: Int = -1): Boolean {
var success = false
while (videoCodec != null && mediaMuxer != null) {
val outputBufferIndex = videoCodec!!.dequeueOutputBuffer(videoBufferInfo, 0L)
logger.info {
"writeToMuxer, outputBufferIndex = ${outputBufferIndex}, bufferFlag = ${videoBufferInfo.flags}," +
" presentationTime = ${((currentFrame * 1000000L) / frameRate)}," +
" bufferInfo.size ${videoBufferInfo.size}, bufferInfo.offset ${videoBufferInfo.offset}"
}
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
onVideoFormatChanged(videoCodec!!.outputFormat)
} else {
if (outputBufferIndex < 0) {
return success
}
success = true
val bufferInfo = videoBufferInfo
if (bufferInfo.offset >= 0 && bufferInfo.size > 0) {
val outputBuffer = videoCodec!!.getOutputBuffer(outputBufferIndex)!!
outputBuffer.position(this.videoBufferInfo.offset)
outputBuffer.limit(bufferInfo.offset + bufferInfo.size)
if (currentFrame != -1) {
if (videoBufferInfo.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
success = false
else
bufferInfo.presentationTimeUs = (currentFrame * 1000000L) / frameRate
}
mediaMuxer!!.writeSampleData(
videoTrackIndex,
outputBuffer,
this.videoBufferInfo
)
}
videoCodec!!.releaseOutputBuffer(outputBufferIndex, false)
if (bufferInfo.flags.and(MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
return success
}
}
}
return success
}
private fun onVideoWritingFinished() {
writingVideoFinished = true
videoRecordingFinished.invoke()
}
private fun checkVideoOutputAvailable() {
while (pendingVEncoderIndices.size > 0 &&
pendingVEncoderInfos.size > 0 && videoCodec != null
) {
val index = pendingVEncoderIndices.removeFirst()
val info = pendingVEncoderInfos.removeFirst()
onVideoOutputAvailable(videoCodec!!, index, info)
}
}
private fun onVideoOutputAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
if (videoCodec == null)
return
if (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
codec.releaseOutputBuffer(index, false)
onVideoWritingFinished()
} else {
val outputBuffer = codec.getOutputBuffer(index)!!
outputBuffer.position(info.offset)
outputBuffer.limit(info.offset + info.size)
info.presentationTimeUs = (currentFrame * 1000000L) / frameRate
if (info.flags != MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
currentFrame++
}
logger.info {
"videoOutputAvailable time ${info.presentationTimeUs}, flags ${info.flags}," +
" size ${info.size}, offset ${info.offset}"
}
mediaMuxer!!.writeSampleData(
videoTrackIndex,
outputBuffer, info
)
codec.releaseOutputBuffer(index, false)
}
}
fun startVideoCodec() {
videoCodec?.start()
}
fun stop() {
audioEncoder?.stop()
pendingVEncoderInfos.clear()
pendingVEncoderIndices.clear()
surface?.release()
surface = null
if (videoCodec != null) {
try {
videoCodec?.stop()
} catch (e: IllegalStateException) {
} finally {
videoCodec?.release()
videoCodec = null
}
}
if (mediaMuxer != null) {
try {
mediaMuxer?.release()
} catch (e: IllegalStateException) {
logger.error(e)
} finally {
mediaMuxer = null
}
}
}
fun sendEndOfStreamSurface() {
videoCodec?.signalEndOfInputStream()
if (!ThreadRecord.VIDEO_CODEC_ASYNC) {
onVideoWritingFinished()
}
}
companion object {
const val ENCODING_VIDEO_BITRATE = 12000000
val POSSIBLE_MIME_TYPES = arrayOf("video/avc", "video/hevc", "video/x-vnd.on2.vp8")
}
}
解决方案
推荐阅读
- microsoft-graph-api - 在 Microsoft Teams 中使用 Microsoft Graph API 列出私人团队频道成员
- python - Qt 错误:QQmlApplicationEngine 无法加载组件,[...path]\main.qml:网络错误
- sql - SQL中某年是否存在名称如何过滤?
- python - 如何在python中删除和弹出矩阵?
- amazon-web-services - 使用 GLUE ETL JOB 将外部供应商 AWS 账户中的数据从 Redshift 复制到我们的 AWS 账户
- python - 使用 df.itertuples() 和列名访问 pandas 单元格值会给出 AttributeError
- python - 它说 NameError: name 'Doctor' is not defined 当我尝试执行
- sql - 无法按一年中的一周显示带有数据透视表的票数
- sql - 添加索引是否会加快字符串通配符百分比搜索?
- android - 如何添加 Android 意图以在 Cordova/Ionic 项目中打开谷歌地图?