技术背景
大牛直播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" />
- Load相关so:
static { System.loadLibrary("SmartPublisher"); }
- 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 } }
- 如需集成到自己系统测试,请用大牛直播SDK的app name,授权版按照授权app name正常使用即可;
- 如何改app-name,strings.xml做以下修改:
<string name="app_name">SmartPublisherSDKDemo</string>
接口设计
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()。
以Android平台Camera2对接为例,onCreate()时,想new SmartPublisherJniV2():
/* * MainActivity.java * Author: daniusdk.com */ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... context_ = this.getApplicationContext(); libPublisher = new SmartPublisherJniV2(); }
推送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("停止推送 "); } }
stopPush()实现如下:
//停止rtmp推送 private void stopPush() { stream_publisher_.StopPublisher(); stream_publisher_.try_release(); if (!stream_publisher_.is_publishing()) stopAudioRecorder(); }
其中,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); }
对应的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; }
数据投递如下(以Camera2采集为例,如果是其他视频格式,也可以正常对接):
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); }
音频采集投递设计如下:
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---"); }
回调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; } 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); } }
图层投递设计如下,图层投递的时候,可设置是否添加文字、图片动态水印:
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; } }
如需摄像头快照,调用以下逻辑实现即可:
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(); } }
如需集成录像模块,开始录像、停止录像设计如下:
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; } }
录像参数配置实现如下:
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; } }
暂停录像、恢复录像设计如下:
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.."); } } } } }
Event回调实现如下:
private static class EventHandlerPublisherV2 implements NTSmartEventCallbackV2 { 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_; }
onDestroy() 的时候,调用stopPush()即可,如果有录像和快照,都停掉,此外,停掉图层投递线程,并关闭camera:
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(); }
总结
以上是大牛直播SDK的Android平台RTMP直播推送模块详细的对接说明,除了可以对接编码前各种类型的音视频数据外,模块还支持对接编码后音视频数据,并实现本地录像、快照等功能,除支持H.264外,RTMP推送模块还支持扩展H.265和Enhanced RTMP。感兴趣的开发者,可以单独跟我们探讨。