技術背景
我們在對接Unity平臺camera場景采集的時候,除了常規的RTMP推送、錄像外,還有一些開發者,需要能實現輕量級RTSP服務,對外提供個拉流的RTSP URL。
目前我們在Windows平臺Unity下數據源可采集到以下部分:
- 采集Unity camera場景;
- 采集攝像頭;
- 采集屏幕;
- 采集Unity聲音;
- 采集麥克風;
- 采集揚聲器;
- Unity PCM混音;
對外提供的技術能力有:
- RTMP直播推送;
- 輕量級RTSP服務;
- 實時錄像、暫停|恢復錄像;
- 實時預覽。
以下錄制下來的MP4文件是采集Unity camera場景,音頻是unity聲音。
Unity平臺實現camera場景實時錄像
技術實現
實際上,在實現Unity平臺音視頻能力之前,我們原生模塊已經有非常成熟的技術積累,Unity下還是調用的原生的推送模塊,不同的是,數據源需要采集Unity的audio、video,然后高效的投遞到底層模塊,底層模塊負責編碼打包,并投遞到RTMP或RTSP服務。
先說支持的音視頻類型:
public void SelVideoPushType(int type){switch (type){case 0:video_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_VIDEO_OPTION.NT_PB_E_VIDEO_OPTION_LAYER; //采集Unity窗體break;case 1:video_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_VIDEO_OPTION.NT_PB_E_VIDEO_OPTION_CAMERA; //采集攝像頭break;case 2:video_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_VIDEO_OPTION.NT_PB_E_VIDEO_OPTION_SCREEN; //采集屏幕break;case 3:video_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_VIDEO_OPTION.NT_PB_E_VIDEO_OPTION_NO_VIDEO; //不采集視頻break;}Debug.Log("SelVideoPushType type: " + type + " video_push_type: " + video_push_type_);}public void SelAudioPushType(int type){switch (type){case 0:audio_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_EXTERNAL_PCM_DATA; //采集Unity聲音break;case 1:audio_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_CAPTURE_MIC; //采集麥克風break;case 2:audio_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_CAPTURE_SPEAKER; //采集揚聲器break;case 3:audio_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_TWO_EXTERNAL_PCM_MIXER; //兩路Unity AudioClip混音測試break;case 4:audio_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_NO_AUDIO; //不采集音頻break;}Debug.Log("SelAudioPushType type: " + type + " audio_push_type: " + audio_push_type_);}
采集音視頻數據:
private void StartCaptureAvData(){if (audio_push_type_ == (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_EXTERNAL_PCM_DATA|| audio_push_type_ == (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_TWO_EXTERNAL_PCM_MIXER){PostUnityAudioClipData();}textures_poll_ = new TexturesPool();post_image_worker_ = new PostImageWorker(textures_poll_, publisher_wrapper_);post_worker_thread_ = new Thread(post_image_worker_.run);post_worker_thread_.Start();}private void StopCaptureAvData(){if (post_image_worker_ != null){post_image_worker_.requestStop();post_image_worker_ = null;}if (post_worker_thread_ != null){post_worker_thread_.Join();post_worker_thread_ = null;}if (textures_poll_ != null){textures_poll_.clear();textures_poll_ = null;}StopAudioSource();}
RTMP推送:
public void btn_start_rtmp_pusher_Click(){if (publisher_wrapper_.IsPushingRtmp()){StopPushRTMP();btn_rtmp_pusher_.GetComponentInChildren<Text>().text = "推送RTMP";return;}String url = rtmp_pusher_url_.text;if (url.Length < 8){publisher_wrapper_.Close();Debug.LogError("請輸入RTMP推送地址");return;}if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsRecording()){publisher_wrapper_.SetVideoPushType(video_push_type_);publisher_wrapper_.SetAudioPushType(audio_push_type_);}if (!publisher_wrapper_.StartRtmpPusher(url)){Debug.LogError("調用StartPublisher失敗..");return;}btn_rtmp_pusher_.GetComponentInChildren<Text>().text = "停止推送";if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsRecording()){StartCaptureAvData();coroutine_ = StartCoroutine(OnPostVideo());}}
輕量級RTSP服務相關調用:
public void btn_rtsp_service_Click(){if (publisher_wrapper_.IsRtspServiceRunning()){publisher_wrapper_.StopRtspService();btn_rtsp_service_.GetComponentInChildren<Text>().text = "啟動RTSP服務";btn_rtsp_publisher_.interactable = false;return;}if (!publisher_wrapper_.StartRtspService()){Debug.LogError("調用StartRtspService失敗..");return;}btn_rtsp_publisher_.interactable = true;btn_rtsp_service_.GetComponentInChildren<Text>().text = "停止RTSP服務";}public void btn_rtsp_publisher_Click(){if (publisher_wrapper_.IsRtspPublisherRunning()){publisher_wrapper_.StopRtspStream();if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsPushingRtmp() && !publisher_wrapper_.IsRecording()){StopCaptureAvData();if (coroutine_ != null){StopCoroutine(coroutine_);coroutine_ = null;}}btn_rtsp_service_.interactable = true;btn_rtsp_publisher_.GetComponentInChildren<Text>().text = "發布RTSP";}else{if (!publisher_wrapper_.IsRtspServiceRunning()){Debug.LogError("RTSP service is not running..");return;}if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsPushingRtmp() && !publisher_wrapper_.IsRecording()){publisher_wrapper_.SetVideoPushType(video_push_type_);publisher_wrapper_.SetAudioPushType(audio_push_type_);}publisher_wrapper_.StartRtspStream();if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsPushingRtmp() && !publisher_wrapper_.IsRecording()){StartCaptureAvData();coroutine_ = StartCoroutine(OnPostVideo());}btn_rtsp_publisher_.GetComponentInChildren<Text>().text = "停止RTSP";btn_rtsp_service_.interactable = false;}}public void btn_get_rtsp_session_numbers_Click(){if (publisher_wrapper_.IsRtspServiceRunning()){btn_get_rtsp_session_numbers_.GetComponentInChildren<Text>().text = "RTSP會話數:" + publisher_wrapper_.GetRtspSessionNumbers();}}
實時錄像調用:
public void btn_record_Click(){if (publisher_wrapper_.IsRecording()){StopRecord();btn_record_.GetComponentInChildren<Text>().text = "開始錄像";return;}if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsPushingRtmp()){publisher_wrapper_.SetVideoPushType(video_push_type_);publisher_wrapper_.SetAudioPushType(audio_push_type_);}if (!publisher_wrapper_.StartRecorder()){Debug.LogError("調用StartRecorder失敗..");return;}btn_record_.GetComponentInChildren<Text>().text = "停止錄像";if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsPushingRtmp()){StartCaptureAvData();coroutine_ = StartCoroutine(OnPostVideo());}}public void StopRecord(){if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsPushingRtmp()){StopCaptureAvData();if (coroutine_ != null){StopCoroutine(coroutine_);coroutine_ = null;}}publisher_wrapper_.StopRecorder();}private void btn_pause_record_Click(){if (!publisher_wrapper_.IsPublisherHandleAvailable()){return;}String btn_pause_rec_text = btn_pause_record_.GetComponentInChildren<Text>().text;if ("暫停錄像" == btn_pause_rec_text){UInt32 ret = publisher_wrapper_.PauseRecorder(true);if ((UInt32)NT.NTSmartPublisherDefine.NT_PB_E_ERROR_CODE.NT_ERC_PB_NEED_RETRY == ret){Debug.LogError("暫停錄像失敗, 請重新嘗試!");return;}else if (NTBaseCodeDefine.NT_ERC_OK == ret){btn_pause_record_.GetComponentInChildren<Text>().text = "恢復錄像";}}else{UInt32 ret = publisher_wrapper_.PauseRecorder(false);if ((UInt32)NT.NTSmartPublisherDefine.NT_PB_E_ERROR_CODE.NT_ERC_PB_NEED_RETRY == ret){Debug.LogError("恢復錄像失敗, 請重新嘗試!");return;}else if (NTBaseCodeDefine.NT_ERC_OK == ret){btn_pause_record_.GetComponentInChildren<Text>().text = "暫停錄像";}}}
實時預覽相關:
public void btn_preview_Click(){if (btn_preview_.GetComponentInChildren<Text>().text.Equals("本地預覽")){if (!publisher_wrapper_.IsPushingRtmp() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsRecording()){publisher_wrapper_.SetVideoPushType(video_push_type_);}if (publisher_wrapper_.StartPreview()){btn_preview_.GetComponentInChildren<Text>().text = "停止預覽";}if (!publisher_wrapper_.IsPushingRtmp() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsRecording()){StartCaptureAvData();coroutine_ = StartCoroutine(OnPostVideo());}}else{if (!publisher_wrapper_.IsPushingRtmp() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsRecording()){StopCaptureAvData();if (coroutine_ != null){StopCoroutine(coroutine_);coroutine_ = null;}}publisher_wrapper_.StopPreview();btn_preview_.GetComponentInChildren<Text>().text = "本地預覽";}}
總結
Unity平臺下RTMP推送、錄像、輕量級RTSP服務,在虛擬仿真、醫療、教育等場景下,應用非常廣泛。要實現低延遲,除了需要高效率的音視頻數據采集,編碼和數據投遞外,還需要好的直播播放器支持。配合我們的SmartPlayer,可輕松實現毫秒級體驗,滿足絕大多數應用場景技術訴求。