2 * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 using static Interop.AudioIO;
20 namespace Tizen.Multimedia
23 /// Provides the ability to directly manage the system audio output devices and play the PCM (pulse-code modulation) data.
25 public class AudioPlayback : IDisposable
28 /// Specifies the minimum value allowed for the audio capture, in Hertz (Hz).
30 /// <seealso cref="SampleRate"/>
31 public static readonly int MinSampleRate = 8000;
34 /// Specifies the maximum value allowed for the audio capture, in Hertz (Hz).
36 /// <seealso cref="SampleRate"/>
37 public static readonly int MaxSampleRate = 48000;
39 private IntPtr _handle = IntPtr.Zero;
41 private AudioIOState _state = AudioIOState.Idle;
45 /// Occurs when the audio playback data can be written.
47 /// <seealso cref="Write(byte[])"/>
48 public event EventHandler<AudioPlaybackBufferAvailableEventArgs> BufferAvailable;
50 private AudioStreamCallback _streamCallback;
52 private void RegisterStreamCallback()
54 _streamCallback = (IntPtr handle, uint bytes, IntPtr _) =>
56 BufferAvailable?.Invoke(this, new AudioPlaybackBufferAvailableEventArgs((int)bytes));
59 AudioIOUtil.ThrowIfError(
60 AudioOutput.SetStreamChangedCallback(_handle, _streamCallback, IntPtr.Zero),
61 $"Failed to create {nameof(AudioPlayback)}");
65 /// Occurs when the state of the AudioPlayback is changed.
67 public event EventHandler<AudioIOStateChangedEventArgs> StateChanged;
69 private AudioStateChangedCallback _stateChangedCallback;
71 private void RegisterStateChangedCallback()
73 _stateChangedCallback = (IntPtr handle, int previous, int current, bool byPolicy, IntPtr _) =>
75 _state = (AudioIOState)current;
77 StateChanged?.Invoke(this,
78 new AudioIOStateChangedEventArgs((AudioIOState)previous, _state, byPolicy));
81 AudioIOUtil.ThrowIfError(
82 AudioOutput.SetStateChangedCallback(_handle, _stateChangedCallback, IntPtr.Zero),
83 $"Failed to create {nameof(AudioPlayback)}");
88 /// Initializes a new instance of the AudioPlayback class with the specified sample rate, channel, and sample type.
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
96 /// <paramref name="sampleRate"/> is greater than <see cref="MaxSampleRate"/>.
98 /// <exception cref="ArgumentException">
99 /// <paramref name="channel"/> is invalid.\n
101 /// <paramref name="sampleType"/> is invalid.
103 public AudioPlayback(int sampleRate, AudioChannel channel, AudioSampleType sampleType)
105 if (sampleRate < MinSampleRate || MaxSampleRate < sampleRate)
107 throw new ArgumentOutOfRangeException(nameof(sampleRate), sampleRate,
108 $"Valid sampleRate range is { MinSampleRate } <= x <= { MaxSampleRate }.");
111 ValidationUtil.ValidateEnum(typeof(AudioChannel), channel, nameof(channel));
112 ValidationUtil.ValidateEnum(typeof(AudioSampleType), sampleType, nameof(sampleType));
114 SampleRate = sampleRate;
116 SampleType = sampleType;
118 AudioIOUtil.ThrowIfError(
119 AudioOutput.Create(SampleRate, (int)Channel, (int)SampleType, out _handle),
120 $"Failed to create {nameof(AudioPlayback)}");
122 RegisterStreamCallback();
123 RegisterStateChangedCallback();
131 #region Dispose support
132 private bool _isDisposed = false;
134 public void Dispose()
137 GC.SuppressFinalize(this);
140 protected virtual void Dispose(bool disposing)
147 if (_handle != IntPtr.Zero)
149 if (_state != AudioIOState.Idle)
160 AudioOutput.Destroy(_handle);
161 _handle = IntPtr.Zero;
166 private void ValidateNotDisposed()
170 throw new ObjectDisposedException(GetType().Name);
175 private void ValidateState(params AudioIOState[] desiredStates)
177 ValidateNotDisposed();
179 AudioIOUtil.ValidateState(_state, desiredStates);
183 /// Gets the sample rate of the audio output data stream, in Hertz (Hz).
185 public int SampleRate { get; }
188 /// Gets the channel type of the audio output data stream.
190 public AudioChannel Channel { get; }
193 /// Gets the sample type of the audio output data stream.
195 public AudioSampleType SampleType { get; }
198 /// Gets the sound type supported by the audio output device.
200 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed of.</exception>
201 public AudioStreamType StreamType
205 ValidateNotDisposed();
208 int ret = AudioOutput.GetSoundType(_handle, out audioType);
209 MultimediaDebug.AssertNoError(ret);
211 return (AudioStreamType)audioType;
216 /// Gets the size allocated for the audio output buffer.
218 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed of.</exception>
219 public int GetBufferSize()
221 AudioIOUtil.ThrowIfError(AudioOutput.GetBufferSize(_handle, out var size));
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.
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>
233 ValidateState(AudioIOState.Running, AudioIOState.Paused);
235 int ret = AudioOutput.Drain(_handle);
237 MultimediaDebug.AssertNoError(ret);
241 /// Starts writing the audio data to the device.
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)
251 ValidateState(AudioIOState.Running);
255 throw new ArgumentNullException(nameof(buffer));
258 if (buffer.Length == 0)
260 throw new ArgumentException("buffer has no data.(the Length is zero.)", nameof(buffer));
263 int ret = AudioOutput.Write(_handle, buffer, (uint)buffer.Length);
265 AudioIOUtil.ThrowIfError(ret, "Failed to write buffer");
271 /// Prepares the AudioPlayback.
274 /// This must be called before <see cref="Write(byte[])"/>.
276 /// <exception cref="InvalidOperationException">
277 /// Operation failed due to an internal error.\n
279 /// The current state is not <see cref="AudioIOState.Idle"/>.
281 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed of.</exception>
282 /// <seealso cref="Unprepare"/>
283 public void Prepare()
285 ValidateState(AudioIOState.Idle);
287 AudioIOUtil.ThrowIfError(AudioOutput.Prepare(_handle),
288 $"Failed to prepare the {nameof(AudioPlayback)}");
292 /// Unprepares the AudioPlayback.
294 /// <exception cref="InvalidOperationException">
295 /// Operation failed due to an internal error.\n
297 /// The current state is <see cref="AudioIOState.Idle"/>.
299 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed of.</exception>
300 /// <seealso cref="Prepare"/>
301 public void Unprepare()
303 ValidateState(AudioIOState.Running, AudioIOState.Paused);
305 AudioIOUtil.ThrowIfError(AudioOutput.Unprepare(_handle),
306 $"Failed to unprepare the {nameof(AudioPlayback)}");
310 /// Pauses feeding of the audio data to the device.
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
316 /// The method is called in the <see cref="BufferAvailable"/> event handler.
318 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed of.</exception>
319 /// <seealso cref="Resume"/>
322 if (_state == AudioIOState.Paused)
326 ValidateState(AudioIOState.Running);
328 AudioIOUtil.ThrowIfError(AudioOutput.Pause(_handle));
332 /// Resumes feeding of the audio data to the device.
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
338 /// The method is called in an event handler.
340 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed of.</exception>
341 /// <seealso cref="Pause"/>
344 if (_state == AudioIOState.Running)
348 ValidateState(AudioIOState.Paused);
350 AudioIOUtil.ThrowIfError(AudioOutput.Resume(_handle));
354 /// Flushes and discards the buffered audio data from the output stream.
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>
360 ValidateState(AudioIOState.Running, AudioIOState.Paused);
362 int ret = AudioOutput.Flush(_handle);
364 MultimediaDebug.AssertNoError(ret);
368 /// Applies the sound stream information to the AudioPlayback.
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
375 /// The AudioPlayback has already been disposed of.
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)
381 if (streamPolicy == null)
383 throw new ArgumentNullException(nameof(streamPolicy));
386 ValidateNotDisposed();
388 AudioIOUtil.ThrowIfError(AudioOutput.SetStreamInfo(_handle, streamPolicy.Handle));