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

news2024/12/23 0:13:52

技术背景

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

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

技术实现

本文以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播放或推流,对接更容易,因为基本不涉及到页面交互,感兴趣的开发者可以尝试看。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/754898.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用Google Chrome浏览器打开Vue项目报错“Uncaught runtime errors”——已解决

使用Google Chrome浏览器打开Vue项目报错&#xff1a; Uncaught runtime errors:ERROR Identifier originalPrompt has already been declared SyntaxError: Identifier originalPrompt has already been declared问题原因&#xff1a; Google Chrome浏览器安装了插件跟Vue项…

2023年最新水果编曲软件FLStudio21.0.3.3517中文直装完整至尊解版下载

2023年最新水果编曲软件FLStudio21.0.3.3517中文直装完整至尊解版下载 是最好的音乐开发和制作软件也称为水果循环。它是最受欢迎的工作室&#xff0c;因为它包含了一个主要的听觉工作场所。 最新fl studio 21有不同的功能&#xff0c;如它包含图形和音乐音序器&#xff0c;帮助…

Nginx Linux设置开机自启动

使用如下命令 vi /lib/systemd/system/nginx.service 创建并编辑文件将以下代码黏贴至此文件中 [Unit] Descriptionnginx Afternetwork.target[Service] Typeforking TimeoutSec0 #防止启动超时 Userroot Grouproot criptionnacos Afternetwork.target[Service] Typeforking T…

哈希的应用(1)——位图

计算机存储单位的常用知识 2^30大约等于10亿 1byte8bit--一个字节等于八个比特位 左移操作符<<表示将值从底地址到高地址的方向移动。 bitset<-1>&#xff0c;开了2^32个bit512MB1GB 位图概念 面试题 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符…

Kerberos协议详解

0x01 kerberos协议的角色组成 Kerberos协议中存在三个角色&#xff1a; 客户端(Client)&#xff1a;发送请求的一方 服务端(Server)&#xff1a;接收请求的一方 密钥分发中心(Key distribution KDC) 密钥分发中心分为两个部分&#xff1a; AS(Authentication Server)&…

Linux下JDK版本与安装版本不一致问题

目录 一. &#x1f981; 前言二. &#x1f981; 操作流程三. &#x1f981; 总结四. &#x1f981; Happy Ending 一. &#x1f981; 前言 最近重新安装了centos7.9,针对以前遇到的Java版本不一致的情况, 提出了另一种方法,该方法简单易行,容易理解。 二. &#x1f981; 操作…

吴恩达机器学习2022-Jupyter1可选实验室: Python 和 Jupyter 笔记本简介

欢迎来到第一个可选实验室&#xff01; 可供选择的实验室包括:提供信息-比如这个笔记本以实际例子加强课堂教材提供分级实验室常规的工作实例 1.1 目标 在本实验中&#xff0c;您将: 对Jupyter笔记本进行简要介绍&#xff0c;参观Jupyter笔记本&#xff0c;了解标记单元格和…

pytorch实现线性回归

转大佬笔记 代码&#xff1a; # -*- coding: utf-8 -*- # Time : 2023-07-14 14:57 # Author : yuer # FileName: exercise05.py # Software: PyCharm import matplotlib.pyplot as plt import torch# x,y是3行1列的矩阵&#xff0c;所以在[]中要分为3个[] x_data torch.…

人物专访 |时静:携手The Open Group,把握时代脉动,助力中国数字经济建设

​ 在由The Open Group主办的2023架构可持续未来峰会上&#xff0c;The Open Group与机械工业出版社进行了战略签约合作仪式&#xff0c;并就备受业界期待的TOGAF标准第10版中文图书发布&#xff0c;以及OPA标准2.1版的本地化工作展开具体合作。 对此&#xff0c;机械工业出版社…

Video Enhancement with Task-Oriented Flow

摘要 Many video enhancement algorithms rely on optical flow to register frames in a video sequence. Precise flow estimation is however intractable; and optical flow itself is often a sub-optimal representation for particular video processing tasks. In thi…

嵌入式linux驱动开发之移远4G模块EC800驱动移植指南

回顾下移远4G模块移植过程&#xff0c; 还是蛮简单的。一通百通&#xff0c;无论是其他4G模块都是一样的。这里记录下过程&#xff0c;分享给有需要的人。环境使用正点原子的imax6ul开发板&#xff0c;板子默认支持中兴和移远EC20的驱动&#xff0c;这里要移植使用的是移远4G模…

在Linux下做性能分析1:基本模型

介绍 本Blog开始介绍一下在Linux分析性能瓶颈的基本方法。主要围绕一个基本的分析模型&#xff0c;介绍perf和ftrace的使用技巧&#xff0c;然后东一扒子&#xff0c;西一扒子&#xff0c;逮到什么说什么&#xff0c;也不一定会严谨。主要是把这个领域的一些思路和技巧串起来。…

Electron + vue 搭建桌面客户端

下载Electron 压缩包&#xff0c;放到本地 Electron 压缩包下载地址 cd ~/Library/Caches/electron

Duilib 父窗口无效化和消息传递

文章目录 1、父窗口无效化和消息传递2、EnableWindow()和SetFocus()的含义和用法 1、父窗口无效化和消息传递 当使用duillib界面库时&#xff0c;我们往往需要建立多个窗口&#xff0c;子窗口和父窗口之间有一定的逻辑需要&#xff0c;比如当子窗口弹出时&#xff0c;让父窗口…

2022 Robocom CAIP 国赛 第二题

原题链接&#xff1a; PTA | 程序设计类实验辅助教学平台 题面&#xff1a; 副本是游戏里的一个特色玩法&#xff0c;主要为玩家带来装备、道具、游戏资源的产出&#xff0c;满足玩家的游戏进程。 在 MMORPG《最终幻想14》里&#xff0c;有一个攻略人数最大达到 48 人的副本“…

Office如何通过VSTO进行EXCEL插件开发?

文章目录 0.引言1.工具准备2.EXCEL外接程序创建和生成3.外接程序生成并使用 0.引言 VSTO&#xff08;Visual Studio Tools for Office &#xff09;是VBA的替代&#xff0c;是一套用于创建自定义Office应用程序的Visual Studio工具包。VSTO可以用Visual Basic 或者Visual C#扩展…

SPEC CPU 2006 在 CentOS 5.0 x86_64 古老系统测试【3】静态编译 invalid run

上篇 SPEC CPU 2006 在 CentOS 5.0 x86_64 古老系统测试【2】_hkNaruto的博客-CSDN博客 修改gcc41.cfg&#xff0c;全部添加上-static 测试指令 runspec -c gcc41.cfg -T all -n 3 -r 1 -I -i ref all 结果&#xff1a;正常运行并生成报告 invalid run Invalid SPEC CFP2006…

【算法基础】2.1栈和队列(单调栈和单调队列)

文章目录 例题3302. 表达式求值&#xff08;栈的应用&#xff09;&#x1f62d;&#x1f62d;&#x1f62d;&#x1f62d;&#x1f62d;830. 单调栈知识点解法 154. 滑动窗口 &#xff08;单调队列&#xff09;知识点解法 相关链接 & 相关题目 例题 3302. 表达式求值&…

在进行自动化测试,遇到验证码的问题,怎么办?

1.找开发去掉验证码或者使用万能验证码 2.使用OCR自动识别 使用OCR自动化识别&#xff0c;一般识别率不是太高&#xff0c;处理一般简单验证码还是没问题 这里使用的是Tesseract-OCR,下载地址&#xff1a;https://github.com/A9T9/Free-Ocr-Windows-Desktop/releases 怎么使…

linux centos7 静默安装 oracle 11g,【亲测有效】,包含远程连接、提供安装包

Centos7 安装oracle11g 环境准备 操作系统 centos7 oracle版本 oracle11g 终端软件 MobaXterm 192.168.46.61 oracleMaster 安装包 网盘地址 一、安装前准备 1、关闭selinux [rootoracleMaster ~]# vim /etc/selinux/config修改 SELINUX 为 disabled # This file controls …