CaptureBase.cs 108 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673
  1. #if UNITY_2017_3_OR_NEWER
  2. #define AVPRO_MOVIECAPTURE_OFFLINE_AUDIOCAPTURE
  3. #endif
  4. #if UNITY_5_6_OR_NEWER && UNITY_2018_3_OR_NEWER
  5. #define AVPRO_MOVIECAPTURE_VIDEOPLAYER_SUPPORT
  6. #endif
  7. #if UNITY_2017_1_OR_NEWER
  8. #define AVPRO_MOVIECAPTURE_PLAYABLES_SUPPORT
  9. #endif
  10. #if UNITY_2019_2_OR_NEWER
  11. #define AVPRO_MOVIECAPTURE_CAPTUREDELTA_SUPPORT
  12. #endif
  13. #if UNITY_2018_1_OR_NEWER
  14. #define UNITY_NATIVEARRAY_UNSAFE_SUPPORT
  15. #endif
  16. #if ENABLE_IL2CPP
  17. using AOT;
  18. #endif
  19. #if UNITY_2018_1_OR_NEWER
  20. using Unity.Collections;
  21. #else
  22. using UnityEngine.Collections;
  23. #endif
  24. using UnityEngine;
  25. using System.IO;
  26. using System;
  27. using System.Collections.Generic;
  28. using UnityEngine.Rendering;
  29. using System.Runtime.InteropServices;
  30. #if UNITY_ANDROID && !UNITY_EDITOR
  31. using UnityEngine.Android;
  32. #endif
  33. //-----------------------------------------------------------------------------
  34. // Copyright 2012-2022 RenderHeads Ltd. All rights reserved.
  35. //-----------------------------------------------------------------------------
  36. namespace RenderHeads.Media.AVProMovieCapture
  37. {
  38. /// <summary>
  39. /// Live stats about an active capture session
  40. /// </summary>
  41. public class CaptureStats
  42. {
  43. public float FPS { get { return _fps; } }
  44. public float FramesTotal { get { return _frameTotal; } }
  45. public uint NumDroppedFrames { get { return _numDroppedFrames; } internal set { _numDroppedFrames = value; } }
  46. public uint NumDroppedEncoderFrames { get { return _numDroppedEncoderFrames; } internal set { _numDroppedEncoderFrames = value; } }
  47. public uint NumEncodedFrames { get { return _numEncodedFrames; } internal set { _numEncodedFrames = value; } }
  48. public uint TotalEncodedSeconds { get { return _totalEncodedSeconds; } internal set { _totalEncodedSeconds = value; } }
  49. public AudioCaptureSource AudioCaptureSource { get { return _audioCaptureSource; } internal set { _audioCaptureSource = value; } }
  50. public int UnityAudioSampleRate { get { return _unityAudioSampleRate; } internal set { _unityAudioSampleRate = value; } }
  51. public int UnityAudioChannelCount { get { return _unityAudioChannelCount; } internal set { _unityAudioChannelCount = value; } }
  52. // Frame stats
  53. private uint _numDroppedFrames = 0;
  54. private uint _numDroppedEncoderFrames = 0;
  55. private uint _numEncodedFrames = 0;
  56. private uint _totalEncodedSeconds = 0;
  57. // Audio
  58. private AudioCaptureSource _audioCaptureSource = AudioCaptureSource.None;
  59. private int _unityAudioSampleRate = -1;
  60. private int _unityAudioChannelCount = -1;
  61. // Capture rate
  62. private float _fps = 0f;
  63. private int _frameTotal = 0;
  64. private int _frameCount = 0;
  65. private float _startFrameTime = 0f;
  66. internal void ResetFPS()
  67. {
  68. _frameCount = 0;
  69. _frameTotal = 0;
  70. _fps = 0.0f;
  71. _startFrameTime = 0.0f;
  72. }
  73. internal void UpdateFPS()
  74. {
  75. _frameCount++;
  76. _frameTotal++;
  77. float timeNow = Time.realtimeSinceStartup;
  78. float timeDelta = timeNow - _startFrameTime;
  79. if (timeDelta >= 1.0f)
  80. {
  81. _fps = (float)_frameCount / timeDelta;
  82. _frameCount = 0;
  83. _startFrameTime = timeNow;
  84. }
  85. }
  86. }
  87. [System.Serializable]
  88. [StructLayout(LayoutKind.Sequential, Pack=1)]
  89. public class VideoEncoderHints
  90. {
  91. public VideoEncoderHints()
  92. {
  93. SetDefaults();
  94. }
  95. public void SetDefaults()
  96. {
  97. averageBitrate = 0;
  98. maximumBitrate = 0;
  99. quality = 1.0f;
  100. keyframeInterval = 0;
  101. allowFastStartStreamingPostProcess = true;
  102. supportTransparency = false;
  103. useHardwareEncoding = true;
  104. injectStereoPacking = NoneAutoCustom.Auto;
  105. stereoPacking = StereoPacking.None;
  106. injectSphericalVideoLayout = NoneAutoCustom.Auto;
  107. sphericalVideoLayout = SphericalVideoLayout.None;
  108. enableFragmentedWriting = false;
  109. movieFragmentInterval = 120; // Default to two minutes
  110. colourSpace = ColourSpace.Unknown;
  111. sourceWidth = 0;
  112. sourceHeight = 0;
  113. androidNoCaptureRotation = false;
  114. transparency = Transparency.None;
  115. androidVulkanPreTransform = AndroidVulkanPreTransform.None;
  116. }
  117. internal void Validate()
  118. {
  119. quality = Mathf.Clamp(quality, 0f, 1f);
  120. }
  121. [Tooltip("Average number of bits per second for the resulting video. Zero uses the codec defaults.")]
  122. public uint averageBitrate;
  123. [Tooltip("Maximum number of bits per second for the resulting video. Zero uses the codec defaults.")]
  124. public uint maximumBitrate;
  125. [Range(0f, 1f)] public float quality;
  126. [Tooltip("How often a keyframe is inserted. Zero uses the codec defaults.")]
  127. public uint keyframeInterval;
  128. // Only for MOV / MP4 files
  129. [Tooltip("Move the 'moov' atom in the video file from the end to the start of the file to make streaming start fast. Also known as 'Fast Start' in some encoders")]
  130. [MarshalAs(UnmanagedType.U1)]
  131. public bool allowFastStartStreamingPostProcess;
  132. // Currently only for HEVC and ProRes 4444 on macOS/iOS, and supported DirectShow codecs (eg Lagarith/Uncompressed) on Windows
  133. [Tooltip("Hints to the encoder to use the alpha channel for transparency if possible")]
  134. [MarshalAs(UnmanagedType.U1)]
  135. public bool supportTransparency;
  136. // Windows only (on macOS/iOS hardware is always used if available)
  137. [MarshalAs(UnmanagedType.U1)]
  138. public bool useHardwareEncoding;
  139. // Only for MP4 files on Windows
  140. [Tooltip("Inject atoms to define stereo video mode")]
  141. public NoneAutoCustom injectStereoPacking;
  142. [Tooltip("Inject atoms to define stereo video mode")]
  143. public StereoPacking stereoPacking;
  144. // Only for MP4 files on Windows
  145. [Tooltip("Inject atoms to define spherical video layout")]
  146. public NoneAutoCustom injectSphericalVideoLayout;
  147. [Tooltip("Inject atoms to define spherical video layout")]
  148. public SphericalVideoLayout sphericalVideoLayout;
  149. // Support fragmented writing of mov/mp4 on macOS/iOS
  150. [Tooltip("Enable fragmented writing support for QuickTime (mov, mp4) files")]
  151. [MarshalAs(UnmanagedType.U1)]
  152. public bool enableFragmentedWriting;
  153. // Not sure of sensible uppoer/lower bounds for this
  154. [Tooltip("The interval at which to write movie fragments in seconds")]
  155. [Range(0f, 300f)]
  156. public double movieFragmentInterval;
  157. public enum ColourSpace : int { Unknown = -1, Gamma = 0, Linear = 1 };
  158. public ColourSpace colourSpace;
  159. // The width and height of the source
  160. public int sourceWidth;
  161. public int sourceHeight;
  162. [MarshalAs(UnmanagedType.U1)]
  163. public bool androidNoCaptureRotation;
  164. // Transparency
  165. [Tooltip("Transparency mode")]
  166. public Transparency transparency;
  167. // Android Vulkan only - not user configurable
  168. public AndroidVulkanPreTransform androidVulkanPreTransform;
  169. }
  170. [System.Serializable]
  171. [StructLayout(LayoutKind.Sequential, Pack=1)]
  172. public class ImageEncoderHints
  173. {
  174. public ImageEncoderHints()
  175. {
  176. SetDefaults();
  177. }
  178. public void SetDefaults()
  179. {
  180. quality = 0.85f;
  181. supportTransparency = false;
  182. colourSpace = ColourSpace.Unknown;
  183. sourceWidth = 0;
  184. sourceHeight = 0;
  185. transparency = Transparency.None;
  186. androidVulkanPreTransform = AndroidVulkanPreTransform.None;
  187. }
  188. internal void Validate()
  189. {
  190. quality = Mathf.Clamp(quality, 0f, 1f);
  191. }
  192. // Currently only affects JPG and HEIF formats (macOS only)
  193. [Range(0f, 1f)] public float quality;
  194. // Currently only for PNG
  195. [Tooltip("Hints to the encoder to use the alpha channel for transparency if possible")]
  196. [MarshalAs(UnmanagedType.U1)]
  197. public bool supportTransparency;
  198. public enum ColourSpace : int { Unknown = -1, Gamma = 0, Linear = 1 };
  199. public ColourSpace colourSpace;
  200. // The width and height of the source
  201. public int sourceWidth;
  202. public int sourceHeight;
  203. // Transparency
  204. [Tooltip("Transparency mode")]
  205. public Transparency transparency;
  206. [MarshalAs(UnmanagedType.U1)]
  207. public AndroidVulkanPreTransform androidVulkanPreTransform;
  208. }
  209. [System.Serializable]
  210. public class EncoderHints
  211. {
  212. public EncoderHints()
  213. {
  214. SetDefaults();
  215. }
  216. public void SetDefaults()
  217. {
  218. videoHints = new VideoEncoderHints();
  219. imageHints = new ImageEncoderHints();
  220. }
  221. public VideoEncoderHints videoHints;
  222. public ImageEncoderHints imageHints;
  223. }
  224. /// <summary>
  225. /// Base class wrapping common capture functionality
  226. /// </summary>
  227. public partial class CaptureBase : MonoBehaviour
  228. {
  229. private const string DocEditionsURL = "https://www.renderheads.com/content/docs/AVProMovieCapture/articles/download.html#editions";
  230. public enum Resolution
  231. {
  232. POW2_8192x8192,
  233. POW2_8192x4096,
  234. POW2_4096x4096,
  235. POW2_4096x2048,
  236. POW2_2048x4096,
  237. UHD_3840x2160,
  238. UHD_3840x2048,
  239. UHD_3840x1920,
  240. UHD_2560x1440,
  241. POW2_2048x2048,
  242. POW2_2048x1024,
  243. HD_1920x1080,
  244. HD_1280x720,
  245. SD_1024x768,
  246. SD_800x600,
  247. SD_800x450,
  248. SD_640x480,
  249. SD_640x360,
  250. SD_320x240,
  251. Original,
  252. Custom,
  253. }
  254. public enum CubemapDepth
  255. {
  256. Depth_24 = 24,
  257. Depth_16 = 16,
  258. Depth_Zero = 0,
  259. }
  260. public enum CubemapResolution
  261. {
  262. POW2_8192 = 8192,
  263. POW2_4096 = 4096,
  264. POW2_2048 = 2048,
  265. POW2_1024 = 1024,
  266. POW2_512 = 512,
  267. POW2_256 = 256,
  268. }
  269. public enum AntiAliasingLevel
  270. {
  271. UseCurrent,
  272. ForceNone,
  273. ForceSample2,
  274. ForceSample4,
  275. ForceSample8,
  276. }
  277. public enum DownScale
  278. {
  279. Original = 1,
  280. Half = 2,
  281. Quarter = 4,
  282. Eighth = 8,
  283. Sixteenth = 16,
  284. Custom = 100,
  285. }
  286. public enum OutputPath
  287. {
  288. RelativeToProject,
  289. RelativeToPeristentData,
  290. Absolute,
  291. RelativeToDesktop,
  292. RelativeToPictures,
  293. RelativeToVideos,
  294. PhotoLibrary,
  295. RelativeToTemporaryCachePath
  296. }
  297. public enum FrameUpdateMode
  298. {
  299. Automatic,
  300. Manual,
  301. }
  302. /*public enum OutputExtension
  303. {
  304. AVI,
  305. MP4,
  306. PNG,
  307. Custom = 100,
  308. }*/
  309. #if false
  310. [System.Serializable]
  311. public class WindowsPostCaptureSettings
  312. {
  313. [SerializeField]
  314. [Tooltip("Move the 'moov' atom in the MP4 file from the end to the start of the file to make streaming start fast. Also called 'Fast Start' in some encoders")]
  315. public bool writeFastStartStreamingForMp4 = true;
  316. }
  317. [System.Serializable]
  318. public class PostCaptureSettings
  319. {
  320. [SerializeField]
  321. [Tooltip("Move the 'moov' atom in the MP4 file from the end to the start of the file to make streaming start fast. Also called 'Fast Start' in some encoders")]
  322. public WindowsPostCaptureSettings windows = new WindowsPostCaptureSettings();
  323. }
  324. [SerializeField] PostCaptureSettings _postCaptureSettings = new PostCaptureSettings();
  325. #endif
  326. [SerializeField] EncoderHints _encoderHintsWindows = new EncoderHints();
  327. [SerializeField] EncoderHints _encoderHintsMacOS = new EncoderHints();
  328. [SerializeField] EncoderHints _encoderHintsIOS = new EncoderHints();
  329. [SerializeField] EncoderHints _encoderHintsAndroid = new EncoderHints();
  330. // General options
  331. [SerializeField] KeyCode _captureKey = KeyCode.None;
  332. [SerializeField] bool _isRealTime = true;
  333. [SerializeField] bool _persistAcrossSceneLoads = false;
  334. // Start options
  335. [SerializeField] StartTriggerMode _startTrigger = StartTriggerMode.Manual;
  336. [SerializeField] StartDelayMode _startDelay = StartDelayMode.None;
  337. [SerializeField] float _startDelaySeconds = 0f;
  338. // Stop options
  339. [SerializeField] StopMode _stopMode = StopMode.None;
  340. // TODO: add option to pause instead of stop?
  341. [SerializeField] int _stopFrames = 0;
  342. [SerializeField] float _stopSeconds = 0f;
  343. #pragma warning disable 0414 // "is assigned but its value is never used"
  344. [SerializeField] bool _pauseCaptureOnAppPause = true;
  345. // Video options
  346. public static readonly string[] DefaultVideoCodecPriorityWindows = { "H264",
  347. "HEVC",
  348. "Lagarith Lossless Codec",
  349. "Uncompressed",
  350. "x264vfw - H.264/MPEG-4 AVC codec",
  351. "Xvid MPEG-4 Codec" };
  352. public static readonly string[] DefaultVideoCodecPriorityMacOS = { "H264",
  353. "HEVC",
  354. "Apple ProRes 422",
  355. "Apple ProRes 4444" };
  356. public static readonly string[] DefaultVideoCodecPriorityAndroid = { "H264",
  357. "HEVC"/*,
  358. "VP8",
  359. "VP9"*/ };
  360. public static readonly string[] DefaultAudioCodecPriorityWindows = { "AAC",
  361. "FLAC",
  362. "Uncompressed" };
  363. public static readonly string[] DefaultAudioCodecPriorityMacOS = { "AAC",
  364. "FLAC",
  365. "Apple Lossless",
  366. "Linear PCM",
  367. "Uncompresssed" };
  368. public static readonly string[] DefaultAudioCodecPriorityIOS = { "AAC",
  369. "FLAC",
  370. "Apple Lossless",
  371. "Linear PCM",
  372. "Uncompresssed" };
  373. public static readonly string[] DefaultAudioCodecPriorityAndroid = {"AAC"/*,
  374. "FLAC",
  375. "OPUS"*/};
  376. public static readonly string[] DefaultAudioCaptureDevicePriorityWindow = { "Microphone (Realtek Audio)", "Stereo Mix", "What U Hear", "What You Hear", "Waveout Mix", "Mixed Output" };
  377. public static readonly string[] DefaultAudioCaptureDevicePriorityMacOS = { };
  378. public static readonly string[] DefaultAudioCaptureDevicePriorityIOS = { };
  379. public static readonly string[] DefaultAudioCaptureDevicePriorityAndroid = { };
  380. [SerializeField] string[] _videoCodecPriorityWindows = DefaultVideoCodecPriorityWindows;
  381. [SerializeField] string[] _videoCodecPriorityMacOS = DefaultVideoCodecPriorityMacOS;
  382. #pragma warning disable 0414 // "is assigned but its value is never used"
  383. [SerializeField] string[] _videoCodecPriorityAndroid = DefaultVideoCodecPriorityAndroid;
  384. #pragma warning restore 0414
  385. [SerializeField] string[] _audioCodecPriorityWindows = DefaultAudioCodecPriorityWindows;
  386. [SerializeField] string[] _audioCodecPriorityMacOS = DefaultAudioCodecPriorityMacOS;
  387. #pragma warning disable 0414 // "is assigned but its value is never used"
  388. [SerializeField] string[] _audioCodecPriorityAndroid = DefaultAudioCodecPriorityAndroid;
  389. #pragma warning restore 0414
  390. [SerializeField] float _frameRate = 30f;
  391. [Tooltip("Timelapse scale makes the frame capture run at a fraction of the target frame rate. Default value is 1")]
  392. [SerializeField] int _timelapseScale = 1;
  393. [Tooltip("Manual update mode requires user to call FrameUpdate() each time a frame is ready")]
  394. [SerializeField] FrameUpdateMode _frameUpdateMode = FrameUpdateMode.Automatic;
  395. [SerializeField] DownScale _downScale = DownScale.Original;
  396. [SerializeField] Vector2 _maxVideoSize = Vector2.zero;
  397. #pragma warning disable 414
  398. [SerializeField, Range(-1, 128)] int _forceVideoCodecIndexWindows = -1;
  399. [SerializeField, Range(-1, 128)] int _forceVideoCodecIndexMacOS = 0;
  400. [SerializeField, Range(0, 128)] int _forceVideoCodecIndexIOS = 0;
  401. [SerializeField, Range(0, 128)] int _forceVideoCodecIndexAndroid = 0;
  402. [SerializeField, Range(-1, 128)] int _forceAudioCodecIndexWindows = -1;
  403. [SerializeField, Range(-1, 128)] int _forceAudioCodecIndexMacOS = 0;
  404. [SerializeField, Range(0, 128)] int _forceAudioCodecIndexIOS = 0;
  405. [SerializeField, Range(0, 128)] int _forceAudioCodecIndexAndroid = -1;
  406. #pragma warning restore 414
  407. [SerializeField] bool _flipVertically = false;
  408. [Tooltip("Flushing the GPU during each capture results in less latency, but can slow down rendering performance for complex scenes.")]
  409. [SerializeField] bool _forceGpuFlush = false;
  410. [Tooltip("This option can help issues where skinning is used, or other animation/rendering effects that only complete later in the frame.")]
  411. [SerializeField] protected bool _useWaitForEndOfFrame = true;
  412. [Tooltip("Update the media gallery")]
  413. [SerializeField] protected bool _androidUpdateMediaGallery = true;
  414. [Tooltip("Portrait captures may be rotated 90° to better utilise the encoder, check this to disable the rotation at the risk of not being able to capture the full vertical resolution.")]
  415. [SerializeField] bool _androidNoCaptureRotation = false;
  416. [Tooltip("Log the start and stop of the capture. Disable this for less garbage generation.")]
  417. [SerializeField] bool _logCaptureStartStop = true;
  418. // Audio options
  419. [SerializeField] AudioCaptureSource _audioCaptureSource = AudioCaptureSource.None;
  420. [SerializeField] UnityAudioCapture _unityAudioCapture = null;
  421. [SerializeField, Range(0, 32)] int _forceAudioInputDeviceIndex = 0;
  422. [SerializeField, Range(8000, 96000)] int _manualAudioSampleRate = 48000;
  423. [SerializeField, Range(1, 8)] int _manualAudioChannelCount = 2;
  424. // Output options
  425. [SerializeField] protected OutputTarget _outputTarget = OutputTarget.VideoFile;
  426. public OutputTarget OutputTarget
  427. {
  428. get { return _outputTarget; }
  429. set { _outputTarget = value; }
  430. }
  431. public const OutputPath DefaultOutputFolderType = OutputPath.RelativeToProject;
  432. private const string DefaultOutputFolderPath = "Captures";
  433. [SerializeField] OutputPath _outputFolderType = DefaultOutputFolderType;
  434. [SerializeField] string _outputFolderPath = DefaultOutputFolderPath;
  435. [SerializeField] string _filenamePrefix = "MovieCapture";
  436. [SerializeField] bool _appendFilenameTimestamp = true;
  437. [SerializeField] bool _allowManualFileExtension = false;
  438. [SerializeField] string _filenameExtension = "mp4";
  439. [SerializeField] string _namedPipePath = @"\\.\pipe\test_pipe";
  440. public OutputPath OutputFolder
  441. {
  442. get { return _outputFolderType; }
  443. set { _outputFolderType = value; }
  444. }
  445. public string OutputFolderPath
  446. {
  447. get { return _outputFolderPath; }
  448. set { _outputFolderPath = value; }
  449. }
  450. public string FilenamePrefix
  451. {
  452. get { return _filenamePrefix; }
  453. set { _filenamePrefix = value; }
  454. }
  455. public bool AppendFilenameTimestamp
  456. {
  457. get { return _appendFilenameTimestamp; }
  458. set { _appendFilenameTimestamp = value; }
  459. }
  460. public bool AllowManualFileExtension
  461. {
  462. get { return _allowManualFileExtension; }
  463. set { _allowManualFileExtension = value; }
  464. }
  465. public string FilenameExtension
  466. {
  467. get { return _filenameExtension; }
  468. set { _filenameExtension = value; }
  469. }
  470. public string NamedPipePath
  471. {
  472. get { return _namedPipePath; }
  473. set { _namedPipePath = value; }
  474. }
  475. [SerializeField] int _imageSequenceStartFrame = 0;
  476. [SerializeField, Range(2, 12)] int _imageSequenceZeroDigits = 6;
  477. #pragma warning disable 414
  478. [SerializeField] ImageSequenceFormat _imageSequenceFormatWindows = ImageSequenceFormat.PNG;
  479. [SerializeField] ImageSequenceFormat _imageSequenceFormatMacOS = ImageSequenceFormat.PNG;
  480. [SerializeField] ImageSequenceFormat _imageSequenceFormatIOS = ImageSequenceFormat.PNG;
  481. [SerializeField] ImageSequenceFormat _imageSequenceFormatAndroid = ImageSequenceFormat.PNG;
  482. #pragma warning restore 414
  483. public int ImageSequenceStartFrame
  484. {
  485. get { return _imageSequenceStartFrame; }
  486. set { _imageSequenceStartFrame = value; }
  487. }
  488. public int ImageSequenceZeroDigits
  489. {
  490. get { return _imageSequenceZeroDigits; }
  491. set { _imageSequenceZeroDigits = Mathf.Clamp(_imageSequenceZeroDigits, 2, 12); }
  492. }
  493. // Camera specific options
  494. [SerializeField] protected Resolution _renderResolution = Resolution.Original;
  495. [SerializeField] protected Vector2 _renderSize = Vector2.one;
  496. [SerializeField] protected int _renderAntiAliasing = -1;
  497. // Motion blur options
  498. [SerializeField] protected bool _useMotionBlur = false;
  499. [SerializeField, Range(0, 64)] protected int _motionBlurSamples = 16;
  500. [SerializeField] protected Camera[] _motionBlurCameras = null;
  501. [SerializeField] protected MotionBlur _motionBlur;
  502. public bool UseMotionBlur
  503. {
  504. get { return _useMotionBlur; }
  505. set { _useMotionBlur = value; }
  506. }
  507. public int MotionBlurSamples
  508. {
  509. get { return _motionBlurSamples; }
  510. set { _motionBlurSamples = (int)Mathf.Clamp((float)value, 0f, 64f); }
  511. }
  512. public Camera[] MotionBlurCameras
  513. {
  514. get { return _motionBlurCameras; }
  515. set { _motionBlurCameras = value; }
  516. }
  517. public MotionBlur MotionBlur
  518. {
  519. get { return _motionBlur; }
  520. set { _motionBlur = value; }
  521. }
  522. // Performance options
  523. [SerializeField] bool _allowVSyncDisable = true;
  524. [SerializeField] protected bool _supportTextureRecreate = false;
  525. // Other options
  526. [SerializeField] int _minimumDiskSpaceMB = -1;
  527. #if AVPRO_MOVIECAPTURE_PLAYABLES_SUPPORT
  528. [SerializeField] TimelineController _timelineController = null;
  529. #endif
  530. #if AVPRO_MOVIECAPTURE_VIDEOPLAYER_SUPPORT
  531. [SerializeField] VideoPlayerController _videoPlayerController = null;
  532. #endif
  533. //public bool _allowFrameRateChange = true;
  534. protected Texture2D _texture;
  535. protected int _handle = -1;
  536. protected int _sourceWidth, _sourceHeight;
  537. protected int _targetWidth, _targetHeight;
  538. protected bool _capturing = false;
  539. protected bool _paused = false;
  540. protected string _filePath;
  541. protected string _finalFilePath;
  542. protected FileInfo _fileInfo;
  543. protected NativePlugin.PixelFormat _pixelFormat = NativePlugin.PixelFormat.YCbCr422_YUY2;
  544. private Codec _selectedVideoCodec = null;
  545. private Codec _selectedAudioCodec = null;
  546. private Device _selectedAudioInputDevice = null;
  547. private int _oldVSyncCount = 0;
  548. //private int _oldTargetFrameRate = -1;
  549. private float _oldFixedDeltaTime = 0f;
  550. protected bool _isTopDown = true;
  551. protected bool _isDirectX11 = false;
  552. private bool _queuedStartCapture = false;
  553. private bool _queuedStopCapture = false;
  554. private float _captureStartTime = 0f;
  555. private float _capturePrePauseTotalTime = 0f;
  556. private float _timeSinceLastFrame = 0f;
  557. protected YieldInstruction _waitForEndOfFrame;
  558. private long _freeDiskSpaceMB;
  559. protected Transparency _Transparency = Transparency.None;
  560. public Transparency Transparency { get { return _Transparency; } }
  561. //
  562. protected RenderTexture _sideBySideTexture;
  563. protected Material _sideBySideMaterial;
  564. #if !(UNITY_EDITOR || UNITY_STANDALONE_WIN)
  565. private bool _wasCapturingOnPause = false;
  566. #endif
  567. private float _startDelayTimer;
  568. private bool _startPaused;
  569. private System.Action<FileWritingHandler> _beginFinalFileWritingAction;
  570. private System.Action<FileWritingHandler> _completedFileWritingAction;
  571. private List<FileWritingHandler> _pendingFileWrites = new List<FileWritingHandler>(4);
  572. private static HashSet<string> _activeFilePaths = new HashSet<string>();
  573. public static HashSet<string> ActiveFilePaths
  574. {
  575. get { return _activeFilePaths; }
  576. }
  577. public string LastFilePath
  578. {
  579. get { return _filePath; }
  580. }
  581. // Register for notification of when the final file writing begins
  582. public System.Action<FileWritingHandler> BeginFinalFileWritingAction
  583. {
  584. get { return _beginFinalFileWritingAction; }
  585. set { _beginFinalFileWritingAction = value; }
  586. }
  587. // Register for notification of when the final file writing completes
  588. public System.Action<FileWritingHandler> CompletedFileWritingAction
  589. {
  590. get { return _completedFileWritingAction; }
  591. set { _completedFileWritingAction = value; }
  592. }
  593. // Stats
  594. private CaptureStats _stats = new CaptureStats();
  595. private static bool _isInitialised = false;
  596. private static bool _isApplicationQuiting = false;
  597. public Resolution CameraRenderResolution
  598. {
  599. get { return _renderResolution; }
  600. set { _renderResolution = value; }
  601. }
  602. public Vector2 CameraRenderCustomResolution
  603. {
  604. get { return _renderSize; }
  605. set { _renderSize = value; }
  606. }
  607. public int CameraRenderAntiAliasing
  608. {
  609. get { return _renderAntiAliasing; }
  610. set { _renderAntiAliasing = value; }
  611. }
  612. public bool IsRealTime
  613. {
  614. get { return _isRealTime; }
  615. set { _isRealTime = value; }
  616. }
  617. public bool PersistAcrossSceneLoads
  618. {
  619. get { return _persistAcrossSceneLoads; }
  620. set { _persistAcrossSceneLoads = value; }
  621. }
  622. public AudioCaptureSource AudioCaptureSource
  623. {
  624. get { return _audioCaptureSource; }
  625. set { _audioCaptureSource = value; }
  626. }
  627. public int ManualAudioSampleRate
  628. {
  629. get { return _manualAudioSampleRate; }
  630. set { _manualAudioSampleRate = value; }
  631. }
  632. public int ManualAudioChannelCount
  633. {
  634. get { return _manualAudioChannelCount; }
  635. set { _manualAudioChannelCount = value; }
  636. }
  637. public UnityAudioCapture UnityAudioCapture
  638. {
  639. get { return _unityAudioCapture; }
  640. set { _unityAudioCapture = value; }
  641. }
  642. public int ForceAudioInputDeviceIndex
  643. {
  644. get { return _forceAudioInputDeviceIndex; }
  645. set { _forceAudioInputDeviceIndex = value; SelectAudioInputDevice(); }
  646. }
  647. public float FrameRate
  648. {
  649. get { return _frameRate; }
  650. set { _frameRate = Mathf.Clamp(value, 0.01f, 240f); }
  651. }
  652. public StartTriggerMode StartTrigger
  653. {
  654. get { return _startTrigger; }
  655. set { _startTrigger = value; }
  656. }
  657. public StartDelayMode StartDelay
  658. {
  659. get { return _startDelay; }
  660. set { _startDelay = value; }
  661. }
  662. public float StartDelaySeconds
  663. {
  664. get { return _startDelaySeconds; }
  665. set { _startDelaySeconds = Mathf.Max(0f, value); }
  666. }
  667. public StopMode StopMode
  668. {
  669. get { return _stopMode; }
  670. set { _stopMode = value; }
  671. }
  672. public int StopAfterFramesElapsed
  673. {
  674. get { return _stopFrames; }
  675. set { _stopFrames = Mathf.Max(0, value); }
  676. }
  677. public float StopAfterSecondsElapsed
  678. {
  679. get { return _stopSeconds; }
  680. set { _stopSeconds = Mathf.Max(0f, value); }
  681. }
  682. public CaptureStats CaptureStats
  683. {
  684. get { return _stats; }
  685. }
  686. public string[] VideoCodecPriorityWindows
  687. {
  688. get { return _videoCodecPriorityWindows; }
  689. set { _videoCodecPriorityWindows = value; SelectVideoCodec(false); }
  690. }
  691. public string[] VideoCodecPriorityMacOS
  692. {
  693. get { return _videoCodecPriorityMacOS; }
  694. set { _videoCodecPriorityMacOS = value; SelectVideoCodec(false); }
  695. }
  696. public string[] AudioCodecPriorityWindows
  697. {
  698. get { return _audioCodecPriorityWindows; }
  699. set { _audioCodecPriorityWindows = value; SelectAudioCodec(); }
  700. }
  701. public string[] AudioCodecPriorityMacOS
  702. {
  703. get { return _audioCodecPriorityMacOS; }
  704. set { _audioCodecPriorityMacOS = value; SelectAudioCodec(); }
  705. }
  706. public int TimelapseScale
  707. {
  708. get { return _timelapseScale; }
  709. set { _timelapseScale = value; }
  710. }
  711. public FrameUpdateMode FrameUpdate
  712. {
  713. get { return _frameUpdateMode; }
  714. set { _frameUpdateMode = value; }
  715. }
  716. public DownScale ResolutionDownScale
  717. {
  718. get { return _downScale; }
  719. set { _downScale = value; }
  720. }
  721. public Vector2 ResolutionDownscaleCustom
  722. {
  723. get { return _maxVideoSize; }
  724. set { _maxVideoSize = value; }
  725. }
  726. public bool FlipVertically
  727. {
  728. get { return _flipVertically; }
  729. set { _flipVertically = value; }
  730. }
  731. public bool UseWaitForEndOfFrame
  732. {
  733. get { return _useWaitForEndOfFrame; }
  734. set { _useWaitForEndOfFrame = value; }
  735. }
  736. public bool LogCaptureStartStop
  737. {
  738. get { return _logCaptureStartStop; }
  739. set { _logCaptureStartStop = value; }
  740. }
  741. #if false
  742. public PostCaptureSettings PostCapture
  743. {
  744. get { return _postCaptureSettings; }
  745. }
  746. #endif
  747. public bool AllowOfflineVSyncDisable
  748. {
  749. get { return _allowVSyncDisable; }
  750. set { _allowVSyncDisable = value; }
  751. }
  752. public bool SupportTextureRecreate
  753. {
  754. get { return _supportTextureRecreate; }
  755. set { _supportTextureRecreate = value; }
  756. }
  757. #if AVPRO_MOVIECAPTURE_PLAYABLES_SUPPORT
  758. public TimelineController TimelineController
  759. {
  760. get { return _timelineController; }
  761. set { _timelineController = value; }
  762. }
  763. #endif
  764. #if AVPRO_MOVIECAPTURE_VIDEOPLAYER_SUPPORT
  765. public VideoPlayerController VideoPlayerController
  766. {
  767. get { return _videoPlayerController; }
  768. set { _videoPlayerController = value; }
  769. }
  770. #endif
  771. public Codec SelectedVideoCodec
  772. {
  773. get { return _selectedVideoCodec; }
  774. }
  775. public Codec SelectedAudioCodec
  776. {
  777. get { return _selectedAudioCodec; }
  778. }
  779. public Device SelectedAudioInputDevice
  780. {
  781. get { return _selectedAudioInputDevice; }
  782. }
  783. public int NativeForceVideoCodecIndex
  784. {
  785. #if UNITY_EDITOR
  786. #if UNITY_EDITOR_WIN
  787. get { return _forceVideoCodecIndexWindows; }
  788. set { _forceVideoCodecIndexWindows = value; }
  789. #elif UNITY_EDITOR_OSX
  790. get { return _forceVideoCodecIndexMacOS; }
  791. set { _forceVideoCodecIndexMacOS = value; }
  792. #else
  793. get { return -1; }
  794. set { }
  795. #endif
  796. #else
  797. #if UNITY_STANDALONE_WIN
  798. get { return _forceVideoCodecIndexWindows; }
  799. set { _forceVideoCodecIndexWindows = value; }
  800. #elif UNITY_STANDALONE_OSX
  801. get { return _forceVideoCodecIndexMacOS; }
  802. set { _forceVideoCodecIndexMacOS = value; }
  803. #elif UNITY_IOS
  804. get { return _forceVideoCodecIndexIOS; }
  805. set { _forceVideoCodecIndexIOS = value; }
  806. #elif UNITY_ANDROID
  807. get { return _forceVideoCodecIndexAndroid; }
  808. set { _forceVideoCodecIndexAndroid = value; }
  809. #else
  810. get { return -1; }
  811. set { }
  812. #endif
  813. #endif
  814. }
  815. public int NativeForceAudioCodecIndex
  816. {
  817. #if UNITY_EDITOR
  818. #if UNITY_EDITOR_WIN
  819. get { return _forceAudioCodecIndexWindows; }
  820. set { _forceAudioCodecIndexWindows = value; }
  821. #elif UNITY_EDITOR_OSX
  822. get { return _forceAudioCodecIndexMacOS; }
  823. set { _forceAudioCodecIndexMacOS = value; }
  824. #else
  825. get { return -1; }
  826. set { }
  827. #endif
  828. #else
  829. #if UNITY_STANDALONE_WIN
  830. get { return _forceAudioCodecIndexWindows; }
  831. set { _forceAudioCodecIndexWindows = value; }
  832. #elif UNITY_STANDALONE_OSX
  833. get { return _forceAudioCodecIndexMacOS; }
  834. set { _forceAudioCodecIndexMacOS = value; }
  835. #elif UNITY_IOS
  836. get { return _forceAudioCodecIndexIOS; }
  837. set { _forceAudioCodecIndexIOS = value; }
  838. #elif UNITY_ANDROID
  839. get { return _forceAudioCodecIndexAndroid; }
  840. set { _forceAudioCodecIndexAndroid = value; }
  841. #else
  842. get { return -1; }
  843. set { }
  844. #endif
  845. #endif
  846. }
  847. public ImageSequenceFormat NativeImageSequenceFormat
  848. {
  849. #if UNITY_EDITOR
  850. #if UNITY_EDITOR_WIN
  851. get { return _imageSequenceFormatWindows; }
  852. set { _imageSequenceFormatWindows = value; }
  853. #elif UNITY_EDITOR_OSX
  854. get { return _imageSequenceFormatMacOS; }
  855. set { _imageSequenceFormatMacOS = value; }
  856. #else
  857. get { return ImageSequenceFormat.PNG; }
  858. set { }
  859. #endif
  860. #else
  861. #if UNITY_STANDALONE_WIN
  862. get { return _imageSequenceFormatWindows; }
  863. set { _imageSequenceFormatWindows = value; }
  864. #elif UNITY_STANDALONE_OSX
  865. get { return _imageSequenceFormatMacOS; }
  866. set { _imageSequenceFormatMacOS = value; }
  867. #elif UNITY_IOS
  868. get { return _imageSequenceFormatIOS; }
  869. set { _imageSequenceFormatIOS = value; }
  870. #elif UNITY_ANDROID
  871. get { return _imageSequenceFormatAndroid; }
  872. set { _imageSequenceFormatAndroid = value; }
  873. #else
  874. get { return ImageSequenceFormat.PNG; }
  875. set { }
  876. #endif
  877. #endif
  878. }
  879. protected static NativePlugin.Platform GetCurrentPlatform()
  880. {
  881. NativePlugin.Platform result = NativePlugin.Platform.Unknown;
  882. #if UNITY_EDITOR
  883. #if UNITY_EDITOR_WIN
  884. result = NativePlugin.Platform.Windows;
  885. #elif UNITY_EDITOR_OSX
  886. result = NativePlugin.Platform.macOS;
  887. #endif
  888. #else
  889. #if UNITY_STANDALONE_WIN
  890. result = NativePlugin.Platform.Windows;
  891. #elif UNITY_STANDALONE_OSX
  892. result = NativePlugin.Platform.macOS;
  893. #elif UNITY_IOS
  894. result = NativePlugin.Platform.iOS;
  895. #elif UNITY_ANDROID
  896. result = NativePlugin.Platform.Android;
  897. #endif
  898. #endif
  899. return result;
  900. }
  901. public EncoderHints GetEncoderHints(NativePlugin.Platform platform = NativePlugin.Platform.Current)
  902. {
  903. EncoderHints result = null;
  904. if (platform == NativePlugin.Platform.Current)
  905. {
  906. platform = GetCurrentPlatform();
  907. }
  908. switch (platform)
  909. {
  910. case NativePlugin.Platform.Windows:
  911. result = _encoderHintsWindows;
  912. break;
  913. case NativePlugin.Platform.macOS:
  914. result = _encoderHintsMacOS;
  915. break;
  916. case NativePlugin.Platform.iOS:
  917. result = _encoderHintsIOS;
  918. break;
  919. case NativePlugin.Platform.Android:
  920. result = _encoderHintsAndroid;
  921. break;
  922. }
  923. return result;
  924. }
  925. public void SetEncoderHints(EncoderHints hints, NativePlugin.Platform platform = NativePlugin.Platform.Current)
  926. {
  927. if (platform == NativePlugin.Platform.Current)
  928. {
  929. platform = GetCurrentPlatform();
  930. }
  931. switch (platform)
  932. {
  933. case NativePlugin.Platform.Windows:
  934. _encoderHintsWindows = hints;
  935. break;
  936. case NativePlugin.Platform.macOS:
  937. _encoderHintsMacOS = hints;
  938. break;
  939. case NativePlugin.Platform.iOS:
  940. _encoderHintsIOS = hints;
  941. break;
  942. case NativePlugin.Platform.Android:
  943. _encoderHintsAndroid = hints;
  944. break;
  945. }
  946. }
  947. #if UNITY_ANDROID && !UNITY_EDITOR
  948. protected static AndroidJavaObject s_ActivityContext = null;
  949. protected static AndroidJavaClass s_Interface = null;
  950. #endif
  951. public static void UpdateMediaGallery( string videoFilePath )
  952. {
  953. if( videoFilePath != null )
  954. {
  955. #if UNITY_ANDROID && !UNITY_EDITOR
  956. // Update video gallery on Android
  957. if( s_Interface != null )
  958. {
  959. s_Interface.CallStatic("UpdateMediaGallery", videoFilePath);
  960. }
  961. #endif
  962. }
  963. }
  964. protected virtual void Awake()
  965. {
  966. if (!_isInitialised)
  967. {
  968. #if UNITY_ANDROID && !UNITY_EDITOR
  969. // Get the activity context
  970. if (s_ActivityContext == null)
  971. {
  972. AndroidJavaClass activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
  973. if (activityClass != null)
  974. {
  975. s_ActivityContext = activityClass.GetStatic<AndroidJavaObject>("currentActivity");
  976. }
  977. }
  978. s_Interface = new AndroidJavaClass("com.renderheads.AVPro.MovieCapture.Manager");
  979. s_Interface.CallStatic("setContext", s_ActivityContext);
  980. #endif
  981. try
  982. {
  983. string pluginVersionString = NativePlugin.GetPluginVersionString();
  984. // Check that the plugin version number is not too old
  985. if (!pluginVersionString.StartsWith(NativePlugin.ExpectedPluginVersion))
  986. {
  987. Debug.LogWarning("[AVProMovieCapture] Plugin version number " + pluginVersionString + " doesn't match the expected version number " + NativePlugin.ExpectedPluginVersion + ". It looks like the plugin didn't upgrade correctly. To resolve this please restart Unity and try to upgrade the package again.");
  988. }
  989. #if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
  990. #if !UNITY_2017_1_OR_NEWER
  991. if (SystemInfo.graphicsDeviceVersion.StartsWith("Metal"))
  992. {
  993. Debug.LogError("[AVProMovieCapture] Metal is not supported below Unity 2017, please switch to OpenGLCore in Player Settings.");
  994. return;
  995. }
  996. #endif
  997. #elif !UNITY_EDITOR && UNITY_IOS && !UNITY_2017_1_OR_NEWER
  998. if (Application.isPlaying)
  999. {
  1000. Debug.LogError("[AVProMovieCapture] iOS is not supported below Unity 2017.");
  1001. return;
  1002. }
  1003. #endif
  1004. if (NativePlugin.Init())
  1005. {
  1006. Debug.Log("[AVProMovieCapture] Init version: " + NativePlugin.ScriptVersion + " (plugin v" + pluginVersionString +") with GPU " + SystemInfo.graphicsDeviceName + " " + SystemInfo.graphicsDeviceVersion + " OS: " + SystemInfo.operatingSystem);
  1007. _isInitialised = true;
  1008. }
  1009. else
  1010. {
  1011. Debug.LogError("[AVProMovieCapture] Failed to initialise plugin on version: " + NativePlugin.ScriptVersion + " (plugin v" + pluginVersionString + ") with GPU " + SystemInfo.graphicsDeviceName + " " + SystemInfo.graphicsDeviceVersion + " OS: " + SystemInfo.operatingSystem);
  1012. }
  1013. }
  1014. catch (DllNotFoundException e)
  1015. {
  1016. string missingDllMessage = string.Empty;
  1017. missingDllMessage = "Unity couldn't find the plugin DLL. Please select the native plugin files in 'Plugins/RenderHeads/AVProMovieCapture/Runtime/Plugins' folder and select the correct platform in the Inspector.";
  1018. Debug.LogError("[AVProMovieCapture] " + missingDllMessage);
  1019. #if UNITY_EDITOR
  1020. UnityEditor.EditorUtility.DisplayDialog("Plugin files not found", missingDllMessage, "Ok");
  1021. #endif
  1022. throw e;
  1023. }
  1024. }
  1025. _isDirectX11 = SystemInfo.graphicsDeviceVersion.StartsWith("Direct3D 11");
  1026. // Moved to Start() now due to no audio on Andorid when creating a capture component in script
  1027. // SelectVideoCodec();
  1028. // SelectAudioCodec();
  1029. // SelectAudioInputDevice();
  1030. if (_persistAcrossSceneLoads)
  1031. {
  1032. GameObject.DontDestroyOnLoad(this.gameObject);
  1033. }
  1034. }
  1035. static CaptureBase()
  1036. {
  1037. #if UNITY_EDITOR
  1038. SetupEditorPlayPauseSupport();
  1039. #endif
  1040. }
  1041. public virtual void Start()
  1042. {
  1043. // Moved from Awake() now due to no audio on Andorid when creating a capture component in script
  1044. SelectVideoCodec();
  1045. SelectAudioCodec();
  1046. SelectAudioInputDevice();
  1047. Application.runInBackground = true;
  1048. _waitForEndOfFrame = new WaitForEndOfFrame();
  1049. if (_startTrigger == StartTriggerMode.OnStart)
  1050. {
  1051. StartCapture();
  1052. }
  1053. }
  1054. // Select the best codec based on criteria
  1055. private static bool SelectCodec(ref Codec codec, CodecList codecList, int forceCodecIndex, string[] codecPriorityList, MediaApi matchMediaApi, bool allowFallbackToFirstCodec, bool logFallbackWarning)
  1056. {
  1057. codec = null;
  1058. // The user has specified their own codec index
  1059. if (forceCodecIndex >= 0)
  1060. {
  1061. if (forceCodecIndex < codecList.Count)
  1062. {
  1063. codec = codecList.Codecs[forceCodecIndex];
  1064. }
  1065. }
  1066. else
  1067. {
  1068. // The user has specified an ordered list of codec name to search for
  1069. if (codecPriorityList != null && codecPriorityList.Length > 0)
  1070. {
  1071. foreach (string codecName in codecPriorityList)
  1072. {
  1073. codec = codecList.FindCodec(codecName.Trim(), matchMediaApi);
  1074. if (codec != null)
  1075. {
  1076. break;
  1077. }
  1078. }
  1079. }
  1080. }
  1081. // If the found codec doesn't match the required MediaApi, set it to null
  1082. if (codec != null && matchMediaApi != MediaApi.Unknown)
  1083. {
  1084. if (codec.MediaApi != matchMediaApi)
  1085. {
  1086. codec = null;
  1087. }
  1088. }
  1089. // Fallback to the first codec
  1090. if (codec == null && allowFallbackToFirstCodec)
  1091. {
  1092. if (codecList.Count > 0)
  1093. {
  1094. if (matchMediaApi != MediaApi.Unknown)
  1095. {
  1096. codec = codecList.GetFirstWithMediaApi(matchMediaApi);
  1097. }
  1098. else
  1099. {
  1100. codec = codecList.Codecs[0];
  1101. }
  1102. if (logFallbackWarning)
  1103. {
  1104. Debug.LogWarning("[AVProMovieCapture] Codec not found. Using the first codec available.");
  1105. }
  1106. }
  1107. }
  1108. return (codec != null);
  1109. }
  1110. public Codec SelectVideoCodec(bool isStartingCapture = false)
  1111. {
  1112. _selectedVideoCodec = null;
  1113. #if UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)
  1114. SelectCodec(ref _selectedVideoCodec, CodecManager.VideoCodecs, NativeForceVideoCodecIndex, _videoCodecPriorityWindows, MediaApi.Unknown, true, isStartingCapture);
  1115. #elif UNITY_EDITOR_OSX || (!UNITY_EDITOR && UNITY_STANDALONE_OSX)
  1116. SelectCodec(ref _selectedVideoCodec, CodecManager.VideoCodecs, NativeForceVideoCodecIndex, _videoCodecPriorityMacOS, MediaApi.Unknown, true, isStartingCapture);
  1117. #elif !UNITY_EDITOR && UNITY_IOS
  1118. SelectCodec(ref _selectedVideoCodec, CodecManager.VideoCodecs, NativeForceVideoCodecIndex, null, MediaApi.Unknown, true, isStartingCapture);
  1119. #elif !UNITY_EDITOR && UNITY_ANDROID
  1120. SelectCodec(ref _selectedVideoCodec, CodecManager.VideoCodecs, NativeForceVideoCodecIndex, _videoCodecPriorityAndroid, MediaApi.Unknown, true, isStartingCapture);
  1121. #endif
  1122. if (isStartingCapture && _selectedVideoCodec == null)
  1123. {
  1124. Debug.LogError("[AVProMovieCapture] Failed to select a suitable video codec");
  1125. }
  1126. return _selectedVideoCodec;
  1127. }
  1128. public Codec SelectAudioCodec()
  1129. {
  1130. _selectedAudioCodec = null;
  1131. if (_audioCaptureSource != AudioCaptureSource.None)
  1132. {
  1133. #if UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)
  1134. // Audio codec selection requires a video codec to be selected first on Windows
  1135. if (_selectedVideoCodec != null)
  1136. {
  1137. SelectCodec(ref _selectedAudioCodec, CodecManager.AudioCodecs, NativeForceAudioCodecIndex, _audioCodecPriorityWindows, _selectedVideoCodec.MediaApi, true, false);
  1138. }
  1139. #elif UNITY_EDITOR_OSX || (!UNITY_EDITOR && UNITY_STANDALONE_OSX)
  1140. SelectCodec(ref _selectedAudioCodec, CodecManager.AudioCodecs, NativeForceAudioCodecIndex, _audioCodecPriorityMacOS, MediaApi.Unknown, true, false);
  1141. #elif !UNITY_EDITOR && UNITY_IOS
  1142. SelectCodec(ref _selectedAudioCodec, CodecManager.AudioCodecs, NativeForceAudioCodecIndex, null, MediaApi.Unknown, true, false);
  1143. #elif !UNITY_EDITOR && UNITY_ANDROID
  1144. SelectCodec(ref _selectedAudioCodec, CodecManager.AudioCodecs, NativeForceAudioCodecIndex, null, MediaApi.Unknown, true, false);
  1145. #endif
  1146. if (_selectedAudioCodec == null)
  1147. {
  1148. //Debug.LogError("[AVProMovieCapture] Failed to select a suitable audio codec");
  1149. }
  1150. }
  1151. return _selectedAudioCodec;
  1152. }
  1153. public Device SelectAudioInputDevice()
  1154. {
  1155. _selectedAudioInputDevice = null;
  1156. if (_audioCaptureSource == AudioCaptureSource.Microphone)
  1157. {
  1158. // Audio input device selection requires a video codec to be selected first
  1159. if (_selectedVideoCodec != null)
  1160. {
  1161. if (_forceAudioInputDeviceIndex >= 0 && _forceAudioInputDeviceIndex < DeviceManager.AudioInputDevices.Count)
  1162. {
  1163. _selectedAudioInputDevice = DeviceManager.AudioInputDevices.Devices[_forceAudioInputDeviceIndex];
  1164. }
  1165. // If the found codec doesn't match the required MediaApi, set it to null
  1166. if (_selectedAudioInputDevice != null && _selectedAudioInputDevice.MediaApi != _selectedVideoCodec.MediaApi)
  1167. {
  1168. _selectedAudioInputDevice = null;
  1169. }
  1170. // Fallback to the first device
  1171. if (_selectedAudioInputDevice == null)
  1172. {
  1173. if (DeviceManager.AudioInputDevices.Count > 0)
  1174. {
  1175. _selectedAudioInputDevice = DeviceManager.AudioInputDevices.GetFirstWithMediaApi(_selectedVideoCodec.MediaApi);
  1176. }
  1177. }
  1178. }
  1179. }
  1180. return _selectedAudioInputDevice;
  1181. }
  1182. public static Vector2 GetRecordingResolution(int width, int height, DownScale downscale, Vector2 maxVideoSize)
  1183. {
  1184. int targetWidth = width;
  1185. int targetHeight = height;
  1186. if (downscale != DownScale.Custom)
  1187. {
  1188. targetWidth /= (int)downscale;
  1189. targetHeight /= (int)downscale;
  1190. }
  1191. else
  1192. {
  1193. if (maxVideoSize.x >= 1.0f && maxVideoSize.y >= 1.0f)
  1194. {
  1195. targetWidth = Mathf.FloorToInt(maxVideoSize.x);
  1196. targetHeight = Mathf.FloorToInt(maxVideoSize.y);
  1197. }
  1198. }
  1199. // Some codecs like Lagarith in YUY2 mode need size to be multiple of 4
  1200. targetWidth = NextMultipleOf4(targetWidth);
  1201. targetHeight = NextMultipleOf4(targetHeight);
  1202. return new Vector2(targetWidth, targetHeight);
  1203. }
  1204. public void SelectRecordingResolution(int width, int height)
  1205. {
  1206. _sourceWidth = width;
  1207. _sourceHeight = height;
  1208. _targetWidth = width;
  1209. _targetHeight = height;
  1210. if (_downScale != DownScale.Custom)
  1211. {
  1212. _targetWidth /= (int)_downScale;
  1213. _targetHeight /= (int)_downScale;
  1214. }
  1215. else
  1216. {
  1217. if (_maxVideoSize.x >= 1.0f && _maxVideoSize.y >= 1.0f)
  1218. {
  1219. _targetWidth = Mathf.FloorToInt(_maxVideoSize.x);
  1220. _targetHeight = Mathf.FloorToInt(_maxVideoSize.y);
  1221. }
  1222. }
  1223. // Some codecs like Lagarith in YUY2 mode need size to be multiple of 4
  1224. _targetWidth = NextMultipleOf4(_targetWidth);
  1225. _targetHeight = NextMultipleOf4(_targetHeight);
  1226. }
  1227. public virtual void OnDestroy()
  1228. {
  1229. _waitForEndOfFrame = null;
  1230. StopCapture(true, true);
  1231. FreePendingFileWrites();
  1232. // Make sure there are no other capture instances running and then deinitialise the plugin
  1233. if (_isApplicationQuiting && _isInitialised)
  1234. {
  1235. // TODO: would it be faster to just look for _pendingFileWrites?
  1236. bool anyCapturesRunning = false;
  1237. #if UNITY_EDITOR
  1238. // In editor we have to search hidden objects as well, as the editor window components are created hidden
  1239. CaptureBase[] captures = (CaptureBase[])Resources.FindObjectsOfTypeAll(typeof(CaptureBase));
  1240. #else
  1241. CaptureBase[] captures = (CaptureBase[])Component.FindObjectsOfType(typeof(CaptureBase));
  1242. #endif
  1243. foreach (CaptureBase capture in captures)
  1244. {
  1245. if (capture != null && capture.IsCapturing())
  1246. {
  1247. anyCapturesRunning = true;
  1248. break;
  1249. }
  1250. }
  1251. if (!anyCapturesRunning)
  1252. {
  1253. NativePlugin.Deinit();
  1254. _isInitialised = false;
  1255. }
  1256. }
  1257. }
  1258. private void FreePendingFileWrites()
  1259. {
  1260. foreach (FileWritingHandler handler in _pendingFileWrites)
  1261. {
  1262. handler.Dispose();
  1263. }
  1264. _pendingFileWrites.Clear();
  1265. }
  1266. private void OnApplicationQuit()
  1267. {
  1268. _isApplicationQuiting = true;
  1269. }
  1270. /*
  1271. private void OnApplicationPause(bool paused)
  1272. {
  1273. #if UNITY_IOS && !UNITY_EDITOR
  1274. if (paused && IsCapturing())
  1275. {
  1276. Debug.LogWarning("Application is being paused, stopping capture...");
  1277. StopCapture();
  1278. }
  1279. #endif
  1280. }
  1281. */
  1282. #if !UNITY_EDITOR
  1283. void OnApplicationFocus(bool focusStatus)
  1284. {
  1285. #if !(UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN)
  1286. // Debug.Log("OnApplicationFocus: focusStatus: " + focusStatus);
  1287. if( focusStatus )
  1288. {
  1289. if( _wasCapturingOnPause )
  1290. {
  1291. _wasCapturingOnPause = false;
  1292. ResumeCapture();
  1293. Debug.Log("OnApplicationFocus: capturing video again");
  1294. }
  1295. }
  1296. #endif
  1297. }
  1298. void OnApplicationPause(bool pauseStatus)
  1299. {
  1300. #if !(UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN)
  1301. // Debug.Log("OnApplicationPause: pauseStatus: " + pauseStatus);
  1302. if( pauseStatus )
  1303. {
  1304. if( _pauseCaptureOnAppPause )
  1305. {
  1306. if( IsCapturing() )
  1307. {
  1308. _wasCapturingOnPause = true;
  1309. #if !UNITY_IPHONE
  1310. PauseCapture();
  1311. #endif
  1312. Debug.Log("OnApplicationPause: pausing video capture");
  1313. }
  1314. }
  1315. }
  1316. else
  1317. {
  1318. if( _pauseCaptureOnAppPause )
  1319. {
  1320. // Catch coming back from power off state when no lock screen
  1321. OnApplicationFocus( true );
  1322. }
  1323. }
  1324. #endif
  1325. }
  1326. #endif
  1327. protected void EncodeTexture(Texture2D texture)
  1328. {
  1329. Color32[] bytes = texture.GetPixels32();
  1330. GCHandle _frameHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
  1331. EncodePointer(_frameHandle.AddrOfPinnedObject());
  1332. if (_frameHandle.IsAllocated)
  1333. {
  1334. _frameHandle.Free();
  1335. }
  1336. }
  1337. protected bool IsUsingUnityAudioComponent()
  1338. {
  1339. if (_outputTarget == OutputTarget.VideoFile && _unityAudioCapture != null)
  1340. {
  1341. if (_audioCaptureSource == AudioCaptureSource.Unity
  1342. #if !AVPRO_MOVIECAPTURE_OFFLINE_AUDIOCAPTURE
  1343. && _isRealTime
  1344. #endif
  1345. )
  1346. {
  1347. return true;
  1348. }
  1349. #if AVPRO_MOVIECAPTURE_WWISE_SUPPORT
  1350. else if (_audioCaptureSource == AudioCaptureSource.Wwise && !_isRealTime)
  1351. {
  1352. return true;
  1353. }
  1354. #endif
  1355. }
  1356. return false;
  1357. }
  1358. protected bool IsUsingMotionBlur()
  1359. {
  1360. return (_useMotionBlur && !_isRealTime && _motionBlur != null);
  1361. }
  1362. public virtual void EncodePointer(System.IntPtr ptr)
  1363. {
  1364. if (!IsUsingUnityAudioComponent())
  1365. {
  1366. NativePlugin.EncodeFrame(_handle, ptr);
  1367. }
  1368. else
  1369. {
  1370. int audioDataLength = 0;
  1371. System.IntPtr audioDataPtr = _unityAudioCapture.ReadData(out audioDataLength);
  1372. if (audioDataLength > 0)
  1373. {
  1374. NativePlugin.EncodeFrameWithAudio(_handle, ptr, audioDataPtr, (uint)audioDataLength);
  1375. }
  1376. else
  1377. {
  1378. NativePlugin.EncodeFrame(_handle, ptr);
  1379. }
  1380. }
  1381. }
  1382. public bool IsPrepared()
  1383. {
  1384. return (_handle >= 0);
  1385. }
  1386. public bool IsCapturing()
  1387. {
  1388. return _capturing;
  1389. }
  1390. public bool IsPaused()
  1391. {
  1392. return _paused;
  1393. }
  1394. public int GetRecordingWidth()
  1395. {
  1396. return _targetWidth;
  1397. }
  1398. public int GetRecordingHeight()
  1399. {
  1400. return _targetHeight;
  1401. }
  1402. protected virtual string GenerateTimestampedFilename(string filenamePrefix, string filenameExtension)
  1403. {
  1404. // TimeSpan span = (DateTime.Now - DateTime.Now.Date);
  1405. // string filename = string.Format("{0}-{1}-{2}-{3}-{4}s-{5}x{6}", filenamePrefix, DateTime.Now.Year, DateTime.Now.Month.ToString("D2"), DateTime.Now.Day.ToString("D2"), ((int)(span.TotalSeconds)).ToString(), _targetWidth, _targetHeight);
  1406. // [MOZ] Use actual time in place of seconds
  1407. string dateTime = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
  1408. string filename = filenamePrefix;// string.Format("{0}_{1}_{2}x{3}", filenamePrefix, dateTime, _targetWidth, _targetHeight);
  1409. // [MOZ] File extension is now optional
  1410. if (!string.IsNullOrEmpty(filenameExtension))
  1411. {
  1412. filename = filename + "." + filenameExtension;
  1413. }
  1414. return filename;
  1415. }
  1416. #if UNITY_ANDROID && !UNITY_EDITOR
  1417. private static string GetAndroidExternalDCIMStoragePath()
  1418. {
  1419. if( Application.platform != RuntimePlatform.Android )
  1420. {
  1421. return Application.persistentDataPath;
  1422. }
  1423. var jClass = new AndroidJavaClass("android.os.Environment");
  1424. var dcimPath = jClass.CallStatic<AndroidJavaObject>("getExternalStoragePublicDirectory", jClass.GetStatic<string>("DIRECTORY_DCIM")).Call<string>("getAbsolutePath");
  1425. return dcimPath;
  1426. }
  1427. #endif
  1428. private static string GetFolder(OutputPath outputPathType, string path)
  1429. {
  1430. string folder = string.Empty;
  1431. #if UNITY_IOS && !UNITY_EDITOR
  1432. // iOS only supports a very limited subset of OutputPath so fix up and warn the user
  1433. switch (outputPathType)
  1434. {
  1435. case OutputPath.RelativeToPeristentData:
  1436. case OutputPath.PhotoLibrary:
  1437. // These are fine
  1438. break;
  1439. case OutputPath.RelativeToProject:
  1440. case OutputPath.Absolute:
  1441. case OutputPath.RelativeToDesktop:
  1442. case OutputPath.RelativeToVideos:
  1443. case OutputPath.RelativeToPictures:
  1444. case OutputPath.RelativeToTemporaryCachePath:
  1445. // These are unsupported
  1446. default:
  1447. Debug.LogWarning(string.Format("[AVProMovieCapture] 'OutputPath.{0}' is not supported on iOS, defaulting to 'OutputPath.RelativeToPeristentData'", outputPathType));
  1448. outputPathType = OutputPath.RelativeToPeristentData;
  1449. break;
  1450. }
  1451. #endif
  1452. #if UNITY_ANDROID && !UNITY_EDITOR
  1453. // Android only supports a very limited subset of OutputPath so fix up and warn the user
  1454. switch (outputPathType)
  1455. {
  1456. case OutputPath.RelativeToPeristentData:
  1457. case OutputPath.RelativeToVideos:
  1458. case OutputPath.Absolute:
  1459. case OutputPath.RelativeToTemporaryCachePath:
  1460. // These are fine
  1461. break;
  1462. case OutputPath.RelativeToProject:
  1463. case OutputPath.RelativeToDesktop:
  1464. case OutputPath.RelativeToPictures:
  1465. case OutputPath.PhotoLibrary:
  1466. // These are unsupported
  1467. default:
  1468. Debug.LogWarning(string.Format("[AVProMovieCapture] 'OutputPath.{0}' is not supported on Android, defaulting to 'OutputPath.RelativeToPeristentData'", outputPathType));
  1469. outputPathType = OutputPath.RelativeToPeristentData;
  1470. break;
  1471. }
  1472. #endif
  1473. #if UNITY_EDITOR
  1474. // Photo Library is unavailable in the editor
  1475. if (outputPathType == OutputPath.PhotoLibrary)
  1476. {
  1477. Debug.LogWarning("[AVProMovieCapture] 'OutputPath.PhotoLibrary' is not available in the Unity Editor, defaulting to 'OutputPath.RelativeToProject'");
  1478. outputPathType = OutputPath.RelativeToProject;
  1479. }
  1480. #endif
  1481. switch (outputPathType)
  1482. {
  1483. case OutputPath.RelativeToProject:
  1484. #if UNITY_STANDALONE_OSX
  1485. // For standalone macOS builds this puts the path at the same level as the application bundle
  1486. folder = System.IO.Path.GetFullPath(System.IO.Path.Combine(Application.dataPath, "../.."));
  1487. #else
  1488. folder = System.IO.Path.GetFullPath(System.IO.Path.Combine(Application.dataPath, ".."));
  1489. #endif
  1490. break;
  1491. case OutputPath.RelativeToPeristentData:
  1492. folder = System.IO.Path.GetFullPath(Application.persistentDataPath);
  1493. break;
  1494. case OutputPath.Absolute:
  1495. break;
  1496. case OutputPath.RelativeToDesktop:
  1497. folder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.DesktopDirectory);
  1498. break;
  1499. case OutputPath.RelativeToPictures:
  1500. folder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyPictures);
  1501. break;
  1502. case OutputPath.RelativeToVideos:
  1503. #if UNITY_ANDROID && !UNITY_EDITOR
  1504. folder = GetAndroidExternalDCIMStoragePath();
  1505. #else
  1506. #if NET_4_6
  1507. folder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyVideos);
  1508. #else
  1509. folder = System.Environment.GetFolderPath((System.Environment.SpecialFolder)14); // Older Mono doesn't have MyVideos defined - but still works!
  1510. #endif
  1511. #endif
  1512. break;
  1513. case OutputPath.PhotoLibrary:
  1514. // use avpmc-photolibrary as the scheme
  1515. folder = "avpmc-photolibrary:///"; // Three slashes are good as we don't need the host component
  1516. break;
  1517. case OutputPath.RelativeToTemporaryCachePath:
  1518. folder = System.IO.Path.GetFullPath(Application.temporaryCachePath);
  1519. break;
  1520. }
  1521. return System.IO.Path.Combine(folder, path);
  1522. }
  1523. private static string GenerateFilePath(OutputPath outputPathType, string path, string filename)
  1524. {
  1525. // Resolve folder
  1526. string fileFolder = GetFolder(outputPathType, path);
  1527. // Combine path and filename
  1528. return System.IO.Path.Combine(fileFolder, filename);
  1529. }
  1530. protected static bool HasExtension(string path, string extension)
  1531. {
  1532. return path.ToLower().EndsWith(extension, StringComparison.OrdinalIgnoreCase);
  1533. }
  1534. protected void GenerateFilename()
  1535. {
  1536. string filename = string.Empty;
  1537. if (_outputTarget == OutputTarget.VideoFile)
  1538. {
  1539. if (!_allowManualFileExtension)
  1540. {
  1541. if (_selectedVideoCodec == null)
  1542. {
  1543. SelectVideoCodec();
  1544. SelectAudioCodec();
  1545. }
  1546. int videoCodec = (_selectedVideoCodec != null) ? _selectedVideoCodec.Index : -1;
  1547. int audioCodec = (_selectedAudioCodec != null) ? _selectedAudioCodec.Index : -1;
  1548. string[] extensions = NativePlugin.GetContainerFileExtensions(videoCodec, audioCodec);
  1549. if (extensions != null && extensions.Length > 0)
  1550. {
  1551. _filenameExtension = extensions[0];
  1552. }
  1553. }
  1554. if (_appendFilenameTimestamp)
  1555. {
  1556. filename = GenerateTimestampedFilename(_filenamePrefix, _filenameExtension);
  1557. }
  1558. else
  1559. {
  1560. filename = _filenamePrefix + "." + _filenameExtension;
  1561. }
  1562. }
  1563. else if (_outputTarget == OutputTarget.ImageSequence)
  1564. {
  1565. // [MOZ] Made the enclosing folder uniquely named, easier for extraction on iOS and simplifies scripts for processing the frames
  1566. string fileExtension = Utils.GetImageFileExtension(NativeImageSequenceFormat);
  1567. filename = GenerateTimestampedFilename(_filenamePrefix, null) + "/frame" + string.Format("-%0{0}d.{1}", _imageSequenceZeroDigits, fileExtension);
  1568. }
  1569. else if (_outputTarget == OutputTarget.NamedPipe)
  1570. {
  1571. _filePath = _namedPipePath;
  1572. }
  1573. if (_outputTarget == OutputTarget.VideoFile ||
  1574. _outputTarget == OutputTarget.ImageSequence)
  1575. {
  1576. OutputPath outputFolderType = _outputFolderType;
  1577. string outputFolderPath = _outputFolderPath;
  1578. _finalFilePath = null;
  1579. #if UNITY_ANDROID && !UNITY_EDITOR
  1580. if( _outputFolderType != OutputPath.RelativeToPeristentData && _outputTarget == OutputTarget.VideoFile )
  1581. {
  1582. // Where do we want to write the final file to?
  1583. _finalFilePath = GenerateFilePath(_outputFolderType, _outputFolderPath, filename);
  1584. // Capture to path relative to the project
  1585. outputFolderType = OutputPath.RelativeToPeristentData;
  1586. outputFolderPath = "Captures";
  1587. // Create target final directory if it doesn't exist
  1588. String finalDirectory = Path.GetDirectoryName(_finalFilePath);
  1589. Debug.Log("[AVProMovieCapture]: finalDirectory = " + finalDirectory);
  1590. if (!string.IsNullOrEmpty(finalDirectory) && !Directory.Exists(finalDirectory))
  1591. {
  1592. Directory.CreateDirectory(finalDirectory);
  1593. }
  1594. }
  1595. #endif
  1596. _filePath = GenerateFilePath(outputFolderType, outputFolderPath, filename);
  1597. // Check to see if this filename is already in use
  1598. if (ActiveFilePaths.Contains(_filePath))
  1599. {
  1600. // It is, strip the extension
  1601. string extension = Path.GetExtension(_filePath);
  1602. string name = Path.GetFileNameWithoutExtension(_filePath);
  1603. string path = Path.GetDirectoryName(_filePath);
  1604. string newPath = null;
  1605. int i = 2;
  1606. do
  1607. {
  1608. const string fmt = "{0} {1}";
  1609. string newName = String.Format(fmt, name, i++);
  1610. newPath = Path.Combine(path, newName);
  1611. newPath = Path.ChangeExtension(newPath, extension);
  1612. }
  1613. while (ActiveFilePaths.Contains(newPath));
  1614. _filePath = newPath;
  1615. }
  1616. ActiveFilePaths.Add(_filePath);
  1617. #if !UNITY_EDITOR && (UNITY_STANDALONE_OSX || UNITY_IOS)
  1618. if (_outputFolderType != OutputPath.PhotoLibrary)
  1619. #endif
  1620. {
  1621. // Create target directory if it doesn't exist
  1622. String directory = Path.GetDirectoryName(_filePath);
  1623. if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
  1624. {
  1625. Directory.CreateDirectory(directory);
  1626. }
  1627. }
  1628. }
  1629. }
  1630. public UnityAudioCapture FindOrCreateUnityAudioCapture(bool logWarnings)
  1631. {
  1632. UnityAudioCapture result = null;
  1633. if (_audioCaptureSource == AudioCaptureSource.Unity)
  1634. {
  1635. Type audioCaptureType = null;
  1636. if (_isRealTime)
  1637. {
  1638. audioCaptureType = typeof(CaptureAudioFromAudioListener);
  1639. }
  1640. else
  1641. {
  1642. #if AVPRO_MOVIECAPTURE_OFFLINE_AUDIOCAPTURE
  1643. audioCaptureType = typeof(CaptureAudioFromAudioRenderer);
  1644. #endif
  1645. }
  1646. if (audioCaptureType != null)
  1647. {
  1648. // Try to find an existing matching component locally
  1649. result = (UnityAudioCapture)this.GetComponent(audioCaptureType);
  1650. if (result == null)
  1651. {
  1652. // Try to find an existing matching component globally
  1653. result = (UnityAudioCapture)GameObject.FindObjectOfType(audioCaptureType);
  1654. }
  1655. // No existing component was found, so create one
  1656. if (result == null)
  1657. {
  1658. // Find a suitable gameobject to add the component to
  1659. GameObject parentGameObject = null;
  1660. if (_isRealTime)
  1661. {
  1662. // Find an AudioListener to attach the UnityAudioCapture component to
  1663. AudioListener audioListener = this.GetComponent<AudioListener>();
  1664. if (audioListener == null)
  1665. {
  1666. audioListener = GameObject.FindObjectOfType<AudioListener>();
  1667. }
  1668. parentGameObject = audioListener.gameObject;
  1669. }
  1670. else
  1671. {
  1672. parentGameObject = this.gameObject;
  1673. }
  1674. // Create the component
  1675. if (_isRealTime)
  1676. {
  1677. if (parentGameObject != null)
  1678. {
  1679. result = (UnityAudioCapture)parentGameObject.AddComponent(audioCaptureType);
  1680. if (logWarnings)
  1681. {
  1682. Debug.LogWarning("[AVProMovieCapture] Capturing audio from Unity without an UnityAudioCapture assigned so we had to create one manually (very slow). Consider adding a UnityAudioCapture component to your scene and assigned it to this MovieCapture component.");
  1683. }
  1684. }
  1685. else
  1686. {
  1687. if (logWarnings)
  1688. {
  1689. Debug.LogWarning("[AVProMovieCapture] No AudioListener found in scene. Unable to capture audio from Unity.");
  1690. }
  1691. }
  1692. }
  1693. else
  1694. {
  1695. #if AVPRO_MOVIECAPTURE_OFFLINE_AUDIOCAPTURE
  1696. result = (UnityAudioCapture)parentGameObject.AddComponent(audioCaptureType);
  1697. ((CaptureAudioFromAudioRenderer)result).Capture = this;
  1698. if (logWarnings)
  1699. {
  1700. Debug.LogWarning("[AVProMovieCapture] Capturing audio from Unity without an UnityAudioCapture assigned so we had to create one manually (very slow). Consider adding a UnityAudioCapture component to your scene and assigned it to this MovieCapture component.");
  1701. }
  1702. #endif
  1703. }
  1704. }
  1705. else
  1706. {
  1707. if (logWarnings)
  1708. {
  1709. Debug.LogWarning("[AVProMovieCapture] Capturing audio from Unity without an UnityAudioCapture assigned so we had to search for one manually (very slow)");
  1710. }
  1711. }
  1712. }
  1713. }
  1714. #if AVPRO_MOVIECAPTURE_WWISE_SUPPORT
  1715. else if (_audioCaptureSource == AudioCaptureSource.Wwise)
  1716. {
  1717. Type audioCaptureType = null;
  1718. if (!_isRealTime)
  1719. {
  1720. audioCaptureType = typeof(CaptureAudioFromWwise);
  1721. }
  1722. if (audioCaptureType != null)
  1723. {
  1724. // Try to find an existing matching component locally
  1725. result = (UnityAudioCapture)this.GetComponent(audioCaptureType);
  1726. if (result == null)
  1727. {
  1728. // Try to find an existing matching component globally
  1729. result = (UnityAudioCapture)GameObject.FindObjectOfType(audioCaptureType);
  1730. }
  1731. // No existing component was found, so create one
  1732. if (result == null)
  1733. {
  1734. result = (UnityAudioCapture)this.gameObject.AddComponent(audioCaptureType);
  1735. }
  1736. if (result)
  1737. {
  1738. ((CaptureAudioFromWwise)result).Capture = this;
  1739. }
  1740. }
  1741. }
  1742. #endif // AVPRO_MOVIECAPTURE_WWISE_SUPPORT
  1743. return result;
  1744. }
  1745. private bool ValidateEditionFeatures()
  1746. {
  1747. bool canContinueCapture = true;
  1748. if (NativePlugin.IsBasicEdition())
  1749. {
  1750. string issues = string.Empty;
  1751. // Abortable issues
  1752. if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D12)
  1753. {
  1754. issues += "• D3D12 is not supported, please switch to D3D11. Aborting capture.\n";
  1755. canContinueCapture = false;
  1756. }
  1757. if (this is CaptureFromCamera || this is CaptureFromTexture || this is CaptureFromCamera360 || this is CaptureFromCamera360ODS)
  1758. {
  1759. issues += "• Only CaptureFromScreen component is supported. Aborting capture.\n";
  1760. canContinueCapture = false;
  1761. }
  1762. // Continuable issues
  1763. if (canContinueCapture)
  1764. {
  1765. if (SelectedVideoCodec != null && SelectedVideoCodec.Name.IndexOf("H264") < 0)
  1766. {
  1767. issues += "• Only H264 video codec supported. Switching to H264\n";
  1768. NativeForceVideoCodecIndex = 0;
  1769. SelectVideoCodec(false);
  1770. }
  1771. if (SelectedAudioCodec != null && SelectedAudioCodec.Name.IndexOf("AAC") < 0)
  1772. {
  1773. issues += "• Only AAC audio codec supported. Switching to AAC\n";
  1774. NativeForceAudioCodecIndex = 0;
  1775. SelectAudioCodec();
  1776. }
  1777. if (!IsRealTime)
  1778. {
  1779. issues += "• Non-realtime captures are not supported. Switching to realtime capture mode.\n";
  1780. IsRealTime = true;
  1781. }
  1782. if (OutputTarget != OutputTarget.VideoFile)
  1783. {
  1784. issues += "• Only output to video file is supported. Switching to video file output.\n";
  1785. OutputTarget = OutputTarget.VideoFile;
  1786. FilenameExtension = "mp4";
  1787. GenerateFilename();
  1788. }
  1789. if (AudioCaptureSource != AudioCaptureSource.None && AudioCaptureSource != AudioCaptureSource.Unity)
  1790. {
  1791. issues += "• Audio source '" + AudioCaptureSource + "' not supported. Disabling audio capture.\n";
  1792. AudioCaptureSource = AudioCaptureSource.None;
  1793. }
  1794. if (FrameRate != 30f)
  1795. {
  1796. issues += "• Frame rate '" + FrameRate + "' not supported. Switching to 30 FPS.\n";
  1797. FrameRate = 30f;
  1798. }
  1799. if (GetEncoderHints().videoHints.supportTransparency || GetEncoderHints().videoHints.transparency != Transparency.None)
  1800. {
  1801. issues += "• Transparent capture not supported. Disabling transparent capture\n";
  1802. GetEncoderHints().videoHints.supportTransparency = false;
  1803. GetEncoderHints().videoHints.transparency = Transparency.None;
  1804. _Transparency = Transparency.None;
  1805. }
  1806. }
  1807. // Log/Display issues
  1808. if (!string.IsNullOrEmpty(issues))
  1809. {
  1810. string message = "Limitations of Basic Edition reached:\n" + issues + "Please upgrade to use these feature, or visit '" + DocEditionsURL + "' for more information.";
  1811. if (canContinueCapture)
  1812. {
  1813. Debug.LogWarning("[AVProMovieCapture] " + message);
  1814. }
  1815. else
  1816. {
  1817. Debug.LogError("[AVProMovieCapture] " + message);
  1818. }
  1819. #if UNITY_EDITOR
  1820. message = "Limitations of Basic Edition reached:\n\n" + issues + "\nPlease upgrade to use these feature. View documention for more information.";
  1821. if (!UnityEditor.EditorUtility.DisplayDialog("AVPro Movie Capture", message, "Ok", "More Info"))
  1822. {
  1823. Application.OpenURL(DocEditionsURL);
  1824. }
  1825. #endif
  1826. }
  1827. }
  1828. return canContinueCapture;
  1829. }
  1830. public virtual bool PrepareCapture()
  1831. {
  1832. if (!ValidateEditionFeatures())
  1833. {
  1834. return false;
  1835. }
  1836. // Delete file if it already exists
  1837. if (_outputTarget == OutputTarget.VideoFile && File.Exists(_filePath))
  1838. {
  1839. File.Delete(_filePath);
  1840. }
  1841. _stats = new CaptureStats();
  1842. #if UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)
  1843. if (_minimumDiskSpaceMB > 0 && _outputTarget == OutputTarget.VideoFile)
  1844. {
  1845. ulong freespace = 0;
  1846. if (Utils.DriveFreeBytes(System.IO.Path.GetPathRoot(_filePath), out freespace))
  1847. {
  1848. _freeDiskSpaceMB = (long)(freespace / (1024 * 1024));
  1849. }
  1850. if (!IsEnoughDiskSpace())
  1851. {
  1852. Debug.LogError("[AVProMovieCapture] Not enough free space to start capture. Stopping capture.");
  1853. return false;
  1854. }
  1855. }
  1856. #endif
  1857. if (_isRealTime)
  1858. {
  1859. /*if (_allowFrameRateChange)
  1860. {
  1861. _oldTargetFrameRate = Application.targetFrameRate;
  1862. Application.targetFrameRate = (int)_frameRate;
  1863. }*/
  1864. }
  1865. else
  1866. {
  1867. // Disable vsync
  1868. #if !UNITY_EDITOR && (UNITY_IOS || UNITY_ANDROID)
  1869. if (_allowVSyncDisable)
  1870. {
  1871. // iOS and Android do not support disabling vsync so use _oldVsyncCount to store the current target framerate.
  1872. _oldVSyncCount = Application.targetFrameRate;
  1873. // We want to runs as fast as possible.
  1874. Application.targetFrameRate = Screen.currentResolution.refreshRate;
  1875. }
  1876. #else
  1877. if (_allowVSyncDisable && !Screen.fullScreen && QualitySettings.vSyncCount > 0)
  1878. {
  1879. _oldVSyncCount = QualitySettings.vSyncCount;
  1880. QualitySettings.vSyncCount = 0;
  1881. }
  1882. #endif
  1883. if (_useMotionBlur && _motionBlurSamples > 1)
  1884. {
  1885. #if AVPRO_MOVIECAPTURE_CAPTUREDELTA_SUPPORT
  1886. Time.captureDeltaTime = 1f / (_motionBlurSamples * _frameRate);
  1887. #else
  1888. Time.captureFramerate = (int)(_motionBlurSamples * _frameRate);
  1889. #endif
  1890. // FromTexture and FromCamera360 captures don't require a camera for rendering, so set up the motion blur component differently
  1891. if (this is CaptureFromTexture || this is CaptureFromCamera360 || this is CaptureFromCamera360ODS)
  1892. {
  1893. if (_motionBlur == null)
  1894. {
  1895. _motionBlur = this.GetComponent<MotionBlur>();
  1896. }
  1897. if (_motionBlur == null)
  1898. {
  1899. _motionBlur = this.gameObject.AddComponent<MotionBlur>();
  1900. }
  1901. if (_motionBlur != null)
  1902. {
  1903. _motionBlur.NumSamples = _motionBlurSamples;
  1904. _motionBlur.SetTargetSize(_targetWidth, _targetHeight);
  1905. _motionBlur.enabled = false;
  1906. }
  1907. }
  1908. // FromCamera and FromScreen use this path
  1909. else if (_motionBlurCameras.Length > 0)
  1910. {
  1911. // Setup the motion blur filters where cameras are used
  1912. foreach (Camera camera in _motionBlurCameras)
  1913. {
  1914. MotionBlur mb = camera.GetComponent<MotionBlur>();
  1915. if (mb == null)
  1916. {
  1917. mb = camera.gameObject.AddComponent<MotionBlur>();
  1918. }
  1919. if (mb != null)
  1920. {
  1921. mb.NumSamples = _motionBlurSamples;
  1922. mb.enabled = true;
  1923. _motionBlur = mb;
  1924. }
  1925. }
  1926. }
  1927. }
  1928. else
  1929. {
  1930. #if AVPRO_MOVIECAPTURE_CAPTUREDELTA_SUPPORT
  1931. Time.captureDeltaTime = 1f / _frameRate;
  1932. #else
  1933. Time.captureFramerate = (int)_frameRate;
  1934. #endif
  1935. }
  1936. // Change physics update speed
  1937. _oldFixedDeltaTime = Time.fixedDeltaTime;
  1938. #if AVPRO_MOVIECAPTURE_CAPTUREDELTA_SUPPORT
  1939. Time.fixedDeltaTime = Time.captureDeltaTime;
  1940. #else
  1941. Time.fixedDeltaTime = 1.0f / Time.captureFramerate;
  1942. #endif
  1943. }
  1944. // Resolve desired audio source
  1945. _stats.AudioCaptureSource = AudioCaptureSource.None;
  1946. if (_audioCaptureSource != AudioCaptureSource.None && _outputTarget == OutputTarget.VideoFile)
  1947. {
  1948. if (_audioCaptureSource == AudioCaptureSource.Microphone && _isRealTime)
  1949. {
  1950. if (_selectedAudioInputDevice != null)
  1951. {
  1952. _stats.AudioCaptureSource = AudioCaptureSource.Microphone;
  1953. }
  1954. else
  1955. {
  1956. Debug.LogWarning("[AVProMovieCapture] No microphone found");
  1957. }
  1958. }
  1959. else if (_audioCaptureSource == AudioCaptureSource.Unity || _audioCaptureSource == AudioCaptureSource.Wwise)
  1960. {
  1961. // If there is already a capture component, make sure it's the right one otherwise remove it
  1962. if (_unityAudioCapture != null)
  1963. {
  1964. bool removeComponent = false;
  1965. if (_audioCaptureSource == AudioCaptureSource.Unity)
  1966. {
  1967. if (_isRealTime)
  1968. {
  1969. removeComponent = !(_unityAudioCapture is CaptureAudioFromAudioListener);
  1970. }
  1971. else
  1972. {
  1973. removeComponent = (_unityAudioCapture is CaptureAudioFromAudioListener);
  1974. }
  1975. }
  1976. else if (_audioCaptureSource == AudioCaptureSource.Wwise)
  1977. {
  1978. #if !AVPRO_MOVIECAPTURE_WWISE_SUPPORT
  1979. removeComponent = true;
  1980. #else
  1981. if (_isRealTime)
  1982. {
  1983. removeComponent = true;
  1984. }
  1985. else
  1986. {
  1987. removeComponent = !(_unityAudioCapture is CaptureAudioFromWwise);
  1988. }
  1989. #endif
  1990. }
  1991. if (removeComponent)
  1992. {
  1993. Destroy(_unityAudioCapture);
  1994. _unityAudioCapture = null;
  1995. }
  1996. }
  1997. // We if try to capture audio from Unity but there isn't an UnityAudioCapture component set
  1998. if (_unityAudioCapture == null)
  1999. {
  2000. _unityAudioCapture = FindOrCreateUnityAudioCapture(true);
  2001. }
  2002. if (_unityAudioCapture != null)
  2003. {
  2004. _unityAudioCapture.PrepareCapture();
  2005. _stats.UnityAudioSampleRate = _unityAudioCapture.SampleRate;
  2006. _stats.UnityAudioChannelCount = _unityAudioCapture.ChannelCount;
  2007. _stats.AudioCaptureSource = _audioCaptureSource;
  2008. }
  2009. else
  2010. {
  2011. Debug.LogWarning("[AVProMovieCapture] Unable to create AudioCapture component in mode " + _audioCaptureSource.ToString());
  2012. }
  2013. }
  2014. else if (_audioCaptureSource == AudioCaptureSource.UnityAudioMixer)
  2015. {
  2016. _stats.UnityAudioSampleRate = AudioSettings.outputSampleRate;
  2017. _stats.UnityAudioChannelCount = UnityAudioCapture.GetUnityAudioChannelCount();
  2018. _stats.AudioCaptureSource = _audioCaptureSource;
  2019. }
  2020. else if (_audioCaptureSource == AudioCaptureSource.Manual)
  2021. {
  2022. _stats.UnityAudioSampleRate = _manualAudioSampleRate;
  2023. _stats.UnityAudioChannelCount = _manualAudioChannelCount;
  2024. _stats.AudioCaptureSource = AudioCaptureSource.Manual;
  2025. }
  2026. }
  2027. if (_selectedVideoCodec == null) return false;
  2028. string info = string.Empty;
  2029. if (_logCaptureStartStop)
  2030. {
  2031. info = string.Format("{0}x{1} @ {2}fps [{3}]", _targetWidth, _targetHeight, _frameRate.ToString("F2"), _pixelFormat.ToString());
  2032. if (_outputTarget == OutputTarget.VideoFile)
  2033. {
  2034. info += string.Format(" vcodec:'{0}'", _selectedVideoCodec.Name);
  2035. if (_stats.AudioCaptureSource != AudioCaptureSource.None)
  2036. {
  2037. if (_audioCaptureSource == AudioCaptureSource.Microphone && _selectedAudioInputDevice != null)
  2038. {
  2039. info += string.Format(" audio source:'{0}'", _selectedAudioInputDevice.Name);
  2040. }
  2041. else if (_audioCaptureSource == AudioCaptureSource.Unity && _unityAudioCapture != null)
  2042. {
  2043. info += string.Format(" audio source:'Unity' {0}hz {1} channels", _stats.UnityAudioSampleRate, _stats.UnityAudioChannelCount);
  2044. }
  2045. else if (_audioCaptureSource == AudioCaptureSource.UnityAudioMixer)
  2046. {
  2047. info += string.Format(" audio source:'AudioMixer' {0}hz {1} channels", _stats.UnityAudioSampleRate, _stats.UnityAudioChannelCount);
  2048. }
  2049. else if (_audioCaptureSource == AudioCaptureSource.Manual)
  2050. {
  2051. info += string.Format(" audio source:'Manual' {0}hz {1} channels", _stats.UnityAudioSampleRate, _stats.UnityAudioChannelCount);
  2052. }
  2053. else if (_audioCaptureSource == AudioCaptureSource.Wwise && _unityAudioCapture != null)
  2054. {
  2055. info += string.Format(" audio source:'Wwise' {0}hz {1} channels", _stats.UnityAudioSampleRate, _stats.UnityAudioChannelCount);
  2056. }
  2057. if (_selectedAudioCodec != null)
  2058. {
  2059. info += string.Format(" acodec:'{0}'", _selectedAudioCodec.Name);
  2060. }
  2061. }
  2062. info += string.Format(" to file: '{0}'", _filePath);
  2063. }
  2064. else if (_outputTarget == OutputTarget.ImageSequence)
  2065. {
  2066. info += string.Format(" to file: '{0}'", _filePath);
  2067. }
  2068. else if (_outputTarget == OutputTarget.NamedPipe)
  2069. {
  2070. info += string.Format(" to pipe: '{0}'", _filePath);
  2071. }
  2072. }
  2073. if (_outputTarget == OutputTarget.VideoFile)
  2074. {
  2075. if (_logCaptureStartStop)
  2076. {
  2077. Debug.Log("[AVProMovieCapture] Start File Capture: " + info);
  2078. }
  2079. bool useRealtimeClock = (_isRealTime && _timelapseScale <= 1);
  2080. AudioCaptureSource audioCaptureSource = _stats.AudioCaptureSource;
  2081. if (audioCaptureSource == AudioCaptureSource.Wwise) { audioCaptureSource = AudioCaptureSource.Unity; } // This is a mild hack until we rebuild the plugins
  2082. VideoEncoderHints hints = GetEncoderHints().videoHints;
  2083. hints.colourSpace = (VideoEncoderHints.ColourSpace)QualitySettings.activeColorSpace;
  2084. hints.sourceWidth = _sourceWidth;
  2085. hints.sourceHeight = _sourceHeight;
  2086. // Android only
  2087. hints.androidNoCaptureRotation = _androidNoCaptureRotation;
  2088. hints.supportTransparency = (hints.transparency == Transparency.Codec);
  2089. _handle = NativePlugin.CreateRecorderVideo(
  2090. _filePath,
  2091. (uint)_targetWidth,
  2092. (uint)_targetHeight,
  2093. _frameRate,
  2094. (int)_pixelFormat,
  2095. useRealtimeClock,
  2096. #if !UNITY_EDITOR && UNITY_ANDROID
  2097. _flipVertically,
  2098. #else
  2099. _flipVertically ? !_isTopDown : _isTopDown,
  2100. #endif
  2101. _selectedVideoCodec.Index,
  2102. audioCaptureSource,
  2103. _stats.UnityAudioSampleRate,
  2104. _stats.UnityAudioChannelCount,
  2105. (_selectedAudioInputDevice != null) ? _selectedAudioInputDevice.Index : -1,
  2106. (_selectedAudioCodec != null) ? _selectedAudioCodec.Index : -1,
  2107. _forceGpuFlush,
  2108. hints);
  2109. }
  2110. else if (_outputTarget == OutputTarget.ImageSequence)
  2111. {
  2112. if (_logCaptureStartStop)
  2113. {
  2114. Debug.Log("[AVProMovieCapture] Start Images Capture: " + info);
  2115. }
  2116. bool useRealtimeClock = (_isRealTime && _timelapseScale <= 1);
  2117. ImageEncoderHints hints = GetEncoderHints().imageHints;
  2118. hints.colourSpace = (ImageEncoderHints.ColourSpace)QualitySettings.activeColorSpace;
  2119. hints.sourceWidth = _sourceWidth;
  2120. hints.sourceHeight = _sourceHeight;
  2121. hints.supportTransparency = ( hints.transparency == Transparency.Codec );
  2122. _handle = NativePlugin.CreateRecorderImages(
  2123. _filePath,
  2124. (uint)_targetWidth,
  2125. (uint)_targetHeight,
  2126. _frameRate,
  2127. (int)_pixelFormat,
  2128. useRealtimeClock,
  2129. _isTopDown,
  2130. (int)NativeImageSequenceFormat,
  2131. _forceGpuFlush,
  2132. _imageSequenceStartFrame,
  2133. hints);
  2134. }
  2135. else if (_outputTarget == OutputTarget.NamedPipe)
  2136. {
  2137. if (_logCaptureStartStop)
  2138. {
  2139. Debug.Log("[AVProMovieCapture] Start Pipe Capture: " + info);
  2140. }
  2141. _handle = NativePlugin.CreateRecorderPipe(_filePath, (uint)_targetWidth, (uint)_targetHeight, _frameRate,
  2142. (int)_pixelFormat, _isTopDown,
  2143. /*GetEncoderHints().videoHints.supportTransparency*//*(GetEncoderHints().videoHints.transparency == Transparency.Codec)*/(int)(GetEncoderHints().videoHints.transparency),
  2144. _forceGpuFlush);
  2145. }
  2146. if (_handle >= 0)
  2147. {
  2148. RenderThreadEvent(NativePlugin.PluginEvent.Setup);
  2149. }
  2150. else
  2151. {
  2152. Debug.LogError("[AVProMovieCapture] Failed to create recorder");
  2153. // Try to give a reason why it failed
  2154. #if UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)
  2155. if (_selectedVideoCodec.MediaApi == MediaApi.MediaFoundation)
  2156. {
  2157. if (!HasExtension(_filePath, ".mp4"))
  2158. {
  2159. Debug.LogError("[AVProMovieCapture] When using a MediaFoundation codec the MP4 extension must be used");
  2160. }
  2161. // MF H.264 encoder has a limit of Level 5.2 which is 9,437,184 luma pixels
  2162. // but we've seen it fail slightly below this limit, so we test against 9360000
  2163. // to offer a useful potential error message
  2164. if (((_targetWidth * _targetHeight) >= 9360000) && _selectedVideoCodec.Name.Contains("H264"))
  2165. {
  2166. Debug.LogError("[AVProMovieCapture] Resolution is possibly too high for the MF H.264 codec");
  2167. }
  2168. }
  2169. else if (_selectedVideoCodec.MediaApi == MediaApi.DirectShow)
  2170. {
  2171. if (HasExtension(_filePath, ".mp4") && _selectedVideoCodec.Name.Contains("Uncompressed"))
  2172. {
  2173. Debug.LogError("[AVProMovieCapture] Uncompressed video codec not supported with MP4 extension, use AVI instead for uncompressed");
  2174. }
  2175. }
  2176. #endif
  2177. StopCapture();
  2178. }
  2179. // (mac|i)OS only for now
  2180. #if UNITY_EDITOR_OSX || (!UNITY_EDITOR && (UNITY_STANDALONE_OSX || UNITY_IOS))
  2181. SetupErrorHandler();
  2182. #endif
  2183. return (_handle >= 0);
  2184. }
  2185. #if UNITY_EDITOR_OSX || (!UNITY_EDITOR && (UNITY_STANDALONE_OSX || UNITY_IOS))
  2186. static Dictionary<int, CaptureBase> _HandleToCaptureMap = new Dictionary<int, CaptureBase>();
  2187. private void SetupErrorHandler()
  2188. {
  2189. NativePlugin.ErrorHandlerDelegate errorHandlerDelegate = new NativePlugin.ErrorHandlerDelegate(ErrorHandler);
  2190. System.IntPtr func = Marshal.GetFunctionPointerForDelegate(errorHandlerDelegate);
  2191. NativePlugin.SetErrorHandler(_handle, func);
  2192. _HandleToCaptureMap.Add(_handle, this);
  2193. }
  2194. private void CleanupErrorHandler()
  2195. {
  2196. _HandleToCaptureMap.Remove(_handle);
  2197. }
  2198. #if ENABLE_IL2CPP
  2199. [MonoPInvokeCallback(typeof(NativePlugin.ErrorHandlerDelegate))]
  2200. #endif
  2201. private static void ErrorHandler(int handle, int domain, int code, string message)
  2202. {
  2203. CaptureBase capture;
  2204. if (_HandleToCaptureMap.TryGetValue(handle, out capture))
  2205. {
  2206. capture.ActualErrorHandler(domain, code, message);
  2207. }
  2208. }
  2209. private void ActualErrorHandler(int domain, int code, string message) {
  2210. if (_capturing)
  2211. {
  2212. CancelCapture();
  2213. Debug.LogError("Capture cancelled");
  2214. }
  2215. Debug.LogErrorFormat("Error: domain: {0}, code: {1}, message: {2}", domain, code, message);
  2216. }
  2217. #endif
  2218. public void QueueStartCapture()
  2219. {
  2220. _queuedStartCapture = true;
  2221. _stats = new CaptureStats();
  2222. }
  2223. public bool IsStartCaptureQueued()
  2224. {
  2225. return _queuedStartCapture;
  2226. }
  2227. protected void UpdateInjectionOptions(StereoPacking stereoPacking, SphericalVideoLayout sphericalVideoLayout)
  2228. {
  2229. VideoEncoderHints videoHints = GetEncoderHints().videoHints;
  2230. if (videoHints.injectStereoPacking == NoneAutoCustom.Auto) { videoHints.stereoPacking = stereoPacking; }
  2231. if (videoHints.injectSphericalVideoLayout == NoneAutoCustom.Auto) { videoHints.sphericalVideoLayout = sphericalVideoLayout; }
  2232. }
  2233. public bool StartCapture()
  2234. {
  2235. if (_capturing)
  2236. {
  2237. return false;
  2238. }
  2239. if (_waitForEndOfFrame == null)
  2240. {
  2241. // Start() hasn't happened yet, so queue the StartCapture
  2242. QueueStartCapture();
  2243. return false;
  2244. }
  2245. if (_handle < 0)
  2246. {
  2247. if (!PrepareCapture())
  2248. {
  2249. return false;
  2250. }
  2251. }
  2252. if (_handle >= 0)
  2253. {
  2254. if (IsUsingUnityAudioComponent())
  2255. {
  2256. _unityAudioCapture.StartCapture();
  2257. }
  2258. // Set limit to number of frames encoded (or 0 for unlimited)
  2259. {
  2260. uint frameLimit = 0;
  2261. if (_stopMode == StopMode.FramesEncoded)
  2262. {
  2263. frameLimit = (uint)_stopFrames;
  2264. }
  2265. else if (_stopMode == StopMode.SecondsEncoded && !_isRealTime)
  2266. {
  2267. frameLimit = (uint)Mathf.FloorToInt(_stopSeconds * _frameRate);
  2268. }
  2269. NativePlugin.SetEncodedFrameLimit(_handle, frameLimit);
  2270. }
  2271. if (!NativePlugin.Start(_handle))
  2272. {
  2273. StopCapture(true);
  2274. Debug.LogError("[AVProMovieCapture] Failed to start recorder");
  2275. return false;
  2276. }
  2277. ResetFPS();
  2278. _captureStartTime = Time.realtimeSinceStartup;
  2279. _capturePrePauseTotalTime = 0f;
  2280. // NOTE: We set this to the elapsed time so that the first frame is captured immediately
  2281. _timeSinceLastFrame = GetSecondsPerCaptureFrame();
  2282. #if AVPRO_MOVIECAPTURE_PLAYABLES_SUPPORT
  2283. if (!_isRealTime && _timelineController != null)
  2284. {
  2285. _timelineController.StartCapture();
  2286. }
  2287. #endif
  2288. #if AVPRO_MOVIECAPTURE_VIDEOPLAYER_SUPPORT
  2289. if (!_isRealTime && _videoPlayerController != null)
  2290. {
  2291. _videoPlayerController.StartCapture();
  2292. }
  2293. #endif
  2294. _capturing = true;
  2295. _paused = false;
  2296. if (_startDelay != StartDelayMode.None)
  2297. {
  2298. _startDelayTimer = 0f;
  2299. _startPaused = true;
  2300. PauseCapture();
  2301. }
  2302. #if UNITY_EDITOR
  2303. if (UnityEditor.EditorApplication.isPaused)
  2304. {
  2305. PauseCapture();
  2306. }
  2307. #endif
  2308. }
  2309. return _capturing;
  2310. }
  2311. public void PauseCapture()
  2312. {
  2313. if (_capturing && !_paused)
  2314. {
  2315. if (IsUsingUnityAudioComponent())
  2316. {
  2317. _unityAudioCapture.enabled = false;
  2318. }
  2319. NativePlugin.Pause(_handle);
  2320. if (!_isRealTime)
  2321. {
  2322. // TODO: should be store the timeScale value and restore it instead of assuming timeScale == 1.0?
  2323. Time.timeScale = 0f;
  2324. }
  2325. _paused = true;
  2326. ResetFPS();
  2327. }
  2328. }
  2329. public void ResumeCapture()
  2330. {
  2331. if (_capturing && _paused)
  2332. {
  2333. if (IsUsingUnityAudioComponent())
  2334. {
  2335. _unityAudioCapture.FlushBuffer();
  2336. _unityAudioCapture.enabled = true;
  2337. }
  2338. NativePlugin.Start(_handle);
  2339. if (!_isRealTime)
  2340. {
  2341. Time.timeScale = 1f;
  2342. }
  2343. _paused = false;
  2344. if (_startPaused)
  2345. {
  2346. _captureStartTime = Time.realtimeSinceStartup;
  2347. _capturePrePauseTotalTime = 0f;
  2348. _startPaused = false;
  2349. }
  2350. }
  2351. }
  2352. public void CancelCapture()
  2353. {
  2354. StopCapture(true, false, true);
  2355. }
  2356. public static void DeleteCapture(OutputTarget outputTarget, string path)
  2357. {
  2358. try
  2359. {
  2360. if (outputTarget == OutputTarget.VideoFile && File.Exists(path))
  2361. {
  2362. File.Delete(path);
  2363. }
  2364. else if (outputTarget == OutputTarget.ImageSequence)
  2365. {
  2366. string directory = Path.GetDirectoryName(path);
  2367. if (Directory.Exists(directory))
  2368. {
  2369. Directory.Delete(directory, true);
  2370. }
  2371. }
  2372. }
  2373. catch (Exception ex)
  2374. {
  2375. Debug.LogWarning("[AVProMovieCapture] Failed to delete capture - " + ex.Message);
  2376. }
  2377. }
  2378. public virtual void UnprepareCapture()
  2379. {
  2380. #if UNITY_EDITOR_OSX || (!UNITY_EDITOR && (UNITY_STANDALONE_OSX || UNITY_IOS))
  2381. CleanupErrorHandler();
  2382. #endif
  2383. }
  2384. public static string LastFileSaved
  2385. {
  2386. get
  2387. {
  2388. #if UNITY_EDITOR
  2389. return UnityEditor.EditorPrefs.GetString("AVProMovieCapture-LastSavedFile", string.Empty);
  2390. #else
  2391. return PlayerPrefs.GetString("AVProMovieCapture-LastSavedFile", string.Empty);
  2392. #endif
  2393. }
  2394. set
  2395. {
  2396. PlayerPrefs.SetString("AVProMovieCapture-LastSavedFile", value);
  2397. #if UNITY_EDITOR
  2398. UnityEditor.EditorPrefs.SetString("AVProMovieCapture-LastSavedFile", value);
  2399. #endif
  2400. }
  2401. }
  2402. protected void RenderThreadEvent(NativePlugin.PluginEvent renderEvent)
  2403. {
  2404. NativePlugin.RenderThreadEvent(renderEvent, _handle);
  2405. }
  2406. public virtual void StopCapture(bool skipPendingFrames = false, bool ignorePendingFileWrites = false, bool deleteCapture = false)
  2407. {
  2408. if (_capturing)
  2409. {
  2410. if (_logCaptureStartStop)
  2411. {
  2412. if (!deleteCapture)
  2413. {
  2414. Debug.Log("[AVProMovieCapture] Stopping capture " + _handle);
  2415. }
  2416. else
  2417. {
  2418. Debug.Log("[AVProMovieCapture] Canceling capture " + _handle);
  2419. }
  2420. }
  2421. _capturing = false;
  2422. }
  2423. bool applyPostOperations = false;
  2424. FileWritingHandler fileWritingHandler = null;
  2425. if (_handle >= 0)
  2426. {
  2427. NativePlugin.Stop(_handle, skipPendingFrames);
  2428. UnprepareCapture();
  2429. if (_outputTarget == OutputTarget.VideoFile)
  2430. {
  2431. applyPostOperations = true;
  2432. }
  2433. #if UNITY_ANDROID && !UNITY_EDITOR
  2434. bool updateMediaGallery = ( _androidUpdateMediaGallery && ( _outputFolderType == OutputPath.RelativeToPictures || _outputFolderType == OutputPath.RelativeToVideos || _outputFolderType == OutputPath.PhotoLibrary ) );
  2435. #else
  2436. bool updateMediaGallery = ( _outputFolderType == OutputPath.RelativeToPictures || _outputFolderType == OutputPath.RelativeToVideos || _outputFolderType == OutputPath.PhotoLibrary );
  2437. #endif
  2438. fileWritingHandler = new FileWritingHandler(_outputTarget, _filePath, _handle, deleteCapture, _finalFilePath, updateMediaGallery);
  2439. if (_completedFileWritingAction != null)
  2440. {
  2441. fileWritingHandler.CompletedFileWritingAction = _completedFileWritingAction;
  2442. }
  2443. // Free the recorder, or if the file is still being written, store the action to be invoked where it is complete
  2444. bool canFreeRecorder = (ignorePendingFileWrites || NativePlugin.IsFileWritingComplete(_handle));
  2445. if (canFreeRecorder)
  2446. {
  2447. // If there is an external action set up, then notify it that writing has begun
  2448. if (_beginFinalFileWritingAction != null)
  2449. {
  2450. _beginFinalFileWritingAction.Invoke(fileWritingHandler);
  2451. }
  2452. // Complete writing immediately
  2453. fileWritingHandler.Dispose();
  2454. fileWritingHandler = null;
  2455. }
  2456. else
  2457. {
  2458. // If no external action has been set up for the checking when the file writing begins and end,
  2459. // add it to an internal list so we can make sure it completes
  2460. if (_beginFinalFileWritingAction == null)
  2461. {
  2462. _pendingFileWrites.Add(fileWritingHandler);
  2463. }
  2464. if (!deleteCapture)
  2465. {
  2466. VideoEncoderHints hints = GetEncoderHints().videoHints;
  2467. if (applyPostOperations && CanApplyPostOperations(_filePath, hints, _finalFilePath))
  2468. {
  2469. MP4FileProcessing.Options options = CreatePostOperationsOptions(hints, _finalFilePath);
  2470. fileWritingHandler.SetFilePostProcess(options);
  2471. }
  2472. }
  2473. applyPostOperations = false;
  2474. }
  2475. // If there is an external action set up, then notify it that writing has begun
  2476. if (_beginFinalFileWritingAction != null && fileWritingHandler != null)
  2477. {
  2478. _beginFinalFileWritingAction.Invoke(fileWritingHandler);
  2479. }
  2480. _handle = -1;
  2481. // Save the last captured path
  2482. if (!deleteCapture)
  2483. {
  2484. if (!string.IsNullOrEmpty(_filePath))
  2485. {
  2486. if (_outputTarget == OutputTarget.VideoFile)
  2487. {
  2488. LastFileSaved = _filePath;
  2489. }
  2490. else if (_outputTarget == OutputTarget.ImageSequence)
  2491. {
  2492. LastFileSaved = System.IO.Path.GetDirectoryName(_filePath);
  2493. }
  2494. }
  2495. }
  2496. #if AVPRO_MOVIECAPTURE_VIDEOPLAYER_SUPPORT
  2497. if (_videoPlayerController != null)
  2498. {
  2499. _videoPlayerController.StopCapture();
  2500. }
  2501. #endif
  2502. #if AVPRO_MOVIECAPTURE_PLAYABLES_SUPPORT
  2503. if (_timelineController != null)
  2504. {
  2505. _timelineController.StopCapture();
  2506. }
  2507. #endif
  2508. }
  2509. _fileInfo = null;
  2510. if (_unityAudioCapture)
  2511. {
  2512. _unityAudioCapture.StopCapture();
  2513. }
  2514. if (_motionBlur)
  2515. {
  2516. _motionBlur.enabled = false;
  2517. }
  2518. // Restore Unity timing
  2519. Time.captureFramerate = 0;
  2520. //Application.targetFrameRate = _oldTargetFrameRate;
  2521. //_oldTargetFrameRate = -1;
  2522. if (_oldFixedDeltaTime > 0f)
  2523. {
  2524. Time.fixedDeltaTime = _oldFixedDeltaTime;
  2525. }
  2526. _oldFixedDeltaTime = 0f;
  2527. #if !UNITY_EDITOR_OSX && (UNITY_IOS || UNITY_ANDROID)
  2528. // Android and iOS do not support disabling vsync so _oldVsyncCount is actually the target framerate before we started capturing.
  2529. if (_oldVSyncCount != 0)
  2530. {
  2531. Application.targetFrameRate = _oldVSyncCount;
  2532. _oldVSyncCount = 0;
  2533. }
  2534. #else
  2535. if (_oldVSyncCount > 0)
  2536. {
  2537. QualitySettings.vSyncCount = _oldVSyncCount;
  2538. _oldVSyncCount = 0;
  2539. }
  2540. #endif
  2541. _motionBlur = null;
  2542. if (_texture != null)
  2543. {
  2544. Destroy(_texture);
  2545. _texture = null;
  2546. }
  2547. if( _sideBySideMaterial != null )
  2548. {
  2549. Destroy( _sideBySideMaterial );
  2550. _sideBySideMaterial = null;
  2551. }
  2552. if( _sideBySideTexture != null )
  2553. {
  2554. RenderTexture.ReleaseTemporary( _sideBySideTexture );
  2555. _sideBySideTexture = null;
  2556. }
  2557. if (applyPostOperations)
  2558. {
  2559. ApplyPostOperations(_filePath, GetEncoderHints().videoHints, _finalFilePath);
  2560. }
  2561. }
  2562. private static MP4FileProcessing.Options CreatePostOperationsOptions(VideoEncoderHints hints, string finalFilePath)
  2563. {
  2564. MP4FileProcessing.Options options = new MP4FileProcessing.Options();
  2565. #if UNITY_EDITOR_WIN || (!UNITY_EDITOR && (UNITY_STANDALONE_WIN || UNITY_ANDROID))
  2566. // macOS and iOS don't require fast start postprocess as it is handled internally
  2567. options.applyFastStart = hints.allowFastStartStreamingPostProcess;
  2568. #endif
  2569. options.applyStereoMode = (hints.injectStereoPacking != NoneAutoCustom.None) && (hints.stereoPacking != StereoPacking.None);
  2570. if (options.applyStereoMode)
  2571. {
  2572. options.stereoMode = hints.stereoPacking;
  2573. }
  2574. options.applySphericalVideoLayout = (hints.injectSphericalVideoLayout != NoneAutoCustom.None) && (hints.sphericalVideoLayout != SphericalVideoLayout.None);
  2575. if (options.applySphericalVideoLayout)
  2576. {
  2577. options.sphericalVideoLayout = hints.sphericalVideoLayout;
  2578. }
  2579. options.applyMoveCaptureFile = (finalFilePath != null);
  2580. if(options.applyMoveCaptureFile)
  2581. {
  2582. options.finalCaptureFilePath = finalFilePath;
  2583. }
  2584. return options;
  2585. }
  2586. private static bool CanApplyPostOperations(string filePath, VideoEncoderHints hints, string finalFilePath)
  2587. {
  2588. bool result = false;
  2589. if (HasExtension(filePath, ".mp4") || HasExtension(filePath, ".mov") && File.Exists(filePath))
  2590. {
  2591. result = CreatePostOperationsOptions(hints, finalFilePath).HasOptions();
  2592. }
  2593. return result;
  2594. }
  2595. protected void ApplyPostOperations(string filePath, VideoEncoderHints hints, string finalFilePath)
  2596. {
  2597. if (CanApplyPostOperations(filePath, hints, finalFilePath))
  2598. {
  2599. try
  2600. {
  2601. MP4FileProcessing.Options options = CreatePostOperationsOptions(hints, finalFilePath);
  2602. if (!MP4FileProcessing.ProcessFile(filePath, false, options))
  2603. {
  2604. Debug.LogWarning("[AVProMovieCapture] failed to postprocess file: " + filePath);
  2605. }
  2606. }
  2607. catch (System.Exception e)
  2608. {
  2609. Debug.LogException(e);
  2610. }
  2611. }
  2612. }
  2613. private void ToggleCapture()
  2614. {
  2615. if (_capturing)
  2616. {
  2617. //_queuedStopCapture = true;
  2618. //_queuedStartCapture = false;
  2619. StopCapture();
  2620. }
  2621. else
  2622. {
  2623. //_queuedStartCapture = true;
  2624. //_queuedStopCapture = false;
  2625. StartCapture();
  2626. }
  2627. }
  2628. private bool IsEnoughDiskSpace()
  2629. {
  2630. bool result = true;
  2631. #if UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)
  2632. long fileSizeMB = GetCaptureFileSize() / (1024 * 1024);
  2633. if ((_freeDiskSpaceMB - fileSizeMB) < _minimumDiskSpaceMB)
  2634. {
  2635. result = false;
  2636. }
  2637. #endif
  2638. return result;
  2639. }
  2640. protected bool CanContinue()
  2641. {
  2642. bool result = true;
  2643. #if AVPRO_MOVIECAPTURE_VIDEOPLAYER_SUPPORT
  2644. if (IsCapturing() && !IsPaused() && !_isRealTime && _videoPlayerController != null)
  2645. {
  2646. result = _videoPlayerController.CanContinue();
  2647. }
  2648. #endif
  2649. return result;
  2650. }
  2651. private void Update()
  2652. {
  2653. if (_queuedStopCapture)
  2654. {
  2655. _queuedStopCapture = _queuedStartCapture = false;
  2656. StopCapture(false, false);
  2657. }
  2658. if (_queuedStartCapture)
  2659. {
  2660. _queuedStopCapture = _queuedStartCapture = false;
  2661. StartCapture();
  2662. }
  2663. }
  2664. private void LateUpdate()
  2665. {
  2666. if (_handle >= 0 && !_paused)
  2667. {
  2668. CheckFreeDiskSpace();
  2669. }
  2670. if (_captureKey != KeyCode.None)
  2671. {
  2672. #if (!ENABLE_INPUT_SYSTEM || ENABLE_LEGACY_INPUT_MANAGER)
  2673. if (Input.GetKeyDown(_captureKey))
  2674. {
  2675. ToggleCapture();
  2676. }
  2677. #endif
  2678. }
  2679. RemoveCompletedFileWrites();
  2680. if (_frameUpdateMode == FrameUpdateMode.Automatic)
  2681. {
  2682. // Resume capture if a start delay has been specified
  2683. if (IsCapturing() && IsPaused() && _stats.NumEncodedFrames == 0)
  2684. {
  2685. float delta = 0f;
  2686. if (_startDelay == StartDelayMode.GameSeconds)
  2687. {
  2688. if (!_isRealTime)
  2689. {
  2690. // In offline render mode Time.deltaTime is always zero due Time.timeScale being set to zero,
  2691. // so just use the real world time
  2692. delta = Time.unscaledDeltaTime;
  2693. }
  2694. else
  2695. {
  2696. delta = Time.deltaTime;
  2697. }
  2698. }
  2699. else if (_startDelay == StartDelayMode.RealSeconds)
  2700. {
  2701. delta = Time.unscaledDeltaTime;
  2702. }
  2703. if (delta > 0f)
  2704. {
  2705. _startDelayTimer += delta;
  2706. if (IsStartDelayComplete())
  2707. {
  2708. ResumeCapture();
  2709. }
  2710. }
  2711. }
  2712. PreUpdateFrame();
  2713. UpdateFrame();
  2714. }
  2715. }
  2716. private void RemoveCompletedFileWrites()
  2717. {
  2718. for (int i = _pendingFileWrites.Count - 1; i >= 0; i--)
  2719. {
  2720. FileWritingHandler handler = _pendingFileWrites[i];
  2721. if (handler.IsFileReady())
  2722. {
  2723. _pendingFileWrites.RemoveAt(i);
  2724. }
  2725. }
  2726. }
  2727. private void CheckFreeDiskSpace()
  2728. {
  2729. if (_minimumDiskSpaceMB > 0)
  2730. {
  2731. if (!IsEnoughDiskSpace())
  2732. {
  2733. Debug.LogWarning("[AVProMovieCapture] Free disk space getting too low. Stopping capture.");
  2734. StopCapture(true);
  2735. }
  2736. }
  2737. }
  2738. protected bool IsStartDelayComplete()
  2739. {
  2740. bool result = false;
  2741. if (_startDelay == StartDelayMode.None)
  2742. {
  2743. result = true;
  2744. }
  2745. else if (_startDelay == StartDelayMode.GameSeconds ||
  2746. _startDelay == StartDelayMode.RealSeconds)
  2747. {
  2748. result = (_startDelayTimer >= _startDelaySeconds);
  2749. }
  2750. return result;
  2751. }
  2752. protected bool IsStopTimeReached()
  2753. {
  2754. bool result = false;
  2755. if (_stopMode != StopMode.None)
  2756. {
  2757. switch (_stopMode)
  2758. {
  2759. case StopMode.FramesEncoded:
  2760. result = (_stats.NumEncodedFrames >= _stopFrames);
  2761. break;
  2762. case StopMode.SecondsEncoded:
  2763. if (!_isRealTime)
  2764. {
  2765. // In non-realtime mode this is a more accurate way to determine encoded time
  2766. result = (_stats.NumEncodedFrames >= _stopSeconds * _frameRate);
  2767. }
  2768. else
  2769. {
  2770. result = (_stats.TotalEncodedSeconds >= _stopSeconds);
  2771. }
  2772. break;
  2773. case StopMode.SecondsElapsed:
  2774. if (!_startPaused && !_paused)
  2775. {
  2776. float timeSinceLastEditorPause = (Time.realtimeSinceStartup - _captureStartTime);
  2777. result = (timeSinceLastEditorPause + _capturePrePauseTotalTime) >= _stopSeconds;
  2778. }
  2779. break;
  2780. }
  2781. }
  2782. return result;
  2783. }
  2784. public float GetProgress()
  2785. {
  2786. float result = 0f;
  2787. if (_stopMode != StopMode.None)
  2788. {
  2789. switch (_stopMode)
  2790. {
  2791. case StopMode.FramesEncoded:
  2792. result = (_stats.NumEncodedFrames / (float)_stopFrames);
  2793. break;
  2794. case StopMode.SecondsEncoded:
  2795. result = ((_stats.NumEncodedFrames / _frameRate) / _stopSeconds);
  2796. break;
  2797. case StopMode.SecondsElapsed:
  2798. if (!_startPaused && !_paused)
  2799. {
  2800. float timeSinceLastEditorPause = (Time.realtimeSinceStartup - _captureStartTime);
  2801. result = (timeSinceLastEditorPause + _capturePrePauseTotalTime) / _stopSeconds;
  2802. }
  2803. break;
  2804. }
  2805. }
  2806. return result;
  2807. }
  2808. protected float GetSecondsPerCaptureFrame()
  2809. {
  2810. float timelapseScale = (float)_timelapseScale;
  2811. if (!_isRealTime)
  2812. {
  2813. timelapseScale = 1f;
  2814. }
  2815. float captureFrameRate = _frameRate / timelapseScale;
  2816. float secondsPerFrame = 1f / captureFrameRate;
  2817. return secondsPerFrame;
  2818. }
  2819. protected bool CanOutputFrame()
  2820. {
  2821. bool result = false;
  2822. if (_handle >= 0)
  2823. {
  2824. if (_isRealTime)
  2825. {
  2826. if (NativePlugin.IsNewFrameDue(_handle))
  2827. {
  2828. result = (_timeSinceLastFrame >= GetSecondsPerCaptureFrame());
  2829. //result = true;
  2830. }
  2831. }
  2832. else
  2833. {
  2834. const int WatchDogLimit = 1000;
  2835. int watchdog = 0;
  2836. if (_outputTarget != OutputTarget.NamedPipe)
  2837. {
  2838. // Wait for the encoder to have an available buffer
  2839. // The watchdog prevents an infinite while loop
  2840. while (_handle >= 0 && !NativePlugin.IsNewFrameDue(_handle) && watchdog < WatchDogLimit)
  2841. {
  2842. System.Threading.Thread.Sleep(1);
  2843. watchdog++;
  2844. }
  2845. }
  2846. // Return handle status as it may have closed elsewhere
  2847. result = (_handle >= 0) && (watchdog < WatchDogLimit);
  2848. }
  2849. }
  2850. return result;
  2851. }
  2852. protected void TickFrameTimer()
  2853. {
  2854. _timeSinceLastFrame += Time.deltaTime;//unscaledDeltaTime;
  2855. }
  2856. protected void RenormTimer()
  2857. {
  2858. float secondsPerFrame = GetSecondsPerCaptureFrame();
  2859. if (_timeSinceLastFrame >= secondsPerFrame)
  2860. {
  2861. _timeSinceLastFrame -= secondsPerFrame;
  2862. }
  2863. }
  2864. public virtual Texture GetPreviewTexture()
  2865. {
  2866. return null;
  2867. }
  2868. public virtual Texture GetSideBySideTexture()
  2869. {
  2870. return _sideBySideTexture;
  2871. }
  2872. protected void EncodeUnityAudio()
  2873. {
  2874. if (IsUsingUnityAudioComponent())
  2875. {
  2876. int audioDataLength = 0;
  2877. System.IntPtr audioDataPtr = _unityAudioCapture.ReadData(out audioDataLength);
  2878. if (audioDataLength > 0)
  2879. {
  2880. NativePlugin.EncodeAudio(_handle, audioDataPtr, (uint)audioDataLength);
  2881. }
  2882. }
  2883. }
  2884. public void EncodeAudio(NativeArray<float> audioData)
  2885. {
  2886. if (audioData.Length > 0)
  2887. {
  2888. #if UNITY_NATIVEARRAY_UNSAFE_SUPPORT
  2889. unsafe
  2890. {
  2891. System.IntPtr pointer = (System.IntPtr)Unity.Collections.LowLevel.Unsafe.NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(audioData);
  2892. NativePlugin.EncodeAudio(_handle, pointer, (uint)audioData.Length);
  2893. }
  2894. #else
  2895. EncodeAudio(audioData.ToArray());
  2896. #endif
  2897. }
  2898. }
  2899. public void EncodeAudio(float[] audioData)
  2900. {
  2901. if (audioData.Length > 0)
  2902. {
  2903. int byteCount = Marshal.SizeOf(audioData[0]) * audioData.Length;
  2904. // Copy the array to unmanaged memory.
  2905. System.IntPtr pointer = Marshal.AllocHGlobal(byteCount);
  2906. Marshal.Copy(audioData, 0, pointer, audioData.Length);
  2907. // Encode
  2908. NativePlugin.EncodeAudio(_handle, pointer, (uint)audioData.Length);
  2909. // Free the unmanaged memory.
  2910. Marshal.FreeHGlobal(pointer);
  2911. }
  2912. }
  2913. public virtual void PreUpdateFrame()
  2914. {
  2915. #if AVPRO_MOVIECAPTURE_PLAYABLES_SUPPORT
  2916. if (IsCapturing() && !IsPaused() && !_isRealTime && _timelineController != null)
  2917. {
  2918. _timelineController.UpdateFrame();
  2919. }
  2920. #endif
  2921. #if AVPRO_MOVIECAPTURE_VIDEOPLAYER_SUPPORT
  2922. if (IsCapturing() && !IsPaused() && !_isRealTime && _videoPlayerController != null)
  2923. {
  2924. _videoPlayerController.UpdateFrame();
  2925. }
  2926. #endif
  2927. }
  2928. public virtual void UpdateFrame()
  2929. {
  2930. // NOTE: Unlike other CaptureFrom components, CaptureFromScreen uses a coroutine, so when it calls base.UpdateFrame() is could still process the frame afterwards
  2931. if (_handle >= 0 && !_paused)
  2932. {
  2933. _stats.NumDroppedFrames = NativePlugin.GetNumDroppedFrames(_handle);
  2934. _stats.NumDroppedEncoderFrames = NativePlugin.GetNumDroppedEncoderFrames(_handle);
  2935. _stats.NumEncodedFrames = NativePlugin.GetNumEncodedFrames(_handle);
  2936. _stats.TotalEncodedSeconds = NativePlugin.GetEncodedSeconds(_handle);
  2937. if (IsStopTimeReached())
  2938. {
  2939. _queuedStopCapture = true;
  2940. }
  2941. }
  2942. }
  2943. protected bool InitialiseSideBySideTransparency( int width, int height, bool screenFlip = false, int antiAliasing = 1 )
  2944. {
  2945. bool reInitialised = false;
  2946. if( _Transparency == Transparency.LeftRight || _Transparency == Transparency.TopBottom )
  2947. {
  2948. if( !_sideBySideMaterial )
  2949. {
  2950. _sideBySideMaterial = new Material(Shader.Find("Hidden/AVProMovieCapture/SideBySideAlpha"));
  2951. switch (_Transparency)
  2952. {
  2953. case Transparency.TopBottom: _sideBySideMaterial.DisableKeyword("ALPHA_LEFT_RIGHT"); _sideBySideMaterial.EnableKeyword("ALPHA_TOP_BOTTOM"); break;
  2954. case Transparency.LeftRight: _sideBySideMaterial.EnableKeyword("ALPHA_LEFT_RIGHT"); _sideBySideMaterial.DisableKeyword("ALPHA_TOP_BOTTOM"); break;
  2955. }
  2956. if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Metal ||
  2957. SystemInfo.graphicsDeviceType == GraphicsDeviceType.Vulkan ||
  2958. SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11 ||
  2959. SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D12)
  2960. {
  2961. _sideBySideMaterial.DisableKeyword( screenFlip ? "FLIPPED" : "SCREEN_FLIPPED" );
  2962. _sideBySideMaterial.EnableKeyword( screenFlip ? "SCREEN_FLIPPED" : "FLIPPED" );
  2963. }
  2964. }
  2965. int createWidth = width;
  2966. int createHeight = height;
  2967. switch( _Transparency )
  2968. {
  2969. case Transparency.TopBottom: createHeight *= 2; break;
  2970. case Transparency.LeftRight: createWidth *= 2; break;
  2971. }
  2972. if( !_sideBySideTexture || (createWidth != _sideBySideTexture.width || createHeight != _sideBySideTexture.height) )
  2973. {
  2974. if( _sideBySideTexture )
  2975. {
  2976. RenderTexture.ReleaseTemporary( _sideBySideTexture );
  2977. _sideBySideTexture = null;
  2978. }
  2979. _sideBySideTexture = RenderTexture.GetTemporary(createWidth,
  2980. createHeight,
  2981. 0,
  2982. RenderTextureFormat.ARGB32,
  2983. RenderTextureReadWrite.sRGB, // Is this correct for gamma colourspace?
  2984. antiAliasing);
  2985. _sideBySideTexture.name = "[AVProMovieCapture] SideBySide Transparency Target";
  2986. _sideBySideTexture.Create();
  2987. reInitialised = true;
  2988. }
  2989. }
  2990. return reInitialised;
  2991. }
  2992. protected RenderTexture UpdateForSideBySideTransparency( Texture sourceTexture, bool screenFlip = false, int antiAliasing = 1)
  2993. {
  2994. if( sourceTexture )
  2995. {
  2996. // Ensure things are setup
  2997. InitialiseSideBySideTransparency( sourceTexture.width, sourceTexture.height, screenFlip, antiAliasing );
  2998. if ( _sideBySideTexture )
  2999. {
  3000. _sideBySideTexture.DiscardContents();
  3001. if( sourceTexture && _sideBySideMaterial )
  3002. {
  3003. Graphics.Blit( sourceTexture, _sideBySideTexture, _sideBySideMaterial );
  3004. }
  3005. }
  3006. }
  3007. return _sideBySideTexture;
  3008. }
  3009. protected void ResetFPS()
  3010. {
  3011. _stats.ResetFPS();
  3012. }
  3013. public void UpdateFPS()
  3014. {
  3015. _stats.UpdateFPS();
  3016. }
  3017. protected int GetCameraAntiAliasingLevel(Camera camera)
  3018. {
  3019. int aaLevel = QualitySettings.antiAliasing;
  3020. if (aaLevel == 0)
  3021. {
  3022. aaLevel = 1;
  3023. }
  3024. if (_renderAntiAliasing > 0)
  3025. {
  3026. aaLevel = _renderAntiAliasing;
  3027. }
  3028. if (aaLevel != 1 && aaLevel != 2 && aaLevel != 4 && aaLevel != 8)
  3029. {
  3030. Debug.LogWarning("[AVProMovieCapture] Invalid antialiasing value, must be 1, 2, 4 or 8. Defaulting to 1. >> " + aaLevel);
  3031. aaLevel = 1;
  3032. }
  3033. if (aaLevel != 1)
  3034. {
  3035. if (camera.actualRenderingPath == RenderingPath.DeferredLighting || camera.actualRenderingPath == RenderingPath.DeferredShading)
  3036. {
  3037. Debug.LogWarning("[AVProMovieCapture] Not using antialiasing because MSAA is not supported by camera render path " + camera.actualRenderingPath);
  3038. aaLevel = 1;
  3039. }
  3040. }
  3041. return aaLevel;
  3042. }
  3043. public long GetCaptureFileSize()
  3044. {
  3045. long result = 0;
  3046. #if UNITY_EDITOR_OSX || (!UNITY_EDITOR && (UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_ANDROID))
  3047. result = NativePlugin.GetFileSize(_handle);
  3048. #elif !UNITY_WEBPLAYER
  3049. if (_handle >= 0 && _outputTarget == OutputTarget.VideoFile)
  3050. {
  3051. if (_fileInfo == null && File.Exists(_filePath))
  3052. {
  3053. _fileInfo = new System.IO.FileInfo(_filePath);
  3054. }
  3055. if (_fileInfo != null)
  3056. {
  3057. _fileInfo.Refresh();
  3058. result = _fileInfo.Length;
  3059. }
  3060. }
  3061. #endif
  3062. return result;
  3063. }
  3064. public static void GetResolution(Resolution res, ref int width, ref int height)
  3065. {
  3066. switch (res)
  3067. {
  3068. case Resolution.POW2_8192x8192:
  3069. width = 8192; height = 8192;
  3070. break;
  3071. case Resolution.POW2_8192x4096:
  3072. width = 8192; height = 4096;
  3073. break;
  3074. case Resolution.POW2_4096x4096:
  3075. width = 4096; height = 4096;
  3076. break;
  3077. case Resolution.POW2_4096x2048:
  3078. width = 4096; height = 2048;
  3079. break;
  3080. case Resolution.POW2_2048x4096:
  3081. width = 2048; height = 4096;
  3082. break;
  3083. case Resolution.UHD_3840x2160:
  3084. width = 3840; height = 2160;
  3085. break;
  3086. case Resolution.UHD_3840x2048:
  3087. width = 3840; height = 2048;
  3088. break;
  3089. case Resolution.UHD_3840x1920:
  3090. width = 3840; height = 1920;
  3091. break;
  3092. case Resolution.UHD_2560x1440:
  3093. width = 2560; height = 1440;
  3094. break;
  3095. case Resolution.POW2_2048x2048:
  3096. width = 2048; height = 2048;
  3097. break;
  3098. case Resolution.POW2_2048x1024:
  3099. width = 2048; height = 1024;
  3100. break;
  3101. case Resolution.HD_1920x1080:
  3102. width = 1920; height = 1080;
  3103. break;
  3104. case Resolution.HD_1280x720:
  3105. width = 1280; height = 720;
  3106. break;
  3107. case Resolution.SD_1024x768:
  3108. width = 1024; height = 768;
  3109. break;
  3110. case Resolution.SD_800x600:
  3111. width = 800; height = 600;
  3112. break;
  3113. case Resolution.SD_800x450:
  3114. width = 800; height = 450;
  3115. break;
  3116. case Resolution.SD_640x480:
  3117. width = 640; height = 480;
  3118. break;
  3119. case Resolution.SD_640x360:
  3120. width = 640; height = 360;
  3121. break;
  3122. case Resolution.SD_320x240:
  3123. width = 320; height = 240;
  3124. break;
  3125. }
  3126. }
  3127. // Returns the next multiple of 4 or the same value if it's already a multiple of 4
  3128. protected static int NextMultipleOf4(int value)
  3129. {
  3130. return (value + 3) & ~0x03;
  3131. }
  3132. // Audio capture support
  3133. /// <summary>
  3134. /// Authorisation for audio capture.
  3135. /// </summary>
  3136. public enum AudioCaptureDeviceAuthorisationStatus
  3137. {
  3138. /// <summary>Audio capture is unavailable.</summary>
  3139. Unavailable = -1,
  3140. /// <summary>Authorisation is still to be requested.</summary>
  3141. NotDetermined,
  3142. /// <summary>Authorisation has been denied.</summary>
  3143. Denied,
  3144. /// <summary>Authorisation has been granted.</summary>
  3145. Authorised
  3146. };
  3147. /// <summary>
  3148. /// Check to see if authorisation has been given to capture audio.
  3149. /// </summary>
  3150. private static bool _waitingForAudioCaptureDeviceAuthorisation = true;
  3151. private static bool _hasCheckedAudioCaptureDeviceAuthorisationStatus = false;
  3152. private static AudioCaptureDeviceAuthorisationStatus _audioCaptureDeviceAuthorisationStatus = AudioCaptureDeviceAuthorisationStatus.NotDetermined;
  3153. public static AudioCaptureDeviceAuthorisationStatus HasUserAuthorisationToCaptureAudio()
  3154. {
  3155. if (!_hasCheckedAudioCaptureDeviceAuthorisationStatus || _audioCaptureDeviceAuthorisationStatus == AudioCaptureDeviceAuthorisationStatus.NotDetermined)
  3156. {
  3157. _hasCheckedAudioCaptureDeviceAuthorisationStatus = true;
  3158. #if !UNITY_EDITOR_OSX && (UNITY_STANDALONE_OSX || (UNITY_IOS && !UNITY_EDITOR))
  3159. _audioCaptureDeviceAuthorisationStatus = (AudioCaptureDeviceAuthorisationStatus)NativePlugin.AudioCaptureDeviceAuthorisationStatus();
  3160. #elif UNITY_ANDROID && !UNITY_EDITOR
  3161. _audioCaptureDeviceAuthorisationStatus = Permission.HasUserAuthorizedPermission(Permission.Microphone) ? AudioCaptureDeviceAuthorisationStatus.Authorised
  3162. : AudioCaptureDeviceAuthorisationStatus.NotDetermined;
  3163. #else
  3164. _audioCaptureDeviceAuthorisationStatus = AudioCaptureDeviceAuthorisationStatus.Unavailable;
  3165. #endif
  3166. }
  3167. return _audioCaptureDeviceAuthorisationStatus;
  3168. }
  3169. #if ENABLE_IL2CPP && (UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX || (UNITY_IOS && !UNITY_EDITOR))
  3170. [MonoPInvokeCallback(typeof(NativePlugin.RequestAudioCaptureDeviceAuthorisationDelegate))]
  3171. #endif
  3172. private static void RequestUserAuthorisationToCaptureAudioCallback(int authorisation)
  3173. {
  3174. _audioCaptureDeviceAuthorisationStatus = (AudioCaptureDeviceAuthorisationStatus)authorisation;
  3175. _hasCheckedAudioCaptureDeviceAuthorisationStatus = true;
  3176. _waitingForAudioCaptureDeviceAuthorisation = false;
  3177. }
  3178. private class WaitForAudioCaptureDeviceAuthorisation : CustomYieldInstruction
  3179. {
  3180. public override bool keepWaiting { get { return CaptureBase._waitingForAudioCaptureDeviceAuthorisation; } }
  3181. }
  3182. /// <summary>
  3183. /// Request authorisation to capture audio.
  3184. /// </summary>
  3185. public static CustomYieldInstruction RequestAudioCaptureDeviceUserAuthorisation()
  3186. {
  3187. #if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX || (UNITY_IOS && !UNITY_EDITOR)
  3188. NativePlugin.RequestAudioCaptureDeviceAuthorisationDelegate callback = new NativePlugin.RequestAudioCaptureDeviceAuthorisationDelegate(RequestUserAuthorisationToCaptureAudioCallback);
  3189. System.IntPtr ptr = Marshal.GetFunctionPointerForDelegate(callback);
  3190. NativePlugin.RequestAudioCaptureDeviceAuthorisation(ptr);
  3191. return new WaitForAudioCaptureDeviceAuthorisation();
  3192. #elif UNITY_ANDROID && !UNITY_EDITOR
  3193. Permission.RequestUserPermission(Permission.Microphone);
  3194. RequestUserAuthorisationToCaptureAudioCallback((int)HasUserAuthorisationToCaptureAudio());
  3195. return new WaitForAudioCaptureDeviceAuthorisation();
  3196. #else
  3197. return null;
  3198. #endif
  3199. }
  3200. // Photo library support
  3201. /// <summary>
  3202. /// Level of access for the photos library.
  3203. ///</summary>
  3204. public enum PhotoLibraryAccessLevel
  3205. {
  3206. /// <summary>Can only add photos to the photo library, cannot create albums or read back images.</summary>
  3207. AddOnly,
  3208. /// <summary>Full access, can add photos, create albums and read back images.</summary>
  3209. ReadWrite
  3210. };
  3211. /// <summary>
  3212. /// Authorisation for access to the photos library.
  3213. /// </summary>
  3214. public enum PhotoLibraryAuthorisationStatus
  3215. {
  3216. /// <summary>The photo library is unavailable.</summary>
  3217. Unavailable = -1,
  3218. /// <summary>Authorisation to the photo library is still to be requested.</summary>
  3219. NotDetermined,
  3220. /// <summary>Access to the photo library has been denied.</summary>
  3221. Denied,
  3222. /// <summary>Access to the photo library has been granted.</summary>
  3223. Authorised
  3224. };
  3225. /// <summary>
  3226. /// Check to see if authorisation has been given to access the photo library.
  3227. /// </summary>
  3228. private static bool _waitingForAuthorisationToAccessPhotos = true;
  3229. private static bool _hasCheckedPhotoLibraryAuthorisationStatus = false;
  3230. private static PhotoLibraryAuthorisationStatus _photoLibraryAuthorisation = PhotoLibraryAuthorisationStatus.NotDetermined;
  3231. public static PhotoLibraryAuthorisationStatus HasUserAuthorisationToAccessPhotos(PhotoLibraryAccessLevel accessLevel)
  3232. {
  3233. if (!_hasCheckedPhotoLibraryAuthorisationStatus || _photoLibraryAuthorisation == PhotoLibraryAuthorisationStatus.NotDetermined)
  3234. {
  3235. _hasCheckedPhotoLibraryAuthorisationStatus = true;
  3236. #if !UNITY_EDITOR_OSX && (UNITY_STANDALONE_OSX || (UNITY_IOS && !UNITY_EDITOR))
  3237. _photoLibraryAuthorisation = (PhotoLibraryAuthorisationStatus)NativePlugin.PhotoLibraryAuthorisationStatus((int)accessLevel);
  3238. #else
  3239. _photoLibraryAuthorisation = PhotoLibraryAuthorisationStatus.Unavailable;
  3240. #endif
  3241. }
  3242. return _photoLibraryAuthorisation;
  3243. }
  3244. #if ENABLE_IL2CPP && (UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX || (UNITY_IOS && !UNITY_EDITOR))
  3245. [MonoPInvokeCallback(typeof(NativePlugin.RequestPhotoLibraryAuthorisationDelegate))]
  3246. #endif
  3247. private static void RequestUserAuthorisationToAccessPhotosCallback(int authorisation)
  3248. {
  3249. _photoLibraryAuthorisation = (PhotoLibraryAuthorisationStatus)authorisation;
  3250. _hasCheckedPhotoLibraryAuthorisationStatus = true;
  3251. _waitingForAuthorisationToAccessPhotos = false;
  3252. }
  3253. private class WaitForAuthorisationToAccessPhotos: CustomYieldInstruction
  3254. {
  3255. public override bool keepWaiting { get { return CaptureBase._waitingForAuthorisationToAccessPhotos; } }
  3256. }
  3257. /// <summary>
  3258. /// Request authorisation to access the photo library.
  3259. /// </summary>
  3260. public static CustomYieldInstruction RequestUserAuthorisationToAccessPhotos(PhotoLibraryAccessLevel accessLevel)
  3261. {
  3262. #if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX || (UNITY_IOS && !UNITY_EDITOR)
  3263. NativePlugin.RequestPhotoLibraryAuthorisationDelegate callback = new NativePlugin.RequestPhotoLibraryAuthorisationDelegate(RequestUserAuthorisationToAccessPhotosCallback);
  3264. System.IntPtr ptr = Marshal.GetFunctionPointerForDelegate(callback);
  3265. NativePlugin.RequestPhotoLibraryAuthorisation((int)accessLevel, ptr);
  3266. return new WaitForAuthorisationToAccessPhotos();
  3267. #else
  3268. return null;
  3269. #endif
  3270. }
  3271. }
  3272. }