Android:如何在视频播放显示时实时获取其帧?
实时获取本地MP4视频播放帧的解决方案
需求背景
需要实现类似ProcessCameraProvider搭配ImageAnalyzer实时获取相机帧的功能——针对本地图库中的MP4视频,在播放过程中实时获取帧并转为Bitmap进行处理。现有MediaMetadataRetriever或MediaExtractor无法满足实时播放时逐帧获取的需求,需可行方案。
可行方案:基于ExoPlayer的视频帧实时监听
ExoPlayer是Android官方推荐的多媒体播放库,提供了VideoFrameMetadataListener接口,可在视频播放过程中实时回调每一帧的元数据与帧数据,完全匹配实时获取并处理帧的需求。
1. 添加ExoPlayer依赖
在Module级别的build.gradle中引入核心库与UI库:
implementation "com.google.android.exoplayer:exoplayer-core:2.19.1" implementation "com.google.android.exoplayer:exoplayer-ui:2.19.1"
2. 初始化播放器并设置帧监听
以下是Kotlin实现示例,包含实时帧获取、转Bitmap及处理逻辑:
import android.graphics.Bitmap import android.graphics.Surface import android.graphics.SurfaceTexture import android.os.Bundle import android.view.TextureView import androidx.appcompat.app.AppCompatActivity import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.video.VideoFrameMetadataListener class VideoFrameCaptureActivity : AppCompatActivity(), TextureView.SurfaceTextureListener { private lateinit var exoPlayer: ExoPlayer private lateinit var textureView: TextureView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_video_frame_capture) textureView = findViewById(R.id.texture_view) textureView.surfaceTextureListener = this } override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture, width: Int, height: Int) { // 初始化ExoPlayer exoPlayer = ExoPlayer.Builder(this).build() exoPlayer.setVideoSurface(Surface(surfaceTexture)) // 注册视频帧监听,实时回调每一帧 exoPlayer.addVideoFrameMetadataListener { _, _, _, surfaceTexture -> surfaceTexture?.let { st -> st.updateTexImage() // 将SurfaceTexture中的帧转为Bitmap val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) val canvas = android.graphics.Canvas(bitmap) canvas.drawBitmap(bitmap, 0f, 0f, android.graphics.Paint().apply { isAntiAlias = true }) // 这里执行你的帧处理逻辑,比如图像识别、滤镜等 processCapturedFrame(bitmap) // 及时回收Bitmap,避免内存泄漏 bitmap.recycle() } } // 设置要播放的本地MP4 Uri(替换为你的视频路径) val mediaItem = MediaItem.fromUri("content://media/external/video/media/123") exoPlayer.setMediaItem(mediaItem) exoPlayer.prepare() exoPlayer.play() } private fun processCapturedFrame(bitmap: Bitmap) { // 自定义帧处理逻辑 } override fun onSurfaceTextureSizeChanged(surfaceTexture: SurfaceTexture, width: Int, height: Int) {} override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean { exoPlayer.release() return true } override fun onSurfaceTextureUpdated(surfaceTexture: SurfaceTexture) {} }
关键说明
- 无画面播放场景:若不需要显示视频画面,可创建一个不可见的
Surface替代TextureView,仅用于接收帧数据。 - 线程注意事项:帧回调运行在ExoPlayer内部线程,处理耗时操作时需切换到后台线程,避免阻塞播放流程。
- 与现有方案对比:
MediaMetadataRetriever适合获取指定时间点单帧,MediaExtractor适合离线批量提取帧,但均无法实现播放过程中实时逐帧回调,ExoPlayer的监听正好解决了这个痛点。
内容的提问来源于stack exchange,提问作者leonel Z
相关产品推荐
相关产品推荐

