Android平台RTMP直播推送模块技术接入说明

本文涉及的产品
视觉智能开放平台,视频资源包5000点
视觉智能开放平台,图像资源包5000点
视觉智能开放平台,分割抠图1万点
简介: 大牛直播SDK跨平台RTMP直播推送模块,始于2015年,支持Windows、Linux(x64_64架构|aarch64)、Android、iOS平台,支持采集推送摄像头、屏幕、麦克风、扬声器、编码前、编码后数据对接,功能强大,性能优异,配合大牛直播SDK的SmartPlayer播放器,轻松实现毫秒级的延迟体验,满足大多数行业的使用场景。RTMP直播推送模块数据源,支持编码前、编码后数据对接

技术背景

大牛直播SDK跨平台RTMP直播推送模块,始于2015年,支持Windows、Linux(x64_64架构|aarch64)、Android、iOS平台,支持采集推送摄像头、屏幕、麦克风、扬声器、编码前、编码后数据对接,功能强大,性能优异,配合大牛直播SDK的SmartPlayer播放器,轻松实现毫秒级的延迟体验,满足大多数行业的使用场景。

RTMP直播推送模块数据源,支持编码前、编码后数据对接:

  • 编码前数据(目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型);
  • 编码后数据(如无人机等264/HEVC数据,或者本地解析的MP4音视频数据)。

技术对接

系统要求

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

准备工作

  • 确保SmartPublisherJniV2.java放到com.daniulive.smartpublisher包名下(可在其他包名下调用);
  • smartavengine.jar加入到工程;
  • 拷贝libSmartPublisher.so到工程;
  • AndroidManifast.xml添加相关权限:
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />

image.gif

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

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">SmartPublisherSDKDemo</string>

image.gif

接口设计

Android 推送端SDK接口详解

调用描述

接口

接口描述

最先调用,如成功返回推送实例

SmartPublisherOpen

ctx:上下文信息;

Audio_opt:

0:不推送音频;

1:推送编码前音频(PCM);

2:对接外部编码后的audio数据(AAC/PCMA/PCMU/SPEEX)

video_opt:

0:不推送视频;

1:推送编码前视频(YUV420SP/YUV420P/RGBA/ARGB);

2:推送编码后视频(H.264)

3:层叠加模式

width|height:宽高信息。

Event回调

SetSmartPublisherEventCallbackV2

设置event callback

硬编码设置

SetSmartPublisherVideoHWEncoder

检测是否支持H.264硬编码,如果返回0,则支持,否则自动采用软编码

SetSmartPublisherVideoHevcHWEncoder

检测是否支持H.265(HEVC)硬编码,如果返回0,则支持,否则自动采用软编码

SetNativeMediaNDK

设置视频硬编码是否使用 Native Media NDK, 默认是不使用, 安卓5.0以下设备不支持

SetVideoHWEncoderBitrateMode

设置视频硬编码码率控制模式

hw_bitrate_mode: -1表示使用默认值, 不设置也会使用默认值, 0:CQ, 1:VBR, 2:CBR, 3:CBR_FD

SetVideoHWEncoderComplexity

设置视频硬编码复杂度, 安卓5.0及以上支持

SetVideoHWEncoderQuality

设置视频硬编码质量, 安卓9及以上支持, 仅当硬编码器码率控制模式(BitrateMode)是CQ(constant-quality mode)时才有效

SetAVCHWEncoderProfile

设置H.264硬编码Profile, 安卓7及以上支持

SetAVCHWEncoderLevel

设置H.264硬编码Level, 这个只有在设置了Profile的情况下才有效, 安卓7及以上支持

SetVideoHWEncoderMaxBitrate

设置视频硬编码最大码率, 安卓没有相关文档说明, 所以不建议设置

水印

文字、png水印

PostLayerBitmap

通过层模式设置水印,投递层

Bitmap.Config.ARGB_888图像

视频参数配置

软编码可变码率

SmartPublisherSetSwVBRMode

设置软编码可变码率,可变码率下,相邻帧之间变化不大时码率更低

GOP间隔(关键帧)

SmartPublisherSetGopInterval

设置推送端GOP间隔,一般建议在帧率的1~3倍,如不设置,用底层默认值

软编码码率设置

SmartPublisherSetSWVideoBitRate

设置软编码视频 bit-rate,最大码流一般是平均码流的2倍,如不设置,用底层计算的默认值

帧率

SmartPublisherSetFPS

设置fps,如不设置,用底层默认值

软编码视频Profile

SmartPublisherSetSWVideoEncoderProfile

设置软编码模式下的video encoder profile,默认baseline profile

软编码编码速度

SmartPublisherSetSWVideoEncoderSpeed

设置软编码编码速度,设置范围(1,6),1最快,6最慢,默认是6

频设置

视频镜像

SmartPublisherSetMirror

镜像模式: 播放端和推送端本地回显方向显示一致(前置摄像头)

视频截图

实时快照

CaptureImage

截图接口, 支持JPEG和PNG两种格式

音频配置

音频编码

类型

SmartPublisherSetAudioCodecType

设置编码类型,默认AAC编码,type设置为2时,启用speex编码(码率更低)

AAC编码码率

SmartPublisherSetAudioBitRate

设置音频编码码率, 当前只对AAC编码有效

SPEEX编码质量

SmartPublisherSetSpeexEncoderQuality

设置speex编码质量,数值越大,质量越高,范围(0,10),默认8

音频处理

噪音抑制

SmartPublisherSetNoiseSuppression

噪音抑制开启后,可去除采集端背景杂音

增益控制

SmartPublisherSetAGC

设置自动增益控制,保持声音稳定

回声消除

SmartPublisherSetEchoCancellation

设置音频回音消除

实时静音

SmartPublisherSetMute

设置实时静音、取消静音

设置输入

音量

SmartPublisherSetInputAudioVolume

设置输入音量,默认是1.0,范围是[0.0, 5.0], 设置成0静音, 1音量不变

RTMP推送模式

SetRtmpPublishingType

设置rtmp publisher类型,0:live,1:record,需服务器支持

Enhanced RTMP设置

DisableEnhancedRTMP

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

RTMP推送URL设置

SmartPublisherSetURL

设置RTMP推送url

编码前实时视频数据

camera数据

SmartPublisherOnCaptureVideoData

对接camera回调的数据

YV12数据

SmartPublisherOnYV12Data

YV12数据接口

NV21数据

SmartPublisherOnNV21Data

NV21数据接口

转换接口

SmartPublisherNV21ToI420Rotate

NV21转换到I420并旋转

YUV(I420)

SmartPublisherOnCaptureVideoI420Data

第三方YUV(I420)接口

RGB24数据

SmartPublisherOnCaptureVideoRGB24Data

RGB24接口

RGBA32数据

SmartPublisherOnCaptureVideoRGBA32Data

RGBA32接口

YUV420888数据

SmartPublisherOnImageYUV420888

YUV420888接口

RGBA数据

SmartPublisherOnCaptureVideoRGBAData

第三方RGBA数据

ABGR垂直翻转数据

SmartPublisherOnCaptureVideoABGRFlip

VerticalData

ABGR flip vertical(垂直翻转) 数据(Demo中用于传递屏幕数据)

RGBA8888图像

PostLayerImageRGBA8888ByteBuffer

投递层RGBA8888图像,如果不需要Aplpha通道的话, 请使用RGBX8888接口

RGBX8888图像

PostLayerImageRGBX8888ByteBuffer

投递层RGBX8888图像

I420图像

PostLayerImageI420ByteBuffer

投递层I420图像

RGB565数据

SmartPublisherOnCaptureVideoRGB565Data

RGB565 data

裁剪过的RGBA

数据

SmartPublisherOnCaptureVideoClipedRGBAData

投递裁剪过的RGBA数据

PCM数据

SmartPublisherOnPCMData

实时PCM数据

远端PCM数据

(用于回音消除)

SmartPublisherOnFarEndPCMData

实时传递远端PCM数据(可用于互动级的回音消除处理)

音频 混音

混音数据

SmartPublisherOnMixPCMData

传递PCM混音音频数据给SDK, 每10ms音频数据传入一次

编码后数据对接

编码后视频数据

SmartPublisherPostVideoEncodedData

设置编码后视频数据

编码后音频数据

SmartPublisherPostAudioEncodedData

编码后音频数据

编码后音视频数据回调

编码后音频数据回调

SmartPublisherSetAudioEncodedDataCallback

设置编码后音频数据回调

编码后视频数据回调

SmartPublisherSetVideoEncodedDataCallback

设置编码后视频数据回调

层结构设置

启用|停用视频层

EnableLayer

video_opt为3时,启用或者停用视频层, 这个接口必须在StartXXX之后调用.

移除视频层

RemoveLayer

移除视频层, 这个接口必须在StartXXX之后调用.

RTMP推送

开始推送

RTMP

SmartPublisherStartPublisher

启动RTMP推送

停止推送

RTMP

SmartPublisherStopPublisher

停止RTMP推送

关闭推送实例

关闭实例

SmartPublisherClose

关闭推送实例,结束时必须调用close接口释放资源

设置授权

授权license设置

SmartPublisherSetSDKClientKey

设置授权Key,如需设置授权Key, 请确保在SmartPublisherOpen之前调用!

功能支持

  • 音频编码:AAC/SPEEX;
  • 视频编码:H.264、H.265;
  • 推流协议:RTMP;
  • [音视频]支持纯音频/纯视频/音视频推送;
  • [摄像头]支持采集过程中,前后摄像头实时切换;
  • 支持帧率、关键帧间隔(GOP)、码率(bit-rate)设置;
  • 支持RTMP推送 live|record模式设置;
  • 支持前置摄像头镜像设置;
  • 支持软编码、特定机型硬编码;
  • 支持横屏、竖屏推送;
  • 支持Android屏幕采集推送;
  • 支持自建标准RTMP服务器或CDN;
  • 支持断网自动重连、网络状态回调;
  • 支持实时动态水印;
  • 支持实时快照;
  • 支持降噪处理、自动增益控制;
  • 支持外部编码前音视频数据对接;
  • 支持外部编码后音视频数据对接;
  • 支持RTMP扩展H.265(需设备支持H.265特定机型硬编码)和Enhanced RTMP;
  • 支持实时音量调节;
  • 支持扩展录像模块;
  • 支持Unity接口;
  • 支持H.264扩展SEI发送模块;
  • 支持Android 5.1及以上版本。

接口调用详解

本文以大牛直播SDK Android平台Camera2Demo为例,推送RTMP之前,可以先选择视频分辨率、软编还是硬编码,音频是AAC、SPEEX还是PCMA编码等基础设置,其他参数的设置,可以参考下面InitAndSetConfig()。

image.gif

以Android平台Camera2对接为例,onCreate()时,想new SmartPublisherJniV2():

/*
 * MainActivity.java
 * Author: daniusdk.com
 */
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    ...
    context_ = this.getApplicationContext();
    
    libPublisher = new SmartPublisherJniV2();
}

image.gif

推送RTMP:

class ButtonStartPushListener implements View.OnClickListener {
    public void onClick(View v) {
        if (stream_publisher_.is_rtmp_publishing()) {
            stopPush();
            btnRTMPPusher.setText("推送RTMP");
            return;
        }
        Log.i(TAG, "onClick start push rtmp..");
        
        InitAndSetConfig();
        String rtmp_pusher_url ="rtmp://192.168.0.101:1935/hls/stream123";;
        if (!stream_publisher_.SetURL(rtmp_pusher_url))
            Log.e(TAG, "Failed to set publish stream URL..");
        boolean start_ret = stream_publisher_.StartPublisher();
        if (!start_ret) {
            stream_publisher_.try_release();
            Log.e(TAG, "Failed to start push stream..");
            return;
        }
        startAudioRecorder();
        startLayerPostThread();
        btnRTMPPusher.setText("停止推送 ");
    }
}

image.gif

stopPush()实现如下:

//停止rtmp推送
private void stopPush() {
    stream_publisher_.StopPublisher();
    stream_publisher_.try_release();
    if (!stream_publisher_.is_publishing())
        stopAudioRecorder();
}

image.gif

其中,InitAndSetConfig()实现如下,通过调SmartPublisherOpen()接口,生成推送实例句柄。

/*
 * MainActivity.java
 * Author: daniusdk.com
 */
private void InitAndSetConfig() {
    if (null == libPublisher)
        return;
    if (!stream_publisher_.empty())
        return;
    Log.i(TAG, "InitAndSetConfig video width: " + video_width_ + ", height" + video_height_ + " imageRotationDegree:" + cameraImageRotationDegree_);
    int audio_opt = 1;
    long handle = libPublisher.SmartPublisherOpen(context_, audio_opt, 3,  video_width_, video_height_);
    if (0==handle) {
        Log.e(TAG, "sdk open failed!");
        return;
    }
    Log.i(TAG, "publisherHandle=" + handle);
    int fps = 25;
    int gop = fps * 3;
    initialize_publisher(libPublisher, handle, video_width_, video_height_, fps, gop);
    stream_publisher_.set(libPublisher, handle);
}

image.gif

对应的initialize_publisher()实现如下,设置软硬编码、帧率、关键帧间隔等。

private boolean initialize_publisher(SmartPublisherJniV2 lib_publisher, long handle, int width, int height, int fps, int gop) {
    if (null == lib_publisher) {
        Log.e(TAG, "initialize_publisher lib_publisher is null");
        return false;
    }
    if (0 == handle) {
        Log.e(TAG, "initialize_publisher handle is 0");
        return false;
    }
    if (videoEncodeType == 1) {
        int kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, true);
        Log.i(TAG, "h264HWKbps: " + kbps);
        int isSupportH264HWEncoder = lib_publisher.SetSmartPublisherVideoHWEncoder(handle, kbps);
        if (isSupportH264HWEncoder == 0) {
            lib_publisher.SetNativeMediaNDK(handle, 0);
            lib_publisher.SetVideoHWEncoderBitrateMode(handle, 1); // 0:CQ, 1:VBR, 2:CBR
            lib_publisher.SetVideoHWEncoderQuality(handle, 39);
            lib_publisher.SetAVCHWEncoderProfile(handle, 0x08); // 0x01: Baseline, 0x02: Main, 0x08: High
            // lib_publisher.SetAVCHWEncoderLevel(handle, 0x200); // Level 3.1
            // lib_publisher.SetAVCHWEncoderLevel(handle, 0x400); // Level 3.2
            // lib_publisher.SetAVCHWEncoderLevel(handle, 0x800); // Level 4
            lib_publisher.SetAVCHWEncoderLevel(handle, 0x1000); // Level 4.1 多数情况下,这个够用了
            //lib_publisher.SetAVCHWEncoderLevel(handle, 0x2000); // Level 4.2
            // lib_publisher.SetVideoHWEncoderMaxBitrate(handle, ((long)h264HWKbps)*1300);
            Log.i(TAG, "Great, it supports h.264 hardware encoder!");
        }
    } else if (videoEncodeType == 2) {
        int kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, false);
        Log.i(TAG, "hevcHWKbps: " + kbps);
        int isSupportHevcHWEncoder = lib_publisher.SetSmartPublisherVideoHevcHWEncoder(handle, kbps);
        if (isSupportHevcHWEncoder == 0) {
            lib_publisher.SetNativeMediaNDK(handle, 0);
            lib_publisher.SetVideoHWEncoderBitrateMode(handle, 1); // 0:CQ, 1:VBR, 2:CBR
            lib_publisher.SetVideoHWEncoderQuality(handle, 39);
            // libPublisher.SetVideoHWEncoderMaxBitrate(handle, ((long)hevcHWKbps)*1200);
            Log.i(TAG, "Great, it supports hevc hardware encoder!");
        }
    }
    boolean is_sw_vbr_mode = true;
    //H.264 software encoder
    if (is_sw_vbr_mode) {
        int is_enable_vbr = 1;
        int video_quality = LibPublisherWrapper.estimate_video_software_quality(width, height, true);
        int vbr_max_kbps = LibPublisherWrapper.estimate_video_vbr_max_kbps(width, height, fps);
        lib_publisher.SmartPublisherSetSwVBRMode(handle, is_enable_vbr, video_quality, vbr_max_kbps);
    }
    if (is_pcma_) {
        lib_publisher.SmartPublisherSetAudioCodecType(handle, 3);
    } else {
        lib_publisher.SmartPublisherSetAudioCodecType(handle, 1);
    }
    lib_publisher.SetSmartPublisherEventCallbackV2(handle, new EventHandlerPublisherV2().set(handler_, record_executor_));
    lib_publisher.SmartPublisherSetSWVideoEncoderProfile(handle, 3);
    lib_publisher.SmartPublisherSetSWVideoEncoderSpeed(handle, 2);
    lib_publisher.SmartPublisherSetGopInterval(handle, gop);
    lib_publisher.SmartPublisherSetFPS(handle, fps);
    // lib_publisher.SmartPublisherSetSWVideoBitRate(handle, 600, 1200);
    boolean is_noise_suppression = true;
    lib_publisher.SmartPublisherSetNoiseSuppression(handle, is_noise_suppression ? 1 : 0);
    boolean is_agc = false;
    lib_publisher.SmartPublisherSetAGC(handle, is_agc ? 1 : 0);
    int echo_cancel_delay = 0;
    lib_publisher.SmartPublisherSetEchoCancellation(handle, 1, echo_cancel_delay);
    return true;
}

image.gif

数据投递如下(以Camera2采集为例,如果是其他视频格式,也可以正常对接):

@Override
public void onCameraImageData(Image image) {
    ....
    for (LibPublisherWrapper i : publisher_array_)
        i.PostLayerImageYUV420888ByteBuffer(0, 0, 0,
            planes[0].getBuffer(), y_offset, planes[0].getRowStride(),
            planes[1].getBuffer(), u_offset, planes[1].getRowStride(),
            planes[2].getBuffer(), v_offset, planes[2].getRowStride(), planes[1].getPixelStride(),
            w, h, 0, 0,
            scale_w, scale_h, scale_filter_mode, rotation_degree);
}

image.gif

音频采集投递设计如下:

void startAudioRecorder() {
    if (audio_recorder_ != null)
        return;
    audio_recorder_ = new NTAudioRecordV2(this);
    Log.i(TAG, "startAudioRecorder call audio_recorder_.start()+++...");
    audio_recorder_callback_ = new NTAudioRecordV2CallbackImpl(stream_publisher_, null);
    audio_recorder_.AddCallback(audio_recorder_callback_);
    if (!audio_recorder_.Start(is_pcma_ ? 8000 : 44100, 1) ) {
        audio_recorder_.RemoveCallback(audio_recorder_callback_);
        audio_recorder_callback_ = null;
        audio_recorder_ = null;
        Log.e(TAG, "startAudioRecorder start failed.");
    }
    else {
        Log.i(TAG, "startAudioRecorder call audio_recorder_.start() OK---...");
    }
}
void stopAudioRecorder() {
    if (null == audio_recorder_)
        return;
    Log.i(TAG, "stopAudioRecorder+++");
    audio_recorder_.Stop();
    if (audio_recorder_callback_ != null) {
        audio_recorder_.RemoveCallback(audio_recorder_callback_);
        audio_recorder_callback_ = null;
    }
    audio_recorder_ = null;
    Log.i(TAG, "stopAudioRecorder---");
}

image.gif

回调Audio数据的地方,直接投递出去:

private static class NTAudioRecordV2CallbackImpl implements NTAudioRecordV2Callback {
    private WeakReference<LibPublisherWrapper> publisher_0_;
    public NTAudioRecordV2CallbackImpl(LibPublisherWrapper publisher_0) {
        if (publisher_0 != null)
            publisher_0_ = new WeakReference<>(publisher_0);
    }
    private final LibPublisherWrapper get_publisher_0() {
        if (publisher_0_ !=null)
            return publisher_0_.get();
        return null;
    }
    @Override
    public void onNTAudioRecordV2Frame(ByteBuffer data, int size, int sampleRate, int channel, int per_channel_sample_number) {
        LibPublisherWrapper publisher_0 = get_publisher_0();
        if (publisher_0 != null)
            publisher_0.OnPCMData(data, size, sampleRate, channel, per_channel_sample_number);
    }
}

image.gif

图层投递设计如下,图层投递的时候,可设置是否添加文字、图片动态水印:

private void startLayerPostThread() {
    if (layer_post_thread_ != null)
        return;
    layer_post_thread_ = new LayerPostThread(this.context_, publisher_array_);
    layer_post_thread_.start_post();
    update_layer_post_video_size();
    layer_post_thread_.enableText(isHasTextWatermark());
    layer_post_thread_.enablePicture(isHasPictureWatermark());
}
private void update_layer_post_video_size() {
    if (null == layer_post_thread_)
        return;
    int w, h;
    int degree = cameraImageRotationDegree_;
    if (degree < 0 ) {
        w = 0;
        h = 0;
    } else if (90 == degree || 270 == degree) {
        w = video_height_;
        h = video_width_;
    }else {
        w = video_width_;
        h = video_height_;
    }
    layer_post_thread_.update_video_size(w, h);
}
private void stopLayerPostThread() {
    if (layer_post_thread_ != null) {
        layer_post_thread_.stop_post();
        layer_post_thread_ = null;
    }
}

image.gif

如需摄像头快照,调用以下逻辑实现即可:

class ButtonCaptureImageListener implements View.OnClickListener {
    public void onClick(View v) {
        if (null == snap_shot_impl_) {
            snap_shot_impl_ = new SnapShotImpl(image_path_, context_, handler_, libPublisher, snap_shot_publisher_);
            snap_shot_impl_.start();
        }
        startLayerPostThread();
        snap_shot_impl_.set_layer_post_thread(layer_post_thread_);
        snap_shot_impl_.capture();
    }
}

image.gif

如需集成录像模块,开始录像、停止录像设计如下:

class ButtonStartRecorderListener implements View.OnClickListener {
    public void onClick(View v) {
        if (layer_post_thread_ != null)
            layer_post_thread_.update_layers();
        if (stream_publisher_.is_recording()) {
            stopRecorder();
            if (stream_publisher_.empty())
                ConfigControlEnable(true);
            btnStartRecorder.setText("实时录像");
            btnPauseRecorder.setText("暂停录像");
            btnPauseRecorder.setEnabled(false);
            isPauseRecording = true;
            return;
        }
        Log.i(TAG, "onClick start recorder..");
        InitAndSetConfig();
        ConfigRecorderParam();
        boolean start_ret = stream_publisher_.StartRecorder();
        if (!start_ret) {
            stream_publisher_.try_release();
            Log.e(TAG, "Failed to start recorder.");
            return;
        }
        startAudioRecorder();
        ConfigControlEnable(false);
        startLayerPostThread();
        btnStartRecorder.setText("停止录像");
        btnPauseRecorder.setEnabled(true);
        isPauseRecording = true;
    }
}

image.gif

录像参数配置实现如下:

void ConfigRecorderParam() {
    if (null == libPublisher)
        return;
    if (null == recDir || recDir.isEmpty())
        return;
    int ret = libPublisher.SmartPublisherCreateFileDirectory(recDir);
    if (ret != 0) {
        Log.e(TAG, "Create record dir failed, path:" + recDir);
        return;
    }
    if (!stream_publisher_.SetRecorderDirectory(recDir)) {
        Log.e(TAG, "Set record dir failed , path:" + recDir);
        return;
    }
    // 更细粒度控制录像的, 一般情况无需调用
    //libPublisher.SmartPublisherSetRecorderAudio(publisherHandle, 0);
    //libPublisher.SmartPublisherSetRecorderVideo(publisherHandle, 0);
    if (!stream_publisher_.SetRecorderFileMaxSize(200)) {
        Log.e(TAG, "SmartPublisherSetRecorderFileMaxSize failed.");
        return;
    }
}

image.gif

暂停录像、恢复录像设计如下:

class ButtonPauseRecorderListener implements View.OnClickListener {
    public void onClick(View v) {
        if (stream_publisher_.is_recording()) {
            if (isPauseRecording) {
                boolean ret = stream_publisher_.PauseRecorder(true);
                if (ret) {
                    isPauseRecording = false;
                    btnPauseRecorder.setText("恢复录像");
                } else {
                    Log.e(TAG, "Pause recorder failed..");
                }
            } else {
                boolean ret = stream_publisher_.PauseRecorder(false);
                if (ret) {
                    isPauseRecording = true;
                    btnPauseRecorder.setText("暂停录像");
                } else {
                    Log.e(TAG, "Resume recorder failed..");
                }
            }
        }
    }
}

image.gif

Event回调实现如下:

private static class EventHandlerPublisherV2 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 publisher_event = "";
        switch (id) {
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_STARTED:
                publisher_event = "开始..";
                break;
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTING:
                publisher_event = "连接中..";
                break;
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTION_FAILED:
                publisher_event = "连接失败..";
                break;
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTED:
                publisher_event = "连接成功..";
                break;
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_DISCONNECTED:
                publisher_event = "连接断开..";
                break;
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_STOP:
                publisher_event = "关闭..";
                break;
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RECORDER_START_NEW_FILE:
                publisher_event = "开始一个新的录像文件 : " + param3;
                break;
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_ONE_RECORDER_FILE_FINISHED:
                if (record_executor_ != null) {
                    RecordExecutorService executor = record_executor_.get();
                    if (executor != null) {
                        RecordFileFinishedHandler file_finished_handler = new RecordFileFinishedHandler().set(handle, param3, param1);
                        if (param2 > 0)
                            file_finished_handler.set_begin_time(param2);
                        executor.execute(file_finished_handler);
                    }
                }
                publisher_event = "已生成一个录像文件 : " + param3;
                break;
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_SEND_DELAY:
                publisher_event = "发送时延: " + param1 + " 帧数:" + param2;
                break;
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CAPTURE_IMAGE:
                publisher_event = "快照: " + param1 + " 路径:" + param3;
                if (0 == param1)
                    publisher_event = publisher_event + "截取快照成功.." + ", 用户数据:" + param4;
                 else
                    publisher_event = publisher_event + "截取快照失败..";
                break;
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:
                publisher_event = "RTSP服务URL: " + param3;
                break;
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUSH_RTSP_SERVER_RESPONSE_STATUS_CODE:
                publisher_event ="RTSP status code received, codeID: " + param1 + ", RTSP URL: " + param3;
                break;
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUSH_RTSP_SERVER_NOT_SUPPORT:
                publisher_event ="服务器不支持RTSP推送, 推送的RTSP URL: " + param3;
                break;
        }
        String str = "当前回调状态:" + publisher_event;
        Log.i(TAG, str);
        if (handler_ != null) {
            android.os.Handler handler = handler_.get();
            if (handler != null) {
                Message message = new Message();
                message.what = PUBLISHER_EVENT_MSG;
                message.obj = publisher_event;
                handler.sendMessage(message);
            }
        }
    }
    public NTSmartEventCallbackV2 set(android.os.Handler handler, RecordExecutorService record_executor) {
        this.handler_ = new WeakReference<>(handler);
        this.record_executor_ = new WeakReference<>(record_executor);
        return this;
    }
    private WeakReference<android.os.Handler> handler_;
    private WeakReference<RecordExecutorService> record_executor_;
}

image.gif

onDestroy() 的时候,调用stopPush()即可,如果有录像和快照,都停掉,此外,停掉图层投递线程,并关闭camera:

@Override
protected void onDestroy() {
    Log.i(TAG, "activity destory!");
    record_executor_.cancel_tasks();
    stopAudioRecorder();
    if (snap_shot_impl_ != null) {
        snap_shot_impl_.stop();
        snap_shot_impl_ = null;
    }
    snap_shot_publisher_.release();
    stopPush();
    stopRecorder();
    stream_publisher_.release();
    stopLayerPostThread();
    if (camera2Helper != null) {
        camera2Helper.release();
    }
    if (!record_executor_.shutdown(60, TimeUnit.SECONDS))
        Log.w(TAG, "call record_executor_.shutdown failed");
    super.onDestroy();
}

image.gif

总结

以上是大牛直播SDK的Android平台RTMP直播推送模块详细的对接说明,除了可以对接编码前各种类型的音视频数据外,模块还支持对接编码后音视频数据,并实现本地录像、快照等功能,除支持H.264外,RTMP推送模块还支持扩展H.265和Enhanced RTMP。感兴趣的开发者,可以单独跟我们探讨。

相关文章
|
6天前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
7天前
|
安全 Android开发 数据安全/隐私保护
探索安卓与iOS的安全性差异:技术深度分析与实践建议
本文旨在深入探讨并比较Android和iOS两大移动操作系统在安全性方面的不同之处。通过详细的技术分析,揭示两者在架构设计、权限管理、应用生态及更新机制等方面的安全特性。同时,针对这些差异提出针对性的实践建议,旨在为开发者和用户提供增强移动设备安全性的参考。
|
9天前
|
安全 Java Android开发
掌握安卓与iOS应用开发中的关键技术
本文深入探讨了安卓和iOS平台上应用开发的关键性技术,包括平台特性、开发工具选择、性能优化技巧及跨平台开发的可行性分析。通过对比两种平台的开发环境与实践案例,旨在为开发者提供全面的视角以理解和把握移动应用开发的核心技术。无论是安卓的Java与Kotlin之争,还是iOS的Swift语言革命,本文都将一一解析其优势与应用场景,帮助开发者在技术选型上有更明智的决策。此外,文章还将触及到当前流行的跨平台框架如React Native和Flutter,评估它们在项目实施中的实用性和限制,为有意进行多平台同步开发的团队提供参考。通过对这些关键技术的梳理,本文期望能够启发开发者深化对移动平台开发的理解,并
|
7天前
|
开发工具 Android开发 iOS开发
安卓与iOS开发环境对比:选择适合你的平台
【9月更文挑战第26天】在移动应用开发的广阔天地中,安卓和iOS是两大巨头。它们各自拥有独特的优势和挑战,影响着开发者的选择和决策。本文将深入探讨这两个平台的开发环境,帮助你理解它们的核心差异,并指导你根据个人或项目需求做出明智的选择。无论你是初学者还是资深开发者,了解这些平台的异同都至关重要。让我们一起探索,找到最适合你的那片开发天地。
|
9天前
|
Android开发 开发者
Android平台无纸化同屏如何实现实时录像功能
Android平台无纸化同屏,如果需要本地录像的话,实现难度不大,只要复用之前开发的录像模块的就可以,对我们来说,同屏采集这块,只是数据源不同而已,如果是自采集的其他数据,我们一样可以编码录像。
|
9天前
|
安全 API 开发工具
Android平台RTMP推送|轻量级RTSP服务如何实现麦克风|扬声器声音采集切换
Android平台扬声器播放声音的采集,在无纸化同屏等场景下,意义很大,早期低版本的Android设备,是没法直接采集扬声器audio的(从Android 10开始支持),所以,如果需要采集扬声器audio,需要先做系统版本判断,添加相应的权限。
|
9天前
|
编解码 开发工具 Android开发
Android平台实现屏幕录制(屏幕投影)|音频播放采集|麦克风采集并推送RTMP或轻量级RTSP服务
Android平台屏幕采集、音频播放声音采集、麦克风采集编码打包推送到RTMP和轻量级RTSP服务的相关技术实现,做成高稳定低延迟的同屏系统,还需要有配套好的RTMP、RTSP直播播放器
|
开发工具 Android开发
Android 7.1 使用mmm编译模块失败
Android 7.1 使用mmm编译模块失败
283 0
|
Android开发
Android不编译某个模块
Android 5.1 源码,编译相关的文件一般在build目录下build/target/product 放了很多mk文件;一般不同的产品会有不同的目录 假设我不想编译OpenWnn,在build目录下grep一下“OpenWnn”target/product/full_base.
1414 0
|
3天前
|
开发框架 移动开发 Android开发
安卓与iOS开发中的跨平台解决方案:Flutter入门
【9月更文挑战第30天】在移动应用开发的广阔舞台上,安卓和iOS两大操作系统各自占据半壁江山。开发者们常常面临着选择:是专注于单一平台深耕细作,还是寻找一种能够横跨两大系统的开发方案?Flutter,作为一种新兴的跨平台UI工具包,正以其现代、响应式的特点赢得开发者的青睐。本文将带你一探究竟,从Flutter的基础概念到实战应用,深入浅出地介绍这一技术的魅力所在。
18 7
下一篇
无影云桌面