Kotlin实现,MediaExtractor+MediaCodec+AudioTrack音视频播放

属性介绍

  • 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?) {
    }
}

猜你喜欢

转载自blog.csdn.net/a940659387/article/details/109592739