Unity平台如何实现RTSP转RTMP推送?

简介: Unity平台下,RTSP、RTMP播放和RTMP推送,甚至包括轻量级RTSP服务这块都不再赘述,今天探讨的一位开发者提到的问题,如果在Unity下,实现RTSP播放的同时,随时转RTMP推送出去?

技术背景

Unity平台下,RTSP、RTMP播放和RTMP推送,甚至包括轻量级RTSP服务这块都不再赘述,今天探讨的一位开发者提到的问题,如果在Unity下,实现RTSP播放的同时,随时转RTMP推送出去?


RTSP转RTMP,在原生环境下老早已经有了,这里,其实就是把原生的挪到Unity即可,相关流程如下:

0b3fce3f9afe4855882ce5e864816e94.png

技术实现

本文以Windows平台为例,在RTSP播放模块的基础上,加个RTSP转RTMP推送模块,废话不多说,上代码:

实时播放、停止播放

/*
 * SmartPlayerWinMono.cs.cs
 * 
 * Author: daniusdk.com
 * Created on 2017/04/19.
 */
public void StartPlayer(int sel)
{
  Debug.Log("StartPlayer++, sel: " + sel);
  if (videoctrl[sel].is_playing_)
  {
    Debug.Log("StartPlayer, already started.. sel: " + sel);
    return;
  }
  lock (videoctrl[sel].frame_lock_)
  {
    videoctrl[sel].cur_video_frame_ = null;
  }
  if (!videoctrl[sel].is_recording_ && !videoctrl[sel].is_pulling_)
  {
    if (!OpenPlayerHandle(sel))
    {
      Debug.LogError("call OpenPlayerHandle failed, sel:" + sel);
      return;
    }
  }
  if (is_enable_hardware_decoder_)
  {
    NTSmartPlayerSDK.NT_SP_SetH264HardwareDecoder(videoctrl[sel].player_handle_, is_support_h264_hardware_decoder_ ? 1 : 0, 0);
    NTSmartPlayerSDK.NT_SP_SetH265HardwareDecoder(videoctrl[sel].player_handle_, is_support_h265_hardware_decoder_ ? 1 : 0, 0);
  }
  else
  {
    NTSmartPlayerSDK.NT_SP_SetH264HardwareDecoder(videoctrl[sel].player_handle_, 0, 0);
    NTSmartPlayerSDK.NT_SP_SetH265HardwareDecoder(videoctrl[sel].player_handle_, 0, 0);
  }
  //video frame callback (YUV/RGB)
  videoctrl[sel].sdk_video_frame_call_back_ = new VideoControl.SetVideoFrameCallBack(SDKVideoFrameCallBack);
  videoctrl[sel].video_frame_call_back_ = new SP_SDKVideoFrameCallBack(NT_SP_SetVideoFrameCallBack);
  NTSmartPlayerSDK.NT_SP_SetVideoFrameCallBack(videoctrl[sel].player_handle_, (Int32)NT.NTSmartPlayerDefine.NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FROMAT_I420, window_handle_, videoctrl[sel].video_frame_call_back_);
  UInt32 flag = NTSmartPlayerSDK.NT_SP_StartPlay(videoctrl[sel].player_handle_);
  if (flag == DANIULIVE_RETURN_OK)
  {
    videoctrl[sel].is_need_get_frame_ = true;
    Debug.Log("NT_SP_StartPlay succeed, sel:" + sel);
  }
  else
  {
    videoctrl[sel].is_need_get_frame_ = false;
    Debug.LogError("NT_SP_StartPlay failed, sel:" + sel);
  }
  videoctrl[sel].is_playing_ = true;
}
private void StopPlayer(int sel)
{
  Debug.Log("StopPlayer++, sel: " + sel);
  videoctrl[sel].is_need_get_frame_ = false;
  videoctrl[sel].is_need_init_texture_ = false;
  if (videoctrl[sel].player_handle_ == IntPtr.Zero)
  {
    return;
  }
  UInt32 flag = NTSmartPlayerSDK.NT_SP_StopPlay(videoctrl[sel].player_handle_);
  if (flag == DANIULIVE_RETURN_OK)
  {
    Debug.Log("call NT_SP_StopPlay succeed, sel: " + sel);
  }
  else
  {
    Debug.LogError("call NT_SP_StopPlay failed, sel: " + sel);
  }
  if (!videoctrl[sel].is_recording_ && !videoctrl[sel].is_pulling_)
  {
    NTSmartPlayerSDK.NT_SP_Close(videoctrl[sel].player_handle_);
    videoctrl[sel].player_handle_ = IntPtr.Zero;
  }
  videoctrl[sel].is_playing_ = false;
}

如果需要转RTMP出去,首先拉流端,需要调用拉流接口:

开始拉流、停止拉流

public void StartPull(int sel)
{
  if (videoctrl[sel].is_pulling_)
  {
    Debug.Log("StartPull, already started.. sel: " + sel);
    return;
  }
  if (!videoctrl[sel].is_playing_ && 
      !videoctrl[sel].is_recording_ )
  {
    if (!OpenPlayerHandle(sel))
    {
      Debug.LogError("call OpenPlayerHandle failed, sel:" + sel);
      return;
    }
  }
  videoctrl[sel].pull_stream_video_data_call_back_ = new SP_SDKPullStreamVideoDataCallBack(OnVideoDataHandle);
  videoctrl[sel].pull_stream_audio_data_call_back_ = new SP_SDKPullStreamAudioDataCallBack(OnAudioDataHandle);
  NTSmartPlayerSDK.NT_SP_SetPullStreamVideoDataCallBack(videoctrl[sel].player_handle_, IntPtr.Zero, videoctrl[sel].pull_stream_video_data_call_back_);
  NTSmartPlayerSDK.NT_SP_SetPullStreamAudioDataCallBack(videoctrl[sel].player_handle_, IntPtr.Zero, videoctrl[sel].pull_stream_audio_data_call_back_);
  int is_transcode_aac = 1;   //PCMA/PCMU/Speex格式转AAC后 再转发
  NTSmartPlayerSDK.NT_SP_SetPullStreamAudioTranscodeAAC(videoctrl[sel].player_handle_, is_transcode_aac);
  UInt32 ret = NTSmartPlayerSDK.NT_SP_StartPullStream(videoctrl[sel].player_handle_);
  if (NTBaseCodeDefine.NT_ERC_OK != ret)
  {
    if (!videoctrl[sel].is_playing_ && !videoctrl[sel].is_recording_)
    {
      NTSmartPlayerSDK.NT_SP_Close(videoctrl[sel].player_handle_);
      videoctrl[sel].player_handle_ = IntPtr.Zero;
    }
    return;
  }
  videoctrl[sel].is_pulling_ = true;
}
public void StopPull(int sel)
{
  if (!videoctrl[sel].is_pulling_)
    return;
  NTSmartPlayerSDK.NT_SP_StopPullStream(videoctrl[sel].player_handle_);
  if (!videoctrl[sel].is_playing_ && !videoctrl[sel].is_recording_)
  {
    NTSmartPlayerSDK.NT_SP_Close(videoctrl[sel].player_handle_);
    videoctrl[sel].player_handle_ = IntPtr.Zero;
  }
  videoctrl[sel].is_pulling_ = false;
}

拉流设置的时候,需要注意的是,如果是其他比如PCMA、PCMU的,考虑到通用性,可以转AAC后再回调数据上来,此外,拉流或播放的时候,判断是不是已经打开了RTSP URL,确保同一路流在一个实例内,不要开两个实例,占用额外的资源。


此外,关闭播放或拉流的时候,需要判断是不是处于拉流或播放状态,只要二者有一个还没关闭,实例就无法关闭。

开始转推RTMP、停止转推:

public bool StartPush(int sel, String url)
{
  if (videoctrl[sel].is_pushing_)
    return false;
  if (String.IsNullOrEmpty(url))
    return false;
  if (!OpenPushHandle(sel))
    return false;
  if (GetPushHandle(sel) == IntPtr.Zero)
    return false;
  IntPtr push_handle = GetPushHandle(sel);
  if (NTBaseCodeDefine.NT_ERC_OK != NTSmartPublisherSDK.NT_PB_SetURL(push_handle, url, IntPtr.Zero))
  {
    NTSmartPublisherSDK.NT_PB_Close(push_handle);
    SetPushHandle(sel, IntPtr.Zero);
    return false;
  }
  if (NTBaseCodeDefine.NT_ERC_OK != NTSmartPublisherSDK.NT_PB_StartPublisher(push_handle, IntPtr.Zero))
  {
    NTSmartPublisherSDK.NT_PB_Close(push_handle);
    SetPushHandle(sel, IntPtr.Zero);
    return false;
  }
  videoctrl[sel].is_pushing_ = true;
  return true;
}
public void StopPush(int sel)
{
  if (!videoctrl[sel].is_pushing_)
    return;
  videoctrl[sel].is_pushing_ = false;
  lock (videoctrl[sel].push_handle_mutex_)
  {
    if (videoctrl[sel].push_handle_ == IntPtr.Zero)
      return;
    NTSmartPublisherSDK.NT_PB_StopPublisher(videoctrl[sel].push_handle_);
    NTSmartPublisherSDK.NT_PB_Close(videoctrl[sel].push_handle_);
    videoctrl[sel].push_handle_ = IntPtr.Zero;
  }
}

音视频数据回调

private void OnVideoDataHandle(IntPtr handle, IntPtr user_data,
                               UInt32 video_codec_id, IntPtr data, UInt32 size,
                               IntPtr info, IntPtr reserve)
{
  int cur_sel = -1;
  for ( int i = 0; i < videoctrl.Length; i++)
  {
    if(handle == videoctrl[i].player_handle_)
    {
      cur_sel = i;
      break;
    }
  }
  if (cur_sel < 0)
    return;
  if (!videoctrl[cur_sel].is_pushing_)
    return;
  if (data == IntPtr.Zero)
    return;
  if (size < 1)
    return;
  if (info == IntPtr.Zero)
    return;
  NT_SP_PullStreamVideoDataInfo video_info = (NT_SP_PullStreamVideoDataInfo)Marshal.PtrToStructure(info, typeof(NT_SP_PullStreamVideoDataInfo));
  lock (videoctrl[cur_sel].push_handle_mutex_)
  {
    if (!videoctrl[cur_sel].is_pushing_)
      return;
    if (videoctrl[cur_sel].push_handle_ == IntPtr.Zero)
      return;
    //新接口
    NTSmartPublisherSDK.NT_PB_PostVideoEncodedDataV2(videoctrl[cur_sel].push_handle_, video_codec_id,
                                                     data, size, video_info.is_key_frame_, video_info.timestamp_, video_info.presentation_timestamp_);
  }
}
private void OnAudioDataHandle(IntPtr handle, IntPtr user_data,
                               UInt32 audio_codec_id, IntPtr data, UInt32 size,
                               IntPtr info, IntPtr reserve)
{
  int cur_sel = -1;
  for (int i = 0; i < videoctrl.Length; i++)
  {
    if (handle == videoctrl[i].player_handle_)
    {
      cur_sel = i;
      break;
    }
  }
  if (cur_sel < 0)
    return;
  if (!videoctrl[cur_sel].is_pushing_)
    return;
  if (data == IntPtr.Zero)
    return;
  if (size < 1)
    return;
  if (info == IntPtr.Zero)
    return;
  NT_SP_PullStreamAuidoDataInfo audio_info = (NT_SP_PullStreamAuidoDataInfo)Marshal.PtrToStructure(info, typeof(NT_SP_PullStreamAuidoDataInfo));
  lock (videoctrl[cur_sel].push_handle_mutex_)
  {
    if (!videoctrl[cur_sel].is_pushing_)
      return;
    if (videoctrl[cur_sel].push_handle_ == IntPtr.Zero)
      return;
    NTSmartPublisherSDK.NT_PB_PostAudioEncodedData(videoctrl[cur_sel].push_handle_, audio_codec_id, data, size,
                                                   audio_info.is_key_frame_, audio_info.timestamp_,
                                                   audio_info.parameter_info_, audio_info.parameter_info_size_);
  }
}

总结

实际上,Unity环境下的RTSP转RTMP推送,相对RTMP、RTSP播放或推流,对接更容易,因为基本不涉及到页面交互,感兴趣的开发者可以尝试看。

相关文章
|
4月前
|
数据采集 编解码 图形学
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
115 0
|
8月前
|
编解码 数据处理 vr&ar
VR头显Unity下如何实现毫秒级延迟的RTMP或RTSP播放?
VR头显Unity下如何实现毫秒级延迟的RTMP或RTSP播放?
182 1
|
8月前
|
Linux 开发工具 图形学
Unity下如何实现RTMP或RTSP播放端录像?
Unity下如何实现RTMP或RTSP播放端录像?
231 0
|
8月前
|
编解码 开发工具 图形学
Unity环境下RTMP推流+RTMP播放低延迟解决方案
在本文之前,我们发布了Unity环境下的RTMP推流(Windows平台+Android平台)和RTMP|RTSP拉流(Windows平台+Android平台+iOS平台)低延迟的解决方案,今天做个整体汇总,权当抛砖引玉。
451 0
|
8月前
|
编解码 监控 vr&ar
Unity3D下如何采集camera场景数据并推送RTMP服务?
Unity3D是非常流行的游戏开发引擎,可以创建各种类型的3D和2D游戏或其他互动应用程序。常见使用场景如下:
|
8月前
|
编解码 监控 图形学
Windows平台Unity下播放RTSP或RTMP如何开启硬解码?
我们在做Windows平台Unity播放RTMP或RTSP的时候,遇到这样的问题,比如展会、安防监控等场景下,需要同时播放多路RTMP或RTSP流,这样对设备性能,提出来更高的要求。
|
8月前
|
Linux 图形学 Android开发
Unity3D下如何实现跨平台低延迟的RTMP、RTSP播放
好多开发者,希望我们能探讨下Unity平台RTMP或RTSP直播流数据播放和录制相关的模块,实际上,这块流程我们已经聊过多次,无非就是通过原生的RTMP或者RTSP模块,先从协议层拉取到数据,并解包解码,回调YUV或RGB数据,然后,在Unity创建响应的shader,获取图像数据填充纹理即可,说起来流程很简单,但是每个环节,如果做到极致体验,都非常难。简单来说,多一次拷贝,都会增大性能瓶颈或延迟。
|
8月前
|
数据采集 vr&ar 图形学
Windows平台Unity Camera场景实现轻量级RTSP服务和RTMP推送
随着VR技术在医疗、军事、农业、学校、景区、消防、公共安全、研学机构、展厅展馆,商场等场所普及,开发者对Unity平台下的直播体验提出了更高的要求。
|
5月前
|
开发框架 Java C#
【Unity逆向】玩游戏遇到的“飞天锁血”是怎么实现的?
【Unity逆向】玩游戏遇到的“飞天锁血”是怎么实现的?
93 0
|
5月前
|
存储 自然语言处理 监控
【Unity 实用工具篇】| 游戏多语言解决方案,官方插件Localization 实现本地化及多种语言切换
Unity的多语言本地化是一个很实用的功能,它可以帮助游戏支持多种语言,让不同语言的玩家都能够更好地体验游戏。 而实现本地化的方案也有很多种,各个方案之间也各有优劣,后面也会对多个方案进行介绍学习。 本文就来介绍一个专门作用于多语言本地化的Unity官方插件:Localization 。 这个插件方便进行游戏的多语言本地化,让游戏支持多种语言,下面就来看看该插件的使用方法吧!