diff --git a/Editor/Sources/BaseTextureRecorder.cs b/Editor/Sources/BaseTextureRecorder.cs
index 6958f2f..30d871a 100644
--- a/Editor/Sources/BaseTextureRecorder.cs
+++ b/Editor/Sources/BaseTextureRecorder.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using UnityEditor.Recorder.Input;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
@@ -11,7 +13,7 @@ namespace UnityEditor.Recorder
/// The class implementing the Recorder Settings.
public abstract class BaseTextureRecorder : GenericRecorder where T : RecorderSettings
{
- int m_OngoingAsyncGPURequestsCount;
+ Dictionary m_OngoingRequests; // live requests and the frame's timestamp
bool m_DelayedEncoderDispose;
///
@@ -33,7 +35,7 @@ protected internal override bool BeginRecording(RecordingSession session)
return false;
UseAsyncGPUReadback = SystemInfo.supportsAsyncGPUReadback;
- m_OngoingAsyncGPURequestsCount = 0;
+ m_OngoingRequests = new Dictionary();
m_DelayedEncoderDispose = false;
return true;
}
@@ -53,9 +55,10 @@ protected internal override void RecordFrame(RecordingSession session)
if (UseAsyncGPUReadback)
{
- AsyncGPUReadback.Request(
+ var request = AsyncGPUReadback.Request(
renderTexture, 0, ReadbackTextureFormat, ReadbackDone);
- ++m_OngoingAsyncGPURequestsCount;
+ // ASG: Use the audio time for determining frame timestamps. This guarantees audio always lines up.
+ m_OngoingRequests.Add(request, ((AudioInputBase) m_Inputs[1]).audioTime);
return;
}
@@ -76,10 +79,10 @@ protected internal override void RecordFrame(RecordingSession session)
private void ReadbackDone(AsyncGPUReadbackRequest r)
{
Profiler.BeginSample("BaseTextureRecorder.ReadbackDone");
- WriteFrame(r);
+ WriteFrame(r, m_OngoingRequests[r]);
Profiler.EndSample();
- --m_OngoingAsyncGPURequestsCount;
- if (m_OngoingAsyncGPURequestsCount == 0 && m_DelayedEncoderDispose)
+ m_OngoingRequests.Remove(r);
+ if (m_OngoingRequests.Count == 0 && m_DelayedEncoderDispose)
DisposeEncoder();
}
@@ -87,7 +90,7 @@ private void ReadbackDone(AsyncGPUReadbackRequest r)
protected internal override void EndRecording(RecordingSession session)
{
base.EndRecording(session);
- if (m_OngoingAsyncGPURequestsCount > 0)
+ if (m_OngoingRequests.Count > 0)
{
Recording = true;
m_DelayedEncoderDispose = true;
@@ -105,7 +108,7 @@ private Texture2D CreateReadbackTexture(int width, int height)
/// Writes the frame from an asynchronous GPU read request.
///
/// The asynchronous readback target.
- protected virtual void WriteFrame(AsyncGPUReadbackRequest r)
+ protected virtual void WriteFrame(AsyncGPUReadbackRequest r, double timestamp)
{
if (m_ReadbackTexture == null)
m_ReadbackTexture = CreateReadbackTexture(r.width, r.height);
diff --git a/Editor/Sources/RecorderControllerSettings.cs b/Editor/Sources/RecorderControllerSettings.cs
index 922ee33..dcea532 100644
--- a/Editor/Sources/RecorderControllerSettings.cs
+++ b/Editor/Sources/RecorderControllerSettings.cs
@@ -166,7 +166,7 @@ public static RecorderControllerSettings GetGlobalSettings()
return LoadOrCreate(globalPath);
}
- internal void ReleaseRecorderSettings()
+ public void ReleaseRecorderSettings()
{
foreach (var recorder in m_RecorderSettings)
{
diff --git a/Editor/Sources/Recorders/AudioRecorder/AudioRecorder.cs b/Editor/Sources/Recorders/AudioRecorder/AudioRecorder.cs
index f42119e..80bc25f 100644
--- a/Editor/Sources/Recorders/AudioRecorder/AudioRecorder.cs
+++ b/Editor/Sources/Recorders/AudioRecorder/AudioRecorder.cs
@@ -27,7 +27,7 @@ protected internal override bool BeginRecording(RecordingSession session)
return false;
}
- var audioInput = (AudioInput)m_Inputs[0];
+ var audioInput = (UnityAudioInput)m_Inputs[0];
var audioAttrsList = new List();
if (audioInput.audioSettings.PreserveAudio)
@@ -67,7 +67,7 @@ protected internal override bool BeginRecording(RecordingSession session)
protected internal override void RecordFrame(RecordingSession session)
{
- var audioInput = (AudioInput)m_Inputs[0];
+ var audioInput = (UnityAudioInput)m_Inputs[0];
if (!audioInput.audioSettings.PreserveAudio)
return;
diff --git a/Editor/Sources/Recorders/MovieRecorder/MovieRecorder.cs b/Editor/Sources/Recorders/MovieRecorder/MovieRecorder.cs
index c08118f..0fea3ab 100644
--- a/Editor/Sources/Recorders/MovieRecorder/MovieRecorder.cs
+++ b/Editor/Sources/Recorders/MovieRecorder/MovieRecorder.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using Core.Harness;
+using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEditor.Recorder;
@@ -29,6 +31,19 @@ class MovieRecorder : BaseTextureRecorder
///
private bool m_RecordingStartedProperly = false;
+ ///
+ /// The frame rate the encoder is set to. This is fixed for the entire recording. Video encoders use rational
+ /// numbers for frame rates to avoid rounding error.
+ ///
+ private MediaRational m_FrameRate;
+
+ [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
+ private static void EnterPlayMode()
+ {
+ s_ConcurrentCount = 0;
+ s_WarnedUserOfConcurrentCount = false;
+ }
+
protected override TextureFormat ReadbackTextureFormat
{
get
@@ -85,23 +100,26 @@ protected internal override bool BeginRecording(RecordingSession session)
return false;
}
+ // In variable frame rate mode, we set the encoder to the frame rate of the current display.
+ m_FrameRate = RationalFromDouble(
+ session.settings.FrameRatePlayback == FrameRatePlayback.Variable
+ ? GameHarness.DisplayFPSTarget
+ : session.settings.FrameRate);
+
var videoAttrs = new VideoTrackAttributes
{
- frameRate = RationalFromDouble(session.settings.FrameRate),
width = (uint)width,
height = (uint)height,
+ frameRate = m_FrameRate,
includeAlpha = alphaWillBeInImage,
bitRateMode = Settings.VideoBitRateMode
};
- if (RecorderOptions.VerboseMode)
- Debug.Log(
- string.Format(
- "MovieRecorder starting to write video {0}x{1}@[{2}/{3}] fps into {4}",
- width, height, videoAttrs.frameRate.numerator,
- videoAttrs.frameRate.denominator, Settings.fileNameGenerator.BuildAbsolutePath(session)));
+ Debug.Log($"(UnityRecorder/MovieRecorder) Encoding video " +
+ $"{width}x{height}@[{videoAttrs.frameRate.numerator}/{videoAttrs.frameRate.denominator}] fps into " +
+ $"{Settings.fileNameGenerator.BuildAbsolutePath(session)}");
- var audioInput = (AudioInput)m_Inputs[1];
+ var audioInput = (AudioInputBase) m_Inputs[1];
var audioAttrsList = new List();
if (audioInput.audioSettings.PreserveAudio)
@@ -189,7 +207,7 @@ protected internal override void RecordFrame(RecordingSession session)
throw new Exception("Unsupported number of sources");
base.RecordFrame(session);
- var audioInput = (AudioInput)m_Inputs[1];
+ var audioInput = (AudioInputBase) m_Inputs[1];
if (audioInput.audioSettings.PreserveAudio)
Settings.m_EncoderManager.AddSamples(m_EncoderHandle, audioInput.mainBuffer);
}
@@ -225,11 +243,42 @@ protected override void WriteFrame(Texture2D t)
WarnOfConcurrentRecorders();
}
+ private long lastFrame = -1;
+
#if UNITY_2019_1_OR_NEWER
- protected override void WriteFrame(AsyncGPUReadbackRequest r)
+ protected override void WriteFrame(AsyncGPUReadbackRequest r, double timestamp)
{
+ double currentTime = ((AudioInputBase)m_Inputs[1]).audioTime;
+ if (currentTime - timestamp > 2)
+ {
+ Debug.Log($"(MovieRecorder) Received heavily delayed frame. Requested at [{timestamp}]. Received at [{currentTime}].");
+ }
+
var format = Settings.GetCurrentEncoder().GetTextureFormat(Settings);
- Settings.m_EncoderManager.AddFrame(m_EncoderHandle, r.width, r.height, 0, format, r.GetData());
+
+ if (Settings.FrameRatePlayback == FrameRatePlayback.Variable)
+ {
+ // The closest media frame to the actual frame's timestamp.
+ // Convert m_FrameRate using floating-point division. The overloaded cast-to-double operator uses integer division.
+ MediaTime time = new MediaTime
+ {
+ count = (long) Math.Round(timestamp * m_FrameRate.numerator / m_FrameRate.denominator),
+ rate = m_FrameRate
+ };
+
+ if (time.count > lastFrame) // If two render frames fall on the same encoding frame, ignore.
+ {
+ Settings.m_EncoderManager.AddFrame(m_EncoderHandle, r.width, r.height, 0, format, r.GetData(),
+ time);
+ }
+
+ lastFrame = time.count;
+ }
+ else
+ {
+ Settings.m_EncoderManager.AddFrame(m_EncoderHandle, r.width, r.height, 0, format, r.GetData());
+ }
+
WarnOfConcurrentRecorders();
}
diff --git a/Editor/Sources/Recorders/MovieRecorder/MovieRecorderSettings.cs b/Editor/Sources/Recorders/MovieRecorder/MovieRecorderSettings.cs
index 7a5aaa9..cc6ddd6 100644
--- a/Editor/Sources/Recorders/MovieRecorder/MovieRecorderSettings.cs
+++ b/Editor/Sources/Recorders/MovieRecorder/MovieRecorderSettings.cs
@@ -230,6 +230,24 @@ public MovieRecorderSettings()
RegisterAllEncoders();
}
+ ///
+ /// (ASG) Some types in Roslyn can't be loaded via GetTypes. Filter those out.
+ ///
+ private Type[] GetValidTypes(Assembly a)
+ {
+ Type[] allTypes;
+ try
+ {
+ allTypes = a.GetTypes();
+ }
+ catch (ReflectionTypeLoadException e)
+ {
+ allTypes = e.Types.Where(t => t != null).ToArray();
+ }
+
+ return allTypes;
+ }
+
///
/// Find all the encoders by looking at the content of the current assemblies.
///
@@ -239,7 +257,7 @@ private void RegisterAllEncoders()
// For all assemblies find all MediaEncoderRegister
foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
{
- var allTypes = a.GetTypes();
+ var allTypes = GetValidTypes(a);
var encoders = allTypes.Where(
type => type.IsSubclassOf(typeof(MediaEncoderRegister))
);
@@ -307,9 +325,27 @@ protected internal override bool ValidityCheck(List errors)
{
var ok = base.ValidityCheck(errors);
- if (FrameRatePlayback == FrameRatePlayback.Variable)
+ // Note(John): Rendering a constant video frame rate with fmod is challenging. Normally, UnityRecorder
+ // approaches rendering constant framerates by setting Time.captureFrameRate. This forces the entire
+ // game to run *slower* than the target frame rate, but overrides Time.deltaTime every frame to be fixed.
+ //
+ // This lets a game capture device take as long as it likes to render and capture each frame. This is
+ // normally great, but FMOD audio is on a separate thread, and is totally unaffected by Time.captureFrameRate.
+ //
+ // The end result is constant video playback, but where audio runs way ahead of the video, leading to heavy
+ // desyncs, and hangs within the Unity MediaEncoder. This issue would have to be solved by putting FMOD into
+ // its synchronous mode, stepping it only for every real frame update. This is possible, but needs work.
+ //
+ // On the whole, recording a constant frame rate is weird in VR. InputRecordings only look smooth if they
+ // are played back with the same frame timings (this is why when you record in fast-forward it looks weird
+ // when played back at normal speed). So if we want to take a video recording with a constant frame rate,
+ // the played InputRecording must have been recorded at *exactly that frame rate, with no hiccups*. Possible,
+ // but a little tricky. Needs further exploration.
+ if (FrameRatePlayback == FrameRatePlayback.Constant &&
+ AudioInputSettings.InputType == typeof(FmodAudioInput))
{
- errors.Add("Movie recorder does not properly support Variable frame rate playback. Please consider using Constant frame rate instead");
+ errors.Add("MovieRecorder does not support recording FMOD Audio with a constant video frame rate. " +
+ "Please use a variable frame rate, instead.");
ok = false;
}
diff --git a/Editor/Sources/Recorders/_Inputs/Audio/AudioInput.cs b/Editor/Sources/Recorders/_Inputs/Audio/AudioInput.cs
index d75324d..afe6ca2 100644
--- a/Editor/Sources/Recorders/_Inputs/Audio/AudioInput.cs
+++ b/Editor/Sources/Recorders/_Inputs/Audio/AudioInput.cs
@@ -1,7 +1,12 @@
using System;
+using System.Collections.Generic;
using UnityEngine;
using System.Reflection;
+using FMOD;
+using FMODUnity;
using Unity.Collections;
+using UnityEngine.Assertions;
+using Debug = UnityEngine.Debug;
namespace UnityEditor.Recorder.Input
{
@@ -61,7 +66,16 @@ public static void Render(NativeArray buffer)
}
}
- class AudioInput : RecorderInput
+ internal abstract class AudioInputBase : RecorderInput
+ {
+ public abstract ushort channelCount { get; }
+ public abstract int sampleRate { get; }
+ public abstract NativeArray mainBuffer { get; }
+ public abstract AudioInputSettings audioSettings { get; }
+ public abstract double audioTime { get; }
+ }
+
+ class UnityAudioInput : AudioInputBase
{
class BufferManager : IDisposable
{
@@ -88,29 +102,32 @@ public void Dispose()
ushort m_ChannelCount;
- public ushort channelCount
+ public override ushort channelCount
{
get { return m_ChannelCount; }
}
- public int sampleRate
+ public override int sampleRate
{
get { return AudioSettings.outputSampleRate; }
}
- public NativeArray mainBuffer
+ public override NativeArray mainBuffer
{
get { return s_BufferManager.GetBuffer(0); }
}
- static AudioInput s_Handler;
+ static UnityAudioInput s_Handler;
static BufferManager s_BufferManager;
- public AudioInputSettings audioSettings
+ public override AudioInputSettings audioSettings
{
get { return (AudioInputSettings)settings; }
}
+ private long recordedSamples = 0;
+ public override double audioTime => (double) recordedSamples / sampleRate;
+
protected internal override void BeginRecording(RecordingSession session)
{
m_ChannelCount = new Func(() => {
@@ -157,6 +174,7 @@ protected internal override void NewFrameReady(RecordingSession session)
s_BufferManager = new BufferManager(bufferCount, sampleFrameCount, m_ChannelCount);
AudioRenderer.Render(mainBuffer);
+ recordedSamples += sampleFrameCount;
}
}
@@ -181,4 +199,242 @@ protected internal override void EndRecording(RecordingSession session)
AudioRenderer.Stop();
}
}
+
+ ///
+ /// (ASG) An Audio Input for FMOD. This reads the raw audio coming out of the FMOD system and forwards it to Unity Recorder.
+ /// Implemented as a custom DSP that is added to the end of the FMOD Master Bus. We read audio blocks from the DSP callback.
+ ///
+ class FmodAudioInput : AudioInputBase
+ {
+ private ushort mChannelCount;
+ public override ushort channelCount => mChannelCount;
+
+ private int mSampleRate;
+ public override int sampleRate => mSampleRate;
+
+ // A list of received audio blocks waiting to be encoded. Stores blocks until we send them to Unity Recorder in NewFrameReady.
+ // These arrays are reused every frame, as blocks are always the same size.
+ private readonly List mixBlockQueue = new List();
+ private int mixBlockQueueSize;
+
+ // If the game is paused, or hung, the audio will continue to accumulate up to this number of blocks, before
+ // cutting. This keeps audio smooth, even with hiccups in the video feed.
+ // Note: 512 blocks at 48000Hz is about 10 seconds of audio, and about 15Mb of memory.
+ private const int MaxBlockQueueSize = 512;
+
+ private NativeArray mMainBuffer; // Allocated temp every frame, with all the unencoded samples
+ public override NativeArray mainBuffer => mMainBuffer;
+
+ public override AudioInputSettings audioSettings => (AudioInputSettings) settings;
+
+ private long sampleFrames = 0;
+ public override double audioTime => (double) sampleFrames / sampleRate;
+
+ // Keep a reference to the dsp callback so it doesn't get garbage collected.
+ private static DSP_READCALLBACK dspCallback;
+ private DSP dsp;
+
+ protected internal override void BeginRecording(RecordingSession session)
+ {
+ var dspName = "RecordSessionVideo(Audio)".ToCharArray();
+ Array.Resize(ref dspName, 32);
+ dspCallback = DspReadCallback;
+ var dspDescription = new DSP_DESCRIPTION
+ {
+ version = 0x00010000,
+ name = dspName,
+ numinputbuffers = 1,
+ numoutputbuffers = 1,
+ read = dspCallback,
+ numparameters = 0
+ };
+
+ FMOD.System system = RuntimeManager.CoreSystem;
+ CheckError(system.getMasterChannelGroup(out ChannelGroup masterGroup));
+ CheckError(masterGroup.getDSP(CHANNELCONTROL_DSP_INDEX.TAIL, out DSP masterDspTail));
+ CheckError(masterDspTail.getChannelFormat(out CHANNELMASK channelMask, out int numChannels,
+ out SPEAKERMODE sourceSpeakerMode));
+
+ if (RecorderOptions.VerboseMode)
+ {
+ Debug.Log(
+ $"(UnityRecorder) Listening to FMOD Audio. Setting DSP to [{channelMask}] [{numChannels}] [{sourceSpeakerMode}]");
+ }
+
+ // Create a new DSP with the format of the existing master group.
+ CheckError(system.createDSP(ref dspDescription, out dsp));
+ CheckError(dsp.setChannelFormat(channelMask, numChannels, sourceSpeakerMode));
+ CheckError(masterGroup.addDSP(CHANNELCONTROL_DSP_INDEX.TAIL, dsp));
+
+ // Fill in some basic information for the Unity audio encoder.
+ mChannelCount = (ushort) numChannels;
+ CheckError(system.getDriver(out int driverId));
+ CheckError(system.getDriverInfo(driverId, out Guid _, out int systemRate, out SPEAKERMODE _, out int _));
+ mSampleRate = systemRate;
+
+ if (RecorderOptions.VerboseMode)
+ Debug.Log($"FmodAudioInput.BeginRecording for capture frame rate {Time.captureFramerate}");
+
+ if (audioSettings.PreserveAudio)
+ AudioRenderer.Start();
+ }
+
+ protected internal override void NewFrameReady(RecordingSession session)
+ {
+ try
+ {
+ int totalReadBlocks;
+ int totalFloats = 0;
+ lock (mixBlockQueue)
+ {
+ totalReadBlocks = mixBlockQueueSize;
+ for (int i = 0; i < totalReadBlocks; i++)
+ {
+ if (totalFloats / channelCount > 1 * sampleRate)
+ {
+ // The Unity MediaEncoder hangs when we send hundreds of thousands of audio samples.
+ // This can happen if the game is paused (ie. the audio continues to queue, but no new
+ // Updates are happening). Instead, we just cut the audio in those cases.
+ // This is a workaround for the lag spikes we were seeing when recording.
+ Debug.Log($"(FmodAudioInput) More than 2 seconds of audio samples [{totalFloats}] " +
+ $"queued up since the last submission. Only sending [{totalFloats}]. Dropping the rest.");
+ totalReadBlocks = i;
+ break;
+ }
+
+ totalFloats += mixBlockQueue[i].Length;
+ }
+ }
+
+ // Allocate a giant buffer with all of the samples, since the last frame.
+ // This is necessary because the Unity audio encoder expects a single native array.
+ mMainBuffer = new NativeArray(totalFloats, Allocator.Temp);
+
+ int index = 0;
+ for (int i = 0; i < totalReadBlocks; i++)
+ {
+ NativeArray.Copy(mixBlockQueue[i], 0, mMainBuffer, index, mixBlockQueue[i].Length);
+ index += mixBlockQueue[i].Length;
+ }
+
+ Assert.AreEqual(0, totalFloats % channelCount);
+ sampleFrames += totalFloats / channelCount;
+ }
+ finally
+ {
+ // Reset the list of blocks, so it can be reused.
+ lock (mixBlockQueue)
+ {
+ mixBlockQueueSize = 0;
+ }
+ }
+ }
+
+ ///
+ protected internal override void EndRecording(RecordingSession session)
+ {
+ base.EndRecording(session);
+
+ CheckError(RuntimeManager.CoreSystem.getMasterChannelGroup(out ChannelGroup master), shouldThrow: false);
+ CheckError(master.removeDSP(dsp), shouldThrow: false);
+
+ // This may throw an error, if EndRecording is called more than once for a single BeginRecording.
+ // However, this shouldn't be case, and should be treated as a bug, instead.
+ CheckError(dsp.release(), shouldThrow: false);
+
+ lock (mixBlockQueue)
+ {
+ mixBlockQueue.Clear();
+ mixBlockQueueSize = 0;
+ }
+ }
+
+ private RESULT DspReadCallback(ref DSP_STATE dspState, IntPtr inBuffer, IntPtr outBuffer, uint samples,
+ int inChannels, ref int outChannels)
+ {
+ try
+ {
+ // Debug.Log($"Received buffer of samples: {samples}, channels: {inChannels}");
+ Assert.AreEqual(inChannels, outChannels);
+
+ const int sampleSizeBytes = 4; // size of a float
+ int blockSizeFloats = (int) (samples * inChannels); // size of a float
+ int blockSizeBytes = blockSizeFloats * sampleSizeBytes;
+
+ // Pass the input through to the output, so we can still hear it.
+ unsafe
+ {
+ Buffer.MemoryCopy(inBuffer.ToPointer(), outBuffer.ToPointer(),
+ blockSizeBytes,
+ blockSizeBytes);
+ }
+
+ // Copy the audio into our buffer queue
+ lock (mixBlockQueue)
+ {
+ // If we've queued up too many blocks, without writing them to the encoder, just stop recording audio.
+ // This can happen if the game pauses, and game Update()'s stop.
+ if (mixBlockQueueSize < MaxBlockQueueSize)
+ {
+ float[] buffer;
+ if (mixBlockQueueSize == mixBlockQueue.Count)
+ {
+ // Add a new buffer if there are no empty buffers left in the list.
+ buffer = new float[blockSizeFloats];
+ mixBlockQueue.Add(buffer);
+ }
+ else
+ {
+ // Use the next free buffer.
+ buffer = mixBlockQueue[mixBlockQueueSize];
+
+ // Reallocate the buffer if the block size has changed (could happen if the audio device changes).
+ if (buffer.Length != blockSizeFloats)
+ {
+ buffer = new float[blockSizeFloats];
+ mixBlockQueue[mixBlockQueueSize] = buffer;
+ }
+ }
+
+ mixBlockQueueSize++;
+
+ // Copy the audio to the block list.
+ unsafe
+ {
+ fixed (float* bufferPtr = buffer)
+ {
+ Buffer.MemoryCopy(inBuffer.ToPointer(), bufferPtr,
+ blockSizeBytes, blockSizeBytes);
+ }
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.LogError("There was an error with DSP Processing.");
+ Debug.LogException(e);
+ return RESULT.ERR_DSP_DONTPROCESS;
+ }
+
+ return RESULT.OK;
+ }
+
+ // Checks for fmod errors.
+ public static void CheckError(RESULT result, bool shouldThrow = true)
+ {
+ if (result != RESULT.OK)
+ {
+ if (shouldThrow)
+ {
+ throw new Exception(result.ToString());
+ }
+ else
+ {
+ Debug.LogException(
+ new Exception("Got error from FMOD, but suppressing exception. ERROR: " + result));
+ }
+ }
+ }
+ }
}
diff --git a/Editor/Sources/Recorders/_Inputs/Audio/AudioInputSettings.cs b/Editor/Sources/Recorders/_Inputs/Audio/AudioInputSettings.cs
index 8751aed..b8a233d 100644
--- a/Editor/Sources/Recorders/_Inputs/Audio/AudioInputSettings.cs
+++ b/Editor/Sources/Recorders/_Inputs/Audio/AudioInputSettings.cs
@@ -25,7 +25,7 @@ public bool PreserveAudio
///
protected internal override Type InputType
{
- get { return typeof(AudioInput); }
+ get { return typeof(FmodAudioInput); }
}
///
diff --git a/Editor/Sources/RecordersInventory.cs b/Editor/Sources/RecordersInventory.cs
index 632a36f..de7faba 100644
--- a/Editor/Sources/RecordersInventory.cs
+++ b/Editor/Sources/RecordersInventory.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
+using System.Reflection;
using UnityEditor.Recorder.FrameCapturer;
namespace UnityEditor.Recorder
@@ -20,6 +21,24 @@ static class RecordersInventory
static HashSet s_BuiltInRecorderInfos;
static HashSet s_LegacyRecorderInfos;
+ ///
+ /// (ASG) Some types in Roslyn can't be loaded via GetTypes. Filter those out.
+ ///
+ private static Type[] GetValidTypes(Assembly a)
+ {
+ Type[] allTypes;
+ try
+ {
+ allTypes = a.GetTypes();
+ }
+ catch (ReflectionTypeLoadException e)
+ {
+ allTypes = e.Types.Where(t => t != null).ToArray();
+ }
+
+ return allTypes;
+ }
+
static IEnumerable> FindRecorders()
{
var attribType = typeof(RecorderSettingsAttribute);
@@ -28,7 +47,7 @@ static IEnumerable> FindRecorders()
Type[] types;
try
{
- types = a.GetTypes();
+ types = GetValidTypes(a);
}
catch (Exception)
{
diff --git a/Editor/Sources/RecordingSession.cs b/Editor/Sources/RecordingSession.cs
index b6bb3f2..4fea118 100644
--- a/Editor/Sources/RecordingSession.cs
+++ b/Editor/Sources/RecordingSession.cs
@@ -29,7 +29,7 @@ public RecorderSettings settings
internal bool isRecording
{
- get { return recorder.Recording; }
+ get { return recorder?.Recording ?? false; }
}
public int frameIndex
@@ -199,6 +199,7 @@ public void Dispose()
EndRecording();
UnityHelpers.Destroy(recorder);
+ recorder = null;
}
}
}
diff --git a/Editor/Unity.Recorder.Editor.asmdef b/Editor/Unity.Recorder.Editor.asmdef
index 30afc39..28944df 100644
--- a/Editor/Unity.Recorder.Editor.asmdef
+++ b/Editor/Unity.Recorder.Editor.asmdef
@@ -1,13 +1,21 @@
{
"name": "Unity.Recorder.Editor",
"references": [
- "Unity.Recorder.Base",
- "Unity.Recorder",
- "Unity.Timeline"
+ "Unity.Recorder.Base",
+ "Unity.Recorder",
+ "Unity.Timeline",
+ "Fmod.Runtime",
+ "Clockwork.Core.Harness"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
- "allowUnsafeCode": true
-}
+ "allowUnsafeCode": true,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file