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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 14:15:54