属性介绍
- videoExtractor :视频源
- audioExtractor :音频源
- mediaCodecVideo :视频解码
- mediaCodecAudio : 音频解码
- audioTrack : 音频播放
- VideoInPutThread : 解码注入
- VideoOutPutThread : 视频解码输出
- AudioInPutThread : 音频注入
- AudioOutPutThread : 音频输出播放
注册surfaceHolder回调 create中开启视频源解析播放, destory中销毁资源。
源代码
package com.cyber.app_test.ui.aty
import android.media.*
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.SurfaceHolder
import android.view.View
import com.cyber.app_test.R
import com.cyber.app_test.base.BaseActivity
import com.cyber.app_test.utils.ToastUtils
import kotlinx.android.synthetic.main.aty_playermediacodec.*
import java.io.File
import java.io.FileInputStream
import java.nio.ByteBuffer
import java.util.concurrent.ArrayBlockingQueue
/***
* 作者 : 于德海
* 时间 : 2020/11/5 17:12
* 描述 :
*/
class VideoPlayerMediaActivity : BaseActivity() {
var fileUrl = ""
var videoExtractor: MediaExtractor? = null
var audioExtractor: MediaExtractor? = null
var audioTrack: AudioTrack? = null
var mediaCodecVideo: MediaCodec? = null
var mediaCodecAudio: MediaCodec? = null
var videoInputBuffers: Array<ByteBuffer>? = null
var audioInputBuffers: Array<ByteBuffer>? = null
var audioOutBuffers: Array<ByteBuffer>? = null
var isRunning = false
var isAudioRunning = true
var decoderQueue: ArrayBlockingQueue<Int> = ArrayBlockingQueue<Int>(30)
var injectFrameQueue: ArrayBlockingQueue<Int> = ArrayBlockingQueue<Int>(30)
var decoderFrameQueue: ArrayBlockingQueue<Int> = ArrayBlockingQueue<Int>(30)
var decoderAllDelay: Int = 0
var injectfpsAll: Int = 0
var decoderfpsAll: Int = 0
val TAG = "VideoPlayer"
override fun initParam(bundle: Bundle?) {
fileUrl = bundle?.getString("fileUrl")!!
Log.i(TAG, "fileUrl = " + fileUrl)
}
override fun initLayout(): Int {
return R.layout.aty_playermediacodec
}
override fun initView() {
surface.holder.addCallback(callback)
}
var callback = object : SurfaceHolder.Callback {
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
release()
}
override fun surfaceCreated(holder: SurfaceHolder?) {
if (fileUrl.isEmpty()) {
ToastUtils.showToast("视频地址为空")
return
}
if (!File(fileUrl).exists()) {
ToastUtils.showToast("文件不存在")
return
}
// var mediaExtra = MediaMetadataRetriever()
// mediaExtra.setDataSource(fileUrl)
// Log.i(TAG,"时长="+mediaExtra.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION))
// Log.i(TAG,"帧率="+mediaExtra.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT))
// Log.i(TAG,"类型="+mediaExtra.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE))
isRunning = true
videoExtractor = MediaExtractor()
var videoInPut = FileInputStream(File(fileUrl))
try {
videoExtractor?.setDataSource(videoInPut.fd)
} catch (e: Exception) {
try {
videoExtractor?.setDataSource(fileUrl)
} catch (e: Exception) {
videoInPut.close()
ToastUtils.showToast("视频源解析失败")
return
}
}
try {
videoInPut.close()
} catch (e: Exception) {
}
audioExtractor = MediaExtractor()
var audioInPut = FileInputStream(File(fileUrl))
try {
audioExtractor?.setDataSource(audioInPut.fd)
} catch (e: Exception) {
try {
audioExtractor?.setDataSource(fileUrl)
} catch (e: Exception) {
audioInPut.close()
ToastUtils.showToast("音频源解析失败")
}
}
try {
audioInPut.close()
} catch (e: Exception) {
}
initAudio()
initVideo()
if (mediaCodecAudio != null) {
audioTrack?.play()
mediaCodecAudio?.start()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
audioInputBuffers = mediaCodecAudio?.inputBuffers
audioOutBuffers = mediaCodecAudio?.outputBuffers
}
// var audioCount = 0
// Log.i(TAG, "audioCount=$audioCount; time=" + audioExtractor?.sampleTime + "; flag = " + audioExtractor?.sampleFlags)
// audioCount++
// while (audioExtractor!!.advance()) {
// Log.i(TAG, "audioCount=$audioCount; time=" + audioExtractor?.sampleTime + "; flag = " + audioExtractor?.sampleFlags)
// audioCount++
// }
AudioInPutThread().start()
AudioOutPutThread().start()
}
if (mediaCodecVideo != null) {
mediaCodecVideo?.start()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
videoInputBuffers = mediaCodecVideo?.inputBuffers
}
// var videoCount = 0
// Log.i(TAG, "videoCount=$videoCount; time=" + videoExtractor?.sampleTime + "; flag = " + videoExtractor?.sampleFlags)
// videoCount++
// while (videoExtractor!!.advance()) {
// Log.i(TAG, "videoCount=$videoCount; time=" + videoExtractor?.sampleTime + "; flag = " + videoExtractor?.sampleFlags)
// videoCount++
// }
VideoInPutThread().start()
VideoOutPutThread().start()
}
if (mediaCodecAudio == null && mediaCodecVideo == null) {
ToastUtils.showToast("视频源读取失败")
} else {
Thread(Runnable {
while (isRunning) {
Thread.sleep(1000)
runOnUiThread {
tv_info.text = "解码时延: $decoderDelay \n 注入帧数: $injectFps \n 输出帧数: $decoderFps"
}
}
}).start()
}
}
}
fun initVideo() {
Log.i(TAG, "count = " + videoExtractor?.trackCount)
for (i in 0 until videoExtractor?.trackCount!!) {
try {
var format = videoExtractor?.getTrackFormat(i)
var mime = format?.getString(MediaFormat.KEY_MIME)
Log.i(TAG, "mime = " + mime)
if (mime!!.startsWith("video/")) {
Log.i(TAG, "video format = " + format?.toString())
videoExtractor?.selectTrack(i)
mediaCodecVideo = MediaCodec.createDecoderByType(mime)
mediaCodecVideo?.configure(format, surface.holder.surface, null, 0)
break
}
} catch (e: Exception) {
}
}
if (mediaCodecVideo == null) {
ToastUtils.showToast("未获取到视频流")
return
}
}
fun initAudio() {
Log.i(TAG, "audio count = " + audioExtractor?.trackCount)
for (i in 0 until audioExtractor?.trackCount!!) {
try {
var format = audioExtractor?.getTrackFormat(i)
var mime = format?.getString(MediaFormat.KEY_MIME)
Log.i(TAG, "mime = " + mime)
if (mime!!.startsWith("audio/")) {
audioExtractor?.selectTrack(i)
Log.i(TAG, "audio format = " + format?.toString())
val audioChannel = format!!.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
val audioSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
var minBufferSize = AudioTrack.getMinBufferSize(audioSampleRate, if (audioChannel == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT)
Log.i(TAG, "audio=" + audioChannel + ";" + audioSampleRate + ";" + minBufferSize + ";")
audioTrack = AudioTrack(AudioManager.STREAM_MUSIC, audioSampleRate
, if (audioChannel == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize * 4, AudioTrack.MODE_STREAM)
mediaCodecAudio = MediaCodec.createDecoderByType(mime)
mediaCodecAudio?.configure(format, null, null, 0)
break
}
} catch (e: Exception) {
}
}
if (mediaCodecAudio == null) {
ToastUtils.showToast("未获取到音频流")
return
}
}
inner class VideoInPutThread : Thread() {
var lastTime = System.currentTimeMillis()
var lastCount = 0
var count = 0
var startTime = 0L
var startSample = 0L
override fun run() {
super.run()
while (isRunning) {
var index = mediaCodecVideo?.dequeueInputBuffer(0L)
if (index!! >= 0) {
var input: ByteBuffer? = null
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
input = videoInputBuffers?.get(index)
} else {
input = mediaCodecVideo?.getInputBuffer(index)
}
input!!.clear()
var size = videoExtractor?.readSampleData(input, 0)
var flag = 0
if (size!! <= 0) {
flag = MediaCodec.BUFFER_FLAG_END_OF_STREAM
ToastUtils.showToast("播完了")
isRunning = false
}
if (startTime == 0L && videoExtractor!!.sampleTime > 0) {
startTime = System.currentTimeMillis()
startSample = videoExtractor?.sampleTime!! / 1000
Log.i(TAG, "视频第一帧:" + startTime + ";" + startSample)
} else if (startTime != 0L) {
var inter1 = System.currentTimeMillis() - startTime
var inter2 = videoExtractor?.sampleTime!! / 1000 - startSample
var dis = inter2 - inter1
Log.i(TAG, "视频dis:" + dis + ";count=" + count)
if (dis > 0) {
try {
sleep(dis)
} catch (e: Exception) {
}
}
}
try {
Log.i(TAG, "视频inputcount =" + count + "inputtime=" + System.currentTimeMillis() + ";pts =" + videoExtractor?.sampleTime)
count++
mediaCodecVideo?.queueInputBuffer(index, 0, size!!, System.currentTimeMillis(), flag)
videoExtractor?.advance()
} catch (e: Exception) {
}
if (System.currentTimeMillis() - lastTime > 1000) {
calcinjectFps(count - lastCount)
lastCount = count
lastTime = System.currentTimeMillis()
}
} else {
try {
sleep(1)
} catch (e: Exception) {
}
}
}
}
}
inner class VideoOutPutThread : Thread() {
var lastTime = System.currentTimeMillis()
var lastCount = 0
var count = 0
override fun run() {
super.run()
var bufferInfo = MediaCodec.BufferInfo()
while (isRunning) {
try {
var index = mediaCodecVideo?.dequeueOutputBuffer(bufferInfo, 0L)
if (index!! >= 0) {
Log.i(TAG, "outcount:" + count + ";outTime =" + System.currentTimeMillis() + ";input=" + bufferInfo.presentationTimeUs)
count++
Log.i(TAG, "视频解码时延:" + (System.currentTimeMillis() - bufferInfo.presentationTimeUs))
calcDecoder((System.currentTimeMillis() - bufferInfo.presentationTimeUs).toInt())
mediaCodecVideo?.releaseOutputBuffer(index, true)
if (System.currentTimeMillis() - lastTime > 1000) {
calcdecoderFps(count - lastCount)
lastTime = System.currentTimeMillis()
lastCount = count
}
} else {
try {
sleep(1)
} catch (e: Exception) {
}
}
} catch (e: Exception) {
}
}
}
}
inner class AudioInPutThread : Thread() {
var count = 0
var startTime = 0L
var startSample = 0L
override fun run() {
super.run()
while (isRunning && isAudioRunning) {
try {
var index = mediaCodecAudio?.dequeueInputBuffer(1000L)
if (index!! >= 0) {
var input: ByteBuffer? = null
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
input = audioInputBuffers?.get(index)
} else {
input = mediaCodecAudio?.getInputBuffer(index)
}
input!!.clear()
var size = audioExtractor?.readSampleData(input, 0)
var flag = 0
if (size!! <= 0) {
flag = MediaCodec.BUFFER_FLAG_END_OF_STREAM
ToastUtils.showToast("音频播完了")
isAudioRunning = false
}
if (startTime == 0L && audioExtractor!!.sampleTime > 0) {
startTime = System.currentTimeMillis()
startSample = audioExtractor?.sampleTime!! / 1000
Log.i(TAG, "音频第一帧:" + startTime + ";" + startSample)
} else if (startTime != 0L) {
var inter1 = System.currentTimeMillis() - startTime
var inter2 = audioExtractor?.sampleTime!! / 1000 - startSample
var dis = inter2 - inter1
Log.i(TAG, "音频dis:" + dis + ";count=" + count)
if (dis > 0) {
try {
sleep(dis)
} catch (e: Exception) {
}
}
}
try {
Log.i(TAG, "音频inputcount =" + count + "inputtime=" + System.currentTimeMillis() + ";pts=" + audioExtractor?.sampleTime)
count++
mediaCodecAudio?.queueInputBuffer(index, 0, size!!, System.currentTimeMillis(), flag)
audioExtractor?.advance()
} catch (e: Exception) {
}
} else {
sleep(1)
}
} catch (e: Exception) {
}
}
}
}
inner class AudioOutPutThread : Thread() {
var lastTime = System.currentTimeMillis()
var lastCount = 0
var count = 0
override fun run() {
super.run()
var bufferInfo = MediaCodec.BufferInfo()
while (isRunning) {
try {
var index = mediaCodecAudio?.dequeueOutputBuffer(bufferInfo, 1000L)
if (index!! >= 0) {
if (audioTrack == null || mediaCodecAudio == null)
return
Log.i(TAG, "音频outcount:" + count + ";outTime =" + System.currentTimeMillis() + ";input=" + bufferInfo.presentationTimeUs)
count++
Log.i(TAG, "音频解码时延:" + (System.currentTimeMillis() - bufferInfo.presentationTimeUs))
var outBuffer: ByteBuffer? = null
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
outBuffer = audioOutBuffers?.get(index)
audioTrack?.write(outBuffer?.array(), 0, bufferInfo.size)
} else {
outBuffer = mediaCodecAudio?.getOutputBuffer(index)
audioTrack?.write(outBuffer, bufferInfo.size, AudioTrack.WRITE_BLOCKING)
}
mediaCodecAudio?.releaseOutputBuffer(index, false)
if (System.currentTimeMillis() - lastTime > 1000) {
Log.i(TAG, "音频每秒帧数:" + (count - lastCount));
lastTime = System.currentTimeMillis()
lastCount = count
}
} else {
try {
sleep(1)
} catch (e: Exception) {
}
}
} catch (e: Exception) {
}
}
}
}
var decoderDelay = 0
fun calcDecoder(delay: Int) {
if (delay > Int.MAX_VALUE / 2) {
return
}
if (decoderQueue.size >= 30) {
var delay_first = decoderQueue.remove()
decoderAllDelay -= delay_first
}
decoderQueue.add(delay)
decoderAllDelay += delay
decoderDelay = decoderAllDelay / decoderQueue.size
}
var injectFps = 0
fun calcinjectFps(fps: Int) {
if (fps > Int.MAX_VALUE / 2) {
return
}
if (injectFrameQueue.size >= 30) {
var delay_first = injectFrameQueue.remove()
injectfpsAll -= delay_first
}
injectFrameQueue.add(fps)
injectfpsAll += fps
injectFps = injectfpsAll / injectFrameQueue.size
}
var decoderFps = 0
fun calcdecoderFps(fps: Int) {
if (fps > Int.MAX_VALUE / 2) {
return
}
if (decoderFrameQueue.size >= 30) {
var delay_first = decoderFrameQueue.remove()
decoderfpsAll -= delay_first
}
decoderFrameQueue.add(fps)
decoderfpsAll += fps
decoderFps = decoderfpsAll / decoderFrameQueue.size
}
fun release(){
isRunning = false
if (mediaCodecAudio != null) {
mediaCodecAudio?.stop()
mediaCodecAudio?.release()
mediaCodecAudio = null
}
if (mediaCodecVideo != null) {
mediaCodecVideo?.stop()
mediaCodecVideo?.release()
mediaCodecVideo = null
}
if (audioTrack != null) {
audioTrack?.stop()
audioTrack?.release()
audioTrack = null
}
if (videoExtractor != null) {
videoExtractor?.release()
videoExtractor = null
}
if (audioExtractor != null) {
audioExtractor?.release()
audioExtractor = null
}
}
override fun onStop() {
release()
super.onStop()
}
override fun onBackPressed() {
release()
super.onBackPressed()
}
override fun initData() {
}
override fun initListener() {
}
override fun onClick(v: View?) {
}
}