JPGRecorder.cs 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. /*
  2. * NatCorder
  3. * Copyright (c) 2020 Yusuf Olokoba.
  4. */
  5. namespace NatSuite.Recorders {
  6. using UnityEngine;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Runtime.InteropServices;
  11. using System.Threading.Tasks;
  12. using Internal;
  13. /// <summary>
  14. /// JPG image sequence recorder.
  15. /// This recorder is currently supported on macOS and Windows.
  16. /// This recorder is NOT thread-safe, and as such it is not fully compliant with the `IMediaRecorder` interfacex.
  17. /// </summary>
  18. public sealed class JPGRecorder : IMediaRecorder {
  19. #region --Client API--
  20. /// <summary>
  21. /// Image size.
  22. /// </summary>
  23. public (int width, int height) frameSize => (framebuffer.width, framebuffer.height);
  24. /// <summary>
  25. /// Create a JPG recorder.
  26. /// </summary>
  27. /// <param name="imageWidth">Image width.</param>
  28. /// <param name="imageHeight">Image height.</param>
  29. public JPGRecorder (int imageWidth, int imageHeight) {
  30. // Save state
  31. this.framebuffer = new Texture2D(imageWidth, imageHeight, TextureFormat.RGBA32, false, false);
  32. this.writeTasks = new List<Task>();
  33. // Create directory
  34. this.recordingPath = Utility.GetPath(string.Empty);
  35. Directory.CreateDirectory(recordingPath);
  36. }
  37. /// <summary>
  38. /// Commit a video pixel buffer for encoding.
  39. /// The pixel buffer MUST have an RGBA8888 pixel layout.
  40. /// </summary>
  41. /// <param name="pixelBuffer">Pixel buffer containing video frame to commit.</param>
  42. /// <param name="timestamp">Not used.</param>
  43. public void CommitFrame<T> (T[] pixelBuffer, long timestamp = default) where T : struct {
  44. var handle = GCHandle.Alloc(pixelBuffer, GCHandleType.Pinned);
  45. CommitFrame(handle.AddrOfPinnedObject(), timestamp);
  46. handle.Free();
  47. }
  48. /// <summary>
  49. /// Commit a video pixel buffer for encoding.
  50. /// The pixel buffer MUST have an RGBA8888 pixel layout.
  51. /// </summary>
  52. /// <param name="nativeBuffer">Pixel buffer in native memory to commit.</param>
  53. /// <param name="timestamp">Not used.</param>
  54. public void CommitFrame (IntPtr nativeBuffer, long timestamp = default) {
  55. // Encode immediately
  56. framebuffer.LoadRawTextureData(nativeBuffer, framebuffer.width * framebuffer.height * 4);
  57. var frameData = ImageConversion.EncodeToJPG(framebuffer);
  58. // Write out on a worker thread
  59. var frameIndex = ++frameCount;
  60. writeTasks.Add(Task.Run(() => File.WriteAllBytes(Path.Combine(recordingPath, $"{frameIndex}.jpg"), frameData)));
  61. }
  62. /// <summary>
  63. /// This recorder does not support committing audio samples.
  64. /// </summary>
  65. public void CommitSamples (float[] sampleBuffer = default, long timestamp = default) { }
  66. /// <summary>
  67. /// Finish writing and return the path to the recorded media file.
  68. /// </summary>
  69. public Task<string> FinishWriting () {
  70. Texture2D.Destroy(framebuffer);
  71. return Task.WhenAll(writeTasks).ContinueWith(_ => recordingPath);
  72. }
  73. #endregion
  74. #region --Operations--
  75. private readonly Texture2D framebuffer;
  76. private readonly string recordingPath;
  77. private readonly List<Task> writeTasks;
  78. private int frameCount;
  79. #endregion
  80. }
  81. }