You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MVVM架构中MediaCodec及MediaCodec.Callback的合理放置位置咨询

MediaCodec 及 MediaCodec.Callback 在 MVVM 架构中的合理放置方案

核心结论

MediaCodec 及其 MediaCodec.Callback 应放在 Model 层,或封装为独立的底层服务类,ViewModel 仅负责协调 Model 与 View 的交互,不处理具体编解码逻辑。


各层职责与放置逻辑

MVVM 架构的核心是分层解耦,各层职责明确:

  • View 层:仅负责 UI 渲染、用户交互,绝不触碰底层编解码操作。MediaCodec 初始化/销毁成本高,且 View 生命周期不稳定,放在这里会导致资源浪费、状态丢失。
  • ViewModel 层:作为中间协调者,接收 View 的指令、调用 Model 方法,将处理结果转换为 View 可直接使用的数据(如 LiveData/Flow)。ViewModel 需保持轻量,不应耦合系统底层 API(MediaCodec 属于此类),否则会因 UI 销毁导致编解码中断。
  • Model 层:负责所有与数据、底层操作相关的逻辑,包括媒体编解码的初始化、配置、回调处理。这完全符合 Model 层封装业务核心逻辑的职责。

具体实现建议

  1. 封装独立的编解码管理类
    创建如 MediaCodecManager 的类(归入 Model 层),将 MediaCodec 的初始化、配置、回调、资源释放等逻辑全部封装在此类中:

    class MediaCodecManager {
        private var mediaCodec: MediaCodec? = null
        private var dataCallback: ((ByteArray) -> Unit)? = null
    
        fun setDataCallback(callback: (ByteArray) -> Unit) {
            this.dataCallback = callback
        }
    
        fun startEncoding(format: MediaFormat) {
            mediaCodec = MediaCodec.createEncoderByType(format.getString(MediaFormat.KEY_MIME)!!)
            mediaCodec?.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
            mediaCodec?.setCallback(object : MediaCodec.Callback() {
                override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
                    // 填充待编码数据至输入缓冲区
                    val buffer = codec.getInputBuffer(index)
                    buffer?.let {
                        // 写入待编码数据(如摄像头帧数据)
                        codec.queueInputBuffer(index, 0, it.capacity(), System.nanoTime(), 0)
                    }
                }
    
                override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
                    // 读取编码后的数据并传递给上层
                    val buffer = codec.getOutputBuffer(index)
                    buffer?.let {
                        val data = ByteArray(info.size)
                        it.get(data)
                        dataCallback?.invoke(data)
                    }
                    codec.releaseOutputBuffer(index, false)
                }
    
                override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
                    // 处理编解码错误,可通过回调通知上层
                }
    
                override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
                    // 处理格式变更,通知上层
                }
            })
            mediaCodec?.start()
        }
    
        fun stopEncoding() {
            mediaCodec?.stop()
            mediaCodec?.release()
            mediaCodec = null
        }
    }
    
  2. ViewModel 中协调交互
    ViewModel 依赖 MediaCodecManager,调用其方法并监听数据回调,将结果转换为 View 可观察的数据流:

    class VideoEncodeViewModel : ViewModel() {
        private val mediaCodecManager = MediaCodecManager()
        val encodedDataFlow = MutableSharedFlow<ByteArray>()
    
        init {
            mediaCodecManager.setDataCallback { data ->
                viewModelScope.launch {
                    encodedDataFlow.emit(data)
                }
            }
        }
    
        fun startEncoding() {
            val videoFormat = MediaFormat.createVideoFormat("video/avc", 1920, 1080)
            videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, 10_000_000)
            videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30)
            videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
            mediaCodecManager.startEncoding(videoFormat)
        }
    
        override fun onCleared() {
            super.onCleared()
            mediaCodecManager.stopEncoding()
        }
    }
    
  3. View 层仅做UI渲染
    在 Activity/Fragment 中观察 ViewModel 的数据流,更新 UI 或处理数据:

    class VideoEncodeFragment : Fragment() {
        private val viewModel: VideoEncodeViewModel by viewModels()
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            lifecycleScope.launch {
                viewModel.encodedDataFlow.collect { data ->
                    // 处理编码后的数据(如写入文件、推流)
                }
            }
            // 用户触发编码开始
            startBtn.setOnClickListener {
                viewModel.startEncoding()
            }
        }
    }
    

内容的提问来源于stack exchange,提问作者user12653758

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 04:05:15