1313// BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file.
1414//-----------------------------------------------------------------------------
1515
16- using Concentus ;
1716using System ;
1817using System . Collections . Generic ;
1918using System . Linq ;
20- using SIPSorceryMedia . Abstractions ;
21- using SIPSorcery . Sys ;
19+ using Concentus ;
2220using Concentus . Enums ;
21+ using Microsoft . Extensions . Logging ;
22+ using SIPSorcery . Sys ;
23+ using SIPSorceryMedia . Abstractions ;
2324
2425namespace SIPSorcery . Media
2526{
26- public class AudioEncoder : IAudioEncoder
27+ public class AudioEncoder : IAudioEncoder , IDisposable
2728 {
2829 private const int G722_BIT_RATE = 64000 ; // G722 sampling rate is 16KHz with bits per sample of 16.
2930 private const int OPUS_SAMPLE_RATE = 48000 ; // Opus codec sampling rate, 48KHz.
3031 private const int OPUS_CHANNELS = 2 ; // Opus codec number of channels.
31- private const int OPUS_MAXIMUM_FRAME_SIZE = 5760 ;
32+
33+ /// <summary>
34+ /// The max frame size that the OPUS encoder will accept is 2880 bytes (see IOpusEncoder.Encode).
35+ /// 2880 corresponds to a sample size of 30ms for a single channel at 48Khz with 16 bit PCM. Therefore
36+ /// the max sample size supported by OPUS is 30ms.
37+ /// </summary>
38+ private const int OPUS_MAXIMUM_INPUT_SAMPLES_PER_CHANNEL = 2880 ;
39+
40+ /// <summary>
41+ /// OPUS max encode size (see IOpusEncoder.Encode).
42+ /// </summary>
43+ private const int OPUS_MAXIMUM_ENCODED_FRAME_SIZE = 1275 ;
44+
45+ private static ILogger logger = Log . Logger ;
46+
47+ private bool _disposedValue = false ;
3248
3349 private G722Codec _g722Codec ;
3450 private G722CodecState _g722CodecState ;
@@ -57,8 +73,8 @@ public class AudioEncoder : IAudioEncoder
5773 new AudioFormat ( SDPWellKnownMediaFormatsEnum . G722 ) ,
5874 new AudioFormat ( SDPWellKnownMediaFormatsEnum . G729 ) ,
5975
60- // Need more testing befoer adding OPUS by default. 24 Dec 2024 AC.
61- //new AudioFormat(111, " OPUS" , OPUS_SAMPLE_RATE, OPUS_CHANNELS, "useinbandfec=1")
76+ // Need more testing before adding OPUS by default. 24 Dec 2024 AC.
77+ //new AudioFormat(111, AudioCodecsEnum. OPUS.ToString() , OPUS_SAMPLE_RATE, OPUS_CHANNELS, "useinbandfec=1")
6278 } ;
6379
6480 public List < AudioFormat > SupportedFormats
@@ -81,10 +97,15 @@ public AudioEncoder(bool includeLinearFormats = false, bool includeOpus = false)
8197
8298 if ( includeOpus )
8399 {
84- _supportedFormats . Add ( new AudioFormat ( 111 , " OPUS" , OPUS_SAMPLE_RATE , OPUS_CHANNELS , "useinbandfec=1" ) ) ;
100+ _supportedFormats . Add ( new AudioFormat ( 111 , AudioCodecsEnum . OPUS . ToString ( ) , OPUS_SAMPLE_RATE , OPUS_CHANNELS , "useinbandfec=1" ) ) ;
85101 }
86102 }
87103
104+ public AudioEncoder ( params AudioFormat [ ] supportedFormats )
105+ {
106+ _supportedFormats = supportedFormats . ToList ( ) ;
107+ }
108+
88109 public byte [ ] EncodeAudio ( short [ ] pcm , AudioFormat format )
89110 {
90111 if ( format . Codec == AudioCodecsEnum . G722 )
@@ -122,31 +143,36 @@ public byte[] EncodeAudio(short[] pcm, AudioFormat format)
122143 }
123144 else if ( format . Codec == AudioCodecsEnum . L16 )
124145 {
146+ // When netstandard2.1 can be used.
147+ //return MemoryMarshal.Cast<short, byte>(pcm)
148+
125149 // Put on the wire in network byte order (big endian).
126- return MemoryOperations . ToBigEndianBytes ( pcm ) ;
150+ return pcm . SelectMany ( x => new byte [ ] { ( byte ) ( x >> 8 ) , ( byte ) ( x ) } ) . ToArray ( ) ;
127151 }
128152 else if ( format . Codec == AudioCodecsEnum . PCM_S16LE )
129153 {
130154 // Put on the wire as little endian.
131- return MemoryOperations . ToLittleEndianBytes ( pcm ) ;
155+ return pcm . SelectMany ( x => new byte [ ] { ( byte ) ( x ) , ( byte ) ( x >> 8 ) } ) . ToArray ( ) ;
132156 }
133157 else if ( format . Codec == AudioCodecsEnum . OPUS )
134158 {
135159 if ( _opusEncoder == null )
136160 {
137- _opusEncoder = OpusCodecFactory . CreateEncoder ( format . ClockRate , format . ChannelCount , OpusApplication . OPUS_APPLICATION_VOIP ) ;
161+ var channelCount = format . ChannelCount > 0 ? format . ChannelCount : OPUS_CHANNELS ;
162+ _opusEncoder = OpusCodecFactory . CreateEncoder ( format . ClockRate , channelCount , OpusApplication . OPUS_APPLICATION_VOIP ) ;
138163 }
139164
140- // Opus expects PCM data in float format [-1.0, 1.0].
141- float [ ] pcmFloat = new float [ pcm . Length ] ;
142- for ( int i = 0 ; i < pcm . Length ; i ++ )
165+ if ( pcm . Length > _opusEncoder . NumChannels * OPUS_MAXIMUM_INPUT_SAMPLES_PER_CHANNEL )
143166 {
144- pcmFloat [ i ] = pcm [ i ] / 32768f ; // Convert to float range [-1.0, 1.0]
167+ logger . LogWarning ( "{audioEncoder} input sample of length {inputSize} supplied to OPUS encoder exceeded maximum limit of {maxLimit}. Reduce sampling period." , nameof ( AudioEncoder ) , pcm . Length , _opusEncoder . NumChannels * OPUS_MAXIMUM_INPUT_SAMPLES_PER_CHANNEL ) ;
168+ return [ ] ;
169+ }
170+ else
171+ {
172+ Span < byte > encodedSample = stackalloc byte [ OPUS_MAXIMUM_ENCODED_FRAME_SIZE ] ;
173+ int encodedLength = _opusEncoder . Encode ( pcm , pcm . Length / _opusEncoder . NumChannels , encodedSample , encodedSample . Length ) ;
174+ return encodedSample . Slice ( 0 , encodedLength ) . ToArray ( ) ;
145175 }
146-
147- byte [ ] encodedSample = new byte [ pcm . Length ] ;
148- int encodedLength = _opusEncoder . Encode ( pcmFloat , pcmFloat . Length / format . ChannelCount , encodedSample , encodedSample . Length ) ;
149- return encodedSample . AsSpan ( 0 , encodedLength ) . ToArray ( ) ;
150176 }
151177 else
152178 {
@@ -172,7 +198,7 @@ public short[] DecodeAudio(byte[] encodedSample, AudioFormat format)
172198 short [ ] decodedPcm = new short [ encodedSample . Length * 2 ] ;
173199 int decodedSampleCount = _g722Decoder . Decode ( _g722DecoderState , decodedPcm , encodedSample , encodedSample . Length ) ;
174200
175- return decodedPcm . AsSpan ( 0 , decodedSampleCount ) . ToArray ( ) ;
201+ return decodedPcm . Take ( decodedSampleCount ) . ToArray ( ) ;
176202 }
177203 if ( format . Codec == AudioCodecsEnum . G729 )
178204 {
@@ -209,10 +235,11 @@ public short[] DecodeAudio(byte[] encodedSample, AudioFormat format)
209235 {
210236 if ( _opusDecoder == null )
211237 {
212- _opusDecoder = OpusCodecFactory . CreateDecoder ( format . ClockRate , format . ChannelCount ) ;
238+ var channelCount = format . ChannelCount > 0 ? format . ChannelCount : OPUS_CHANNELS ;
239+ _opusDecoder = OpusCodecFactory . CreateDecoder ( format . ClockRate , channelCount ) ;
213240 }
214241
215- int maxSamples = OPUS_MAXIMUM_FRAME_SIZE * format . ChannelCount ;
242+ int maxSamples = OPUS_MAXIMUM_INPUT_SAMPLES_PER_CHANNEL * _opusDecoder . NumChannels ;
216243 float [ ] floatBuf = new float [ maxSamples ] ;
217244
218245 // Decode returns the number of samples per channel.
@@ -222,7 +249,7 @@ public short[] DecodeAudio(byte[] encodedSample, AudioFormat format)
222249 floatBuf . Length ,
223250 false ) ;
224251
225- int totalFloats = samplesPerChannel * format . ChannelCount ;
252+ int totalFloats = samplesPerChannel * _opusDecoder . NumChannels ;
226253
227254 // Convert to 16-bit interleaved PCM.
228255 short [ ] pcm16 = new short [ totalFloats ] ;
@@ -246,24 +273,33 @@ public short[] Resample(short[] pcm, int inRate, int outRate)
246273 return PcmResampler . Resample ( pcm , inRate , outRate ) ;
247274 }
248275
249- private short ClampToShort ( float value )
250- {
251- if ( value > short . MaxValue )
276+ private float ClampToFloat ( float value , float min , float max )
252277 {
253- return short . MaxValue ;
278+ if ( value < min ) { return min ; }
279+ if ( value > max ) { return max ; }
280+ return value ;
254281 }
255- if ( value < short . MinValue )
282+
283+ protected virtual void Dispose ( bool disposing )
284+ {
285+ if ( ! _disposedValue )
256286 {
257- return short . MinValue ;
287+ if ( disposing )
288+ {
289+ ( _opusEncoder as IDisposable ) ? . Dispose ( ) ;
290+ ( _opusDecoder as IDisposable ) ? . Dispose ( ) ;
291+ ( _g729Encoder as IDisposable ) ? . Dispose ( ) ;
292+ ( _g729Decoder as IDisposable ) ? . Dispose ( ) ;
293+ }
294+
295+ _disposedValue = true ;
258296 }
259- return ( short ) value ;
260297 }
261298
262- private float ClampToFloat ( float value , float min , float max )
299+ public void Dispose ( )
263300 {
264- if ( value < min ) { return min ; }
265- if ( value > max ) { return max ; }
266- return value ;
301+ Dispose ( disposing : true ) ;
302+ GC . SuppressFinalize ( this ) ;
267303 }
268304 }
269305}
0 commit comments