2022-06-29 14:45:17 +03:00

269 lines
8.9 KiB
C#

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<B>(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<T>(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<T> : IEncoderDirect<T[]>
{
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<ArraySegment<byte>, 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<byte> EmptyBuffer = new ArraySegment<byte>(new byte[] { });
public ArraySegment<byte> DequeueOutput(out FrameFlags flags) { flags = 0; return EmptyBuffer; }
protected abstract ArraySegment<byte> encodeTyped(T[] buf);
public I GetPlatformAPI<I>() where I : class
{
return null;
}
public void Dispose()
{
lock (this)
{
if (encoder != null)
{
encoder.Dispose();
}
disposed = true;
}
}
}
public class EncoderFloat : Encoder<float>
{
internal EncoderFloat(VoiceInfo i, ILogger logger) : base(i, logger) { }
override protected ArraySegment<byte> encodeTyped(float[] buf)
{
return encoder.Encode(buf);
}
}
public class EncoderShort : Encoder<short>
{
internal EncoderShort(VoiceInfo i, ILogger logger) : base(i, logger) { }
override protected ArraySegment<byte> encodeTyped(short[] buf)
{
return encoder.Encode(buf);
}
}
public class Decoder<T> : IDecoder
{
protected OpusDecoder<T> decoder;
ILogger logger;
public Decoder(Action<FrameOut<T>> output, ILogger logger)
{
this.output = output;
this.logger = logger;
}
public void Open(VoiceInfo i)
{
try
{
decoder = new OpusDecoder<T>((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<FrameOut<T>> output;
public void Dispose()
{
if (decoder != null)
{
decoder.Dispose();
}
}
FrameOut<T> frameOut = new FrameOut<T>(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;
}
}
}
}