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。感兴趣的开发者,可以单独跟我们探讨。

相关文章
|
26天前
|
安全 Android开发 iOS开发
Android vs. iOS:构建生态差异与技术较量的深度剖析###
本文深入探讨了Android与iOS两大移动操作系统在构建生态系统上的差异,揭示了它们各自的技术优势及面临的挑战。通过对比分析两者的开放性、用户体验、安全性及市场策略,本文旨在揭示这些差异如何塑造了当今智能手机市场的竞争格局,为开发者和用户提供决策参考。 ###
|
1月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
109 1
|
22天前
|
安全 Android开发 iOS开发
安卓与iOS的较量:技术深度对比
【10月更文挑战第18天】 在智能手机操作系统领域,安卓和iOS无疑是两大巨头。本文将深入探讨这两种系统的技术特点、优势以及它们之间的主要差异,帮助读者更好地理解这两个平台的独特之处。
37 0
|
12天前
|
安全 搜索推荐 Android开发
揭秘安卓与iOS系统的差异:技术深度对比
【10月更文挑战第27天】 本文深入探讨了安卓(Android)与iOS两大移动操作系统的技术特点和用户体验差异。通过对比两者的系统架构、应用生态、用户界面、安全性等方面,揭示了为何这两种系统能够在市场中各占一席之地,并为用户提供不同的选择。文章旨在为读者提供一个全面的视角,理解两种系统的优势与局限,从而更好地根据自己的需求做出选择。
28 2
|
14天前
|
安全 搜索推荐 Android开发
揭秘iOS与安卓系统的差异:一场技术与哲学的较量
在智能手机的世界里,iOS和Android无疑是两大巨头,它们不仅定义了操作系统的标准,也深刻影响了全球数亿用户的日常生活。本文旨在探讨这两个平台在设计理念、用户体验、生态系统及安全性等方面的本质区别,揭示它们背后的技术哲学和市场策略。通过对比分析,我们将发现,选择iOS或Android,不仅仅是选择一个操作系统,更是选择了一种生活方式和技术信仰。
|
19天前
|
安全 Android开发 iOS开发
iOS与安卓:技术生态的双雄争霸
在当今数字化时代,智能手机操作系统的竞争愈发激烈。iOS和安卓作为两大主流平台,各自拥有独特的技术优势和市场地位。本文将从技术架构、用户体验、安全性以及开发者支持四个方面,深入探讨iOS与安卓之间的差异,并分析它们如何塑造了今天的移动技术生态。无论是追求极致体验的苹果用户,还是享受开放自由的安卓粉丝,了解这两大系统的内在逻辑对于把握未来趋势至关重要。
|
20天前
|
安全 搜索推荐 Android开发
揭秘iOS与Android系统的差异:一场技术与哲学的较量
在当今数字化时代,智能手机操作系统的选择成为了用户个性化表达和技术偏好的重要标志。iOS和Android,作为市场上两大主流操作系统,它们之间的竞争不仅仅是技术的比拼,更是设计理念、用户体验和生态系统构建的全面较量。本文将深入探讨iOS与Android在系统架构、应用生态、用户界面及安全性等方面的本质区别,揭示这两种系统背后的哲学思想和市场策略,帮助读者更全面地理解两者的优劣,从而做出更适合自己的选择。
|
24天前
|
安全 Android开发 iOS开发
安卓vs iOS:探索两种操作系统的独特魅力与技术深度###
【10月更文挑战第16天】 本文旨在深入浅出地探讨安卓(Android)与iOS这两种主流移动操作系统的特色、优势及背后的技术理念。通过对比分析,揭示它们各自如何塑造了移动互联网的生态,并为用户提供丰富多彩的智能体验。无论您是科技爱好者还是普通用户,都能从这篇文章中感受到技术创新带来的无限可能。 ###
46 2
|
24天前
|
机器学习/深度学习 人工智能 Android开发
安卓与iOS:技术演进的双城记
【10月更文挑战第16天】 在移动操作系统的世界里,安卓和iOS无疑是两个最重要的玩家。它们各自代表了不同的技术理念和市场策略,塑造了全球数亿用户的移动体验。本文将深入探讨这两个平台的发展历程、技术特点以及它们如何影响了我们的数字生活,旨在为读者提供一个全面而深入的视角,理解这两个操作系统背后的哲学和未来趋势。
32 2
|
27天前
|
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开发知识可参考相关书籍。
75 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库

热门文章

最新文章