From cdf390fccbff97382a083d7b530d2bd6e39fd667 Mon Sep 17 00:00:00 2001 From: stefan <52441940+stef-t@users.noreply.github.com> Date: Thu, 18 Jul 2019 11:34:06 +0200 Subject: [PATCH 1/7] adjusting gitignore such that Rider, Visual Studio Code and other Visual Studio auto-generated files are now properly ignored --- .gitignore | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 0d541cd..87523cb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,10 @@ Desktop.ini *.swp # Unity -/Library +/[Ll]ibrary /Logs -/Temp +/[Tt]emp +/[Oo]bj/ /Assets/StreamingAssets/FFmpegOut/Windows/ffmpeg.exe /Assets/StreamingAssets/FFmpegOut/macOS/ffmpeg @@ -20,3 +21,27 @@ Desktop.ini /*.mov /*.mp4 /*.webm + + +# Rider +/.idea/ +/Assets/Plugins/Editor/Jetbrains/ +/Assets/Plugins/Editor/Jetbrains.meta + +# Visual Studio Code +/.vscode/ + +# Autogenerated VS solution and project files +ExportedObj/ +.consulo/ +*.csproj +*.unityproj +*.sln +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd +*.pdb From f51ee097652d098db4e39b7456274fb3ae13bb87 Mon Sep 17 00:00:00 2001 From: stefan <52441940+stef-t@users.noreply.github.com> Date: Thu, 18 Jul 2019 11:34:06 +0200 Subject: [PATCH 2/7] we want to track the directory Plugins/Editor with git such that we don't run into complications in regards to the Rider-plugin/Unity/gitignore --- Assets/Plugins.meta | 8 ++++++++ Assets/Plugins/Editor.meta | 8 ++++++++ Assets/Plugins/Editor/.gitkeep | 0 3 files changed, 16 insertions(+) create mode 100644 Assets/Plugins.meta create mode 100644 Assets/Plugins/Editor.meta create mode 100644 Assets/Plugins/Editor/.gitkeep diff --git a/Assets/Plugins.meta b/Assets/Plugins.meta new file mode 100644 index 0000000..fa10717 --- /dev/null +++ b/Assets/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f9f34b9cb25824546b4f94e2aa7491c5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Editor.meta b/Assets/Plugins/Editor.meta new file mode 100644 index 0000000..52ea49c --- /dev/null +++ b/Assets/Plugins/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 548f4fe239d296044a0badc6d6e82d0f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Editor/.gitkeep b/Assets/Plugins/Editor/.gitkeep new file mode 100644 index 0000000..e69de29 From ca502e3944c3c10bea912be3a81153d21e2a50bd Mon Sep 17 00:00:00 2001 From: stefan <52441940+stef-t@users.noreply.github.com> Date: Thu, 18 Jul 2019 11:34:07 +0200 Subject: [PATCH 3/7] making CameraCapture and FFmpegSession derivable --- Assets/FFmpegOut/Runtime/CameraCapture.cs | 22 +++++++++++++++++----- Assets/FFmpegOut/Runtime/FFmpegSession.cs | 4 ++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Assets/FFmpegOut/Runtime/CameraCapture.cs b/Assets/FFmpegOut/Runtime/CameraCapture.cs index 754fb47..52c6e88 100644 --- a/Assets/FFmpegOut/Runtime/CameraCapture.cs +++ b/Assets/FFmpegOut/Runtime/CameraCapture.cs @@ -7,7 +7,7 @@ namespace FFmpegOut { [AddComponentMenu("FFmpegOut/Camera Capture")] - public sealed class CameraCapture : MonoBehaviour + public class CameraCapture : MonoBehaviour { #region Public properties @@ -57,6 +57,20 @@ int GetAntiAliasingLevel(Camera camera) return camera.allowMSAA ? QualitySettings.antiAliasing : 1; } + #endregion + + #region Public members + + protected virtual FFmpegSession GetSession( int texWidth, int texHeight ) + { + return FFmpegSession.Create( + gameObject.name, + texWidth, + texHeight, + _frameRate, _preset + ); + } + #endregion #region Time-keeping variables @@ -145,11 +159,9 @@ void Update() } // Start an FFmpeg session. - _session = FFmpegSession.Create( - gameObject.name, + _session = GetSession( camera.targetTexture.width, - camera.targetTexture.height, - _frameRate, preset + camera.targetTexture.height ); _startTime = Time.time; diff --git a/Assets/FFmpegOut/Runtime/FFmpegSession.cs b/Assets/FFmpegOut/Runtime/FFmpegSession.cs index 2f4384d..0552ae6 100644 --- a/Assets/FFmpegOut/Runtime/FFmpegSession.cs +++ b/Assets/FFmpegOut/Runtime/FFmpegSession.cs @@ -7,7 +7,7 @@ namespace FFmpegOut { - public sealed class FFmpegSession : System.IDisposable + public class FFmpegSession : System.IDisposable { #region Factory methods @@ -96,7 +96,7 @@ public void Dispose() FFmpegPipe _pipe; Material _blitMaterial; - FFmpegSession(string arguments) + protected FFmpegSession(string arguments) { if (!FFmpegPipe.IsAvailable) Debug.LogWarning( From 6e412f4595289daca53abbac8a40f09d6c0e3808 Mon Sep 17 00:00:00 2001 From: stefan <52441940+stef-t@users.noreply.github.com> Date: Thu, 18 Jul 2019 11:34:07 +0200 Subject: [PATCH 4/7] deriving from FFmpegOut classes to create everything required to capture a Camera LiveStream; setting up ffmpeg argument presets for different types of live stream protocols --- Assets/FFmpegOut/Runtime/LiveStream.meta | 8 ++++ .../Runtime/LiveStream/StreamCameraCapture.cs | 21 ++++++++++ .../LiveStream/StreamCameraCapture.cs.meta | 11 +++++ .../Runtime/LiveStream/StreamFFmpegSession.cs | 29 +++++++++++++ .../LiveStream/StreamFFmpegSession.cs.meta | 11 +++++ .../Runtime/LiveStream/StreamPreset.cs | 42 +++++++++++++++++++ .../Runtime/LiveStream/StreamPreset.cs.meta | 11 +++++ 7 files changed, 133 insertions(+) create mode 100644 Assets/FFmpegOut/Runtime/LiveStream.meta create mode 100644 Assets/FFmpegOut/Runtime/LiveStream/StreamCameraCapture.cs create mode 100644 Assets/FFmpegOut/Runtime/LiveStream/StreamCameraCapture.cs.meta create mode 100644 Assets/FFmpegOut/Runtime/LiveStream/StreamFFmpegSession.cs create mode 100644 Assets/FFmpegOut/Runtime/LiveStream/StreamFFmpegSession.cs.meta create mode 100644 Assets/FFmpegOut/Runtime/LiveStream/StreamPreset.cs create mode 100644 Assets/FFmpegOut/Runtime/LiveStream/StreamPreset.cs.meta diff --git a/Assets/FFmpegOut/Runtime/LiveStream.meta b/Assets/FFmpegOut/Runtime/LiveStream.meta new file mode 100644 index 0000000..c60cd13 --- /dev/null +++ b/Assets/FFmpegOut/Runtime/LiveStream.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f4c8b4446e0408b4abd1c88ea22054c8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FFmpegOut/Runtime/LiveStream/StreamCameraCapture.cs b/Assets/FFmpegOut/Runtime/LiveStream/StreamCameraCapture.cs new file mode 100644 index 0000000..5a7cb1f --- /dev/null +++ b/Assets/FFmpegOut/Runtime/LiveStream/StreamCameraCapture.cs @@ -0,0 +1,21 @@ +using UnityEngine; + +namespace FFmpegOut.LiveStream +{ + public class StreamCameraCapture : CameraCapture + { + [SerializeField] protected string _streamAddress; + [SerializeField] protected StreamPreset _streamPreset; + + protected override FFmpegSession GetSession(int texWidth, int texHeight) + { + return StreamFFmpegSession.Create( + texWidth, + texHeight, + frameRate, + preset, + _streamPreset, + _streamAddress); + } + } +} diff --git a/Assets/FFmpegOut/Runtime/LiveStream/StreamCameraCapture.cs.meta b/Assets/FFmpegOut/Runtime/LiveStream/StreamCameraCapture.cs.meta new file mode 100644 index 0000000..0eb5cbc --- /dev/null +++ b/Assets/FFmpegOut/Runtime/LiveStream/StreamCameraCapture.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0595c30dce42144d9388cb40476e729 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FFmpegOut/Runtime/LiveStream/StreamFFmpegSession.cs b/Assets/FFmpegOut/Runtime/LiveStream/StreamFFmpegSession.cs new file mode 100644 index 0000000..c056ed6 --- /dev/null +++ b/Assets/FFmpegOut/Runtime/LiveStream/StreamFFmpegSession.cs @@ -0,0 +1,29 @@ +namespace FFmpegOut.LiveStream +{ + /// + /// This FFmpegSession pipes whatever is on stdin to FFmpeg. + /// + public sealed class StreamFFmpegSession : FFmpegSession + { + private const string UNITY_CAM_TEX_BYTE_FORMAT = + "-pixel_format rgba -colorspace bt709 -f rawvideo -vcodec rawvideo"; + + private const string FFMPEG_LOGLEVEL = "-loglevel warning"; + + private StreamFFmpegSession(string arguments) : base(arguments) { } + + public static StreamFFmpegSession Create( + int width, int height, float frameRate, + FFmpegPreset encodingPreset, StreamPreset streamPreset, + string address) + { + // pipe:0 corresponds to stdin + string ffmpegArguments = + $"{UNITY_CAM_TEX_BYTE_FORMAT} {FFMPEG_LOGLEVEL} -framerate {frameRate} -video_size {width}x{height} " + + $"-re -i pipe:0 {encodingPreset.GetOptions()} " + + $"{streamPreset.GetOptions()} {address}"; + + return new StreamFFmpegSession(ffmpegArguments); + } + } +} diff --git a/Assets/FFmpegOut/Runtime/LiveStream/StreamFFmpegSession.cs.meta b/Assets/FFmpegOut/Runtime/LiveStream/StreamFFmpegSession.cs.meta new file mode 100644 index 0000000..2124721 --- /dev/null +++ b/Assets/FFmpegOut/Runtime/LiveStream/StreamFFmpegSession.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6c9e2ff9089460b49bd7fe32562273e1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FFmpegOut/Runtime/LiveStream/StreamPreset.cs b/Assets/FFmpegOut/Runtime/LiveStream/StreamPreset.cs new file mode 100644 index 0000000..ef8e062 --- /dev/null +++ b/Assets/FFmpegOut/Runtime/LiveStream/StreamPreset.cs @@ -0,0 +1,42 @@ +namespace FFmpegOut.LiveStream +{ + public enum StreamPreset + { + Udp, + Rtp, + Rtsp, + Hls, + Hls_ssegment, + Rtmp + } + + public static class StreamPresetExtensions + { + public static string GetOptions(this StreamPreset preset) + { + switch (preset) + { + case StreamPreset.Udp: + //Inspector: [udp adress] / udp://192.168.0.1:10755 + return "-f mpegts"; + case StreamPreset.Rtp: + //Inspector: [rtp adress] / rtp://192.168.0.1:10755 + return "-f rtp_mpegts"; + case StreamPreset.Rtsp: + //Inspector: [rtsp adress] / rtsp://192.168.0.1:10755 + return "-f rtsp"; + case StreamPreset.Hls: + //Inspector: [http server base url] [htdocs directory path + m3u8 file name extension] / http://192.168.0.1:8000/ D:\Repo\UnityCameraStream\miniweb\htdocs\stream.m3u8 + return "-f hls -hls_flags delete_segments -hls_init_time 0.5 -hls_time 0.5 -hls_list_size 10 -hls_allow_cache 1 -hls_base_url"; + case StreamPreset.Hls_ssegment: + //Inspector: [http server base url] -segment_list [htdocs directory path + m3u8 file name extension] [htdocs directory path + ts file name extension] / http://192.168.0.185:8000/ -segment_list D:\Repo\UnityCameraStream\miniweb\htdocs\stream.m3u8 D:\Repo\UnityCameraStream\miniweb\htdocs\out%03d.ts + return "-f segment -segment_list_type m3u8 -segment_list_size 10 -segment_list_flags +live -segment_time 1 -segment_wrap 10 -segment_list_entry_prefix"; + case StreamPreset.Rtmp: + //Inspector: [rtmp adress] / rtmp://192.168.0.1:1935/rtmp_stream/mystream + return "-f flv"; + } + + return null; + } + } +} diff --git a/Assets/FFmpegOut/Runtime/LiveStream/StreamPreset.cs.meta b/Assets/FFmpegOut/Runtime/LiveStream/StreamPreset.cs.meta new file mode 100644 index 0000000..21df863 --- /dev/null +++ b/Assets/FFmpegOut/Runtime/LiveStream/StreamPreset.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5cb9ea0087156d143b5d912aa6def310 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 6b99d3d6b7e0cd655b58e6999de71da140897b7b Mon Sep 17 00:00:00 2001 From: stefan <52441940+stef-t@users.noreply.github.com> Date: Thu, 18 Jul 2019 11:34:07 +0200 Subject: [PATCH 5/7] adding test scene which displays the current time; this is useful for testing the latency of a live stream --- Assets/Test/SetTimeNow.cs | 18 ++ Assets/Test/SetTimeNow.cs.meta | 11 + Assets/Test/Time.unity | 541 +++++++++++++++++++++++++++++++++ Assets/Test/Time.unity.meta | 7 + 4 files changed, 577 insertions(+) create mode 100644 Assets/Test/SetTimeNow.cs create mode 100644 Assets/Test/SetTimeNow.cs.meta create mode 100644 Assets/Test/Time.unity create mode 100644 Assets/Test/Time.unity.meta diff --git a/Assets/Test/SetTimeNow.cs b/Assets/Test/SetTimeNow.cs new file mode 100644 index 0000000..b73f1ca --- /dev/null +++ b/Assets/Test/SetTimeNow.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using UnityEngine; +using UnityEngine.UI; + +public class SetTimeNow : MonoBehaviour +{ + public Text timeText; + + void Update() + { + timeText.text = DateTime.UtcNow.ToString( + "HH:mm:ss.fff", + CultureInfo.InvariantCulture ); + } +} diff --git a/Assets/Test/SetTimeNow.cs.meta b/Assets/Test/SetTimeNow.cs.meta new file mode 100644 index 0000000..3e1c329 --- /dev/null +++ b/Assets/Test/SetTimeNow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 778589f8e6245bd49a4f1f2197e3177f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Test/Time.unity b/Assets/Test/Time.unity new file mode 100644 index 0000000..191090e --- /dev/null +++ b/Assets/Test/Time.unity @@ -0,0 +1,541 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.12731749, g: 0.13414757, b: 0.1210787, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_GIWorkflowMode: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 1 + m_LightmapEditorSettings: + serializedVersion: 10 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVRFilteringMode: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ShowResolutionOverlay: 1 + m_LightingDataAsset: {fileID: 0} + m_UseShadowmask: 1 +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &179020920 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 179020923} + - component: {fileID: 179020922} + - component: {fileID: 179020921} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &179020921 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 179020920} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b0595c30dce42144d9388cb40476e729, type: 3} + m_Name: + m_EditorClassIdentifier: + _width: 1024 + _height: 768 + _preset: 0 + _frameRate: 25 + _preset: 0 + _streamAddress: +--- !u!20 &179020922 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 179020920} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0.038047355, g: 0.09295766, b: 0.1792453, a: 0} + m_projectionMatrixMode: 1 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_GateFitMode: 2 + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 1 + orthographic size: 5 + m_Depth: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &179020923 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 179020920} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &374991529 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 374991532} + - component: {fileID: 374991531} + - component: {fileID: 374991530} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &374991530 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 374991529} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1077351063, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &374991531 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 374991529} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -619905303, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &374991532 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 374991529} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &682748741 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 682748745} + - component: {fileID: 682748744} + - component: {fileID: 682748743} + - component: {fileID: 682748742} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &682748742 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 682748741} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1301386320, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &682748743 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 682748741} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1980459831, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 +--- !u!223 &682748744 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 682748741} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 1 + m_Camera: {fileID: 179020922} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &682748745 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 682748741} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_Children: + - {fileID: 1733948551} + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &1733948550 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1733948551} + - component: {fileID: 1733948553} + - component: {fileID: 1733948552} + - component: {fileID: 1733948554} + m_Layer: 5 + m_Name: TimeText + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1733948551 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1733948550} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 682748745} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 600, y: 300} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1733948552 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1733948550} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 14 + m_MaxSize: 300 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: time +--- !u!222 &1733948553 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1733948550} + m_CullTransparentMesh: 0 +--- !u!114 &1733948554 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1733948550} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 778589f8e6245bd49a4f1f2197e3177f, type: 3} + m_Name: + m_EditorClassIdentifier: + timeText: {fileID: 1733948552} +--- !u!1 &1801282590 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1801282594} + - component: {fileID: 1801282593} + - component: {fileID: 1801282592} + m_Layer: 0 + m_Name: Cube + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!23 &1801282592 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1801282590} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 0 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 +--- !u!33 &1801282593 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1801282590} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &1801282594 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1801282590} + m_LocalRotation: {x: 0.34499216, y: 0.33085546, z: -0.13142933, w: 0.8684708} + m_LocalPosition: {x: 0.18, y: 2.85, z: 90} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 43.33, y: 41.71, z: 0} diff --git a/Assets/Test/Time.unity.meta b/Assets/Test/Time.unity.meta new file mode 100644 index 0000000..7be62bb --- /dev/null +++ b/Assets/Test/Time.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b274371f30943094fb08ed824afcb358 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 66c5f75906ea0b0c4d23b41b8c90cdeb03f75440 Mon Sep 17 00:00:00 2001 From: stefan <52441940+stef-t@users.noreply.github.com> Date: Thu, 18 Jul 2019 11:34:08 +0200 Subject: [PATCH 6/7] allowing the editor to run in background; implementing a PropertyDrawer for the StreamPreset enum which displays StreamAddress examples corresponding to the currently selected enum value --- Assets/FFmpegOut/Editor/StreamPresetDrawer.cs | 58 +++++++++++++++++++ .../Editor/StreamPresetDrawer.cs.meta | 11 ++++ .../Runtime/LiveStream/StreamCameraCapture.cs | 2 +- Assets/Test/Time.unity | 4 +- ProjectSettings/ProjectSettings.asset | 2 +- 5 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 Assets/FFmpegOut/Editor/StreamPresetDrawer.cs create mode 100644 Assets/FFmpegOut/Editor/StreamPresetDrawer.cs.meta diff --git a/Assets/FFmpegOut/Editor/StreamPresetDrawer.cs b/Assets/FFmpegOut/Editor/StreamPresetDrawer.cs new file mode 100644 index 0000000..a7637f3 --- /dev/null +++ b/Assets/FFmpegOut/Editor/StreamPresetDrawer.cs @@ -0,0 +1,58 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace FFmpegOut.LiveStream +{ + [CustomPropertyDrawer(typeof(StreamPreset))] + public class StreamPresetDrawer : PropertyDrawer + { + const int LINES = 3; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + Rect tmpRect = new Rect( + position.x, position.y, + position.width, base.GetPropertyHeight(property, GUIContent.none)); + + tmpRect.y += base.GetPropertyHeight(property, GUIContent.none) / 2f; + EditorGUI.PropertyField(tmpRect, property); + + // starting next line + tmpRect.y += base.GetPropertyHeight(property, GUIContent.none); + StreamPreset p = (StreamPreset) property.enumValueIndex; + string tooltip = string.Empty; + switch (p) + { + case StreamPreset.Udp: + tooltip = @"[udp adress]; e.g. udp://127.0.0.1:10755"; + break; + case StreamPreset.Rtp: + tooltip = @"[rtp adress]; e.g. rtp://127.0.0.1:10755"; + break; + case StreamPreset.Rtsp: + tooltip = @"[rtsp adress]; e.g. rtsp://127.0.0.1:10755"; + break; + case StreamPreset.Hls: + tooltip = @"[http server base url] [htdocs directory path + m3u8 file name extension]; e.g. http://127.0.0.1:8000/ D:\[ServerLocation]\htdocs\stream.m3u8"; + break; + case StreamPreset.Hls_ssegment: + tooltip = @"[http server base url] -segment_list [htdocs directory path + m3u8 file name extension] [htdocs directory path + ts file name extension]; e.g. http://127.0.0.185:8000/ -segment_list D:\[ServerLocation]\htdocs\stream.m3u8 D:\[ServerLocation]\htdocs\out%03d.ts"; + break; + case StreamPreset.Rtmp: + tooltip = @"[rtmp adress]; e.g. rtmp://127.0.0.1:1935/rtmp_stream/mystream"; + break; + } + + Rect indentPosition = new Rect(tmpRect); + indentPosition = EditorGUI.PrefixLabel(tmpRect, new GUIContent("Address Example")); + EditorGUI.SelectableLabel(indentPosition, tooltip); + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + return base.GetPropertyHeight(property, label) * LINES; + } + } +} diff --git a/Assets/FFmpegOut/Editor/StreamPresetDrawer.cs.meta b/Assets/FFmpegOut/Editor/StreamPresetDrawer.cs.meta new file mode 100644 index 0000000..0be9b6f --- /dev/null +++ b/Assets/FFmpegOut/Editor/StreamPresetDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2d2a4888079582543919fe4d18c457ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FFmpegOut/Runtime/LiveStream/StreamCameraCapture.cs b/Assets/FFmpegOut/Runtime/LiveStream/StreamCameraCapture.cs index 5a7cb1f..3605536 100644 --- a/Assets/FFmpegOut/Runtime/LiveStream/StreamCameraCapture.cs +++ b/Assets/FFmpegOut/Runtime/LiveStream/StreamCameraCapture.cs @@ -4,8 +4,8 @@ namespace FFmpegOut.LiveStream { public class StreamCameraCapture : CameraCapture { - [SerializeField] protected string _streamAddress; [SerializeField] protected StreamPreset _streamPreset; + [SerializeField] protected string _streamAddress; protected override FFmpegSession GetSession(int texWidth, int texHeight) { diff --git a/Assets/Test/Time.unity b/Assets/Test/Time.unity index 191090e..a9a17be 100644 --- a/Assets/Test/Time.unity +++ b/Assets/Test/Time.unity @@ -146,8 +146,8 @@ MonoBehaviour: _height: 768 _preset: 0 _frameRate: 25 - _preset: 0 - _streamAddress: + _streamPreset: 1 + _streamAddress: rtp://127.0.0.1:10755 --- !u!20 &179020922 Camera: m_ObjectHideFlags: 0 diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 153d321..79c6c0d 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -67,7 +67,7 @@ PlayerSettings: androidBlitType: 0 defaultIsNativeResolution: 1 macRetinaSupport: 1 - runInBackground: 0 + runInBackground: 1 captureSingleScreen: 0 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 From 98ddfb3b9827531a9eb8c20d950bd182782cd2a4 Mon Sep 17 00:00:00 2001 From: stefan <52441940+stef-t@users.noreply.github.com> Date: Thu, 18 Jul 2019 11:34:08 +0200 Subject: [PATCH 7/7] extending the readme by adding a section about the Stream Camera Capture component; cleanup of comments --- .../FFmpegOut/Runtime/LiveStream/StreamPreset.cs | 6 ------ README.md | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Assets/FFmpegOut/Runtime/LiveStream/StreamPreset.cs b/Assets/FFmpegOut/Runtime/LiveStream/StreamPreset.cs index ef8e062..861bd82 100644 --- a/Assets/FFmpegOut/Runtime/LiveStream/StreamPreset.cs +++ b/Assets/FFmpegOut/Runtime/LiveStream/StreamPreset.cs @@ -17,22 +17,16 @@ public static string GetOptions(this StreamPreset preset) switch (preset) { case StreamPreset.Udp: - //Inspector: [udp adress] / udp://192.168.0.1:10755 return "-f mpegts"; case StreamPreset.Rtp: - //Inspector: [rtp adress] / rtp://192.168.0.1:10755 return "-f rtp_mpegts"; case StreamPreset.Rtsp: - //Inspector: [rtsp adress] / rtsp://192.168.0.1:10755 return "-f rtsp"; case StreamPreset.Hls: - //Inspector: [http server base url] [htdocs directory path + m3u8 file name extension] / http://192.168.0.1:8000/ D:\Repo\UnityCameraStream\miniweb\htdocs\stream.m3u8 return "-f hls -hls_flags delete_segments -hls_init_time 0.5 -hls_time 0.5 -hls_list_size 10 -hls_allow_cache 1 -hls_base_url"; case StreamPreset.Hls_ssegment: - //Inspector: [http server base url] -segment_list [htdocs directory path + m3u8 file name extension] [htdocs directory path + ts file name extension] / http://192.168.0.185:8000/ -segment_list D:\Repo\UnityCameraStream\miniweb\htdocs\stream.m3u8 D:\Repo\UnityCameraStream\miniweb\htdocs\out%03d.ts return "-f segment -segment_list_type m3u8 -segment_list_size 10 -segment_list_flags +live -segment_time 1 -segment_wrap 10 -segment_list_entry_prefix"; case StreamPreset.Rtmp: - //Inspector: [rtmp adress] / rtmp://192.168.0.1:1935/rtmp_stream/mystream return "-f flv"; } diff --git a/README.md b/README.md index f546fe4..4242699 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,20 @@ At the moment the following presets are available for use. | HAP Alpha | QuickTime | Supports alpha channel | | HAP Q | QuickTime | | +Stream Camera Capture component +------------------------------- + +The **Stream Camera Capture component** (`StreamCameraCapture`) is used to capture frames +and send them via the selected streaming protocol to the given address. The same presets +as for the **Camera Capture component** (`CameraCapture`) are available. + +When e.g. starting the the **Time** scene as is, one can use e.g. **ffplay** to display +the live stream by calling "`.\ffplay.exe -fflags nobuffer rtp://127.0.0.1:10755`". + +Note that for the protocols `RTSP`, `RTMP`, `HLS` and `HLS_segment` a server is required +to handle the sent traffic and its further distribution on the network. `UDP` and +`RTP` allow peer-to-peer connections and no server is necessary. + ### Frame Rate The **Frame Rate** property controls the sampling frequency of the capture