【技術(shù)分享】Windows平臺低延遲RTMP、RTSP播放器接口設(shè)計探討

2023-01-09 14:32:41 來源:51CTO博客

背景

我們看過了太多介紹RTSP、RTMP播放相關(guān)的技術(shù)資料,大多接口設(shè)計簡約,延遲和擴(kuò)展能力也受到一定的局限,好多開發(fā)者希望我們能從接口設(shè)計的角度,大概介紹下大牛直播SDK關(guān)于RTMP、RTSP播放器開發(fā)設(shè)計,本文以Windows平臺RTMP、RTSP播放模塊為例,大概介紹下常用的接口。

接口設(shè)計

Windows平臺我們是C接口,對外C++和C#均可正常調(diào)用,本文就以C++為例,大概介紹下常用的接口設(shè)計。

1. Init/UnInit()接口

Init和UnInit接口,在多個播放實例啟動的時候,也僅需調(diào)用一次,做基礎(chǔ)的初始化/反初始化操作。


(資料圖)

/*flag目前傳0,后面擴(kuò)展用, pReserve傳NULL,擴(kuò)展用,成功返回 NT_ERC_OK*/NT_UINT32(NT_API *Init)(NT_UINT32 flag, NT_PVOID pReserve);/*這個是最后一個調(diào)用的接口成功返回 NT_ERC_OK*/NT_UINT32(NT_API *UnInit)();

2. Open/Close()接口

Open接口的目的,主要是創(chuàng)建實例,正常返回player實例句柄,如有多路播放訴求,創(chuàng)建多個實例即可。

Close接口,和Open()接口對應(yīng),負(fù)責(zé)釋放相應(yīng)實例的資源,調(diào)用Close()接口后,記得實例句柄置0。

注意:比如一個實例既可以實現(xiàn)播放,又可同時錄像,亦或拉流(轉(zhuǎn)發(fā)),這種情況下,調(diào)Close()接口時,需要確保錄像、拉流都正常停止后,再調(diào)用。

/*flag目前傳0,后面擴(kuò)展用, pReserve傳NULL,擴(kuò)展用,NT_HWND hwnd, 繪制畫面用的窗口, 可以設(shè)置為NULL獲取Handle成功返回 NT_ERC_OK*/NT_UINT32(NT_API *Open)(NT_PHANDLE pHandle, NT_HWND hwnd, NT_UINT32 flag, NT_PVOID pReserve);/*調(diào)用這個接口之后handle失效,成功返回 NT_ERC_OK*/NT_UINT32(NT_API *Close)(NT_HANDLE handle);

3. 網(wǎng)絡(luò)狀態(tài)回調(diào)

一個好的播放器,好的狀態(tài)回調(diào)必不可少,比如網(wǎng)絡(luò)連通狀態(tài)、快照、錄像狀態(tài)、當(dāng)前下載速度等實時反饋,可以讓上層開發(fā)者更好的掌控播放端狀態(tài),給用戶更好的播放體驗。

/*設(shè)置事件回調(diào),如果想監(jiān)聽事件的話,建議調(diào)用Open成功后,就調(diào)用這個接口*/NT_UINT32(NT_API *SetEventCallBack)(NT_HANDLE handle,    NT_PVOID call_back_data, NT_SP_SDKEventCallBack call_back);

demo實現(xiàn)實例:

LRESULT CSmartPlayerDlg::OnSDKEvent(WPARAM wParam, LPARAM lParam){    if (!is_playing_ && !is_recording_)    {        return S_OK;    }    NT_UINT32 event_id = (NT_UINT32)(wParam);    if ( NT_SP_E_EVENT_ID_PLAYBACK_REACH_EOS == event_id )    {        StopPlayback();        return S_OK;    }    else if ( NT_SP_E_EVENT_ID_RECORDER_REACH_EOS == event_id )    {        StopRecorder();        return S_OK;    }    else if ( NT_SP_E_EVENT_ID_RTSP_STATUS_CODE == event_id )    {        int status_code = (int)lParam;        if ( 401 == status_code )        {            HandleVerification();        }        return S_OK;    }    else if (NT_SP_E_EVENT_ID_NEED_KEY == event_id)    {        HandleKeyEvent(false);        return S_OK;    }    else if (NT_SP_E_EVENT_ID_KEY_ERROR == event_id)    {        HandleKeyEvent(true);        return S_OK;    }    else if ( NT_SP_E_EVENT_ID_PULLSTREAM_REACH_EOS == event_id )    {        if (player_handle_ != NULL)        {            player_api_.StopPullStream(player_handle_);        }        return S_OK;    }    else if ( NT_SP_E_EVENT_ID_DURATION == event_id )    {        NT_INT64 duration = (NT_INT64)(lParam);        edit_duration_.SetWindowTextW(GetHMSMsFormatStr(duration, false, false).c_str());        return S_OK;    }    if ( NT_SP_E_EVENT_ID_CONNECTING == event_id        || NT_SP_E_EVENT_ID_CONNECTION_FAILED == event_id        || NT_SP_E_EVENT_ID_CONNECTED == event_id        || NT_SP_E_EVENT_ID_DISCONNECTED == event_id        || NT_SP_E_EVENT_ID_NO_MEDIADATA_RECEIVED == event_id)    {        if ( NT_SP_E_EVENT_ID_CONNECTING == event_id )        {            OutputDebugStringA("connection status: connecting\r\n");        }        else if ( NT_SP_E_EVENT_ID_CONNECTION_FAILED == event_id )        {            OutputDebugStringA("connection status: connection failed\r\n");        }        else if ( NT_SP_E_EVENT_ID_CONNECTED == event_id )        {            OutputDebugStringA("connection status: connected\r\n");        }        else if (NT_SP_E_EVENT_ID_DISCONNECTED == event_id)        {            OutputDebugStringA("connection status: disconnected\r\n");        }        else if (NT_SP_E_EVENT_ID_NO_MEDIADATA_RECEIVED == event_id)        {            OutputDebugStringA("connection status: no mediadata received\r\n");        }        connection_status_ = event_id;    }    if ( NT_SP_E_EVENT_ID_START_BUFFERING == event_id        || NT_SP_E_EVENT_ID_BUFFERING == event_id        || NT_SP_E_EVENT_ID_STOP_BUFFERING == event_id )    {        buffer_status_ = event_id;                if ( NT_SP_E_EVENT_ID_BUFFERING == event_id )        {            buffer_percent_ = (NT_INT32)lParam;            std::wostringstream ss;            ss << L"buffering:" << buffer_percent_ << "%";            OutputDebugStringW(ss.str().c_str());            OutputDebugStringW(L"\r\n");        }    }    if ( NT_SP_E_EVENT_ID_DOWNLOAD_SPEED == event_id )    {        download_speed_ = (NT_INT32)lParam;        /*std::wostringstream ss;        ss << L"downloadspeed:" << download_speed_ << L"\r\n";        OutputDebugStringW(ss.str().c_str());*/    }    CString show_str = base_title_;    if ( connection_status_ != 0 )    {        show_str += _T("--鏈接狀態(tài): ");        if ( NT_SP_E_EVENT_ID_CONNECTING == connection_status_ )        {            show_str += _T("鏈接中");        }        else if ( NT_SP_E_EVENT_ID_CONNECTION_FAILED == connection_status_ )        {            show_str += _T("鏈接失敗");        }        else if ( NT_SP_E_EVENT_ID_CONNECTED == connection_status_ )        {            show_str += _T("鏈接成功");        }        else if ( NT_SP_E_EVENT_ID_DISCONNECTED == connection_status_ )        {            show_str += _T("鏈接斷開");        }        else if (NT_SP_E_EVENT_ID_NO_MEDIADATA_RECEIVED == connection_status_)        {            show_str += _T("收不到數(shù)據(jù)");        }    }    if (download_speed_ != -1)    {        std::wostringstream ss;        ss << L"--下載速度:" << (download_speed_ * 8 / 1000) << "kbps"          << L"(" << (download_speed_ / 1024) << "KB/s)";        show_str += ss.str().c_str();    }    if ( buffer_status_ != 0 )    {        show_str += _T("--緩沖狀態(tài): ");        if ( NT_SP_E_EVENT_ID_START_BUFFERING == buffer_status_ )        {            show_str += _T("開始緩沖");        }        else if (NT_SP_E_EVENT_ID_BUFFERING == buffer_status_)        {            std::wostringstream ss;            ss << L"緩沖中" << buffer_percent_ << "%";            show_str += ss.str().c_str();        }        else if (NT_SP_E_EVENT_ID_STOP_BUFFERING == buffer_status_)        {            show_str += _T("結(jié)束緩沖");        }    }    SetWindowText(show_str);    return S_OK;}

4. 軟解碼還是硬解碼?

一般來說,Windows平臺如果同時播放的實例不多或者分辨率不是太高的話,考慮到播放體驗,建議優(yōu)先考慮軟解碼,如果特定設(shè)備需要多路播放,也可以考慮硬解,需要注意的是,如果調(diào)用硬解碼,需要先做是否支持硬解碼檢測,接口如下:

/*檢查是否支持H264硬解碼如果支持的話返回NT_ERC_OK*/NT_UINT32(NT_API *IsSupportH264HardwareDecoder)();/*檢查是否支持H265硬解碼如果支持的話返回NT_ERC_OK*/NT_UINT32(NT_API *IsSupportH265HardwareDecoder)();/**設(shè)置H264硬解*is_hardware_decoder: 1:表示硬解, 0:表示不用硬解*reserve: 保留參數(shù), 當(dāng)前傳0就好*成功返回NT_ERC_OK*/NT_UINT32(NT_API *SetH264HardwareDecoder)(NT_HANDLE handle, NT_INT32 is_hardware_decoder, NT_INT32 reserve);/**設(shè)置H265硬解*is_hardware_decoder: 1:表示硬解, 0:表示不用硬解*reserve: 保留參數(shù), 當(dāng)前傳0就好*成功返回NT_ERC_OK*/NT_UINT32(NT_API *SetH265HardwareDecoder)(NT_HANDLE handle, NT_INT32 is_hardware_decoder, NT_INT32 reserve);
/**   * 設(shè)置視頻硬解碼下Mediacodec自行繪制模式(此種模式下,硬解碼兼容性和效率更好,回調(diào)YUV/RGB和快照功能將不可用)   *   * @param handle: return value from SmartPlayerOpen()   *   * @param isHWRenderMode: 0: not enable; 1: 用SmartPlayerSetSurface設(shè)置的surface自行繪制   *   * @return {0} if successful   */  publicnativeintSmartPlayerSetHWRenderMode(long handle, int isHWRenderMode);  /**   * 更新硬解碼surface   *   * @param handle: return value from SmartPlayerOpen()   *   * @return {0} if successful   */  publicnativeintSmartPlayerUpdateHWRenderSurface(long handle);

5.只解關(guān)鍵幀

移動端,一般對只播放關(guān)鍵幀真正場景,需求不大,但是window端,好多場景下,因為需要播放非常多路,但是又不想占用太多的系統(tǒng)資源,如果全幀播放,路數(shù)過多,全部解碼、繪制,系統(tǒng)資源占用會加大,如果能靈活的處理,可以隨時只播放關(guān)鍵幀,全幀播放切換,對系統(tǒng)性能要求大幅降低,想全幀播放的時候,隨時切換全幀繪制。

/**設(shè)置只解碼視頻關(guān)鍵幀*is_only_dec_key_frame: 1:表示只解碼關(guān)鍵幀, 0:表示都解碼, 默認(rèn)是0*成功返回NT_ERC_OK*/NT_UINT32(NT_API *SetOnlyDecodeVideoKeyFrame)(NT_HANDLE handle, NT_INT32 is_only_dec_key_frame);

6. 緩沖時間設(shè)置

緩沖時間,顧名思義,緩存多少數(shù)據(jù)才開始播放,比如設(shè)置2000ms的buffer time,直播模式下,收到2秒數(shù)據(jù)后,才正常播放。

加大buffer time,會增大播放延遲,好處是,網(wǎng)絡(luò)抖動的時候,流暢性更好。

/*設(shè)置buffer,最小0ms*/NT_UINT32(NT_API *SetBuffer)(NT_HANDLE handle, NT_INT32 buffer);

7. 實時靜音、實時音量調(diào)節(jié)

實時靜音、實時音量調(diào)節(jié)顧名思義,播放端可以實時調(diào)整播放音量,或者直接靜音掉,特別是多路播放場景下,非常有必要。

/*靜音接口,1為靜音,0為不靜音*/NT_UINT32(NT_API *SetMute)(NT_HANDLE handle, NT_INT32 is_mute);/*設(shè)置播放音量, 范圍是[0, 100], 0是靜音,100是最大音量, 默認(rèn)是100調(diào)用正確返回NT_ERC_OK*/NT_UINT32(NT_API *SetAudioVolume)(NT_HANDLE handle, NT_INT32 volume);

8. RTSP TCP-UDP模式設(shè)置、超時時間設(shè)置或模式切換

有的RTSP服務(wù)器或攝像機(jī),只支持RTSP TCP模式或者UDP模式,這個時候,默認(rèn)設(shè)置TCP、UDP模式就至關(guān)重要,此外,我們還設(shè)計支持如TCP或UDP模式收不到數(shù)據(jù),在超時時間后,可以自動切換到UDP或TCP。

/*設(shè)置RTSP TCP 模式, 1為TCP, 0為UDP, 僅RTSP有效*/NT_UINT32(NT_API* SetRTSPTcpMode)(NT_HANDLE handle, NT_INT32 isUsingTCP);/*設(shè)置RTSP超時時間, timeout單位為秒,必須大于0*/NT_UINT32 (NT_API* SetRtspTimeout)(NT_HANDLE handle, NT_INT32 timeout);/*對于RTSP來說,有些可能支持rtp over udp方式,有些可能支持使用rtp over tcp方式. 為了方便使用,有些場景下可以開啟自動嘗試切換開關(guān), 打開后如果udp無法播放,sdk會自動嘗試tcp, 如果tcp方式播放不了,sdk會自動嘗試udp.is_auto_switch_tcp_udp: 如果設(shè)置1的話, sdk將在tcp和udp之間嘗試切換播放,如果設(shè)置為0,則不嘗試切換.*/NT_UINT32 (NT_API* SetRtspAutoSwitchTcpUdp)(NT_HANDLE handle, NT_INT32 is_auto_switch_tcp_udp);

9. 快速啟動

快速啟動,主要是針對服務(wù)器緩存GOP的場景下,快速刷到最新的數(shù)據(jù),確保畫面的持續(xù)性。

/*設(shè)置秒開, 1為秒開, 0為不秒開*/NT_UINT32(NT_API* SetFastStartup)(NT_HANDLE handle, NT_INT32 isFastStartup);

10. 低延遲模式

低延遲模式下,設(shè)置buffer time為0,延遲更低,適用于比如需要操控控制的超低延遲場景下。

/*設(shè)置低延時播放模式,默認(rèn)是正常播放模式mode: 1為低延時模式, 0為正常模式,其他只無效接口調(diào)用成功返回NT_ERC_OK*/NT_UINT32(NT_API* SetLowLatencyMode)(NT_HANDLE handle, NT_INT32 mode);

11. 視頻view旋轉(zhuǎn)、水平|垂直翻轉(zhuǎn)

接口主要用于,比如原始的視頻倒置等場景下,設(shè)備端無法調(diào)整時,通過播放端完成圖像的正常角度播放。

/**上下反轉(zhuǎn)(垂直反轉(zhuǎn))*is_flip: 1:表示反轉(zhuǎn), 0:表示不反轉(zhuǎn)*/NT_UINT32(NT_API *SetFlipVertical)(NT_HANDLE handle, NT_INT32 is_flip);/**水平反轉(zhuǎn)*is_flip: 1:表示反轉(zhuǎn), 0:表示不反轉(zhuǎn)*/NT_UINT32(NT_API *SetFlipHorizontal)(NT_HANDLE handle, NT_INT32 is_flip);/*設(shè)置旋轉(zhuǎn),順時針旋轉(zhuǎn)degress: 設(shè)置0, 90, 180, 270度有效,其他值無效注意:除了0度,其他角度播放會耗費(fèi)更多CPU接口調(diào)用成功返回NT_ERC_OK*/NT_UINT32(NT_API* SetRotation)(NT_HANDLE handle, NT_INT32 degress);

12. 設(shè)置實時回調(diào)下載速度

調(diào)用實時下載速度接口,通過設(shè)置下載速度時間間隔,和是否需要上報當(dāng)前下載速度,實現(xiàn)APP層和底層SDK更友好的交互。

/*設(shè)置下載速度上報, 默認(rèn)不上報下載速度is_report: 上報開關(guān), 1: 表上報. 0: 表示不上報. 其他值無效.report_interval: 上報時間間隔(上報頻率),單位是秒,最小值是1秒1次. 如果小于1且設(shè)置了上報,將調(diào)用失敗注意:如果設(shè)置上報的話,請設(shè)置SetEventCallBack, 然后在回調(diào)函數(shù)里面處理這個事件.上報事件是:NT_SP_E_EVENT_ID_DOWNLOAD_SPEED這個接口必須在StartXXX之前調(diào)用成功返回NT_ERC_OK*/NT_UINT32(NT_API *SetReportDownloadSpeed)(NT_HANDLE handle,NT_INT32 is_report, NT_INT32 report_interval);/*主動獲取下載速度speed: 返回下載速度,單位是Byte/s(注意:這個接口必須在startXXX之后調(diào)用,否則會失?。┏晒Ψ祷豊T_ERC_OK*/NT_UINT32(NT_API *GetDownloadSpeed)(NT_HANDLE handle, NT_INT32* speed);

13. 實時快照

簡單來說,播放過程中,是不是要存取當(dāng)前的播放畫面。

/*捕獲圖片file_name_utf8: 文件名稱,utf8編碼call_back_data: 回調(diào)時用戶自定義數(shù)據(jù)call_back: 回調(diào)函數(shù),用來通知用戶截圖已經(jīng)完成或者失敗成功返回 NT_ERC_OK只有在播放時調(diào)用才可能成功,其他情況下調(diào)用,返回錯誤.因為生成PNG文件比較耗時,一般需要幾百毫秒,為防止CPU過高,SDK會限制截圖請求數(shù)量,當(dāng)超過一定數(shù)量時,調(diào)用這個接口會返回NT_ERC_SP_TOO_MANY_CAPTURE_IMAGE_REQUESTS. 這種情況下, 請延時一段時間,等SDK處理掉一些請求后,再嘗試.*/NT_UINT32(NT_API* CaptureImage)(NT_HANDLE handle, NT_PCSTR file_name_utf8,NT_PVOID call_back_data, SP_SDKCaptureImageCallBack call_back);

調(diào)用實例如下:

void CSmartPlayerDlg::OnBnClickedButtonCaptureImage(){  if ( capture_image_path_.empty() )  {    AfxMessageBox(_T("請先設(shè)置保存截圖文件的目錄! 點(diǎn)擊截圖左邊的按鈕設(shè)置!"));    return;  }  if ( player_handle_ == NULL )  {    return;  }  if ( !is_playing_ )  {    return;  }  std::wostringstream ss;  ss << capture_image_path_;  if ( capture_image_path_.back() != L"\\" )  {    ss << L"\\";  }  SYSTEMTIME sysTime;  ::GetLocalTime(&sysTime);  ss << L"SmartPlayer-"    << std::setfill(L"0") << std::setw(4) << sysTime.wYear    << std::setfill(L"0") << std::setw(2) << sysTime.wMonth    << std::setfill(L"0") << std::setw(2) << sysTime.wDay    << L"-"    << std::setfill(L"0") << std::setw(2) << sysTime.wHour    << std::setfill(L"0") << std::setw(2) << sysTime.wMinute    << std::setfill(L"0") << std::setw(2) << sysTime.wSecond;  ss << L"-" << std::setfill(L"0") << std::setw(3) << sysTime.wMilliseconds    << L".png";  std::wstring_convert > conv;  auto val_str = conv.to_bytes(ss.str());  auto ret = player_api_.CaptureImage(player_handle_, val_str.c_str(), NULL, &SM_SDKCaptureImageHandle);  if (NT_ERC_OK == ret)  {    // 發(fā)送截圖請求成功  }  else if (NT_ERC_SP_TOO_MANY_CAPTURE_IMAGE_REQUESTS == ret)  {    // 通知用戶延時    OutputDebugStringA("Too many capture image requests!!!\r\n");  }  else  {    // 其他失敗  }}

14. 擴(kuò)展錄像操作

播放端錄像,我們做的非常細(xì)化,比如可以只錄制音頻或者只錄制視頻,設(shè)置錄像存儲路徑,設(shè)置單個文件size,如果非AAC數(shù)據(jù),可以轉(zhuǎn)AAC后再錄像。

/** 設(shè)置是否錄視頻,默認(rèn)的話,如果視頻源有視頻就錄,沒有就沒得錄, 但有些場景下可能不想錄制視頻,只想錄音頻,所以增加個開關(guān)* is_record_video: 1 表示錄制視頻, 0 表示不錄制視頻, 默認(rèn)是1*/NT_UINT32(NT_API *SetRecorderVideo)(NT_HANDLE handle, NT_INT32 is_record_video);/** 設(shè)置是否錄音頻,默認(rèn)的話,如果視頻源有音頻就錄,沒有就沒得錄, 但有些場景下可能不想錄制音頻,只想錄視頻,所以增加個開關(guān)* is_record_audio: 1 表示錄制音頻, 0 表示不錄制音頻, 默認(rèn)是1*/NT_UINT32(NT_API *SetRecorderAudio)(NT_HANDLE handle, NT_INT32 is_record_audio);/*設(shè)置本地錄像目錄, 必須是英文目錄,否則會失敗*/NT_UINT32(NT_API *SetRecorderDirectory)(NT_HANDLE handle, NT_PCSTR dir);/*設(shè)置單個錄像文件最大大小, 當(dāng)超過這個值的時候,將切割成第二個文件size: 單位是KB(1024Byte), 當(dāng)前范圍是 [5MB-800MB], 超出將被設(shè)置到范圍內(nèi)*/NT_UINT32(NT_API *SetRecorderFileMaxSize)(NT_HANDLE handle, NT_UINT32 size);/*設(shè)置錄像文件名生成規(guī)則*/NT_UINT32(NT_API *SetRecorderFileNameRuler)(NT_HANDLE handle, NT_SP_RecorderFileNameRuler* ruler);/*設(shè)置錄像回調(diào)接口*/NT_UINT32(NT_API *SetRecorderCallBack)(NT_HANDLE handle,NT_PVOID call_back_data, SP_SDKRecorderCallBack call_back);/*設(shè)置錄像時音頻轉(zhuǎn)AAC編碼的開關(guān), aac比較通用,sdk增加其他音頻編碼(比如speex, pcmu, pcma等)轉(zhuǎn)aac的功能.is_transcode: 設(shè)置為1的話,如果音頻編碼不是aac,則轉(zhuǎn)成aac, 如果是aac,則不做轉(zhuǎn)換. 設(shè)置為0的話,則不做任何轉(zhuǎn)換. 默認(rèn)是0.注意: 轉(zhuǎn)碼會增加性能消耗*/NT_UINT32(NT_API *SetRecorderAudioTranscodeAAC)(NT_HANDLE handle, NT_INT32 is_transcode);/*啟動錄像*/NT_UINT32(NT_API *StartRecorder)(NT_HANDLE handle);/*停止錄像*/NT_UINT32(NT_API *StopRecorder)(NT_HANDLE handle);

15. 拉流回調(diào)編碼后的數(shù)據(jù)(配合轉(zhuǎn)發(fā)模塊使用)

拉流回調(diào)編碼后的數(shù)據(jù),主要是為了配合轉(zhuǎn)發(fā)模塊使用,比如拉取rtsp流數(shù)據(jù),直接轉(zhuǎn)RTMP推送到RTMP服務(wù)。

/** 設(shè)置拉流時,吐視頻數(shù)據(jù)的回調(diào)*/NT_UINT32(NT_API *SetPullStreamVideoDataCallBack)(NT_HANDLE handle,NT_PVOID call_back_data, SP_SDKPullStreamVideoDataCallBack call_back);/** 設(shè)置拉流時,吐音頻數(shù)據(jù)的回調(diào)*/NT_UINT32(NT_API *SetPullStreamAudioDataCallBack)(NT_HANDLE handle,NT_PVOID call_back_data, SP_SDKPullStreamAudioDataCallBack call_back);/*設(shè)置拉流時音頻轉(zhuǎn)AAC編碼的開關(guān), aac比較通用,sdk增加其他音頻編碼(比如speex, pcmu, pcma等)轉(zhuǎn)aac的功能.is_transcode: 設(shè)置為1的話,如果音頻編碼不是aac,則轉(zhuǎn)成aac, 如果是aac,則不做轉(zhuǎn)換. 設(shè)置為0的話,則不做任何轉(zhuǎn)換. 默認(rèn)是0.注意: 轉(zhuǎn)碼會增加性能消耗*/NT_UINT32(NT_API *SetPullStreamAudioTranscodeAAC)(NT_HANDLE handle, NT_INT32 is_transcode);/*啟動拉流*/NT_UINT32(NT_API *StartPullStream)(NT_HANDLE handle);/*停止拉流*/NT_UINT32(NT_API *StopPullStream)(NT_HANDLE handle);

16. H264用戶數(shù)據(jù)回調(diào)或SEI數(shù)據(jù)回調(diào)

如發(fā)送端在264編碼時,加了自定義的user data數(shù)據(jù),可以通過以下接口實現(xiàn)數(shù)據(jù)回調(diào),如需直接回調(diào)SEI數(shù)據(jù),調(diào)下面SEI回調(diào)接口即可。

/*設(shè)置用戶數(shù)據(jù)回調(diào)*/NT_UINT32(NT_API *SetUserDataCallBack)(NT_HANDLE handle,NT_PVOID call_back_data, NT_SP_SDKUserDataCallBack call_back);

調(diào)用實例如下:

extern "C" NT_VOID NT_CALLBACK NT_SP_SDKUserDataHandle(NT_HANDLE handle, NT_PVOID user_data,  NT_INT32  data_type,  NT_PVOID  data,  NT_UINT32 size,  NT_UINT64 timestamp,  NT_UINT64 reserve1,  NT_INT64  reserve2,  NT_PVOID  reserve3){  if ( 1 == data_type )  {    std::wostringstream oss;    oss << L"userdata ";    const NT_BYTE* byte_data = reinterpret_cast(data);    if ( byte_data != nullptr && size > 0 )    {      oss << L" byte data size=" << size;    }    std::wstring_convert > conv;    oss << L" t:" << timestamp << L"\r\n";    OutputDebugStringW(oss.str().c_str());  }  else if ( 2 == data_type )  {    const NT_CHAR* str_data = reinterpret_cast(data);    if (str_data != nullptr && size > 0)    {      std::unique_ptr s(new std::string(str_data, str_data + size));      // oss << L" utf8 string:" << conv.from_bytes(*s);      // oss << L" size=" << size;      if ( !s->empty() )      {        HWND hwnd = reinterpret_cast(user_data);        if ( hwnd != nullptr && ::IsWindow(hwnd) )        {          ::PostMessage(hwnd, WM_USER_SDK_SP_RECV_USER_DATA, (WPARAM)s.release(), (LPARAM)timestamp);        }      }    }  }}

17. 設(shè)置回調(diào)解碼后YUV、RGB數(shù)據(jù)

如需對解碼后的yuv或rgb數(shù)據(jù),進(jìn)行二次處理,如人臉識別等,可以通回調(diào)yuv rgb接口實現(xiàn)數(shù)據(jù)二次處理,對于Windows平臺來說,如果設(shè)備不支持D3D,也可以數(shù)據(jù)回調(diào)上來GDI模式繪制:

player_api_.SetVideoFrameCallBack(player_handle_, NT_SP_E_VIDEO_FRAME_FORMAT_RGB32,GetSafeHwnd(), SM_SDKVideoFrameHandle);extern "C" NT_VOID NT_CALLBACK SM_SDKVideoFrameHandle(NT_HANDLE handle, NT_PVOID userData, NT_UINT32 status,  const NT_SP_VideoFrame* frame){  /*if (frame != NULL)  {  std::ostringstream ss;  ss << "Receive frame time_stamp:" << frame->timestamp_ << "ms" << "\r\n";  OutputDebugStringA(ss.str().c_str());  }*/  if ( frame != NULL )  {    if ( NT_SP_E_VIDEO_FRAME_FORMAT_RGB32 == frame->format_      && frame->plane0_ != NULL      && frame->stride0_ > 0      && frame->height_ > 0 )    {      std::unique_ptr pImage(new nt_rgb32_image());      pImage->size_ = frame->stride0_* frame->height_;      pImage->data_ = new NT_BYTE[pImage->size_];      memcpy(pImage->data_, frame->plane0_, pImage->size_);      pImage->width_  = frame->width_;      pImage->height_ = frame->height_;      pImage->stride_ = frame->stride0_;      HWND hwnd = (HWND)userData;      if ( hwnd != NULL && ::IsWindow(hwnd) )      {        ::PostMessage(hwnd, WM_USER_SDK_RGB32_IMAGE, (WPARAM)handle, (LPARAM)pImage.release());      }    }  }}

18. 設(shè)置視頻畫面填充模式

設(shè)置視頻畫面的填充模式,如填充整個view、等比例填充view,如不設(shè)置,默認(rèn)填充整個view。

相關(guān)接口設(shè)計如下:

player_api_.SetRenderScaleMode(player_handle_, btn_check_render_scale_mode_.GetCheck() == BST_CHECKED ? 1 : 0);

總結(jié)

以上就是大牛直播SDK(??官網(wǎng)??)關(guān)于Windows平臺RTSP、RTMP播放器接口設(shè)計需要參考的點(diǎn),其他還有些,比如如果不支持D3D,GDI模式繪制,播放界面疊加實時文字,播放畫面全屏等,這里就不再贅述,除Windows平臺外,我們還同步開發(fā)了Linux、Android、iOS平臺的RTSP、RTMP播放器,大多常規(guī)接口四個平臺基本統(tǒng)一,延遲也都做到了毫秒級。對于大多數(shù)開發(fā)者來說,不一定需要實現(xiàn)上述所有部分,只要按照產(chǎn)品訴求,實現(xiàn)其中的30-40%就足夠滿足特定場景使用了。

一個好的播放器,特別是要滿足低延遲穩(wěn)定的播放(毫秒級延遲),需要注意的點(diǎn)遠(yuǎn)不止如此,感興趣的開發(fā)者,可以參考blog其他文章。

標(biāo)簽: 下載速度 是否支持 填充模式

上一篇:天天快資訊丨場景編程集錦-懵懂的青春
下一篇:每日聚焦:【Redis 技術(shù)探索】「數(shù)據(jù)遷移實戰(zhàn)」手把手教你如何實現(xiàn)在線 + 離線模式進(jìn)行遷移 Redis 數(shù)據(jù)實戰(zhàn)指南(scan模式遷移)