开发个好的RTMP播放器到底难在哪里?RTMP播放器对标和考察指标
好多開發(fā)者提到,RTMP播放器,不知道有哪些對(duì)標(biāo)和考察指標(biāo),以下大概聊聊我們的一點(diǎn)經(jīng)驗(yàn),感興趣的,可以關(guān)注 github:
1. 低延遲:大多數(shù)RTMP的播放都面向直播場(chǎng)景,如果延遲過大,嚴(yán)重影響體驗(yàn),所以,低延遲是衡量一個(gè)好的RTMP播放器非常重要的指標(biāo),目前大牛直播SDK的RTMP直播播放延遲比開源播放器更優(yōu)異(大牛直播SDK延遲在1秒左右,開源播放器如VLC,延遲在5-7秒),而且長(zhǎng)時(shí)間運(yùn)行下,大牛直播SDK播放端不會(huì)造成延遲累積,開源或第三方播放器,長(zhǎng)時(shí)間運(yùn)行,容易產(chǎn)生延遲累積;
部分服務(wù)器會(huì)緩存GOP,確保快速實(shí)現(xiàn)首屏播放,又不影響延遲,對(duì)此,我們?cè)O(shè)計(jì)了快速啟動(dòng)接口,快速render第一幀的同時(shí),追到最新的播放數(shù)據(jù):
/** 設(shè)置秒開, 1為秒開, 0為不秒開*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetFastStartup(IntPtr handle, Int32 isFastStartup);2. 音視頻同步處理:大多播放器為了追求低延遲,甚至不做音視頻同步,拿到audio video直接播放,導(dǎo)致a/v不同步,還有就是時(shí)間戳亂跳等各種問題,大牛直播SDK提供的播放器,具備好的時(shí)間戳同步和異常時(shí)間戳矯正機(jī)制;
備注:如果是超低延遲模式下,可以0 buffer,不做音視頻同步:
/** 設(shè)置低延時(shí)播放模式,默認(rèn)是正常播放模式* mode: 1為低延時(shí)模式, 0為正常模式,其他只無效* 接口調(diào)用成功返回NT_ERC_OK*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetLowLatencyMode(IntPtr handle, Int32 mode);3. 支持多實(shí)例:大牛直播SDK提供的RTMP直播播放SDK支持在設(shè)備性能允許的情況下,支持多實(shí)例播放RTMP流數(shù)據(jù),大多開源播放器對(duì)多實(shí)例支持不太友好;
除了常規(guī)的多實(shí)例外,比如大屏監(jiān)控場(chǎng)景下,盡管我們CPU占用已經(jīng)是行業(yè)內(nèi)非常低的了,但是好多廠家下,不是每路都需要全幀播放,針對(duì)此種情況,我們做了實(shí)時(shí)只播放關(guān)鍵幀和全幀播放的接口設(shè)計(jì),比如8個(gè)實(shí)例,其中不太重要的幾路數(shù)據(jù),可以設(shè)置只播放關(guān)鍵幀,需要重點(diǎn)關(guān)注時(shí),點(diǎn)擊全幀率播放即可,這樣既節(jié)省了系統(tǒng)開銷,又實(shí)現(xiàn)了多路播放的目的:
/** 設(shè)置只解碼視頻關(guān)鍵幀* is_only_dec_key_frame: 1:表示只解碼關(guān)鍵幀, 0:表示都解碼, 默認(rèn)是0* 成功返回NT_ERC_OK*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetOnlyDecodeVideoKeyFrame(IntPtr handle, Int32 is_only_dec_key_frame);4. 支持buffer time設(shè)置:在一些有網(wǎng)絡(luò)抖動(dòng)的場(chǎng)景,播放器需要支持buffer time設(shè)置,一般來說,以毫秒計(jì),開源播放器對(duì)此支持不夠友好;
5. 實(shí)時(shí)靜音:比如,多窗口播放RTMP流,如果每個(gè)audio都播放出來,體驗(yàn)非常不好,所以實(shí)時(shí)靜音功能非常必要,開源播放器不具備實(shí)時(shí)靜音功能;
6. 視頻view旋轉(zhuǎn):好多攝像頭由于安裝限制,導(dǎo)致圖像倒置,所以一個(gè)好的RTMP播放器應(yīng)該支持如視頻view實(shí)時(shí)旋轉(zhuǎn)(0° 90° 180° 270°)、水平反轉(zhuǎn)、垂直反轉(zhuǎn),開源或第三方播放器不具備此功能;
/**上下反轉(zhuǎn)(垂直反轉(zhuǎn))*is_flip: 1:表示反轉(zhuǎn), 0:表示不反轉(zhuǎn)*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetFlipVertical(IntPtr handle, Int32 is_flip);/**水平反轉(zhuǎn)*is_flip: 1:表示反轉(zhuǎn), 0:表示不反轉(zhuǎn)*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetFlipHorizontal(IntPtr handle, Int32 is_flip);/** 設(shè)置旋轉(zhuǎn),順時(shí)針旋轉(zhuǎn)* degress: 設(shè)置0, 90, 180, 270度有效,其他值無效* 注意:除了0度,其他角度播放會(huì)耗費(fèi)更多CPU* 接口調(diào)用成功返回NT_ERC_OK*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetRotation(IntPtr handle, Int32 degress);7. 支持解碼后audio/video數(shù)據(jù)輸出:大牛直播SDK接觸到好多開發(fā)者,希望能在播放的同時(shí),獲取到Y(jié)UV或RGB數(shù)據(jù),進(jìn)行人臉匹配等算法分析,開源播放器不具備此功能;
public void SDKVideoFrameCallBack(UInt32 status, NT_SP_VideoFrame frame){if (cur_video_frame_.plane0_ != IntPtr.Zero){Marshal.FreeHGlobal(cur_video_frame_.plane0_);cur_video_frame_.plane0_ = IntPtr.Zero;}cur_video_frame_ = frame;this.Invalidate();}public void SDKAudioPCMFrameCallBack(UInt32 status, IntPtr data, UInt32 size,Int32 sample_rate, Int32 channel, Int32 per_channel_sample_number){//這里拿到回調(diào)的PCM frame,進(jìn)行相關(guān)操作(如自己播放)//label_debug.Text = per_channel_sample_number.ToString();//releaseMarshal.FreeHGlobal(data);}public void SetVideoFrameCallBack(IntPtr handle, IntPtr userData, UInt32 status, IntPtr frame){if (frame == IntPtr.Zero){return;}//如需直接處理RGB數(shù)據(jù),請(qǐng)參考以下流程N(yùn)T_SP_VideoFrame video_frame = (NT_SP_VideoFrame)Marshal.PtrToStructure(frame, typeof(NT_SP_VideoFrame));NT_SP_VideoFrame pVideoFrame = new NT_SP_VideoFrame();pVideoFrame.format_ = video_frame.format_;pVideoFrame.width_ = video_frame.width_;pVideoFrame.height_ = video_frame.height_;pVideoFrame.timestamp_ = video_frame.timestamp_;pVideoFrame.stride0_ = video_frame.stride0_;pVideoFrame.stride1_ = video_frame.stride1_;pVideoFrame.stride2_ = video_frame.stride2_;pVideoFrame.stride3_ = video_frame.stride3_;Int32 argb_size = video_frame.stride0_ * video_frame.height_;pVideoFrame.plane0_ = Marshal.AllocHGlobal(argb_size);CopyMemory(pVideoFrame.plane0_, video_frame.plane0_, (UInt32)argb_size);if (playWnd.InvokeRequired){BeginInvoke(set_video_frame_call_back_, status, pVideoFrame);}else{set_video_frame_call_back_(status, pVideoFrame);}}public void SetAudioPCMFrameCallBack(IntPtr handle, IntPtr user_data,UInt32 status, IntPtr data, UInt32 size,Int32 sample_rate, Int32 channel, Int32 per_channel_sample_number){if (data == IntPtr.Zero || size == 0){return;}IntPtr pcmData = Marshal.AllocHGlobal((Int32)size);CopyMemory(pcmData, data, (UInt32)size);if (playWnd.InvokeRequired){BeginInvoke(set_audio_pcm_frame_call_back_, status, pcmData, size, sample_rate, channel, per_channel_sample_number);}else{set_audio_pcm_frame_call_back_(status, pcmData, size, sample_rate, channel, per_channel_sample_number);}}8. 實(shí)時(shí)快照:感興趣或重要的畫面,實(shí)時(shí)截取下來非常必要,一般播放器不具備快照能力,開源播放器不具備此功能;
/** 捕獲圖片* file_name_utf8: 文件名稱,utf8編碼* call_back_data: 回調(diào)時(shí)用戶自定義數(shù)據(jù)* call_back: 回調(diào)函數(shù),用來通知用戶截圖已經(jīng)完成或者失敗* 成功返回 NT_ERC_OK* 只有在播放時(shí)調(diào)用才可能成功,其他情況下調(diào)用,返回錯(cuò)誤.* 因?yàn)樯蒔NG文件比較耗時(shí),一般需要幾百毫秒,為防止CPU過高,SDK會(huì)限制截圖請(qǐng)求數(shù)量,當(dāng)超過一定數(shù)量時(shí),* 調(diào)用這個(gè)接口會(huì)返回NT_ERC_SP_TOO_MANY_CAPTURE_IMAGE_REQUESTS. 這種情況下, 請(qǐng)延時(shí)一段時(shí)間,等SDK處理掉一些請(qǐng)求后,再嘗試.*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_CaptureImage(IntPtr handle, IntPtr file_name_utf8,IntPtr call_back_data, SP_SDKCaptureImageCallBack call_back);public void SDKCaptureImageCallBack(IntPtr handle, IntPtr userData, UInt32 result, IntPtr file_name){if (file_name == IntPtr.Zero)return;int index = 0;while (true){if (0 == Marshal.ReadByte(file_name, index))break;index++;}byte[] file_name_buffer = new byte[index];Marshal.Copy(file_name, file_name_buffer, 0, index);byte[] dst_buffer = Encoding.Convert(Encoding.UTF8, Encoding.Default, file_name_buffer, 0, file_name_buffer.Length);String image_name = Encoding.Default.GetString(dst_buffer, 0, dst_buffer.Length);if (playWnd.InvokeRequired){BeginInvoke(set_capture_image_call_back_, result, image_name);}else{set_capture_image_call_back_(result, image_name);}}9. 網(wǎng)絡(luò)抖動(dòng)處理(如斷網(wǎng)重連):穩(wěn)定的網(wǎng)絡(luò)處理機(jī)制、支持如斷網(wǎng)重連等,開源播放器對(duì)網(wǎng)絡(luò)異常處理支持較差;
/*事件ID*/public enum NT_SP_E_EVENT_ID : uint{NT_SP_E_EVENT_ID_BASE = NTBaseCodeDefine.NT_EVENT_ID_SMART_PLAYER_SDK,NT_SP_E_EVENT_ID_CONNECTING = NT_SP_E_EVENT_ID_BASE | 0x2, /*連接中*/NT_SP_E_EVENT_ID_CONNECTION_FAILED = NT_SP_E_EVENT_ID_BASE | 0x3, /*連接失敗*/NT_SP_E_EVENT_ID_CONNECTED = NT_SP_E_EVENT_ID_BASE | 0x4, /*已連接*/NT_SP_E_EVENT_ID_DISCONNECTED = NT_SP_E_EVENT_ID_BASE | 0x5, /*斷開連接*/NT_SP_E_EVENT_ID_NO_MEDIADATA_RECEIVED = NT_SP_E_EVENT_ID_BASE | 0x8, /*收不到RTMP數(shù)據(jù)*/NT_SP_E_EVENT_ID_RTSP_STATUS_CODE = NT_SP_E_EVENT_ID_BASE | 0xB, /*rtsp status code上報(bào), 目前只上報(bào)401, param1表示status code*//* 接下來請(qǐng)從0x81開始*/NT_SP_E_EVENT_ID_START_BUFFERING = NT_SP_E_EVENT_ID_BASE | 0x81, /*開始緩沖*/NT_SP_E_EVENT_ID_BUFFERING = NT_SP_E_EVENT_ID_BASE | 0x82, /*緩沖中, param1 表示百分比進(jìn)度*/NT_SP_E_EVENT_ID_STOP_BUFFERING = NT_SP_E_EVENT_ID_BASE | 0x83, /*停止緩沖*/NT_SP_E_EVENT_ID_DOWNLOAD_SPEED = NT_SP_E_EVENT_ID_BASE | 0x91, /*下載速度, param1表示下載速度,單位是(Byte/s)*/NT_SP_E_EVENT_ID_PLAYBACK_REACH_EOS = NT_SP_E_EVENT_ID_BASE | 0xa1, /*播放結(jié)束, 直播流沒有這個(gè)事件,點(diǎn)播流才有*/NT_SP_E_EVENT_ID_RECORDER_REACH_EOS = NT_SP_E_EVENT_ID_BASE | 0xa2, /*錄像結(jié)束, 直播流沒有這個(gè)事件, 點(diǎn)播流才有*/NT_SP_E_EVENT_ID_PULLSTREAM_REACH_EOS = NT_SP_E_EVENT_ID_BASE | 0xa3, /*拉流結(jié)束, 直播流沒有這個(gè)事件,點(diǎn)播流才有*/NT_SP_E_EVENT_ID_DURATION = NT_SP_E_EVENT_ID_BASE | 0xa8, /*視頻時(shí)長(zhǎng),如果是直播,則不上報(bào),如果是點(diǎn)播的話, 若能從視頻源獲取視頻時(shí)長(zhǎng)的話,則上報(bào), param1表示視頻時(shí)長(zhǎng),單位是毫秒(ms)*/}10. 長(zhǎng)期運(yùn)行穩(wěn)定性:大牛直播SDK提供的RTMP直播播放SDK適用于長(zhǎng)時(shí)間運(yùn)行,開源播放器對(duì)長(zhǎng)時(shí)間運(yùn)行穩(wěn)定性支持較差;
11. 實(shí)時(shí)下載速度反饋:大牛直播SDK提供音視頻流實(shí)時(shí)下載回調(diào),并可設(shè)置回調(diào)時(shí)間間隔,確保實(shí)時(shí)下載速度反饋,以此來監(jiān)聽網(wǎng)絡(luò)狀態(tài),開源播放器不具備此能力;
/** 設(shè)置下載速度上報(bào), 默認(rèn)不上報(bào)下載速度* is_report: 上報(bào)開關(guān), 1: 表上報(bào). 0: 表示不上報(bào). 其他值無效.* report_interval: 上報(bào)時(shí)間間隔(上報(bào)頻率),單位是秒,最小值是1秒1次. 如果小于1且設(shè)置了上報(bào),將調(diào)用失敗* 注意:如果設(shè)置上報(bào)的話,請(qǐng)?jiān)O(shè)置SetEventCallBack, 然后在回調(diào)函數(shù)里面處理這個(gè)事件.* 上報(bào)事件是:NT_SP_E_EVENT_ID_DOWNLOAD_SPEED* 這個(gè)接口必須在StartXXX之前調(diào)用* 成功返回NT_ERC_OK*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetReportDownloadSpeed(IntPtr handle, Int32 is_report, Int32 report_interval);/** 主動(dòng)獲取下載速度* speed: 返回下載速度,單位是Byte/s* (注意:這個(gè)接口必須在startXXX之后調(diào)用,否則會(huì)失敗)* 成功返回NT_ERC_OK*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_GetDownloadSpeed(IntPtr handle, ref Int32 speed);12. 異常狀態(tài)處理、Event狀態(tài)回調(diào):如播放的過程中斷網(wǎng),大牛直播SDK提供的播放器可實(shí)時(shí)回調(diào)相關(guān)狀態(tài),確保上層模塊感知處理,開源播放器對(duì)此支持不好;
/** 設(shè)置事件回調(diào),如果想監(jiān)聽事件的話,建議調(diào)用Open成功后,就調(diào)用這個(gè)接口*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetEventCallBack(IntPtr handle, IntPtr call_back_data, SP_SDKEventCallBack call_back);/** 設(shè)置視頻大小回調(diào)接口*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetVideoSizeCallBack(IntPtr handle, IntPtr callbackdata, SP_SDKVideoSizeCallBack call_back);/** 設(shè)置視頻回調(diào), 吐視頻數(shù)據(jù)出來* frame_format: 只能是NT_SP_E_VIDEO_FRAME_FORMAT_RGB32, NT_SP_E_VIDEO_FRAME_FROMAT_I420*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetVideoFrameCallBack(IntPtr handle, Int32 frame_format,IntPtr callbackdata, SP_SDKVideoFrameCallBack call_back);/** 設(shè)置視頻回調(diào), 吐視頻數(shù)據(jù)出來, 可以指定吐出來的視頻寬高* handle: 播放句柄* scale_width:縮放寬度(必須是偶數(shù),建議是 16 的倍數(shù))* scale_height:縮放高度(必須是偶數(shù))* scale_filter_mode: 縮放質(zhì)量, 0 的話 SDK 將使用默認(rèn)值, 目前可設(shè)置范圍為[1, 3], 值越大 縮放質(zhì)量越好,但越耗性能* frame_format: 只能是NT_SP_E_VIDEO_FRAME_FORMAT_RGB32, NT_SP_E_VIDEO_FRAME_FROMAT_I420* 成功返回NT_ERC_OK*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetVideoFrameCallBackV2(IntPtr handle,Int32 scale_width, Int32 scale_height,Int32 scale_filter_mode, Int32 frame_format,IntPtr call_back_data, SP_SDKVideoFrameCallBack call_back);/** 設(shè)置繪制視頻幀時(shí),視頻幀時(shí)間戳回調(diào)* 注意如果當(dāng)前播放流是純音頻,那么將不會(huì)回調(diào),這個(gè)僅在有視頻的情況下才有效*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetRenderVideoFrameTimestampCallBack(IntPtr handle,IntPtr callbackdata, SP_SDKRenderVideoFrameTimestampCallBack call_back);/** 設(shè)置音頻PCM幀回調(diào), 吐PCM數(shù)據(jù)出來,目前每幀大小是10ms.*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetAudioPCMFrameCallBack(IntPtr handle,IntPtr call_back_data, SP_SDKAudioPCMFrameCallBack call_back);/** 設(shè)置用戶數(shù)據(jù)回調(diào)*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetUserDataCallBack(IntPtr handle,IntPtr call_back_data, SP_SDKUserDataCallBack call_back);/** 設(shè)置視頻sei數(shù)據(jù)回調(diào)*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetSEIDataCallBack(IntPtr handle,IntPtr call_back_data, SP_SDKSEIDataCallBack call_back);13. 設(shè)置視頻填充模式(等比例顯示):好多情況下,有些場(chǎng)景需要全view鋪滿播放,有些為了防止視頻拉伸,可以設(shè)置成等比例縮放顯示;
/** 設(shè)置視頻畫面的填充模式,如填充整個(gè)繪制窗口、等比例填充繪制窗口,如不設(shè)置,默認(rèn)填充整個(gè)繪制窗口* handle: 播放句柄* mode: 0: 填充整個(gè)繪制窗口; 1: 等比例填充繪制窗口, 默認(rèn)值是0* 成功返回NT_ERC_OK*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_SetRenderScaleMode(IntPtr handle, Int32 mode);14. D3D檢測(cè):一般來說市面上的大多Windows都支持D3D,有些小眾化的,只支持GDI模式繪制,所以為了更好的兼容性,這個(gè)接口非常必要。
/** handle: 播放句柄* hwnd: 這個(gè)要傳入真正用來繪制的窗口句柄* is_support: 如果支持的話 *is_support 為1, 不支持的話為0* 接口調(diào)用成功返回NT_ERC_OK*/[DllImport(@"SmartPlayerSDK.dll")]public static extern UInt32 NT_SP_IsSupportD3DRender(IntPtr handle, IntPtr hwnd, ref Int32 is_support);?
總結(jié)
以上是生活随笔為你收集整理的开发个好的RTMP播放器到底难在哪里?RTMP播放器对标和考察指标的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何用U盘之家U盘启动盘制作工具实现U盘
- 下一篇: ECharts 饼图 legend 样式