// %BANNER_BEGIN% // --------------------------------------------------------------------- // %COPYRIGHT_BEGIN% // Copyright (c) 2022-2023 Magic Leap, Inc. All Rights Reserved. // Use of this file is governed by the Magic Leap 2 Software License Agreement, located here: https://www.magicleap.com/software-license-agreement-ml2 // Terms and conditions applicable to third-party materials accompanying this distribution may also be found in the top-level NOTICE file appearing herein. // %COPYRIGHT_END% // --------------------------------------------------------------------- // %BANNER_END% #if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN #define WIN #endif #if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX #define MAC #endif #if !UNITY_EDITOR && UNITY_ANDROID #define ML2 #endif using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.InteropServices; using AOT; using UnityEngine; namespace MagicLeap.Spectator { #region C++ API public class AACEncoderAPI { protected delegate void Callback(int id, IntPtr buffer, int length); protected enum Result { NOT_INITIALIZED = -1, FAIL = 0, SUCCESS = 1, } #if WIN || MAC || ML2 #if MAC private const string EncoderLib = "__Internal"; #elif WIN || ML2 private const string EncoderLib = "aac_encoder"; #endif [DllImport(EncoderLib, CallingConvention = CallingConvention.Cdecl)] protected static extern int AACEncoder_Create(int sampleRate, int channels, int bitrate, Callback callback); [DllImport(EncoderLib, CallingConvention = CallingConvention.Cdecl)] protected static extern Result AACEncoder_Destroy(int id); [DllImport(EncoderLib, CallingConvention = CallingConvention.Cdecl)] protected static extern Result AACEncoder_Push(int id, in float data, int length, long hnsTimestamp); #else protected static int AACEncoder_Create(int sampleRate, int channels, int bitrate, Callback callback) => 0; protected static Result AACEncoder_Destroy(int id) => Result.NOT_INITIALIZED; protected static Result AACEncoder_Push(int id, in float data, int length, long hnsTimestamp) => Result.NOT_INITIALIZED; #endif } #endregion #region C# API public class AACEncoder : AACEncoderAPI, IDisposable { #region Static private static Dictionary encoderMap = new Dictionary(); [MonoPInvokeCallback(typeof(Callback))] private static void OnCallback(int id, IntPtr buffer, int length) { if (encoderMap.TryGetValue(id, out AACEncoder encoder)) encoder.OnCallback(buffer, length); } public static AACEncoder Create(int sampleRate, int channels, int bitrate) { int id = AACEncoder_Create(sampleRate, channels, bitrate, OnCallback); if (id < 1) { Debug.LogError("AudioEncoder: Create() - Failed to create audio encoder"); return null; } Debug.Log("AudioEncoder: Create() - Succeeded"); return new AACEncoder(id); } #endregion #region Private variables private int id = -1; private ConcurrentQueue timestamps = new(); #endregion #region Public events public event Action onOutput; #endregion #region Constructor / Destructor private AACEncoder(int id) { this.id = id; encoderMap.Add(id, this); } ~AACEncoder() { Dispose(); } #endregion #region Private methods private void OnCallback(IntPtr buffer, int length) { timestamps.TryDequeue(out ulong timestamp); onOutput?.Invoke(buffer, length, timestamp); } #endregion #region Public methods public bool Push(ReadOnlySpan buffer, ulong nsTimestamp) { ref readonly float audioPtr = ref MemoryMarshal.GetReference(buffer); var result = AACEncoder_Push(id, audioPtr, buffer.Length, (long)(nsTimestamp / 100)); if (result != Result.SUCCESS) Debug.LogError("AudioEncoder: Push() - Failed"); else timestamps.Enqueue(nsTimestamp); return result == Result.SUCCESS; } public void Dispose() { if (id < 0) return; if (AACEncoder_Destroy(id) != Result.SUCCESS) { Debug.LogError("AudioEncoder: Dispose() - Failed to destroy encoder"); //return; } id = -1; } #endregion } #endregion }