f61ef9e7f30ecb3b795ddd5af08c2229d039f055
[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.</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 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 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         /// <summary>
241         /// Flushes and discards buffered audio data from the input stream.
242         /// </summary>
243         /// <exception cref="InvalidOperationException">The current state is <see cref="AudioIOState.Idle"/>.</exception>
244         public void Flush()
245         {
246             ValidateState(AudioIOState.Running, AudioIOState.Paused);
247
248             int ret = AudioInput.Flush(_handle);
249
250             MultimediaDebug.AssertNoError(ret);
251         }
252
253         /// <summary>
254         /// Sets the sound stream information to the audio input.
255         /// </summary>
256         /// <param name="streamPolicy">The <see cref="AudioStreamPolicy"/> to apply for the AudioCapture.</param>
257         /// <exception cref="ArgumentNullException"><paramref name="streamPolicy"/> is null.</exception>
258         /// <exception cref="ObjectDisposedException"><paramref name="streamPolicy"/> has already been disposed.</exception>
259         /// <exception cref="NotSupportedException"><paramref name="streamPolicy"/> is not supported.</exception>
260         /// <exception cref="ArgumentException">Not able to retrieve information from <paramref name="streamPolicy"/>.</exception>
261         public void ApplyStreamPolicy(AudioStreamPolicy streamPolicy)
262         {
263             if (streamPolicy == null)
264             {
265                 throw new ArgumentNullException(nameof(streamPolicy));
266             }
267
268             ValidateNotDisposed();
269
270             AudioIOUtil.ThrowIfError(AudioInput.SetStreamInfo(_handle, streamPolicy.Handle));
271         }
272     }
273
274     /// <summary>
275     /// Provides the ability to record audio from system audio input devices in synchronous way.
276     /// </summary>
277     /// <privilege>http://tizen.org/privilege/recorder</privilege>
278     public class AudioCapture : AudioCaptureBase
279     {
280         /// <summary>
281         /// Initializes a new instance of the AudioCapture class with the specified sample rate, channel and sampleType.
282         /// </summary>
283         /// <param name="sampleRate">The audio sample rate.(8000 ~ 48000Hz)</param>
284         /// <param name="channel">The audio channel type.</param>
285         /// <param name="sampleType">The audio sample type.</param>
286         /// <exception cref="ArgumentOutOfRangeException">
287         ///     <paramref name="sampleRate"/> is less than <see cref="AudioCaptureBase.MinSampleRate"/>.\n
288         ///     -or-\n
289         ///     <paramref name="sampleRate"/> is greater than <see cref="AudioCaptureBase.MaxSampleRate"/>.
290         /// </exception>
291         /// <exception cref="ArgumentException">
292         ///     <paramref name="channel"/> is invalid.\n
293         ///     -or-\n
294         ///     <paramref name="sampleType"/> is invalid.
295         /// </exception>
296         /// <exception cref="InvalidOperationException">The required privilege is not specified.</exception>
297         /// <exception cref="NotSupportedException">The system does not support microphone.</exception>
298         public AudioCapture(int sampleRate, AudioChannel channel, AudioSampleType sampleType)
299             : base(sampleRate, channel, sampleType)
300         {
301         }
302
303         /// <summary>
304         /// Reads audio data from the audio input buffer.
305         /// </summary>
306         /// <param name="count">The number of bytes to be read.</param>
307         /// <returns>The buffer of audio data captured.</returns>
308         /// <exception cref="InvalidOperationException">The current state is not <see cref="AudioIOState.Running"/>.</exception>
309         /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is equal to or less than zero.</exception>
310         public byte[] Read(int count)
311         {
312             if (count <= 0)
313             {
314                 throw new ArgumentOutOfRangeException(nameof(count), count,
315                     $"{ nameof(count) } can't be equal to or less than zero.");
316             }
317             ValidateState(AudioIOState.Running);
318
319             byte[] buffer = new byte[count];
320
321             AudioIOUtil.ThrowIfError(AudioInput.Read(_handle, buffer, count),
322                 "Failed to read");
323
324             return buffer;
325         }
326     }
327
328     /// <summary>
329     /// Provides the ability to record audio from system audio input devices in asynchronous way.
330     /// </summary>
331     /// <privilege>http://tizen.org/privilege/recorder</privilege>
332     public class AsyncAudioCapture : AudioCaptureBase
333     {
334
335         /// <summary>
336         /// Occurs when audio data is available.
337         /// </summary>
338         public event EventHandler<AudioDataAvailableEventArgs> DataAvailable;
339
340         /// <summary>
341         /// Initializes a new instance of the AsyncAudioCapture class with the specified sample rate, channel and sampleType.
342         /// </summary>
343         /// <param name="sampleRate">The audio sample rate.(8000 ~ 48000Hz)</param>
344         /// <param name="channel">The audio channel type.</param>
345         /// <param name="sampleType">The audio sample type.</param>
346         /// <exception cref="ArgumentOutOfRangeException">
347         ///     <paramref name="sampleRate"/> is less than <see cref="AudioCaptureBase.MinSampleRate"/>.\n
348         ///     -or-\n
349         ///     <paramref name="sampleRate"/> is greater than <see cref="AudioCaptureBase.MaxSampleRate"/>.
350         /// </exception>
351         /// <exception cref="ArgumentException">
352         ///     <paramref name="channel"/> is invalid.\n
353         ///     -or-\n
354         ///     <paramref name="sampleType"/> is invalid.
355         /// </exception>
356         /// <exception cref="InvalidOperationException">The required privilege is not specified.</exception>
357         /// <exception cref="NotSupportedException">The system does not support microphone.</exception>
358         public AsyncAudioCapture(int sampleRate, AudioChannel channel, AudioSampleType sampleType)
359             : base(sampleRate, channel, sampleType)
360         {
361             _streamCallback = (IntPtr handle, uint length, IntPtr _) => { OnInputDataAvailable(handle, length); };
362
363             AudioIOUtil.ThrowIfError(
364                 AudioInput.SetStreamCallback(_handle, _streamCallback, IntPtr.Zero),
365                 $"Failed to initialize a { nameof(AsyncAudioCapture) }");
366         }
367
368         private AudioStreamCallback _streamCallback;
369
370         private void OnInputDataAvailable(IntPtr handle, uint length)
371         {
372             if (length == 0U)
373             {
374                 return;
375             }
376
377             IntPtr ptr = IntPtr.Zero;
378             try
379             {
380                 AudioIOUtil.ThrowIfError(AudioInput.Peek(_handle, out ptr, ref length));
381
382                 byte[] buffer = new byte[length];
383                 Marshal.Copy(ptr, buffer, 0, (int)length);
384
385                 AudioInput.Drop(_handle);
386
387                 DataAvailable?.Invoke(this, new AudioDataAvailableEventArgs(buffer));
388             }
389             catch (Exception e)
390             {
391                 Log.Error(nameof(AsyncAudioCapture), e.Message);
392             }
393         }
394     }
395 }