using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v5.0", FrameworkDisplayName = ".NET 5.0")] [assembly: AssemblyCompany("SIP Sorcery PTY LTD")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © 2020-2026 Aaron Clauson")] [assembly: AssemblyDescription("Don't reference this package unless you are building a media end point to work with the SIPSorcery real-time communications library. In most cases a concrete implementation package such as SIPSorceryMedia.Windows should be referenced.\r\n ")] [assembly: AssemblyFileVersion("10.0.16")] [assembly: AssemblyInformationalVersion("10.0.16+4ca86773993a875706d8a7a4c1e0108e3a885ebf")] [assembly: AssemblyProduct("SIPSorceryMedia.Abstractions")] [assembly: AssemblyTitle("SIPSorceryMedia.Abstractions")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/sipsorcery-org/sipsorcery")] [assembly: AssemblyVersion("10.0.16.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SIPSorcery { public class LogFactory { private ILoggerFactory _factory = NullLoggerFactory.Instance; private static LogFactory _appLog; public static LogFactory Instance { get { if (_appLog == null) { _appLog = new LogFactory(); } return _appLog; } } public event Action OnFactorySet; private LogFactory() { } public static ILogger CreateLogger(string categoryName) { return Instance._factory.CreateLogger(categoryName); } public static ILogger CreateLogger() { return Instance._factory.CreateLogger(); } public static void Set(ILoggerFactory factory) { Instance._factory = factory; Instance.OnFactorySet?.Invoke(); } } } namespace SIPSorceryMedia.Abstractions { public interface IAudioEncoder { List SupportedFormats { get; } byte[] EncodeAudio(short[] pcm, AudioFormat format); short[] DecodeAudio(byte[] encodedSample, AudioFormat format); } public interface ITextEncoder { byte[] EncodeText(char[] text, TextFormat format); char[] DecodeText(byte[] encodedSample, TextFormat format); } public interface IVideoEncoder : IDisposable { List SupportedFormats { get; } byte[] EncodeVideo(int width, int height, byte[] sample, VideoPixelFormatsEnum pixelFormat, VideoCodecsEnum codec); byte[] EncodeVideoFaster(RawImage rawImage, VideoCodecsEnum codec); void ForceKeyFrame(); IEnumerable DecodeVideo(byte[] encodedSample, VideoPixelFormatsEnum pixelFormat, VideoCodecsEnum codec); IEnumerable DecodeVideoFaster(byte[] encodedSample, VideoPixelFormatsEnum pixelFormat, VideoCodecsEnum codec); } public class RawImage { public int Width { get; set; } public int Height { get; set; } public int Stride { get; set; } public IntPtr Sample { get; set; } public VideoPixelFormatsEnum PixelFormat { get; set; } public byte[] GetBuffer() { byte[] array = null; if (Height > 0 && Stride > 0) { int num = Height * Stride; array = new byte[num]; Marshal.Copy(Sample, array, 0, num); } return array; } } public struct VideoSample { public uint Width; public uint Height; public byte[] Sample; } public enum AudioCodecsEnum { Unknown, PCMU, GSM, G723, DVI4, LPC, PCMA, G722, L16, QCELP, CN, MPA, G728, G729, OPUS, PCM_S16LE } public enum AudioSamplingRatesEnum { Rate8KHz = 8000, Rate16KHz = 16000, Rate24kHz = 24000, Rate44_1kHz = 44100, Rate48kHz = 48000 } public enum SDPWellKnownMediaFormatsEnum { PCMU = 0, GSM = 3, G723 = 4, DVI4 = 5, DVI4_16K = 6, LPC = 7, PCMA = 8, G722 = 9, L16_2 = 10, L16 = 11, QCELP = 12, CN = 13, MPA = 14, G728 = 15, DVI4_11K = 16, DVI4_22K = 17, G729 = 18, CELB = 24, JPEG = 26, NV = 28, H261 = 31, MPV = 32, MP2T = 33, H263 = 34 } public enum TextCodecsEnum { Unknown, T140, RED } public enum VideoCodecsEnum { Unknown, CELB, JPEG, NV, H261, MPV, MP2T, H263, VP8, VP9, AV1, H264, H265 } public enum VideoPixelFormatsEnum { Rgb, Bgr, Bgra, I420, NV12, Rgba } public class EncodedAudioFrame { public int MediaStreamIndex { get; } public AudioFormat AudioFormat { get; } public uint DurationMilliSeconds { get; } public byte[] EncodedAudio { get; } public EncodedAudioFrame(int mediaStreamIndex, AudioFormat mediaformat, uint durationMilliSeconds, byte[] encodedMedia) { MediaStreamIndex = mediaStreamIndex; AudioFormat = mediaformat; DurationMilliSeconds = durationMilliSeconds; EncodedAudio = encodedMedia; } } public interface IAudioEndPoint : IAudioSource, IAudioSink { Task Pause(); Task Resume(); Task Start(); Task Close(); } public interface IAudioSink { event SourceErrorDelegate OnAudioSinkError; List GetAudioSinkFormats(); void SetAudioSinkFormat(AudioFormat audioFormat); [Obsolete("Use GotEncodedMediaFrame instead.")] void GotAudioRtp(IPEndPoint remoteEndPoint, uint ssrc, uint seqnum, uint timestamp, int payloadID, bool marker, byte[] payload); void GotEncodedMediaFrame(EncodedAudioFrame encodedMediaFrame); void RestrictFormats(Func filter); Task PauseAudioSink(); Task ResumeAudioSink(); Task StartAudioSink(); Task CloseAudioSink(); } public delegate void RawAudioSampleDelegate(AudioSamplingRatesEnum samplingRate, uint durationMilliseconds, short[] sample); public interface IAudioSource { event EncodedSampleDelegate OnAudioSourceEncodedSample; event Action OnAudioSourceEncodedFrameReady; event RawAudioSampleDelegate OnAudioSourceRawSample; event SourceErrorDelegate OnAudioSourceError; Task PauseAudio(); Task ResumeAudio(); Task StartAudio(); Task CloseAudio(); List GetAudioSourceFormats(); void SetAudioSourceFormat(AudioFormat audioFormat); void RestrictFormats(Func filter); void ExternalAudioSourceRawSample(AudioSamplingRatesEnum samplingRate, uint durationMilliseconds, short[] sample); bool HasEncodedAudioSubscribers(); bool IsAudioSourcePaused(); } public interface ITextEndPoint : ITextSource, ITextSink { Task Pause(); Task Resume(); Task Start(); Task Close(); } public interface ITextSink { Task CloseTextSink(); void GotTextRtp(IPEndPoint remoteEndPoint, uint ssrc, uint seqnum, uint timestamp, int payloadID, int marker, byte[] payload); void SetTextSinkFormat(TextFormat textFormat); TextFormat GetTextSinkFormat(); Task StartTextSink(); Task PauseTextSink(); Task ResumeTextSink(); } public delegate void EncodedTextSampleDelegate(byte[] sample); public interface ITextSource { event EncodedTextSampleDelegate OnTextSourceEncodedSample; Task CloseText(); TextFormat GetTextSourceFormat(); void SetTextSourceFormat(TextFormat textFormat); Task StartText(); Task PauseText(); Task ResumeText(); } public interface IVideoEndPoint : IVideoSource, IVideoSink { Task Pause(); Task Resume(); Task Start(); Task Close(); } public delegate void VideoSinkSampleDecodedDelegate(byte[] sample, uint width, uint height, int stride, VideoPixelFormatsEnum pixelFormat); public delegate void VideoSinkSampleDecodedFasterDelegate(RawImage rawImage); public interface IVideoSink { event VideoSinkSampleDecodedDelegate OnVideoSinkDecodedSample; event VideoSinkSampleDecodedFasterDelegate OnVideoSinkDecodedSampleFaster; void GotVideoRtp(IPEndPoint remoteEndPoint, uint ssrc, uint seqnum, uint timestamp, int payloadID, bool marker, byte[] payload); void GotVideoFrame(IPEndPoint remoteEndPoint, uint timestamp, byte[] payload, VideoFormat format); List GetVideoSinkFormats(); void SetVideoSinkFormat(VideoFormat videoFormat); void RestrictFormats(Func filter); Task PauseVideoSink(); Task ResumeVideoSink(); Task StartVideoSink(); Task CloseVideoSink(); } public delegate void RawVideoSampleDelegate(uint durationMilliseconds, int width, int height, byte[] sample, VideoPixelFormatsEnum pixelFormat); public delegate void RawVideoSampleFasterDelegate(uint durationMilliseconds, RawImage rawImage); public interface IVideoSource { event EncodedSampleDelegate OnVideoSourceEncodedSample; event RawVideoSampleDelegate OnVideoSourceRawSample; event RawVideoSampleFasterDelegate OnVideoSourceRawSampleFaster; event SourceErrorDelegate OnVideoSourceError; Task PauseVideo(); Task ResumeVideo(); Task StartVideo(); Task CloseVideo(); List GetVideoSourceFormats(); void SetVideoSourceFormat(VideoFormat videoFormat); void RestrictFormats(Func filter); void ExternalVideoSourceRawSample(uint durationMilliseconds, int width, int height, byte[] sample, VideoPixelFormatsEnum pixelFormat); void ExternalVideoSourceRawSampleFaster(uint durationMilliseconds, RawImage rawImage); void ForceKeyFrame(); bool HasEncodedVideoSubscribers(); bool IsVideoSourcePaused(); } public delegate void EncodedSampleDelegate(uint durationRtpUnits, byte[] sample); public delegate void SourceErrorDelegate(string errorMessage); public class MediaEndPoints { public IAudioSource AudioSource { get; set; } public IAudioSink AudioSink { get; set; } public IVideoSource VideoSource { get; set; } public IVideoSink VideoSink { get; set; } public ITextSource TextSource { get; set; } public ITextSink TextSink { get; set; } } public class MediaFormatManager { public readonly List SupportedFormats = new List(); private List _filteredFormats = new List(); public T SelectedFormat { get; private set; } public MediaFormatManager(List supportedFormats) { SupportedFormats = supportedFormats; _filteredFormats = new List(SupportedFormats); } public List GetSourceFormats() { return _filteredFormats; } public void RestrictFormats(Func filter) { if (filter == null) { _filteredFormats = new List(SupportedFormats); return; } _filteredFormats = _filteredFormats.Where((T x) => filter(x)).ToList(); } public void SetSelectedFormat(T format) { SelectedFormat = format; } } public static class AudioCommonlyUsedFormats { public const int OPUS_SAMPLE_RATE = 48000; public const int OPUS_CHANNELS = 2; public static AudioFormat OpusWebRTC => new AudioFormat(111, "OPUS", 48000, 2, "useinbandfec=1"); } public struct AudioFormat { public const int DYNAMIC_ID_MIN = 96; public const int DYNAMIC_ID_MAX = 127; public const int DEFAULT_CLOCK_RATE = 8000; public const int DEFAULT_CHANNEL_COUNT = 1; public static readonly AudioFormat Empty = new AudioFormat { _isNonEmpty = false, ClockRate = 8000, ChannelCount = 1 }; private bool _isNonEmpty; public AudioCodecsEnum Codec { get; set; } public int FormatID { get; set; } public string FormatName { get; set; } public int RtpClockRate { get; set; } public int ClockRate { get; set; } public int ChannelCount { get; set; } public string Parameters { get; set; } public AudioFormat(SDPWellKnownMediaFormatsEnum wellKnown) : this(AudioVideoWellKnown.WellKnownAudioFormats[wellKnown]) { } public AudioFormat(AudioCodecsEnum codec, int formatID, int clockRate = 8000, int channelCount = 1, string parameters = null) : this(codec, formatID, clockRate, clockRate, channelCount, parameters) { } public AudioFormat(AudioCodecsEnum codec, int formatID, int clockRate, int rtpClockRate, int channelCount, string parameters) : this(formatID, codec.ToString(), clockRate, rtpClockRate, channelCount, parameters) { } public AudioFormat(int formatID, string formatName, int clockRate = 8000, int channelCount = 1, string parameters = null) : this(formatID, formatName, clockRate, clockRate, channelCount, parameters) { } public AudioFormat(AudioFormat format) : this(format.FormatID, format.FormatName, format.ClockRate, format.RtpClockRate, format.ChannelCount, format.Parameters) { } public AudioFormat(int formatID, string formatName, int clockRate, int rtpClockRate, int channelCount, string parameters) { if (formatID < 0) { throw new ApplicationException("The format ID for an AudioFormat must be greater than 0."); } if (formatID > 127) { throw new ApplicationException($"The format ID for an AudioFormat exceeded the maximum allowed vale of {127}."); } if (string.IsNullOrWhiteSpace(formatName)) { throw new ApplicationException("The format name must be provided for an AudioFormat."); } if (clockRate <= 0) { throw new ApplicationException("The clock rate for an AudioFormat must be greater than 0."); } if (rtpClockRate <= 0) { throw new ApplicationException("The RTP clock rate for an AudioFormat must be greater than 0."); } if (channelCount <= 0) { throw new ApplicationException("The channel count for an AudioFormat must be greater than 0."); } FormatID = formatID; FormatName = formatName; ClockRate = clockRate; RtpClockRate = rtpClockRate; ChannelCount = channelCount; Parameters = parameters; _isNonEmpty = true; if (Enum.TryParse(FormatName, ignoreCase: true, out var result)) { Codec = result; } else { Codec = AudioCodecsEnum.Unknown; } } public bool IsEmpty() { return !_isNonEmpty; } } public static class AudioVideoWellKnown { public static Dictionary WellKnownAudioFormats = new Dictionary { { SDPWellKnownMediaFormatsEnum.PCMU, new AudioFormat(AudioCodecsEnum.PCMU, 0) }, { SDPWellKnownMediaFormatsEnum.GSM, new AudioFormat(AudioCodecsEnum.GSM, 3) }, { SDPWellKnownMediaFormatsEnum.G723, new AudioFormat(AudioCodecsEnum.G723, 4) }, { SDPWellKnownMediaFormatsEnum.DVI4, new AudioFormat(AudioCodecsEnum.DVI4, 5) }, { SDPWellKnownMediaFormatsEnum.DVI4_16K, new AudioFormat(AudioCodecsEnum.DVI4, 6, 16000) }, { SDPWellKnownMediaFormatsEnum.LPC, new AudioFormat(AudioCodecsEnum.LPC, 7) }, { SDPWellKnownMediaFormatsEnum.PCMA, new AudioFormat(AudioCodecsEnum.PCMA, 8) }, { SDPWellKnownMediaFormatsEnum.G722, new AudioFormat(AudioCodecsEnum.G722, 9, 16000, 8000, 1, null) }, { SDPWellKnownMediaFormatsEnum.L16_2, new AudioFormat(AudioCodecsEnum.L16, 10, 44100, 2) }, { SDPWellKnownMediaFormatsEnum.L16, new AudioFormat(AudioCodecsEnum.L16, 11, 44100) }, { SDPWellKnownMediaFormatsEnum.QCELP, new AudioFormat(AudioCodecsEnum.QCELP, 12) }, { SDPWellKnownMediaFormatsEnum.CN, new AudioFormat(AudioCodecsEnum.CN, 13) }, { SDPWellKnownMediaFormatsEnum.MPA, new AudioFormat(AudioCodecsEnum.MPA, 14, 90000) }, { SDPWellKnownMediaFormatsEnum.G728, new AudioFormat(AudioCodecsEnum.G728, 15) }, { SDPWellKnownMediaFormatsEnum.DVI4_11K, new AudioFormat(AudioCodecsEnum.DVI4, 16, 11025) }, { SDPWellKnownMediaFormatsEnum.DVI4_22K, new AudioFormat(AudioCodecsEnum.DVI4, 17, 22050) }, { SDPWellKnownMediaFormatsEnum.G729, new AudioFormat(AudioCodecsEnum.G729, 18) } }; public static Dictionary WellKnownVideoFormats = new Dictionary { { SDPWellKnownMediaFormatsEnum.CELB, new VideoFormat(VideoCodecsEnum.CELB, 24) }, { SDPWellKnownMediaFormatsEnum.JPEG, new VideoFormat(VideoCodecsEnum.JPEG, 26) }, { SDPWellKnownMediaFormatsEnum.NV, new VideoFormat(VideoCodecsEnum.NV, 28) }, { SDPWellKnownMediaFormatsEnum.H261, new VideoFormat(VideoCodecsEnum.H261, 31) }, { SDPWellKnownMediaFormatsEnum.MPV, new VideoFormat(VideoCodecsEnum.MPV, 32) }, { SDPWellKnownMediaFormatsEnum.MP2T, new VideoFormat(VideoCodecsEnum.MP2T, 33) }, { SDPWellKnownMediaFormatsEnum.H263, new VideoFormat(VideoCodecsEnum.H263, 34) } }; } public struct TextFormat { public const int DYNAMIC_ID_MIN = 96; public const int DYNAMIC_ID_MAX = 127; public const int DEFAULT_CLOCK_RATE = 1000; public static readonly TextFormat Empty = new TextFormat { ClockRate = 1000 }; public TextCodecsEnum Codec { get; set; } public int FormatID { get; set; } public string FormatName { get; set; } public int ClockRate { get; set; } public string Parameters { get; set; } public TextFormat(TextFormat format) : this(format.FormatID, format.FormatName, format.ClockRate, format.Parameters) { } public TextFormat(TextCodecsEnum codec, int formatID, int clockRate = 1000, string parameters = null) : this(formatID, codec.ToString(), clockRate, parameters) { } public TextFormat(int formatID, string formatName, int clockRate = 1000, string parameters = null) { if (formatID < 0) { throw new ApplicationException("The format ID for an TextFormat must be greater than 0."); } if (formatID > 127) { throw new ApplicationException($"The format ID for an TextFormat exceeded the maximum allowed vale of {127}."); } if (string.IsNullOrWhiteSpace(formatName)) { throw new ApplicationException("The format name must be provided for a TextFormat."); } if (clockRate <= 0) { throw new ApplicationException("The clock rate for a TextFormat must be greater than 0."); } FormatID = formatID; FormatName = formatName; ClockRate = clockRate; Parameters = parameters; if (Enum.TryParse(FormatName, out var result)) { Codec = result; } else { Codec = TextCodecsEnum.Unknown; } } } public struct VideoFormat { public const int DYNAMIC_ID_MIN = 96; public const int DYNAMIC_ID_MAX = 127; public const int DEFAULT_CLOCK_RATE = 90000; public static readonly VideoFormat Empty = new VideoFormat { _isNonEmpty = false, ClockRate = 90000 }; private bool _isNonEmpty; public VideoCodecsEnum Codec { get; set; } public int FormatID { get; set; } public string FormatName { get; set; } public int ClockRate { get; set; } public string Parameters { get; set; } public VideoFormat(SDPWellKnownMediaFormatsEnum wellKnown) : this(AudioVideoWellKnown.WellKnownVideoFormats[wellKnown]) { } public VideoFormat(VideoCodecsEnum codec, int formatID, int clockRate = 90000, string parameters = null) : this(formatID, codec.ToString(), clockRate, parameters) { } public VideoFormat(VideoFormat format) : this(format.FormatID, format.FormatName, format.ClockRate, format.Parameters) { } public VideoFormat(int formatID, string formatName, int clockRate = 90000, string parameters = null) { if (formatID < 0) { throw new ApplicationException("The format ID for an VideoFormat must be greater than 0."); } if (formatID > 127) { throw new ApplicationException($"The format ID for an VideoFormat exceeded the maximum allowed vale of {127}."); } if (string.IsNullOrWhiteSpace(formatName)) { throw new ApplicationException("The format name must be provided for a VideoFormat."); } if (clockRate <= 0) { throw new ApplicationException("The clock rate for a VideoFormat must be greater than 0."); } FormatID = formatID; FormatName = formatName; ClockRate = clockRate; Parameters = parameters; _isNonEmpty = true; if (Enum.TryParse(FormatName, ignoreCase: true, out var result)) { Codec = result; } else { Codec = VideoCodecsEnum.Unknown; } } public bool IsEmpty() { return !_isNonEmpty; } } public class PixelConverter { private static readonly Dictionary _optDOP = new Dictionary(); [Obsolete("Use overload with stride parameter in order to deal with uneven dimensions.")] public static byte[] ToI420(int width, int height, byte[] sample, VideoPixelFormatsEnum pixelFormat) { return pixelFormat switch { VideoPixelFormatsEnum.I420 => sample, VideoPixelFormatsEnum.Bgra => RGBAtoI420(sample, width, height, width * 4), VideoPixelFormatsEnum.Bgr => BGRtoI420(sample, width, height, width * 3), VideoPixelFormatsEnum.Rgb => RGBtoI420(sample, width, height, width * 3), _ => throw new ApplicationException($"Pixel format {pixelFormat} does not have an I420 conversion implemented."), }; } public static byte[] ToI420(int width, int height, int stride, byte[] sample, VideoPixelFormatsEnum pixelFormat) { return pixelFormat switch { VideoPixelFormatsEnum.I420 => sample, VideoPixelFormatsEnum.Bgra => BGRAtoI420(sample, width, height, stride), VideoPixelFormatsEnum.Bgr => BGRtoI420(sample, width, height, stride), VideoPixelFormatsEnum.Rgba => RGBAtoI420(sample, width, height, stride), VideoPixelFormatsEnum.Rgb => RGBtoI420(sample, width, height, stride), VideoPixelFormatsEnum.NV12 => NV12toI420(sample, width, height), _ => throw new ApplicationException($"Pixel format {pixelFormat} does not have an I420 conversion implemented."), }; } [Obsolete("Use overload with stride parameter in order to deal with uneven dimensions.")] public static byte[] RGBAtoI420(byte[] rgba, int width, int height) { return RGBAtoI420(rgba, width, height, width * 4); } public static byte[] RGBAtoI420(byte[] rgba, int width, int height, int stride, int dop = 1) { if (rgba == null || rgba.Length < stride * height) { throw new ApplicationException($"RGBA buffer supplied to RGBAtoI420 was too small, expected {stride * height} but got {rgba?.Length}."); } int num = width * height; int num2 = (width + 1) / 2 * ((height + 1) / 2) * 2; int uOffset = num; int vOffset = num + num2 / 2; byte[] buffer = new byte[num + num2]; if (!_optDOP.ContainsKey(dop)) { _optDOP[dop] = new ParallelOptions { MaxDegreeOfParallelism = dop }; } Parallel.For(0, height, _optDOP[dop], delegate(int row) { for (int i = 0; i < width; i++) { int num3 = rgba[row * stride + i * 4] & 0xFF; int num4 = rgba[row * stride + i * 4 + 1] & 0xFF; int num5 = rgba[row * stride + i * 4 + 2] & 0xFF; int num6 = (int)(0.299 * (double)num3 + 0.587 * (double)num4 + 0.114 * (double)num5); int num7 = (int)(-0.147 * (double)num3 - 0.289 * (double)num4 + 0.436 * (double)num5) + 128; int num8 = (int)(0.615 * (double)num3 - 0.515 * (double)num4 - 0.1 * (double)num5) + 128; buffer[i + row * width] = (byte)((num6 > 255) ? 255u : ((num6 >= 0) ? ((uint)num6) : 0u)); int num9 = i / 2 + row / 2 * width / 2; buffer[uOffset + num9] = (byte)((num7 > 255) ? 255u : ((num7 >= 0) ? ((uint)num7) : 0u)); buffer[vOffset + num9] = (byte)((num8 > 255) ? 255u : ((num8 >= 0) ? ((uint)num8) : 0u)); } }); return buffer; } [Obsolete("Use overload with stride parameter in order to deal with uneven dimensions.")] public static byte[] RGBtoI420(byte[] rgb, int width, int height) { return RGBtoI420(rgb, width, height, width * 3); } public static byte[] RGBtoI420(byte[] rgb, int width, int height, int stride, int dop = 1) { if (rgb == null || rgb.Length < stride * height) { throw new ApplicationException($"RGB buffer supplied to RGBtoI420 was too small, expected {stride * height} but got {rgb?.Length}."); } int num = width * height; int num2 = (width + 1) / 2 * ((height + 1) / 2) * 2; int uOffset = num; int vOffset = num + num2 / 2; byte[] buffer = new byte[num + num2]; if (!_optDOP.ContainsKey(dop)) { _optDOP[dop] = new ParallelOptions { MaxDegreeOfParallelism = dop }; } Parallel.For(0, height, _optDOP[dop], delegate(int row) { for (int i = 0; i < width; i++) { int num3 = rgb[row * stride + i * 3] & 0xFF; int num4 = rgb[row * stride + i * 3 + 1] & 0xFF; int num5 = rgb[row * stride + i * 3 + 2] & 0xFF; int num6 = (int)(0.299 * (double)num3 + 0.587 * (double)num4 + 0.114 * (double)num5); int num7 = (int)(-0.147 * (double)num3 - 0.289 * (double)num4 + 0.436 * (double)num5) + 128; int num8 = (int)(0.615 * (double)num3 - 0.515 * (double)num4 - 0.1 * (double)num5) + 128; buffer[i + row * width] = (byte)((num6 > 255) ? 255u : ((num6 >= 0) ? ((uint)num6) : 0u)); int num9 = i / 2 + row / 2 * width / 2; buffer[uOffset + num9] = (byte)((num7 > 255) ? 255u : ((num7 >= 0) ? ((uint)num7) : 0u)); buffer[vOffset + num9] = (byte)((num8 > 255) ? 255u : ((num8 >= 0) ? ((uint)num8) : 0u)); } }); return buffer; } [Obsolete("Use overload with stride parameter in order to deal with uneven dimensions.")] public static byte[] BGRtoI420(byte[] bgr, int width, int height) { return BGRtoI420(bgr, width, height, width * 3); } public static byte[] BGRtoI420(byte[] bgr, int width, int height, int stride, int dop = 1) { if (bgr == null || bgr.Length < stride * height) { throw new ApplicationException($"BGR buffer supplied to BGRtoI420 was too small, expected {stride * height} but got {bgr?.Length}."); } int num = width * height; int num2 = (width + 1) / 2 * ((height + 1) / 2) * 2; int uOffset = num; int vOffset = num + num2 / 2; byte[] buffer = new byte[num + num2]; if (!_optDOP.ContainsKey(dop)) { _optDOP[dop] = new ParallelOptions { MaxDegreeOfParallelism = dop }; } Parallel.For(0, height, _optDOP[dop], delegate(int row) { for (int i = 0; i < width; i++) { int num3 = bgr[row * stride + i * 3] & 0xFF; int num4 = bgr[row * stride + i * 3 + 1] & 0xFF; int num5 = bgr[row * stride + i * 3 + 2] & 0xFF; int num6 = (int)(0.299 * (double)num5 + 0.587 * (double)num4 + 0.114 * (double)num3); int num7 = (int)(-0.147 * (double)num5 - 0.289 * (double)num4 + 0.436 * (double)num3) + 128; int num8 = (int)(0.615 * (double)num5 - 0.515 * (double)num4 - 0.1 * (double)num3) + 128; buffer[i + row * width] = (byte)((num6 > 255) ? 255u : ((num6 >= 0) ? ((uint)num6) : 0u)); int num9 = i / 2 + row / 2 * width / 2; buffer[uOffset + num9] = (byte)((num7 > 255) ? 255u : ((num7 >= 0) ? ((uint)num7) : 0u)); buffer[vOffset + num9] = (byte)((num8 > 255) ? 255u : ((num8 >= 0) ? ((uint)num8) : 0u)); } }); return buffer; } public static byte[] BGRAtoI420(byte[] bgra, int width, int height, int stride, int dop = 1) { if (bgra == null || bgra.Length < stride * height) { throw new ApplicationException($"BGRA buffer supplied to BGRAtoI420 was too small, expected {stride * height} but got {bgra?.Length}."); } int num = width * height; int num2 = (width + 1) / 2 * ((height + 1) / 2) * 2; int uOffset = num; int vOffset = num + num2 / 2; byte[] buffer = new byte[num + num2]; if (!_optDOP.ContainsKey(dop)) { _optDOP[dop] = new ParallelOptions { MaxDegreeOfParallelism = dop }; } Parallel.For(0, height, _optDOP[dop], delegate(int row) { for (int i = 0; i < width; i++) { int num3 = bgra[row * stride + i * 4] & 0xFF; int num4 = bgra[row * stride + i * 4 + 1] & 0xFF; int num5 = bgra[row * stride + i * 4 + 2] & 0xFF; int num6 = (int)(0.299 * (double)num5 + 0.587 * (double)num4 + 0.114 * (double)num3); int num7 = (int)(-0.147 * (double)num5 - 0.289 * (double)num4 + 0.436 * (double)num3) + 128; int num8 = (int)(0.615 * (double)num5 - 0.515 * (double)num4 - 0.1 * (double)num3) + 128; buffer[i + row * width] = (byte)((num6 > 255) ? 255u : ((num6 >= 0) ? ((uint)num6) : 0u)); int num9 = i / 2 + row / 2 * (width / 2); buffer[uOffset + num9] = (byte)((num7 > 255) ? 255u : ((num7 >= 0) ? ((uint)num7) : 0u)); buffer[vOffset + num9] = (byte)((num8 > 255) ? 255u : ((num8 >= 0) ? ((uint)num8) : 0u)); } }); return buffer; } [Obsolete("Use overload with stride parameter in order to deal with uneven dimensions.")] public static byte[] I420toRGB(byte[] data, int width, int height) { int stride; return I420toRGB(data, width, height, out stride); } public static byte[] I420toRGB(byte[] data, int width, int height, out int stride, int dop = 1) { int num = width * height; int num2 = (width + 1) / 2 * ((height + 1) / 2) * 2; if (data == null || data.Length < num + num2) { throw new ApplicationException($"I420 buffer supplied to I420toRGB was too small, expected {num + num2} but got {data?.Length}."); } int uOffset = num; int vOffset = num + num / 4; int lclStride = (stride = (width * 3 + 3) / 4 * 4); byte[] rgb = new byte[height * stride]; if (!_optDOP.ContainsKey(dop)) { _optDOP[dop] = new ParallelOptions { MaxDegreeOfParallelism = dop }; } Parallel.For(0, height, _optDOP[dop], delegate(int row) { for (int i = 0; i < width; i++) { byte num3 = data[i + row * width]; int num4 = i / 2 + row / 2 * width / 2; int num5 = data[uOffset + num4] - 128; int num6 = data[vOffset + num4] - 128; int num7 = (int)((double)(int)num3 + 1.14 * (double)num6); int num8 = (int)((double)(int)num3 - 0.395 * (double)num5 - 0.581 * (double)num6); int num9 = (int)((double)(int)num3 + 2.302 * (double)num5); rgb[row * lclStride + i * 3] = (byte)((num7 > 255) ? 255u : ((num7 >= 0) ? ((uint)num7) : 0u)); rgb[row * lclStride + i * 3 + 1] = (byte)((num8 > 255) ? 255u : ((num8 >= 0) ? ((uint)num8) : 0u)); rgb[row * lclStride + i * 3 + 2] = (byte)((num9 > 255) ? 255u : ((num9 >= 0) ? ((uint)num9) : 0u)); } }); return rgb; } [Obsolete("Use overload with stride parameter in order to deal with uneven dimensions.")] public static byte[] I420toBGR(byte[] data, int width, int height) { int stride; return I420toBGR(data, width, height, out stride); } public static byte[] I420toBGR(byte[] data, int width, int height, out int stride, int dop = 1) { int num = width * height; int num2 = (width + 1) / 2 * ((height + 1) / 2) * 2; if (data == null || data.Length < num + num2) { throw new ApplicationException($"I420 buffer supplied to I420toBGR was too small, expected {num + num2} but got {data?.Length}."); } int uOffset = num; int vOffset = num + num2 / 2; int lclStride = (stride = (width * 3 + 3) / 4 * 4); byte[] bgr = new byte[height * stride]; if (!_optDOP.ContainsKey(dop)) { _optDOP[dop] = new ParallelOptions { MaxDegreeOfParallelism = dop }; } Parallel.For(0, height, _optDOP[dop], delegate(int row) { for (int i = 0; i < width; i++) { byte num3 = data[i + row * width]; int num4 = i / 2 + row / 2 * width / 2; int num5 = data[uOffset + num4] - 128; int num6 = data[vOffset + num4] - 128; int num7 = (int)((double)(int)num3 + 1.14 * (double)num6); int num8 = (int)((double)(int)num3 - 0.395 * (double)num5 - 0.581 * (double)num6); int num9 = (int)((double)(int)num3 + 2.302 * (double)num5); bgr[row * lclStride + i * 3] = (byte)((num9 > 255) ? 255u : ((num9 >= 0) ? ((uint)num9) : 0u)); bgr[row * lclStride + i * 3 + 1] = (byte)((num8 > 255) ? 255u : ((num8 >= 0) ? ((uint)num8) : 0u)); bgr[row * lclStride + i * 3 + 2] = (byte)((num7 > 255) ? 255u : ((num7 >= 0) ? ((uint)num7) : 0u)); } }); return bgr; } [Obsolete("Use overload with stride parameter in order to deal with uneven dimensions.")] public static byte[] NV12toBGR(byte[] data, int width, int height) { return NV12toBGR(data, width, height, width * 3); } public static byte[] NV12toBGR(byte[] data, int width, int height, int stride, int dop = 1) { int num = width * height; int num2 = (width + 1) / 2 * ((height + 1) / 2) * 2; if (data == null || data.Length < num + num2) { throw new ApplicationException($"NV12 buffer supplied to NV12toBGR was too small, expected {num + num2} but got {data?.Length}."); } int uvOffset = num; byte[] bgr = new byte[height * stride]; if (!_optDOP.ContainsKey(dop)) { _optDOP[dop] = new ParallelOptions { MaxDegreeOfParallelism = dop }; } Parallel.For(0, height, _optDOP[dop], delegate(int row) { for (int i = 0; i < width; i++) { byte num3 = data[i + row * width]; int num4 = row / 2 * width + i / 2 * 2; int num5 = data[uvOffset + num4] - 128; int num6 = data[uvOffset + num4 + 1] - 128; int num7 = (int)((double)(int)num3 + 1.14 * (double)num6); int num8 = (int)((double)(int)num3 - 0.395 * (double)num5 - 0.581 * (double)num6); int num9 = (int)((double)(int)num3 + 2.302 * (double)num5); bgr[row * stride + i * 3] = (byte)((num9 > 255) ? 255u : ((num9 >= 0) ? ((uint)num9) : 0u)); bgr[row * stride + i * 3 + 1] = (byte)((num8 > 255) ? 255u : ((num8 >= 0) ? ((uint)num8) : 0u)); bgr[row * stride + i * 3 + 2] = (byte)((num7 > 255) ? 255u : ((num7 >= 0) ? ((uint)num7) : 0u)); } }); return bgr; } public static byte[] NV12toI420(byte[] nv12, int width, int height, int dop = 1) { int num = width * height; int uvWidth = (width + 1) / 2; int num2 = (height + 1) / 2; int num3 = uvWidth * num2 * 2; if (nv12 == null || nv12.Length < num + num3) { throw new ApplicationException($"NV12 buffer supplied to NV12toI420 was too small, expected {num + num3} but got {nv12?.Length}."); } byte[] i420 = new byte[num + num3]; Buffer.BlockCopy(nv12, 0, i420, 0, num); int nv12UvOffset = num; int i420UOffset = num; int i420VOffset = num + uvWidth * num2; if (!_optDOP.ContainsKey(dop)) { _optDOP[dop] = new ParallelOptions { MaxDegreeOfParallelism = dop }; } Parallel.For(0, num2, _optDOP[dop], delegate(int row) { for (int j = 0; j < uvWidth; j++) { int num4 = nv12UvOffset + row * uvWidth * 2 + j * 2; int num5 = i420UOffset + row * uvWidth + j; int num6 = i420VOffset + row * uvWidth + j; i420[num5] = nv12[num4]; i420[num6] = nv12[num4 + 1]; } }); return i420; } public static byte[] I420toNV12(byte[] i420, int width, int height, int dop = 1) { int num = width * height; int uvWidth = (width + 1) / 2; int num2 = (height + 1) / 2; int num3 = uvWidth * num2 * 2; if (i420 == null || i420.Length < num + num3) { throw new ApplicationException($"I420 buffer supplied to I420toNV12 was too small, expected {num + num3} but got {i420?.Length}."); } byte[] nv12 = new byte[num + num3]; Buffer.BlockCopy(i420, 0, nv12, 0, num); int i420UOffset = num; int i420VOffset = num + uvWidth * num2; int nv12UvOffset = num; if (!_optDOP.ContainsKey(dop)) { _optDOP[dop] = new ParallelOptions { MaxDegreeOfParallelism = dop }; } Parallel.For(0, num2, _optDOP[dop], delegate(int row) { for (int j = 0; j < uvWidth; j++) { int num4 = i420UOffset + row * uvWidth + j; int num5 = i420VOffset + row * uvWidth + j; int num6 = nv12UvOffset + row * uvWidth * 2 + j * 2; nv12[num6] = i420[num4]; nv12[num6 + 1] = i420[num5]; } }); return nv12; } } }