ScriptingDemoUI.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. //--------------------------------------------
  2. // Movie Player
  3. // Copyright © 2014 SHUU Games
  4. //--------------------------------------------
  5. using UnityEngine;
  6. using System.Collections;
  7. using MP;
  8. using MP.AVI;
  9. using System;
  10. using System.IO;
  11. using System.ComponentModel;// for simple threading
  12. public class ScriptingDemoUI : MonoBehaviour
  13. {
  14. public string infile = "Assets/MoviePlayer/Sample movies/Sintel (480p mjpeg, 2ch24khz alaw).avi.bytes";
  15. public string outfile = "out.bytes";
  16. public string frameOutFile = "out.png";
  17. public int frame = 150;
  18. public string httpStreamUrl = "http://194.126.108.66:8883/1396114247132"; // just one traffic camera
  19. public bool runInSeparateThread;
  20. [ContextMenu("Load file and play forever (play mode)")]
  21. void LoadFileAndPlayForever ()
  22. {
  23. if (!Application.isPlaying) return;
  24. // Using MoviePlayer is the easiest way to play back a movie and it requires
  25. // zero lines of code because all can be set up an controlled in Inspector.
  26. // Here however we create that component from a script.
  27. var moviePlayer = GetComponent<MoviePlayer> ();
  28. if (moviePlayer == null) {
  29. moviePlayer = gameObject.AddComponent<MoviePlayer> ();
  30. }
  31. // The component has multiple Load overloads capable of playing back files,
  32. // binary TextAssets, byte[] arrays and System.IO.Streams. Optionally
  33. // a LoadOptions argument can be used to give more context to what and how
  34. // to load. For example if you're loading raw pcm audio, then there's no
  35. // way to know what is the sample rate, sample size and channel count without
  36. // LoadOptions.audioStreamInfo.
  37. moviePlayer.Load (infile);
  38. // By default the MoviePlayer will bind the framebuffer to renderer.sharedMaterial,
  39. // but in this case we don't have a renderer attached. Instead we instruct it to
  40. // draw the frames directly onto the scree.
  41. moviePlayer.drawToScreen = true;
  42. // MoviePlayer doesn't have methods for starting or stopping the playback. Instead
  43. // it uses state variable that you set. Besides play, seeking is another common
  44. // field that you will read and set.
  45. moviePlayer.play = true;
  46. // Looping a movie is as simple as setting loop=true. MoviePlayer has events for
  47. // loop, play and stop that you can hook your scripts onto.
  48. moviePlayer.loop = true;
  49. moviePlayer.OnLoop += delegate(MoviePlayerBase mp) {
  50. Debug.Log ("Loop it");
  51. };
  52. }
  53. [ContextMenu("Connect and play MJPEG HTTP stream")]
  54. void ConnectAndPlayAStream ()
  55. {
  56. if (!Application.isPlaying)
  57. return;
  58. // MovieStreamer is like a MoviePlayer that can play streams.
  59. // The main difference is that you can't seek a network stream.
  60. // It can be used with zero code, but here we'll create it from a script.
  61. var movieStreamer = GetComponent<MovieStreamer> ();
  62. if (movieStreamer == null) {
  63. movieStreamer = gameObject.AddComponent<MovieStreamer> ();
  64. }
  65. // Connect to a stream. We could also set movieStreamer.sourceUrl and
  66. // then call movieStreamer.Reconnect() for the same result.
  67. movieStreamer.Load (httpStreamUrl);
  68. // Set the streamer to draw on screen. The play field for a streamer only
  69. // controls wether the framebuffer is being updated or not. The connection
  70. // is kept open until movieStreamer.Unload() is called.
  71. movieStreamer.drawToScreen = true;
  72. movieStreamer.play = true;
  73. }
  74. [ContextMenu("Stop and unload (play mode)")]
  75. void StopAndUnload ()
  76. {
  77. if (!Application.isPlaying)
  78. return;
  79. // Get a MoviePlayer component and stop the playback
  80. var moviePlayer = GetComponent<MoviePlayer> ();
  81. if (moviePlayer != null) {
  82. // Stopping a movie is as simple as setting play=false. Unload method will
  83. // release all resources associated with currently loaded movie so that
  84. // MoviePlayer can be reused to Load and play back other movies.
  85. // Actually if you just call Load on MoviePlayer it'll safely unload the
  86. // current movie and load another. If loading the other fails, current one
  87. // will keep playing.
  88. moviePlayer.play = false;
  89. moviePlayer.Unload ();
  90. }
  91. // Use the same method for stopping MovieStreamer too
  92. var movieStreamer = GetComponent<MovieStreamer> ();
  93. if (movieStreamer != null) {
  94. // We could set play=false, but that is not necessary, becuse if the
  95. // stream is not connected, we're not playing it back anyway.
  96. movieStreamer.Unload ();
  97. }
  98. }
  99. [ContextMenu("Extract one frame")]
  100. void ExtractOneFrame ()
  101. {
  102. #if UNITY_WEBPLAYER
  103. Debug.Log("This example doesn't work in web player");
  104. #else
  105. // First we load the movie, but not using a MoviePlayer component, but a
  106. // MoviePlayerUtil class. The Load method requires a System.IO.Stream as an input
  107. // and returns a framebuffer Texture2D and AudioClip which is optional.
  108. // The Load method detects the stream type, initializes a Demux that can read audio and
  109. // video streams in it and then it creates stream Decoder instances that you will use
  110. // to extract video frames or audio samples.
  111. Texture2D framebuffer;
  112. Movie movie = MoviePlayerUtil.Load (File.OpenRead (infile), out framebuffer);
  113. if (movie.demux.hasVideo) {
  114. // Invoking a Decoder like this will fetch an encoded frame bytes from Demux
  115. // and decode it into framebuffer texture specified earlier. There is
  116. // another overload that doesn't take a frame as an argument, but returns it
  117. // instead. This is used with Streamer demux where you have sequential access only.
  118. movie.videoDecoder.Decode (frame);
  119. }
  120. // Just encode the frame as PNG and write to disk
  121. File.WriteAllBytes (frameOutFile, framebuffer.EncodeToPNG ());
  122. Debug.Log ("Extracted frame " + frame);
  123. #endif
  124. }
  125. [ContextMenu("Drop half the frames remux")]
  126. void DropHalfTheFramesRemux ()
  127. {
  128. // In this example we're going one level deeper in the API and work directly
  129. // with Demux class. We could use MoviePlayerUtil.Load too, but for remuxing
  130. // we don't need Decoders to be instantiated, because we're just copying encoded
  131. // frame bytes around.
  132. //
  133. // Since we're not using decoders, we're not referencing anything from Unity API.
  134. // Therefore it's possible to run it in separate thread.
  135. RunInBackgroundOrNot (delegate() {
  136. // Instantiate a demux for an input stream based on stream type.
  137. Stream instream = File.OpenRead (infile);
  138. Demux demux = Demux.forSource (instream);
  139. demux.Init (instream);
  140. // Instantiate a remux for an output stream. Here we have to explicity
  141. // instantiate the remux we want, in this case, AviRemux, and set its
  142. // properties. Since we're not doing much here, we can use the same
  143. // videoStreamInfo and audioStreamInfo for remux as demux. For the video
  144. // however we clone the stream info, because we want to change it. Since
  145. // we're going to drop every other frame, we also need to lower the
  146. // video framerate.
  147. Stream outstream = File.OpenWrite (outfile);
  148. Remux remux = new AviRemux ();
  149. var remuxVideoStreamInfo = new VideoStreamInfo (demux.videoStreamInfo);
  150. remuxVideoStreamInfo.framerate /= 2;
  151. remux.Init (outstream, remuxVideoStreamInfo, demux.audioStreamInfo);
  152. // Just sum buffers and variables needed later
  153. byte[] videoBuffer, audioBuffer;
  154. int videoBytesRead, audioBytesRead;
  155. // Loop until we've processed all the video frames. If we wanted to run this code
  156. // in main Unity thread without blocking, then we could wrap it all in a coroutine
  157. // and do "yield return 1" inside the loop.
  158. do {
  159. // Here we're using sequential access to video (and audio) stream. The same could
  160. // be achieved with random access, but then only demuxes that can seek in a file
  161. // can be used (no streaming from network or webcam).
  162. videoBytesRead = demux.ReadVideoFrame (out videoBuffer);
  163. if (videoBytesRead > 0) {
  164. // Read the exact number of audio samples that are to be played during this frame
  165. int samplesPerVideoFrame = (int)(demux.audioStreamInfo.sampleRate / demux.videoStreamInfo.framerate);
  166. audioBytesRead = demux.ReadAudioSamples (out audioBuffer, samplesPerVideoFrame);
  167. // Only write every second video frame, but all the audio samples. The total stream
  168. // lengths will still be the same, because we've set the framerate for remuxed stream
  169. // to half of the original.
  170. if (demux.VideoPosition % 2 == 1) {
  171. remux.WriteNextVideoFrame (videoBuffer, videoBytesRead);
  172. }
  173. remux.WriteNextAudioSamples (audioBuffer, audioBytesRead);
  174. }
  175. } while(videoBytesRead > 0);
  176. // Close the remux and demux. While it's possible to leave demux just hanging there unclosed and
  177. // possibly introducing a memory leak, we have to Close the remux for the output to be playable.
  178. // The reason is that AviDemux needs to write all unwritten index chunks and update the avi header
  179. // after all frames have been written.
  180. remux.Shutdown ();
  181. demux.Shutdown ();
  182. });
  183. }
  184. [ContextMenu("Instant movie switching")]
  185. void InstantMovieSwitching ()
  186. {
  187. if (!Application.isPlaying)
  188. return;
  189. StartCoroutine (InstantMovieSwitchingCoroutine ());
  190. }
  191. IEnumerator InstantMovieSwitchingCoroutine ()
  192. {
  193. // Load the file and start playing
  194. LoadFileAndPlayForever ();
  195. var moviePlayer = GetComponent<MoviePlayer> ();
  196. while (true) {
  197. // Wait for a bit, random
  198. yield return new WaitForSeconds (UnityEngine.Random.Range (0.25f, 2f));
  199. // Reload the file again. In this case it's the same file, but it doesn't have to be.
  200. // The expected behaviour is that the playback will continue smoothly, because no
  201. // state variables are changed prior to loading. While it's true for video, there'll
  202. // be a tiny switching delay in audio due threading. When a new clip is loaded,
  203. // the first frame is decoded automatically, so if you want to start playing the new
  204. // clip from a certain position, then set the new videoFrame or videoTime before calling Load.
  205. //moviePlayer.videoFrame = 150;
  206. moviePlayer.Load (infile);
  207. }
  208. }
  209. [ContextMenu("Capture webcam to file")]
  210. void CaptureWebcamToFile ()
  211. {
  212. if (!Application.isPlaying)
  213. return;
  214. StartCoroutine (CaptureWebcamToFileCoroutine ());
  215. }
  216. IEnumerator CaptureWebcamToFileCoroutine ()
  217. {
  218. // Open a webcam streamer. The url prefix for this is webcam://
  219. // Optionally a webcam device id can be added (to get a list, use WebCamTexture.devices)
  220. string webcamStreamUrl = "webcam://";
  221. Streamer streamer = Streamer.forUrl (webcamStreamUrl);
  222. streamer.Connect (webcamStreamUrl);
  223. // Set up a remux @ 15fps
  224. var vi = new VideoStreamInfo (streamer.videoStreamInfo);
  225. vi.framerate = 15; // must be lower than framerate with this approach!
  226. AviRemux remux = new AviRemux ();
  227. remux.Init (File.OpenWrite (outfile), vi, null);
  228. // Do fixed time capture, 10 seconds (150 frames @ 15fps)
  229. // The webcam framerate can be lower or higher than this. If it is lower then
  230. // a frame is written multiple times, if higher, then some frames are now written.
  231. float captureStartTime = Time.realtimeSinceStartup;
  232. int realFrameNr, lastRealFrameNr = -1;
  233. do {
  234. // Read a frame from webcam. It returns a frame number, but we're not using it.
  235. byte[] buf;
  236. frame = streamer.VideoPosition;
  237. int bytesCnt = streamer.ReadVideoFrame (out buf);
  238. // Calculate the video frame number that we should be writing.
  239. realFrameNr = Mathf.RoundToInt ((Time.realtimeSinceStartup - captureStartTime) * vi.framerate);
  240. // If the loop is being executed too seldom compared to vi.framerate, write a warning to console.
  241. if (realFrameNr - lastRealFrameNr > 1) {
  242. Debug.LogWarning ("Output framerate too high, possibly just skipped " + (realFrameNr - lastRealFrameNr) + " frames");
  243. }
  244. // Write as many frames as we need. Normally this is 0 or 1, but can be higher (see the warning a few lines above)
  245. while (lastRealFrameNr < realFrameNr) {
  246. remux.WriteNextVideoFrame (buf, bytesCnt);
  247. lastRealFrameNr ++;
  248. }
  249. // Give control back to Unity for one frame
  250. yield return 1;
  251. } while(realFrameNr < 150);
  252. // We're done. Close the remux and streamer
  253. remux.Shutdown ();
  254. streamer.Shutdown ();
  255. Debug.Log ("Done capturing");
  256. }
  257. [ContextMenu("Extract raw video")]
  258. void ExtractRawVideo ()
  259. {
  260. #if UNITY_WEBPLAYER
  261. Debug.Log("This example doesn't work in web player");
  262. #else
  263. // ExtractRawVideo is thread safe. Multiple instances of it can run on the same System.IO.Stream
  264. RunInBackgroundOrNot (delegate() {
  265. File.WriteAllBytes (outfile, MoviePlayerUtil.ExtractRawVideo (File.OpenRead (infile)));
  266. });
  267. #endif
  268. }
  269. [ContextMenu("Extract raw audio")]
  270. void ExtractRawAudio ()
  271. {
  272. #if UNITY_WEBPLAYER
  273. Debug.Log("This example doesn't work in web player");
  274. #else
  275. // ExtractRawVideo is thread safe. Multiple instances of it can run on the same System.IO.Stream
  276. RunInBackgroundOrNot (delegate() {
  277. File.WriteAllBytes (outfile, MoviePlayerUtil.ExtractRawAudio (File.OpenRead (infile)));
  278. });
  279. #endif
  280. }
  281. void OnGUI ()
  282. {
  283. GUI.depth = -1;
  284. GUI.Label (new Rect (10, 3, Screen.width - 220, 70), "Open ScriptingDemoUI.cs an see what these methods do, then try them out. Most of these don't need PLAY mode");
  285. int buttonHeight = 25;
  286. GUILayout.BeginArea (new Rect (10, 40, Screen.width - 220, 200));
  287. GUILayout.BeginHorizontal ();
  288. if (GUILayout.Button ("LoadFileAndPlayForever", GUILayout.Height (buttonHeight))) {
  289. LoadFileAndPlayForever ();
  290. }
  291. if (GUILayout.Button ("ConnectAndPlayAStream", GUILayout.Height (buttonHeight))) {
  292. ConnectAndPlayAStream ();
  293. }
  294. if (GUILayout.Button ("StopAndUnload", GUILayout.Height (buttonHeight))) {
  295. StopAndUnload ();
  296. }
  297. if (GUILayout.Button ("ExtractOneFrame", GUILayout.Height (buttonHeight))) {
  298. ExtractOneFrame ();
  299. }
  300. if (GUILayout.Button ("DropHalfTheFramesRemux", GUILayout.Height (buttonHeight))) {
  301. DropHalfTheFramesRemux ();
  302. }
  303. GUILayout.EndHorizontal ();
  304. GUILayout.BeginHorizontal ();
  305. if (GUILayout.Button ("InstantMovieSwitching", GUILayout.Height (buttonHeight))) {
  306. InstantMovieSwitching ();
  307. }
  308. if (GUILayout.Button ("ExtractRawVideo", GUILayout.Height (buttonHeight))) {
  309. ExtractRawVideo ();
  310. }
  311. if (GUILayout.Button ("ExtractRawAudio", GUILayout.Height (buttonHeight))) {
  312. ExtractRawVideo ();
  313. }
  314. if (GUILayout.Button ("CaptureWebcamToFile", GUILayout.Height (buttonHeight))) {
  315. CaptureWebcamToFile ();
  316. }
  317. GUILayout.EndHorizontal ();
  318. GUILayout.EndArea ();
  319. // me
  320. GUI.Label (new Rect (Screen.width - 170, 10, 200, 70),
  321. "MoviePlayer " + MoviePlayer.PACKAGE_VERSION + "\n" +
  322. "scripting demo\n" +
  323. "by SHUU Games 2014");
  324. }
  325. #region ----- simple threading -----
  326. delegate void Action ();
  327. void RunInBackgroundOrNot (Action action)
  328. {
  329. if (runInSeparateThread) {
  330. var worker = new BackgroundWorker ();
  331. worker.DoWork += delegate(object sender, DoWorkEventArgs e) {
  332. action ();
  333. };
  334. worker.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs e) {
  335. Debug.Log ("Background thread done");
  336. };
  337. worker.RunWorkerAsync ();
  338. } else {
  339. action ();
  340. Debug.Log ("Done");
  341. }
  342. }
  343. #endregion
  344. }