Android平台RTMP|RTSP播放器如何回调YUV或RGB数据?

devtools/2024/10/21 14:32:55/

技术选型

我们知道,Android平台一般RTMP|RTSP播放器通常不直接提供回调YUV或RGB数据的功能。如果播放端有视觉分析或类似的需求,需要播放端,能支持YUV或ARG的数据回调,一般来说,可参考的方法如下:

1. 使用FFmpeg和JNI

FFmpeg是一个强大的多媒体处理库,它支持解码视频并提取帧数据。你可以通过JNI在Android的Java层调用C/C++层的FFmpeg库来解码RTSP流,并获取YUV或RGB数据。

步骤

  • 将FFmpeg库集成到你的Android项目中。
  • 使用FFmpeg的API来设置RTSP流的解码器。
  • 解码视频帧,并将YUV或RGB数据从解码器传输到Java层。

2. 使用OpenGL ES

如果你使用的是OpenGL ES进行视频渲染,你可以在着色器(Shader)中处理视频帧的YUV数据,并将其转换为RGB格式(如果需要)。然而,这种方法并不会直接回调YUV或RGB数据到Java层,而是允许你在GPU级别上操作这些数据。

3. 使用MediaCodec和ImageReader

从Android 5.0(API 级别 21)开始,MediaCodec支持与ImageReader一起使用,以捕获解码后的视频帧作为Image对象。这些Image对象可以直接访问YUV或RGB数据(取决于配置)。

步骤

  • 配置MediaCodec以使用ImageReader作为输出。
  • 解码RTSP流并捕获解码后的帧。
  • ImageReaderImage对象中读取YUV或RGB数据。

4. 使用第三方RTMP|RTSP播放器直接回调数据

大牛直播SDK的RTMP|RTSP播放模块为例,我们是可以直接设置YUV或RGB数据回调,并提供相关调用示例:

btnStartStopPlayback.setOnClickListener(new Button.OnClickListener() {// @Overridepublic void onClick(View v) {if (isPlaying) {Log.i(TAG, "Stop playback stream++");int iRet = libPlayer.SmartPlayerStopPlay(playerHandle);if (iRet != 0) {Log.e(TAG, "Call SmartPlayerStopPlay failed..");return;}btnHardwareDecoder.setEnabled(true);btnLowLatency.setEnabled(true);if (!isRecording) {btnPopInputUrl.setEnabled(true);btnPopInputKey.setEnabled(true);btnSetPlayBuffer.setEnabled(true);btnFastStartup.setEnabled(true);btnRecoderMgr.setEnabled(true);libPlayer.SmartPlayerClose(playerHandle);playerHandle = 0;}isPlaying = false;btnStartStopPlayback.setText("开始播放 ");if (is_enable_hardware_render_mode && sSurfaceView != null) {sSurfaceView.setVisibility(View.GONE);sSurfaceView.setVisibility(View.VISIBLE);}Log.i(TAG, "Stop playback stream--");} else {Log.i(TAG, "Start playback stream++");if (!isRecording) {InitAndSetConfig();}// 如果第二个参数设置为null,则播放纯音频libPlayer.SmartPlayerSetSurface(playerHandle, sSurfaceView);libPlayer.SmartPlayerSetRenderScaleMode(playerHandle, 1);//int render_format = 1;//libPlayer.SmartPlayerSetSurfaceRenderFormat(playerHandle, render_format);//int is_enable_anti_alias = 1;//libPlayer.SmartPlayerSetSurfaceAntiAlias(playerHandle, is_enable_anti_alias);if (isHardwareDecoder && is_enable_hardware_render_mode) {libPlayer.SmartPlayerSetHWRenderMode(playerHandle, 1);}// External Render test//libPlayer.SmartPlayerSetExternalRender(playerHandle, new RGBAExternalRender(imageSavePath));libPlayer.SmartPlayerSetExternalRender(playerHandle, new I420ExternalRender(imageSavePath));libPlayer.SmartPlayerSetUserDataCallback(playerHandle, new UserDataCallback());//libPlayer.SmartPlayerSetSEIDataCallback(playerHandle, new SEIDataCallback());libPlayer.SmartPlayerSetAudioOutputType(playerHandle, 1);if (isMute) {libPlayer.SmartPlayerSetMute(playerHandle, isMute ? 1: 0);}if (isHardwareDecoder) {int isSupportHevcHwDecoder = libPlayer.SetSmartPlayerVideoHevcHWDecoder(playerHandle, 1);int isSupportH264HwDecoder = libPlayer.SetSmartPlayerVideoHWDecoder(playerHandle, 1);Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder);}libPlayer.SmartPlayerSetLowLatencyMode(playerHandle, isLowLatency ? 1: 0);libPlayer.SmartPlayerSetFlipVertical(playerHandle, is_flip_vertical ? 1 : 0);libPlayer.SmartPlayerSetFlipHorizontal(playerHandle, is_flip_horizontal ? 1 : 0);libPlayer.SmartPlayerSetRotation(playerHandle, rotate_degrees);libPlayer.SmartPlayerSetAudioVolume(playerHandle, curAudioVolume);int iPlaybackRet = libPlayer.SmartPlayerStartPlay(playerHandle);if (iPlaybackRet != 0) {Log.e(TAG, "Call SmartPlayerStartPlay failed..");return;}btnStartStopPlayback.setText("停止播放 ");btnPopInputUrl.setEnabled(false);btnPopInputKey.setEnabled(false);btnHardwareDecoder.setEnabled(false);btnSetPlayBuffer.setEnabled(false);btnLowLatency.setEnabled(false);btnFastStartup.setEnabled(false);btnRecoderMgr.setEnabled(false);isPlaying = true;Log.i(TAG, "Start playback stream--");}}
});

对应的设置如下:

// External Render test
libPlayer.SmartPlayerSetExternalRender(playerHandle, new RGBAExternalRender(imageSavePath));
libPlayer.SmartPlayerSetExternalRender(playerHandle, new I420ExternalRender(imageSavePath));

如果是RGBA数据,处理如下:

/** RGBA数据回调处理* Author: daniusdk.com* WeChat: xinsheng120*/   
private static class RGBAExternalRender implements NTExternalRender {// public static final int NT_FRAME_FORMAT_RGBA = 1;// public static final int NT_FRAME_FORMAT_ABGR = 2;// public static final int NT_FRAME_FORMAT_I420 = 3;private final String image_path_;private long last_save_image_time_ms_;private int width_;private int height_;private int row_bytes_;private ByteBuffer rgba_buffer_;public RGBAExternalRender(String image_path) {this.image_path_ = image_path;}@Overridepublic int getNTFrameFormat() {Log.i(TAG, "RGBAExternalRender::getNTFrameFormat return " + NT_FRAME_FORMAT_RGBA);return NT_FRAME_FORMAT_RGBA;}@Overridepublic void onNTFrameSizeChanged(int width, int height) {width_ = width;height_ = height;row_bytes_ = width_ * 4;rgba_buffer_ = ByteBuffer.allocateDirect(row_bytes_ * height_);Log.i(TAG, "RGBAExternalRender::onNTFrameSizeChanged width_:" + width_ + " height_:" + height_);}@Overridepublic ByteBuffer getNTPlaneByteBuffer(int index) {if (index == 0)return rgba_buffer_;Log.e(TAG, "RGBAExternalRender::getNTPlaneByteBuffer index error:" + index);return null;}@Overridepublic int getNTPlanePerRowBytes(int index) {if (index == 0)return row_bytes_;Log.e(TAG, "RGBAExternalRender::getNTPlanePerRowBytes index error:" + index);return 0;}public void onNTRenderFrame(int width, int height, long timestamp) {if (rgba_buffer_ == null)return;rgba_buffer_.rewind();// copy buffer// test// byte[] test_buffer = new byte[16];// rgba_buffer_.get(test_buffer);Log.i(TAG, "RGBAExternalRender:onNTRenderFrame " + width + "*" + height + ", t:" + timestamp);// Log.i(TAG, "RGBAExternalRender:onNTRenderFrame rgba:" +// bytesToHexString(test_buffer));}}

如果是I420数据:

/**YUV数据回调处理* Author: daniusdk.com* WeChat: xinsheng120*/   
private static class I420ExternalRender implements NTExternalRender {// public static final int NT_FRAME_FORMAT_RGBA = 1;// public static final int NT_FRAME_FORMAT_ABGR = 2;// public static final int NT_FRAME_FORMAT_I420 = 3;private final String image_path_;private long last_save_image_time_ms_;private int width_;private int height_;private int y_row_bytes_;private int u_row_bytes_;private int v_row_bytes_;private ByteBuffer y_buffer_;private ByteBuffer u_buffer_;private ByteBuffer v_buffer_;public I420ExternalRender(String image_path) {this.image_path_ = image_path;}@Overridepublic int getNTFrameFormat() {Log.i(TAG, "I420ExternalRender::getNTFrameFormat return " + NT_FRAME_FORMAT_I420);return NT_FRAME_FORMAT_I420;}@Overridepublic void onNTFrameSizeChanged(int width, int height) {width_ = width;height_ = height;y_row_bytes_ = width;u_row_bytes_ = (width+1)/2;v_row_bytes_ = (width+1)/2;y_buffer_ = ByteBuffer.allocateDirect(y_row_bytes_*height_);u_buffer_ = ByteBuffer.allocateDirect(u_row_bytes_*((height_ + 1) / 2));v_buffer_ = ByteBuffer.allocateDirect(v_row_bytes_*((height_ + 1) / 2));Log.i(TAG, "I420ExternalRender::onNTFrameSizeChanged width_="+ width_ + " height_=" + height_ + " y_row_bytes_="+ y_row_bytes_ + " u_row_bytes_=" + u_row_bytes_+ " v_row_bytes_=" + v_row_bytes_);}@Overridepublic ByteBuffer getNTPlaneByteBuffer(int index) {switch (index) {case 0:return y_buffer_;case 1:return u_buffer_;case 2:return v_buffer_;default:Log.e(TAG, "I420ExternalRender::getNTPlaneByteBuffer index error:" + index);return null;}}@Overridepublic int getNTPlanePerRowBytes(int index) {switch (index) {case 0:return y_row_bytes_;case 1:return u_row_bytes_;case 2:return v_row_bytes_;default:Log.e(TAG, "I420ExternalRender::getNTPlanePerRowBytes index error:" + index);return 0;}}public void onNTRenderFrame(int width, int height, long timestamp) {if (null == y_buffer_ || null == u_buffer_ || null == v_buffer_)return;y_buffer_.rewind();u_buffer_.rewind();v_buffer_.rewind();Log.i(TAG, "I420ExternalRender::onNTRenderFrame " + width + "*" + height + ", t:" + timestamp);}}

总结

无论使用哪种方法,处理视频帧数据都可能是计算密集型的,特别是在高清视频或高帧率视频的情况下。确保你的应用能够处理这些性能要求,并考虑在后台线程中执行解码和数据处理操作。确保回调数据,尽可能小的占用资源。以上抛砖引玉,感兴趣的开发者,可以单独跟我沟通讨论。


http://www.ppmy.cn/devtools/109799.html

相关文章

vue组件通信

父传子 父组件通过自定义属性向子传值,子通过props接收 示例一: 示例二: 注意: 子传父 props完整写法:

Mac电脑IDEA2024安装后打不开问题解决

Mac电脑IDEA2024安装后打不开问题解决 由于电脑系统升级,导致我用的2019版本的IDEA一打开就卡,机缘巧合拥有了一个正版的IDEA账号,下载2024版本的IDEA,打开报错。 由于电脑系统升级,导致我用的2019版本的IDEA一打开就卡…

python安装以及访问openAI API

安装python 我是python小白,所以需要一步一步来,先安装。 一口吃不成胖子,记住。 从官网下载python,目前最新版本是3.12,但是据说稳定版3.11更好一点,所以,下载3.11,注意不要下载…

如何修复软件中的BUG

笔者上一篇博文《如何开发出一款优秀的软件》主要讲了如何开发一款优秀的软件及相应的必要条件。但对一个已上线,已经成型的产品,该如何解决存在的bug呢?这是本文要阐述的内容。 在这里,首先说一下bug的种类及bug严重程度分类&…

Matlab实现MPC算法

模型预测控制(Model Predictive Control, MPC)是一种先进的过程控制方法,它使用模型来预测系统未来的行为,并基于这些预测来优化控制动作。在Matlab中实现MPC算法通常涉及到使用Matlab的MPC Toolbox,我们可以考虑一个线…

数字资产管理工具Adobe Bridge (BR) 2024WIN/MAC下载及使用技巧

目录 一、Adobe Bridge 软件简介 1.1 软件概述 1.2 主要功能 1.3 用户体验 二、下载 2.1 下载 2.2 注意事项 2.3 安装包信息 三、系统要求 3.1 Windows 系统要求 3.2 macOS 系统要求 四、使用技巧 4.1 文件筛选和搜索 4.2 批量重命名和文件处理 一、Adobe Bridge…

推荐一个Python流式JSON处理模块:streaming-json-py

每天,我们的设备、应用程序和服务都在生成大量的数据流,这些数据往往大多是以JSON格式存在的。 如何高效地解析和处理这些JSON数据流是一大挑战。今天,我要为大家介绍一个能极大简化这一过程的利器:streaming-json-py streaming…

快速上手基于 BaGet 的脚本自动化构建 .net 应用打包

脚本自动化打包 .net 应用 1. BaGet 介绍1.2 主要特点1.3 使用说明1.3.1 安装与部署1.3.1.1 Docker 部署1.3.1.2 手动部署1.3.1.3 配置 2. 应用举例2.1 推送包2.2 下载包 3. 配置信息3.1 基本配置3.2 其他配置选项 4. 脚本编写4.1 编写 PowerShell 脚本4.2 编写 Bash 脚本4.3 运…