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

本文涉及的产品
视觉智能开放平台,图像资源包5000点
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,视频资源包5000点
简介: 本文探讨了在Unity平台上实现VR头显播放8K RTSP/RTMP直播流的技术方案。需确保播放器兼容8K并具高效解码能力,利用GPU加速;网络须稳定且带宽充足;VR头显如Quest 3需拥有高性能处理器与内存。文中以大牛直播SDK为例,详细介绍了播放流程及参数设置,最终实现在Quest 3上毫秒级延迟的8K视频播放,适用于高实时性需求的应用场景。

技术背景

我们在做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播放模块为例:

image.gif 编辑

开始播放:

/*
 * 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;  
}

image.gif

对应的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");
}

image.gif

关闭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;
}

image.gif

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;
}

image.gif

总结

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

相关文章
|
1月前
|
编解码 vr&ar C#
### 超时空穿越!揭秘如何在VR头显端实现毫秒级低延迟的RTSP|RTMP播放,打造沉浸式直播新纪元!
【8月更文挑战第14天】随着VR技术进步,VR头显在直播领域的应用日益广泛。为提升用户体验,本文介绍如何在VR头显上实现低延迟的RTSP/RTMP播放,包括环境搭建、依赖引入、客户端初始化、视频解码与渲染及优化技巧,并提供C#示例代码,帮助开发者快速上手,打造流畅直播体验。
27 2
|
1月前
|
编解码 网络协议 vr&ar
Android平台下VR头显如何低延迟播放4K以上超高分辨率RTSP|RTMP流
这段内容讲述了VR头显中实现高分辨率视频播放的技术背景与实现方法,并强调了其重要性。高分辨率对于提升VR体验至关重要,它能提供更清晰的画面、增强沉浸感、补偿透镜放大效应,并维持宽广视场角下的图像质量。文中提到的大牛直播SDK具备极低的延迟(200-400ms),支持多种协议与格式,并具有丰富的功能特性,如多实例播放、事件回调、视频及音频格式支持等。此外,提供了基于Unity的播放器示例代码,展示了如何配置播放参数并开始播放。最后,作者指出此类技术在远程控制、虚拟仿真等应用场景中的重要意义。
|
12月前
|
编解码 数据处理 vr&ar
VR头显Unity下如何实现毫秒级延迟的RTMP或RTSP播放?
VR头显Unity下如何实现毫秒级延迟的RTMP或RTSP播放?
214 1
|
数据采集 编解码 vr&ar
Android平台实现VR头显Unity下音视频数据RTMP推送
随着技术发展的日新月异,虚拟现实产业已经从过去的探索期,自2020年起,慢慢过渡到高速发展期,随着5G时代的到来,大带宽高可靠低延迟网络环境,为虚拟现实产业提供了很好的网络保障,虚拟现实在越来越多的场景下有了应用价值,典型场景如工业互联网、虚拟仿真、文旅文博、智慧交通、智慧能源、智慧医疗、智慧校园、智慧农业等。同事,行业也对清晰度、流畅性和交互感也提出了更高的要求。本文从Android平台的采集推送为例,介绍下基于头显或类似终端的低延迟解决方案。
|
vr&ar 开发工具 图形学
如何在VR头显端实现低延迟的RTSP或RTMP播放
VR(虚拟现实技术)给我们带来身临其境的视觉体验,广泛的应用于城市规划、教育培训、工业仿真、房地产、水利电力、室内设计、文旅、军事等众多领域
|
vr&ar
眼神有杀气!这是全球首款搭载眼球追踪技术的 VR 头显
VR 的交互方式有很多,目前最常用的主要有:基本的按键手柄操作、体感交互手柄操作、手势识别技术等,另外最原始的就是通过头部转动控制屏幕“指针”来进行各种点击事件。
156 0
眼神有杀气!这是全球首款搭载眼球追踪技术的 VR 头显
|
机器学习/深度学习 存储 算法
全家要用机器人上货了?!50公里外VR操控,延迟不到一秒,网友:不愧是你,霓虹
全家要用机器人上货了?!50公里外VR操控,延迟不到一秒,网友:不愧是你,霓虹
151 0
E3新秀Immerex发布VRG-9020,会是一款颠覆VR行业的头显吗?
Immerex公司在E3大展上发布颠覆性的VR头显VRG-9020,可能会成为移动VR设备中的“ipod”吗?
341 0
Facebook又要重组VR部门,并将于明年推出新一代Rift头显
这次重组并未进行裁员,只是将现有技术人员整合起来。
285 0
|
3月前
|
人工智能 编解码 5G
虚拟现实(VR)与增强现实(AR)的融合:开启全新交互时代
【6月更文挑战第17天】虚拟现实(VR)与增强现实(AR)融合成混合现实(MR),打造全新交互体验。MR结合VR的沉浸感和AR的现实增强,应用于教育、游戏、设计和营销,带来创新教学方式、沉浸式游戏体验和高效设计工具。尽管面临技术挑战,随着5G和AI的发展,MR有望引领未来交互的革命。