技术背景
我们在对接RTSP、RTMP推拉流播放的时候,开发者提到这样的技术诉求,他们在用于安检等场景的时候,采集分辨率甚至需要4K+,帧率需要达到50帧以上,码率也非常高,这就对推流和播放模块,提出了更高的要求。
以播放端为例,如果需要播放50帧以上高帧率高码率高分辨率的RTSP或RTMP流,以下是一些关键的步骤和考虑因素:
1. 确保视频源支持高帧率
- 视频源设备:确保你的视频源设备(如摄像头、摄像机等)能够输出50帧的视频流。
- 编码器设置:如果视频流在传输前经过编码器,需要确保编码器支持并配置为输出50帧的视频。
2. 选择合适的RTSP播放器
- 播放器性能:选择一款性能强大的RTSP|RTMP播放器,能够处理高帧率视频流的解码和渲染。
- 硬解码支持:超高帧率码率和分辨率的RTSP|RTMP视频流播放,由于解码非常耗费性能,建议采用硬解码模式。
3. 网络条件
- 带宽:高帧率视频流需要更高的带宽来保持流畅的播放。确保你的网络环境能够提供足够的带宽来支持50帧的视频流。
- 延迟:低延迟是直播和实时视频传输的重要要求。
4. 播放器配置
- 软硬解码设置:根据设备性能,选择软解或硬解码。
- 缓冲设置:合理配置播放器的缓冲策略,以应对网络波动和视频流中的突发情况。
5. 优化和调试
- 性能监控:使用性能监控工具来评估播放器的性能,包括CPU使用率、内存占用和帧率等。
- 调试工具:利用播放器的调试工具来诊断和解决可能出现的问题,如解码错误、同步问题等。
6. 跨平台兼容性
- 多平台支持:如果你需要在不同的平台上播放高帧率视频(如Windows、Linux、Android、iOS等),需要确保播放器在这些平台上都有良好的表现和兼容性。
技术实现
以大牛直播SDK的SmartPlayer为例,目前实现的功能如下,如不单独介绍,Windows、Linux、Android、iOS均支持,现场测试,超过1080p,50帧以上,依然可以达到150-300ms延迟:
- [支持播放协议]高稳定、超低延迟、业内首屈一指的RTSP直播播放器SDK;
- [多实例播放]支持多实例播放;
- [事件回调]支持网络状态、buffer状态等回调;
- [视频格式]支持H.265、H.264,此外,还支持RTSP MJPEG播放;
- [音频格式]支持AAC/PCMA/PCMU;
- [H.264/H.265软解码]支持H.264/H.265软解;
- [H.264硬解码]Windows/Android/iOS支持特定机型H.264硬解;
- [H.265硬解]Windows/Android/iOS支持特定机型H.265硬解;
- [H.264/H.265硬解码]Android支持设置Surface模式硬解和普通模式硬解码;
- [RTSP模式设置]支持RTSP TCP/UDP模式设置;
- [RTSP TCP/UDP自动切换]支持RTSP TCP、UDP模式自动切换;
- [RTSP超时设置]支持RTSP超时时间设置,单位:秒;
- [RTSP 401认证处理]支持上报RTSP 401事件,如URL携带鉴权信息,会自动处理;
- [缓冲时间设置]支持buffer time设置;
- [首屏秒开]支持首屏秒开模式;
- [复杂网络处理]支持断网重连等各种网络环境自动适配;
- [快速切换URL]支持播放过程中,快速切换其他URL,内容切换更快;
- [音视频多种render机制]Android平台,视频:surfaceview/OpenGL ES,音频:AudioTrack/OpenSL ES;
- [实时静音]支持播放过程中,实时静音/取消静音;
- [实时音量调节]支持播放过程中实时调节音量;
- [实时快照]支持播放过程中截取当前播放画面;
- [只播关键帧]Windows平台支持实时设置是否只播放关键帧;
- [渲染角度]支持0°,90°,180°和270°四个视频画面渲染角度设置;
- [渲染镜像]支持水平反转、垂直反转模式设置;
- [等比例缩放]支持图像等比例缩放绘制(Android设置surface模式硬解模式不支持);
- [实时下载速度更新]支持当前下载速度实时回调(支持设置回调时间间隔);
- [解码前视频数据回调]支持H.264/H.265数据回调;
- [解码后视频数据回调]支持解码后YUV/RGB数据回调;
- [解码前音频数据回调]支持AAC/PCMA/PCMU数据回调;
- [音视频自适应]支持播放过程中,音视频信息改变后自适应;
- [扩展录像功能]完美支持和录像SDK组合使用。
对应Demo:
- Windows测试程序:SmartPlayer.exe;
- Windows C++工程:WIN-PlayerSDK-CPP-Demo;
- Windows C#工程:WIN-PlayerSDK-CSharp-Demo;
- Linux工程:single_playerdemo|multi_playerdemo;
- Android工程:SmartPlayerV2;
- iOS工程:SmartiOSPlayerV2。
以Winodws平台为例,开始播放实现如下:
/* * SmartPlayerForm.cs * Author:daniusdk.com * QQ 89030985 */ private void btn_play_Click(object sender, EventArgs e) { if (btn_play.Text == "播放") { if (!is_recording_) { if (!InitCommonSDKParam()) { MessageBox.Show("设置参数错误!"); return; } } //video resolution callback video_size_call_back_ = new SP_SDKVideoSizeCallBack(SP_SDKVideoSizeHandle); NTSmartPlayerSDK.NT_SP_SetVideoSizeCallBack(player_handle_, IntPtr.Zero, video_size_call_back_); bool is_support_d3d_render = false; Int32 in_support_d3d_render = 0; if (NT.NTBaseCodeDefine.NT_ERC_OK == NTSmartPlayerSDK.NT_SP_IsSupportD3DRender(player_handle_, playWnd.Handle, ref in_support_d3d_render)) { if (1 == in_support_d3d_render) { is_support_d3d_render = true; } } //is_support_d3d_render = false; if (is_support_d3d_render) { is_gdi_render_ = false; // 支持d3d绘制的话,就用D3D绘制 NTSmartPlayerSDK.NT_SP_SetRenderWindow(player_handle_, playWnd.Handle); if (btn_check_render_scale_mode.Checked) { NTSmartPlayerSDK.NT_SP_SetRenderScaleMode(player_handle_, 1); } else { NTSmartPlayerSDK.NT_SP_SetRenderScaleMode(player_handle_, 0); } } else { is_gdi_render_ = true; playWnd.Visible = false; // 不支持D3D就让播放器吐出数据来,用GDI绘制 //video frame callback (YUV/RGB) //format请参见 NT_SP_E_VIDEO_FRAME_FORMAT,如需回调YUV,请设置为 NT_SP_E_VIDEO_FRAME_FROMAT_I420 video_frame_call_back_ = new SP_SDKVideoFrameCallBack(SetVideoFrameCallBack); NTSmartPlayerSDK.NT_SP_SetVideoFrameCallBack(player_handle_, (Int32)NT.NTSmartPlayerDefine.NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FORMAT_RGB32, IntPtr.Zero, video_frame_call_back_); } user_data_call_back_ = new SP_SDKUserDataCallBack(SDKUserDataCallBack); NTSmartPlayerSDK.NT_SP_SetUserDataCallBack(player_handle_, IntPtr.Zero, user_data_call_back_); if (btn_check_add_osd.Checked) { DrawOSD("叠加字符展示"); } else { DrawOSD(null); } UInt32 ret_start = NTSmartPlayerSDK.NT_SP_StartPlay(player_handle_); if (ret_start != 0) { MessageBox.Show("播放失败.."); return; } //转发相关,具体参见转发demo /* pull_stream_video_data_call_back_ = new SP_SDKPullStreamVideoDataCallBack(OnVideoDataHandle); pull_stream_audio_data_call_back_ = new SP_SDKPullStreamAudioDataCallBack(OnAudioDataHandle); NTSmartPlayerSDK.NT_SP_SetPullStreamVideoDataCallBack(pull_handle_, IntPtr.Zero, pull_stream_video_data_call_back_); NTSmartPlayerSDK.NT_SP_SetPullStreamAudioDataCallBack(pull_handle_, IntPtr.Zero, pull_stream_audio_data_call_back_); UInt32 ret = NTSmartPlayerSDK.NT_SP_StartPullStream(pull_handle_); */ btn_capture_image.Enabled = true; is_playing_ = true; btn_play.Text = "停止"; } else { StopPlayback(); } }
停止播放:
private void StopPlayback() { if (player_handle_ == IntPtr.Zero) { return; } if (is_playing_) { NTSmartPlayerSDK.NT_SP_StopPlay(player_handle_); is_playing_ = false; //playWnd.Invalidate(); //清空最后一帧数据,如不加,默认保留最后一帧画面 } if (cur_video_frame_.plane0_ != IntPtr.Zero) { Marshal.FreeHGlobal(cur_video_frame_.plane0_); cur_video_frame_.plane0_ = IntPtr.Zero; } btn_full_screen_.Enabled = false; btn_capture_image.Enabled = false; textBox_resolution.Text = ""; btn_play.Text = "播放"; lable_cur_status_txt.Text = ""; edit_player_msg_.Text = ""; DrawOSD(null); }
如需要实时快照:
private void btn_capture_image_Click(object sender, EventArgs e) { if ( String.IsNullOrEmpty(capture_image_path_) ) { MessageBox.Show("请先设置保存截图文件的目录! 点击截图左边的按钮设置!"); return; } if ( player_handle_ == IntPtr.Zero ) { return; } if ( !is_playing_) { MessageBox.Show("请在播放状态下截图!"); return; } String name = capture_image_path_ + "\\" + DateTime.Now.ToString("hh-mm-ss") + ".png"; byte[] buffer1 = Encoding.Default.GetBytes(name); byte[] buffer2 = Encoding.Convert(Encoding.Default, Encoding.UTF8, buffer1, 0, buffer1.Length); byte[] buffer3 = new byte[buffer2.Length + 1]; buffer3[buffer2.Length] = 0; Array.Copy(buffer2, buffer3, buffer2.Length); IntPtr file_name_ptr = Marshal.AllocHGlobal(buffer3.Length); Marshal.Copy(buffer3, 0, file_name_ptr, buffer3.Length); capture_image_call_back_ = new SP_SDKCaptureImageCallBack(SDKCaptureImageCallBack); UInt32 ret = NTSmartPlayerSDK.NT_SP_CaptureImage(player_handle_, file_name_ptr, IntPtr.Zero, capture_image_call_back_); Marshal.FreeHGlobal(file_name_ptr); if (NT.NTBaseCodeDefine.NT_ERC_OK == ret) { // 发送截图请求成功 } else if ((UInt32)NT.NTSmartPlayerDefine.SP_E_ERROR_CODE.NT_ERC_SP_TOO_MANY_CAPTURE_IMAGE_REQUESTS == ret) { // 通知用户延时 MessageBox.Show("Too many capture image requests!"); } else { // 其他失败 } }
如果需要实时录像:
private void btn_record_Click(object sender, EventArgs e) { if (player_handle_ == IntPtr.Zero) return; if (btn_record.Text == "录像") { if (!is_rec_video_ && !is_rec_audio_) { MessageBox.Show("音频录制选项和视频录制选项至少需要选择一个!"); return; } if (!is_playing_) { if (!InitCommonSDKParam()) { MessageBox.Show("设置参数错误!"); return; } } NTSmartPlayerSDK.NT_SP_SetRecorderVideo(player_handle_, is_rec_video_ ? 1 : 0); NTSmartPlayerSDK.NT_SP_SetRecorderAudio(player_handle_, is_rec_audio_ ? 1 : 0); UInt32 ret = NTSmartPlayerSDK.NT_SP_SetRecorderDirectoryW(player_handle_, rec_dir_); if (NT.NTBaseCodeDefine.NT_ERC_OK != ret) { MessageBox.Show("设置录像目录失败"); return; } NTSmartPlayerSDK.NT_SP_SetRecorderFileMaxSize(player_handle_, max_file_size_); NT_SP_RecorderFileNameRuler rec_name_ruler = new NT_SP_RecorderFileNameRuler(); rec_name_ruler.type_ = 0; rec_name_ruler.file_name_prefix_ = rec_name_file_prefix_; rec_name_ruler.append_date_ = is_append_date_ ? 1 : 0; rec_name_ruler.append_time_ = is_append_time_ ? 1 : 0; NTSmartPlayerSDK.NT_SP_SetRecorderFileNameRuler(player_handle_, ref rec_name_ruler); record_call_back_ = new SP_SDKRecorderCallBack(SDKRecorderCallBack); NTSmartPlayerSDK.NT_SP_SetRecorderCallBack(player_handle_, IntPtr.Zero, record_call_back_); NTSmartPlayerSDK.NT_SP_SetRecorderAudioTranscodeAAC(player_handle_, is_audio_transcode_aac_ ? 1 : 0); if (NT.NTBaseCodeDefine.NT_ERC_OK != NTSmartPlayerSDK.NT_SP_StartRecorder(player_handle_)) { MessageBox.Show("录像失败!"); return; } btn_record.Text = "停止录像"; is_recording_ = true; } else { StopRecorder(); } }
停止录像:
private void StopRecorder() { if (player_handle_ == IntPtr.Zero) { return; } NTSmartPlayerSDK.NT_SP_StopRecorder(player_handle_); btn_record.Text = "录像"; is_recording_ = false; }
总结
RTSP|RTMP播放器,如果需要低延迟的播放50帧以上的高帧率码率的数据,需要有好的解码性能、做好音视频的同步处理,确保播放器的每个环节可控。才可以达到高稳定、低延迟的播放体验,感兴趣的开发者,可以单独跟我沟通讨论。