Android平台RTSP|RTMP播放器如何实现TextureView渲染

本文涉及的产品
视觉智能开放平台,视频通用资源包5000点
视觉智能开放平台,图像通用资源包5000点
视觉智能开放平台,分割抠图1万点
简介: 本文介绍了在Android平台上使用TextureView进行RTSP和RTMP视频流渲染的技术背景和实现方法。TextureView相较于SurfaceView具备更高性能、更强功能性和更灵活的绘制方式等优势,但也有必须在硬件加速环境下运行和较高内存占用等局限。文中详细展示了如何在SmartPlayerV2工程中创建和配置TextureView,并通过代码示例解释了如何根据视频分辨率信息调整显示比例,以及处理TextureView的各种生命周期回调。此外,还列举了该播放器SDK支持的多项高级功能,如多实例播放、多种编码格式支持、硬解码能力等,旨在帮助开发者更好地理解和实现高性能的直播播放器。

技术背景

自2015年我们发布Android平台RTSP、RTMP直播播放模块以来,渲染这块,支持SurfaceView或GlSurfaceView,当然如果开发者需要TextureView渲染,可以把RTSP、RTMP流数据解码回调YUV或RGB数据上来,上层自己渲染。本文主要介绍,如何实现RTSP、RTMP播放器TextureView渲染。在此之前,我们先看看TextureView优缺点:

先说优点:

  1. 更高的性能:TextureView使用基于硬件加速的渲染管道,可以在GPU中进行图像处理和渲染,这比SurfaceView的软件渲染方式更高效。因此,TextureView在图像和视频渲染方面具有更好的性能。
  2. 更强的功能:TextureView可以与其他控件进行自由组合,可以在布局中灵活地放置和调整大小。而SurfaceView只能全屏显示,无法与其他控件混合使用。
  3. 更灵活的绘制方式:TextureView允许开发者直接在其上面绘制图像,通过Canvas和OpenGL ES等API,可以实现更丰富的渲染效果。
  4. 支持动画和截图:TextureView支持移动、旋转、缩放等动画,并且支持截图功能。

再说缺点:

  1. 必须在硬件加速的窗口中使用:TextureView必须在硬件加速的窗口中使用,如果设备不支持硬件加速或者硬件加速被禁用,TextureView可能无法正常工作。
  2. 占用内存较高:与SurfaceView相比,TextureView占用内存更高,这可能会影响到应用的性能,特别是在处理大型图像或视频时。
  3. 渲染线程问题:在Android 5.0以前,TextureView在主线程进行渲染,这可能导致UI卡顿。虽然在Android 5.0及以后版本中,TextureView有了单独的渲染线程,但在高GPU负荷的场景下,可能存在帧率下降的问题。
  4. 同步问题:TextureView需要在多个线程之间进行写读同步,包括CPU和GPU的同步。当同步失调时,可能会出现掉帧或吞帧导致的卡顿和抖动现象。

技术实现

本文以大牛直播SDK的Android平台SmartPlayerV2工程demo为例:

image.gif

开始播放之前,CreateView()实现如下:

/*
     * SmartPlayer.java
     * Author: daniusdk.com
     * Create rendering with different type
     */
    private boolean CreateView() {
        if (sSurfaceView != null)
            return true;
        Log.i(TAG, "CreateView");
        if (SURFACE_TYPE_NULL == surface_type_) {
            String manufacturer = Build.MANUFACTURER;
            Log.i(TAG, "CreateView, current manufacturer: " + manufacturer);
            if (is_enable_hardware_render_mode) {
                //hardware render模式,第二个参数设置为false
                sSurfaceView = NTRenderer.CreateRenderer(this, false);
            } else {
                //这个字符串可以自己定义,例如判断华为就填写huawei,魅族就填写meizu
                if ("huawei".equalsIgnoreCase(manufacturer)) {
                    sSurfaceView = NTRenderer.CreateRenderer(this, true);
                } else {
                    /*
                     * useOpenGLES2: If with true: Check if system supports openGLES, if
                     * supported, it will choose openGLES. If with false: it will set
                     * with default surfaceView;
                     */
                    sSurfaceView = NTRenderer.CreateRenderer(this, true);
                }
            }
        } else {
            if (SURFACE_TYPE_TEXTURE_VIEW == surface_type_) {
                TextureView texture_view = new TextureView(this);
                texture_view.setSurfaceTextureListener(this);
                sSurfaceView = texture_view;
            } else
                sSurfaceView= new SurfaceView(this);
        }
        if (sSurfaceView == null) {
            Log.i(TAG, "Create render failed..");
            return false;
        }
        if (is_enable_hardware_render_mode || SURFACE_TYPE_SURFACE_VIEW == surface_type_) {
            if (sSurfaceView instanceof SurfaceView) {
                SurfaceHolder surfaceHolder = ((SurfaceView)sSurfaceView).getHolder();
                if (surfaceHolder == null)
                    Log.e(TAG, "CreateView, surfaceHolder with null..");
                 else
                    surfaceHolder.addCallback(this);
            }
        }
        return true;
    }

image.gif

视频流开始播放后,我们会把视频宽高信息回调上来(EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO),然后,根据获取到的宽高信息,调用adjustTextureViewAspectRatio()按比例显示窗口,如果需要铺满显示,不调用比例显示即可。

class EventHandeV2 implements NTSmartEventCallbackV2 {
        @Override
        public void onNTSmartEventCallbackV2(long handle, int id, long param1,
                                             long param2, String param3, String param4, Object param5) {
            //Log.i(TAG, "EventHandeV2: handle=" + handle + " id:" + id);
            String player_event = "";
            switch (id) {
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STARTED:
                    player_event = "开始..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING:
                    player_event = "连接中..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED:
                    player_event = "连接失败..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED:
                    player_event = "连接成功..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED:
                    player_event = "连接断开..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP:
                    player_event = "停止播放..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO:
                    player_event = "分辨率信息: width: " + param1 + ", height: " + param2;
                    handler.post(new OnNTPlayerVideoSize((int)param1, (int)param2));
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED:
                    player_event = "收不到媒体数据,可能是url错误..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL:
                    player_event = "切换播放URL..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE:
                    player_event = "快照: " + param1 + " 路径:" + param3;
                    if (param1 == 0)
                        player_event = player_event + ", 截取快照成功";
                     else
                        player_event = player_event + ", 截取快照失败";
                    if (param4 != null && !param4.isEmpty())
                        player_event += (", user data:" + param4);
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE:
                    player_event = "[record]开始一个新的录像文件 : " + param3;
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED:
                    player_event = "[record]已生成一个录像文件 : " + param3;
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING:
                    Log.i(TAG, "Start Buffering");
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING:
                    Log.i(TAG, "Buffering:" + param1 + "%");
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING:
                    Log.i(TAG, "Stop Buffering");
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED:
                    player_event = "download_speed:" + param1 + "Byte/s" + ", "
                            + (param1 * 8 / 1000) + "kbps" + ", " + (param1 / 1024)
                            + "KB/s";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RTSP_STATUS_CODE:
                    Log.e(TAG, "RTSP error code received, please make sure username/password is correct, error code:" + param1);
                    player_event = "RTSP error code:" + param1;
                    break;
                 ....
            }
            if (player_event.length() > 0) {
                Log.i(TAG, player_event);
                Message message = new Message();
                message.what = PLAYER_EVENT_MSG;
                message.obj = player_event;
                handler.sendMessage(message);
            }
        }
    }

image.gif

OnNTPlayerVideoSize实现如下:

class OnNTPlayerVideoSize implements Runnable{
        int width_;
        int height_;
        OnNTPlayerVideoSize(int w, int h ) {
            this.width_ = w;
            this.height_ = h;
        }
        public void run() {
            if (this.width_ < 1 || this.height_ < 1)
                return;
            video_width_ = this.width_;
            video_height_ = this.height_;
            if (null == sSurfaceView)
                return;
            if (SURFACE_TYPE_TEXTURE_VIEW == surface_type_ && sSurfaceView instanceof TextureView)
                adjustTextureViewAspectRatio((TextureView)sSurfaceView, this.width_, this.height_);
            else if (((isHardwareDecoder&&is_enable_hardware_render_mode) || SURFACE_TYPE_SURFACE_VIEW == surface_type_)
                && sSurfaceView instanceof SurfaceView )
                adjustSurfaceViewAspectRatio((SurfaceView)sSurfaceView, this.width_, this.height_);
        }
    }

image.gif

针对TextureView处理如下:

/**
     * Invoked when a {@link TextureView}'s SurfaceTexture is ready for use.
     *
     * @param surface The surface returned by
     *                {@link android.view.TextureView#getSurfaceTexture()}
     * @param width The width of the surface
     * @param height The height of the surface
     */
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        Log.i(TAG, "TextureView onSurfaceTextureAvailable w:" + width + ", h:" + height);
        if (texture_view_surface_ != null) {
            texture_view_surface_.release();
            texture_view_surface_ = null;
        }
        texture_view_surface_ = new Surface(surface);
        if (isPlaying && SURFACE_TYPE_TEXTURE_VIEW == surface_type_) {
            libPlayer.SetSurface(playerHandle, texture_view_surface_, 0, disable_codec_render_surface_, disable_sdk_render_surface_);
            if (video_width_ > 0 && video_height_ > 0 && sSurfaceView != null && (sSurfaceView instanceof TextureView))
                adjustTextureViewAspectRatio((TextureView)sSurfaceView, video_width_, video_height_);
        }
    }
    /**
     * Invoked when the {@link SurfaceTexture}'s buffers size changed.
     *
     * @param surface The surface returned by
     *                {@link android.view.TextureView#getSurfaceTexture()}
     * @param width The new width of the surface
     * @param height The new height of the surface
     */
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        Log.i(TAG, "TextureView onSurfaceTextureSizeChanged w:" + width + ", h:" + height);
        if(isPlaying && SURFACE_TYPE_TEXTURE_VIEW == surface_type_) {
            if (sSurfaceView != null && (sSurfaceView instanceof TextureView) && video_width_ > 0 && video_height_ > 0)
                adjustTextureViewAspectRatio((TextureView) sSurfaceView, video_width_, video_height_);
        }
    }
    /**
     * Invoked when the specified {@link SurfaceTexture} is about to be destroyed.
     * If returns true, no rendering should happen inside the surface texture after this method
     * is invoked. If returns false, the client needs to call {@link SurfaceTexture#release()}.
     * Most applications should return true.
     *
     * @param surface The surface about to be destroyed
     */
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        Log.i(TAG, "TextureView onSurfaceTextureDestroyed");
        if(isPlaying && SURFACE_TYPE_TEXTURE_VIEW == surface_type_)
            libPlayer.SetSurface(playerHandle, null, 0, 0, 0);
        if (texture_view_surface_ != null) {
            texture_view_surface_.release();
            texture_view_surface_ = null;
        }
        return true;
    }
    /**
     * Invoked when the specified {@link SurfaceTexture} is updated through
     * {@link SurfaceTexture#updateTexImage()}.
     *
     * @param surface The surface just updated
     */
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        // Log.i(TAG, "TextureView onSurfaceTextureUpdated");
    }

image.gif

至此,RTSP|RTMP播放,我们是实现的功能如下(如不特别说明,代表Windows、Linux、Android、iOS平台均支持):

  • [支持播放协议]高稳定、超低延迟、业内首屈一指的RTSP|RTMP直播播放器SDK;
  • [多实例播放]支持多实例播放;
  • [事件回调]支持网络状态、buffer状态等回调;
  • [视频格式]支持H.265、H.264,此外,还支持RTSP MJPEG播放;
  • [音频格式]RTSP支持AAC/PCMA/PCMU、RTMP支持AAC/PCMA/PCMU/Speex;
  • [H.264/H.265软解码]支持H.264/H.265软解,支持Enhanced RTMP H.265
  • [H.264硬解码]Windows/Android/iOS支持特定机型H.264硬解;
  • [H.265硬解]Windows/Android/iOS支持特定机型H.265硬解;
  • [H.264/H.265硬解码]Android支持设置Surface模式硬解和普通模式硬解码;
  • [RTSP模式设置]支持RTSP TCP/UDP模式设置;
  • [RTSP TCP/UDP自动切换]支持RTSP TCP、UDP模式自动切换;
  • [RTSP超时设置]支持RTSP超时时间设置,单位:秒;
  • [RTSP 401认证处理]支持上报RTSP 401事件,如URL携带鉴权信息,会自动处理;
  • [缓冲时间设置]支持buffer time设置;
  • [首屏秒开]支持首屏秒开模式;
  • [复杂网络处理]支持断网重连等各种网络环境自动适配;
  • [快速切换URL]支持播放过程中,快速切换其他URL,内容切换更快;
  • [音视频多种render机制]Android平台,视频:surfaceview/OpenGL ES,音频:AudioTrack/OpenSL ES;
  • [实时静音]支持播放过程中,实时静音/取消静音;
  • [实时音量调节]支持播放过程中实时调节音量;
  • [实时快照]支持播放过程中截取当前播放画面;
  • [只播关键帧]Windows平台支持实时设置是否只播放关键帧;
  • [渲染角度]支持0°,90°,180°和270°四个视频画面渲染角度设置;
  • [渲染镜像]支持水平反转、垂直反转模式设置;
  • [等比例缩放]支持图像等比例缩放绘制(Android设置surface模式硬解模式不支持);
  • [实时下载速度更新]支持当前下载速度实时回调(支持设置回调时间间隔);
  • [解码前视频数据回调]支持H.264/H.265数据回调;
  • [解码后视频数据回调]支持解码后YUV/RGB数据回调;
  • [解码前音频数据回调]支持AAC/PCMA/PCMU数据回调;
  • [音视频自适应]支持播放过程中,音视频信息改变后自适应;
  • [扩展录像功能]完美支持和录像SDK组合使用。

总结

做播放器不难,做高稳定低延迟低资源占用的RTMP|RTSP直播播放器还是有点儿难度,以上是大牛直播SDK针对Android平台RTMP|RTSP播放器TextureView渲染相关的技术交流,感兴趣的开发者,也可以找我单独沟通。

相关文章
|
3月前
|
监控 Android开发 数据安全/隐私保护
批量发送短信的平台,安卓群发短信工具插件脚本,批量群发短信软件【autojs版】
这个Auto.js脚本实现了完整的批量短信发送功能,包含联系人管理、短信内容编辑、发送状态监控等功能
|
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平台设计规范整理(尺寸+组成元素+字体+滑块)
转自:http://www.ui.cn/project.php?id=12394
794 0
|
27天前
|
开发工具 Android开发
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
282 11
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
|
1月前
|
Java 开发工具 Maven
【01】完整的安卓二次商业实战-详细的初级步骤同步项目和gradle配置以及开发思路-优雅草伊凡
【01】完整的安卓二次商业实战-详细的初级步骤同步项目和gradle配置以及开发思路-优雅草伊凡
123 6
|
3月前
|
安全 数据库 Android开发
在Android开发中实现两个Intent跳转及数据交换的方法
总结上述内容,在Android开发中,Intent不仅是活动跳转的桥梁,也是两个活动之间进行数据交换的媒介。运用Intent传递数据时需注意数据类型、传输大小限制以及安全性问题的处理,以确保应用的健壯性和安全性。
243 11

热门文章

最新文章