技术背景
我们在做Unity平台RTSP、RTMP播放器的时候,有公司提出来这样的技术需求,希望在头显播放全景的8K RTSP|RTMP直播流,8K的数据,对头显和播放器,都提出了新的要求,我们从几个方面,探讨下VR头显设备如何播放8K的RTSP|RTMP流数据:
一、播放器支持
- 兼容性:首先,RTSP|RTMP播放器需要支持8K分辨率的视频流。这意味着播放器必须能够解码8K视频,并在支持8K分辨率的显示设备上播放,这个不必多说,我们已经支持。
- 解码能力:播放器需要具备强大的解码能力,以处理8K视频流中的大量数据。这通常要求播放器使用高效的解码算法,并充分利用硬件加速功能(如GPU加速),这就需要头显支持8K的硬解码。
二、网络要求
- 带宽:8K视频流需要极高的网络带宽来支持实时传输。确保网络带宽足够大,以避免播放过程中出现卡顿、延迟或缓冲等问题,如果是内网环境下,基本不要纠结带宽问题。
- 稳定性:网络连接的稳定性也非常重要。不稳定的网络连接可能导致视频流中断或质量下降。
三、硬件要求
- 处理器与内存:VR头显播放8K的视频流,对VR头显的性能,提了很高的要求,比如说quest3,就是不错的选择。
四、播放步骤
- 选择RTSP播放器:我们的做法,是用大牛直播SDK的原生的RTSP|RTMP播放器,硬解码模式,回调解码后的YUV或RGB数据到unity,需要注意的是,由于8K的RTSP|RTMP流,数据量非常大,特别是解码后的数据,条件允许的情况下,需要尽可能少的减少拷贝。
技术实现
本文以大牛直播SDK的Android平台Unity3D RTSP|RTMP播放模块为例:
编辑
开始播放:
/* * SmartPlayerAndroidMono.cs * Author: daniusdk.com * QQ:89030985 */ public void Play() { if (is_running) { Debug.Log("已经在播放。。"); return; } //获取输入框的url string url = input_url_.text.Trim(); if (!url.StartsWith("rtmp://") && !url.StartsWith("rtsp://")) { videoUrl = "rtsp://admin:daniulive12345@192.168.0.120:554/h264/ch1/main/av_stream"; } else { videoUrl = url; } OpenPlayer(); if ( player_handle_ == 0 ) return; NT_U3D_Set_Game_Object(player_handle_, game_object_); /* ++ 播放前参数配置可加在此处 ++ */ int is_using_tcp = 0; //TCP/UDP模式设置 NT_U3D_SetRTSPTcpMode(player_handle_, is_using_tcp); int is_report = 0; int report_interval = 1; NT_U3D_SetReportDownloadSpeed(player_handle_, is_report, report_interval); //下载速度回调 NT_U3D_SetBuffer(player_handle_, play_buffer_time_); //设置buffer time NT_U3D_SetPlayerLowLatencyMode(player_handle_, is_low_latency_ ? 1 : 0); //设置是否启用低延迟模式 NT_U3D_SetMute(player_handle_, is_mute_ ? 1 : 0); //是否启动播放的时候静音 NT_U3D_SetAudioVolume(player_handle_, cur_audio_volume_); //设置播放音量 NT_U3D_SetVideoDecoderMode(player_handle_, is_hw_decode_ ? 1 : 0); //设置H.264软硬解模式 NT_U3D_SetVideoHevcDecoderMode(player_handle_, is_hw_decode_ ? 1 : 0); //设置H.265软硬解模式 int is_output = 1; int disable_use_image_planes = 0; bool is_supports_texture_format = SystemInfo.SupportsTextureFormat(TextureFormat.RG16); Debug.Log("is_supports_texture_format: " + is_supports_texture_format); int is_supported_multiple_format = is_supports_texture_format? 1:0; int max_images = 3; int buffer_pool_max_size = 0; NT_U3D_SetImageReaderOutput(player_handle_, is_output, disable_use_image_planes, is_supported_multiple_format, max_images, buffer_pool_max_size); //硬解码image reader int is_fast_startup = 1; NT_U3D_SetFastStartup(player_handle_, is_fast_startup); //设置快速启动模式 int rtsp_timeout = 10; NT_U3D_SetRTSPTimeout(player_handle_, rtsp_timeout); //设置RTSP超时时间 int is_auto_switch_tcp_udp = 1; NT_U3D_SetRTSPAutoSwitchTcpUdp(player_handle_, is_auto_switch_tcp_udp); //设置TCP/UDP模式自动切换 int is_audiotrack = 1; NT_U3D_SetAudioOutputType(player_handle_, is_audiotrack); //设置音频输出模式: if 0: 自动选择; if with 1: audiotrack模式 NT_U3D_SetUrl(player_handle_, videoUrl); /* -- 播放前参数配置可加在此处 -- */ int flag = NT_U3D_StartPlay(player_handle_); if (flag == DANIULIVE_RETURN_OK) { is_need_get_frame_ = true; Debug.Log("播放成功"); } else { is_need_get_frame_ = false; Debug.LogError("播放失败"); } is_running = true; }
对应的OpenPlayer()实现如下:
private void OpenPlayer() { if ( java_obj_cur_activity_ == null ) { Debug.LogError("getApplicationContext is null"); return; } player_handle_ = NT_U3D_Open(); if (player_handle_ != 0) Debug.Log("open success"); else Debug.LogError("open fail"); }
关闭Player:
private void ClosePlayer() { is_need_get_frame_ = false; is_need_init_texture_ = false; int flag = NT_U3D_StopPlay(player_handle_); if (flag == DANIULIVE_RETURN_OK) { Debug.Log("停止成功"); } else { Debug.LogError("停止失败"); } flag = NT_U3D_Close(player_handle_); if (flag == DANIULIVE_RETURN_OK) { Debug.Log("关闭成功"); } else { Debug.LogError("关闭失败"); } player_handle_ = 0; NT_U3D_UnInit(); is_running = false; video_format_ = VideoFrame.FORMAT_UNKNOWN; video_width_ = 0; video_height_ = 0; }
Update刷新数据:
private void Update() { if (!is_need_get_frame_) return; if (player_handle_ == 0) return; AndroidJavaObject u3d_video_frame_obj = NT_U3D_GetVideoFrame(player_handle_); if (u3d_video_frame_obj == null) { return; } VideoFrame converted_video_frame = ConvertToVideoFrame(u3d_video_frame_obj); if (converted_video_frame == null) { u3d_video_frame_obj.Call("release"); u3d_video_frame_obj = null; return; } if (!is_need_init_texture_) { if (converted_video_frame.format_ != video_format_) { is_need_init_texture_ = true; } else if (converted_video_frame.width_ != video_width_ || converted_video_frame.height_ != video_height_ || converted_video_frame.stride0_ != y_row_bytes_ || converted_video_frame.stride1_ != u_row_bytes_ || converted_video_frame.stride2_ != v_row_bytes_) { is_need_init_texture_ = true; } } if (is_need_init_texture_) { if (InitYUVTexture(converted_video_frame)) { is_need_init_texture_ = false; } } UpdateYUVTexture(converted_video_frame); converted_video_frame.java_frame_obj_ = null; converted_video_frame = null; u3d_video_frame_obj.Call("release"); u3d_video_frame_obj = null; }
总结
VR头显如果需要播放8K的RTSP或RTSP流,对硬件和网络的要求非常高,因此在实际应用中可能会遇到一些挑战。通过实际测试,在quest3头显,配合我们的RTSP|RTMP播放器,在unity下,可以实现毫秒级延迟的8K视频数据播放,以满足平衡操控等对实时性要求非常高的使用场景,感兴趣的开发者,可以单独跟我探讨。