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

本文涉及的产品
视觉智能开放平台,视频资源包5000点
视觉智能开放平台,图像资源包5000点
视觉智能开放平台,分割抠图1万点
简介: 大牛直播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做过一些技术方面的探讨,从低延迟、音视频同步处理、多实例实现、解码效率、性能占用、解码后数据对接、实时截图、录像、网络抖动处理等各个维度,做过相关的技术分享。感兴趣的开发者,可以单独跟我们探讨。

相关文章
|
3天前
|
安全 Android开发 iOS开发
Android vs. iOS:构建生态差异与技术较量的深度剖析###
本文深入探讨了Android与iOS两大移动操作系统在构建生态系统上的差异,揭示了它们各自的技术优势及面临的挑战。通过对比分析两者的开放性、用户体验、安全性及市场策略,本文旨在揭示这些差异如何塑造了当今智能手机市场的竞争格局,为开发者和用户提供决策参考。 ###
|
13天前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
51 1
|
1天前
|
安全 Android开发 iOS开发
安卓vs iOS:探索两种操作系统的独特魅力与技术深度###
【10月更文挑战第16天】 本文旨在深入浅出地探讨安卓(Android)与iOS这两种主流移动操作系统的特色、优势及背后的技术理念。通过对比分析,揭示它们各自如何塑造了移动互联网的生态,并为用户提供丰富多彩的智能体验。无论您是科技爱好者还是普通用户,都能从这篇文章中感受到技术创新带来的无限可能。 ###
9 2
|
1天前
|
机器学习/深度学习 人工智能 Android开发
安卓与iOS:技术演进的双城记
【10月更文挑战第16天】 在移动操作系统的世界里,安卓和iOS无疑是两个最重要的玩家。它们各自代表了不同的技术理念和市场策略,塑造了全球数亿用户的移动体验。本文将深入探讨这两个平台的发展历程、技术特点以及它们如何影响了我们的数字生活,旨在为读者提供一个全面而深入的视角,理解这两个操作系统背后的哲学和未来趋势。
9 2
|
4天前
|
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开发知识可参考相关书籍。
22 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
7天前
|
Java Android开发 Swift
掌握安卓与iOS应用开发:技术比较与选择指南
在移动应用开发领域,谷歌的安卓和苹果的iOS系统无疑是两大巨头。它们不仅塑造了智能手机市场,还影响了开发者的日常决策。本文深入探讨了安卓与iOS平台的技术差异、开发环境及工具、以及市场表现和用户基础。通过对比分析,旨在为开发者提供实用的指导,帮助他们根据项目需求、预算限制和性能要求,做出最合适的平台选择。无论是追求高度定制的用户体验,还是期望快速进入市场,本文都将为您的开发旅程提供有价值的见解。
|
15天前
|
IDE Android开发 iOS开发
探索安卓与iOS系统的技术差异:开发者的视角
本文深入分析了安卓(Android)与苹果iOS两大移动操作系统在技术架构、开发环境、用户体验和市场策略方面的主要差异。通过对比这两种系统的不同特点,旨在为移动应用开发者提供有价值的见解,帮助他们在不同平台上做出更明智的开发决策。
|
2天前
|
Android开发 Swift 数据安全/隐私保护
安卓与iOS的较量:一场永无止境的技术竞赛
【10月更文挑战第14天】 在智能手机操作系统的战场上,安卓和iOS一直是两大主角。它们各自拥有独特的优势和特性,吸引了全球数以亿计的用户。本文将深入探讨这两个系统的发展历程、技术特点以及它们之间的竞争关系,带您领略这场永无止境的技术竞赛的魅力。
10 0
|
10天前
|
Web App开发 编解码 视频直播
视频直播技术干货(十二):从入门到放弃,快速学习Android端直播技术
本文详细介绍了Android端直播技术的全貌,涵盖了从实时音视频采集、编码、传输到解码与播放的各个环节。文章还探讨了直播中音视频同步、编解码器选择、传输协议以及直播延迟优化等关键问题。希望本文能为你提供有关Andriod端直播技术的深入理解和实践指导。
15 0
|
17天前
|
安全 Unix Android开发
探索安卓与iOS的安全性差异:技术对比与未来展望
本文旨在深入探讨和比较安卓(Android)与iOS两大移动操作系统在安全性方面的不同之处。随着智能手机在日常生活中扮演着越来越重要的角色,了解这两个系统的安全特性变得尤为重要。通过分析它们的架构、安全机制、隐私保护措施以及更新策略等方面的差异,我们可以更好地理解它们各自的优势和劣势。此外,文章还将展望未来可能的发展趋势,为开发者和用户提供参考。
26 0