通过 android api MediaMuxer&MediaExtractor 实现视频转格式
概述 最近app内需要上传视频,为了保证视频播放在各端的统一,需要将视频封装格式转换为mp4
本来上ffmpeg非常简单,但是ffmpeg对包体的增加会比较大,而且需求功能很简单 就考虑用Android的media相关api来实现.
流程上来说就是 未知视频格式->解码->编码->mp4
经测试 非h265/h264 编码+mp4封装 ios支持 所以为了速度就不转码视频编码格式了
老视频中每读到一帧的数据 直接编码进新视频中就好了
代码 MediaExtractor 一次只能解析一个轨道,所以需要创建两个,一个负责视频解码,一个负责音频解码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 import android.media.MediaCodecimport android.media.MediaExtractorimport android.media.MediaFormatimport android.media.MediaMuxerimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.withContextimport shop.memall.newapp.base.CMimport java.io.Fileimport java.lang.Exceptionimport java.lang.RuntimeExceptionimport java.nio.ByteBufferimport java.util.concurrent.atomic.AtomicInteger@Suppress("BlockingMethodInNonBlockingContext" ) object VideoTranscodingUtils { @JvmStatic suspend fun transcoding2Mp4 (origin: String ) : String? { return withContext(Dispatchers.IO) { val dir = CM.mainApplication().getExternalFilesDir("transcodingVideos" ) ?: File(CM.mainApplication().cacheDir, "transcodingVideos" ) try { dir.mkdirs() } catch (ignore: Exception) { } val output = File(dir, "${System.currentTimeMillis()} .mp4" ) var mediaMuxer: MediaMuxer? = null try { val videoExtractor = MediaExtractor() val audioExtractor = MediaExtractor() mediaMuxer = MediaMuxer(output.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) videoExtractor.setDataSource(origin) audioExtractor.setDataSource(origin) val numTracks: Int = videoExtractor.trackCount var muxerTrackNum = 0 for (idx in 0 until numTracks) { val trackFormat = videoExtractor.getTrackFormat(idx) val mime = trackFormat.getString(MediaFormat.KEY_MIME) ?: "" if (mime.contains("video" )) { videoExtractor.selectTrack(idx) mediaMuxer.addTrack(trackFormat) muxerTrackNum++ } else if (mime.contains("audio" )) { audioExtractor.selectTrack(idx) mediaMuxer.addTrack(trackFormat) muxerTrackNum++ } if (muxerTrackNum >= 2 ) break LG.fi("convert video track idx:$idx mime:$mime " ) } val atomicInteger = AtomicInteger(0 ) mediaMuxer.start() ThreadUtils.start(DecodeThread(videoExtractor, mediaMuxer, 0 , atomicInteger)) ThreadUtils.start(DecodeThread(audioExtractor, mediaMuxer, 1 , atomicInteger)) while (atomicInteger.get () < 2 ) { Thread.sleep(0 ) } if (atomicInteger.get () > 2 ) throw RuntimeException("解码异常" ); output.absolutePath } catch (e: Throwable) { e.printStackTrace() output.delete() return @withContext null } finally { LG.fi("convert transcoding compile" ) mediaMuxer?.stop() mediaMuxer?.release() } } } private class DecodeThread ( val extractor: MediaExtractor, val mediaMuxer: MediaMuxer, val model: Int , val atomicInteger: AtomicInteger ) : Runnable { override fun run () { try { val buffer = ByteBuffer.allocate(500 * 1024 ) val bufferInfo = MediaCodec.BufferInfo() var firstTime = 0L var size: Int while (extractor.readSampleData(buffer, 0 ).also { size = it } > 0 ) { val rackIndex = extractor.sampleTrackIndex if (firstTime == 0L ) firstTime = extractor.sampleTime bufferInfo.presentationTimeUs = extractor.sampleTime - firstTime bufferInfo.flags = extractor.sampleFlags bufferInfo.size = size mediaMuxer.writeSampleData(rackIndex, buffer, bufferInfo) extractor.advance() } LG.fi("convert write success mode:$model " ) atomicInteger.addAndGet(1 ) } catch (e: Throwable) { e.printStackTrace() LG.fi("convert write has an exception mode:$model " ) atomicInteger.addAndGet(4 ) } finally { extractor.release() } } } }