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

本文涉及的产品
视觉智能开放平台,视频资源包5000点
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,图像资源包5000点
简介: 在开发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

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

相关文章
|
1月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
109 1
|
2月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
21天前
|
Java 程序员 开发工具
Android|修复阿里云播放器下载不回调的问题
虽然 GC 带来了很多便利,但在实际编码时,我们也需要注意对象的生命周期管理,该存活的存活,该释放的释放,避免因为 GC 导致的问题。
28 2
|
29天前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
81 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
1月前
|
存储 大数据 数据库
Android经典面试题之Intent传递数据大小为什么限制是1M?
在 Android 中,使用 Intent 传递数据时存在约 1MB 的大小限制,这是由于 Binder 机制的事务缓冲区限制、Intent 的设计初衷以及内存消耗和性能问题所致。推荐使用文件存储、SharedPreferences、数据库存储或 ContentProvider 等方式传递大数据。
59 0
|
2月前
|
开发工具 Android开发 iOS开发
安卓与iOS开发环境对比:选择适合你的平台
【9月更文挑战第26天】在移动应用开发的广阔天地中,安卓和iOS是两大巨头。它们各自拥有独特的优势和挑战,影响着开发者的选择和决策。本文将深入探讨这两个平台的开发环境,帮助你理解它们的核心差异,并指导你根据个人或项目需求做出明智的选择。无论你是初学者还是资深开发者,了解这些平台的异同都至关重要。让我们一起探索,找到最适合你的那片开发天地。
|
Android开发
android 回调函数一:基本概念
1、概念 客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数。 一般说来,C不会自己调用B,C提供B的目的就是让S来调用它,而且是C不得不提供。
1016 0
|
3天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
5天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
7天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
【10月更文挑战第35天】在数字化时代,安卓应用的开发成为了一个热门话题。本文旨在通过浅显易懂的语言,带领初学者了解安卓开发的基础知识,同时为有一定经验的开发者提供进阶技巧。我们将一起探讨如何从零开始构建第一个安卓应用,并逐步深入到性能优化和高级功能的实现。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你提供有价值的指导和灵感。

热门文章

最新文章