You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
7.4 KiB
230 lines
7.4 KiB
5 years ago
|
using System;
|
||
|
using System.Collections;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Rendering;
|
||
|
#if UNITY_EDITOR
|
||
|
using UnityEditor;
|
||
|
#endif
|
||
|
|
||
|
|
||
|
namespace UTJ.FrameCapturer
|
||
|
{
|
||
|
[AddComponentMenu("UTJ/FrameCapturer/Movie Recorder")]
|
||
|
[RequireComponent(typeof(Camera))]
|
||
|
[ExecuteInEditMode]
|
||
|
public class MovieRecorder : RecorderBase
|
||
|
{
|
||
|
#region inner_types
|
||
|
public enum CaptureTarget
|
||
|
{
|
||
|
FrameBuffer,
|
||
|
RenderTexture,
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
|
||
|
#region fields
|
||
|
[SerializeField] MovieEncoderConfigs m_encoderConfigs = new MovieEncoderConfigs(MovieEncoder.Type.WebM);
|
||
|
[SerializeField] CaptureTarget m_captureTarget = CaptureTarget.FrameBuffer;
|
||
|
[SerializeField] RenderTexture m_targetRT;
|
||
|
[SerializeField] bool m_captureVideo = true;
|
||
|
[SerializeField] bool m_captureAudio = true;
|
||
|
|
||
|
[SerializeField] Shader m_shCopy;
|
||
|
Material m_matCopy;
|
||
|
Mesh m_quad;
|
||
|
CommandBuffer m_cb;
|
||
|
RenderTexture m_scratchBuffer;
|
||
|
MovieEncoder m_encoder;
|
||
|
#endregion
|
||
|
|
||
|
|
||
|
#region properties
|
||
|
public CaptureTarget captureTarget
|
||
|
{
|
||
|
get { return m_captureTarget; }
|
||
|
set { m_captureTarget = value; }
|
||
|
}
|
||
|
public RenderTexture targetRT
|
||
|
{
|
||
|
get { return m_targetRT; }
|
||
|
set { m_targetRT = value; }
|
||
|
}
|
||
|
public bool captureAudio
|
||
|
{
|
||
|
get { return m_captureAudio; }
|
||
|
set { m_captureAudio = value; }
|
||
|
}
|
||
|
public bool captureVideo
|
||
|
{
|
||
|
get { return m_captureVideo; }
|
||
|
set { m_captureVideo = value; }
|
||
|
}
|
||
|
|
||
|
public bool supportVideo { get { return m_encoderConfigs.supportVideo; } }
|
||
|
public bool supportAudio { get { return m_encoderConfigs.supportAudio; } }
|
||
|
public RenderTexture scratchBuffer { get { return m_scratchBuffer; } }
|
||
|
#endregion
|
||
|
|
||
|
|
||
|
public override bool BeginRecording()
|
||
|
{
|
||
|
if (m_recording) { return false; }
|
||
|
if (m_shCopy == null)
|
||
|
{
|
||
|
Debug.LogError("MovieRecorder: copy shader is missing!");
|
||
|
return false;
|
||
|
}
|
||
|
if (m_captureTarget == CaptureTarget.RenderTexture && m_targetRT == null)
|
||
|
{
|
||
|
Debug.LogError("MovieRecorder: target RenderTexture is null!");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
m_outputDir.CreateDirectory();
|
||
|
if (m_quad == null) m_quad = fcAPI.CreateFullscreenQuad();
|
||
|
if (m_matCopy == null) m_matCopy = new Material(m_shCopy);
|
||
|
|
||
|
var cam = GetComponent<Camera>();
|
||
|
if (cam.targetTexture != null)
|
||
|
{
|
||
|
m_matCopy.EnableKeyword("OFFSCREEN");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_matCopy.DisableKeyword("OFFSCREEN");
|
||
|
}
|
||
|
|
||
|
// create scratch buffer
|
||
|
{
|
||
|
int captureWidth = cam.pixelWidth;
|
||
|
int captureHeight = cam.pixelHeight;
|
||
|
GetCaptureResolution(ref captureWidth, ref captureHeight);
|
||
|
if (m_encoderConfigs.format == MovieEncoder.Type.MP4 ||
|
||
|
m_encoderConfigs.format == MovieEncoder.Type.WebM)
|
||
|
{
|
||
|
captureWidth = (captureWidth + 1) & ~1;
|
||
|
captureHeight = (captureHeight + 1) & ~1;
|
||
|
}
|
||
|
|
||
|
m_scratchBuffer = new RenderTexture(captureWidth, captureHeight, 0, RenderTextureFormat.ARGB32);
|
||
|
m_scratchBuffer.wrapMode = TextureWrapMode.Repeat;
|
||
|
m_scratchBuffer.Create();
|
||
|
}
|
||
|
|
||
|
// initialize encoder
|
||
|
{
|
||
|
int targetFramerate = 60;
|
||
|
if(m_framerateMode == FrameRateMode.Constant)
|
||
|
{
|
||
|
targetFramerate = m_targetFramerate;
|
||
|
}
|
||
|
string outPath = m_outputDir.GetFullPath() + "/" + DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||
|
|
||
|
m_encoderConfigs.captureVideo = m_captureVideo;
|
||
|
m_encoderConfigs.captureAudio = m_captureAudio;
|
||
|
m_encoderConfigs.Setup(m_scratchBuffer.width, m_scratchBuffer.height, 3, targetFramerate);
|
||
|
m_encoder = MovieEncoder.Create(m_encoderConfigs, outPath);
|
||
|
if (m_encoder == null || !m_encoder.IsValid())
|
||
|
{
|
||
|
EndRecording();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// create command buffer
|
||
|
{
|
||
|
int tid = Shader.PropertyToID("_TmpFrameBuffer");
|
||
|
m_cb = new CommandBuffer();
|
||
|
m_cb.name = "MovieRecorder: copy frame buffer";
|
||
|
|
||
|
if(m_captureTarget == CaptureTarget.FrameBuffer)
|
||
|
{
|
||
|
m_cb.GetTemporaryRT(tid, -1, -1, 0, FilterMode.Bilinear);
|
||
|
m_cb.Blit(BuiltinRenderTextureType.CurrentActive, tid);
|
||
|
m_cb.SetRenderTarget(m_scratchBuffer);
|
||
|
m_cb.DrawMesh(m_quad, Matrix4x4.identity, m_matCopy, 0, 0);
|
||
|
m_cb.ReleaseTemporaryRT(tid);
|
||
|
}
|
||
|
else if(m_captureTarget == CaptureTarget.RenderTexture)
|
||
|
{
|
||
|
m_cb.SetRenderTarget(m_scratchBuffer);
|
||
|
m_cb.SetGlobalTexture("_TmpRenderTarget", m_targetRT);
|
||
|
m_cb.DrawMesh(m_quad, Matrix4x4.identity, m_matCopy, 0, 1);
|
||
|
}
|
||
|
cam.AddCommandBuffer(CameraEvent.AfterEverything, m_cb);
|
||
|
}
|
||
|
|
||
|
base.BeginRecording();
|
||
|
Debug.Log("MovieRecorder: BeginRecording()");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public override void EndRecording()
|
||
|
{
|
||
|
if (m_encoder != null)
|
||
|
{
|
||
|
m_encoder.Release();
|
||
|
m_encoder = null;
|
||
|
}
|
||
|
if (m_cb != null)
|
||
|
{
|
||
|
GetComponent<Camera>().RemoveCommandBuffer(CameraEvent.AfterEverything, m_cb);
|
||
|
m_cb.Release();
|
||
|
m_cb = null;
|
||
|
}
|
||
|
if (m_scratchBuffer != null)
|
||
|
{
|
||
|
m_scratchBuffer.Release();
|
||
|
m_scratchBuffer = null;
|
||
|
}
|
||
|
|
||
|
if (m_recording)
|
||
|
{
|
||
|
Debug.Log("MovieRecorder: EndRecording()");
|
||
|
}
|
||
|
base.EndRecording();
|
||
|
}
|
||
|
|
||
|
|
||
|
#region impl
|
||
|
#if UNITY_EDITOR
|
||
|
void Reset()
|
||
|
{
|
||
|
m_shCopy = fcAPI.GetFrameBufferCopyShader();
|
||
|
}
|
||
|
#endif // UNITY_EDITOR
|
||
|
|
||
|
IEnumerator OnPostRender()
|
||
|
{
|
||
|
if (m_recording && m_encoder != null && Time.frameCount % m_captureEveryNthFrame == 0)
|
||
|
{
|
||
|
yield return new WaitForEndOfFrame();
|
||
|
|
||
|
double timestamp = Time.unscaledTime - m_initialTime;
|
||
|
if (m_framerateMode == FrameRateMode.Constant)
|
||
|
{
|
||
|
timestamp = 1.0 / m_targetFramerate * m_recordedFrames;
|
||
|
}
|
||
|
|
||
|
fcAPI.fcLock(m_scratchBuffer, TextureFormat.RGB24, (data, fmt) =>
|
||
|
{
|
||
|
m_encoder.AddVideoFrame(data, fmt, timestamp);
|
||
|
});
|
||
|
++m_recordedFrames;
|
||
|
}
|
||
|
++m_frame;
|
||
|
}
|
||
|
|
||
|
void OnAudioFilterRead(float[] samples, int channels)
|
||
|
{
|
||
|
if (m_recording && m_encoder != null)
|
||
|
{
|
||
|
m_encoder.AddAudioSamples(samples);
|
||
|
m_recordedSamples += samples.Length;
|
||
|
}
|
||
|
}
|
||
|
#endregion
|
||
|
}
|
||
|
}
|