H264解码

class MainActivity : AppCompatActivity() {
    // 定义一个阻塞队列,用于存储视频帧数据
    private val videoQueue = LinkedBlockingQueue<ByteArray>()
    // 声明一个 SurfaceView,用于显示视频
    private lateinit var surfaceView: SurfaceView
    // 声明一个 Surface 对象,用于与 MediaCodec 交互
    private var surface: Surface? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        surfaceView = findViewById(R.id.surface)
        surfaceView.holder.addCallback(object: SurfaceHolder.Callback {
            // 当 Surface 被创建时调用
            override fun surfaceCreated(holder: SurfaceHolder) {
                // 将 holder 的 surface 赋值给 surface 变量
                surface = holder.surface
                // 创建一个线程来读取视频文件数据
                thread {
                    //使用 assets.open 方法打开应用资源中的 “video.h264” 文件,并创建一个 InputStream 对象来读取文件内容。
                    val inputStream = assets.open("video.h264")
                    //创建一个大小为 1024 字节的字节数组 videoData,用于存储从输入流中读取的数据块
                    val videoData = ByteArray(1024)
                    //声明一个变量 remainData 并初始化为一个空的字节数组。这个变量用于存储上一次调用 pullFrame 函数后剩余的不完整帧数据。
                    var remainData = byteArrayOf()
                    // 循环读取视频数据直到文件结束
                    while (inputStream.read(videoData) != -1) {
                        // 将剩余数据和当前读取的数据合并
                        val videoData = byteArrayOf(*remainData, *videoData)
                        // 调用 pullFrame 函数,传入合并后的数据块 videoData,以提取视频帧。pullFrame 函数返回剩余的不完整帧数据,这些数据被赋值给 remainData 变量,以供下一次循环使用
                        remainData = pullFrame(videoData)
                    }
                }

                // 创建另一个线程来处理解码和显示视频帧
                thread {
                    // 启动解码器
                    mediaCodec.start()
                    // 无限循环处理输入和输出缓冲区
                    mediaCodec.start()
                    while (true) {
                        // 调用 dequeueInputBuffer 方法来获取输入缓冲区的索引。参数 10000 是超时时间(以微秒为单位),表示如果当前没有可用的输入缓冲区,该方法将最多等待 10000 微秒。
                        val inputBufferIndex = mediaCodec.dequeueInputBuffer(10000)
                        //检查 dequeueInputBuffer 方法返回的索引是否有效(非负数)。如果有效,则表示获得了可用的输入缓冲区
                        if (inputBufferIndex >= 0) {
                            // 获取输入缓冲区
                            val buffer = mediaCodec.getInputBuffer(inputBufferIndex)
                            //清空缓冲区,准备填充新的数据
                            buffer?.clear()
                            // 从 videoQueue 队列中取出一个视频帧数据。take 方法是一个阻塞操作,如果队列为空,它将等待直到有数据可用
                            val data = videoQueue.take()
                            // 将数据放入缓冲区
                            buffer?.put(data, 0, data.size)
                            // 将填充好的输入缓冲区提交给 MediaCodec 进行解码。参数分别是缓冲区索引、数据偏移量、数据大小、时间戳和标志位
                            mediaCodec.queueInputBuffer(inputBufferIndex, 0, data.size, 0, 0)
                        } else {
                            continue
                        }
                        // 获取解码后的输出缓冲区的索引。MediaCodec.BufferInfo() 是用于存储输出缓冲区信息的对象
                        val outputBufferIndex = mediaCodec.dequeueOutputBuffer(MediaCodec.BufferInfo(), 10000)
                        if (outputBufferIndex >= 0) {
                            // 等待一段时间,模拟帧率
                            Thread.sleep(10)
                            // 释放输出缓冲区并渲染到 Surface
                            mediaCodec.releaseOutputBuffer(outputBufferIndex, true)
                        }
                    }
                }
            }

            override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
            }

            override fun surfaceDestroyed(p0: SurfaceHolder) {
            }

        })
    }

    // 初始化 MediaCodec
    private fun initMediaCodec(): MediaCodec {
        // 创建一个解码器
        val mediaCodec = MediaCodec.createDecoderByType("video/avc")
        // 创建视频格式
        val mediaFormat = MediaFormat.createVideoFormat("video/avc", 1920, 1080)
        // 配置解码器
        mediaCodec.configure(mediaFormat, surface, null, 0)
        // 返回解码器
        return mediaCodec
    }

    //这个变量用于跟踪数据中当前帧的开始索引
    private var beginIndex = 0;
    //这个函数用于从输入数据中提取视频帧
    private fun pullFrame(data: ByteArray): ByteArray {
        beginIndex = 0
        //使用 forEachIndexed 方法遍历 data 数组。对于数组中的每个字节,都会执行闭包中的代码,同时传递当前字节的位置索引 index 和字节值 byte
        data.forEachIndexed { index, byte ->
            /** 这是一个条件判断,检查当前索引是否满足以下条件:
              *当前索引大于 0(确保不是数组的第一个元素)。
              *当前索引小于 data.size - 3(确保至少有四个字节可以检查)。
              *当前字节和接下来的三个字节是否分别是 0x00 0x00 0x00 0x01,这是 H.264 视频帧分隔符。
            **/
            if (index > 0 && index <  data.size - 3 && data[index].toInt() == 0 && data[index + 1].toInt() == 0 && data[index + 2].toInt() == 0 && data[index + 3].toInt() == 1) {
                //如果找到了帧分隔符,使用 copyOfRange 方法从 beginIndex 到 index(不包括)提取帧数据,并将其赋值给 frameData 变量。
                val frameData = data.copyOfRange(beginIndex, index)
                videoQueue.put(frameData)
                beginIndex = index
            }

            //如果当前索引是 data 数组的最后一个索引,并且 beginIndex 小于 index,这意味着数组末尾有未处理的数据。
            if (index == data.size - 1 && beginIndex < index) {
                //从 beginIndex 到 data.size(不包括)提取剩余的数据,并将其赋值给 outData 变量
                val outData = data.copyOfRange(beginIndex, data.size)
                return outData
            }
        }
        return byteArrayOf()
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值