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

本文涉及的产品
视觉智能开放平台,图像通用资源包5000点
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,视频通用资源包5000点
简介: 大牛直播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。感兴趣的开发者,可以单独跟我们探讨。

相关文章
|
9月前
|
JavaScript 前端开发 Android开发
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
293 13
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
4月前
|
监控 Android开发 数据安全/隐私保护
批量发送短信的平台,安卓群发短信工具插件脚本,批量群发短信软件【autojs版】
这个Auto.js脚本实现了完整的批量短信发送功能,包含联系人管理、短信内容编辑、发送状态监控等功能
|
4月前
|
API Android开发 数据安全/隐私保护
|
8月前
|
存储 编解码 监控
Android平台GB28181执法记录仪技术方案与实现
本文介绍了大牛直播SDK的SmartGBD在执法记录仪场景中的应用。GB28181协议作为视频监控联网的国家标准,为设备互联互通提供规范。SmartGBD专为Android平台设计,支持音视频采集、编码与传输,具备自适应算法和多功能扩展优势。文章分析了执法记录仪的需求,如实时音视频传输、设备管理及数据安全,并详细阐述了基于SmartGBD的技术实现方案,包括环境准备、SDK集成、设备注册、音视频处理及功能扩展等步骤。最后展望了SmartGBD在未来智慧物联领域的广阔应用前景。
355 13
|
8月前
|
存储 编解码 开发工具
Android平台毫秒级低延迟HTTP-FLV直播播放器技术探究与实现
本文详细探讨了在Android平台上实现HTTP-FLV播放器的过程。首先介绍了FLV格式的基础,包括文件头和标签结构。接着分析了HTTP-FLV传输原理,通过分块传输实现流畅播放。然后重点讲解了播放器的实现步骤,涵盖网络请求、数据解析、音视频解码与渲染,以及播放控制功能的设计。文章还讨论了性能优化和网络异常处理的方法,并总结了HTTP-FLV播放器的技术价值,尤其是在特定场景下的应用意义。
325 11
|
8月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
9月前
|
安全 Android开发 iOS开发
escrcpy:【技术党必看】Android开发,Escrcpy 让你无线投屏新体验!图形界面掌控 Android,30-120fps 超流畅!🔥
escrcpy 是一款基于 Scrcpy 的开源项目,使用 Electron 构建,提供图形化界面来显示和控制 Android 设备。它支持 USB 和 Wi-Fi 连接,帧率可达 30-120fps,延迟低至 35-70ms,启动迅速且画质清晰。escrcpy 拥有丰富的功能,包括自动化任务、多设备管理、反向网络共享、批量操作等,无需注册账号或广告干扰。适用于游戏直播、办公协作和教育演示等多种场景,是一款轻量级、高性能的 Android 控制工具。
677 1
|
Android开发
Android不编译某个模块
Android 5.1 源码,编译相关的文件一般在build目录下build/target/product 放了很多mk文件;一般不同的产品会有不同的目录 假设我不想编译OpenWnn,在build目录下grep一下“OpenWnn”target/product/full_base.
1552 0
|
开发工具 Android开发
Android 7.1 使用mmm编译模块失败
Android 7.1 使用mmm编译模块失败
530 0
|
17天前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
129 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡

热门文章

最新文章