using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("OpusDotNet")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("OpusDotNet")]
[assembly: AssemblyTitle("OpusDotNet")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace OpusDotNet;
internal static class API
{
[DllImport("opus", CallingConvention = CallingConvention.Cdecl)]
public static extern SafeEncoderHandle opus_encoder_create(int Fs, int channels, int application, out int error);
[DllImport("opus", CallingConvention = CallingConvention.Cdecl)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
public static extern void opus_encoder_destroy(IntPtr st);
[DllImport("opus", CallingConvention = CallingConvention.Cdecl)]
public static extern int opus_encode(SafeEncoderHandle st, IntPtr pcm, int frame_size, IntPtr data, int max_data_bytes);
[DllImport("opus", CallingConvention = CallingConvention.Cdecl)]
public static extern int opus_encoder_ctl(SafeEncoderHandle st, int request, out int value);
[DllImport("opus", CallingConvention = CallingConvention.Cdecl)]
public static extern int opus_encoder_ctl(SafeEncoderHandle st, int request, int value);
[DllImport("opus", CallingConvention = CallingConvention.Cdecl)]
public static extern SafeDecoderHandle opus_decoder_create(int Fs, int channels, out int error);
[DllImport("opus", CallingConvention = CallingConvention.Cdecl)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
public static extern void opus_decoder_destroy(IntPtr st);
[DllImport("opus", CallingConvention = CallingConvention.Cdecl)]
public static extern int opus_decode(SafeDecoderHandle st, IntPtr data, int len, IntPtr pcm, int frame_size, int decode_fec);
public static int GetSampleCount(double frameSize, int sampleRate)
{
return (int)(frameSize * (double)sampleRate / 1000.0);
}
public static int GetPCMLength(int samples, int channels)
{
return samples * channels * 2;
}
public static double GetFrameSize(int pcmLength, int sampleRate, int channels)
{
return (double)pcmLength / (double)sampleRate / (double)channels / 2.0 * 1000.0;
}
public static void ThrowIfError(int result)
{
if (result < 0)
{
throw new OpusException(result);
}
}
}
public enum Application
{
VoIP = 2048,
Audio = 2049,
RestrictedLowDelay = 2051
}
public enum Bandwidth
{
NarrowBand = 1101,
MediumBand,
WideBand,
SuperWideBand,
FullBand
}
internal enum Control
{
SetBitrate = 4002,
SetMaxBandwidth = 4004,
GetMaxBandwidth = 4005,
SetVBR = 4006,
GetVBR = 4007,
SetComplexity = 4010,
GetComplexity = 4011,
SetInbandFEC = 4012,
GetInbandFEC = 4013,
SetPacketLossPerc = 4014,
GetPacketLossPerc = 4015,
SetDTX = 4016,
GetDTX = 4017,
SetForceChannels = 4022,
GetForceChannels = 4023
}
public enum ForceChannels
{
None = -1000,
Mono = 1,
Stereo = 2
}
public class OpusDecoder : IDisposable
{
private readonly SafeDecoderHandle _handle;
private readonly int _samples;
private readonly int _pcmLength;
private bool _fec;
[Obsolete("This property was used for the old decode method and is deprecated, please use the new decode method instead.")]
public double? FrameSize { get; }
public int SampleRate { get; }
public int Channels { get; }
[Obsolete("This property was used for the old decode method and is deprecated, please use the new decode method instead.")]
public bool FEC
{
get
{
return _fec;
}
set
{
if (!FrameSize.HasValue)
{
throw new InvalidOperationException("A frame size has to be specified in the constructor for FEC to work.");
}
_fec = value;
}
}
public OpusDecoder()
: this(60.0, 48000, 2, frameSizeWasSpecified: false)
{
}
[Obsolete("This constructor was used for the old decode method and is deprecated, please use the new decode method instead.")]
public OpusDecoder(double frameSize)
: this(frameSize, 48000, 2, frameSizeWasSpecified: true)
{
}
public OpusDecoder(int sampleRate, int channels)
: this(60.0, sampleRate, channels, frameSizeWasSpecified: false)
{
}
[Obsolete("This constructor was used for the old decode method and is deprecated, please use the new decode method instead.")]
public OpusDecoder(double frameSize, int sampleRate, int channels)
: this(frameSize, sampleRate, channels, frameSizeWasSpecified: true)
{
}
private OpusDecoder(double frameSize, int sampleRate, int channels, bool frameSizeWasSpecified)
{
if (frameSize != 2.5 && frameSize != 5.0 && frameSize != 10.0 && frameSize != 20.0 && frameSize != 40.0 && frameSize != 60.0)
{
throw new ArgumentException("Value must be one of the following: 2.5, 5, 10, 20, 40 or 60.", "frameSize");
}
switch (sampleRate)
{
default:
throw new ArgumentException("Value must be one of the following: 8000, 12000, 16000, 24000 or 48000.", "sampleRate");
case 8000:
case 12000:
case 16000:
case 24000:
case 48000:
{
if (channels < 1 || channels > 2)
{
throw new ArgumentOutOfRangeException("channels", "Value must be between 1 and 2.");
}
if (frameSizeWasSpecified)
{
FrameSize = frameSize;
}
SampleRate = sampleRate;
Channels = channels;
_samples = API.GetSampleCount(frameSize, sampleRate);
_pcmLength = API.GetPCMLength(_samples, channels);
_handle = API.opus_decoder_create(sampleRate, channels, out var error);
API.ThrowIfError(error);
break;
}
}
}
[Obsolete("This method is deprecated, please use the new decode method instead.")]
public unsafe byte[] Decode(byte[] opusBytes, int length, out int decodedLength)
{
if (opusBytes == null && !FEC)
{
throw new ArgumentNullException("opusBytes", "Value cannot be null when FEC is disabled.");
}
if (length < 0 && (!FEC || opusBytes != null))
{
throw new ArgumentOutOfRangeException("length", "Value cannot be negative when opusBytes is not null or FEC is disabled.");
}
if (opusBytes != null && opusBytes.Length < length)
{
throw new ArgumentOutOfRangeException("length", "Value cannot be greater than the length of opusBytes.");
}
ThrowIfDisposed();
byte[] array = new byte[_pcmLength];
int num;
fixed (byte* ptr = opusBytes)
{
fixed (byte* ptr2 = array)
{
IntPtr data = (IntPtr)ptr;
IntPtr pcm = (IntPtr)ptr2;
num = ((opusBytes == null) ? API.opus_decode(_handle, IntPtr.Zero, 0, pcm, _samples, FEC ? 1 : 0) : API.opus_decode(_handle, data, length, pcm, _samples, 0));
}
}
API.ThrowIfError(num);
decodedLength = num * Channels * 2;
return array;
}
public unsafe int Decode(byte[] opusBytes, int opusLength, byte[] pcmBytes, int pcmLength)
{
if (opusLength < 0 && opusBytes != null)
{
throw new ArgumentOutOfRangeException("opusLength", "Value cannot be negative when opusBytes is not null.");
}
if (opusBytes != null && opusBytes.Length < opusLength)
{
throw new ArgumentOutOfRangeException("opusLength", "Value cannot be greater than the length of opusBytes.");
}
if (pcmBytes == null)
{
throw new ArgumentNullException("pcmBytes");
}
if (pcmLength < 0)
{
throw new ArgumentOutOfRangeException("pcmLength", "Value cannot be negative.");
}
if (pcmBytes.Length < pcmLength)
{
throw new ArgumentOutOfRangeException("pcmLength", "Value cannot be greater than the length of pcmBytes.");
}
double frameSize = API.GetFrameSize(pcmLength, SampleRate, Channels);
if (opusBytes == null && frameSize != 2.5 && frameSize != 5.0 && frameSize != 10.0 && frameSize != 20.0 && frameSize != 40.0 && frameSize != 60.0)
{
throw new ArgumentException("When using FEC the frame size must be one of the following: 2.5, 5, 10, 20, 40 or 60.", "pcmLength");
}
ThrowIfDisposed();
int sampleCount = API.GetSampleCount(frameSize, SampleRate);
int num;
fixed (byte* ptr = opusBytes)
{
fixed (byte* ptr2 = pcmBytes)
{
IntPtr data = (IntPtr)ptr;
IntPtr pcm = (IntPtr)ptr2;
num = ((opusBytes == null) ? API.opus_decode(_handle, IntPtr.Zero, 0, pcm, sampleCount, 1) : API.opus_decode(_handle, data, opusLength, pcm, sampleCount, 0));
}
}
API.ThrowIfError(num);
return API.GetPCMLength(num, Channels);
}
public void Dispose()
{
_handle?.Dispose();
}
private void ThrowIfDisposed()
{
if (_handle.IsClosed)
{
throw new ObjectDisposedException(GetType().FullName);
}
}
}
public class OpusEncoder : IDisposable
{
private readonly SafeEncoderHandle _handle;
private int _bitrate;
public Application Application { get; }
public int SampleRate { get; }
public int Channels { get; }
[Obsolete("This property was used for the old encode method and is deprecated, please use the new encode method instead.")]
public int Bitrate
{
get
{
return _bitrate;
}
set
{
if (value < 8000 || value > 512000)
{
throw new ArgumentOutOfRangeException("value", "Value must be between 8000 and 512000.");
}
_bitrate = value;
}
}
public bool VBR
{
get
{
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4007, out var value));
return value == 1;
}
set
{
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4006, value ? 1 : 0));
}
}
public Bandwidth MaxBandwidth
{
get
{
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4005, out var value));
return (Bandwidth)value;
}
set
{
if (!Enum.IsDefined(typeof(Bandwidth), value))
{
throw new ArgumentException("Value is not defined in the enumeration.", "value");
}
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4004, (int)value));
}
}
public int Complexity
{
get
{
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4011, out var value));
return value;
}
set
{
if (value < 0 || value > 10)
{
throw new ArgumentOutOfRangeException("value", "Value must be between 0 and 10.");
}
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4010, value));
}
}
public bool FEC
{
get
{
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4013, out var value));
return value == 1;
}
set
{
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4012, value ? 1 : 0));
}
}
public int ExpectedPacketLoss
{
get
{
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4015, out var value));
return value;
}
set
{
if (value < 0 || value > 100)
{
throw new ArgumentOutOfRangeException("value", "Value must be between 0 and 100.");
}
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4014, value));
}
}
public bool DTX
{
get
{
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4017, out var value));
return value == 1;
}
set
{
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4016, value ? 1 : 0));
}
}
public ForceChannels ForceChannels
{
get
{
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4023, out var value));
return (ForceChannels)value;
}
set
{
if (!Enum.IsDefined(typeof(ForceChannels), value))
{
throw new ArgumentException("Value is not defined in the enumeration.", "value");
}
ThrowIfDisposed();
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4022, (int)value));
}
}
public OpusEncoder(Application application)
: this(application, 48000, 2)
{
}
public OpusEncoder(Application application, int sampleRate, int channels)
{
if (!Enum.IsDefined(typeof(Application), application))
{
throw new ArgumentException("Value is not defined in the enumeration.", "application");
}
switch (sampleRate)
{
default:
throw new ArgumentException("Value must be one of the following: 8000, 12000, 16000, 24000 or 48000.", "sampleRate");
case 8000:
case 12000:
case 16000:
case 24000:
case 48000:
{
if (channels < 1 || channels > 2)
{
throw new ArgumentOutOfRangeException("channels", "Value must be between 1 and 2.");
}
Application = application;
SampleRate = sampleRate;
Channels = channels;
Bitrate = 128000;
_handle = API.opus_encoder_create(sampleRate, channels, (int)application, out var error);
API.ThrowIfError(error);
API.ThrowIfError(API.opus_encoder_ctl(_handle, 4002, -1));
break;
}
}
}
[Obsolete("This method is deprecated, please use the new encode method instead.")]
public unsafe byte[] Encode(byte[] pcmBytes, int length, out int encodedLength)
{
if (pcmBytes == null)
{
throw new ArgumentNullException("pcmBytes");
}
if (length < 0)
{
throw new ArgumentOutOfRangeException("length", "Value cannot be negative.");
}
if (pcmBytes.Length < length)
{
throw new ArgumentOutOfRangeException("length", "Value cannot be greater than the length of pcmBytes.");
}
double frameSize = API.GetFrameSize(length, SampleRate, Channels);
if (frameSize != 2.5 && frameSize != 5.0 && frameSize != 10.0 && frameSize != 20.0 && frameSize != 40.0 && frameSize != 60.0)
{
throw new ArgumentException("The frame size must be one of the following: 2.5, 5, 10, 20, 40 or 60.", "length");
}
ThrowIfDisposed();
byte[] array = new byte[(int)(frameSize * (double)Bitrate / 8.0 / 1000.0)];
int sampleCount = API.GetSampleCount(frameSize, SampleRate);
int num;
fixed (byte* ptr = pcmBytes)
{
fixed (byte* ptr2 = array)
{
IntPtr pcm = (IntPtr)ptr;
IntPtr data = (IntPtr)ptr2;
num = API.opus_encode(_handle, pcm, sampleCount, data, array.Length);
}
}
API.ThrowIfError(num);
encodedLength = num;
return array;
}
public unsafe int Encode(byte[] pcmBytes, int pcmLength, byte[] opusBytes, int opusLength)
{
if (pcmBytes == null)
{
throw new ArgumentNullException("pcmBytes");
}
if (pcmLength < 0)
{
throw new ArgumentOutOfRangeException("pcmLength", "Value cannot be negative.");
}
if (pcmBytes.Length < pcmLength)
{
throw new ArgumentOutOfRangeException("pcmLength", "Value cannot be greater than the length of pcmBytes.");
}
if (opusBytes == null)
{
throw new ArgumentNullException("opusBytes");
}
if (opusLength < 0)
{
throw new ArgumentOutOfRangeException("opusLength", "Value cannot be negative.");
}
if (opusBytes.Length < opusLength)
{
throw new ArgumentOutOfRangeException("opusLength", "Value cannot be greater than the length of opusBytes.");
}
double frameSize = API.GetFrameSize(pcmLength, SampleRate, Channels);
if (frameSize != 2.5 && frameSize != 5.0 && frameSize != 10.0 && frameSize != 20.0 && frameSize != 40.0 && frameSize != 60.0)
{
throw new ArgumentException("The frame size must be one of the following: 2.5, 5, 10, 20, 40 or 60.", "pcmLength");
}
ThrowIfDisposed();
int sampleCount = API.GetSampleCount(frameSize, SampleRate);
int result;
fixed (byte* ptr = pcmBytes)
{
fixed (byte* ptr2 = opusBytes)
{
IntPtr pcm = (IntPtr)ptr;
IntPtr data = (IntPtr)ptr2;
result = API.opus_encode(_handle, pcm, sampleCount, data, opusLength);
}
}
API.ThrowIfError(result);
return result;
}
public void Dispose()
{
_handle?.Dispose();
}
private void ThrowIfDisposed()
{
if (_handle.IsClosed)
{
throw new ObjectDisposedException(GetType().FullName);
}
}
}
public enum OpusError
{
BadArg = -1,
BufferTooSmall = -2,
InternalError = -3,
InvalidPacket = -4,
Unimplemented = -5,
InvalidState = -6,
AllocFail = -7
}
public class OpusException : Exception
{
public OpusError Error { get; }
public OpusException(int errorCode)
: base(GetMessage((OpusError)errorCode))
{
Error = (OpusError)errorCode;
}
private static string GetMessage(OpusError error)
{
return error switch
{
OpusError.BadArg => "One or more invalid/out of range arguments.",
OpusError.BufferTooSmall => "Not enough bytes allocated in the buffer.",
OpusError.InternalError => "An internal error was detected.",
OpusError.InvalidPacket => "The compressed data passed is corrupted.",
OpusError.Unimplemented => "Invalid/unsupported request number.",
OpusError.InvalidState => "An encoder or decoder structure is invalid or already freed.",
OpusError.AllocFail => "Memory allocation has failed.",
_ => "An unknown error has occurred.",
};
}
}
internal sealed class SafeDecoderHandle : SafeHandle
{
public override bool IsInvalid => handle == IntPtr.Zero;
private SafeDecoderHandle()
: base(IntPtr.Zero, ownsHandle: true)
{
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
protected override bool ReleaseHandle()
{
API.opus_decoder_destroy(handle);
return true;
}
}
internal sealed class SafeEncoderHandle : SafeHandle
{
public override bool IsInvalid => handle == IntPtr.Zero;
private SafeEncoderHandle()
: base(IntPtr.Zero, ownsHandle: true)
{
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
protected override bool ReleaseHandle()
{
API.opus_encoder_destroy(handle);
return true;
}
}