64b43b5156d8912672d0d4166315784074a405b5
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia / 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 readonly int MinSampleRate = 8000;
27         public 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"/>.
85         ///     <para>-or-</para>
86         ///     <paramref name="sampleRate"/> is greater than <see cref="MaxSampleRate"/>.
87         /// </exception>
88         /// <exception cref="ArgumentException">
89         ///     The value of <paramref name="channel"/> is invalid.
90         ///     <para>-or-</para>
91         ///     The value of <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);
102             ValidationUtil.ValidateEnum(typeof(AudioSampleType), 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 to be 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             int size = 0;
212             AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.GetBufferSize(_handle, out size));
213             return size;
214         }
215
216         /// <summary>
217         /// Drains buffered audio data from the output stream.
218         /// It blocks the calling thread until draining the stream buffer completely. (e.g end of playback)
219         /// </summary>
220         /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
221         /// <exception cref="InvalidOperationException">The current state is <see cref="AudioIOState.Idle"/>.</exception>
222         public void Drain()
223         {
224             ValidateState(AudioIOState.Running, AudioIOState.Paused);
225
226             int ret = Interop.AudioIO.AudioOutput.Drain(_handle);
227
228             MultimediaDebug.AssertNoError(ret);
229         }
230
231         /// <summary>
232         /// Starts writing the audio data to the device.
233         /// </summary>
234         /// <param name="buffer">The buffer to write.</param>
235         /// <returns>The written size.</returns>
236         /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is null.</exception>
237         /// <exception cref="ArgumentException">The length of <paramref name="buffer"/> is zero.</exception>
238         /// <exception cref="InvalidOperationException">The current state is not <see cref="AudioIOState.Running"/>.</exception>
239         /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
240         public int Write(byte[] buffer)
241         {
242             ValidateState(AudioIOState.Running);
243
244             if (buffer == null)
245             {
246                 throw new ArgumentNullException(nameof(buffer));
247             }
248
249             if (buffer.Length == 0)
250             {
251                 throw new ArgumentException("buffer has no data.(the Length is zero.)", nameof(buffer));
252             }
253
254             int ret = Interop.AudioIO.AudioOutput.Write(_handle, buffer, (uint)buffer.Length);
255
256             AudioIOUtil.ThrowIfError(ret, "Failed to write buffer");
257
258             return ret;
259         }
260
261         /// <summary>
262         /// Prepares the AudioPlayback.
263         /// </summary>
264         /// <remarks>
265         /// This must be called before <see cref="Write(byte[])"/>.
266         /// </remarks>
267         /// <exception cref="InvalidOperationException">
268         ///     Operation failed due to internal error.
269         ///     <para>-or-</para>
270         ///     The current state is not <see cref="AudioIOState.Idle"/>.
271         /// </exception>
272         /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
273         /// <seealso cref="Unprepare"/>
274         public void Prepare()
275         {
276             ValidateState(AudioIOState.Idle);
277
278             AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.Prepare(_handle),
279                 $"Failed to prepare the {nameof(AudioPlayback)}");
280         }
281
282         /// <summary>
283         /// Unprepares the AudioPlayback.
284         /// </summary>
285         /// <exception cref="InvalidOperationException">
286         ///     Operation failed due to internal error.
287         ///     <para>-or-</para>
288         ///     The current state is <see cref="AudioIOState.Idle"/>.
289         /// </exception>
290         /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
291         /// <seealso cref="Prepare"/>
292         public void Unprepare()
293         {
294             ValidateState(AudioIOState.Running, AudioIOState.Paused);
295
296             AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.Unprepare(_handle),
297                 $"Failed to unprepare the {nameof(AudioPlayback)}");
298         }
299
300         /// <summary>
301         /// Pauses feeding of audio data to the device.
302         /// </summary>
303         /// <remarks>It has no effect if the current is the <see cref="AudioIOState.Paused"/>.</remarks>
304         /// <exception cref="InvalidOperationException">
305         ///     The current state is <see cref="AudioIOState.Idle"/>.
306         ///     <para>-or-</para>
307         ///     The method is called in the <see cref="BufferAvailable"/> event handler.
308         /// </exception>
309         /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
310         /// <seealso cref="Resume"/>
311         public void Pause()
312         {
313             if (_state == AudioIOState.Paused)
314             {
315                 return;
316             }
317             ValidateState(AudioIOState.Running);
318
319             AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.Pause(_handle));
320         }
321
322         /// <summary>
323         /// Resumes feeding of audio data to the device.
324         /// </summary>
325         /// <remarks>It has no effect if the current is the <see cref="AudioIOState.Running"/>.</remarks>
326         /// <exception cref="InvalidOperationException">
327         ///     The current state is <see cref="AudioIOState.Idle"/>.
328         ///     <para>-or-</para>
329         ///     The method is called in an event handler.
330         /// </exception>
331         /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
332         /// <seealso cref="Pause"/>
333         public void Resume()
334         {
335             if (_state == AudioIOState.Running)
336             {
337                 return;
338             }
339             ValidateState(AudioIOState.Paused);
340
341             AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.Resume(_handle));
342         }
343
344         /// <summary>
345         /// Flushes and discards buffered audio data from the output stream.
346         /// </summary>
347         /// <exception cref="InvalidOperationException">The current state is <see cref="AudioIOState.Idle"/>.</exception>
348         /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
349         public void Flush()
350         {
351             ValidateState(AudioIOState.Running, AudioIOState.Paused);
352
353             int ret = Interop.AudioIO.AudioOutput.Flush(_handle);
354
355             MultimediaDebug.AssertNoError(ret);
356         }
357
358         /// <summary>
359         /// Applies the sound stream information to the AudioPlayback.
360         /// </summary>
361         /// <param name="streamPolicy">The <see cref="AudioStreamPolicy"/> to apply for the AudioPlayback.</param>
362         /// <exception cref="ArgumentNullException"><paramref name="streamPolicy"/> is null.</exception>
363         /// <exception cref="ObjectDisposedException">
364         ///     <paramref name="streamPolicy"/> has already been disposed.
365         ///     <para>-or-</para>
366         ///     The AudioPlayback has already been disposed.
367         /// </exception>
368         /// <exception cref="NotSupportedException"><paramref name="streamPolicy"/> is not supported.</exception>
369         /// <exception cref="ArgumentException">Not able to retrieve information from <paramref name="streamPolicy"/>.</exception>
370         public void ApplyStreamPolicy(AudioStreamPolicy streamPolicy)
371         {
372             if (streamPolicy == null)
373             {
374                 throw new ArgumentNullException(nameof(streamPolicy));
375             }
376
377             if (streamPolicy.Handle == IntPtr.Zero)
378             {
379                 throw new ObjectDisposedException(nameof(streamPolicy));
380             }
381
382             ValidateNotDisposed();
383
384             AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.SetStreamInfo(_handle, streamPolicy.Handle));
385         }
386     }
387 }