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.
19 namespace Tizen.Multimedia
22 /// Provides the ability to directly manage the system audio output devices and play PCM (pulse-code modulation) data.
24 public class AudioPlayback : IDisposable
26 public readonly int MinSampleRate = 8000;
27 public readonly int MaxSampleRate = 48000;
29 private IntPtr _handle = IntPtr.Zero;
31 private AudioIOState _state = AudioIOState.Idle;
35 /// Occurs when audio playback data can be written.
37 /// <seealso cref="Write(byte[])"/>
38 public event EventHandler<AudioPlaybackBufferAvailableEventArgs> BufferAvailable;
40 private Interop.AudioIO.AudioStreamCallback _streamCallback;
42 private void RegisterStreamCallback()
44 _streamCallback = (IntPtr handle, uint bytes, IntPtr _) =>
46 BufferAvailable?.Invoke(this, new AudioPlaybackBufferAvailableEventArgs((int)bytes));
49 AudioIOUtil.ThrowIfError(
50 Interop.AudioIO.AudioOutput.SetStreamChangedCallback(_handle, _streamCallback, IntPtr.Zero),
51 $"Failed to create {nameof(AudioPlayback)}");
55 /// Occurs when the state of the AudioPlayback is changed.
57 public event EventHandler<AudioIOStateChangedEventArgs> StateChanged;
59 private Interop.AudioIO.AudioStateChangedCallback _stateChangedCallback;
61 private void RegisterStateChangedCallback()
63 _stateChangedCallback = (IntPtr handle, int previous, int current, bool byPolicy, IntPtr _) =>
65 _state = (AudioIOState)current;
67 StateChanged?.Invoke(this,
68 new AudioIOStateChangedEventArgs((AudioIOState)previous, _state, byPolicy));
71 AudioIOUtil.ThrowIfError(
72 Interop.AudioIO.AudioOutput.SetStateChangedCallback(_handle, _stateChangedCallback, IntPtr.Zero),
73 $"Failed to create {nameof(AudioPlayback)}");
78 /// Initializes a new instance of the AudioPlayback class with the specified sample rate, channel and sample type.
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"/>.
86 /// <paramref name="sampleRate"/> is greater than <see cref="MaxSampleRate"/>.
88 /// <exception cref="ArgumentException">
89 /// The value of <paramref name="channel"/> is invalid.
91 /// The value of <paramref name="sampleType"/> is invalid.
93 public AudioPlayback(int sampleRate, AudioChannel channel, AudioSampleType sampleType)
95 if (sampleRate < MinSampleRate || MaxSampleRate < sampleRate)
97 throw new ArgumentOutOfRangeException(nameof(sampleRate), sampleRate,
98 $"Valid sampleRate range is { MinSampleRate } <= x <= { MaxSampleRate }.");
101 ValidationUtil.ValidateEnum(typeof(AudioChannel), channel);
102 ValidationUtil.ValidateEnum(typeof(AudioSampleType), sampleType);
104 SampleRate = sampleRate;
106 SampleType = sampleType;
108 AudioIOUtil.ThrowIfError(
109 Interop.AudioIO.AudioOutput.Create(SampleRate, (int)Channel, (int)SampleType, out _handle),
110 $"Failed to create {nameof(AudioPlayback)}");
112 RegisterStreamCallback();
113 RegisterStateChangedCallback();
121 #region Dispose support
122 private bool _isDisposed = false;
124 public void Dispose()
127 GC.SuppressFinalize(this);
130 protected virtual void Dispose(bool disposing)
137 if (_handle != IntPtr.Zero)
139 if (_state != AudioIOState.Idle)
150 Interop.AudioIO.AudioOutput.Destroy(_handle);
151 _handle = IntPtr.Zero;
156 private void ValidateNotDisposed()
160 throw new ObjectDisposedException(GetType().Name);
165 private void ValidateState(params AudioIOState[] desiredStates)
167 ValidateNotDisposed();
169 AudioIOUtil.ValidateState(_state, desiredStates);
173 /// Gets the sample rate of the audio output data stream.
175 public int SampleRate { get; }
178 /// Gets the channel type of the audio output data stream.
180 public AudioChannel Channel { get; }
183 /// Gets the sample type of the audio output data stream.
185 public AudioSampleType SampleType { get; }
188 /// Gets the sound type supported by the audio output device.
190 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
191 public AudioStreamType StreamType
195 ValidateNotDisposed();
198 int ret = Interop.AudioIO.AudioOutput.GetSoundType(_handle, out audioType);
199 MultimediaDebug.AssertNoError(ret);
201 return (AudioStreamType)audioType;
206 /// Gets the size to be allocated for the audio output buffer.
208 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
209 public int GetBufferSize()
212 AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.GetBufferSize(_handle, out size));
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)
220 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
221 /// <exception cref="InvalidOperationException">The current state is <see cref="AudioIOState.Idle"/>.</exception>
224 ValidateState(AudioIOState.Running, AudioIOState.Paused);
226 int ret = Interop.AudioIO.AudioOutput.Drain(_handle);
228 MultimediaDebug.AssertNoError(ret);
232 /// Starts writing the audio data to the device.
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)
242 ValidateState(AudioIOState.Running);
246 throw new ArgumentNullException(nameof(buffer));
249 if (buffer.Length == 0)
251 throw new ArgumentException("buffer has no data.(the Length is zero.)", nameof(buffer));
254 int ret = Interop.AudioIO.AudioOutput.Write(_handle, buffer, (uint)buffer.Length);
256 AudioIOUtil.ThrowIfError(ret, "Failed to write buffer");
262 /// Prepares the AudioPlayback.
265 /// This must be called before <see cref="Write(byte[])"/>.
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"/>.
272 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
273 /// <seealso cref="Unprepare"/>
274 public void Prepare()
276 ValidateState(AudioIOState.Idle);
278 AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.Prepare(_handle),
279 $"Failed to prepare the {nameof(AudioPlayback)}");
283 /// Unprepares the AudioPlayback.
285 /// <exception cref="InvalidOperationException">
286 /// Operation failed due to internal error.
287 /// <para>-or-</para>
288 /// The current state is <see cref="AudioIOState.Idle"/>.
290 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
291 /// <seealso cref="Prepare"/>
292 public void Unprepare()
294 ValidateState(AudioIOState.Running, AudioIOState.Paused);
296 AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.Unprepare(_handle),
297 $"Failed to unprepare the {nameof(AudioPlayback)}");
301 /// Pauses feeding of audio data to the device.
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.
309 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
310 /// <seealso cref="Resume"/>
313 if (_state == AudioIOState.Paused)
317 ValidateState(AudioIOState.Running);
319 AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.Pause(_handle));
323 /// Resumes feeding of audio data to the device.
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.
331 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
332 /// <seealso cref="Pause"/>
335 if (_state == AudioIOState.Running)
339 ValidateState(AudioIOState.Paused);
341 AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.Resume(_handle));
345 /// Flushes and discards buffered audio data from the output stream.
347 /// <exception cref="InvalidOperationException">The current state is <see cref="AudioIOState.Idle"/>.</exception>
348 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
351 ValidateState(AudioIOState.Running, AudioIOState.Paused);
353 int ret = Interop.AudioIO.AudioOutput.Flush(_handle);
355 MultimediaDebug.AssertNoError(ret);
359 /// Applies the sound stream information to the AudioPlayback.
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.
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)
372 if (streamPolicy == null)
374 throw new ArgumentNullException(nameof(streamPolicy));
377 if (streamPolicy.Handle == IntPtr.Zero)
379 throw new ObjectDisposedException(nameof(streamPolicy));
382 ValidateNotDisposed();
384 AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.SetStreamInfo(_handle, streamPolicy.Handle));