using UnityEngine; using System.Collections.Generic; using System.IO; //----------------------------------------------------------------------------- // Copyright 2012-2022 RenderHeads Ltd. All rights reserved. //----------------------------------------------------------------------------- namespace RenderHeads.Media.AVProMovieCapture { /// /// Combine multiple AmbisonicSource streams into a single ambisonic mix /// We had to use an overcomplicated buffer queue system because the with the audio thread /// we have no idea which frame we are on, and so no reliable way to know when to write /// or accumulate the audio into the mix. /// [AddComponentMenu("AVPro Movie Capture/Audio/Ambisonic WAV Writer", 601)] public class AmbisonicWavWriter : MonoBehaviour { [SerializeField] CaptureBase _capture = null; [SerializeField] AmbisonicOrder _order = AmbisonicOrder.Third; [SerializeField] AmbisonicFormat _format = AmbisonicFormat.ACN_SN3D; [SerializeField] string _filename = "output.wav"; [SerializeField, Range(4, 32)] int _bufferCount = 16; private float[] _outSamples = null; private WavWriter _wavWriter = null; private List _sources = new List(8); private int _pendingSampleCount = 0; public AmbisonicOrder Order { get { return _order; } } public AmbisonicFormat Format { get { return _format; } } internal void AddSource(AmbisonicSource source) { lock (_sources) { SetupSource(source); _sources.Add(source); } } internal void RemoveSource(AmbisonicSource source) { lock (_sources) { _sources.Remove(source); } } void OnDisable() { StopCapture(); } void SetupSource(AmbisonicSource source) { source.Setup(_order, Ambisonic.GetChannelOrder(_format), Ambisonic.GetNormalisation(_format), _bufferCount); source.FlushBuffers(); } void ToggleCapturing(bool isCapturing) { if (isCapturing && !IsCapturing()) { StartCapture(); } else if (!isCapturing && IsCapturing()) { StopCapture(); } } void StartCapture() { #if UNITY_EDITOR if (UnityEditor.EditorApplication.isPaused) return; #endif Debug.Assert(_outSamples == null); Debug.Assert(_wavWriter == null); _pendingSampleCount = 0; int coeffCount = Ambisonic.GetCoeffCount(_order); Debug.Assert(coeffCount == 4 || coeffCount == 9 || coeffCount == 16); lock (this) { foreach (AmbisonicSource source in _sources) { SetupSource(source); } string path = Path.Combine(Application.persistentDataPath, _filename); if (_capture) { path = _capture.LastFilePath + ".wav"; } Debug.Log("[AVProMovieCapture] Writing Ambisonic WAV to " + path); _wavWriter = new WavWriter(path, coeffCount, AudioSettings.outputSampleRate, WavWriter.SampleFormat.Float32); _outSamples = new float[coeffCount * AudioSettings.outputSampleRate * 1]; // 1 second buffer } } void StopCapture() { if (_wavWriter != null) { lock (_wavWriter) { FlushWavWriter(); _wavWriter.Dispose(); _wavWriter = null; _outSamples = null; } } } bool IsCapturing() { return (_wavWriter != null && _outSamples != null); } void LateUpdate() { ToggleCapturing(_capture != null && _capture.IsCapturing()); ProcessSources(isDraining:false); } void ProcessSources(bool isDraining) { if (!IsCapturing() || _capture.IsPaused()) return; lock(this) { if (_sources.Count > 0) { // Find the minimum number of full buffers across all sources int minBuffers = int.MaxValue; for (int i = 0; i < _sources.Count; i++) { int bufferCount = _sources[i].GetFullBufferCount(); minBuffers = Mathf.Min(bufferCount, minBuffers); } // Process the minimum number of full buffers for (int j = 0; j < minBuffers; j++) { for (int i = 0; i < _sources.Count; i++) { // TODO: fix this draining - it doesn't take into account cases where some of the sources // still have a full buffer, but other's don't. _sources[i].SendSamplesToSink(i != 0, isDraining); } FlushWavWriter(); } } } } internal void MixSamples(float[] samples, int sampleCount, bool addSamples) { Debug.Assert(sampleCount < _outSamples.Length); if (sampleCount < _outSamples.Length) { if (!addSamples) { _pendingSampleCount = sampleCount; System.Buffer.BlockCopy(samples, 0, _outSamples, 0, sampleCount * sizeof(float)); } else { // Accumulate samples into the mix for (int i = 0; i < sampleCount; i++) { _outSamples[i] += samples[i]; } } } else { Debug.LogError("too many samples"); } } void FlushWavWriter() { if (_pendingSampleCount > 0) { _wavWriter.WriteInterleaved(_outSamples, _pendingSampleCount); _pendingSampleCount = 0; } } } }