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 static readonly int MinSampleRate = 8000;
27 public static 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"/>.\n
86 /// <paramref name="sampleRate"/> is greater than <see cref="MaxSampleRate"/>.
88 /// <exception cref="ArgumentException">
89 /// <paramref name="channel"/> is invalid.\n
91 /// <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, nameof(channel));
102 ValidationUtil.ValidateEnum(typeof(AudioSampleType), sampleType, nameof(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 allocated for the audio output buffer.
208 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
209 public int GetBufferSize()
211 AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.GetBufferSize(_handle, out var size));
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)
219 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
220 /// <exception cref="InvalidOperationException">The current state is <see cref="AudioIOState.Idle"/>.</exception>
223 ValidateState(AudioIOState.Running, AudioIOState.Paused);
225 int ret = Interop.AudioIO.AudioOutput.Drain(_handle);
227 MultimediaDebug.AssertNoError(ret);
231 /// Starts writing the audio data to the device.
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)
241 ValidateState(AudioIOState.Running);
245 throw new ArgumentNullException(nameof(buffer));
248 if (buffer.Length == 0)
250 throw new ArgumentException("buffer has no data.(the Length is zero.)", nameof(buffer));
253 int ret = Interop.AudioIO.AudioOutput.Write(_handle, buffer, (uint)buffer.Length);
255 AudioIOUtil.ThrowIfError(ret, "Failed to write buffer");
261 /// Prepares the AudioPlayback.
264 /// This must be called before <see cref="Write(byte[])"/>.
266 /// <exception cref="InvalidOperationException">
267 /// Operation failed due to internal error.\n
269 /// The current state is not <see cref="AudioIOState.Idle"/>.
271 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
272 /// <seealso cref="Unprepare"/>
273 public void Prepare()
275 ValidateState(AudioIOState.Idle);
277 AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.Prepare(_handle),
278 $"Failed to prepare the {nameof(AudioPlayback)}");
282 /// Unprepares the AudioPlayback.
284 /// <exception cref="InvalidOperationException">
285 /// Operation failed due to internal error.\n
287 /// The current state is <see cref="AudioIOState.Idle"/>.
289 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
290 /// <seealso cref="Prepare"/>
291 public void Unprepare()
293 ValidateState(AudioIOState.Running, AudioIOState.Paused);
295 AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.Unprepare(_handle),
296 $"Failed to unprepare the {nameof(AudioPlayback)}");
300 /// Pauses feeding of audio data to the device.
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
306 /// The method is called in the <see cref="BufferAvailable"/> event handler.
308 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
309 /// <seealso cref="Resume"/>
312 if (_state == AudioIOState.Paused)
316 ValidateState(AudioIOState.Running);
318 AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.Pause(_handle));
322 /// Resumes feeding of audio data to the device.
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
328 /// The method is called in an event handler.
330 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
331 /// <seealso cref="Pause"/>
334 if (_state == AudioIOState.Running)
338 ValidateState(AudioIOState.Paused);
340 AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.Resume(_handle));
344 /// Flushes and discards buffered audio data from the output stream.
346 /// <exception cref="InvalidOperationException">The current state is <see cref="AudioIOState.Idle"/>.</exception>
347 /// <exception cref="ObjectDisposedException">The AudioPlayback has already been disposed.</exception>
350 ValidateState(AudioIOState.Running, AudioIOState.Paused);
352 int ret = Interop.AudioIO.AudioOutput.Flush(_handle);
354 MultimediaDebug.AssertNoError(ret);
358 /// Applies the sound stream information to the AudioPlayback.
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
365 /// The AudioPlayback has already been disposed.
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)
371 if (streamPolicy == null)
373 throw new ArgumentNullException(nameof(streamPolicy));
376 if (streamPolicy.Handle == IntPtr.Zero)
378 throw new ObjectDisposedException(nameof(streamPolicy));
381 ValidateNotDisposed();
383 AudioIOUtil.ThrowIfError(Interop.AudioIO.AudioOutput.SetStreamInfo(_handle, streamPolicy.Handle));