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