Java安卓应用中如何从ExoPlayer获取RTSP流原始视频帧数据?
获取ExoPlayer播放RTSP流的视频帧并发送至API
你可以通过ImageReader结合ExoPlayer的自定义Surface来获取RTSP流的原始视频帧,无需直接操作MediaCodec,实现步骤如下:
1. 修改VideoStreamer类,添加帧捕获逻辑
在原有播放逻辑基础上,加入ImageReader及后台线程处理帧数据:
import android.content.Context; import android.graphics.ImageFormat; import android.media.Image; import android.media.ImageReader; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import android.view.Surface; import androidx.annotation.OptIn; import androidx.media3.common.MediaItem; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.rtsp.RtspMediaSource; import androidx.media3.exoplayer.source.MediaSource; public class VideoStreamer { private ExoPlayer player; private final Context context; private ImageReader imageReader; private HandlerThread frameCaptureThread; private Handler frameHandler; public VideoStreamer(Context context) { this.context = context; initFrameCaptureThread(); initializePlayer(); } // 初始化后台线程,避免帧处理阻塞主线程 private void initFrameCaptureThread() { frameCaptureThread = new HandlerThread("VideoFrameCapture"); frameCaptureThread.start(); frameHandler = new Handler(frameCaptureThread.getLooper()); } @OptIn(markerClass = UnstableApi.class) private void initializePlayer() { Log.d("VideoStreamer", "initializePlayer: "); player = new ExoPlayer.Builder(context).build(); // 初始化ImageReader,参数需匹配RTSP流实际分辨率,YUV_420_888为安卓通用视频帧格式 imageReader = ImageReader.newInstance(1280, 720, ImageFormat.YUV_420_888, 2); // 设置帧可用回调,在后台线程处理 imageReader.setOnImageAvailableListener((reader) -> { Image image = reader.acquireLatestImage(); if (image == null) return; try { // 处理帧数据并发送至API processAndSendFrame(image); } finally { // 必须关闭Image,否则会引发内存泄漏 image.close(); } }, frameHandler); // 将ImageReader的Surface设置为ExoPlayer的视频输出 Surface captureSurface = imageReader.getSurface(); player.setVideoSurface(captureSurface); Uri rtspUri = Uri.parse("my_rtsp_link"); MediaSource mediaSource = new RtspMediaSource.Factory().createMediaSource(MediaItem.fromUri(rtspUri)); player.setMediaSource(mediaSource); player.prepare(); player.play(); } // 处理帧数据并发送到API private void processAndSendFrame(Image image) { // 将YUV帧转为API需要的格式(如JPEG) byte[] jpegData = convertYuvToJpeg(image); if (jpegData != null) { // 调用API发送逻辑,需在后台线程执行 sendFrameToApi(jpegData); } } // YUV转JPEG的工具方法(可根据需求替换为更高效的实现或使用开源库) private byte[] convertYuvToJpeg(Image image) { Image.Plane[] planes = image.getPlanes(); // 此处省略具体YUV转JPEG的逻辑,可通过Android Bitmap API或libyuv等库实现 return null; } // 发送帧数据到API的方法 private void sendFrameToApi(byte[] frameData) { // 实现POST请求逻辑,使用OkHttp/Retrofit等库,确保在后台线程执行 } public ExoPlayer getPlayer() { return player; } public void releasePlayer() { if (player != null) { player.release(); player = null; } // 释放ImageReader与线程资源 if (imageReader != null) { imageReader.close(); imageReader = null; } if (frameCaptureThread != null) { frameCaptureThread.quitSafely(); frameCaptureThread = null; } } }
2. 关键注意事项
- 分辨率匹配:
ImageReader.newInstance中的宽高必须与RTSP流的实际分辨率一致,否则会出现帧变形或捕获失败。 - 资源释放:务必在帧处理完成后调用
image.close(),退出时释放ImageReader和后台线程,避免内存泄漏。 - 线程隔离:帧捕获、格式转换、API发送都要放在后台线程,防止阻塞主线程影响UI流畅度。
- 格式适配:如果API需要特定格式(如JPEG、PNG),可借助Android原生API或开源库(如
libyuv)完成YUV到目标格式的转换。
3. 同时显示视频与捕获帧
若需要在界面播放视频的同时捕获帧,可通过PlayerView绑定播放器,并添加帧监听:
// 在Activity/Fragment中 playerView.setPlayer(videoStreamer.getPlayer()); videoStreamer.getPlayer().addListener(new Player.Listener() { @OptIn(markerClass = UnstableApi.class) @Override public void onVideoFrameCaptured(VideoFrameMetadata metadata, ByteBuffer frameBuffer) { // 处理原始帧数据,逻辑同上述processAndSendFrame方法 } });
注:onVideoFrameCaptured为@UnstableApi注解方法,需注意API兼容性。
内容的提问来源于stack exchange,提问作者M Khtb
相关产品推荐
相关产品推荐

