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