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