【技术分享】Windows平台低延迟RTMP、RTSP播放器接口设计探讨

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

背景

我们看过了太多介绍RTSP、RTMP播放相关的技术资料,大多接口设计简约,延迟和扩展能力也受到一定的局限,好多开发者希望我们能从接口设计的角度,大概介绍下大牛直播SDK关于RTMP、RTSP播放器开发设计,本文以Windows平台RTMP、RTSP播放模块为例,大概介绍下常用的接口。

接口设计

Windows平台我们是C接口,对外C++和C#均可正常调用,本文就以C++为例,大概介绍下常用的接口设计。

1. Init/UnInit()接口

Init和UnInit接口,在多个播放实例启动的时候,也仅需调用一次,做基础的初始化/反初始化操作。


(资料图)

/*flag目前传0,后面扩展用, pReserve传NULL,扩展用,成功返回 NT_ERC_OK*/NT_UINT32(NT_API *Init)(NT_UINT32 flag, NT_PVOID pReserve);/*这个是最后一个调用的接口成功返回 NT_ERC_OK*/NT_UINT32(NT_API *UnInit)();

2. Open/Close()接口

Open接口的目的,主要是创建实例,正常返回player实例句柄,如有多路播放诉求,创建多个实例即可。

Close接口,和Open()接口对应,负责释放相应实例的资源,调用Close()接口后,记得实例句柄置0。

注意:比如一个实例既可以实现播放,又可同时录像,亦或拉流(转发),这种情况下,调Close()接口时,需要确保录像、拉流都正常停止后,再调用。

/*flag目前传0,后面扩展用, pReserve传NULL,扩展用,NT_HWND hwnd, 绘制画面用的窗口, 可以设置为NULL获取Handle成功返回 NT_ERC_OK*/NT_UINT32(NT_API *Open)(NT_PHANDLE pHandle, NT_HWND hwnd, NT_UINT32 flag, NT_PVOID pReserve);/*调用这个接口之后handle失效,成功返回 NT_ERC_OK*/NT_UINT32(NT_API *Close)(NT_HANDLE handle);

3. 网络状态回调

一个好的播放器,好的状态回调必不可少,比如网络连通状态、快照、录像状态、当前下载速度等实时反馈,可以让上层开发者更好的掌控播放端状态,给用户更好的播放体验。

/*设置事件回调,如果想监听事件的话,建议调用Open成功后,就调用这个接口*/NT_UINT32(NT_API *SetEventCallBack)(NT_HANDLE handle,    NT_PVOID call_back_data, NT_SP_SDKEventCallBack call_back);

demo实现实例:

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("--链接状态: ");        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("收不到数据");        }    }    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("--缓冲状态: ");        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("结束缓冲");        }    }    SetWindowText(show_str);    return S_OK;}

4. 软解码还是硬解码?

一般来说,Windows平台如果同时播放的实例不多或者分辨率不是太高的话,考虑到播放体验,建议优先考虑软解码,如果特定设备需要多路播放,也可以考虑硬解,需要注意的是,如果调用硬解码,需要先做是否支持硬解码检测,接口如下:

/*检查是否支持H264硬解码如果支持的话返回NT_ERC_OK*/NT_UINT32(NT_API *IsSupportH264HardwareDecoder)();/*检查是否支持H265硬解码如果支持的话返回NT_ERC_OK*/NT_UINT32(NT_API *IsSupportH265HardwareDecoder)();/**设置H264硬解*is_hardware_decoder: 1:表示硬解, 0:表示不用硬解*reserve: 保留参数, 当前传0就好*成功返回NT_ERC_OK*/NT_UINT32(NT_API *SetH264HardwareDecoder)(NT_HANDLE handle, NT_INT32 is_hardware_decoder, NT_INT32 reserve);/**设置H265硬解*is_hardware_decoder: 1:表示硬解, 0:表示不用硬解*reserve: 保留参数, 当前传0就好*成功返回NT_ERC_OK*/NT_UINT32(NT_API *SetH265HardwareDecoder)(NT_HANDLE handle, NT_INT32 is_hardware_decoder, NT_INT32 reserve);
/**   * 设置视频硬解码下Mediacodec自行绘制模式(此种模式下,硬解码兼容性和效率更好,回调YUV/RGB和快照功能将不可用)   *   * @param handle: return value from SmartPlayerOpen()   *   * @param isHWRenderMode: 0: not enable; 1: 用SmartPlayerSetSurface设置的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.只解关键帧

移动端,一般对只播放关键帧真正场景,需求不大,但是window端,好多场景下,因为需要播放非常多路,但是又不想占用太多的系统资源,如果全帧播放,路数过多,全部解码、绘制,系统资源占用会加大,如果能灵活的处理,可以随时只播放关键帧,全帧播放切换,对系统性能要求大幅降低,想全帧播放的时候,随时切换全帧绘制。

/**设置只解码视频关键帧*is_only_dec_key_frame: 1:表示只解码关键帧, 0:表示都解码, 默认是0*成功返回NT_ERC_OK*/NT_UINT32(NT_API *SetOnlyDecodeVideoKeyFrame)(NT_HANDLE handle, NT_INT32 is_only_dec_key_frame);

6. 缓冲时间设置

缓冲时间,顾名思义,缓存多少数据才开始播放,比如设置2000ms的buffer time,直播模式下,收到2秒数据后,才正常播放。

加大buffer time,会增大播放延迟,好处是,网络抖动的时候,流畅性更好。

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

7. 实时静音、实时音量调节

实时静音、实时音量调节顾名思义,播放端可以实时调整播放音量,或者直接静音掉,特别是多路播放场景下,非常有必要。

/*静音接口,1为静音,0为不静音*/NT_UINT32(NT_API *SetMute)(NT_HANDLE handle, NT_INT32 is_mute);/*设置播放音量, 范围是[0, 100], 0是静音,100是最大音量, 默认是100调用正确返回NT_ERC_OK*/NT_UINT32(NT_API *SetAudioVolume)(NT_HANDLE handle, NT_INT32 volume);

8. RTSP TCP-UDP模式设置、超时时间设置或模式切换

有的RTSP服务器或摄像机,只支持RTSP TCP模式或者UDP模式,这个时候,默认设置TCP、UDP模式就至关重要,此外,我们还设计支持如TCP或UDP模式收不到数据,在超时时间后,可以自动切换到UDP或TCP。

/*设置RTSP TCP 模式, 1为TCP, 0为UDP, 仅RTSP有效*/NT_UINT32(NT_API* SetRTSPTcpMode)(NT_HANDLE handle, NT_INT32 isUsingTCP);/*设置RTSP超时时间, timeout单位为秒,必须大于0*/NT_UINT32 (NT_API* SetRtspTimeout)(NT_HANDLE handle, NT_INT32 timeout);/*对于RTSP来说,有些可能支持rtp over udp方式,有些可能支持使用rtp over tcp方式. 为了方便使用,有些场景下可以开启自动尝试切换开关, 打开后如果udp无法播放,sdk会自动尝试tcp, 如果tcp方式播放不了,sdk会自动尝试udp.is_auto_switch_tcp_udp: 如果设置1的话, sdk将在tcp和udp之间尝试切换播放,如果设置为0,则不尝试切换.*/NT_UINT32 (NT_API* SetRtspAutoSwitchTcpUdp)(NT_HANDLE handle, NT_INT32 is_auto_switch_tcp_udp);

9. 快速启动

快速启动,主要是针对服务器缓存GOP的场景下,快速刷到最新的数据,确保画面的持续性。

/*设置秒开, 1为秒开, 0为不秒开*/NT_UINT32(NT_API* SetFastStartup)(NT_HANDLE handle, NT_INT32 isFastStartup);

10. 低延迟模式

低延迟模式下,设置buffer time为0,延迟更低,适用于比如需要操控控制的超低延迟场景下。

/*设置低延时播放模式,默认是正常播放模式mode: 1为低延时模式, 0为正常模式,其他只无效接口调用成功返回NT_ERC_OK*/NT_UINT32(NT_API* SetLowLatencyMode)(NT_HANDLE handle, NT_INT32 mode);

11. 视频view旋转、水平|垂直翻转

接口主要用于,比如原始的视频倒置等场景下,设备端无法调整时,通过播放端完成图像的正常角度播放。

/**上下反转(垂直反转)*is_flip: 1:表示反转, 0:表示不反转*/NT_UINT32(NT_API *SetFlipVertical)(NT_HANDLE handle, NT_INT32 is_flip);/**水平反转*is_flip: 1:表示反转, 0:表示不反转*/NT_UINT32(NT_API *SetFlipHorizontal)(NT_HANDLE handle, NT_INT32 is_flip);/*设置旋转,顺时针旋转degress: 设置0, 90, 180, 270度有效,其他值无效注意:除了0度,其他角度播放会耗费更多CPU接口调用成功返回NT_ERC_OK*/NT_UINT32(NT_API* SetRotation)(NT_HANDLE handle, NT_INT32 degress);

12. 设置实时回调下载速度

调用实时下载速度接口,通过设置下载速度时间间隔,和是否需要上报当前下载速度,实现APP层和底层SDK更友好的交互。

/*设置下载速度上报, 默认不上报下载速度is_report: 上报开关, 1: 表上报. 0: 表示不上报. 其他值无效.report_interval: 上报时间间隔(上报频率),单位是秒,最小值是1秒1次. 如果小于1且设置了上报,将调用失败注意:如果设置上报的话,请设置SetEventCallBack, 然后在回调函数里面处理这个事件.上报事件是:NT_SP_E_EVENT_ID_DOWNLOAD_SPEED这个接口必须在StartXXX之前调用成功返回NT_ERC_OK*/NT_UINT32(NT_API *SetReportDownloadSpeed)(NT_HANDLE handle,NT_INT32 is_report, NT_INT32 report_interval);/*主动获取下载速度speed: 返回下载速度,单位是Byte/s(注意:这个接口必须在startXXX之后调用,否则会失败)成功返回NT_ERC_OK*/NT_UINT32(NT_API *GetDownloadSpeed)(NT_HANDLE handle, NT_INT32* speed);

13. 实时快照

简单来说,播放过程中,是不是要存取当前的播放画面。

/*捕获图片file_name_utf8: 文件名称,utf8编码call_back_data: 回调时用户自定义数据call_back: 回调函数,用来通知用户截图已经完成或者失败成功返回 NT_ERC_OK只有在播放时调用才可能成功,其他情况下调用,返回错误.因为生成PNG文件比较耗时,一般需要几百毫秒,为防止CPU过高,SDK会限制截图请求数量,当超过一定数量时,调用这个接口会返回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);

调用实例如下:

void CSmartPlayerDlg::OnBnClickedButtonCaptureImage(){  if ( capture_image_path_.empty() )  {    AfxMessageBox(_T("请先设置保存截图文件的目录! 点击截图左边的按钮设置!"));    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)  {    // 发送截图请求成功  }  else if (NT_ERC_SP_TOO_MANY_CAPTURE_IMAGE_REQUESTS == ret)  {    // 通知用户延时    OutputDebugStringA("Too many capture image requests!!!\r\n");  }  else  {    // 其他失败  }}

14. 扩展录像操作

播放端录像,我们做的非常细化,比如可以只录制音频或者只录制视频,设置录像存储路径,设置单个文件size,如果非AAC数据,可以转AAC后再录像。

/** 设置是否录视频,默认的话,如果视频源有视频就录,没有就没得录, 但有些场景下可能不想录制视频,只想录音频,所以增加个开关* is_record_video: 1 表示录制视频, 0 表示不录制视频, 默认是1*/NT_UINT32(NT_API *SetRecorderVideo)(NT_HANDLE handle, NT_INT32 is_record_video);/** 设置是否录音频,默认的话,如果视频源有音频就录,没有就没得录, 但有些场景下可能不想录制音频,只想录视频,所以增加个开关* is_record_audio: 1 表示录制音频, 0 表示不录制音频, 默认是1*/NT_UINT32(NT_API *SetRecorderAudio)(NT_HANDLE handle, NT_INT32 is_record_audio);/*设置本地录像目录, 必须是英文目录,否则会失败*/NT_UINT32(NT_API *SetRecorderDirectory)(NT_HANDLE handle, NT_PCSTR dir);/*设置单个录像文件最大大小, 当超过这个值的时候,将切割成第二个文件size: 单位是KB(1024Byte), 当前范围是 [5MB-800MB], 超出将被设置到范围内*/NT_UINT32(NT_API *SetRecorderFileMaxSize)(NT_HANDLE handle, NT_UINT32 size);/*设置录像文件名生成规则*/NT_UINT32(NT_API *SetRecorderFileNameRuler)(NT_HANDLE handle, NT_SP_RecorderFileNameRuler* ruler);/*设置录像回调接口*/NT_UINT32(NT_API *SetRecorderCallBack)(NT_HANDLE handle,NT_PVOID call_back_data, SP_SDKRecorderCallBack call_back);/*设置录像时音频转AAC编码的开关, aac比较通用,sdk增加其他音频编码(比如speex, pcmu, pcma等)转aac的功能.is_transcode: 设置为1的话,如果音频编码不是aac,则转成aac, 如果是aac,则不做转换. 设置为0的话,则不做任何转换. 默认是0.注意: 转码会增加性能消耗*/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. 拉流回调编码后的数据(配合转发模块使用)

拉流回调编码后的数据,主要是为了配合转发模块使用,比如拉取rtsp流数据,直接转RTMP推送到RTMP服务。

/** 设置拉流时,吐视频数据的回调*/NT_UINT32(NT_API *SetPullStreamVideoDataCallBack)(NT_HANDLE handle,NT_PVOID call_back_data, SP_SDKPullStreamVideoDataCallBack call_back);/** 设置拉流时,吐音频数据的回调*/NT_UINT32(NT_API *SetPullStreamAudioDataCallBack)(NT_HANDLE handle,NT_PVOID call_back_data, SP_SDKPullStreamAudioDataCallBack call_back);/*设置拉流时音频转AAC编码的开关, aac比较通用,sdk增加其他音频编码(比如speex, pcmu, pcma等)转aac的功能.is_transcode: 设置为1的话,如果音频编码不是aac,则转成aac, 如果是aac,则不做转换. 设置为0的话,则不做任何转换. 默认是0.注意: 转码会增加性能消耗*/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用户数据回调或SEI数据回调

如发送端在264编码时,加了自定义的user data数据,可以通过以下接口实现数据回调,如需直接回调SEI数据,调下面SEI回调接口即可。

/*设置用户数据回调*/NT_UINT32(NT_API *SetUserDataCallBack)(NT_HANDLE handle,NT_PVOID call_back_data, NT_SP_SDKUserDataCallBack call_back);

调用实例如下:

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. 设置回调解码后YUV、RGB数据

如需对解码后的yuv或rgb数据,进行二次处理,如人脸识别等,可以通回调yuv rgb接口实现数据二次处理,对于Windows平台来说,如果设备不支持D3D,也可以数据回调上来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. 设置视频画面填充模式

设置视频画面的填充模式,如填充整个view、等比例填充view,如不设置,默认填充整个view。

相关接口设计如下:

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

总结

以上就是大牛直播SDK(​​官网​​)关于Windows平台RTSP、RTMP播放器接口设计需要参考的点,其他还有些,比如如果不支持D3D,GDI模式绘制,播放界面叠加实时文字,播放画面全屏等,这里就不再赘述,除Windows平台外,我们还同步开发了Linux、Android、iOS平台的RTSP、RTMP播放器,大多常规接口四个平台基本统一,延迟也都做到了毫秒级。对于大多数开发者来说,不一定需要实现上述所有部分,只要按照产品诉求,实现其中的30-40%就足够满足特定场景使用了。

一个好的播放器,特别是要满足低延迟稳定的播放(毫秒级延迟),需要注意的点远不止如此,感兴趣的开发者,可以参考blog其他文章。

标签: 下载速度 是否支持 填充模式

上一篇:天天快资讯丨场景编程集锦-懵懂的青春
下一篇:每日聚焦:【Redis 技术探索】「数据迁移实战」手把手教你如何实现在线 + 离线模式进行迁移 Redis 数据实战指南(scan模式迁移)