技术背景
大牛直播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
*/
@Override
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采集为例,如果是其他视频格式,也可以正常对接):
@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);
}
音频采集投递设计如下:
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;
}
@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);
}
}
图层投递设计如下,图层投递的时候,可设置是否添加文字、图片动态水印:
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 {
@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_;
}
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();
}
总结
以上是大牛直播SDK的Android平台RTMP直播推送模块详细的对接说明,除了可以对接编码前各种类型的音视频数据外,模块还支持对接编码后音视频数据,并实现本地录像、快照等功能,除支持H.264外,RTMP推送模块还支持扩展H.265和Enhanced RTMP。感兴趣的开发者,可以单独跟我们探讨。