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