Android平台RTSP|RTMP直播播放器技术接入说明

本文涉及的产品
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,视频资源包5000点
视觉智能开放平台,图像资源包5000点
简介: 大牛直播SDK自2015年发布RTSP、RTMP直播播放模块,迭代从未停止,SmartPlayer功能强大、性能强劲、高稳定、超低延迟、超低资源占用。无需赘述,全自研内核,行业内一致认可的跨平台RTSP、RTMP直播播放器。本文以Android平台为例,介绍下如何集成RTSP、RTMP播放模块。

技术背景

大牛直播SDK自2015年发布RTSP、RTMP直播播放模块,迭代从未停止,SmartPlayer功能强大、性能强劲、高稳定、超低延迟、超低资源占用。无需赘述,全自研内核,行业内一致认可的跨平台RTSP、RTMP直播播放器。本文以Android平台为例,介绍下如何集成RTSP、RTMP播放模块。

技术对接

系统要求

  • SDK支持Android5.1及以上版本;
  • 支持的CPU架构:armv7, arm64, x86, x86_64。

准备工作

  • 确保SmartPlayerJniV2.java放到com.daniulive.smartplayer包名下(可在其他包名下调用);
  • Smartavengine.jar加入到工程;
  • 拷贝SmartPlayerV2\app\src\main\jniLibs\armeabi-v7a、 SmartPlayerV2\app\src\main\jniLibs\arm64-v8a、SmartPlayerV2\app\src\main\jniLibs\x86和SmartPlayerV2\app\src\main\jniLibs\x86_64 下 libSmartPlayer.so到工程;
  • AndroidManifast.xml添加相关权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" >
</uses-permission>
<uses-permission android:name="android.permission.INTERNET" >
</uses-permission>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

image.gif

  • Load相关so:
static {  
    System.loadLibrary("SmartPlayer");
}

image.gif

  • build.gradle配置32/64位库:
splits {
    abi {
        enable true
        reset()
        // Specifies a list of ABIs that Gradle should create APKs for
        include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' //select ABIs to build APKs for
        // Specify that we do not want to also generate a universal APK that includes all ABIs
        universalApk true
    }
}

image.gif

  • 如需集成到自己系统测试,请用大牛直播SDK的app name,授权版按照授权app name正常使用即可;
  • 如何改app-name,strings.xml做以下修改:
<string name="app_name">SmartPlayerSDKDemo</string>

image.gif

接口设计

Android RTSP|RTMP播放端SDK接口详解

调用描述

接口

接口描述

最先调用,如成功返回播放实例

SmartPlayerOpen

player初始化,设置上下文信息,返回player句柄

Event回调

SetSmartPlayerEventCallbackV2

设置event callback

硬解码设置H.264

SetSmartPlayerVideoHWDecoder

设置是否用H.264硬解码播放,如硬解码不支持,自动适配到软解码

硬解码设置H.265

SetSmartPlayerVideoHevcHWDecoder

设置是否用H.265硬解码播放,如硬解码不支持,自动适配到软解码

视频画面

填充模式

SmartPlayerSetRenderScaleMode

设置视频画面的填充模式,如填充整个view、等比例填充view,如不设置,默认填充整个view

设置SurfaceView模式下render类型

SmartPlayerSetSurfaceRenderFormat

设置SurfaceView模式下(NTRenderer.CreateRenderer第二个参数传false的情况),render类型

0: RGB565格式,如不设置,默认此模式; 1: ARGB8888格式

设置SurfaceView模式下抗锯齿效果

SmartPlayerSetSurfaceAntiAlias

设置SurfaceView模式下(NTRenderer.CreateRenderer第二个参数传false的情况),抗锯齿效果,注意:抗锯齿模式开启后,可能会影像性能,请慎用

设置播放的surface

SmartPlayerSetSurface

设置播放的surface,如果为null,则播放纯音频

设置视频硬解码下Mediacodec自行绘制模式

SmartPlayerSetHWRenderMode

此种模式下,硬解码兼容性和效率更好,回调YUV/RGB快照和图像等比例缩放功能将不可用

更新硬解码surface

SmartPlayerUpdateHWRenderSurface

设置更新硬解码surface

音频回调

YUV/RGB

SmartPlayerSetExternalRender

提供解码后YUV/RGB数据接口,供用户自己render或进一步处理(如视频分析)

Audio

SmartPlayerSetExternalAudioOutput

回调audio数据到上层(供二次处理之用)

audio输出类型

SmartPlayerSetAudioOutputType

如果use_audiotrack设置为0,将会自动选择输出设备,如果设置为1,使用audiotrack模式,一对一回音消除模式下,请选用audiotrack模式

Video输出类型

NTRenderer.CreateRenderer(上层demo内)

第二个参数,如果是true,用openGLES绘制,false则用默认surfaceView

播放模式

缓冲时间设置

SmartPlayerSetBuffer

设置播放端缓存数据buffer,单位:毫秒,如不需buffer,设置为0

首屏秒开

SmartPlayerSetFastStartup

设置快速启动后,如果CDN缓存GOP,实现首屏秒开

低延迟模式

SmartPlayerSetLowLatencyMode

针对类似于直播娃娃机等期待超低延迟的使用场景,超低延迟播放模式下,延迟可达到200~400ms

快速切换URL

SmartPlayerSwitchPlaybackUrl

快速切换播放url,快速切换时,只换播放source部分,适用于不同数据流之间,快速切换(如娃娃机双摄像头切换或高低分辨率流切换)

RTSP TCP/UDP模式设置

SmartPlayerSetRTSPTcpMode

设置RTSP TCP/UDP模式,如不设置,默认UDP模式

RTSP超时时间设置

SmartPlayerSetRTSPTimeout

设置RTSP超时时间,timeout单位为秒,必须大于0

设置RTSP TCP/UDP自动切换

SmartPlayerSetRTSPAutoSwitchTcpUdp

对于RTSP来说,有些可能支持rtp over udp方式,有些可能支持使用rtp over tcp方式

为了方便使用,有些场景下可以开启自动尝试切换开关, 打开后如果udp无法播放,sdk会自动尝试tcp, 如果tcp方式播放不了,sdk会自动尝试udp.

设置RTSP用户名和密码

SetRTSPAuthenticationInfo

如果RTSP URL已包含用户名和密码, 此接口设置的用户名和密码将无效. 就是说要用这个接口设置的用户名和密码去做认证, RTSP URL不能包含用户名和密码.

实时静音

SmartPlayerSetMute

实时静音

设置播放音量

SmartPlayerSetAudioVolume

播放端音量实时调节,范围[0,100],0时为静音,100为原始流数据最大音量

设置是否禁用 Enhanced

 RTMP

DisableEnhancedRTMP

disable enhanced RTMP, SDK默认是开启enhanced RTMP的

实时截图

CaptureImage

支持JPEG和PNG两种格式

视频镜像旋转

旋转

SmartPlayerSetRotation

设置顺时针旋转, 注意除了0度之外, 其他角度都会额外消耗性能,当前支持 0度,90度, 180度, 270度 旋转

水平反转

SmartPlayerSetFlipHorizontal

设置视频水平反转

垂直反转

SmartPlayerSetFlipVertical

设置视频垂直反转

设置URL

SmartPlayerSetUrl

设置需要播放或录像的RTMP/RTSP url

开始播放

SmartPlayerStartPlay

开始播放RTSP/RTMP流

停止播放

SmartPlayerStopPlay

停止播放RTSP/RTMP流

关闭播放实例

SmartPlayerClose

结束时必须调用close接口释放资源

功能支持

  • 音频:AAC/Speex(RTMP)/PCMA/PCMU;
  • 视频:H.264、H.265;
  • 播放协议:RTSP|RTMP;
  • 支持纯音频、纯视频、音视频播放;
  • 支持多实例播放;
  • 支持软解码,特定机型硬解码;
  • 支持RTSP TCP、UDP模式设置;
  • 支持RTSP TCP、UDP模式自动切换;
  • 支持RTSP超时时间设置,单位:秒;
  • 支持buffer时间设置,单位:毫秒;
  • 支持超低延迟模式;
  • 支持断网自动重连、视频追赶,支持buffer状态等回调;
  • 支持视频view实时旋转(0° 90° 180° 270°);
  • 支持视频view水平反转、垂直反转;
  • 支持Surfaceview/OpenGL ES/TextureView绘制;
  • 支持视频画面填充模式设置;
  • 音频支持AudioTrack、OpenSL ES模式;
  • 支持jpeg、png实时截图;
  • 支持实时音量调节;
  • 支持解码前音视频数据回调;
  • 支持解码后YUV/RGB数据回调;
  • 支持Enhanced RTMP;
  • 支持扩展录像功能;
  • 支持Android 5.1及以上版本。

接口调用详解

本文以大牛直播SDK Android平台SmartPlayerV2为例,播放之前,设置初始化参数配置(软解还是硬解、buffer time等)和需要播放的RTSP或RTMP URL,点开始播放即可。

image.gif

onCreate()时,先new SmartPlayerJniV2():

/*
 * SmartPlayer.java
 * Author: daniusdk.com
 */
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_smart_player);
    
    ...
    libPlayer = new SmartPlayerJniV2();
    myContext = this.getApplicationContext();
}

image.gif

开始播放、停止播放实现,开始播放的时候,调用InitAndSetConfig(),完成常规参数初始化,然后调用仅播放相关的其他接口。

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);
                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);
            btnSetPlayBuffer.setEnabled(false);
            btnLowLatency.setEnabled(false);
            btnFastStartup.setEnabled(false);
            btnRecoderMgr.setEnabled(false);
            isPlaying = true;
            Log.i(TAG, "Start playback stream--");
        }
    }
});

image.gif

由于RTSP、RTMP播放模块,除了常规的直播播放外,也可能录像、或者实时拉流转发到RTMP服务器或轻量级RTSP服务,所以,和录像、转发相关的播放端基础参数配置,放到InitAndSetConfig()实现:

private void InitAndSetConfig() {
    playerHandle = libPlayer.SmartPlayerOpen(myContext);
    if (playerHandle == 0) {
        Log.e(TAG, "surfaceHandle with nil..");
        return;
    }
    libPlayer.SetSmartPlayerEventCallbackV2(playerHandle,
            new EventHandeV2());
    libPlayer.SmartPlayerSetBuffer(playerHandle, playBuffer);
    // set report download speed(默认2秒一次回调 用户可自行调整report间隔)
    libPlayer.SmartPlayerSetReportDownloadSpeed(playerHandle, 1, 2);
    libPlayer.SmartPlayerSetFastStartup(playerHandle, isFastStartup ? 1 : 0);
    //设置RTSP超时时间
    int rtsp_timeout = 10;
    libPlayer.SmartPlayerSetRTSPTimeout(playerHandle, rtsp_timeout);
    //设置RTSP TCP/UDP模式自动切换
    int is_auto_switch_tcp_udp = 1;
    libPlayer.SmartPlayerSetRTSPAutoSwitchTcpUdp(playerHandle, is_auto_switch_tcp_udp);
    libPlayer.SmartPlayerSaveImageFlag(playerHandle, 1);
    // It only used when playback RTSP stream..
    // libPlayer.SmartPlayerSetRTSPTcpMode(playerHandle, 1);
    // playbackUrl = "rtmp://localhost:1935/live/stream1";
    if (playbackUrl == null) {
        Log.e(TAG, "playback URL with NULL...");
        return;
    }
    libPlayer.SmartPlayerSetUrl(playerHandle, playbackUrl);
    // try_set_rtsp_url(playbackUrl);
}

image.gif

EventHandle播放端事件回调处理,是底层状态反馈非常重要的媒介,除了网络状态、buffering状态回调外、还有录像状态、快照状态等回调:

class EventHandeV2 implements NTSmartEventCallbackV2 {
    @Override
    public void onNTSmartEventCallbackV2(long handle, int id, long param1,
                                         long param2, String param3, String param4, Object param5) {
        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;
                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

如果RTSP、RTMP流需要录像:

btnStartStopRecorder.setOnClickListener(new Button.OnClickListener() {
    // @Override
    public void onClick(View v) {
        if (isRecording) {
            int iRet = libPlayer.SmartPlayerStopRecorder(playerHandle);
            if (iRet != 0) {
                Log.e(TAG, "Call SmartPlayerStopRecorder failed..");
                return;
            }
            if (!isPlaying) {
                btnPopInputUrl.setEnabled(true);
                btnSetPlayBuffer.setEnabled(true);
                btnFastStartup.setEnabled(true);
                btnRecoderMgr.setEnabled(true);
                libPlayer.SmartPlayerClose(playerHandle);
                playerHandle = 0;
            }
            btnStartStopRecorder.setText(" 开始录像");
            isRecording = false;
        } else {
            Log.i(TAG, "onClick start recorder..");
            if (!isPlaying) {
                InitAndSetConfig();
            }
            ConfigRecorderFunction();
            int startRet = libPlayer.SmartPlayerStartRecorder(playerHandle);
            if (startRet != 0) {
                Log.e(TAG, "Failed to start recorder.");
                return;
            }
            btnPopInputUrl.setEnabled(false);
            btnSetPlayBuffer.setEnabled(false);
            btnFastStartup.setEnabled(false);
            btnRecoderMgr.setEnabled(false);
            isRecording = true;
            btnStartStopRecorder.setText("停止录像");
        }
    }
});

image.gif

其中,录像参数配置选项设置如下,除了下面演示接口外,还可以设置仅录视频或音频:

void ConfigRecorderFunction() {
    if (libPlayer != null) {
        int is_rec_trans_code = 1;
        libPlayer.SmartPlayerSetRecorderAudioTranscodeAAC(playerHandle, is_rec_trans_code);
        if (recDir != null && !recDir.isEmpty()) {
            int ret = libPlayer.SmartPlayerCreateFileDirectory(recDir);
            if (0 == ret) {
                if (0 != libPlayer.SmartPlayerSetRecorderDirectory(
                        playerHandle, recDir)) {
                    Log.e(TAG, "Set recoder dir failed , path:" + recDir);
                    return;
                }
                if (0 != libPlayer.SmartPlayerSetRecorderFileMaxSize(
                        playerHandle, 200)) {
                    Log.e(TAG,
                            "SmartPublisherSetRecorderFileMaxSize failed.");
                    return;
                }
            } else {
                Log.e(TAG, "Create recorder dir failed, path:" + recDir);
            }
        }
    }
}

image.gif

如需播放过程中实时截图:

btnCaptureImage.setOnClickListener(new Button.OnClickListener() {
    @SuppressLint("SimpleDateFormat")
    public void onClick(View v) {
        if (0 == playerHandle)
            return;
        if (null == capture_image_date_format_)
            capture_image_date_format_ = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS");
        String timestamp = capture_image_date_format_.format(new Date());
        String imageFileName = timestamp;
        String image_path = imageSavePath + "/" + imageFileName;
        int quality;
        boolean is_jpeg = true;
        if (is_jpeg) {
            image_path += ".jpeg";
            quality = 100;
        }
        else {
            image_path += ".png";
            quality = 100;
        }
        int capture_ret = libPlayer.CaptureImage(playerHandle,is_jpeg?0:1, quality, image_path, "test cix");
        Log.i(TAG, "capture image ret:" + capture_ret + ", file:" + image_path);
    }
});

image.gif

如需对视频view做水平、垂直翻转或旋转:

btnFlipVertical.setOnClickListener(new Button.OnClickListener() {
    public void onClick(View v) {
        is_flip_vertical = !is_flip_vertical;
        if (is_flip_vertical) {
            btnFlipVertical.setText("取消反转");
        } else {
            btnFlipVertical.setText("垂直反转");
        }
        if (playerHandle != 0) {
            libPlayer.SmartPlayerSetFlipVertical(playerHandle,
                    is_flip_vertical ? 1 : 0);
        }
    }
});
btnFlipHorizontal.setOnClickListener(new Button.OnClickListener() {
    public void onClick(View v) {
        is_flip_horizontal = !is_flip_horizontal;
        if (is_flip_horizontal) {
            btnFlipHorizontal.setText("取消反转");
        } else {
            btnFlipHorizontal.setText("水平反转");
        }
        if (playerHandle != 0) {
            libPlayer.SmartPlayerSetFlipHorizontal(playerHandle,
                    is_flip_horizontal ? 1 : 0);
        }
    }
});
btnRotation.setOnClickListener(new Button.OnClickListener() {
    public void onClick(View v) {
        rotate_degrees += 90;
        rotate_degrees = rotate_degrees % 360;
        if (0 == rotate_degrees) {
            btnRotation.setText("旋转90度");
        } else if (90 == rotate_degrees) {
            btnRotation.setText("旋转180度");
        } else if (180 == rotate_degrees) {
            btnRotation.setText("旋转270度");
        } else if (270 == rotate_degrees) {
            btnRotation.setText("不旋转");
        }
        if (playerHandle != 0) {
            libPlayer.SmartPlayerSetRotation(playerHandle,
                    rotate_degrees);
        }
    }
});

image.gif

onDestroy() 的时候,停掉播放、录像、释放播放端实例句柄:

@Override
protected void onDestroy() {
    Log.i(TAG, "Run into activity destory++");
    if (playerHandle != 0) {
        if (isPlaying) {
            libPlayer.SmartPlayerStopPlay(playerHandle);
        }
        if (isRecording) {
            libPlayer.SmartPlayerStopRecorder(playerHandle);
        }
        libPlayer.SmartPlayerClose(playerHandle);
        playerHandle = 0;
    }
    super.onDestroy();
    finish();
    System.exit(0);
}

image.gif

以上是大概的流程,如果需要播放多实例,可以做个简单的封装,多实例效果如下:

image.gif

LibPlayerWrapper.java参考封装代码如下,如需额外功能,只要按照设计框架,添加进去即可:

/*
 * LibPlayerWrapper.java.java
 * Author: daniusdk.com
 */
package com.daniulive.smartplayer;
import android.content.Context;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.View;
import com.eventhandle.NTSmartEventCallbackV2;
import java.lang.ref.WeakReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LibPlayerWrapper {
    private static String TAG = "NTLogLibPlayerW";
    private static final int OK = 0;
    private WeakReference<Context> context_;
    private final ReadWriteLock rw_lock_ = new ReentrantReadWriteLock(true);
    private final java.util.concurrent.locks.Lock write_lock_ = rw_lock_.writeLock();
    private final java.util.concurrent.locks.Lock read_lock_ = rw_lock_.readLock();
    private SmartPlayerJniV2 lib_player_;
    private volatile long native_handle_;
    private View view_;
    private volatile boolean is_playing_;
    private volatile boolean is_recording_;
    private WeakReference<EventListener> event_listener_;
    public LibPlayerWrapper(SmartPlayerJniV2 lib_player, Context context, EventListener listener) {
        if (!empty())
            throw new IllegalStateException("it is not empty");
        if (null == lib_player)
            throw new NullPointerException("lib_player is null");
        this.lib_player_ = lib_player;
        if (context != null)
            this.context_ = new WeakReference<>(context);
        if (listener == null ) {
            this.event_listener_ = null;
        }
        else {
            this.event_listener_ = new WeakReference<>(listener);
        }
    }
    private void clear_all_playing_flags() {
        this.is_playing_ = false;
        this.is_recording_ = false;
    }
    public void set(long handle) {
        if (!empty())
            throw new IllegalStateException("it is not empty");
        write_lock_.lock();
        try {
            clear_all_playing_flags();
            this.native_handle_ = handle;
        } finally {
            write_lock_.unlock();
        }
        Log.i(TAG, "set native_handle:" + handle);
    }
    public void SetView(View view) {
        Log.i(TAG, "SetView: " + view);
        this.view_ = view;
    }
    @Override
    protected void finalize() throws Throwable {
        try {
            if (check_native_handle()) {
                if(is_playing()) {
                    lib_player_.SmartPlayerStopPlay(get());
                    this.is_playing_ = false;
                }
                if(is_recording()) {
                    lib_player_.SmartPlayerStopRecorder(get());
                    this.is_recording_ = false;
                }
                lib_player_.SmartPlayerClose(this.native_handle_);
                Log.i(TAG, "finalize close handle:" + this.native_handle_);
                this.native_handle_ = 0;
            }
        }catch (Exception e) {
        }
        super.finalize();
    }
    public void release() {
        if (empty())
            return;
        if(is_playing())
            StopPlayer();
        if (is_recording())
            StopRecorder();
        long handle;
        write_lock_.lock();
        try {
            handle = this.native_handle_;
            this.native_handle_ = 0;
            clear_all_playing_flags();
        } finally {
            write_lock_.unlock();
        }
        if (lib_player_ != null && handle != 0)
            lib_player_.SmartPlayerClose(handle);
    }
    public boolean try_release() {
        if (empty())
            return false;
        if (is_player_running()) {
            Log.i(TAG, "try_release it is running, native_handle:" + get());
            return false;
        }
        long handle;
        write_lock_.lock();
        try {
            if (is_player_running())
                return false;
            handle = this.native_handle_;
            this.native_handle_ = 0;
        } finally {
            write_lock_.unlock();
        }
        if (lib_player_ != null && handle != 0)
            lib_player_.SmartPlayerClose(handle);
        return true;
    }
    public final boolean empty() { return 0 == this.native_handle_; }
    public final long get() { return this.native_handle_; }
    public View get_view() {return this.view_;}
    public final boolean check_native_handle() {
        return this.lib_player_ != null && this.native_handle_ != 0;
    }
    public final boolean is_playing() { return is_playing_; }
    public final boolean is_recording() { return is_recording_; }
    public final boolean is_player_running() { return is_playing_ || is_recording_; }
    private boolean isValidRtspOrRtmpUrl(String url) {
        if (url == null || url.isEmpty()) {
            return false;
        }
        return url.trim().startsWith("rtsp://") || url.startsWith("rtmp://");
    }
    private EventListener getListener() {
        if ( this.event_listener_ == null )
            return null;
        return this.event_listener_.get();
    }
    protected final Context application_context() {
        if (null == context_)
            return null;
        return context_.get();
    }
    public boolean OpenPlayerHandle(String playback_url, int play_buffer, int is_using_tcp) {
        if (check_native_handle())
            return true;
        if(!isValidRtspOrRtmpUrl(playback_url))
            return false;
        long handle = lib_player_.SmartPlayerOpen(application_context());
        if (0==handle) {
            Log.e(TAG, "sdk open failed!");
            return false;
        }
        lib_player_.SetSmartPlayerEventCallbackV2(handle, new EventHandleV2());
        lib_player_.SmartPlayerSetBuffer(handle, play_buffer);
        // set report download speed(默认2秒一次回调 用户可自行调整report间隔)
        lib_player_.SmartPlayerSetReportDownloadSpeed(handle, 1, 4);
        boolean isFastStartup = true;
        lib_player_.SmartPlayerSetFastStartup(handle, isFastStartup ? 1 : 0);
        //设置RTSP超时时间
        int rtsp_timeout = 10;
        lib_player_.SmartPlayerSetRTSPTimeout(handle, rtsp_timeout);
        //设置RTSP TCP/UDP模式自动切换
        int is_auto_switch_tcp_udp = 1;
        lib_player_.SmartPlayerSetRTSPAutoSwitchTcpUdp(handle, is_auto_switch_tcp_udp);
        lib_player_.SmartPlayerSaveImageFlag(handle, 1);
        // It only used when playback RTSP stream..
        lib_player_.SmartPlayerSetRTSPTcpMode(handle, is_using_tcp);
        lib_player_.DisableEnhancedRTMP(handle, 0);
        lib_player_.SmartPlayerSetUrl(handle, playback_url);
        set(handle);
        return true;
    }
    private void SetPlayerParam(boolean is_hardware_decoder, boolean is_enable_hardware_render_mode, boolean is_mute)
    {
         Surface surface = null;
         int surface_codec_media_color_format = 0;
         if (view_ != null && view_ instanceof SurfaceView && ((SurfaceView) view_).getHolder() != null)
             surface = ((SurfaceView) view_).getHolder().getSurface();
         lib_player_.SetSurface(get(), surface, surface_codec_media_color_format, 0, 0);
        lib_player_.SmartPlayerSetRenderScaleMode(get(), 1);
        //int render_format = 1;
        //lib_player.SmartPlayerSetSurfaceRenderFormat(handle, render_format);
        //int is_enable_anti_alias = 1;
        //lib_player.SmartPlayerSetSurfaceAntiAlias(handle, is_enable_anti_alias);
        if (is_hardware_decoder && is_enable_hardware_render_mode) {
            lib_player_.SmartPlayerSetHWRenderMode(get(), 1);
        }
        lib_player_.SmartPlayerSetAudioOutputType(get(), 1);
        lib_player_.SmartPlayerSetMute(get(), is_mute ? 1 : 0);
        if (is_hardware_decoder) {
            int isSupportHevcHwDecoder = lib_player_.SetSmartPlayerVideoHevcHWDecoder(get(), 1);
            int isSupportH264HwDecoder = lib_player_.SetSmartPlayerVideoHWDecoder(get(), 1);
            Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder);
        }
        boolean isLowLatency = true;
        lib_player_.SmartPlayerSetLowLatencyMode(get(), isLowLatency ? 1 : 0);
        boolean is_flip_vertical = false;
        lib_player_.SmartPlayerSetFlipVertical(get(), is_flip_vertical ? 1 : 0);
        boolean is_flip_horizontal = false;
        lib_player_.SmartPlayerSetFlipHorizontal(get(), is_flip_horizontal ? 1 : 0);
        int rotate_degrees = 0;
        lib_player_.SmartPlayerSetRotation(get(), rotate_degrees);
        int curAudioVolume = 100;
        lib_player_.SmartPlayerSetAudioVolume(get(), curAudioVolume);
    }
    class EventHandleV2 implements NTSmartEventCallbackV2 {
        @Override
        public void onNTSmartEventCallbackV2(long handle, int id, long param1,
                                             long param2, String param3, String param4, Object param5) {
            if(event_listener_.get() != null)
            {
                event_listener_.get().onPlayerEventCallback(handle, id, param1, param2, param3, param4, param5);
            }
        }
    }
    public boolean SetMute(boolean is_mute) {
        if (!check_native_handle())
            return false;
        return OK == lib_player_.SmartPlayerSetMute(get(), is_mute? 1 : 0);
    }
    public boolean SetInputAudioVolume(int volume) {
        if (!check_native_handle())
            return false;
        return OK == lib_player_.SmartPlayerSetAudioVolume(get(), volume);
    }
    public boolean CaptureImage(int compress_format, int quality, String file_name, String user_data_string) {
        if (!check_native_handle())
            return false;
        return OK == lib_player_.CaptureImage(get(), compress_format, quality, file_name, user_data_string);
    }
    public boolean StartPlayer(boolean is_hardware_decoder, boolean is_enable_hardware_render_mode, boolean is_mute) {
        if (is_playing()) {
            Log.e(TAG, "already playing, native_handle:" + get());
            return false;
        }
        SetPlayerParam(is_hardware_decoder, is_enable_hardware_render_mode, is_mute);
        int ret = lib_player_.SmartPlayerStartPlay(get());
        if (ret != OK) {
            Log.e(TAG, "call StartPlay failed, native_handle:" + get() + ", ret:" + ret);
            return false;
        }
        write_lock_.lock();
        try {
            this.is_playing_ = true;
        } finally {
            write_lock_.unlock();
        }
        Log.i(TAG, "call StartPlayer OK, native_handle:" + get());
        return true;
    }
    public boolean StopPlayer() {
        if (!check_native_handle())
            return false;
        if (!is_playing()) {
            Log.w(TAG, "it's not playing, native_handle:" + get());
            return false;
        }
        boolean is_need_call = false;
        write_lock_.lock();
        try {
            if (this.is_playing_) {
                this.is_playing_ = false;
                is_need_call = true;
            }
        } finally {
            write_lock_.unlock();
        }
        if (is_need_call)
            lib_player_.SmartPlayerStopPlay(get());
        return true;
    }
    public boolean ConfigRecorderParam(String rec_dir, int file_max_size, int is_transcode_aac,
                                       int is_record_video, int is_record_audio) {
        if(!check_native_handle())
            return false;
        if (null == rec_dir || rec_dir.isEmpty())
            return false;
        int ret = lib_player_.SmartPlayerCreateFileDirectory(rec_dir);
        if (ret != 0) {
            Log.e(TAG, "Create record dir failed, path:" + rec_dir);
            return false;
        }
        if (lib_player_.SmartPlayerSetRecorderDirectory(get(), rec_dir) != 0) {
            Log.e(TAG, "Set record dir failed , path:" + rec_dir);
            return false;
        }
        if (lib_player_.SmartPlayerSetRecorderFileMaxSize(get(),file_max_size) != 0) {
            Log.e(TAG, "SmartPlayerSetRecorderFileMaxSize failed.");
            return false;
        }
        lib_player_.SmartPlayerSetRecorderAudioTranscodeAAC(get(), is_transcode_aac);
        // 更细粒度控制录像的, 一般情况无需调用
        lib_player_.SmartPlayerSetRecorderVideo(get(), is_record_video);
        lib_player_.SmartPlayerSetRecorderAudio(get(), is_record_audio);
        return true;
    }
    public boolean StartRecorder() {
        if (is_recording()) {
            Log.e(TAG, "already recording, native_handle:" + get());
            return false;
        }
        int ret = lib_player_.SmartPlayerStartRecorder(get());
        if (ret != OK) {
            Log.e(TAG, "call SmartPlayerStartRecorder failed, native_handle:" + get() + ", ret:" + ret);
            return false;
        }
        write_lock_.lock();
        try {
            this.is_recording_ = true;
        } finally {
            write_lock_.unlock();
        }
        Log.i(TAG, "call SmartPlayerStartRecorder OK, native_handle:" + get());
        return true;
    }
    public boolean StopRecorder() {
        if (!check_native_handle())
            return false;
        if (!is_recording()) {
            Log.w(TAG, "it's not recording, native_handle:" + get());
            return false;
        }
        boolean is_need_call = false;
        write_lock_.lock();
        try {
            if (this.is_recording_) {
                this.is_recording_ = false;
                is_need_call = true;
            }
        } finally {
            write_lock_.unlock();
        }
        if (is_need_call)
            lib_player_.SmartPlayerStopRecorder(get());
        return true;
    }
    private static boolean is_null_or_empty(String val) {
        return null == val || val.isEmpty();
    }
}

image.gif

总结

以上是Android平台RTSP、RTMP直播播放模块对接说明,在此之前,我们针对SmartPlayer做过一些技术方面的探讨,从低延迟、音视频同步处理、多实例实现、解码效率、性能占用、解码后数据对接、实时截图、录像、网络抖动处理等各个维度,做过相关的技术分享。感兴趣的开发者,可以单独跟我们探讨。

相关文章
|
2月前
|
安全 Android开发 iOS开发
Android vs. iOS:构建生态差异与技术较量的深度剖析###
本文深入探讨了Android与iOS两大移动操作系统在构建生态系统上的差异,揭示了它们各自的技术优势及面临的挑战。通过对比分析两者的开放性、用户体验、安全性及市场策略,本文旨在揭示这些差异如何塑造了当今智能手机市场的竞争格局,为开发者和用户提供决策参考。 ###
|
2月前
|
安全 Android开发 iOS开发
安卓与iOS的较量:技术深度对比
【10月更文挑战第18天】 在智能手机操作系统领域,安卓和iOS无疑是两大巨头。本文将深入探讨这两种系统的技术特点、优势以及它们之间的主要差异,帮助读者更好地理解这两个平台的独特之处。
58 0
|
14天前
|
IDE 开发工具 Android开发
移动应用开发之旅:探索Android和iOS平台
在这篇文章中,我们将深入探讨移动应用开发的两个主要平台——Android和iOS。我们将了解它们的操作系统、开发环境和工具,并通过代码示例展示如何在这两个平台上创建一个简单的“Hello World”应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧,帮助你更好地理解和掌握移动应用开发。
40 17
|
1月前
|
安全 搜索推荐 Android开发
揭秘安卓与iOS系统的差异:技术深度对比
【10月更文挑战第27天】 本文深入探讨了安卓(Android)与iOS两大移动操作系统的技术特点和用户体验差异。通过对比两者的系统架构、应用生态、用户界面、安全性等方面,揭示了为何这两种系统能够在市场中各占一席之地,并为用户提供不同的选择。文章旨在为读者提供一个全面的视角,理解两种系统的优势与局限,从而更好地根据自己的需求做出选择。
96 2
|
1月前
|
安全 搜索推荐 Android开发
揭秘iOS与安卓系统的差异:一场技术与哲学的较量
在智能手机的世界里,iOS和Android无疑是两大巨头,它们不仅定义了操作系统的标准,也深刻影响了全球数亿用户的日常生活。本文旨在探讨这两个平台在设计理念、用户体验、生态系统及安全性等方面的本质区别,揭示它们背后的技术哲学和市场策略。通过对比分析,我们将发现,选择iOS或Android,不仅仅是选择一个操作系统,更是选择了一种生活方式和技术信仰。
|
2月前
|
Java 程序员 开发工具
Android|修复阿里云播放器下载不回调的问题
虽然 GC 带来了很多便利,但在实际编码时,我们也需要注意对象的生命周期管理,该存活的存活,该释放的释放,避免因为 GC 导致的问题。
39 2
|
2月前
|
安全 Android开发 iOS开发
iOS与安卓:技术生态的双雄争霸
在当今数字化时代,智能手机操作系统的竞争愈发激烈。iOS和安卓作为两大主流平台,各自拥有独特的技术优势和市场地位。本文将从技术架构、用户体验、安全性以及开发者支持四个方面,深入探讨iOS与安卓之间的差异,并分析它们如何塑造了今天的移动技术生态。无论是追求极致体验的苹果用户,还是享受开放自由的安卓粉丝,了解这两大系统的内在逻辑对于把握未来趋势至关重要。
|
2月前
|
安全 搜索推荐 Android开发
揭秘iOS与Android系统的差异:一场技术与哲学的较量
在当今数字化时代,智能手机操作系统的选择成为了用户个性化表达和技术偏好的重要标志。iOS和Android,作为市场上两大主流操作系统,它们之间的竞争不仅仅是技术的比拼,更是设计理念、用户体验和生态系统构建的全面较量。本文将深入探讨iOS与Android在系统架构、应用生态、用户界面及安全性等方面的本质区别,揭示这两种系统背后的哲学思想和市场策略,帮助读者更全面地理解两者的优劣,从而做出更适合自己的选择。
|
2月前
|
安全 Android开发 iOS开发
安卓vs iOS:探索两种操作系统的独特魅力与技术深度###
【10月更文挑战第16天】 本文旨在深入浅出地探讨安卓(Android)与iOS这两种主流移动操作系统的特色、优势及背后的技术理念。通过对比分析,揭示它们各自如何塑造了移动互联网的生态,并为用户提供丰富多彩的智能体验。无论您是科技爱好者还是普通用户,都能从这篇文章中感受到技术创新带来的无限可能。 ###
58 2
|
2月前
|
机器学习/深度学习 人工智能 Android开发
安卓与iOS:技术演进的双城记
【10月更文挑战第16天】 在移动操作系统的世界里,安卓和iOS无疑是两个最重要的玩家。它们各自代表了不同的技术理念和市场策略,塑造了全球数亿用户的移动体验。本文将深入探讨这两个平台的发展历程、技术特点以及它们如何影响了我们的数字生活,旨在为读者提供一个全面而深入的视角,理解这两个操作系统背后的哲学和未来趋势。
36 2

热门文章

最新文章