技术背景
在前面的blog,我们发布了Android平台轻量级RTSP服务模块的技术对接说明,好多开发者希望,更黑盒的对接轻量级RTSP服务这块,专注于自身业务逻辑。为此,我们针对Android平台轻量级RTSP服务模块,做了更进一步的封装(LibPublisherWrapper.java)。
技术对接
本文还是以大牛直播SDK Android平台Camera2Demo为例,如果需要测试轻量级RTSP服务模块,分两步:首先启动RTSP服务,RTSP服务启动后,发布RTSP即可,在此之前,可选择设置视频分辨率、软硬编码类型,音频编码类型等,如需关闭轻量级RTSP服务,先停止发布RTSP流,再停止RTSP服务。
onCreate()的时候,调用LibPublisherWrapper的initialize_sdk():
/* * MainActivity.java * Author: daniusdk.com */ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... context_ = this.getApplicationContext(); libPublisher = new SmartPublisherJniV2(); LibPublisherWrapper.RTSPServer.initialize_sdk(libPublisher, context_); }
对应封装代码如下:
public static boolean initialize_sdk(SmartPublisherJniV2 lib_publisher, android.content.Context context) { return sdk_context_.initialize(lib_publisher, context); }
initalize()具体实现如下:
/* * LibPublisherWrapper.java * Author: daniusdk.com */ public boolean initialize(SmartPublisherJniV2 lib_publisher, android.content.Context context) { if (initialized_) return initialized_result_; if (null == lib_publisher) return false; if (null == context) return false; synchronized (this) { if (initialized_) return initialized_result_; try { int sdk_ret = lib_publisher.InitRtspServer(context); if (0 == sdk_ret) initialized_result_ = true; else { initialized_result_ = false; Log.e(TAG, "call sdk InitRtspServer failed, ret:" + sdk_ret); } } catch (Exception e) { initialized_result_ = false; Log.e(TAG, "call sdk InitRtspServer Exception:", e); } initialized_ = true; return initialized_result_; } }
启动、停止RTSP服务:
//启动/停止RTSP服务 class ButtonRtspServiceListener implements View.OnClickListener { public void onClick(View v) { if (!rtsp_server_.empty()) { rtsp_server_.reset(); btnRtspService.setText("启动RTSP服务"); btnRtspPublisher.setEnabled(false); return; } Log.i(TAG, "onClick start rtsp service.."); int port = 8554; String user_name = null; String password = null; LibPublisherWrapper.RTSPServer.Handle server_handle = LibPublisherWrapper.RTSPServer.create_and_start_server(libPublisher, port, user_name, password); if (null == server_handle) { Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!"); return; } rtsp_server_.reset(server_handle); btnRtspService.setText("停止RTSP服务"); btnRtspPublisher.setEnabled(true); } }
发布、停止RTSP流:
//发布/停止RTSP流 class ButtonRtspPublisherListener implements View.OnClickListener { public void onClick(View v) { if (stream_publisher_.is_rtsp_publishing()) { stopRtspPublisher(); btnRtspPublisher.setText("发布RTSP流"); btnGetRtspSessionNumbers.setEnabled(false); btnRtspService.setEnabled(true); return; } Log.i(TAG, "onClick start rtsp publisher.."); InitAndSetConfig(); String rtsp_stream_name = "stream1"; stream_publisher_.SetRtspStreamName(rtsp_stream_name); stream_publisher_.ClearRtspStreamServer(); stream_publisher_.AddRtspStreamServer(rtsp_server_.get_native()); if (!stream_publisher_.StartRtspStream()) { stream_publisher_.try_release(); Log.e(TAG, "调用发布rtsp流接口失败!"); return; } startAudioRecorder(); startLayerPostThread(); btnRtspPublisher.setText("停止RTSP流"); btnGetRtspSessionNumbers.setEnabled(true); btnRtspService.setEnabled(false); } }
stopRtspPublisher()实现如下:
//停止发布RTSP流 private void stopRtspPublisher() { stream_publisher_.StopRtspStream(); 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; }
发布RTSP流成功后,会回调上来可供拉流的RTSP URL:
private static class EventHandlerPublisherV2 implements NTSmartEventCallbackV2 { public void onNTSmartEventCallbackV2(long handle, int id, long param1, long param2, String param3, String param4, Object param5) { switch (id) { ... case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL: publisher_event = "RTSP服务URL: " + param3; break; } } }
获取RTSP Session会话数:
//获取RTSP会话数 class ButtonGetRtspSessionNumbersListener implements View.OnClickListener { public void onClick(View v) { if (rtsp_server_.is_running()) { int session_numbers = rtsp_server_.get_client_session_number(); Log.i(TAG, "GetRtspSessionNumbers: " + session_numbers); PopRtspSessionNumberDialog(session_numbers); } } }
对应的Wrapper封装代码如下:
public int get_client_session_number() { if (!is_running()) return 0; if (null == lib_publisher_) return 0; long handle = native_handle_.get(); if (0 == handle) return 0; try { int ret = lib_publisher_.GetRtspServerClientSessionNumbers(handle); return ret; } catch (Exception e) { Log.e(TAG, "RTSPServer.Handle.get_client_session_number Exception:", e); return 0; } }
数据投递如下(以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_; private WeakReference<LibPublisherWrapper> publisher_1_; 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); } }
onDestroy() 的时候,调用deinitialize_sdk()即可:
protected void onDestroy() { Log.i(TAG, "activity destory!"); stopAudioRecorder(); stopRtspPublisher(); stream_publisher_.release(); rtsp_server_.reset(); //如已启用内置服务功能(InitRtspServer),调用UnInitRtspServer, 注意,即便是启动多个RTSP服务,也只需调用UnInitRtspServer一次 LibPublisherWrapper.RTSPServer.deinitialize_sdk(libPublisher); stopLayerPostThread(); if (camera2Helper != null) { camera2Helper.release(); } super.onDestroy(); }
总结
Android平台轻量级RTSP服务模块二次封装版,相对接口版,对接更方便。开发者无需关注底层实现,可以更方便的对接到自己的业务系统。感兴趣的开发者,可以单独跟我们探讨。