using POpusCodec.Enums; using POpusCodec; using System; namespace Photon.Voice { public class OpusCodec { static public string Version { get { return OpusLib.Version; } } public enum FrameDuration { Frame2dot5ms = 2500, Frame5ms = 5000, Frame10ms = 10000, Frame20ms = 20000, Frame40ms = 40000, Frame60ms = 60000 } public static class Factory { static public IEncoder CreateEncoder(VoiceInfo i, ILogger logger) { if (typeof(B) == typeof(float[])) return new EncoderFloat(i, logger); else if (typeof(B) == typeof(short[])) return new EncoderShort(i, logger); else throw new UnsupportedCodecException("Factory.CreateEncoder<" + typeof(B) + ">", i.Codec); } } public static class DecoderFactory { public static IEncoder Create(VoiceInfo i, ILogger logger) { var x = new T[1]; if (x[0].GetType() == typeof(float)) return new EncoderFloat(i, logger); else if (x[0].GetType() == typeof(short)) return new EncoderShort(i, logger); else throw new UnsupportedCodecException("EncoderFactory.Create<" + x[0].GetType() + ">", i.Codec); } } abstract public class Encoder : IEncoderDirect { protected OpusEncoder encoder; protected bool disposed; protected Encoder(VoiceInfo i, ILogger logger) { try { encoder = new OpusEncoder((SamplingRate)i.SamplingRate, (Channels)i.Channels, i.Bitrate, OpusApplicationType.Voip, (Delay)(i.FrameDurationUs * 2 / 1000)); logger.LogInfo("[PV] OpusCodec.Encoder created. Opus version " + Version + ". Bitrate " + encoder.Bitrate + ". EncoderDelay " + encoder.EncoderDelay); } catch (Exception e) { Error = e.ToString(); if (Error == null) // should never happen but since Error used as validity flag, make sure that it's not null { Error = "Exception in OpusCodec.Encoder constructor"; } logger.LogError("[PV] OpusCodec.Encoder: " + Error); } } public string Error { get; private set; } public Action, FrameFlags> Output { set; get; } public void Input(T[] buf) { if (Error != null) { return; } if (Output == null) { Error = "OpusCodec.Encoder: Output action is not set"; return; } lock (this) { if (disposed || Error != null) { } else { var res = encodeTyped(buf); if (res.Count != 0) { Output(res, 0); } } } } public void EndOfStream() { lock (this) { if (disposed || Error != null) { } else { Output(EmptyBuffer, FrameFlags.EndOfStream); } } return; } private static readonly ArraySegment EmptyBuffer = new ArraySegment(new byte[] { }); public ArraySegment DequeueOutput(out FrameFlags flags) { flags = 0; return EmptyBuffer; } protected abstract ArraySegment encodeTyped(T[] buf); public I GetPlatformAPI() where I : class { return null; } public void Dispose() { lock (this) { if (encoder != null) { encoder.Dispose(); } disposed = true; } } } public class EncoderFloat : Encoder { internal EncoderFloat(VoiceInfo i, ILogger logger) : base(i, logger) { } override protected ArraySegment encodeTyped(float[] buf) { return encoder.Encode(buf); } } public class EncoderShort : Encoder { internal EncoderShort(VoiceInfo i, ILogger logger) : base(i, logger) { } override protected ArraySegment encodeTyped(short[] buf) { return encoder.Encode(buf); } } public class Decoder : IDecoder { protected OpusDecoder decoder; ILogger logger; public Decoder(Action> output, ILogger logger) { this.output = output; this.logger = logger; } public void Open(VoiceInfo i) { try { decoder = new OpusDecoder((SamplingRate)i.SamplingRate, (Channels)i.Channels); logger.LogInfo("[PV] OpusCodec.Decoder created. Opus version " + Version); } catch (Exception e) { Error = e.ToString(); if (Error == null) // should never happen but since Error used as validity flag, make sure that it's not null { Error = "Exception in OpusCodec.Decoder constructor"; } logger.LogError("[PV] OpusCodec.Decoder: " + Error); } } public string Error { get; private set; } private Action> output; public void Dispose() { if (decoder != null) { decoder.Dispose(); } } FrameOut frameOut = new FrameOut(null, false); public void Input(ref FrameBuffer buf) { if (Error == null) { bool endOfStream = (buf.Flags & FrameFlags.EndOfStream) != 0; if (endOfStream) { T[] res1 = null; T[] res2; // EndOfStream packet may have data // normally we do not send null with EndOfStream flag, but null is still valid here if (buf.Array == null && buf.Length > 0) { res1 = decoder.DecodePacket(ref buf); } // flush decoder res2 = decoder.DecodeEndOfStream(); // if res1 is empty, res2 has correct (possible empty) buffer for EndOfStream frame if (res1 != null && res1.Length == 0) { // output cal per res required if (res2 != null && res2.Length != 0) { output(frameOut.Set(res1, false)); } else { // swap results to reuse the code below res2 = res1; } } output(frameOut.Set(res2, true)); } else { T[] res; res = decoder.DecodePacket(ref buf); if (res.Length != 0) { output(frameOut.Set(res, false)); } } } } } public class Util { internal static int bestEncoderSampleRate(int f) { int diff = int.MaxValue; int res = (int)SamplingRate.Sampling48000; foreach (var x in Enum.GetValues(typeof(SamplingRate))) { var d = Math.Abs((int)x - f); if (d < diff) { diff = d; res = (int)x; } } return res; } } } }