├── VideoStreamingServerPackages ├── Native~ │ ├── H264Encoder │ │ ├── stdafx.cpp │ │ ├── H264Encoder.vcxproj.user │ │ ├── targetver.h │ │ ├── stdafx.h │ │ ├── dllmain.cpp │ │ ├── H264Encoder.vcxproj.filters │ │ └── H264Encoder.vcxproj │ └── H264Encoder.sln ├── Plugins │ ├── H264Encoder.dll │ └── H264Encoder.dll.meta ├── Runtime │ ├── Sdp.meta │ ├── Internal.meta │ ├── Messages.meta │ ├── com.unity.ig.video-streaming.server.asmdef.meta │ ├── Messages │ │ ├── RTSPRequestRecord.cs │ │ ├── PortCouple.cs.meta │ │ ├── RTSPChunk.cs.meta │ │ ├── RTSPData.cs.meta │ │ ├── RTSPHeaderNames.cs.meta │ │ ├── RTSPMessage.cs.meta │ │ ├── RTSPRequest.cs.meta │ │ ├── RTSPRequestPlay.cs.meta │ │ ├── RTSPResponse.cs.meta │ │ ├── RTSPTransport.cs.meta │ │ ├── RTSPRequestAnnounce.cs.meta │ │ ├── RTSPRequestDescribe.cs.meta │ │ ├── RTSPRequestOptions.cs.meta │ │ ├── RTSPRequestPause.cs.meta │ │ ├── RTSPRequestRecord.cs.meta │ │ ├── RTSPRequestSetup.cs.meta │ │ ├── RTSPRequestTeardown.cs.meta │ │ ├── RTSPRequestGetParameter.cs.meta │ │ ├── RTSPRequestPlay.cs │ │ ├── RTSPRequestPause.cs │ │ ├── RTSPRequestAnnounce.cs │ │ ├── RTSPRequestTeardown.cs │ │ ├── RTSPRequestDescribe.cs │ │ ├── RTSPRequestGetParameter.cs │ │ ├── RTSPHeaderNames.cs │ │ ├── RTSPRequestOptions.cs │ │ ├── RTSPRequestSetup.cs │ │ ├── RTSPChunk.cs │ │ ├── RTSPData.cs │ │ ├── PortCouple.cs │ │ ├── RTSPRequest.cs │ │ ├── RTSPResponse.cs │ │ ├── RTSPMessage.cs │ │ └── RTSPTransport.cs │ ├── BitStream.cs.meta │ ├── RTSPUtils.cs.meta │ ├── Sdp │ │ ├── Media.cs.meta │ │ ├── Attribut.cs.meta │ │ ├── Bandwidth.cs.meta │ │ ├── Connection.cs.meta │ │ ├── Origin.cs.meta │ │ ├── SdpFile.cs.meta │ │ ├── Timing.cs.meta │ │ ├── AttributFmtp.cs.meta │ │ ├── AttributRtpMap.cs.meta │ │ ├── ConnectionIP4.cs.meta │ │ ├── ConnectionIP6.cs.meta │ │ ├── EncriptionKey.cs.meta │ │ ├── H264Parameter.cs.meta │ │ ├── H265Parameter.cs.meta │ │ ├── SdpTimeZone.cs.meta │ │ ├── Bandwidth.cs │ │ ├── Timing.cs │ │ ├── EncriptionKey.cs │ │ ├── SdpTimeZone.cs │ │ ├── ConnectionIP6.cs │ │ ├── ConnectionIP4.cs │ │ ├── Connection.cs │ │ ├── Media.cs │ │ ├── AttributRtpMap.cs │ │ ├── AttributFmtp.cs │ │ ├── Attribut.cs │ │ ├── Origin.cs │ │ ├── H264Parameter.cs │ │ ├── H265Parameter.cs │ │ └── SdpFile.cs │ ├── UdpSocket.cs.meta │ ├── AACPayload.cs.meta │ ├── AMRPayload.cs.meta │ ├── Authentication.cs.meta │ ├── G711Payload.cs.meta │ ├── H264Encoder.cs.meta │ ├── H264Payload.cs.meta │ ├── H265Payload.cs.meta │ ├── IRTSPTransport.cs.meta │ ├── RTPPacketUtil.cs.meta │ ├── RTSPListener.cs.meta │ ├── RtspServer.cs.meta │ ├── RTSPTCPTransport.cs.meta │ ├── RTSPMessageEventArgs.cs.meta │ ├── VideoStreamingServer.cs.meta │ ├── Internal │ │ ├── H264EncoderPlugin.cs.meta │ │ └── H264EncoderPlugin.cs │ ├── com.unity.ig.video-streaming.server.asmdef │ ├── RTSPUtils.cs │ ├── RTSPMessageEventArgs.cs │ ├── AMRPayload.cs │ ├── IRTSPTransport.cs │ ├── RTPPacketUtil.cs │ ├── G711Payload.cs │ ├── BitStream.cs │ ├── RTSPTCPTransport.cs │ ├── AACPayload.cs │ ├── H264Encoder.cs │ ├── Authentication.cs │ ├── UdpSocket.cs │ ├── H264Payload.cs │ ├── VideoStreamingServer.cs │ └── H265Payload.cs ├── Shaders │ ├── RGBToNV12.shader.meta │ └── RGBToNV12.shader └── package.json ├── .github └── ISSUE_TEMPLATE │ └── custom.md ├── README.md └── Packages └── manifest.json /VideoStreamingServerPackages/Native~/H264Encoder/stdafx.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Plugins/H264Encoder.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ezharjan/UnityVideoStreamingPackage/HEAD/VideoStreamingServerPackages/Plugins/H264Encoder.dll -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Native~/H264Encoder/H264Encoder.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5df94805077263c4aaf1f9bfb11234fd 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Internal.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 87ebdf4f18b7b034c8a147d73aeaef51 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 318328a1821c6d547ab4e2f085f48dca 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/com.unity.ig.video-streaming.server.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 144152992fa66ea47938c83ea250308d 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Shaders/RGBToNV12.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 027b7d2de9ddae1469c9062583e9ee6a 3 | ShaderImporter: 4 | externalObjects: {} 5 | defaultTextures: [] 6 | nonModifiableTextures: [] 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Unity.Ig.VideoStreaming.Server.Messages 2 | { 3 | public class RtspRequestRecord : RtspRequest 4 | { 5 | public RtspRequestRecord() 6 | { 7 | Command = "RECORD * RTSP/1.0"; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/BitStream.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 53076b446d7104f4381e17017a38dd7d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/RTSPUtils.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e9adfaf54d2ca1644a6450f189c6d357 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/Media.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d7c81ae5695daab42a87e9c9a9016d1c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/UdpSocket.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 225aa63e79bc0d74d86f7fb8cde0aa13 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/AACPayload.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: db64fbce65b5d2e4da2fd9b1779db575 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/AMRPayload.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 64fd405d92c453f4a91253b39ade4d81 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Authentication.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 653a84ab7b23b7d4bb27eddb1a5e6289 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/G711Payload.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 676dc96f37ada454a995ffb7468a45a8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/H264Encoder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fe90b55e11ed7e04ba873c72ae53d14a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/H264Payload.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5e9fda91b28d1ef4aaad474784293832 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/H265Payload.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: baafe81c99db6a94881fa83f50193019 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/IRTSPTransport.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 97fc6e928c6718643889368441f39c21 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/RTPPacketUtil.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b74aee1e95228c949a15c569744b3b8d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/RTSPListener.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6fd4c7ebb0ed21c469c30ffba2e43ca9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/RtspServer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9f0067a727dc7ec49be20ffc44586597 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/Attribut.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d318e08f19991fc4494690cacc0becaa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/Bandwidth.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: df1d50d2e58785a4fabc15059f33da8c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/Connection.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 44697c9949e51104a86463b45c3b2e07 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/Origin.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6d888a2228180fc4c8de84b7ed1a0135 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/SdpFile.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 860d6a8dd9dfa8440be2e3bd5b5a185a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/Timing.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 91c990a7b4c0c064a871245e57d558b6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/PortCouple.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aff663461cd63db41b3ab0c6d8b14967 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPChunk.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0d92562925fa88844bb76a00ed9b228d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 985d9a5256acba44c9dec6976aeceaa4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/RTSPTCPTransport.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3c13b11f4d1b6cc45988ae017975aa35 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/AttributFmtp.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c4705f176fed1d14ea19c991fef528f0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/AttributRtpMap.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 53d3cb3126247484fae61430e4662f21 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/ConnectionIP4.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c19b65fca37deb24e96d02b10f7040e7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/ConnectionIP6.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dfe4c8163dd1f7246b3f3fdbd65cea33 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/EncriptionKey.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7051b4295c5367f488a198fe88634ed9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/H264Parameter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 57c5da9b14e7227479a56399f2340f47 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/H265Parameter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 135699988c31b634f94a8b0f77811650 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/SdpTimeZone.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bbeafe6176f214a438587c96c2f600b4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPHeaderNames.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 127b57b12b01ced4a8a973da24fc09f5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPMessage.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ee7dc418d14757e4eb5df313326125ba 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1bd9f783ac360bb4bbd3298d979dc086 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestPlay.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1f9b1816423266d4393d61321e146f61 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPResponse.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8f9108ab8774e43439deb1670b0b9da6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPTransport.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d85cd3863ea2a7747988253cd14798c0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/RTSPMessageEventArgs.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e4b5b89a1c5710940b04f4a58b65fcdf 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/VideoStreamingServer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c339252bdd8d1ba4ab70c785ef665a2b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Internal/H264EncoderPlugin.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 81d8f6787b48b264f9c29a5c9865546e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestAnnounce.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b4f3a122a2c2adc4481b275dd30f49cf 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestDescribe.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cb44387fb1ea71c4b853042f823c06f9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestOptions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 560d7a6b5da792d4f959bf4483eba52c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestPause.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 36385b58c4c57c34a90e8bcc6b4e0401 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestRecord.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 163d2aa8dbff4a7488f30bf2d197c855 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestSetup.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7f41b5afc32fb06409178073c2f67eb1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestTeardown.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 19f6c0d6760a4a942a8f06107287543c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestGetParameter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dfd7ae93280f80a49a646f445ef181f2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Native~/H264Encoder/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestPlay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server.Messages 7 | { 8 | public class RtspRequestPlay : RtspRequest 9 | { 10 | 11 | // Constructor 12 | public RtspRequestPlay() 13 | { 14 | Command = "PLAY * RTSP/1.0"; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/com.unity.ig.video-streaming.server.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Unity.Ig.VideoStreaming.Server", 3 | "references": ["Unity.Collections"], 4 | "optionalUnityReferences": [], 5 | "includePlatforms": [], 6 | "excludePlatforms": [], 7 | "allowUnsafeCode": true, 8 | "overrideReferences": false, 9 | "precompiledReferences": [], 10 | "autoReferenced": true, 11 | "defineConstraints": [] 12 | } -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestPause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server.Messages 7 | { 8 | public class RtspRequestPause : RtspRequest 9 | { 10 | 11 | // Constructor 12 | public RtspRequestPause() 13 | { 14 | Command = "PAUSE * RTSP/1.0"; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestAnnounce.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server.Messages 7 | { 8 | public class RtspRequestAnnounce : RtspRequest 9 | { 10 | // constructor 11 | 12 | public RtspRequestAnnounce() 13 | { 14 | Command = "ANNOUNCE * RTSP/1.0"; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestTeardown.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server.Messages 7 | { 8 | public class RtspRequestTeardown : RtspRequest 9 | { 10 | 11 | // Constructor 12 | public RtspRequestTeardown() 13 | { 14 | Command = "TEARDOWN * RTSP/1.0"; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestDescribe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server.Messages 7 | { 8 | public class RtspRequestDescribe : RtspRequest 9 | { 10 | 11 | // constructor 12 | 13 | public RtspRequestDescribe() 14 | { 15 | Command = "DESCRIBE * RTSP/1.0"; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestGetParameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server.Messages 7 | { 8 | public class RtspRequestGetParameter : RtspRequest 9 | { 10 | 11 | // Constructor 12 | public RtspRequestGetParameter() 13 | { 14 | Command = "GET_PARAMETER * RTSP/1.0"; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/Bandwidth.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server.Sdp 7 | { 8 | public class Bandwidth 9 | { 10 | public Bandwidth() 11 | { 12 | } 13 | 14 | internal static Bandwidth Parse(string value) 15 | { 16 | //TODO really parse. 17 | return new Bandwidth(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Native~/H264Encoder/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include "targetver.h" 9 | 10 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 11 | // Windows Header Files 12 | #include 13 | 14 | 15 | 16 | // reference additional headers your program requires here 17 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/Timing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server.Sdp 7 | { 8 | public class Timing 9 | { 10 | private string timing; 11 | private string repeat; 12 | 13 | public Timing(string timing, string repeat) 14 | { 15 | // TODO: Complete member initialization 16 | this.timing = timing; 17 | this.repeat = repeat; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/RTSPUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server 7 | { 8 | public static class RtspUtils 9 | { 10 | /// 11 | /// Registers the URI. 12 | /// 13 | public static void RegisterUri() 14 | { 15 | if (!UriParser.IsKnownScheme("rtsp")) 16 | UriParser.Register(new HttpStyleUriParser(), "rtsp", 554); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Native~/H264Encoder/dllmain.cpp: -------------------------------------------------------------------------------- 1 | // dllmain.cpp : Defines the entry point for the DLL application. 2 | #include "stdafx.h" 3 | 4 | BOOL APIENTRY DllMain( HMODULE hModule, 5 | DWORD ul_reason_for_call, 6 | LPVOID lpReserved 7 | ) 8 | { 9 | switch (ul_reason_for_call) 10 | { 11 | case DLL_PROCESS_ATTACH: 12 | case DLL_THREAD_ATTACH: 13 | case DLL_THREAD_DETACH: 14 | case DLL_PROCESS_DETACH: 15 | break; 16 | } 17 | return TRUE; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.unity.ig.video-streaming.server", 3 | "displayName": "VideoStreaming Server", 4 | "version": "0.0.1", 5 | "unity": "2018.4", 6 | "description": "Wraps native platform APIs for performing H.264 encoding and RTP streaming.", 7 | "dependencies": { 8 | "com.unity.collections": "0.0.9-preview12" 9 | }, 10 | "keywords": [ 11 | "unity", 12 | "media", 13 | "video", 14 | "streaming", 15 | "live", 16 | "h.264", 17 | "rtp", 18 | "rtsp" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/EncriptionKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Diagnostics.Contracts; 6 | 7 | namespace Unity.Ig.VideoStreaming.Server.Sdp 8 | { 9 | public class EncriptionKey 10 | { 11 | public EncriptionKey(string p) 12 | { 13 | } 14 | 15 | public static EncriptionKey ParseInvariant(string value) 16 | { 17 | if (value == null) 18 | throw new ArgumentNullException("value"); 19 | 20 | Contract.EndContractBlock(); 21 | 22 | throw new NotImplementedException(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/SdpTimeZone.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Diagnostics.Contracts; 6 | 7 | namespace Unity.Ig.VideoStreaming.Server.Sdp 8 | { 9 | public class SdpTimeZone 10 | { 11 | public SdpTimeZone() 12 | { 13 | } 14 | 15 | public static SdpTimeZone ParseInvariant(string value) 16 | { 17 | if (value == null) 18 | throw new ArgumentNullException("value"); 19 | Contract.EndContractBlock(); 20 | 21 | SdpTimeZone returnValue = new SdpTimeZone(); 22 | 23 | throw new NotImplementedException(); 24 | // return returnValue; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPHeaderNames.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server.Messages 7 | { 8 | /// 9 | /// Class containing helper constant for general use headers. 10 | /// 11 | public static class RtspHeaderNames 12 | { 13 | public const string ContentBase = "Content-Base"; 14 | public const string ContentEncoding = "Content-Encoding"; 15 | public const string ContentType = "Content-Type"; 16 | 17 | public const string Public = "Public"; 18 | public const string Session = "Session"; 19 | public const string Transport = "Transport"; 20 | 21 | public const string WWWAuthenticate = "WWW-Authenticate"; 22 | public const string Authorization = "Authorization"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/RTSPMessageEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server 7 | { 8 | using Messages; 9 | /// 10 | /// Event args containing information for message events. 11 | /// 12 | public class RtspChunkEventArgs :EventArgs 13 | { 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// A message. 19 | public RtspChunkEventArgs(RtspChunk aMessage) 20 | { 21 | Message = aMessage; 22 | } 23 | 24 | /// 25 | /// Gets or sets the message. 26 | /// 27 | /// The message. 28 | public RtspChunk Message { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/AMRPayload.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace Unity.Ig.VideoStreaming.Server 6 | { 7 | // This class handles the AMR Payload 8 | // It has methods to process the RTP Payload 9 | 10 | public class AMRPayload 11 | { 12 | // Constructor 13 | public AMRPayload() 14 | { 15 | } 16 | 17 | public List Process_AMR_RTP_Packet(byte[] rtp_payload, int rtp_marker) { 18 | 19 | // Octet-Aligned Mode (RFC 4867 Section 4.4.1) 20 | 21 | // First byte is the Payload Header 22 | if (rtp_payload.Length < 1) return null; 23 | byte payloadHeader = rtp_payload[0]; 24 | 25 | // The rest of the RTP packet is the AMR data 26 | List audio_data = new List(); 27 | 28 | byte[] amr_data = new byte[rtp_payload.Length - 1]; 29 | System.Array.Copy(rtp_payload,1,amr_data,0,rtp_payload.Length-1); 30 | audio_data.Add(amr_data); 31 | 32 | return audio_data; 33 | } 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server.Messages 7 | { 8 | public class RtspRequestOptions : RtspRequest 9 | { 10 | 11 | // Constructor 12 | public RtspRequestOptions() 13 | { 14 | Command = "OPTIONS * RTSP/1.0"; 15 | } 16 | 17 | /// 18 | /// Gets the assiociate OK response with the request. 19 | /// 20 | /// 21 | /// an Rtsp response corresponding to request. 22 | /// 23 | public override RtspResponse CreateResponse() 24 | { 25 | RtspResponse response = base.CreateResponse(); 26 | // Add genric suported operations. 27 | response.Headers.Add(RtspHeaderNames.Public, "OPTIONS,DESCRIBE,ANNOUNCE,SETUP,PLAY,PAUSE,TEARDOWN,GET_PARAMETER,SET_PARAMETER,REDIRECT"); 28 | 29 | return response; 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity Video Streaming Server Package 2 | 3 | 4 | 5 | ## Introduction 6 | 7 | This is a Unity 3D package for live video streaming using RTP and the server here includes RTSP server which wraps native platform APIs for performing H.264 encoding and RTP streaming. 8 | 9 | 10 | 11 | ## Utilization 12 | 13 | 1. Replace `Packages` folder in your Unity 3D project to the one in this folder, which namely means that you should add this folder and other two packages as dependencies in your `Packages/manifest.json` file. 14 | 2. Place `VideoStreamingServerPackages` folder into your Unity 3D project's root folder in order to make the path described in `manifest.json` the same as in real path, otherwise you should change the path described in `manifest.json` file. 15 | 3. Use it as you want. The native C++ file is in `Native~` folder and you can even redevelop it yourself. 16 | 4. The main script is `VideoStreamingServer` and you can use it as a component. 17 | 18 | 19 | 20 | 21 | 22 |


23 | 24 |

Alexander Ezharjan

9th Sep, 2020

25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/ConnectionIP6.cs: -------------------------------------------------------------------------------- 1 | namespace Unity.Ig.VideoStreaming.Server.Sdp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Net; 6 | using System.Globalization; 7 | 8 | public class ConnectionIP6 : Connection 9 | { 10 | internal new static ConnectionIP6 Parse(string ipAddress) 11 | { 12 | string[] parts = ipAddress.Split('/'); 13 | 14 | if (parts.Length > 2) 15 | throw new FormatException("Too much address subpart in " + ipAddress); 16 | 17 | ConnectionIP6 result = new ConnectionIP6(); 18 | 19 | result.Host = parts[0]; 20 | 21 | int numberOfAddress; 22 | if (parts.Length > 1) 23 | { 24 | if (!int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out numberOfAddress)) 25 | throw new FormatException("Invalid number of address : " + parts[1]); 26 | result.NumberOfAddress = numberOfAddress; 27 | } 28 | 29 | return result; 30 | 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/IRTSPTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Unity.Ig.VideoStreaming.Server 4 | { 5 | /// 6 | /// Interface for Transport of Rtsp (TCP, TCP+SSL,..) 7 | /// 8 | public interface IRtspTransport 9 | { 10 | /// 11 | /// Gets the stream of the transport. 12 | /// 13 | /// A stream 14 | System.IO.Stream GetStream(); 15 | 16 | /// 17 | /// Gets the remote address. 18 | /// 19 | /// The remote address. 20 | string RemoteAddress 21 | { 22 | get; 23 | } 24 | 25 | /// 26 | /// Closes this instance. 27 | /// 28 | void Close(); 29 | 30 | /// 31 | /// Gets a value indicating whether this is connected. 32 | /// 33 | /// true if connected; otherwise, false. 34 | bool Connected { get; } 35 | 36 | /// 37 | /// Reconnect this instance. 38 | /// Must do nothing if already connected. 39 | /// 40 | /// Error during socket 41 | void Reconnect(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequestSetup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server.Messages 7 | { 8 | public class RtspRequestSetup : RtspRequest 9 | { 10 | 11 | // Constructor 12 | public RtspRequestSetup() 13 | { 14 | Command = "SETUP * RTSP/1.0"; 15 | } 16 | 17 | 18 | /// 19 | /// Gets the transports associate with the request. 20 | /// 21 | /// The transport. 22 | public RtspTransport[] GetTransports() 23 | { 24 | 25 | if (!Headers.ContainsKey(RtspHeaderNames.Transport)) 26 | return new RtspTransport[] { new RtspTransport() }; 27 | 28 | string[] items = Headers[RtspHeaderNames.Transport].Split(','); 29 | return Array.ConvertAll(items, 30 | new Converter(RtspTransport.Parse)); 31 | 32 | } 33 | 34 | public void AddTransport(RtspTransport newTransport) 35 | { 36 | string actualTransport = string.Empty; 37 | if(Headers.ContainsKey(RtspHeaderNames.Transport)) 38 | actualTransport = Headers[RtspHeaderNames.Transport] + ","; 39 | Headers[RtspHeaderNames.Transport] = actualTransport + newTransport.ToString(); 40 | 41 | 42 | 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/ConnectionIP4.cs: -------------------------------------------------------------------------------- 1 | namespace Unity.Ig.VideoStreaming.Server.Sdp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using System.Net; 7 | 8 | public class ConnectionIP4 : Connection 9 | { 10 | 11 | public int Ttl { get; set; } 12 | 13 | internal new static ConnectionIP4 Parse(string ipAddress) 14 | { 15 | string[] parts = ipAddress.Split('/'); 16 | 17 | if (parts.Length > 3) 18 | throw new FormatException("Too much address subpart in " + ipAddress); 19 | 20 | ConnectionIP4 result = new ConnectionIP4(); 21 | 22 | result.Host = parts[0]; 23 | 24 | int ttl; 25 | if (parts.Length > 1) 26 | { 27 | if (!int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out ttl)) 28 | throw new FormatException("Invalid TTL format : " + parts[1]); 29 | result.Ttl = ttl; 30 | } 31 | int numberOfAddress; 32 | if (parts.Length > 2) 33 | { 34 | if (!int.TryParse(parts[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out numberOfAddress)) 35 | throw new FormatException("Invalid number of address : " + parts[2]); 36 | result.NumberOfAddress = numberOfAddress; 37 | } 38 | 39 | return result; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/RTPPacketUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | 8 | public static class RTPPacketUtil 9 | { 10 | 11 | public static void WriteHeader(byte[] rtp_packet, int rtp_version, int rtp_padding, int rtp_extension, int rtp_csrc_count, int rtp_marker, int rtp_payload_type) 12 | { 13 | rtp_packet[0] = (byte)((rtp_version << 6) | (rtp_padding << 5) | (rtp_extension << 4) | rtp_csrc_count); 14 | rtp_packet[1] = (byte)((rtp_marker << 7) | (rtp_payload_type & 0x7F)); 15 | } 16 | 17 | public static void WriteSequenceNumber(byte[] rtp_packet, uint empty_sequence_id) 18 | { 19 | rtp_packet[2] = ((byte)((empty_sequence_id >> 8) & 0xFF)); 20 | rtp_packet[3] = ((byte)((empty_sequence_id >> 0) & 0xFF)); 21 | } 22 | 23 | public static void WriteTS(byte[] rtp_packet, uint ts) 24 | { 25 | rtp_packet[4] = ((byte)((ts >> 24) & 0xFF)); 26 | rtp_packet[5] = ((byte)((ts >> 16) & 0xFF)); 27 | rtp_packet[6] = ((byte)((ts >> 8) & 0xFF)); 28 | rtp_packet[7] = ((byte)((ts >> 0) & 0xFF)); 29 | } 30 | 31 | public static void WriteSSRC(byte[] rtp_packet, uint ssrc) 32 | { 33 | rtp_packet[8] = ((byte)((ssrc >> 24) & 0xFF)); 34 | rtp_packet[9] = ((byte)((ssrc >> 16) & 0xFF)); 35 | rtp_packet[10] = ((byte)((ssrc >> 8) & 0xFF)); 36 | rtp_packet[11] = ((byte)((ssrc >> 0) & 0xFF)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Native~/H264Encoder/H264Encoder.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | 26 | 27 | Source Files 28 | 29 | 30 | Source Files 31 | 32 | 33 | Source Files 34 | 35 | 36 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Native~/H264Encoder.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.645 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "H264Encoder", "H264Encoder\H264Encoder.vcxproj", "{45BDF642-D8AA-400A-A088-A66D09B1E247}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {45BDF642-D8AA-400A-A088-A66D09B1E247}.Debug|x64.ActiveCfg = Debug|x64 17 | {45BDF642-D8AA-400A-A088-A66D09B1E247}.Debug|x64.Build.0 = Debug|x64 18 | {45BDF642-D8AA-400A-A088-A66D09B1E247}.Debug|x86.ActiveCfg = Debug|Win32 19 | {45BDF642-D8AA-400A-A088-A66D09B1E247}.Debug|x86.Build.0 = Debug|Win32 20 | {45BDF642-D8AA-400A-A088-A66D09B1E247}.Release|x64.ActiveCfg = Release|x64 21 | {45BDF642-D8AA-400A-A088-A66D09B1E247}.Release|x64.Build.0 = Release|x64 22 | {45BDF642-D8AA-400A-A088-A66D09B1E247}.Release|x86.ActiveCfg = Release|Win32 23 | {45BDF642-D8AA-400A-A088-A66D09B1E247}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {00188A34-F7F4-42F0-B379-438E7DCCEDDA} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Internal/H264EncoderPlugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | internal struct H264EncoderPlugin 5 | { 6 | [DllImport("H264Encoder", EntryPoint ="Create")] 7 | extern public static IntPtr CreateEncoder(uint width, uint height, uint frameRateNumerator, uint frameRateDenominator, uint averageBitRate); 8 | 9 | [DllImport("H264Encoder", EntryPoint = "Destroy")] 10 | [return: MarshalAs(UnmanagedType.U1)] 11 | extern public static bool DestroyEncoder(IntPtr encoder); 12 | 13 | [DllImport("H264Encoder", EntryPoint = "Encode")] 14 | [return: MarshalAs(UnmanagedType.U1)] 15 | extern public unsafe static bool EncodeFrame(IntPtr encoder, byte* pixelData, ulong timeStampNs); 16 | 17 | [DllImport("H264Encoder", EntryPoint = "BeginConsume")] 18 | [return: MarshalAs(UnmanagedType.U1)] 19 | extern public static bool BeginConsumeEncodedBuffer(IntPtr encoder, out uint sizeOut); 20 | 21 | [DllImport("H264Encoder", EntryPoint = "EndConsume")] 22 | [return: MarshalAs(UnmanagedType.U1)] 23 | extern public unsafe static bool EndConsumeEncodedBuffer( 24 | IntPtr encoder, 25 | byte* dst, 26 | out ulong timeStampNs, 27 | [MarshalAs(UnmanagedType.U1)] out bool isKeyFrame); 28 | 29 | [DllImport("H264Encoder", EntryPoint = "GetSps")] 30 | extern public unsafe static uint GetSpsNAL(IntPtr encoder, byte* spsData); 31 | 32 | [DllImport("H264Encoder", EntryPoint = "GetPps")] 33 | extern public unsafe static uint GetPpsNAL(IntPtr encoder, byte* ppsData); 34 | } 35 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPChunk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace Unity.Ig.VideoStreaming.Server.Messages 5 | { 6 | /// 7 | /// Class wich represent each message echanged on Rtsp socket. 8 | /// 9 | public abstract class RtspChunk : ICloneable 10 | { 11 | /// 12 | /// Logs the message to debug. 13 | /// 14 | public void LogMessage() 15 | { 16 | LogMessage(LogType.Log); 17 | } 18 | 19 | /// 20 | /// Logs the message. 21 | /// 22 | /// The log level. 23 | public abstract void LogMessage(LogType aLevel); 24 | 25 | /// 26 | /// Gets or sets the data associate with the message. 27 | /// 28 | /// Array of byte transmit with the message. 29 | public byte[] Data 30 | { get; set; } 31 | 32 | /// 33 | /// Gets or sets the source port wich receive the message. 34 | /// 35 | /// The source port. 36 | public RtspListener SourcePort { get; set; } 37 | 38 | #region ICloneable Membres 39 | 40 | /// 41 | /// Crée un nouvel objet qui est une copie de l'instance en cours. 42 | /// 43 | /// 44 | /// Nouvel objet qui est une copie de cette instance. 45 | /// 46 | public abstract object Clone(); 47 | 48 | #endregion 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPData.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Unity.Ig.VideoStreaming.Server.Messages 4 | { 5 | /// 6 | /// Message wich represent data. ($ limited message) 7 | /// 8 | public class RtspData : RtspChunk 9 | { 10 | private static ILogger _logger = new Logger(Debug.unityLogger.logHandler); 11 | 12 | static RtspData() 13 | { 14 | _logger.logEnabled = false; 15 | } 16 | 17 | /// 18 | /// Logs the message to debug. 19 | /// 20 | public override void LogMessage(LogType aLevel) 21 | { 22 | // if the level is not logged directly return 23 | if (!_logger.IsLogTypeAllowed(aLevel)) 24 | return; 25 | _logger.Log(aLevel, "Data message"); 26 | if (Data == null) 27 | _logger.Log(aLevel, "Data : null"); 28 | else 29 | _logger.Log(aLevel, "Data length :-{0}-", Data.Length); 30 | } 31 | 32 | public int Channel { get; set; } 33 | 34 | /// 35 | /// Clones this instance. 36 | /// Listner is not cloned 37 | /// 38 | /// a clone of this instance 39 | public override object Clone() 40 | { 41 | RtspData result = new RtspData(); 42 | result.Channel = this.Channel; 43 | if (this.Data != null) 44 | result.Data = this.Data.Clone() as byte[]; 45 | result.SourcePort = this.SourcePort; 46 | return result; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/Connection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Globalization; 6 | using System.Net; 7 | 8 | namespace Unity.Ig.VideoStreaming.Server.Sdp 9 | { 10 | public abstract class Connection 11 | { 12 | public Connection() 13 | { 14 | //Default value from spec 15 | NumberOfAddress = 1; 16 | } 17 | 18 | public string Host { get; set; } 19 | 20 | /// 21 | /// Gets or sets the number of address specifed in connection. 22 | /// 23 | /// The number of address. 24 | //TODO handle it a different way (list of adress ?) 25 | public int NumberOfAddress { get; set; } 26 | 27 | public static Connection Parse(string value) 28 | { 29 | if(value ==null) 30 | throw new ArgumentNullException("value"); 31 | 32 | string[] parts = value.Split(' '); 33 | 34 | if (parts.Length != 3) 35 | throw new FormatException("Value do not contain 3 parts as needed."); 36 | 37 | if (parts[0] != "IN") 38 | throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Net type {0} not suported", parts[0])); 39 | 40 | switch (parts[1]) 41 | { 42 | case "IP4": 43 | return ConnectionIP4.Parse(parts[2]); 44 | case "IP6": 45 | return ConnectionIP6.Parse(parts[2]); 46 | default: 47 | throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Address type {0} not suported", parts[1])); 48 | } 49 | 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Plugins/H264Encoder.dll.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2642012849465054ea2a1780f18ba369 3 | PluginImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | iconMap: {} 7 | executionOrder: {} 8 | defineConstraints: [] 9 | isPreloaded: 0 10 | isOverridable: 1 11 | isExplicitlyReferenced: 0 12 | validateReferences: 1 13 | platformData: 14 | - first: 15 | '': Any 16 | second: 17 | enabled: 0 18 | settings: 19 | Exclude Editor: 0 20 | Exclude Linux: 0 21 | Exclude Linux64: 0 22 | Exclude LinuxUniversal: 0 23 | Exclude OSXUniversal: 0 24 | Exclude Win: 1 25 | Exclude Win64: 0 26 | - first: 27 | Any: 28 | second: 29 | enabled: 1 30 | settings: {} 31 | - first: 32 | Editor: Editor 33 | second: 34 | enabled: 1 35 | settings: 36 | CPU: AnyCPU 37 | DefaultValueInitialized: true 38 | OS: AnyOS 39 | - first: 40 | Facebook: Win 41 | second: 42 | enabled: 0 43 | settings: 44 | CPU: None 45 | - first: 46 | Facebook: Win64 47 | second: 48 | enabled: 0 49 | settings: 50 | CPU: AnyCPU 51 | - first: 52 | Standalone: Linux 53 | second: 54 | enabled: 1 55 | settings: 56 | CPU: x86 57 | - first: 58 | Standalone: Linux64 59 | second: 60 | enabled: 1 61 | settings: 62 | CPU: x86_64 63 | - first: 64 | Standalone: LinuxUniversal 65 | second: 66 | enabled: 1 67 | settings: 68 | CPU: AnyCPU 69 | - first: 70 | Standalone: OSXUniversal 71 | second: 72 | enabled: 1 73 | settings: 74 | CPU: AnyCPU 75 | - first: 76 | Standalone: Win 77 | second: 78 | enabled: 0 79 | settings: 80 | CPU: None 81 | - first: 82 | Standalone: Win64 83 | second: 84 | enabled: 1 85 | settings: 86 | CPU: AnyCPU 87 | userData: 88 | assetBundleName: 89 | assetBundleVariant: 90 | -------------------------------------------------------------------------------- /Packages/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "com.unity.ads": "2.0.8", 4 | "com.unity.analytics": "3.2.2", 5 | "com.unity.collab-proxy": "1.2.15", 6 | "com.unity.collections": "0.0.9-preview.12", 7 | "com.unity.ig.video-streaming.server": "file:../VideoStreamingServerPackages/", 8 | "com.unity.mathematics": "1.1.0", 9 | "com.unity.package-manager-ui": "2.0.8", 10 | "com.unity.purchasing": "2.0.3", 11 | "com.unity.textmeshpro": "1.4.1", 12 | "com.unity.modules.ai": "1.0.0", 13 | "com.unity.modules.animation": "1.0.0", 14 | "com.unity.modules.assetbundle": "1.0.0", 15 | "com.unity.modules.audio": "1.0.0", 16 | "com.unity.modules.cloth": "1.0.0", 17 | "com.unity.modules.director": "1.0.0", 18 | "com.unity.modules.imageconversion": "1.0.0", 19 | "com.unity.modules.imgui": "1.0.0", 20 | "com.unity.modules.jsonserialize": "1.0.0", 21 | "com.unity.modules.particlesystem": "1.0.0", 22 | "com.unity.modules.physics": "1.0.0", 23 | "com.unity.modules.physics2d": "1.0.0", 24 | "com.unity.modules.screencapture": "1.0.0", 25 | "com.unity.modules.terrain": "1.0.0", 26 | "com.unity.modules.terrainphysics": "1.0.0", 27 | "com.unity.modules.tilemap": "1.0.0", 28 | "com.unity.modules.ui": "1.0.0", 29 | "com.unity.modules.uielements": "1.0.0", 30 | "com.unity.modules.umbra": "1.0.0", 31 | "com.unity.modules.unityanalytics": "1.0.0", 32 | "com.unity.modules.unitywebrequest": "1.0.0", 33 | "com.unity.modules.unitywebrequestassetbundle": "1.0.0", 34 | "com.unity.modules.unitywebrequestaudio": "1.0.0", 35 | "com.unity.modules.unitywebrequesttexture": "1.0.0", 36 | "com.unity.modules.unitywebrequestwww": "1.0.0", 37 | "com.unity.modules.vehicles": "1.0.0", 38 | "com.unity.modules.video": "1.0.0", 39 | "com.unity.modules.vr": "1.0.0", 40 | "com.unity.modules.wind": "1.0.0", 41 | "com.unity.modules.xr": "1.0.0" 42 | } 43 | } -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/Media.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Unity.Ig.VideoStreaming.Server.Sdp 8 | { 9 | public class Media 10 | { 11 | private string mediaString; 12 | 13 | public Media(string mediaString) 14 | { 15 | // Example is 'video 0 RTP/AVP 26; 16 | this.mediaString = mediaString; 17 | 18 | var parts = mediaString.Split(new char[] { ' ' } , 4); 19 | 20 | if (parts.Count() >= 1) { 21 | if (parts[0].Equals("video")) MediaType = MediaTypes.video; 22 | else if (parts[0].Equals("audio")) MediaType = MediaTypes.audio; 23 | else if (parts[0].Equals("text")) MediaType = MediaTypes.text; 24 | else if (parts[0].Equals("application")) MediaType = MediaTypes.application; 25 | else if (parts[0].Equals("message")) MediaType = MediaTypes.message; 26 | else MediaType = MediaTypes.unknown; // standard does allow for future types to be defined 27 | } 28 | 29 | int pt; 30 | if (parts.Count() >= 4) { 31 | if(int.TryParse(parts[3], out pt)) 32 | { 33 | PayloadType = pt; 34 | } else { 35 | PayloadType = 0; 36 | } 37 | } 38 | } 39 | 40 | // RFC4566 Media Types 41 | public enum MediaTypes { video, audio, text, application, message, unknown }; 42 | 43 | public Connection Connection { get; set; } 44 | 45 | public Bandwidth Bandwidth { get; set; } 46 | 47 | public EncriptionKey EncriptionKey { get; set; } 48 | 49 | public MediaTypes MediaType { get; set; } 50 | 51 | public int PayloadType { get; set; } 52 | 53 | private readonly List attributs = new List(); 54 | 55 | public IList Attributs 56 | { 57 | get 58 | { 59 | return attributs; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/AttributRtpMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server.Sdp 7 | { 8 | public class AttributRtpMap : Attribut 9 | { 10 | // Format 11 | // rtpmap: / [/] 12 | // Examples 13 | // rtpmap:96 H264/90000 14 | // rtpmap:8 PCMA/8000 15 | 16 | public const string NAME = "rtpmap"; 17 | 18 | public AttributRtpMap() 19 | { 20 | } 21 | 22 | public override string Key 23 | { 24 | get 25 | { 26 | return NAME; 27 | } 28 | } 29 | 30 | public override string Value 31 | { 32 | get 33 | { 34 | if(string.IsNullOrEmpty(EncodingParameters)) 35 | { 36 | return string.Format("{0} {1}/{2}", PayloadNumber, EncodingName, ClockRate); 37 | } else { 38 | return string.Format("{0} {1}/{2}/{3}", PayloadNumber, EncodingName, ClockRate, EncodingParameters); 39 | } 40 | } 41 | protected set 42 | { 43 | ParseValue(value); 44 | } 45 | } 46 | 47 | public int PayloadNumber { get; set; } 48 | public String EncodingName { get; set; } 49 | public String ClockRate { get; set; } 50 | public String EncodingParameters { get; set; } 51 | 52 | protected override void ParseValue(string value) 53 | { 54 | var parts = value.Split(new char[] { ' ', '/' }); 55 | 56 | if (parts.Length >= 1) { 57 | if (int.TryParse(parts[0], out int tmp_payloadNumber)) 58 | { 59 | PayloadNumber = tmp_payloadNumber; 60 | } 61 | } 62 | if (parts.Length >= 2) 63 | { 64 | EncodingName = parts[1]; 65 | } 66 | if (parts.Length >= 3) 67 | { 68 | ClockRate = parts[2]; 69 | } 70 | if (parts.Length >= 4) 71 | { 72 | EncodingParameters = parts[3]; 73 | } 74 | 75 | 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/AttributFmtp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server.Sdp 7 | { 8 | public class AttributFmtp : Attribut 9 | { 10 | public const string NAME = "fmtp"; 11 | 12 | private Dictionary parameters = new Dictionary(); 13 | 14 | public AttributFmtp() 15 | { 16 | } 17 | 18 | public override string Key 19 | { 20 | get 21 | { 22 | return NAME; 23 | } 24 | } 25 | 26 | public override string Value 27 | { 28 | get 29 | { 30 | return string.Format("{0} {1}", PayloadNumber, FormatParameter); 31 | } 32 | protected set 33 | { 34 | ParseValue(value); 35 | } 36 | } 37 | 38 | public int PayloadNumber { get; set; } 39 | 40 | // temporary aatibute to store remaning data not parsed 41 | public string FormatParameter { get; set; } 42 | 43 | 44 | // Extract the Payload Number and the Format Parameters 45 | protected override void ParseValue(string value) 46 | { 47 | var parts = value.Split(new char[] { ' ' }, 2); 48 | 49 | int payloadNumber; 50 | if(int.TryParse(parts[0], out payloadNumber)) 51 | { 52 | this.PayloadNumber = payloadNumber; 53 | } 54 | if(parts.Length > 1) 55 | { 56 | FormatParameter = parts[1]; 57 | 58 | // Split on ';' to get a list of items. 59 | // Then Trim each item and then Split on the first '=' 60 | // Add them to the dictionary 61 | parameters.Clear(); 62 | foreach (var pair in parts[1].Split(';').Select(x => x.Trim().Split(new char[] { '=' }, 2))) { 63 | if (!string.IsNullOrWhiteSpace(pair[0])) 64 | parameters[pair[0]] = pair.Length > 1 ? pair[1] : null; 65 | } 66 | } 67 | } 68 | 69 | public String GetParameter(String index) 70 | { 71 | if (parameters.ContainsKey(index)) return parameters[index]; 72 | else return ""; 73 | } 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/Attribut.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Diagnostics.Contracts; 6 | 7 | namespace Unity.Ig.VideoStreaming.Server.Sdp 8 | { 9 | public class Attribut 10 | { 11 | private static readonly Dictionary attributMap = new Dictionary() 12 | { 13 | {AttributRtpMap.NAME,typeof(AttributRtpMap)}, 14 | {AttributFmtp.NAME,typeof(AttributFmtp)}, 15 | }; 16 | 17 | 18 | public virtual string Key { get; private set; } 19 | public virtual string Value { get; protected set; } 20 | 21 | public static void RegisterNewAttributeType(string key, Type attributType) 22 | { 23 | if(!attributType.IsSubclassOf(typeof(Attribut))) 24 | throw new ArgumentException("Type must be subclass of Rtsp.Sdp.Attribut","attributType"); 25 | 26 | attributMap[key] = attributType; 27 | } 28 | 29 | 30 | 31 | public Attribut() 32 | { 33 | } 34 | 35 | public Attribut(string key) 36 | { 37 | Key = key; 38 | } 39 | 40 | 41 | public static Attribut ParseInvariant(string value) 42 | { 43 | if(value == null) 44 | throw new ArgumentNullException("value"); 45 | 46 | Contract.EndContractBlock(); 47 | 48 | var listValues = value.Split(new char[] {':'}, 2); 49 | 50 | 51 | Attribut returnValue; 52 | 53 | // Call parser of child type 54 | Type childType; 55 | attributMap.TryGetValue(listValues[0], out childType); 56 | if (childType != null) 57 | { 58 | var defaultContructor = childType.GetConstructor(Type.EmptyTypes); 59 | returnValue = defaultContructor.Invoke(Type.EmptyTypes) as Attribut; 60 | } 61 | else 62 | { 63 | returnValue = new Attribut(listValues[0]); 64 | } 65 | // Parse the value. Note most attributes have a value but recvonly does not have a value 66 | if (listValues.Count() > 1) returnValue.ParseValue(listValues[1]); 67 | 68 | return returnValue; 69 | } 70 | 71 | protected virtual void ParseValue(string value) 72 | { 73 | Value = value; 74 | } 75 | 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/G711Payload.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace Unity.Ig.VideoStreaming.Server 6 | { 7 | // This class handles the G711 Payload 8 | // It has methods to process the RTP Payload 9 | 10 | public class G711Payload 11 | { 12 | // Constructor 13 | public G711Payload() 14 | { 15 | } 16 | 17 | public List Process_G711_RTP_Packet(byte[] rtp_payload, int rtp_marker) { 18 | 19 | List audio_data = new List(); 20 | audio_data.Add(rtp_payload); 21 | 22 | return audio_data; 23 | } 24 | 25 | /* Untested - used with G711.1 and PCMA-WB and PCMU-WB Codec Names */ 26 | public List Process_G711_1_RTP_Packet(byte[] rtp_payload, int rtp_marker) { 27 | 28 | // Look at the Header. This tells us the G711 mode being used 29 | 30 | // Mode Index (MI) is 31 | // 1 - R1 40 octets containg Layer 0 data 32 | // 2 - R2a 50 octets containing Layer 0 plus Layer 1 data 33 | // 3 - R2b 50 octets containing Layer 0 plus Layer 2 data 34 | // 4 - R3 60 octets containing Layer 0 plus Layer 1 plus Layer 2 data 35 | 36 | byte mode_index = (byte)(rtp_payload[0] & 0x07); 37 | 38 | int size_of_one_frame = 0; // will be in bytes 39 | switch (mode_index) { 40 | case 1: size_of_one_frame = 40; break; 41 | case 2: size_of_one_frame = 50; break; 42 | case 3: size_of_one_frame = 50; break; 43 | case 4: size_of_one_frame = 60; break; 44 | default: return null; // invalid Mode Index 45 | } 46 | 47 | int number_frames = (rtp_payload.Length - 1) / size_of_one_frame; 48 | 49 | 50 | // Return just the basic u-Law or A-Law audio (the Layer 0 audio) 51 | 52 | List audio_data = new List(); 53 | 54 | // Extract each audio frame and place in the audio_data List 55 | int frame_start = 1; // starts just after the MI header 56 | while (frame_start + size_of_one_frame < rtp_payload.Length) { 57 | byte[] layer_0_audio = new byte[40]; 58 | System.Array.Copy(rtp_payload,frame_start,layer_0_audio,0,40); // 40 octets in Layer 0 data 59 | audio_data.Add(layer_0_audio); 60 | 61 | frame_start += size_of_one_frame; 62 | } 63 | return audio_data; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Shaders/RGBToNV12.shader: -------------------------------------------------------------------------------- 1 | Shader "Video/RGBToNV12" 2 | { 3 | Properties 4 | { 5 | _MainTex ("Texture", 2D) = "white" {} 6 | } 7 | SubShader 8 | { 9 | Pass 10 | { 11 | CGPROGRAM 12 | #pragma vertex vert 13 | #pragma fragment frag 14 | #pragma multi_compile _ UNITY_COLORSPACE_GAMMA 15 | 16 | #include "UnityCG.cginc" 17 | 18 | struct appdata 19 | { 20 | float4 vertex : POSITION; 21 | float2 uv : TEXCOORD0; 22 | }; 23 | 24 | struct v2f 25 | { 26 | float2 uv : TEXCOORD0; 27 | float4 vertex : SV_POSITION; 28 | }; 29 | 30 | sampler2D _MainTex; 31 | float4 _MainTex_TexelSize; 32 | float4 _MainTex_ST; 33 | 34 | v2f vert (appdata v) 35 | { 36 | v2f o; 37 | o.vertex = UnityObjectToClipPos(v.vertex); 38 | o.uv = TRANSFORM_TEX(v.uv, _MainTex); 39 | return o; 40 | } 41 | 42 | // Adobe-flavored HDTV Rec.709 (2.2 gamma, 16-235 limit) 43 | fixed3 RGB2YUV(half3 rgb) 44 | { 45 | const half K_B = 0.0722; 46 | const half K_R = 0.2126; 47 | 48 | #if !UNITY_COLORSPACE_GAMMA 49 | rgb = LinearToGammaSpace(rgb); 50 | #endif 51 | half y = dot(half3(K_R, 1 - K_B - K_R, K_B), rgb); 52 | half u = ((rgb.b - y) / (1 - K_B) * 112 + 128) / 255; 53 | half v = ((rgb.r - y) / (1 - K_R) * 112 + 128) / 255; 54 | 55 | y = (y * 219 + 16) / 255; 56 | 57 | return fixed3(y, u, v); 58 | } 59 | 60 | fixed3 RGB2YUV601(half3 rgb) 61 | { 62 | #if !UNITY_COLORSPACE_GAMMA 63 | rgb = LinearToGammaSpace(rgb); 64 | #endif 65 | half y = ( 16.0F/255.0F + 0.258348F * rgb.r + 0.50676F * rgb.g + 0.099756F * rgb.b); 66 | half u = (128.0F/255.0F - 0.150534F * rgb.r - 0.295278F * rgb.g + 0.445811F * rgb.b); 67 | half v = (128.0F/255.0F + 0.440022F * rgb.r - 0.367650F * rgb.g - 0.072372F * rgb.b); 68 | 69 | return fixed3(y, u, v); 70 | } 71 | 72 | fixed frag(v2f i) : SV_Target 73 | { 74 | if (3.0F * i.uv.y < 2.0F) 75 | { 76 | // Y 77 | i.uv.y = i.uv.y * 1.5F; 78 | return RGB2YUV(tex2D(_MainTex, i.uv)).r; 79 | } 80 | 81 | // Move the y coordinate back into the main image to do the chroma subsampling. 82 | i.uv.y = 3.0F * i.uv.y - 2.0F; 83 | i.uv.y += _MainTex_TexelSize.y * 0.5F; // Offset by 1/2 line to sample in the middle of the 4 pixels. 84 | 85 | float halfTexelWidth = _MainTex_TexelSize.x * 0.5F; 86 | 87 | int x = (int)floor(i.uv.x * (_MainTex_TexelSize.z - 0.5F) + 0.5F); 88 | if (fmod(x, 2.0F) == 0.0F) 89 | { 90 | // Even columns: U 91 | // Get x position between current pixel and the one to the right, so U and V are sampled at the same column. 92 | i.uv.x += halfTexelWidth; 93 | return RGB2YUV(tex2D(_MainTex, i.uv)).g; 94 | } 95 | 96 | // Odd columns: V 97 | // Get x position between current pixel and the one to the left, so U and V are sampled at the same column. 98 | i.uv.x -= halfTexelWidth; 99 | return RGB2YUV(tex2D(_MainTex, i.uv)).b; 100 | } 101 | 102 | ENDCG 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/BitStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | // (c) 2018 Roger Hardiman, RJH Technical Consultancy Ltd 5 | // Simple class to Read and Write bits in a bit stream. 6 | // Data is written to the end of the bit stream and the bit stream can be returned as a Byte Array 7 | // Data can be read from the head of the bit stream 8 | // Example 9 | // bitstream.AddValue(0xA,4); // Write 4 bit value 10 | // bitstream.AddValue(0xB,4); 11 | // bitstream.AddValue(0xC,4); 12 | // bitstream.AddValue(0xD,4); 13 | // bitstream.ToArray() -> {0xAB, 0xCD} // Return Byte Array 14 | // bitstream.Read(8) -> 0xAB // Read 8 bit value 15 | 16 | namespace Unity.Ig.VideoStreaming.Server 17 | { 18 | 19 | // Very simple bitstream 20 | public class BitStream { 21 | 22 | private List data = new List(); // List only stores 0 or 1 (one 'bit' per List item) 23 | 24 | // Constructor 25 | public BitStream() { 26 | } 27 | 28 | public void AddValue(int value, int num_bits) { 29 | // Add each bit to the List 30 | for (int i = num_bits-1; i >= 0; i--) { 31 | data.Add((byte)((value>>i) & 0x01)); 32 | } 33 | } 34 | 35 | public void AddHexString(String hex_string) { 36 | char[] hex_chars = hex_string.ToUpper().ToCharArray(); 37 | foreach (char c in hex_chars) { 38 | if ((c.Equals('0'))) this.AddValue(0,4); 39 | else if ((c.Equals('1'))) this.AddValue(1, 4); 40 | else if ((c.Equals('2'))) this.AddValue(2, 4); 41 | else if ((c.Equals('3'))) this.AddValue(3, 4); 42 | else if ((c.Equals('4'))) this.AddValue(4, 4); 43 | else if ((c.Equals('5'))) this.AddValue(5, 4); 44 | else if ((c.Equals('6'))) this.AddValue(6, 4); 45 | else if ((c.Equals('7'))) this.AddValue(7, 4); 46 | else if ((c.Equals('8'))) this.AddValue(8, 4); 47 | else if ((c.Equals('9'))) this.AddValue(9, 4); 48 | else if ((c.Equals('A'))) this.AddValue(10, 4); 49 | else if ((c.Equals('B'))) this.AddValue(11, 4); 50 | else if ((c.Equals('C'))) this.AddValue(12, 4); 51 | else if ((c.Equals('D'))) this.AddValue(13, 4); 52 | else if ((c.Equals('E'))) this.AddValue(14, 4); 53 | else if ((c.Equals('F'))) this.AddValue(15, 4); 54 | } 55 | } 56 | 57 | public uint Read(int num_bits) { 58 | // Read and remove items from the front of the list of bits 59 | if (data.Count < num_bits) return 0; 60 | uint result = 0; 61 | for (int i = 0; i < num_bits; i++) { 62 | result = result << 1; 63 | result = result + data[0]; 64 | data.RemoveAt(0); 65 | } 66 | return result; 67 | } 68 | 69 | public byte[] ToArray() { 70 | int num_bytes = (int)Math.Ceiling((double)data.Count/8.0); 71 | byte[] array = new byte[num_bytes]; 72 | int ptr = 0; 73 | int shift = 7; 74 | for (int i = 0; i < data.Count; i++) { 75 | array[ptr] += (byte)(data[i] << shift); 76 | if (shift == 0) { 77 | shift = 7; 78 | ptr++; 79 | } 80 | else { 81 | shift--; 82 | } 83 | } 84 | 85 | return array; 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/PortCouple.cs: -------------------------------------------------------------------------------- 1 | namespace Unity.Ig.VideoStreaming.Server.Messages 2 | { 3 | using System; 4 | using System.Diagnostics.Contracts; 5 | using System.Globalization; 6 | 7 | /// 8 | /// Describe a couple of port used to transfer video and command. 9 | /// 10 | public class PortCouple 11 | { 12 | /// 13 | /// Gets or sets the first port number. 14 | /// 15 | /// The first port. 16 | public int First { get; set; } 17 | /// 18 | /// Gets or sets the second port number. 19 | /// 20 | /// If not present the value is 0 21 | /// The second port. 22 | public int Second { get; set; } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | public PortCouple() 28 | { } 29 | /// 30 | /// Initializes a new instance of the class. 31 | /// 32 | /// The first port. 33 | public PortCouple(int first) 34 | { 35 | First = first; 36 | Second = 0; 37 | } 38 | /// 39 | /// Initializes a new instance of the class. 40 | /// 41 | /// The first port. 42 | /// The second port. 43 | public PortCouple(int first, int second) 44 | { 45 | First = first; 46 | Second = second; 47 | } 48 | 49 | /// 50 | /// Gets a value indicating whether this instance has second port. 51 | /// 52 | /// 53 | /// true if this instance has second port; otherwise, false. 54 | /// 55 | public bool IsSecondPortPresent 56 | { 57 | get { return Second != 0; } 58 | } 59 | 60 | /// 61 | /// Parses the int values of port. 62 | /// 63 | /// A string value. 64 | /// The port couple 65 | public static PortCouple Parse(string stringValue) 66 | { 67 | if (stringValue == null) 68 | throw new ArgumentNullException("stringValue"); 69 | Contract.Requires(!string.IsNullOrEmpty(stringValue)); 70 | 71 | string[] values = stringValue.Split('-'); 72 | 73 | int tempValue; 74 | 75 | int.TryParse(values[0], out tempValue); 76 | PortCouple result = new PortCouple(tempValue); 77 | 78 | tempValue = 0; 79 | if (values.Length > 1) 80 | int.TryParse(values[1], out tempValue); 81 | 82 | result.Second = tempValue; 83 | 84 | return result; 85 | } 86 | 87 | /// 88 | /// Returns a that represents this instance. 89 | /// 90 | /// 91 | /// A that represents this instance. 92 | /// 93 | public override string ToString() 94 | { 95 | if (IsSecondPortPresent) 96 | return First.ToString(CultureInfo.InvariantCulture) + "-" + Second.ToString(CultureInfo.InvariantCulture); 97 | else 98 | return First.ToString(CultureInfo.InvariantCulture); 99 | } 100 | 101 | 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/Origin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Globalization; 6 | 7 | namespace Unity.Ig.VideoStreaming.Server.Sdp 8 | { 9 | /// 10 | /// Object ot represent orgin in an Session Description Protocol 11 | /// 12 | public class Origin 13 | { 14 | public Origin() 15 | { 16 | } 17 | 18 | /// 19 | /// Parses the specified origin string. 20 | /// 21 | /// The string to convert to origin object. 22 | /// 23 | public static Origin Parse(string originString) 24 | { 25 | if (originString == null) 26 | throw new ArgumentNullException("originString"); 27 | 28 | string[] parts = originString.Split(' '); 29 | 30 | if (parts.Length != 6) 31 | throw new FormatException("Number of element invalid in origin string."); 32 | 33 | Origin result = new Origin(); 34 | result.Username = parts[0]; 35 | result.SessionId = parts[1]; 36 | result.SessionVersion = parts[2]; 37 | result.NetType = parts[3]; 38 | result.AddressType = parts[4]; 39 | result.UnicastAddress = parts[5]; 40 | 41 | return result; 42 | } 43 | 44 | /// 45 | /// Gets or sets the username. 46 | /// 47 | /// It is the user's login on the originating host, or it is "-" 48 | /// if the originating host does not support the concept of user IDs. 49 | /// This MUST NOT contain spaces 50 | /// The username. 51 | public string Username { get; set; } 52 | 53 | /// 54 | /// Gets or sets the session id. 55 | /// 56 | /// It is a numeric string such that the tuple of , 57 | /// , , , and forms a 58 | /// globally unique identifier for the session. The method of 59 | /// allocation is up to the creating tool, but it has been 60 | /// suggested that a Network Time Protocol (NTP) format timestamp be 61 | /// used to ensure uniqueness 62 | /// The session id. 63 | public string SessionId { get; set; } 64 | 65 | /// 66 | /// Gets or sets the session version. 67 | /// 68 | /// The session version. 69 | public string SessionVersion { get; set; } 70 | 71 | /// 72 | /// Gets or sets the type of the net. 73 | /// 74 | /// The type of the net. 75 | public string NetType { get; set; } 76 | 77 | /// 78 | /// Gets or sets the type of the address. 79 | /// 80 | /// The type of the address. 81 | public string AddressType { get; set; } 82 | 83 | /// 84 | /// Gets or sets the unicast address (IP or FDQN). 85 | /// 86 | /// The unicast address. 87 | public string UnicastAddress { get; set; } 88 | 89 | public override string ToString() 90 | { 91 | return String.Join(" ", 92 | new string[] 93 | { 94 | Username, 95 | SessionId, 96 | SessionVersion.ToString(CultureInfo.InvariantCulture), 97 | NetType, 98 | AddressType, 99 | UnicastAddress, 100 | } 101 | ); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/RTSPTCPTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using System.Diagnostics.Contracts; 6 | using System.Globalization; 7 | 8 | namespace Unity.Ig.VideoStreaming.Server 9 | { 10 | /// 11 | /// TCP Connection for Rtsp 12 | /// 13 | public class RtspTcpTransport : IRtspTransport, IDisposable 14 | { 15 | private IPEndPoint _currentEndPoint; 16 | private TcpClient _RtspServerClient; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The underlying TCP connection. 22 | public RtspTcpTransport(TcpClient tcpConnection) 23 | { 24 | if (tcpConnection == null) 25 | throw new ArgumentNullException("tcpConnection"); 26 | Contract.EndContractBlock(); 27 | 28 | _currentEndPoint = (IPEndPoint)tcpConnection.Client.RemoteEndPoint; 29 | _RtspServerClient = tcpConnection; 30 | } 31 | 32 | /// 33 | /// Initializes a new instance of the class. 34 | /// 35 | /// A host. 36 | /// A port number. 37 | public RtspTcpTransport(string aHost, int aPortNumber) 38 | : this(new TcpClient(aHost, aPortNumber)) 39 | { 40 | } 41 | 42 | 43 | #region IRtspTransport Membres 44 | 45 | /// 46 | /// Gets the stream of the transport. 47 | /// 48 | /// A stream 49 | public Stream GetStream() 50 | { 51 | return _RtspServerClient.GetStream(); 52 | } 53 | 54 | /// 55 | /// Gets the remote address. 56 | /// 57 | /// The remote address. 58 | public string RemoteAddress 59 | { 60 | get 61 | { 62 | return string.Format(CultureInfo.InvariantCulture,"{0}:{1}", _currentEndPoint.Address, _currentEndPoint.Port); 63 | } 64 | } 65 | 66 | /// 67 | /// Closes this instance. 68 | /// 69 | public void Close() 70 | { 71 | Dispose(true); 72 | } 73 | 74 | /// 75 | /// Gets a value indicating whether this is connected. 76 | /// 77 | /// true if connected; otherwise, false. 78 | public bool Connected 79 | { 80 | get { return _RtspServerClient.Client != null && _RtspServerClient.Connected; } 81 | } 82 | 83 | /// 84 | /// Reconnect this instance. 85 | /// Must do nothing if already connected. 86 | /// 87 | /// Error during socket 88 | public void Reconnect() 89 | { 90 | if (Connected) 91 | return; 92 | _RtspServerClient = new TcpClient(); 93 | _RtspServerClient.Connect(_currentEndPoint); 94 | } 95 | 96 | #endregion 97 | 98 | public void Dispose() 99 | { 100 | Dispose(true); 101 | GC.SuppressFinalize(this); 102 | } 103 | 104 | protected virtual void Dispose(bool disposing) 105 | { 106 | if (disposing) 107 | { 108 | _RtspServerClient.Close(); 109 | /* // free managed resources 110 | if (managedResource != null) 111 | { 112 | managedResource.Dispose(); 113 | managedResource = null; 114 | }*/ 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/H264Parameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Unity.Ig.VideoStreaming.Server.Sdp 8 | { 9 | public class H264Parameters : IDictionary 10 | { 11 | private readonly Dictionary parameters = new Dictionary(); 12 | 13 | public List SpropParameterSets 14 | { 15 | get 16 | { 17 | List result = new List(); 18 | 19 | if (ContainsKey("sprop-parameter-sets")&& this["sprop-parameter-sets"] != null) 20 | { 21 | result.AddRange(this["sprop-parameter-sets"].Split(',').Select(x => Convert.FromBase64String(x))); 22 | } 23 | 24 | return result; 25 | } 26 | } 27 | 28 | public static H264Parameters Parse(String parameterString) 29 | { 30 | var result = new H264Parameters(); 31 | foreach (var pair in parameterString.Split(';').Select(x => x.Trim().Split(new char[] { '=' }, 2))) 32 | { 33 | if(!string.IsNullOrWhiteSpace(pair[0])) 34 | result[pair[0]] = pair.Length > 1 ? pair[1] : null; 35 | } 36 | return result; 37 | } 38 | 39 | public override string ToString() 40 | { 41 | return parameters.Select(p => p.Key + (p.Value != null ? "=" + p.Value : string.Empty)).Aggregate((x, y) => x + ";" + y); 42 | } 43 | 44 | public String this[String index] 45 | { 46 | get { return parameters[index]; } 47 | set { parameters[index] = value; } 48 | } 49 | 50 | public int Count 51 | { 52 | get 53 | { 54 | return parameters.Count; 55 | } 56 | } 57 | 58 | public bool IsReadOnly 59 | { 60 | get 61 | { 62 | return ((IDictionary)parameters).IsReadOnly; 63 | } 64 | } 65 | 66 | public ICollection Keys 67 | { 68 | get 69 | { 70 | return ((IDictionary)parameters).Keys; 71 | } 72 | } 73 | 74 | public ICollection Values 75 | { 76 | get 77 | { 78 | return ((IDictionary)parameters).Values; 79 | } 80 | } 81 | 82 | public void Add(KeyValuePair item) 83 | { 84 | ((IDictionary)parameters).Add(item); 85 | } 86 | 87 | public void Add(string key, string value) 88 | { 89 | parameters.Add(key, value); 90 | } 91 | 92 | public void Clear() 93 | { 94 | parameters.Clear(); 95 | } 96 | 97 | public bool Contains(KeyValuePair item) 98 | { 99 | return ((IDictionary)parameters).Contains(item); 100 | } 101 | 102 | public bool ContainsKey(string key) 103 | { 104 | return parameters.ContainsKey(key); 105 | } 106 | 107 | public void CopyTo(KeyValuePair[] array, int arrayIndex) 108 | { 109 | ((IDictionary)parameters).CopyTo(array, arrayIndex); 110 | } 111 | 112 | public IEnumerator> GetEnumerator() 113 | { 114 | return ((IDictionary)parameters).GetEnumerator(); 115 | } 116 | 117 | public bool Remove(KeyValuePair item) 118 | { 119 | return ((IDictionary)parameters).Remove(item); 120 | } 121 | 122 | public bool Remove(string key) 123 | { 124 | return parameters.Remove(key); 125 | } 126 | 127 | public bool TryGetValue(string key, out string value) 128 | { 129 | return parameters.TryGetValue(key, out value); 130 | } 131 | 132 | IEnumerator IEnumerable.GetEnumerator() 133 | { 134 | return ((IDictionary)parameters).GetEnumerator(); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/H265Parameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | // Parse 'fmtp' attribute in SDP 8 | // Extract H265 fields 9 | // By Roger Hardiman, RJH Technical Consultancy Ltd 10 | 11 | namespace Unity.Ig.VideoStreaming.Server.Sdp 12 | { 13 | public class H265Parameters : IDictionary 14 | { 15 | private readonly Dictionary parameters = new Dictionary(); 16 | 17 | public List SpropParameterSets 18 | { 19 | get 20 | { 21 | List result = new List(); 22 | 23 | if (ContainsKey("sprop-vps")&& this["sprop-vps"] != null) 24 | { 25 | result.AddRange(this["sprop-vps"].Split(',').Select(x => Convert.FromBase64String(x))); 26 | } 27 | 28 | if (ContainsKey("sprop-sps") && this["sprop-sps"] != null) 29 | { 30 | result.AddRange(this["sprop-sps"].Split(',').Select(x => Convert.FromBase64String(x))); 31 | } 32 | 33 | if (ContainsKey("sprop-pps") && this["sprop-pps"] != null) 34 | { 35 | result.AddRange(this["sprop-pps"].Split(',').Select(x => Convert.FromBase64String(x))); 36 | } 37 | return result; 38 | } 39 | } 40 | 41 | public static H265Parameters Parse(String parameterString) 42 | { 43 | var result = new H265Parameters(); 44 | foreach (var pair in parameterString.Split(';').Select(x => x.Trim().Split(new char[] { '=' }, 2))) 45 | { 46 | if(!string.IsNullOrWhiteSpace(pair[0])) 47 | result[pair[0]] = pair.Length > 1 ? pair[1] : null; 48 | } 49 | return result; 50 | } 51 | 52 | public override string ToString() 53 | { 54 | return parameters.Select(p => p.Key + (p.Value != null ? "=" + p.Value : string.Empty)).Aggregate((x, y) => x + ";" + y); 55 | } 56 | 57 | public String this[String index] 58 | { 59 | get { return parameters[index]; } 60 | set { parameters[index] = value; } 61 | } 62 | 63 | public int Count 64 | { 65 | get 66 | { 67 | return parameters.Count; 68 | } 69 | } 70 | 71 | public bool IsReadOnly 72 | { 73 | get 74 | { 75 | return ((IDictionary)parameters).IsReadOnly; 76 | } 77 | } 78 | 79 | public ICollection Keys 80 | { 81 | get 82 | { 83 | return ((IDictionary)parameters).Keys; 84 | } 85 | } 86 | 87 | public ICollection Values 88 | { 89 | get 90 | { 91 | return ((IDictionary)parameters).Values; 92 | } 93 | } 94 | 95 | public void Add(KeyValuePair item) 96 | { 97 | ((IDictionary)parameters).Add(item); 98 | } 99 | 100 | public void Add(string key, string value) 101 | { 102 | parameters.Add(key, value); 103 | } 104 | 105 | public void Clear() 106 | { 107 | parameters.Clear(); 108 | } 109 | 110 | public bool Contains(KeyValuePair item) 111 | { 112 | return ((IDictionary)parameters).Contains(item); 113 | } 114 | 115 | public bool ContainsKey(string key) 116 | { 117 | return parameters.ContainsKey(key); 118 | } 119 | 120 | public void CopyTo(KeyValuePair[] array, int arrayIndex) 121 | { 122 | ((IDictionary)parameters).CopyTo(array, arrayIndex); 123 | } 124 | 125 | public IEnumerator> GetEnumerator() 126 | { 127 | return ((IDictionary)parameters).GetEnumerator(); 128 | } 129 | 130 | public bool Remove(KeyValuePair item) 131 | { 132 | return ((IDictionary)parameters).Remove(item); 133 | } 134 | 135 | public bool Remove(string key) 136 | { 137 | return parameters.Remove(key); 138 | } 139 | 140 | public bool TryGetValue(string key, out string value) 141 | { 142 | return parameters.TryGetValue(key, out value); 143 | } 144 | 145 | IEnumerator IEnumerable.GetEnumerator() 146 | { 147 | return ((IDictionary)parameters).GetEnumerator(); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/AACPayload.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace Unity.Ig.VideoStreaming.Server 6 | { 7 | // This class handles the AAC-hbd (High Bitrate) Payload 8 | // It has methods to process the RTP Payload 9 | 10 | // (c) 2018 Roger Hardiman, RJH Technical Consultancy Ltd 11 | 12 | 13 | /* 14 | RFC 3640 15 | 3.3.6. High Bit-rate AAC 16 | 17 | This mode is signaled by mode=AAC-hbr.This mode supports the 18 | transportation of variable size AAC frames.In one RTP packet, 19 | either one or more complete AAC frames are carried, or a single 20 | fragment of an AAC frame is carried.In this mode, the AAC frames 21 | are allowed to be interleaved and hence receivers MUST support de- 22 | interleaving.The maximum size of an AAC frame in this mode is 8191 23 | octets. 24 | 25 | In this mode, the RTP payload consists of the AU Header Section, 26 | followed by either one AAC frame, several concatenated AAC frames or 27 | one fragmented AAC frame.The Auxiliary Section MUST be empty. For 28 | each AAC frame contained in the payload, there MUST be an AU-header 29 | in the AU Header Section to provide: 30 | 31 | a) the size of each AAC frame in the payload and 32 | 33 | b) index information for computing the sequence(and hence timing) of 34 | each AAC frame. 35 | 36 | To code the maximum size of an AAC frame requires 13 bits. 37 | Therefore, in this configuration 13 bits are allocated to the AU- 38 | size, and 3 bits to the AU-Index(-delta) field.Thus, each AU-header 39 | has a size of 2 octets.Each AU-Index field MUST be coded with the 40 | value 0. In the AU Header Section, the concatenated AU-headers MUST 41 | be preceded by the 16-bit AU-headers-length field, as specified in 42 | section 3.2.1. 43 | 44 | In addition to the required MIME format parameters, the following 45 | parameters MUST be present: sizeLength, indexLength, and 46 | indexDeltaLength.AAC frames always have a fixed duration per Access 47 | Unit; when interleaving in this mode, this specific duration MUST be 48 | signaled by the MIME format parameter constantDuration.In addition, 49 | the parameter maxDisplacement MUST be present when interleaving. 50 | 51 | For example: 52 | 53 | m= audio 49230 RTP/AVP 96 54 | a= rtpmap:96 mpeg4-generic/48000/6 55 | a= fmtp:96 streamtype= 5; profile-level-id= 16; mode= AAC-hbr;config= 11B0; sizeLength= 13; indexLength= 3;indexDeltaLength= 3; constantDuration= 1024 56 | 57 | The hexadecimal value of the "config" parameter is the AudioSpecificConfig(), as defined in ISO/IEC 14496-3. 58 | AudioSpecificConfig() specifies a 5.1 channel AAC stream with a sampling rate of 48 kHz.For the description of MIME parameters, see 59 | section 4.1. 60 | 61 | */ 62 | 63 | 64 | public class AACPayload 65 | { 66 | public uint ObjectType = 0; 67 | public uint FrequencyIndex = 0; 68 | public uint ChannelConfiguration = 0; 69 | 70 | // Constructor 71 | public AACPayload(String config_string) 72 | { 73 | /*** 74 | 5 bits: object type 75 | if (object type == 31) 76 | 6 bits + 32: object type 77 | 4 bits: frequency index 78 | if (frequency index == 15) 79 | 24 bits: frequency 80 | 4 bits: channel configuration 81 | var bits: AOT Specific Config 82 | ***/ 83 | 84 | // config is a string in hex eg 1490 or 0x1210 85 | // Read each ASCII character and add to a bit array 86 | BitStream bs = new BitStream(); 87 | bs.AddHexString(config_string); 88 | 89 | // Read 5 bits 90 | ObjectType = bs.Read(5); 91 | 92 | // Read 4 bits 93 | FrequencyIndex = bs.Read(4); 94 | 95 | // Read 4 bits 96 | ChannelConfiguration = bs.Read(4); 97 | } 98 | 99 | public List Process_AAC_RTP_Packet(byte[] rtp_payload, int rtp_marker) { 100 | 101 | // RTP Payload for MPEG4-GENERIC can consist of multple blocks. 102 | // Each block has 3 parts 103 | // Part 1 - Acesss Unit Header Length + Header 104 | // Part 2 - Access Unit Auxiliary Data Length + Data (not used in AAC High Bitrate) 105 | // Part 3 - Access Unit Audio Data 106 | 107 | // The rest of the RTP packet is the AMR data 108 | List audio_data = new List(); 109 | 110 | int ptr = 0; 111 | 112 | while (true) { 113 | if (ptr + 4 > rtp_payload.Length) break; // 2 bytes for AU Header Length, 2 bytes of AU Header payload 114 | 115 | // Get Size of the AU Header 116 | int au_headers_length_bits = (((rtp_payload[ptr] << 8) + (rtp_payload[ptr + 1] << 0))); // 16 bits 117 | int au_headers_length = (int)Math.Ceiling((double)au_headers_length_bits / 8.0); 118 | ptr += 2; 119 | 120 | // Examine the AU Header. Get the size of the AAC data 121 | int aac_frame_size = (((rtp_payload[ptr] << 8) + (rtp_payload[ptr+1] << 0)) >> 3); // 13 bits 122 | int aac_index_delta = rtp_payload[ptr+1] & 0x03; // 3 bits 123 | ptr += au_headers_length; 124 | 125 | // extract the AAC block 126 | if (ptr + aac_frame_size > rtp_payload.Length) break; // not enough data to copy 127 | byte[] aac_data = new byte[aac_frame_size]; 128 | System.Array.Copy(rtp_payload, ptr, aac_data, 0, aac_frame_size); 129 | audio_data.Add(aac_data); 130 | ptr += aac_frame_size; 131 | } 132 | 133 | return audio_data; 134 | } 135 | 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Diagnostics; 6 | 7 | namespace Unity.Ig.VideoStreaming.Server.Messages 8 | { 9 | /// 10 | /// An Rtsp Request 11 | /// 12 | public class RtspRequest : RtspMessage 13 | { 14 | 15 | /// 16 | /// Request type. 17 | /// 18 | public enum RequestType 19 | { 20 | UNKNOWN, 21 | DESCRIBE, 22 | ANNOUNCE, 23 | GET_PARAMETER, 24 | OPTIONS, 25 | PAUSE, 26 | PLAY, 27 | RECORD, 28 | REDIRECT, 29 | SETUP, 30 | SET_PARAMETER, 31 | TEARDOWN, 32 | } 33 | 34 | /// 35 | /// Parses the request command. 36 | /// 37 | /// A string request command. 38 | /// The typed request. 39 | internal static RequestType ParseRequest(string aStringRequest) 40 | { 41 | RequestType returnValue; 42 | if (!Enum.TryParse(aStringRequest, true, out returnValue)) 43 | returnValue = RequestType.UNKNOWN; 44 | return returnValue; 45 | } 46 | 47 | /// 48 | /// Gets the Rtsp request. 49 | /// 50 | /// A request parts. 51 | /// the parsed request 52 | internal static RtspMessage GetRtspRequest(string[] aRequestParts) 53 | { 54 | // 55 | Debug.Assert(aRequestParts != (string[])null, "aRequestParts"); 56 | Debug.Assert(aRequestParts.Length != 0, "aRequestParts.Length == 0"); 57 | // 58 | // we already know this is a Request 59 | RtspRequest returnValue; 60 | switch (ParseRequest(aRequestParts[0])) 61 | { 62 | case RequestType.OPTIONS: 63 | returnValue = new RtspRequestOptions(); 64 | break; 65 | case RequestType.DESCRIBE: 66 | returnValue = new RtspRequestDescribe(); 67 | break; 68 | case RequestType.SETUP: 69 | returnValue = new RtspRequestSetup(); 70 | break; 71 | case RequestType.PLAY: 72 | returnValue = new RtspRequestPlay(); 73 | break; 74 | case RequestType.PAUSE: 75 | returnValue = new RtspRequestPause(); 76 | break; 77 | case RequestType.TEARDOWN: 78 | returnValue = new RtspRequestTeardown(); 79 | break; 80 | case RequestType.GET_PARAMETER: 81 | returnValue = new RtspRequestGetParameter(); 82 | break; 83 | case RequestType.ANNOUNCE: 84 | returnValue = new RtspRequestAnnounce(); 85 | break; 86 | case RequestType.RECORD: 87 | returnValue = new RtspRequestRecord(); 88 | break; 89 | /* 90 | case RequestType.REDIRECT: 91 | break; 92 | 93 | case RequestType.SET_PARAMETER: 94 | break; 95 | */ 96 | case RequestType.UNKNOWN: 97 | default: 98 | returnValue = new RtspRequest(); 99 | break; 100 | } 101 | 102 | 103 | 104 | return returnValue; 105 | } 106 | 107 | /// 108 | /// Initializes a new instance of the class. 109 | /// 110 | public RtspRequest() 111 | { 112 | Command = "OPTIONS * RTSP/1.0"; 113 | } 114 | 115 | /// 116 | /// Gets the request. 117 | /// 118 | /// The request in string format. 119 | public string Request 120 | { 121 | get 122 | { 123 | return commandArray[0]; 124 | } 125 | } 126 | 127 | /// 128 | /// Gets the request. 129 | /// The return value is typed with if the value is not 130 | /// reconise the value is sent. The string value can be get by 131 | /// 132 | /// The request. 133 | public RequestType RequestTyped 134 | { 135 | get 136 | { 137 | return ParseRequest(commandArray[0]); 138 | } 139 | set 140 | { 141 | if (Enum.IsDefined(typeof(RequestType), value)) 142 | commandArray[0] = value.ToString(); 143 | else 144 | commandArray[0] = RequestType.UNKNOWN.ToString(); 145 | } 146 | } 147 | 148 | private Uri _RtspUri; 149 | /// 150 | /// Gets or sets the Rtsp asked URI. 151 | /// 152 | /// The Rtsp asked URI. 153 | /// The request with uri * is return with null URI 154 | public Uri RtspUri 155 | { 156 | get 157 | { 158 | if (commandArray.Length < 2 || commandArray[1]=="*") 159 | return null; 160 | if (_RtspUri == null) 161 | Uri.TryCreate(commandArray[1], UriKind.Absolute, out _RtspUri); 162 | return _RtspUri; 163 | } 164 | set 165 | { 166 | _RtspUri = value; 167 | if (commandArray.Length < 2) 168 | { 169 | Array.Resize(ref commandArray, 3); 170 | } 171 | commandArray[1] = (value != null ? value.ToString().TrimEnd('/') : "*"); 172 | } 173 | } 174 | 175 | /// 176 | /// Gets the assiociate OK response with the request. 177 | /// 178 | /// an Rtsp response correcponding to request. 179 | public virtual RtspResponse CreateResponse() 180 | { 181 | RtspResponse returnValue = new RtspResponse(); 182 | returnValue.ReturnCode = 200; 183 | returnValue.CSeq = this.CSeq; 184 | if (this.Headers.ContainsKey(RtspHeaderNames.Session)) 185 | { 186 | returnValue.Headers[RtspHeaderNames.Session] = this.Headers[RtspHeaderNames.Session]; 187 | } 188 | 189 | return returnValue; 190 | } 191 | 192 | public Object ContextData { get; set; } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/H264Encoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEngine.Profiling; 4 | using Unity.Collections; 5 | using Unity.Collections.LowLevel.Unsafe; 6 | using Unity.Jobs; 7 | 8 | namespace Unity.Ig.VideoStreaming.Server 9 | { 10 | public struct H264Encoder : IDisposable 11 | { 12 | public void Dispose() 13 | { 14 | m_CreateJob.Complete(); 15 | m_EncodeJob.Complete(); 16 | if (m_EncoderPtr.IsCreated) 17 | { 18 | H264EncoderPlugin.DestroyEncoder(m_EncoderPtr[0]); 19 | m_EncoderPtr.Dispose(); 20 | } 21 | } 22 | 23 | // Start is called before the first frame update 24 | public JobHandle Start(uint width, uint height, uint frameRate, ref NativeList spsNalu, ref NativeList ppsNalu) 25 | { 26 | if (!m_CreateJob.Equals(default(JobHandle))) 27 | { 28 | Debug.LogWarning("Trying to start the H264Encoder more than once."); 29 | return default; 30 | } 31 | 32 | m_EncoderPtr = new NativeArray(1, Allocator.Persistent); 33 | 34 | var createJob = new CreateEncoderJob 35 | { 36 | width = width, 37 | height = height, 38 | frameRateNumerator = frameRate, 39 | encoderPtr = m_EncoderPtr, 40 | spsNalu = spsNalu, 41 | ppsNalu = ppsNalu 42 | }; 43 | m_CreateJob = createJob.Schedule(); 44 | return m_CreateJob; 45 | } 46 | 47 | public JobHandle Encode(in JobHandle dependsOn, in NativeArray imageData, ulong timeStampNs, ref NativeList spsNalu, ref NativeList ppsNalu, ref NativeList imageNalu) 48 | { 49 | if (!m_CreateJob.IsCompleted) 50 | return default; 51 | m_CreateJob.Complete(); 52 | 53 | if (!m_EncoderPtr.IsCreated || m_EncoderPtr[0] == IntPtr.Zero || !m_EncodeJob.IsCompleted) 54 | return default; 55 | m_EncodeJob.Complete(); 56 | 57 | var encodeJob = new EncodeJob 58 | { 59 | encoderPtr = m_EncoderPtr[0], 60 | pixelData = imageData, 61 | timeStampNs = timeStampNs, 62 | spsNalu = spsNalu, 63 | ppsNalu = ppsNalu, 64 | imageNalu = imageNalu 65 | }; 66 | m_EncodeJob = encodeJob.Schedule(dependsOn); 67 | return m_EncodeJob; 68 | } 69 | 70 | static bool ConsumeBuffer(IntPtr encoderPtr, ref NativeList spsNalu, ref NativeList ppsNalu, ref NativeList imageNalu) 71 | { 72 | Profiler.BeginSample("BeginConsumeEncodedBuffer"); 73 | bool success = H264EncoderPlugin.BeginConsumeEncodedBuffer(encoderPtr, out uint bufferSize); 74 | Profiler.EndSample(); 75 | if (!success) 76 | { 77 | Debug.LogWarning("No encoded frame ready."); 78 | return false; 79 | } 80 | 81 | imageNalu.ResizeUninitialized((int)bufferSize); 82 | 83 | Profiler.BeginSample("EndConsumeEncodedBuffer"); 84 | ulong bufferTimeStampNs; 85 | bool isKeyFrame; 86 | unsafe 87 | { 88 | success = H264EncoderPlugin.EndConsumeEncodedBuffer(encoderPtr, (byte*)imageNalu.GetUnsafePtr(), out bufferTimeStampNs, out isKeyFrame); 89 | } 90 | 91 | Profiler.EndSample(); 92 | if (!success) 93 | { 94 | Debug.LogErrorFormat("Could not get {0} bytes encoded buffer", bufferSize); 95 | return false; 96 | } 97 | 98 | if (isKeyFrame) 99 | unsafe 100 | { 101 | //Debug.LogFormat("Produced keyframe at t={0}, size={1}", bufferTimeStampNs, bufferSize); 102 | var sz = H264EncoderPlugin.GetSpsNAL(encoderPtr, (byte*)0); 103 | spsNalu.ResizeUninitialized((int)sz); 104 | H264EncoderPlugin.GetSpsNAL(encoderPtr, (byte*)spsNalu.GetUnsafePtr()); 105 | 106 | sz = H264EncoderPlugin.GetPpsNAL(encoderPtr, (byte*)0); 107 | ppsNalu.ResizeUninitialized((int)sz); 108 | H264EncoderPlugin.GetPpsNAL(encoderPtr, (byte*)ppsNalu.GetUnsafePtr()); 109 | } 110 | 111 | return true; 112 | } 113 | 114 | private struct CreateEncoderJob : IJob 115 | { 116 | [ReadOnly] 117 | public uint width; 118 | 119 | [ReadOnly] 120 | public uint height; 121 | 122 | [ReadOnly] 123 | public uint frameRateNumerator; 124 | 125 | [WriteOnly] 126 | public NativeArray encoderPtr; 127 | 128 | public NativeList spsNalu; 129 | public NativeList ppsNalu; 130 | 131 | public void Execute() 132 | { 133 | var encoder = H264EncoderPlugin.CreateEncoder(width, height, frameRateNumerator, 1, 1000000); 134 | encoderPtr[0] = encoder; 135 | unsafe 136 | { 137 | uint sz = H264EncoderPlugin.GetSpsNAL(encoder, (byte*)0); 138 | spsNalu.ResizeUninitialized((int)sz); 139 | H264EncoderPlugin.GetSpsNAL(encoder, (byte*)spsNalu.GetUnsafePtr()); 140 | 141 | sz = H264EncoderPlugin.GetPpsNAL(encoder, (byte*)0); 142 | ppsNalu.ResizeUninitialized((int)sz); 143 | H264EncoderPlugin.GetPpsNAL(encoder, (byte*)ppsNalu.GetUnsafePtr()); 144 | } 145 | } 146 | } 147 | 148 | private struct EncodeJob : IJob 149 | { 150 | [ReadOnly] 151 | [NativeDisableUnsafePtrRestriction] 152 | public IntPtr encoderPtr; 153 | 154 | [ReadOnly] 155 | public NativeArray pixelData; 156 | 157 | [ReadOnly] 158 | public ulong timeStampNs; 159 | 160 | public NativeList spsNalu; 161 | public NativeList ppsNalu; 162 | public NativeList imageNalu; 163 | 164 | public void Execute() 165 | { 166 | unsafe 167 | { 168 | Profiler.BeginSample("EncodeFrame"); 169 | 170 | //Debug.Log("Encoding source image with " + pixelData.Length + " bytes"); 171 | bool success = H264EncoderPlugin.EncodeFrame(encoderPtr, (byte*)pixelData.GetUnsafeReadOnlyPtr(), timeStampNs); 172 | Profiler.EndSample(); 173 | 174 | if (!success) 175 | { 176 | Debug.LogErrorFormat("Error encoding frame at t = {0}", timeStampNs); 177 | return; 178 | } 179 | } 180 | 181 | Profiler.BeginSample("ConsumeBuffer"); 182 | ConsumeBuffer(encoderPtr, ref spsNalu, ref ppsNalu, ref imageNalu); 183 | Profiler.EndSample(); 184 | } 185 | } 186 | 187 | // Held in native array so we can have it initialized from a job. 188 | NativeArray m_EncoderPtr; 189 | private JobHandle m_CreateJob; 190 | private JobHandle m_EncodeJob; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Authentication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | using UnityEngine; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server 7 | { 8 | 9 | // WWW-Authentication and Authorization Headers 10 | public class Authentication 11 | { 12 | private static ILogger _logger = new Logger(Debug.unityLogger.logHandler); 13 | 14 | static Authentication() 15 | { 16 | _logger.logEnabled = false; 17 | } 18 | 19 | public enum Type {Basic, Digest}; 20 | 21 | private String username = null; 22 | private String password = null; 23 | private String realm = null; 24 | private String nonce = null; 25 | private Type authentication_type = Type.Digest; 26 | private readonly MD5 md5 = System.Security.Cryptography.MD5.Create(); 27 | 28 | 29 | private const char quote = '\"'; 30 | 31 | // Constructor 32 | public Authentication(String username, String password, String realm, Type authentication_type) 33 | { 34 | this.username = username; 35 | this.password = password; 36 | this.realm = realm; 37 | this.authentication_type = authentication_type; 38 | 39 | this.nonce = new System.Random().Next(100000000,999999999).ToString(); // random 9 digit number 40 | } 41 | 42 | public String GetHeader() { 43 | if (authentication_type == Type.Basic) { 44 | return "Basic realm=" + quote + realm + quote; 45 | } 46 | if (authentication_type == Type.Digest) { 47 | return "Digest realm=" + quote + realm + quote + ", nonce=" + quote + nonce + quote; 48 | } 49 | return null; 50 | } 51 | 52 | 53 | public bool IsValid(Messages.RtspMessage received_message) { 54 | 55 | string authorization = received_message.Headers["Authorization"]; 56 | 57 | 58 | // Check Username and Password 59 | if (authentication_type == Type.Basic && authorization.StartsWith("Basic ")) { 60 | string base64_str = authorization.Substring(6); // remove 'Basic ' 61 | byte[] data = Convert.FromBase64String(base64_str); 62 | string decoded = Encoding.UTF8.GetString(data); 63 | int split_position = decoded.IndexOf(':'); 64 | string decoded_username = decoded.Substring(0, split_position); 65 | string decoded_password = decoded.Substring(split_position + 1); 66 | 67 | if ((decoded_username == username) && (decoded_password == password)) { 68 | _logger.Log("Basic Authorization passed"); 69 | return true; 70 | } else { 71 | _logger.Log("Basic Authorization failed"); 72 | return false; 73 | } 74 | } 75 | 76 | // Check Username, URI, Nonce and the MD5 hashed Response 77 | if (authentication_type == Type.Digest && authorization.StartsWith("Digest ")) { 78 | string value_str = authorization.Substring(7); // remove 'Digest ' 79 | string[] values = value_str.Split(','); 80 | string auth_header_username = null; 81 | string auth_header_realm = null; 82 | string auth_header_nonce = null; 83 | string auth_header_uri = null; 84 | string auth_header_response = null; 85 | string message_method = null; 86 | string message_uri = null; 87 | try { 88 | message_method = received_message.Command.Split(' ')[0]; 89 | message_uri = received_message.Command.Split(' ')[1]; 90 | } catch {} 91 | 92 | foreach (string value in values) { 93 | string[] tuple = value.Trim().Split(new char[] {'='},2); // split on first '=' 94 | if (tuple.Length == 2 && tuple[0].Equals("username")) { 95 | auth_header_username = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes 96 | } 97 | else if (tuple.Length == 2 && tuple[0].Equals("realm")) { 98 | auth_header_realm = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes 99 | } 100 | else if (tuple.Length == 2 && tuple[0].Equals("nonce")) { 101 | auth_header_nonce = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes 102 | } 103 | else if (tuple.Length == 2 && tuple[0].Equals("uri")) { 104 | auth_header_uri = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes 105 | } 106 | else if (tuple.Length == 2 && tuple[0].Equals("response")) { 107 | auth_header_response = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes 108 | } 109 | } 110 | 111 | // Create the MD5 Hash using all parameters passed in the Auth Header with the 112 | // addition of the 'Password' 113 | String hashA1 = CalculateMD5Hash(md5, auth_header_username+":"+auth_header_realm+":"+this.password); 114 | String hashA2 = CalculateMD5Hash(md5, message_method + ":" + auth_header_uri); 115 | String expected_response = CalculateMD5Hash(md5, hashA1 + ":" + auth_header_nonce + ":" + hashA2); 116 | 117 | // Check if everything matches 118 | // ToDo - extract paths from the URIs (ignoring SETUP's trackID) 119 | if ((auth_header_username == this.username) 120 | && (auth_header_realm == this.realm) 121 | && (auth_header_nonce == this.nonce) 122 | && (auth_header_response == expected_response) 123 | ){ 124 | _logger.Log("Digest Authorization passed"); 125 | return true; 126 | } else { 127 | _logger.Log("Digest Authorization failed"); 128 | return false; 129 | } 130 | } 131 | return false; 132 | } 133 | 134 | 135 | 136 | // Generate Basic or Digest Authorization 137 | public string GenerateAuthorization(string username, string password, 138 | string auth_type, string realm, string nonce, string url, string command) { 139 | 140 | if (username == null || username.Length == 0) return null; 141 | if (password == null || password.Length == 0) return null; 142 | if (realm == null || realm.Length == 0) return null; 143 | if (auth_type.Equals("Digest") && (nonce == null || nonce.Length == 0)) return null; 144 | 145 | if (auth_type.Equals("Basic")) { 146 | byte[] credentials = System.Text.Encoding.UTF8.GetBytes(username+":"+password); 147 | String credentials_base64 = Convert.ToBase64String(credentials); 148 | String basic_authorization = "Basic " + credentials_base64; 149 | return basic_authorization; 150 | } 151 | else if (auth_type.Equals("Digest")) { 152 | 153 | MD5 md5 = System.Security.Cryptography.MD5.Create(); 154 | String hashA1 = CalculateMD5Hash(md5, username+":"+realm+":"+password); 155 | String hashA2 = CalculateMD5Hash(md5, command + ":" + url); 156 | String response = CalculateMD5Hash(md5, hashA1 + ":" + nonce + ":" + hashA2); 157 | 158 | const String quote = "\""; 159 | String digest_authorization = "Digest username=" + quote + username + quote +", " 160 | + "realm=" + quote + realm + quote + ", " 161 | + "nonce=" + quote + nonce + quote + ", " 162 | + "uri=" + quote + url + quote + ", " 163 | + "response=" + quote + response + quote; 164 | 165 | return digest_authorization; 166 | } 167 | else { 168 | return null; 169 | } 170 | 171 | } 172 | 173 | 174 | 175 | // MD5 (lower case) 176 | private string CalculateMD5Hash(MD5 md5_session, string input) 177 | { 178 | byte[] inputBytes = System.Text.Encoding.UTF8.GetBytes(input); 179 | byte[] hash = md5_session.ComputeHash(inputBytes); 180 | 181 | StringBuilder output = new StringBuilder(); 182 | for (int i = 0; i < hash.Length; i++) { 183 | output.Append(hash[i].ToString("x2")); 184 | } 185 | 186 | return output.ToString(); 187 | } 188 | } 189 | } -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Native~/H264Encoder/H264Encoder.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {45BDF642-D8AA-400A-A088-A66D09B1E247} 24 | Win32Proj 25 | H264Encoder 26 | 10.0.17763.0 27 | 28 | 29 | 30 | DynamicLibrary 31 | true 32 | v141 33 | Unicode 34 | 35 | 36 | DynamicLibrary 37 | false 38 | v141 39 | true 40 | Unicode 41 | 42 | 43 | DynamicLibrary 44 | true 45 | v141 46 | Unicode 47 | 48 | 49 | DynamicLibrary 50 | false 51 | v141 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | true 78 | 79 | 80 | false 81 | 82 | 83 | false 84 | 85 | 86 | 87 | Use 88 | Level3 89 | Disabled 90 | true 91 | WIN32;_DEBUG;H264ENCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 92 | true 93 | 94 | 95 | Windows 96 | true 97 | 98 | 99 | 100 | 101 | Use 102 | Level3 103 | Disabled 104 | true 105 | _DEBUG;H264ENCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 106 | true 107 | 108 | 109 | Windows 110 | true 111 | 112 | 113 | 114 | 115 | Use 116 | Level3 117 | MaxSpeed 118 | true 119 | true 120 | true 121 | WIN32;NDEBUG;H264ENCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 122 | true 123 | 124 | 125 | Windows 126 | true 127 | true 128 | true 129 | 130 | 131 | 132 | 133 | Use 134 | Level3 135 | MaxSpeed 136 | true 137 | true 138 | true 139 | NDEBUG;H264ENCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 140 | true 141 | 142 | 143 | Windows 144 | true 145 | true 146 | true 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | Create 158 | Create 159 | Create 160 | Create 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Sdp/SdpFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.IO; 6 | using System.Globalization; 7 | 8 | namespace Unity.Ig.VideoStreaming.Server.Sdp 9 | { 10 | public class SdpFile 11 | { 12 | private static KeyValuePair GetKeyValue(TextReader sdpStream) 13 | { 14 | string line = sdpStream.ReadLine(); 15 | 16 | // end of file ? 17 | if(string.IsNullOrEmpty(line)) 18 | return new KeyValuePair(null, null); 19 | 20 | 21 | string[] parts = line.Split(new char[] { '=' }, 2); 22 | if (parts.Length != 2) 23 | throw new InvalidDataException(); 24 | if (parts[0].Length != 1) 25 | throw new InvalidDataException(); 26 | 27 | KeyValuePair value = new KeyValuePair(parts[0], parts[1]); 28 | return value; 29 | } 30 | 31 | /// 32 | /// Reads the specified SDP stream. 33 | /// As define in RFC 4566 34 | /// 35 | /// The SDP stream. 36 | /// 37 | public static SdpFile Read(TextReader sdpStream) 38 | { 39 | SdpFile returnValue = new SdpFile(); 40 | KeyValuePair value = GetKeyValue(sdpStream); 41 | 42 | // Version mandatory 43 | if (value.Key == "v") 44 | { 45 | returnValue.Version = int.Parse(value.Value, CultureInfo.InvariantCulture); 46 | value = GetKeyValue(sdpStream); 47 | } 48 | else { 49 | throw new InvalidDataException(); 50 | } 51 | 52 | // Origin mandatory 53 | if (value.Key == "o") 54 | { 55 | returnValue.Origin = Origin.Parse(value.Value); 56 | value = GetKeyValue(sdpStream); 57 | } 58 | else { 59 | throw new InvalidDataException(); 60 | } 61 | 62 | // Session mandatory. 63 | // However the MuxLab HDMI Encoder (TX-500762) Firmware 1.0.6 64 | // does not include the 'Session' so supress InvalidDatarException 65 | if (value.Key == "s") 66 | { 67 | returnValue.Session = value.Value; 68 | value = GetKeyValue(sdpStream); 69 | } 70 | else { 71 | // throw new InvalidDataException(); // we should throw, but instead we just ignore the error 72 | } 73 | 74 | // Session Information optional 75 | if (value.Key == "i") 76 | { 77 | returnValue.SessionInformation = value.Value; 78 | value = GetKeyValue(sdpStream); 79 | } 80 | 81 | // Uri optional 82 | if (value.Key == "u") 83 | { 84 | returnValue.Url = new Uri(value.Value); 85 | value = GetKeyValue(sdpStream); 86 | } 87 | 88 | // Email optional 89 | if (value.Key == "e") 90 | { 91 | returnValue.Email = value.Value; 92 | value = GetKeyValue(sdpStream); 93 | } 94 | 95 | // Phone optional 96 | if (value.Key == "p") 97 | { 98 | returnValue.Phone = value.Value; 99 | value = GetKeyValue(sdpStream); 100 | } 101 | 102 | // Connection optional 103 | if (value.Key == "c") 104 | { 105 | returnValue.Connection = Connection.Parse(value.Value); 106 | value = GetKeyValue(sdpStream); 107 | } 108 | 109 | // bandwidth optional 110 | if (value.Key == "b") 111 | { 112 | returnValue.Bandwidth = Bandwidth.Parse(value.Value); 113 | value = GetKeyValue(sdpStream); 114 | } 115 | 116 | // Timing mandatory 117 | while (value.Key == "t") 118 | { 119 | string timing = value.Value; 120 | string repeat = string.Empty; 121 | value = GetKeyValue(sdpStream); 122 | if (value.Key == "r") 123 | { 124 | repeat = value.Value; 125 | value = GetKeyValue(sdpStream); 126 | } 127 | returnValue.Timings.Add(new Timing(timing, repeat)); 128 | } 129 | 130 | // timezone optional 131 | if (value.Key == "z") 132 | { 133 | 134 | returnValue.TimeZone = SdpTimeZone.ParseInvariant(value.Value); 135 | value = GetKeyValue(sdpStream); 136 | } 137 | 138 | // encryption key optional 139 | if (value.Key == "k") 140 | { 141 | 142 | returnValue.EncriptionKey = EncriptionKey.ParseInvariant(value.Value); 143 | value = GetKeyValue(sdpStream); 144 | } 145 | 146 | //Attribute optional multiple 147 | while (value.Key == "a") 148 | { 149 | returnValue.Attributs.Add(Attribut.ParseInvariant(value.Value)); 150 | value = GetKeyValue(sdpStream); 151 | } 152 | 153 | // Hack for MuxLab HDMI Encoder (TX-500762) Firmware 1.0.6 154 | // Skip over all other Key/Value pairs until the 'm=' key 155 | while (value.Key != "m") { 156 | value = GetKeyValue(sdpStream); 157 | } 158 | 159 | // Media 160 | while (value.Key == "m") 161 | { 162 | Media newMedia = ReadMedia(sdpStream, ref value); 163 | returnValue.Medias.Add(newMedia); 164 | } 165 | 166 | 167 | return returnValue; 168 | } 169 | 170 | private static Media ReadMedia(TextReader sdpStream, ref KeyValuePair value) 171 | { 172 | Media returnValue = new Media(value.Value); 173 | value = GetKeyValue(sdpStream); 174 | 175 | // Media title 176 | if (value.Key == "i") 177 | { 178 | value = GetKeyValue(sdpStream); 179 | } 180 | 181 | // Connexion optional 182 | if (value.Key == "c") 183 | { 184 | returnValue.Connection = Connection.Parse(value.Value); 185 | value = GetKeyValue(sdpStream); 186 | } 187 | 188 | // bandwidth optional 189 | if (value.Key == "b") 190 | { 191 | returnValue.Bandwidth = Bandwidth.Parse(value.Value); 192 | value = GetKeyValue(sdpStream); 193 | } 194 | 195 | // enkription key optional 196 | if (value.Key == "k") 197 | { 198 | 199 | returnValue.EncriptionKey = EncriptionKey.ParseInvariant(value.Value); 200 | value = GetKeyValue(sdpStream); 201 | } 202 | 203 | //Attribut optional multiple 204 | while (value.Key == "a") 205 | { 206 | returnValue.Attributs.Add(Attribut.ParseInvariant(value.Value)); 207 | value = GetKeyValue(sdpStream); 208 | } 209 | 210 | return returnValue; 211 | } 212 | 213 | 214 | public int Version { get; set; } 215 | 216 | 217 | public Origin Origin { get; set; } 218 | 219 | public string Session { get; set; } 220 | 221 | public string SessionInformation { get; set; } 222 | 223 | public Uri Url { get; set; } 224 | 225 | public string Email { get; set; } 226 | 227 | public string Phone { get; set; } 228 | 229 | public Connection Connection { get; set; } 230 | 231 | public Bandwidth Bandwidth { get; set; } 232 | 233 | private readonly List timingList = new List(); 234 | 235 | public IList Timings 236 | { 237 | get 238 | { 239 | return timingList; 240 | } 241 | } 242 | 243 | public SdpTimeZone TimeZone { get; set; } 244 | 245 | public EncriptionKey EncriptionKey { get; set; } 246 | 247 | private readonly List attributs = new List(); 248 | 249 | public IList Attributs 250 | { 251 | get 252 | { 253 | return attributs; 254 | } 255 | } 256 | 257 | private readonly List medias = new List(); 258 | 259 | public IList Medias 260 | { 261 | get 262 | { 263 | return medias; 264 | } 265 | 266 | } 267 | 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/UdpSocket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | using System.Threading; 5 | using UnityEngine.Profiling; 6 | 7 | namespace Unity.Ig.VideoStreaming.Server 8 | { 9 | public class UDPSocket 10 | { 11 | 12 | private UdpClient data_socket = null; 13 | private UdpClient control_socket = null; 14 | 15 | private Thread data_read_thread = null; 16 | private Thread control_read_thread = null; 17 | 18 | public int data_port = 50000; 19 | public int control_port = 50001; 20 | 21 | bool is_multicast = false; 22 | IPAddress data_mcast_addr; 23 | IPAddress control_mcast_addr; 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// Creates two new UDP sockets using the start and end Port range 28 | /// 29 | public UDPSocket(int start_port, int end_port) 30 | { 31 | 32 | is_multicast = false; 33 | 34 | // open a pair of UDP sockets - one for data (video or audio) and one for the status channel (RTCP messages) 35 | data_port = start_port; 36 | control_port = start_port + 1; 37 | 38 | bool ok = false; 39 | while (ok == false && (control_port < end_port)) 40 | { 41 | // Video/Audio port must be odd and command even (next one) 42 | try 43 | { 44 | data_socket = new UdpClient(data_port); 45 | control_socket = new UdpClient(control_port); 46 | ok = true; 47 | } 48 | catch (SocketException) 49 | { 50 | // Fail to allocate port, try again 51 | if (data_socket != null) 52 | data_socket.Close(); 53 | if (control_socket != null) 54 | control_socket.Close(); 55 | 56 | // try next data or control port 57 | data_port += 2; 58 | control_port += 2; 59 | } 60 | 61 | if (ok) 62 | { 63 | data_socket.Client.ReceiveBufferSize = 100 * 1024; 64 | data_socket.Client.SendBufferSize = 65535; // default is 8192. Make it as large as possible for large RTP packets which are not fragmented 65 | 66 | control_socket.Client.DontFragment = false; 67 | 68 | } 69 | } 70 | } 71 | 72 | 73 | /// 74 | /// Initializes a new instance of the class. 75 | /// Used with Multicast mode with the Multicast Address and Port 76 | /// 77 | public UDPSocket(String data_multicast_address, int data_multicast_port, String control_multicast_address, int control_multicast_port) 78 | { 79 | 80 | is_multicast = true; 81 | 82 | // open a pair of UDP sockets - one for data (video or audio) and one for the status channel (RTCP messages) 83 | this.data_port = data_multicast_port; 84 | this.control_port = control_multicast_port; 85 | 86 | try 87 | { 88 | IPEndPoint data_ep = new IPEndPoint(IPAddress.Any, data_port); 89 | IPEndPoint control_ep = new IPEndPoint(IPAddress.Any, control_port); 90 | 91 | data_mcast_addr = IPAddress.Parse(data_multicast_address); 92 | control_mcast_addr = IPAddress.Parse(control_multicast_address); 93 | 94 | data_socket = new UdpClient(); 95 | data_socket.Client.Bind(data_ep); 96 | data_socket.JoinMulticastGroup(data_mcast_addr); 97 | 98 | control_socket = new UdpClient(); 99 | control_socket.Client.Bind(control_ep); 100 | control_socket.JoinMulticastGroup(control_mcast_addr); 101 | 102 | 103 | data_socket.Client.ReceiveBufferSize = 100 * 1024; 104 | data_socket.Client.SendBufferSize = 65535; // default is 8192. Make it as large as possible for large RTP packets which are not fragmented 105 | 106 | 107 | control_socket.Client.DontFragment = false; 108 | 109 | } 110 | catch (SocketException) 111 | { 112 | // Fail to allocate port, try again 113 | if (data_socket != null) 114 | data_socket.Close(); 115 | if (control_socket != null) 116 | control_socket.Close(); 117 | 118 | return; 119 | } 120 | } 121 | 122 | /// 123 | /// Starts this instance. 124 | /// 125 | public void Start() 126 | { 127 | if (data_socket == null || control_socket == null) 128 | { 129 | throw new InvalidOperationException("UDP Forwader host was not initialized, can't continue"); 130 | } 131 | 132 | if (data_read_thread != null) 133 | { 134 | throw new InvalidOperationException("Forwarder was stopped, can't restart it"); 135 | } 136 | 137 | data_read_thread = new Thread(() => DoWorkerJob(data_socket, data_port)); 138 | data_read_thread.Name = "DataPort " + data_port; 139 | data_read_thread.Start(); 140 | 141 | control_read_thread = new Thread(() => DoWorkerJob(control_socket, control_port)); 142 | control_read_thread.Name = "ControlPort " + control_port; 143 | control_read_thread.Start(); 144 | } 145 | 146 | /// 147 | /// Stops this instance. 148 | /// 149 | public void Stop() 150 | { 151 | if (is_multicast) 152 | { 153 | // leave the multicast groups 154 | data_socket.DropMulticastGroup(data_mcast_addr); 155 | control_socket.DropMulticastGroup(control_mcast_addr); 156 | } 157 | data_socket.Close(); 158 | control_socket.Close(); 159 | } 160 | 161 | /// 162 | /// Occurs when message is received. 163 | /// 164 | public event EventHandler DataReceived; 165 | 166 | /// 167 | /// Raises the event. 168 | /// 169 | /// The instance containing the event data. 170 | protected void OnDataReceived(RtspChunkEventArgs rtspChunkEventArgs) 171 | { 172 | EventHandler handler = DataReceived; 173 | 174 | if (handler != null) 175 | handler(this, rtspChunkEventArgs); 176 | } 177 | 178 | 179 | /// 180 | /// Does the video job. 181 | /// 182 | private void DoWorkerJob(System.Net.Sockets.UdpClient socket, int data_port) 183 | { 184 | Profiler.BeginThreadProfiling("RTSP", $"UDPSocket.DoWorkerJob port {data_port}"); 185 | IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, data_port); 186 | try 187 | { 188 | // loop until we get an exception eg the socket closed 189 | while (true) 190 | { 191 | byte[] frame = socket.Receive(ref ipEndPoint); 192 | 193 | Profiler.BeginSample("OnDataReceived"); 194 | // We have an RTP frame. 195 | // Fire the DataReceived event with 'frame' 196 | Console.WriteLine("Received RTP data on port " + data_port); 197 | 198 | Messages.RtspChunk currentMessage = new Messages.RtspData(); 199 | // aMessage.SourcePort = ?? 200 | currentMessage.Data = frame; 201 | ((Messages.RtspData)currentMessage).Channel = data_port; 202 | 203 | 204 | OnDataReceived(new RtspChunkEventArgs(currentMessage)); 205 | 206 | Profiler.EndSample(); 207 | } 208 | } 209 | catch (ObjectDisposedException) 210 | { 211 | } 212 | catch (SocketException) 213 | { 214 | } 215 | } 216 | 217 | /// 218 | /// Write to the RTP Data Port 219 | /// 220 | public void Write_To_Data_Port(byte[] data, String hostname, int port) { 221 | data_socket.Send(data,data.Length, hostname, port); 222 | } 223 | 224 | /// 225 | /// Write to the RTP Control Port 226 | /// 227 | public void Write_To_Control_Port(byte[] data, String hostname, int port) 228 | { 229 | data_socket.Send(data, data.Length, hostname, port); 230 | } 231 | 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Globalization; 6 | 7 | namespace Unity.Ig.VideoStreaming.Server.Messages 8 | { 9 | public class RtspResponse : RtspMessage 10 | { 11 | public const int DEFAULT_TIMEOUT = 60; 12 | 13 | /// 14 | /// Gets the default error message for an error code. 15 | /// 16 | /// An error code. 17 | /// The default error message associate 18 | private static string GetDefaultError(int aErrorCode) 19 | { 20 | switch (aErrorCode) 21 | { 22 | 23 | case 100: return "Continue"; 24 | 25 | case 200: return "OK"; 26 | case 201: return "Created"; 27 | case 250: return "Low on Storage Space"; 28 | 29 | case 300: return "Multiple Choices"; 30 | case 301: return "Moved Permanently"; 31 | case 302: return "Moved Temporarily"; 32 | case 303: return "See Other"; 33 | case 305: return "Use Proxy"; 34 | 35 | case 400: return "Bad Request"; 36 | case 401: return "Unauthorized"; 37 | case 402: return "Payment Required"; 38 | case 403: return "Forbidden"; 39 | case 404: return "Not Found"; 40 | case 405: return "Method Not Allowed"; 41 | case 406: return "Not Acceptable"; 42 | case 407: return "Proxy Authentication Required"; 43 | case 408: return "Request Timeout"; 44 | case 410: return "Gone"; 45 | case 411: return "Length Required"; 46 | case 412: return "Precondition Failed"; 47 | case 413: return "Request Entity Too Large"; 48 | case 414: return "Request-URI Too Long"; 49 | case 415: return "Unsupported Media Type"; 50 | case 451: return "Invalid parameter"; 51 | case 452: return "Illegal Conference Identifier"; 52 | case 453: return "Not Enough Bandwidth"; 53 | case 454: return "Session Not Found"; 54 | case 455: return "Method Not Valid In This State"; 55 | case 456: return "Header Field Not Valid"; 56 | case 457: return "Invalid Range"; 57 | case 458: return "Parameter Is Read-Only"; 58 | case 459: return "Aggregate Operation Not Allowed"; 59 | case 460: return "Only Aggregate Operation Allowed"; 60 | case 461: return "Unsupported Transport"; 61 | case 462: return "Destination Unreachable"; 62 | 63 | case 500: return "Internal Server Error"; 64 | case 501: return "Not Implemented"; 65 | case 502: return "Bad Gateway"; 66 | case 503: return "Service Unavailable"; 67 | case 504: return "Gateway Timeout"; 68 | case 505: return "RTSP Version Not Supported"; 69 | case 551: return "Option not support"; 70 | default: 71 | return "Return: " + aErrorCode.ToString(CultureInfo.InvariantCulture); 72 | } 73 | } 74 | 75 | /// 76 | /// Initializes a new instance of the class. 77 | /// 78 | public RtspResponse() 79 | : base() 80 | { 81 | // Initialise with a default result code. 82 | Command = "RTSP/1.0 200 OK"; 83 | } 84 | 85 | private int _returnCode; 86 | /// 87 | /// Gets or sets the return code of the response. 88 | /// 89 | /// The return code. 90 | /// On change the error message is set to the default one associate with the code 91 | public int ReturnCode 92 | { 93 | get 94 | { 95 | if (_returnCode == 0 && commandArray.Length >= 2) 96 | { 97 | int.TryParse(commandArray[1], out _returnCode); 98 | } 99 | 100 | return _returnCode; 101 | } 102 | set 103 | { 104 | if (ReturnCode != value) 105 | { 106 | _returnCode = value; 107 | // make sure we have the room 108 | if (commandArray.Length < 3) 109 | { 110 | Array.Resize(ref commandArray, 3); 111 | } 112 | commandArray[1] = value.ToString(CultureInfo.InvariantCulture); 113 | commandArray[2] = GetDefaultError(value); 114 | } 115 | } 116 | } 117 | 118 | /// 119 | /// Gets or sets the error/return message. 120 | /// 121 | /// The return message. 122 | public string ReturnMessage 123 | { 124 | get 125 | { 126 | if (commandArray.Length < 3) 127 | return String.Empty; 128 | return commandArray[2]; 129 | } 130 | set 131 | { 132 | // Make sure we have the room 133 | if (commandArray.Length < 3) 134 | { 135 | Array.Resize(ref commandArray, 3); 136 | } 137 | commandArray[2] = value; 138 | 139 | } 140 | } 141 | 142 | /// 143 | /// Gets a value indicating whether this instance correspond to an OK response. 144 | /// 145 | /// true if this instance is OK; otherwise, false. 146 | public bool IsOk 147 | { 148 | get 149 | { 150 | if (ReturnCode > 0 && ReturnCode < 400) 151 | return true; 152 | return false; 153 | } 154 | } 155 | 156 | /// 157 | /// Gets the timeout in second. 158 | /// The default timeout is 60. 159 | /// 160 | /// The timeout. 161 | public int Timeout 162 | { 163 | get 164 | { 165 | int returnValue = DEFAULT_TIMEOUT; 166 | if (Headers.ContainsKey(RtspHeaderNames.Session)) 167 | { 168 | string[] parts = Headers[RtspHeaderNames.Session].Split(';'); 169 | if (parts.Length > 1) 170 | { 171 | string[] subParts = parts[1].Split('='); 172 | if (subParts.Length > 1 && 173 | subParts[0].ToUpperInvariant() == "TIMEOUT") 174 | if (!int.TryParse(subParts[1], out returnValue)) 175 | returnValue = DEFAULT_TIMEOUT; 176 | } 177 | } 178 | return returnValue; 179 | } 180 | set 181 | { 182 | if(Headers.ContainsKey(RtspHeaderNames.Session)) 183 | if (value != DEFAULT_TIMEOUT) 184 | { 185 | 186 | Headers[RtspHeaderNames.Session] = Headers[RtspHeaderNames.Session].Split(';').First() 187 | + ";timeout=" + value.ToString(CultureInfo.InvariantCulture); 188 | } 189 | else 190 | { 191 | //remove timeout part 192 | Headers[RtspHeaderNames.Session] = Headers[RtspHeaderNames.Session].Split(';').First(); 193 | } 194 | } 195 | } 196 | 197 | /// 198 | /// Gets the session ID. 199 | /// 200 | /// The session ID. 201 | public override string Session 202 | { 203 | get 204 | { 205 | if (!Headers.ContainsKey(RtspHeaderNames.Session)) 206 | return null; 207 | 208 | return Headers[RtspHeaderNames.Session].Split(';')[0]; 209 | } 210 | set 211 | { 212 | if(Timeout != DEFAULT_TIMEOUT) 213 | { 214 | Headers[RtspHeaderNames.Session] = value + ";timeout=" + Timeout.ToString(CultureInfo.InvariantCulture); 215 | } 216 | else 217 | { 218 | Headers[RtspHeaderNames.Session] = value; 219 | } 220 | } 221 | } 222 | 223 | /// 224 | /// Gets or sets the original request associate with the response. 225 | /// 226 | /// The original request. 227 | public RtspRequest OriginalRequest 228 | { get; set; } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/H264Payload.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using UnityEngine; 5 | 6 | namespace Unity.Ig.VideoStreaming.Server 7 | { 8 | // This class handles the H264 Payload 9 | // It has methods to parse parameters in the SDP 10 | // It has methods to process the RTP Payload 11 | 12 | public class H264Payload 13 | { 14 | private static ILogger _logger = new Logger(Debug.unityLogger.logHandler); 15 | static H264Payload() 16 | { 17 | _logger.logEnabled = false; 18 | } 19 | 20 | int norm, fu_a, fu_b, stap_a, stap_b, mtap16, mtap24 = 0; // used for diagnostics stats 21 | 22 | List temporary_rtp_payloads = new List(); // used to assemble the RTP packets that form one RTP Frame 23 | // Eg all the RTP Packets from M=0 through to M=1 24 | 25 | MemoryStream fragmented_nal = new MemoryStream(); // used to concatenate fragmented H264 NALs where NALs are split over RTP packets 26 | 27 | 28 | // Constructor 29 | public H264Payload() 30 | { 31 | } 32 | 33 | public List Process_H264_RTP_Packet(byte[] rtp_payload, int rtp_marker) { 34 | 35 | // Add to the list of payloads for the current Frame of video 36 | temporary_rtp_payloads.Add(rtp_payload); // Todo Could optimise this and go direct to Process Frame if just 1 packet in frame 37 | 38 | if (rtp_marker == 1) 39 | { 40 | // End Marker is set. Process the list of RTP Packets (forming 1 RTP frame) and save the NALs to a file 41 | List nal_units = Process_H264_RTP_Frame(temporary_rtp_payloads); 42 | temporary_rtp_payloads.Clear(); 43 | 44 | return nal_units; 45 | } 46 | 47 | return null; // we don't have a frame yet. Keep accumulating RTP packets 48 | } 49 | 50 | 51 | // Process a RTP Frame. A RTP Frame can consist of several RTP Packets which have the same Timestamp 52 | // Returns a list of NAL Units (with no 00 00 00 01 header and with no Size header) 53 | private List Process_H264_RTP_Frame(List rtp_payloads) 54 | { 55 | _logger.Log("RTP Data comprised of " + rtp_payloads.Count + " rtp packets"); 56 | 57 | List nal_units = new List(); // Stores the NAL units for a Video Frame. May be more than one NAL unit in a video frame. 58 | 59 | for (int payload_index = 0; payload_index < rtp_payloads.Count; payload_index++) 60 | { 61 | // Examine the first rtp_payload and the first byte (the NAL header) 62 | int nal_header_f_bit = (rtp_payloads[payload_index][0] >> 7) & 0x01; 63 | int nal_header_nri = (rtp_payloads[payload_index][0] >> 5) & 0x03; 64 | int nal_header_type = (rtp_payloads[payload_index][0] >> 0) & 0x1F; 65 | 66 | // If the Nal Header Type is in the range 1..23 this is a normal NAL (not fragmented) 67 | // So write the NAL to the file 68 | if (nal_header_type >= 1 && nal_header_type <= 23) 69 | { 70 | _logger.Log("Normal NAL"); 71 | norm++; 72 | nal_units.Add(rtp_payloads[payload_index]); 73 | } 74 | // There are 4 types of Aggregation Packet (split over RTP payloads) 75 | else if (nal_header_type == 24) 76 | { 77 | _logger.Log("Agg STAP-A"); 78 | stap_a++; 79 | 80 | // RTP packet contains multiple NALs, each with a 16 bit header 81 | // Read 16 byte size 82 | // Read NAL 83 | try 84 | { 85 | int ptr = 1; // start after the nal_header_type which was '24' 86 | // if we have at least 2 more bytes (the 16 bit size) then consume more data 87 | while (ptr + 2 < (rtp_payloads[payload_index].Length - 1)) 88 | { 89 | int size = (rtp_payloads[payload_index][ptr] << 8) + (rtp_payloads[payload_index][ptr + 1] << 0); 90 | ptr = ptr + 2; 91 | byte[] nal = new byte[size]; 92 | System.Array.Copy(rtp_payloads[payload_index], ptr, nal, 0, size); // copy the NAL 93 | nal_units.Add(nal); // Add to list of NALs for this RTP frame. Start Codes like 00 00 00 01 get added later 94 | ptr = ptr + size; 95 | } 96 | } 97 | catch 98 | { 99 | _logger.Log("H264 Aggregate Packet processing error"); 100 | } 101 | } 102 | else if (nal_header_type == 25) 103 | { 104 | _logger.Log("Agg STAP-B not supported"); 105 | stap_b++; 106 | } 107 | else if (nal_header_type == 26) 108 | { 109 | _logger.Log("Agg MTAP16 not supported"); 110 | mtap16++; 111 | } 112 | else if (nal_header_type == 27) 113 | { 114 | _logger.Log("Agg MTAP24 not supported"); 115 | mtap24++; 116 | } 117 | else if (nal_header_type == 28) 118 | { 119 | _logger.Log("Frag FU-A"); 120 | fu_a++; 121 | 122 | // Parse Fragmentation Unit Header 123 | int fu_header_s = (rtp_payloads[payload_index][1] >> 7) & 0x01; // start marker 124 | int fu_header_e = (rtp_payloads[payload_index][1] >> 6) & 0x01; // end marker 125 | int fu_header_r = (rtp_payloads[payload_index][1] >> 5) & 0x01; // reserved. should be 0 126 | int fu_header_type = (rtp_payloads[payload_index][1] >> 0) & 0x1F; // Original NAL unit header 127 | 128 | _logger.Log("Frag FU-A s=" + fu_header_s + "e=" + fu_header_e); 129 | 130 | // Check Start and End flags 131 | if (fu_header_s == 1 && fu_header_e == 0) 132 | { 133 | // Start of Fragment. 134 | // Initiise the fragmented_nal byte array 135 | // Build the NAL header with the original F and NRI flags but use the the Type field from the fu_header_type 136 | byte reconstructed_nal_type = (byte)((nal_header_f_bit << 7) + (nal_header_nri << 5) + fu_header_type); 137 | 138 | // Empty the stream 139 | fragmented_nal.SetLength(0); 140 | 141 | // Add reconstructed_nal_type byte to the memory stream 142 | fragmented_nal.WriteByte(reconstructed_nal_type); 143 | 144 | // copy the rest of the RTP payload to the memory stream 145 | fragmented_nal.Write(rtp_payloads[payload_index], 2, rtp_payloads[payload_index].Length - 2); 146 | } 147 | 148 | if (fu_header_s == 0 && fu_header_e == 0) 149 | { 150 | // Middle part of Fragment 151 | // Append this payload to the fragmented_nal 152 | // Data starts after the NAL Unit Type byte and the FU Header byte 153 | fragmented_nal.Write(rtp_payloads[payload_index], 2, rtp_payloads[payload_index].Length - 2); 154 | } 155 | 156 | if (fu_header_s == 0 && fu_header_e == 1) 157 | { 158 | // End part of Fragment 159 | // Append this payload to the fragmented_nal 160 | // Data starts after the NAL Unit Type byte and the FU Header byte 161 | fragmented_nal.Write(rtp_payloads[payload_index], 2, rtp_payloads[payload_index].Length - 2); 162 | 163 | // Add the NAL to the array of NAL units 164 | nal_units.Add(fragmented_nal.ToArray()); 165 | } 166 | } 167 | 168 | else if (nal_header_type == 29) 169 | { 170 | _logger.Log("Frag FU-B not supported"); 171 | fu_b++; 172 | } 173 | else 174 | { 175 | _logger.Log("Unknown NAL header " + nal_header_type + " not supported"); 176 | } 177 | 178 | } 179 | 180 | // Output some statistics 181 | _logger.Log("Norm=" + norm + " ST-A=" + stap_a + " ST-B=" + stap_b + " M16=" + mtap16 + " M24=" + mtap24 + " FU-A=" + fu_a + " FU-B=" + fu_b); 182 | 183 | // Output all the NALs that form one RTP Frame (one frame of video) 184 | return nal_units; 185 | 186 | } 187 | 188 | 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/VideoStreamingServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using Unity.Collections; 7 | using Unity.Jobs; 8 | using UnityEngine; 9 | using UnityEngine.Profiling; 10 | using UnityEngine.Rendering; 11 | using Unity.Ig.VideoStreaming.Server; 12 | 13 | namespace Unity.Ig.VideoStreaming.Server 14 | { 15 | public class VideoStreamingServer : MonoBehaviour 16 | { 17 | [SerializeField] 18 | int m_Port = 8554; 19 | public int Port { set { m_Port = value; } } 20 | 21 | [SerializeField] 22 | string m_Username = "user"; // or use NUL if there is no username 23 | [SerializeField] 24 | string m_Password = "password"; // or use NUL if there is no password 25 | 26 | public void SetCredentials(string username, string password) 27 | { 28 | m_Username = username; 29 | m_Password = password; 30 | } 31 | 32 | [SerializeField] 33 | RenderTexture m_SourceTexture; 34 | public RenderTexture SourceTexture { set { m_SourceTexture = value; } } 35 | 36 | [SerializeField] 37 | int m_Framerate = 30; 38 | public int Framerate { set { m_Framerate = value; } } 39 | 40 | RtspServer m_Server; 41 | H264Encoder m_Encoder; 42 | const float m_StartTime = 3.0F; 43 | int m_LastFrameIndex = -1; 44 | bool m_RtspServerActive = false; 45 | RenderTexture m_NV12Texture; 46 | Material m_RGBToNV12Material; 47 | 48 | struct H264EncoderForStartup : IH264Encoder 49 | { 50 | public byte[] spsNalu; 51 | public byte[] ppsNalu; 52 | 53 | public void CompressFrame(NativeArray data, ref ArraySegment compressedData, out bool isKeyFrame) 54 | { 55 | throw new NotImplementedException(); 56 | } 57 | 58 | public bool ExpectsYUV() 59 | { 60 | return false; 61 | } 62 | 63 | public byte[] GetRawPPS() 64 | { 65 | return ppsNalu; 66 | } 67 | 68 | public byte[] GetRawSPS() 69 | { 70 | return spsNalu; 71 | } 72 | } 73 | 74 | // Start is called before the first frame update 75 | void Start() 76 | { 77 | if (m_SourceTexture == null) 78 | return; 79 | 80 | try 81 | { 82 | m_Server = new RtspServer(m_Port, m_Username, m_Password); 83 | m_Encoder = new H264Encoder(); 84 | NativeList spsNalu = new NativeList(Allocator.TempJob); 85 | NativeList ppsNalu = new NativeList(Allocator.TempJob); 86 | var startJob = m_Encoder.Start((uint)m_SourceTexture.width, (uint)m_SourceTexture.height, 87 | (uint)m_Framerate, ref spsNalu, ref ppsNalu); 88 | m_JobQueue.Enqueue(new JobItem { job = startJob, spsNalu = spsNalu, ppsNalu = ppsNalu }); 89 | 90 | if (String.IsNullOrEmpty(m_Username) != String.IsNullOrEmpty(m_Password)) 91 | Debug.LogError("Both username and password should be specified, not just one of them."); 92 | else if (!String.IsNullOrEmpty(m_Username) && !String.IsNullOrEmpty(m_Password)) 93 | Debug.Log($"Watch the output on rtsp://{m_Username}:{m_Password}@{GetMyIPAddress()}:{m_Port}"); 94 | else 95 | Debug.Log($"Watch the output on rtsp://{GetMyIPAddress()}:{m_Port}"); 96 | 97 | var shaderName = "Video/RGBToNV12"; 98 | var rgbToNV12Shader = Shader.Find(shaderName); 99 | if (rgbToNV12Shader == null) 100 | Debug.LogError("Could not find shader " + shaderName); 101 | m_RGBToNV12Material = new Material(rgbToNV12Shader); 102 | m_NV12Texture = new RenderTexture(m_SourceTexture.width, m_SourceTexture.height * 3 / 2, 0, RenderTextureFormat.R8, RenderTextureReadWrite.Linear); 103 | m_NV12Texture.Create(); 104 | } 105 | catch (Exception e) 106 | { 107 | Debug.LogError("Error: Could not start server: " + e); 108 | } 109 | } 110 | 111 | void OnDestroy() 112 | { 113 | m_Server?.StopListen(); 114 | m_Server?.Dispose(); 115 | while (m_JobQueue.Count > 0) 116 | CompleteJobs(); 117 | m_Server = null; 118 | m_Encoder.Dispose(); 119 | if (m_NV12Texture != null) 120 | { 121 | Destroy(m_NV12Texture); 122 | m_NV12Texture = null; 123 | } 124 | 125 | if (m_RGBToNV12Material != null) 126 | { 127 | Destroy(m_RGBToNV12Material); 128 | m_RGBToNV12Material = null; 129 | } 130 | } 131 | 132 | struct JobItem 133 | { 134 | public AsyncGPUReadbackRequest req; 135 | public NativeList spsNalu; 136 | public NativeList ppsNalu; 137 | public NativeList imageNalu; 138 | public JobHandle job; 139 | public ulong timeStampNs; 140 | 141 | public bool CompleteIfPossible() 142 | { 143 | if (!job.IsCompleted) 144 | return false; 145 | job.Complete(); 146 | return true; 147 | } 148 | 149 | public void DisposeLists() 150 | { 151 | if (spsNalu.IsCreated) 152 | spsNalu.Dispose(); 153 | if (ppsNalu.IsCreated) 154 | ppsNalu.Dispose(); 155 | if (imageNalu.IsCreated) 156 | imageNalu.Dispose(); 157 | } 158 | }; 159 | 160 | Queue m_JobQueue = new Queue(); 161 | 162 | void Update() 163 | { 164 | CompleteJobs(); 165 | if (m_Framerate == 0 || m_SourceTexture == null) 166 | return; 167 | 168 | var curTime = Time.timeSinceLevelLoad; 169 | if (curTime < m_StartTime) 170 | return; 171 | 172 | var elapsedTime = curTime - m_StartTime; 173 | var frameIndex = (int)(elapsedTime * m_Framerate); 174 | if (frameIndex == m_LastFrameIndex) 175 | return; 176 | 177 | m_RtspServerActive = m_Server.RefreshConnectionList(); 178 | if (!m_RtspServerActive) 179 | return; 180 | 181 | ulong timeStampNs = (ulong)(elapsedTime * 1000000000); 182 | m_LastFrameIndex = frameIndex; 183 | var readbackStart = Time.realtimeSinceStartup; 184 | 185 | Graphics.Blit(m_SourceTexture, m_NV12Texture, m_RGBToNV12Material); 186 | 187 | Profiler.BeginSample("Schedule AsyncGPUReadback"); 188 | AsyncGPUReadback.Request(m_NV12Texture, 0, (request) => 189 | { 190 | //Debug.Log($"Readback took {Time.realtimeSinceStartup - readbackStart} s"); 191 | EncodeImage(request, timeStampNs); 192 | }); 193 | Profiler.EndSample(); 194 | } 195 | 196 | void EncodeImage(AsyncGPUReadbackRequest req, ulong timeStampNs) 197 | { 198 | if (m_Server == null) 199 | return; 200 | var spsNalu = new NativeList(Allocator.TempJob); 201 | var ppsNalu = new NativeList(Allocator.TempJob); 202 | var imageNalu = new NativeList(Allocator.TempJob); 203 | JobHandle prevJob = m_JobQueue.Count > 0 ? m_JobQueue.Peek().job : default(JobHandle); 204 | var encodeJob = m_Encoder.Encode(prevJob, req.GetData(), timeStampNs, ref spsNalu, ref ppsNalu, ref imageNalu); 205 | 206 | //Debug.Log("Encode image at t=" + timeStampNs); 207 | if (encodeJob.Equals(default(JobHandle))) 208 | { 209 | spsNalu.Dispose(); 210 | ppsNalu.Dispose(); 211 | imageNalu.Dispose(); 212 | return; 213 | } 214 | 215 | m_JobQueue.Enqueue(new JobItem { job = encodeJob, req = req, spsNalu = spsNalu, ppsNalu = ppsNalu, imageNalu = imageNalu, timeStampNs = timeStampNs }); 216 | } 217 | 218 | private void CompleteJobs() 219 | { 220 | while (m_JobQueue.Count > 0) 221 | { 222 | if (!m_JobQueue.Peek().CompleteIfPossible()) 223 | break; 224 | var jobItem = m_JobQueue.Dequeue(); 225 | if (!jobItem.imageNalu.IsCreated) 226 | 227 | // Nalu without image denotes the initial job, which just returns the sps/pps. Getting this info makes us ready to open 228 | // the RTSP TCP end point. 229 | m_Server.StartListen(new H264EncoderForStartup() 230 | { 231 | spsNalu = jobItem.spsNalu.ToArray(), 232 | ppsNalu = jobItem.ppsNalu.ToArray() 233 | }); 234 | else 235 | { 236 | //Debug.LogFormat("Sending image NALU with {0} bytes", jobItem.imageNalu.Length); 237 | m_Server.SendNALUs(jobItem.timeStampNs, in jobItem.spsNalu, in jobItem.ppsNalu, in jobItem.imageNalu); 238 | } 239 | 240 | jobItem.DisposeLists(); 241 | } 242 | } 243 | 244 | string GetMyIPAddress() 245 | { 246 | var host = Dns.GetHostEntry(Dns.GetHostName()); 247 | var ipAddress = host.AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork); 248 | return ipAddress.ToString(); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/H265Payload.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace Unity.Ig.VideoStreaming.Server 6 | { 7 | // This class handles the H265 Payload 8 | // It has methods to parse parameters in the SDP 9 | // It has methods to process the RTP Payload 10 | 11 | // By Roger Hardiman, RJH Technical Consultancy Ltd 12 | 13 | public class H265Payload 14 | { 15 | // H265 / HEVC structure. 16 | // An 'Access Unit' is the set of NAL Units that form one Picture 17 | // NAL Units have a 2 byte header comprising of 18 | // F Bit, Type, Layer ID and TID 19 | 20 | 21 | int single, agg, frag = 0; // used for diagnostics stats 22 | bool has_donl = false; 23 | 24 | List temporary_rtp_payloads = new List(); // used to assemble the RTP packets that form one RTP Frame 25 | // Eg all the RTP Packets from M=0 through to M=1 26 | 27 | MemoryStream fragmented_nal = new MemoryStream(); // used to concatenate fragmented H264 NALs where NALs are split over RTP packets 28 | 29 | 30 | // Constructor 31 | public H265Payload(bool has_donl) 32 | { 33 | this.has_donl = has_donl; 34 | } 35 | 36 | public List Process_H265_RTP_Packet(byte[] rtp_payload, int rtp_marker) { 37 | 38 | // Add payload to the List of payloads for the current Frame of Video 39 | // ie all the payloads with M=0 up to the final payload where M=1 40 | temporary_rtp_payloads.Add(rtp_payload); // Todo Could optimise this and go direct to Process Frame if just 1 packet in frame 41 | 42 | if (rtp_marker == 1) 43 | { 44 | // End Marker is set. Process the list of RTP Packets (forming 1 RTP frame) and save the NALs to a file 45 | List nal_units = Process_H265_RTP_Frame(temporary_rtp_payloads); 46 | temporary_rtp_payloads.Clear(); 47 | 48 | return nal_units; 49 | } 50 | 51 | return null; // we don't have a frame yet. Keep accumulating RTP packets 52 | } 53 | 54 | 55 | // Process a RTP Frame. A RTP Frame can consist of several RTP Packets which have the same Timestamp 56 | // Returns a list of NAL Units (with no 00 00 00 01 header and with no Size header) 57 | private List Process_H265_RTP_Frame(List rtp_payloads) 58 | { 59 | Console.WriteLine("RTP Data comprised of " + rtp_payloads.Count + " rtp packets"); 60 | 61 | List nal_units = new List(); // Stores the NAL units for a Video Frame. May be more than one NAL unit in a video frame. 62 | 63 | for (int payload_index = 0; payload_index < rtp_payloads.Count; payload_index++) 64 | { 65 | // Examine the first two bytes of the RTP data, the Payload Header 66 | // F (Forbidden Bit), 67 | // Type of NAL Unit (or VCL NAL Unit if Type is < 32), 68 | // LayerId 69 | // TID (TemporalID = TID - 1) 70 | /*+---------------+---------------+ 71 | *|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| 72 | *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 73 | *|F| Type | LayerId | TID | 74 | *+-------------+-----------------+ 75 | */ 76 | 77 | int payload_header = (rtp_payloads[payload_index][0] << 8) | (rtp_payloads[payload_index][1]); 78 | int payload_header_f_bit = (payload_header >> 15) & 0x01; 79 | int payload_header_type = (payload_header >> 9) & 0x3F; 80 | int payload_header_layer_id = (payload_header >> 3) & 0x3F; 81 | int payload_header_tid = payload_header & 0x7; 82 | 83 | 84 | // There are three ways to Packetize NAL units into RTP Packets 85 | // Single NAL Unit Packet 86 | // Aggregation Packet (payload_header_type = 48) 87 | // Fragmentation Unit (payload_header_type = 49) 88 | 89 | 90 | // Single NAL Unit Packet 91 | // 32=VPS 92 | // 33=SPS 93 | // 34=PPS 94 | if (payload_header_type != 48 && payload_header_type != 49) 95 | { 96 | Console.WriteLine("Single NAL"); 97 | single++; 98 | 99 | //TODO - Handle DONL 100 | 101 | nal_units.Add(rtp_payloads[payload_index]); 102 | } 103 | 104 | // Aggregation Packet 105 | else if (payload_header_type == 48) 106 | { 107 | Console.WriteLine("Aggregation Packet"); 108 | agg++; 109 | 110 | // RTP packet contains multiple NALs, each with a 16 bit header 111 | // Read 16 byte size 112 | // Read NAL 113 | // Use a Try/Catch to protect from bad RTP data where block sizes exceed the 114 | // available data 115 | try 116 | { 117 | int ptr = 2; // start after 16 bit Payload Header 118 | 119 | // loop until the ptr has moved beyond the length of the data 120 | while (ptr < (rtp_payloads[payload_index].Length - 1)) 121 | { 122 | if (has_donl) ptr = ptr + 2; // step over the DONL data 123 | int size = (rtp_payloads[payload_index][ptr] << 8) + (rtp_payloads[payload_index][ptr + 1] << 0); 124 | ptr = ptr + 2; 125 | byte[] nal = new byte[size]; 126 | System.Array.Copy(rtp_payloads[payload_index], ptr, nal, 0, size); // copy the NAL 127 | nal_units.Add(nal); // Add to list of NALs for this RTP frame. Start Codes like 00 00 00 01 get added later 128 | ptr = ptr + size; 129 | } 130 | } 131 | catch 132 | { 133 | Console.WriteLine("H265 Aggregate Packet processing error"); 134 | } 135 | } 136 | 137 | // Fragmentation Unit 138 | else if (payload_header_type == 49) 139 | { 140 | Console.WriteLine("Fragmentation Unit"); 141 | frag++; 142 | 143 | // Parse Fragmentation Unit Header 144 | int fu_header_s = (rtp_payloads[payload_index][2] >> 7) & 0x01; // start marker 145 | int fu_header_e = (rtp_payloads[payload_index][2] >> 6) & 0x01; // end marker 146 | int fu_header_type = (rtp_payloads[payload_index][2] >> 0) & 0x3F; // fu type 147 | 148 | Console.WriteLine("Frag FU-A s=" + fu_header_s + "e=" + fu_header_e); 149 | 150 | // Check Start and End flags 151 | if (fu_header_s == 1 && fu_header_e == 0) 152 | { 153 | // Start of Fragment. 154 | // Initiise the fragmented_nal byte array 155 | 156 | // Empty the stream 157 | fragmented_nal.SetLength(0); 158 | 159 | // Reconstrut the NAL header from the rtp_payload_header, replacing the Type with FU Type 160 | int nal_header = (payload_header & 0x81FF); // strip out existing 'type' 161 | nal_header = nal_header | (fu_header_type << 9); 162 | 163 | fragmented_nal.WriteByte((byte)((nal_header >> 8) & 0xFF)); 164 | fragmented_nal.WriteByte((byte)((nal_header >> 0) & 0xFF)); 165 | 166 | if (has_donl) 167 | { 168 | // start copying after the DONL data 169 | fragmented_nal.Write(rtp_payloads[payload_index], 5, rtp_payloads[payload_index].Length - 5); 170 | } 171 | else 172 | { 173 | // there is no DONL data 174 | fragmented_nal.Write(rtp_payloads[payload_index], 3, rtp_payloads[payload_index].Length - 3); 175 | } 176 | } 177 | 178 | if (fu_header_s == 0 && fu_header_e == 0) 179 | { 180 | // Middle part of Fragment 181 | // Append this payload to the fragmented_nal 182 | 183 | if (has_donl) { 184 | // start copying after the DONL data 185 | fragmented_nal.Write(rtp_payloads[payload_index], 5, rtp_payloads[payload_index].Length - 5); 186 | } else { 187 | // there is no DONL data 188 | fragmented_nal.Write(rtp_payloads[payload_index], 3, rtp_payloads[payload_index].Length - 3); 189 | } 190 | } 191 | 192 | if (fu_header_s == 0 && fu_header_e == 1) 193 | { 194 | // End part of Fragment 195 | // Append this payload to the fragmented_nal 196 | if (has_donl) 197 | { 198 | // start copying after the DONL data 199 | fragmented_nal.Write(rtp_payloads[payload_index], 5, rtp_payloads[payload_index].Length - 5); 200 | } 201 | else 202 | { 203 | // there is no DONL data 204 | fragmented_nal.Write(rtp_payloads[payload_index], 3, rtp_payloads[payload_index].Length - 3); 205 | } 206 | 207 | // Add the NAL to the array of NAL units 208 | nal_units.Add(fragmented_nal.ToArray()); 209 | } 210 | } 211 | else { 212 | Console.WriteLine("Unknown Payload Header Type = " + payload_header_type); 213 | } 214 | } 215 | 216 | // Output some statistics 217 | Console.WriteLine("Single=" + single + " Agg=" + agg + " Frag=" + frag); 218 | 219 | // Output all the NALs that form one RTP Frame (one frame of video) 220 | return nal_units; 221 | 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPMessage.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Unity.Ig.VideoStreaming.Server.Messages 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics.Contracts; 8 | using System.Globalization; 9 | using System.IO; 10 | using System.Text; 11 | using System.Text.RegularExpressions; 12 | 13 | public class RtspMessage : RtspChunk 14 | { 15 | private static ILogger _logger = new Logger(Debug.unityLogger.logHandler); 16 | 17 | static RtspMessage() 18 | { 19 | _logger.logEnabled = false; 20 | } 21 | 22 | /// 23 | /// The regex to validate the Rtsp message. 24 | /// 25 | private static readonly Regex _rtspVersionTest = new Regex(@"^RTSP/\d\.\d", RegexOptions.Compiled); 26 | /// 27 | /// Create the good type of Rtsp Message from the header. 28 | /// 29 | /// A request line. 30 | /// An Rtsp message 31 | public static RtspMessage GetRtspMessage(string aRequestLine) 32 | { 33 | // We can't determine the message 34 | if (string.IsNullOrEmpty(aRequestLine)) 35 | return new RtspMessage(); 36 | string[] requestParts = aRequestLine.Split(new char[] { ' ' }, 3); 37 | RtspMessage returnValue; 38 | if (requestParts.Length == 3) 39 | { 40 | // A request is : Method SP Request-URI SP RTSP-Version 41 | // A response is : RTSP-Version SP Status-Code SP Reason-Phrase 42 | // RTSP-Version = "RTSP" "/" 1*DIGIT "." 1*DIGIT 43 | if (_rtspVersionTest.IsMatch(requestParts[2])) 44 | returnValue = RtspRequest.GetRtspRequest(requestParts); 45 | else if (_rtspVersionTest.IsMatch(requestParts[0])) 46 | returnValue = new RtspResponse(); 47 | else 48 | { 49 | _logger.LogFormat(LogType.Warning, "Got a strange message {0}", aRequestLine); 50 | returnValue = new RtspMessage(); 51 | } 52 | } 53 | else 54 | { 55 | _logger.LogFormat(LogType.Warning, "Got a strange message {0}", aRequestLine); 56 | returnValue = new RtspMessage(); 57 | } 58 | returnValue.Command = aRequestLine; 59 | return returnValue; 60 | } 61 | 62 | /// 63 | /// Initializes a new instance of the class. 64 | /// 65 | public RtspMessage() 66 | { 67 | Data = new byte[0]; 68 | Creation = DateTime.Now; 69 | } 70 | 71 | private Dictionary _headers = new Dictionary(StringComparer.OrdinalIgnoreCase); 72 | 73 | internal protected string[] commandArray; 74 | 75 | /// 76 | /// Gets or sets the creation time. 77 | /// 78 | /// The creation time. 79 | public DateTime Creation { get; private set; } 80 | 81 | /// 82 | /// Gets or sets the command of the message (first line). 83 | /// 84 | /// The command. 85 | public string Command 86 | { 87 | get 88 | { 89 | if (commandArray == null) 90 | return string.Empty; 91 | return string.Join(" ", commandArray); 92 | } 93 | set 94 | { 95 | if (value == null) 96 | commandArray = new string[] { String.Empty }; 97 | else 98 | commandArray = value.Split(new char[] {' '}, 3); 99 | } 100 | } 101 | 102 | 103 | /// 104 | /// Gets the Method of the message (eg OPTIONS, DESCRIBE, SETUP, PLAY). 105 | /// 106 | /// The Method 107 | public string Method 108 | { 109 | get 110 | { 111 | if (commandArray == null) 112 | return string.Empty; 113 | return commandArray[0]; 114 | } 115 | } 116 | 117 | 118 | /// 119 | /// Gets the headers of the message. 120 | /// 121 | /// The headers. 122 | public Dictionary Headers 123 | { 124 | get 125 | { 126 | return _headers; 127 | } 128 | } 129 | 130 | /// 131 | /// Adds one header from a string. 132 | /// 133 | /// The string containing header of format Header: Value. 134 | /// is null 135 | public void AddHeader(string line) 136 | { 137 | if (line == (string)null) 138 | throw new ArgumentNullException("line"); 139 | 140 | //spliter 141 | string[] elements = line.Split(new char[] { ':' }, 2); 142 | if (elements.Length == 2) 143 | { 144 | _headers[elements[0].Trim()] = elements[1].TrimStart(); 145 | } 146 | else 147 | { 148 | _logger.LogFormat(LogType.Warning, "Invalid Header received : -{0}-", line); 149 | } 150 | } 151 | 152 | /// 153 | /// Gets or sets the Ccommande Seqquence number. 154 | /// If the header is not define or not a valid number it return 0 155 | /// 156 | /// The sequence number. 157 | public int CSeq 158 | { 159 | get 160 | { 161 | string returnStringValue; 162 | int returnValue; 163 | if (!(_headers.TryGetValue("CSeq", out returnStringValue) && 164 | int.TryParse(returnStringValue, out returnValue))) 165 | returnValue = 0; 166 | 167 | return returnValue; 168 | } 169 | set 170 | { 171 | _headers["CSeq"] = value.ToString(CultureInfo.InvariantCulture); 172 | } 173 | } 174 | 175 | /// 176 | /// Gets the session ID. 177 | /// 178 | /// The session ID. 179 | public virtual string Session 180 | { 181 | get 182 | { 183 | if (!_headers.ContainsKey("Session")) 184 | return null; 185 | 186 | return _headers["Session"]; 187 | } 188 | set 189 | { 190 | _headers["Session"] = value; 191 | } 192 | } 193 | 194 | /// 195 | /// Initialises the length of the data byte array from content lenth header. 196 | /// 197 | public void InitialiseDataFromContentLength() 198 | { 199 | int dataLength; 200 | if (!(_headers.ContainsKey("Content-Length") 201 | && int.TryParse(_headers["Content-Length"], out dataLength))) 202 | { 203 | dataLength = 0; 204 | } 205 | this.Data = new byte[dataLength]; 206 | } 207 | 208 | /// 209 | /// Adjusts the content length header. 210 | /// 211 | public void AdjustContentLength() 212 | { 213 | if (Data.Length > 0) 214 | { 215 | _headers["Content-Length"] = Data.Length.ToString(CultureInfo.InvariantCulture); 216 | } 217 | else 218 | { 219 | _headers.Remove("Content-Length"); 220 | } 221 | } 222 | 223 | /// 224 | /// Sends to the message to a stream. 225 | /// 226 | /// The stream. 227 | /// is empty 228 | /// can't be written. 229 | public void SendTo(Stream stream) 230 | { 231 | // 232 | if (stream == null) 233 | throw new ArgumentNullException("stream"); 234 | if (!stream.CanWrite) 235 | throw 236 | new ArgumentException("Stream CanWrite == false, can't send message to it", "stream"); 237 | // 238 | Contract.EndContractBlock(); 239 | 240 | Encoding encoder = ASCIIEncoding.UTF8; 241 | StringBuilder outputString = new StringBuilder(); 242 | 243 | AdjustContentLength(); 244 | 245 | // output header 246 | outputString.Append(Command); 247 | outputString.Append("\r\n"); 248 | foreach (KeyValuePair item in _headers) 249 | { 250 | outputString.AppendFormat("{0}: {1}\r\n", item.Key, item.Value); 251 | } 252 | outputString.Append("\r\n"); 253 | byte[] buffer = encoder.GetBytes(outputString.ToString()); 254 | lock(stream) { 255 | stream.Write(buffer, 0, buffer.Length); 256 | 257 | // Output data 258 | if (Data.Length > 0) 259 | stream.Write(Data, 0, Data.Length); 260 | 261 | } 262 | stream.Flush(); 263 | } 264 | 265 | 266 | 267 | /// 268 | /// Logs the message. 269 | /// 270 | /// A log level. 271 | public override void LogMessage(LogType aLevel) 272 | { 273 | // if the level is not logged directly return 274 | if (!_logger.IsLogTypeAllowed(aLevel)) 275 | return; 276 | 277 | _logger.Log(aLevel, "Commande : {0}", Command); 278 | foreach (KeyValuePair item in _headers) 279 | { 280 | _logger.LogFormat(aLevel, "Header : {0}: {1}", item.Key, item.Value); 281 | } 282 | 283 | if (Data.Length > 0) 284 | { 285 | _logger.Log(aLevel, "Data :-{0}-", ASCIIEncoding.ASCII.GetString(Data)); 286 | } 287 | } 288 | 289 | /// 290 | /// Crée un nouvel objet qui est une copie de l'instance en cours. 291 | /// 292 | /// 293 | /// Nouvel objet qui est une copie de cette instance. 294 | /// 295 | public override object Clone() 296 | { 297 | RtspMessage returnValue = GetRtspMessage(this.Command); 298 | 299 | foreach (var item in this.Headers) 300 | { 301 | if (item.Value == null) 302 | returnValue.Headers.Add(item.Key.Clone() as string, null); 303 | else 304 | returnValue.Headers.Add(item.Key.Clone() as string, item.Value.Clone() as string); 305 | } 306 | returnValue.Data = this.Data.Clone() as byte[]; 307 | returnValue.SourcePort = this.SourcePort; 308 | 309 | return returnValue; 310 | } 311 | 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /VideoStreamingServerPackages/Runtime/Messages/RTSPTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Diagnostics.Contracts; 6 | using System.Globalization; 7 | 8 | namespace Unity.Ig.VideoStreaming.Server.Messages 9 | { 10 | public class RtspTransport 11 | { 12 | public RtspTransport() 13 | { 14 | // Default value is true in RFC 15 | IsMulticast = true; 16 | LowerTransport = LowerTransportType.UDP; 17 | Mode = "PLAY"; 18 | } 19 | /* 20 | RFC 21 | Transport = "Transport" ":" 22 | 1\#transport-spec 23 | transport-spec = transport-protocol/profile[/lower-transport] 24 | *parameter 25 | transport-protocol = "RTP" 26 | profile = "AVP" 27 | lower-transport = "TCP" | "UDP" 28 | parameter = ( "unicast" | "multicast" ) 29 | | ";" "destination" [ "=" address ] 30 | | ";" "interleaved" "=" channel [ "-" channel ] 31 | | ";" "append" 32 | | ";" "ttl" "=" ttl 33 | | ";" "layers" "=" 1*DIGIT 34 | | ";" "port" "=" port [ "-" port ] 35 | | ";" "client_port" "=" port [ "-" port ] 36 | | ";" "server_port" "=" port [ "-" port ] 37 | | ";" "ssrc" "=" ssrc 38 | | ";" "mode" = <"> 1\#mode <"> 39 | ttl = 1*3(DIGIT) 40 | port = 1*5(DIGIT) 41 | ssrc = 8*8(HEX) 42 | channel = 1*3(DIGIT) 43 | address = host 44 | mode = <"> *Method <"> | Method 45 | 46 | */ 47 | /// 48 | /// List of transport 49 | /// 50 | [Serializable] 51 | public enum TransportType 52 | { 53 | /// 54 | /// RTP for now 55 | /// 56 | RTP, 57 | } 58 | 59 | /// 60 | /// Profile type 61 | /// 62 | [Serializable] 63 | public enum ProfileType 64 | { 65 | /// 66 | /// RTP/AVP of now 67 | /// 68 | AVP, 69 | } 70 | 71 | /// 72 | /// Transport type. 73 | /// 74 | [Serializable] 75 | public enum LowerTransportType 76 | { 77 | /// 78 | /// UDP transport. 79 | /// 80 | UDP, 81 | /// 82 | /// TCP transport. 83 | /// 84 | TCP, 85 | } 86 | 87 | 88 | /// 89 | /// Gets or sets the transport. 90 | /// 91 | /// The transport. 92 | public TransportType Transport { get; set; } 93 | /// 94 | /// Gets or sets the profile. 95 | /// 96 | /// The profile. 97 | public ProfileType Profile { get; set; } 98 | /// 99 | /// Gets or sets the lower transport. 100 | /// 101 | /// The lower transport. 102 | public LowerTransportType LowerTransport { get; set; } 103 | /// 104 | /// Gets or sets a value indicating whether this instance is multicast. 105 | /// 106 | /// 107 | /// true if this instance is multicast; otherwise, false. 108 | /// 109 | public bool IsMulticast { get; set; } 110 | /// 111 | /// Gets or sets the destination. 112 | /// 113 | /// The destination. 114 | public string Destination { get; set; } 115 | /// 116 | /// Gets or sets the source. 117 | /// 118 | /// The source. 119 | public string Source { get; set; } 120 | /// 121 | /// Gets or sets the interleaved. 122 | /// 123 | /// The interleaved. 124 | public PortCouple Interleaved { get; set; } 125 | /// 126 | /// Gets or sets a value indicating whether this instance is append. 127 | /// 128 | /// true if this instance is append; otherwise, false. 129 | public bool IsAppend { get; set; } 130 | /// 131 | /// Gets or sets the TTL. 132 | /// 133 | /// The TTL. 134 | public int TTL { get; set; } 135 | /// 136 | /// Gets or sets the layers. 137 | /// 138 | /// The layers. 139 | public int Layers { get; set; } 140 | /// 141 | /// Gets or sets the port. 142 | /// 143 | /// The port. 144 | public PortCouple Port { get; set; } 145 | /// 146 | /// Gets or sets the client port. 147 | /// 148 | /// The client port. 149 | public PortCouple ClientPort { get; set; } 150 | /// 151 | /// Gets or sets the server port. 152 | /// 153 | /// The server port. 154 | public PortCouple ServerPort { get; set; } 155 | /// 156 | /// Gets or sets the S SRC. 157 | /// 158 | /// The S SRC. 159 | public string SSrc { get; set; } 160 | /// 161 | /// Gets or sets the mode. 162 | /// 163 | /// The mode. 164 | public string Mode { get; set; } 165 | 166 | /// 167 | /// Parses the specified transport string. 168 | /// 169 | /// A transport string. 170 | /// The transport class. 171 | /// is null. 172 | public static RtspTransport Parse(string aTransportString) 173 | { 174 | if (aTransportString == null) 175 | throw new ArgumentNullException("aTransportString"); 176 | Contract.EndContractBlock(); 177 | 178 | RtspTransport returnValue = new RtspTransport(); 179 | 180 | string[] transportPart = aTransportString.Split(';'); 181 | string[] transportProtocolPart = transportPart[0].Split('/'); 182 | 183 | ReadTransport(returnValue, transportProtocolPart); 184 | ReadProfile(returnValue, transportProtocolPart); 185 | ReadLowerTransport(returnValue, transportProtocolPart); 186 | 187 | foreach (string part in transportPart) 188 | { 189 | string[] subPart = part.Split('='); 190 | 191 | switch (subPart[0].ToUpperInvariant()) 192 | { 193 | case "UNICAST": 194 | returnValue.IsMulticast = false; 195 | break; 196 | case "MULTICAST": 197 | returnValue.IsMulticast = true; 198 | break; 199 | case "DESTINATION": 200 | if (subPart.Length == 2) 201 | returnValue.Destination = subPart[1]; 202 | break; 203 | case "SOURCE": 204 | if (subPart.Length == 2) 205 | returnValue.Source = subPart[1]; 206 | break; 207 | case "INTERLEAVED": 208 | returnValue.IsMulticast = false; 209 | if (subPart.Length < 2) 210 | throw new ArgumentException("interleaved value invalid", "aTransportString"); 211 | 212 | returnValue.Interleaved = PortCouple.Parse(subPart[1]); 213 | break; 214 | case "APPEND": 215 | returnValue.IsAppend = true; 216 | break; 217 | case "TTL": 218 | int ttl = 0; 219 | if (subPart.Length < 2 || !int.TryParse(subPart[1], out ttl)) 220 | throw new ArgumentException("TTL value invalid", "aTransportString"); 221 | returnValue.TTL = ttl; 222 | break; 223 | case "LAYERS": 224 | int layers = 0; 225 | if (subPart.Length < 2 || !int.TryParse(subPart[1], out layers)) 226 | throw new ArgumentException("Layers value invalid", "aTransportString"); 227 | returnValue.TTL = layers; 228 | break; 229 | case "PORT": 230 | if (subPart.Length < 2) 231 | throw new ArgumentException("Port value invalid", "aTransportString"); 232 | returnValue.Port = PortCouple.Parse(subPart[1]); 233 | break; 234 | case "CLIENT_PORT": 235 | if (subPart.Length < 2) 236 | throw new ArgumentException("client_port value invalid", "aTransportString"); 237 | returnValue.ClientPort = PortCouple.Parse(subPart[1]); 238 | break; 239 | case "SERVER_PORT": 240 | if (subPart.Length < 2) 241 | throw new ArgumentException("server_port value invalid", "aTransportString"); 242 | returnValue.ServerPort = PortCouple.Parse(subPart[1]); 243 | break; 244 | case "SSRC": 245 | if (subPart.Length < 2) 246 | throw new ArgumentException("ssrc value invalid", "aTransportString"); 247 | returnValue.SSrc = subPart[1]; 248 | break; 249 | case "MODE": 250 | if (subPart.Length < 2) 251 | throw new ArgumentException("mode value invalid", "aTransportString"); 252 | returnValue.Mode = subPart[1]; 253 | break; 254 | default: 255 | // TODO log invalid part 256 | break; 257 | } 258 | } 259 | return returnValue; 260 | } 261 | 262 | private static void ReadLowerTransport(RtspTransport returnValue, string[] transportProtocolPart) 263 | { 264 | if (transportProtocolPart.Length == 3) 265 | { 266 | LowerTransportType lowerTransport; 267 | if (!Enum.TryParse(transportProtocolPart[2], out lowerTransport)) 268 | throw new ArgumentException("Lower transport type invalid", "aTransportString"); 269 | returnValue.LowerTransport = lowerTransport; 270 | } 271 | } 272 | 273 | private static void ReadProfile(RtspTransport returnValue, string[] transportProtocolPart) 274 | { 275 | ProfileType profile; 276 | if (transportProtocolPart.Length < 2 || !Enum.TryParse(transportProtocolPart[1], out profile)) 277 | throw new ArgumentException("Transport profile type invalid", "aTransportString"); 278 | returnValue.Profile = profile; 279 | } 280 | 281 | private static void ReadTransport(RtspTransport returnValue, string[] transportProtocolPart) 282 | { 283 | TransportType transport; 284 | if (!Enum.TryParse(transportProtocolPart[0], out transport)) 285 | throw new ArgumentException("Transport type invalid", "aTransportString"); 286 | returnValue.Transport = transport; 287 | } 288 | 289 | /// 290 | /// Returns a that represents this instance. 291 | /// 292 | /// 293 | /// A that represents this instance. 294 | /// 295 | public override string ToString() 296 | { 297 | StringBuilder transportString = new StringBuilder(); 298 | transportString.Append(Transport.ToString()); 299 | transportString.Append('/'); 300 | transportString.Append(Profile.ToString()); 301 | transportString.Append('/'); 302 | transportString.Append(LowerTransport.ToString()); 303 | if (LowerTransport == LowerTransportType.TCP) 304 | { 305 | transportString.Append(";unicast"); 306 | } 307 | if (LowerTransport == LowerTransportType.UDP) 308 | { 309 | transportString.Append(';'); 310 | transportString.Append(IsMulticast ? "multicast" : "unicast"); 311 | } 312 | if (Destination != null) 313 | { 314 | transportString.Append(";destination="); 315 | transportString.Append(Destination); 316 | } 317 | if (Source != null) 318 | { 319 | transportString.Append(";source="); 320 | transportString.Append(Source); 321 | } 322 | if (Interleaved != null) 323 | { 324 | transportString.Append(";interleaved="); 325 | transportString.Append(Interleaved.ToString()); 326 | } 327 | if (IsAppend) 328 | { 329 | transportString.Append(";append"); 330 | } 331 | if (TTL > 0) 332 | { 333 | transportString.Append(";ttl="); 334 | transportString.Append(TTL); 335 | } 336 | if (Layers > 0) 337 | { 338 | transportString.Append(";layers="); 339 | transportString.Append(Layers); 340 | } 341 | if (Port != null) 342 | { 343 | transportString.Append(";port="); 344 | transportString.Append(Port.ToString()); 345 | } 346 | if (ClientPort != null) 347 | { 348 | transportString.Append(";client_port="); 349 | transportString.Append(ClientPort.ToString()); 350 | } 351 | if (ServerPort != null) 352 | { 353 | transportString.Append(";server_port="); 354 | transportString.Append(ServerPort.ToString()); 355 | } 356 | if (SSrc != null) 357 | { 358 | transportString.Append(";ssrc="); 359 | transportString.Append(SSrc); 360 | } 361 | if (Mode != null && Mode != "PLAY") 362 | { 363 | transportString.Append(";mode="); 364 | transportString.Append(Mode); 365 | } 366 | return transportString.ToString(); 367 | } 368 | 369 | } 370 | } 371 | --------------------------------------------------------------------------------