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

本文涉及的产品
视觉智能开放平台,视频通用资源包5000点
视觉智能开放平台,图像通用资源包5000点
视觉智能开放平台,分割抠图1万点
简介: 在开发Android平台上的RTSP或RTMP播放器时,开发者不仅追求低延迟播放,还希望获取解码后的视频数据(如YUV或RGB格式),以便进行视觉算法分析。使用大牛直播SDK中的SmartPlayer,可在确保播放流畅的同时,通过设置外部渲染器(`SmartPlayerSetExternalRender`)来高效地回调原始视频数据。例如,对于RGBA数据,需实现`NTExternalRender`接口,并重写相关方法以处理数据和尺寸变化。同样地,对于I420(YUV)数据,也需要相应地实现接口以满足需求。这种方式使得开发者能在不影响常规播放功能的情况下,进行定制化的视频处理任务。

技术背景

我们在做Android平台RTSP、RTMP播放器的时候,遇到这样的技术诉求,开发者除了希望低延迟的播放外,还想把数据回调上来,然后做视觉算法分析。

单纯地回调数据,不难,需要保证的是,在不影响播放、录像、快照等常规功能的前提下,尽可能高效的数据回调。

技术实现

以大牛直播SDK的SmartPlayer为例,点开始播放之前,初始化参数的时候,我们设置YUV或RGB数据回调:

image.gif

设置YUV或RGB数据回调:

btnStartStopPlayback.setOnClickListener(new Button.OnClickListener() {
            // @Override
            public 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--");
                }
            }
        });

image.gif

对应的设置如下:

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

image.gif

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

/*
 * RGBA数据回调处理
 * Author: daniusdk.com
 */   
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;
        }
        @Override
        public int getNTFrameFormat() {
            Log.i(TAG, "RGBAExternalRender::getNTFrameFormat return " + NT_FRAME_FORMAT_RGBA);
            return NT_FRAME_FORMAT_RGBA;
        }
        @Override
        public 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_);
        }
        @Override
        public ByteBuffer getNTPlaneByteBuffer(int index) {
            if (index == 0)
                return rgba_buffer_;
            Log.e(TAG, "RGBAExternalRender::getNTPlaneByteBuffer index error:" + index);
            return null;
        }
        @Override
        public 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));
        }
    }

image.gif

如果是I420数据:

/*
 *YUV数据回调处理
 * Author: daniusdk.com
 */   
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;
        }
        @Override
        public int getNTFrameFormat() {
            Log.i(TAG, "I420ExternalRender::getNTFrameFormat return " + NT_FRAME_FORMAT_I420);
            return NT_FRAME_FORMAT_I420;
        }
        @Override
        public 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_);
        }
        @Override
        public 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;
            }
        }
        @Override
        public 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);
            // copy buffer
            // test
            // byte[] test_buffer = new byte[16];
            // y_buffer_.get(test_buffer);
            // Log.i(TAG, "I420ExternalRender::onNTRenderFrame y data:" + bytesToHexString(test_buffer));
            // u_buffer_.get(test_buffer);
            // Log.i(TAG, "I420ExternalRender::onNTRenderFrame u data:" + bytesToHexString(test_buffer));
            // v_buffer_.get(test_buffer);
            // Log.i(TAG, "I420ExternalRender::onNTRenderFrame v data:" + bytesToHexString(test_buffer));
        }
    }

image.gif

感兴趣的开发者,可以参考看看,如果需要测试,可以私信探讨。

相关文章
|
3月前
|
安全 数据库 Android开发
在Android开发中实现两个Intent跳转及数据交换的方法
总结上述内容,在Android开发中,Intent不仅是活动跳转的桥梁,也是两个活动之间进行数据交换的媒介。运用Intent传递数据时需注意数据类型、传输大小限制以及安全性问题的处理,以确保应用的健壯性和安全性。
243 11
|
3月前
|
监控 Android开发 数据安全/隐私保护
批量发送短信的平台,安卓群发短信工具插件脚本,批量群发短信软件【autojs版】
这个Auto.js脚本实现了完整的批量短信发送功能,包含联系人管理、短信内容编辑、发送状态监控等功能
|
5月前
|
存储 XML Java
Android 文件数据储存之内部储存 + 外部储存
简介:本文详细介绍了Android内部存储与外部存储的使用方法及核心原理。内部存储位于手机内存中,默认私有,适合存储SharedPreferences、SQLite数据库等重要数据,应用卸载后数据会被清除。外部存储包括公共文件和私有文件,支持SD卡或内部不可移除存储,需申请权限访问。文章通过代码示例展示了如何保存、读取、追加、删除文件以及将图片保存到系统相册的操作,帮助开发者理解存储机制并实现相关功能。
1272 2
|
7月前
|
存储 编解码 监控
Android平台GB28181执法记录仪技术方案与实现
本文介绍了大牛直播SDK的SmartGBD在执法记录仪场景中的应用。GB28181协议作为视频监控联网的国家标准,为设备互联互通提供规范。SmartGBD专为Android平台设计,支持音视频采集、编码与传输,具备自适应算法和多功能扩展优势。文章分析了执法记录仪的需求,如实时音视频传输、设备管理及数据安全,并详细阐述了基于SmartGBD的技术实现方案,包括环境准备、SDK集成、设备注册、音视频处理及功能扩展等步骤。最后展望了SmartGBD在未来智慧物联领域的广阔应用前景。
297 13
|
7月前
|
存储 编解码 开发工具
Android平台毫秒级低延迟HTTP-FLV直播播放器技术探究与实现
本文详细探讨了在Android平台上实现HTTP-FLV播放器的过程。首先介绍了FLV格式的基础,包括文件头和标签结构。接着分析了HTTP-FLV传输原理,通过分块传输实现流畅播放。然后重点讲解了播放器的实现步骤,涵盖网络请求、数据解析、音视频解码与渲染,以及播放控制功能的设计。文章还讨论了性能优化和网络异常处理的方法,并总结了HTTP-FLV播放器的技术价值,尤其是在特定场景下的应用意义。
273 11
|
7月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
数据采集 Android开发 索引
【Android RTMP】音频数据采集编码 ( AAC 音频格式解析 | FLV 音频数据标签解析 | AAC 音频数据标签头 | 音频解码配置信息 )(二)
【Android RTMP】音频数据采集编码 ( AAC 音频格式解析 | FLV 音频数据标签解析 | AAC 音频数据标签头 | 音频解码配置信息 )(二)
635 0
【Android RTMP】音频数据采集编码 ( AAC 音频格式解析 | FLV 音频数据标签解析 | AAC 音频数据标签头 | 音频解码配置信息 )(二)
|
数据采集 存储 传感器
【Android RTMP】音频数据采集编码 ( AAC 音频格式解析 | FLV 音频数据标签解析 | AAC 音频数据标签头 | 音频解码配置信息 )(一)
【Android RTMP】音频数据采集编码 ( AAC 音频格式解析 | FLV 音频数据标签解析 | AAC 音频数据标签头 | 音频解码配置信息 )(一)
349 0
【Android RTMP】音频数据采集编码 ( AAC 音频格式解析 | FLV 音频数据标签解析 | AAC 音频数据标签头 | 音频解码配置信息 )(一)
|
数据采集 传感器 编解码
【Android RTMP】音频数据采集编码 ( FAAC 头文件与静态库拷贝到 AS | CMakeList.txt 配置 FAAC | AudioRecord 音频采样 PCM 格式 )(一)
【Android RTMP】音频数据采集编码 ( FAAC 头文件与静态库拷贝到 AS | CMakeList.txt 配置 FAAC | AudioRecord 音频采样 PCM 格式 )(一)
331 0
【Android RTMP】音频数据采集编码 ( FAAC 头文件与静态库拷贝到 AS | CMakeList.txt 配置 FAAC | AudioRecord 音频采样 PCM 格式 )(一)
|
数据采集 Ubuntu Shell
【Android RTMP】音频数据采集编码 ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )(二)
【Android RTMP】音频数据采集编码 ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )(二)
403 0
【Android RTMP】音频数据采集编码 ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )(二)

热门文章

最新文章