[Audio] make max/min sample-rate as static
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia / AudioIO / AudioCapture.cs
1 /*
2 * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3 *
4 * Licensed under the Apache License, Version 2.0 (the License);
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an AS IS BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 using System;
18 using System.Runtime.InteropServices;
19
20 namespace Tizen.Multimedia
21 {
22     /// <summary>
23     /// Provides the ability to directly manage the system audio input devices.
24     /// </summary>
25     /// <remarks>The recorder privilege(http://tizen.org/privilege/recorder) is required.</remarks>
26     public abstract class AudioCaptureBase : IDisposable
27     {
28         public static readonly int MinSampleRate = 8000;
29         public static readonly int MaxSampleRate = 48000;
30
31         internal IntPtr _handle = IntPtr.Zero;
32
33         private AudioIOState _state = AudioIOState.Idle;
34
35         internal AudioCaptureBase(int sampleRate, AudioChannel channel, AudioSampleType sampleType)
36         {
37             if (sampleRate < MinSampleRate || MaxSampleRate < sampleRate)
38             {
39                 throw new ArgumentOutOfRangeException(nameof(sampleRate), sampleRate,
40                     $"Valid sampleRate range is { MinSampleRate } <= x <= { MaxSampleRate }.");
41             }
42
43             ValidationUtil.ValidateEnum(typeof(AudioChannel), channel);
44             ValidationUtil.ValidateEnum(typeof(AudioSampleType), sampleType);
45
46             SampleRate = sampleRate;
47             Channel = channel;
48             SampleType = sampleType;
49
50             AudioIOUtil.ThrowIfError(
51                 Interop.AudioIO.AudioInput.Create(SampleRate, (int)Channel, (int)SampleType, out _handle));
52
53             RegisterStateChangedCallback();
54         }
55
56         ~AudioCaptureBase()
57         {
58             Dispose(false);
59         }
60
61         /// <summary>
62         /// Occurs when the state of the AudioCapture is changed.
63         /// </summary>
64         public event EventHandler<AudioIOStateChangedEventArgs> StateChanged;
65
66         private Interop.AudioIO.AudioStateChangedCallback _stateChangedCallback;
67
68         private void RegisterStateChangedCallback()
69         {
70             _stateChangedCallback = (IntPtr handle, int previous, int current, bool byPolicy, IntPtr _) =>
71             {
72                 _state = (AudioIOState)current;
73
74                 StateChanged?.Invoke(this,
75                     new AudioIOStateChangedEventArgs((AudioIOState)previous, _state, byPolicy));
76             };
77
78             AudioIOUtil.ThrowIfError(
79                 Interop.AudioIO.AudioInput.SetStateChangedCallback(_handle, _stateChangedCallback, IntPtr.Zero));
80         }
81
82         #region Dispose support
83         private bool _isDisposed = false;
84
85         public void Dispose()
86         {
87             Dispose(true);
88             GC.SuppressFinalize(this);
89         }
90
91         protected virtual void Dispose(bool disposing)
92         {
93             if (_isDisposed)
94             {
95                 return;
96             }
97
98             if (_handle != IntPtr.Zero)
99             {
100                 if (_state != AudioIOState.Idle)
101                 {
102                     try
103                     {
104                         Unprepare();
105                     }
106                     catch (Exception)
107                     {
108                     }
109                 }
110
111                 Interop.AudioIO.AudioInput.Destroy(_handle);
112                 _handle = IntPtr.Zero;
113                 _isDisposed = true;
114             }
115         }
116
117         internal void ValidateNotDisposed()
118         {
119             if (_isDisposed)
120             {
121                 throw new ObjectDisposedException(GetType().Name);
122             }
123         }
124         #endregion
125
126         internal void ValidateState(params AudioIOState[] desiredStates)
127         {
128             ValidateNotDisposed();
129
130             AudioIOUtil.ValidateState(_state, desiredStates);
131         }
132
133         /// <summary>
134         /// Gets the sample rate of the audio input data stream.
135         /// </summary>
136         public int SampleRate { get; }
137
138         /// <summary>
139         /// Gets the channel type of the audio input data stream.
140         /// </summary>
141         public AudioChannel Channel { get; }
142
143         /// <summary>
144         /// Gets the sample type of the audio input data stream.
145         /// </summary>
146         public AudioSampleType SampleType { get; }
147
148         /// <summary>
149         /// Gets the size to be allocated for the audio input buffer.
150         /// </summary>
151         /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
152         public int GetBufferSize()
153         {
154             int size;
155             AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioInput.GetBufferSize(_handle, out size));
156             return size;
157         }
158
159         /// <summary>
160         /// Prepares the AudioCapture for reading audio data by starting buffering of audio data from the device.
161         /// </summary>
162         /// <exception cref="InvalidOperationException">
163         ///     Operation failed due to internal error.
164         ///     <para>-or-</para>
165         ///     The current state is not <see cref="AudioIOState.Idle"/>.
166         /// </exception>
167         /// <seealso cref="Unprepare"/>
168         public void Prepare()
169         {
170             ValidateState(AudioIOState.Idle);
171
172             AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioInput.Prepare(_handle),
173                 "Failed to prepare the AudioCapture");
174         }
175
176         /// <summary>
177         /// Unprepares the AudioCapture.
178         /// </summary>
179         /// <exception cref="InvalidOperationException">
180         ///     Operation failed due to internal error.
181         ///     <para>-or-</para>
182         ///     The current state is <see cref="AudioIOState.Idle"/>.
183         /// </exception>
184         /// <seealso cref="Prepare"/>
185         public void Unprepare()
186         {
187             ValidateState(AudioIOState.Running, AudioIOState.Paused);
188
189             AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioInput.Unprepare(_handle),
190                 "Failed to unprepare the AudioCapture");
191         }
192
193         /// <summary>
194         /// Pauses buffering of audio data from the device.
195         /// </summary>
196         /// <exception cref="InvalidOperationException">
197         ///     The current state is <see cref="AudioState.Idle"/>.
198         ///     <para>-or-</para>
199         ///     The method is called in the <see cref="AsyncAudioCapture.DataAvailable"/> event handler.
200         /// </exception>
201         /// <seealso cref="Resume"/>
202         public void Pause()
203         {
204             if (_state == AudioIOState.Paused)
205             {
206                 return;
207             }
208             ValidateState(AudioIOState.Running);
209
210             AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioInput.Pause(_handle));
211         }
212         /// <summary>
213         /// Resumes buffering audio data from the device.
214         /// </summary>
215         /// <exception cref="InvalidOperationException">
216         ///     The current state is <see cref="AudioState.Idle"/>.
217         ///     <para>-or-</para>
218         ///     The method is called in the <see cref="AsyncAudioCapture.DataAvailable"/> event handler.
219         /// </exception>
220         /// <seealso cref="Pause"/>
221         public void Resume()
222         {
223             if (_state == AudioIOState.Running)
224             {
225                 return;
226             }
227             ValidateState(AudioIOState.Paused);
228
229             AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioInput.Resume(_handle));
230         }
231         /// <summary>
232         /// Flushes and discards buffered audio data from the input stream.
233         /// </summary>
234         /// <exception cref="InvalidOperationException">The current state is <see cref="AudioState.Idle"/>.</exception>
235         public void Flush()
236         {
237             ValidateState(AudioIOState.Running, AudioIOState.Paused);
238
239             int ret = Interop.AudioIO.AudioInput.Flush(_handle);
240
241             MultimediaDebug.AssertNoError(ret);
242         }
243
244         /// <summary>
245         /// Sets the sound stream information to the audio input.
246         /// </summary>
247         /// <param name="streamPolicy">The <see cref="AudioStreamPolicy"/> to apply for the AudioCapture.</param>
248         /// <exception cref="ArgumentNullException"><paramref name="streamPolicy"/> is null.</exception>
249         /// <exception cref="ObjectDisposedException"><paramref name="streamPolicy"/> has already been disposed.</exception>
250         /// <exception cref="NotSupportedException"><paramref name="streamPolicy"/> is not supported.</exception>
251         /// <exception cref="ArgumentException">Not able to retrieve information from <paramref name="streamPolicy"/>.</exception>
252         public void ApplyStreamPolicy(AudioStreamPolicy streamPolicy)
253         {
254             if (streamPolicy == null)
255             {
256                 throw new ArgumentNullException(nameof(streamPolicy));
257             }
258
259             if (streamPolicy.Handle == IntPtr.Zero)
260             {
261                 throw new ObjectDisposedException(nameof(streamPolicy));
262             }
263
264             ValidateNotDisposed();
265
266             AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioInput.SetStreamInfo(_handle, streamPolicy.Handle));
267         }
268     }
269
270     /// <summary>
271     /// Provides the ability to record audio from system audio input devices in synchronous way.
272     /// </summary>
273     /// <remarks>The recorder privilege(http://tizen.org/privilege/recorder) is required.</remarks>
274     public class AudioCapture : AudioCaptureBase
275     {
276         /// <summary>
277         /// Initializes a new instance of the AudioCapture class with the specified sample rate, channel and sampleType.
278         /// </summary>
279         /// <param name="sampleRate">The audio sample rate.(8000 ~ 48000Hz)</param>
280         /// <param name="channel">The audio channel type.</param>
281         /// <param name="sampleType">The audio sample type.</param>
282         /// <exception cref="ArgumentOutOfRangeException">
283         ///     <paramref name="sampleRate"/> is less than <see cref="MinSampleRate"/>.
284         ///     <para>-or-</para>
285         ///     <paramref name="sampleRate"/> is greater than <see cref="MaxSampleRate"/>.
286         /// </exception>
287         /// <exception cref="ArgumentException">
288         ///     The value of <paramref name="channel"/> is invalid.
289         ///     <para>-or-</para>
290         ///     The value of <paramref name="sampleType"/> is invalid.
291         /// </exception>
292         /// <exception cref="InvalidOperationException">The required privilege is not specified.</exception>
293         /// <exception cref="NotSupportedException">The system does not support microphone.</exception>
294         public AudioCapture(int sampleRate, AudioChannel channel, AudioSampleType sampleType)
295             : base(sampleRate, channel, sampleType)
296         {
297         }
298
299         /// <summary>
300         /// Reads audio data from the audio input buffer.
301         /// </summary>
302         /// <param name="length"></param>
303         /// <returns>The buffer of audio data receiving an input</returns>
304         /// <exception cref="InvalidOperationException">The current state is not <see cref="AudioIOState.Running"/>.</exception>
305         /// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> is equal to or less than zero.</exception>
306         public byte[] Read(int length)
307         {
308             if (length <= 0)
309             {
310                 throw new ArgumentOutOfRangeException(nameof(length), length,
311                     $"{ nameof(length) } can't be equal to or less than zero.");
312             }
313             ValidateState(AudioIOState.Running);
314
315             byte[] buffer = new byte[length];
316
317             AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioInput.Read(_handle, buffer, length),
318                 "Failed to read");
319
320             return buffer;
321         }
322     }
323
324     /// <summary>
325     /// Provides the ability to record audio from system audio input devices in asynchronous way.
326     /// </summary>
327     /// <remarks>The recorder privilege(http://tizen.org/privilege/recorder) is required.</remarks>
328     public class AsyncAudioCapture : AudioCaptureBase
329     {
330
331         /// <summary>
332         /// Occurs when audio data is available.
333         /// </summary>
334         public event EventHandler<AudioDataAvailableEventArgs> DataAvailable;
335
336         /// <summary>
337         /// Initializes a new instance of the AsyncAudioCapture class with the specified sample rate, channel and sampleType.
338         /// </summary>
339         /// <param name="sampleRate">The audio sample rate.(8000 ~ 48000Hz)</param>
340         /// <param name="channel">The audio channel type.</param>
341         /// <param name="sampleType">The audio sample type.</param>
342         /// <exception cref="ArgumentOutOfRangeException">
343         ///     <paramref name="sampleRate"/> is less than <see cref="MinSampleRate"/>.
344         ///     <para>-or-</para>
345         ///     <paramref name="sampleRate"/> is greater than <see cref="MaxSampleRate"/>.
346         /// </exception>
347         /// <exception cref="ArgumentException">
348         ///     The value of <paramref name="channel"/> is invalid.
349         ///     <para>-or-</para>
350         ///     The value of <paramref name="sampleType"/> is invalid.
351         /// </exception>
352         /// <exception cref="InvalidOperationException">The required privilege is not specified.</exception>
353         /// <exception cref="NotSupportedException">The system does not support microphone.</exception>
354         public AsyncAudioCapture(int sampleRate, AudioChannel channel, AudioSampleType sampleType)
355             : base(sampleRate, channel, sampleType)
356         {
357             _streamCallback = (IntPtr handle, uint length, IntPtr _) => { OnInputDataAvailable(handle, length); };
358
359             AudioIOUtil.ThrowIfError(
360                 Interop.AudioIO.AudioInput.SetStreamCallback(_handle, _streamCallback, IntPtr.Zero),
361                 $"Failed to initialize a { nameof(AsyncAudioCapture) }");
362         }
363
364         private Interop.AudioIO.AudioStreamCallback _streamCallback;
365
366         private void OnInputDataAvailable(IntPtr handle, uint length)
367         {
368             if (length == 0U)
369             {
370                 return;
371             }
372
373             IntPtr ptr = IntPtr.Zero;
374             try
375             {
376                 AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioInput.Peek(_handle, out ptr, ref length));
377
378                 byte[] buffer = new byte[length];
379                 Marshal.Copy(ptr, buffer, 0, (int)length);
380
381                 Interop.AudioIO.AudioInput.Drop(_handle);
382
383                 DataAvailable?.Invoke(this, new AudioDataAvailableEventArgs(buffer));
384             }
385             catch (Exception e)
386             {
387                 Log.Error(nameof(AsyncAudioCapture), e.Message);
388             }
389         }
390     }
391 }