• VR头显如何低延迟播放8K的RTSP|RTMP流


    技术背景

    我们在做Unity平台RTSP、RTMP播放器的时候,有公司提出来这样的技术需求,希望在头显播放全景的8K RTSP|RTMP直播流,8K的数据,对头显和播放器,都提出了新的要求,我们从几个方面,探讨下VR头显设备如何播放8K的RTSP|RTMP流数据:

    一、播放器支持

    1. 兼容性:首先,RTSP|RTMP播放器需要支持8K分辨率的视频流。这意味着播放器必须能够解码8K视频,并在支持8K分辨率的显示设备上播放,这个不必多说,我们已经支持。
    2. 解码能力:播放器需要具备强大的解码能力,以处理8K视频流中的大量数据。这通常要求播放器使用高效的解码算法,并充分利用硬件加速功能(如GPU加速),这就需要头显支持8K的硬解码。

    二、网络要求

    1. 带宽:8K视频流需要极高的网络带宽来支持实时传输。确保网络带宽足够大,以避免播放过程中出现卡顿、延迟或缓冲等问题,如果是内网环境下,基本不要纠结带宽问题。
    2. 稳定性:网络连接的稳定性也非常重要。不稳定的网络连接可能导致视频流中断或质量下降。

    三、硬件要求

    1. 处理器与内存:VR头显播放8K的视频流,对VR头显的性能,提了很高的要求,比如说quest3,就是不错的选择。

    四、播放步骤

    1. 选择RTSP播放器:我们的做法,是用大牛直播SDK的原生的RTSP|RTMP播放器,硬解码模式,回调解码后的YUV或RGB数据到unity,需要注意的是,由于8K的RTSP|RTMP流,数据量非常大,特别是解码后的数据,条件允许的情况下,需要尽可能少的减少拷贝。

    技术实现

    本文以大牛直播SDK的Android平台Unity3D RTSP|RTMP播放模块为例:

    开始播放:

    1. /*
    2. * SmartPlayerAndroidMono.cs
    3. * Author: daniusdk.com
    4. * QQ:89030985
    5. */
    6. public void Play()
    7. {
    8. if (is_running)
    9. {
    10. Debug.Log("已经在播放。。");
    11. return;
    12. }
    13. //获取输入框的url
    14. string url = input_url_.text.Trim();
    15. if (!url.StartsWith("rtmp://") && !url.StartsWith("rtsp://"))
    16. {
    17. videoUrl = "rtsp://admin:daniulive12345@192.168.0.120:554/h264/ch1/main/av_stream";
    18. }
    19. else
    20. {
    21. videoUrl = url;
    22. }
    23. OpenPlayer();
    24. if ( player_handle_ == 0 )
    25. return;
    26. NT_U3D_Set_Game_Object(player_handle_, game_object_);
    27. /* ++ 播放前参数配置可加在此处 ++ */
    28. int is_using_tcp = 0; //TCP/UDP模式设置
    29. NT_U3D_SetRTSPTcpMode(player_handle_, is_using_tcp);
    30. int is_report = 0;
    31. int report_interval = 1;
    32. NT_U3D_SetReportDownloadSpeed(player_handle_, is_report, report_interval); //下载速度回调
    33. NT_U3D_SetBuffer(player_handle_, play_buffer_time_); //设置buffer time
    34. NT_U3D_SetPlayerLowLatencyMode(player_handle_, is_low_latency_ ? 1 : 0); //设置是否启用低延迟模式
    35. NT_U3D_SetMute(player_handle_, is_mute_ ? 1 : 0); //是否启动播放的时候静音
    36. NT_U3D_SetAudioVolume(player_handle_, cur_audio_volume_); //设置播放音量
    37. NT_U3D_SetVideoDecoderMode(player_handle_, is_hw_decode_ ? 1 : 0); //设置H.264软硬解模式
    38. NT_U3D_SetVideoHevcDecoderMode(player_handle_, is_hw_decode_ ? 1 : 0); //设置H.265软硬解模式
    39. int is_output = 1;
    40. int disable_use_image_planes = 0;
    41. bool is_supports_texture_format = SystemInfo.SupportsTextureFormat(TextureFormat.RG16);
    42. Debug.Log("is_supports_texture_format: " + is_supports_texture_format);
    43. int is_supported_multiple_format = is_supports_texture_format? 1:0;
    44. int max_images = 3;
    45. int buffer_pool_max_size = 0;
    46. NT_U3D_SetImageReaderOutput(player_handle_, is_output, disable_use_image_planes, is_supported_multiple_format, max_images, buffer_pool_max_size); //硬解码image reader
    47. int is_fast_startup = 1;
    48. NT_U3D_SetFastStartup(player_handle_, is_fast_startup); //设置快速启动模式
    49. int rtsp_timeout = 10;
    50. NT_U3D_SetRTSPTimeout(player_handle_, rtsp_timeout); //设置RTSP超时时间
    51. int is_auto_switch_tcp_udp = 1;
    52. NT_U3D_SetRTSPAutoSwitchTcpUdp(player_handle_, is_auto_switch_tcp_udp); //设置TCP/UDP模式自动切换
    53. int is_audiotrack = 1;
    54. NT_U3D_SetAudioOutputType(player_handle_, is_audiotrack); //设置音频输出模式: if 0: 自动选择; if with 1: audiotrack模式
    55. NT_U3D_SetUrl(player_handle_, videoUrl);
    56. /* -- 播放前参数配置可加在此处 -- */
    57. int flag = NT_U3D_StartPlay(player_handle_);
    58. if (flag == DANIULIVE_RETURN_OK)
    59. {
    60. is_need_get_frame_ = true;
    61. Debug.Log("播放成功");
    62. }
    63. else
    64. {
    65. is_need_get_frame_ = false;
    66. Debug.LogError("播放失败");
    67. }
    68. is_running = true;
    69. }

    对应的OpenPlayer()实现如下:

    1. private void OpenPlayer()
    2. {
    3. if ( java_obj_cur_activity_ == null )
    4. {
    5. Debug.LogError("getApplicationContext is null");
    6. return;
    7. }
    8. player_handle_ = NT_U3D_Open();
    9. if (player_handle_ != 0)
    10. Debug.Log("open success");
    11. else
    12. Debug.LogError("open fail");
    13. }

    关闭Player:

    1. private void ClosePlayer()
    2. {
    3. is_need_get_frame_ = false;
    4. is_need_init_texture_ = false;
    5. int flag = NT_U3D_StopPlay(player_handle_);
    6. if (flag == DANIULIVE_RETURN_OK)
    7. {
    8. Debug.Log("停止成功");
    9. }
    10. else
    11. {
    12. Debug.LogError("停止失败");
    13. }
    14. flag = NT_U3D_Close(player_handle_);
    15. if (flag == DANIULIVE_RETURN_OK)
    16. {
    17. Debug.Log("关闭成功");
    18. }
    19. else
    20. {
    21. Debug.LogError("关闭失败");
    22. }
    23. player_handle_ = 0;
    24. NT_U3D_UnInit();
    25. is_running = false;
    26. video_format_ = VideoFrame.FORMAT_UNKNOWN;
    27. video_width_ = 0;
    28. video_height_ = 0;
    29. }

    Update刷新数据:

    1. private void Update()
    2. {
    3. if (!is_need_get_frame_)
    4. return;
    5. if (player_handle_ == 0)
    6. return;
    7. AndroidJavaObject u3d_video_frame_obj = NT_U3D_GetVideoFrame(player_handle_);
    8. if (u3d_video_frame_obj == null)
    9. {
    10. return;
    11. }
    12. VideoFrame converted_video_frame = ConvertToVideoFrame(u3d_video_frame_obj);
    13. if (converted_video_frame == null)
    14. {
    15. u3d_video_frame_obj.Call("release");
    16. u3d_video_frame_obj = null;
    17. return;
    18. }
    19. if (!is_need_init_texture_)
    20. {
    21. if (converted_video_frame.format_ != video_format_)
    22. {
    23. is_need_init_texture_ = true;
    24. }
    25. else if (converted_video_frame.width_ != video_width_
    26. || converted_video_frame.height_ != video_height_
    27. || converted_video_frame.stride0_ != y_row_bytes_
    28. || converted_video_frame.stride1_ != u_row_bytes_
    29. || converted_video_frame.stride2_ != v_row_bytes_)
    30. {
    31. is_need_init_texture_ = true;
    32. }
    33. }
    34. if (is_need_init_texture_)
    35. {
    36. if (InitYUVTexture(converted_video_frame))
    37. {
    38. is_need_init_texture_ = false;
    39. }
    40. }
    41. UpdateYUVTexture(converted_video_frame);
    42. converted_video_frame.java_frame_obj_ = null;
    43. converted_video_frame = null;
    44. u3d_video_frame_obj.Call("release");
    45. u3d_video_frame_obj = null;
    46. }

    总结

    VR头显如果需要播放8K的RTSP或RTSP流,对硬件和网络的要求非常高,因此在实际应用中可能会遇到一些挑战。通过实际测试,在quest3头显,配合我们的RTSP|RTMP播放器,在unity下,可以实现毫秒级延迟的8K视频数据播放,以满足平衡操控等对实时性要求非常高的使用场景,感兴趣的开发者,可以单独跟我探讨。

  • 相关阅读:
    基于 idea 将 springboot 应用部署到 docker环境
    【LeetCode】每日一题 2023_11_14 阈值距离内邻居最少的城市(Floyd 最短路算法)
    微机原理与接口技术复习题
    掌握rpc、grpc并探究内在本质
    PWM杂项
    element - el-table动态设置高度?不滚动了?
    MySQL IO相关配置参数
    4种常见的鉴权方式及说明
    ubuntu 清理缓存
    ESP32网络开发实例-使用NTP获取当前时间
  • 原文地址:https://blog.csdn.net/renhui1112/article/details/140357161