/* * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using static Interop.AudioIO; namespace Tizen.Multimedia { /// /// Provides the ability to directly manage the system audio output devices and play the PCM (pulse-code modulation) data. /// public class AudioPlayback : IDisposable { /// /// Specifies the minimum value allowed for the audio capture, in Hertz (Hz). /// /// public static readonly int MinSampleRate = 8000; /// /// Specifies the maximum value allowed for the audio capture, in Hertz (Hz). /// /// public static readonly int MaxSampleRate = 48000; private IntPtr _handle = IntPtr.Zero; private AudioIOState _state = AudioIOState.Idle; #region Event /// /// Occurs when the audio playback data can be written. /// /// public event EventHandler BufferAvailable; private AudioStreamCallback _streamCallback; private void RegisterStreamCallback() { _streamCallback = (IntPtr handle, uint bytes, IntPtr _) => { BufferAvailable?.Invoke(this, new AudioPlaybackBufferAvailableEventArgs((int)bytes)); }; AudioIOUtil.ThrowIfError( AudioOutput.SetStreamChangedCallback(_handle, _streamCallback, IntPtr.Zero), $"Failed to create {nameof(AudioPlayback)}"); } /// /// Occurs when the state of the AudioPlayback is changed. /// public event EventHandler StateChanged; private AudioStateChangedCallback _stateChangedCallback; private void RegisterStateChangedCallback() { _stateChangedCallback = (IntPtr handle, int previous, int current, bool byPolicy, IntPtr _) => { _state = (AudioIOState)current; StateChanged?.Invoke(this, new AudioIOStateChangedEventArgs((AudioIOState)previous, _state, byPolicy)); }; AudioIOUtil.ThrowIfError( AudioOutput.SetStateChangedCallback(_handle, _stateChangedCallback, IntPtr.Zero), $"Failed to create {nameof(AudioPlayback)}"); } #endregion /// /// Initializes a new instance of the AudioPlayback class with the specified sample rate, channel, and sample type. /// /// The audio sample rate (8000 ~ 48000Hz). /// The audio channel type. /// The audio sample type. /// /// is less than .\n /// -or-\n /// is greater than . /// /// /// is invalid.\n /// -or-\n /// is invalid. /// public AudioPlayback(int sampleRate, AudioChannel channel, AudioSampleType sampleType) { if (sampleRate < MinSampleRate || MaxSampleRate < sampleRate) { throw new ArgumentOutOfRangeException(nameof(sampleRate), sampleRate, $"Valid sampleRate range is { MinSampleRate } <= x <= { MaxSampleRate }."); } ValidationUtil.ValidateEnum(typeof(AudioChannel), channel, nameof(channel)); ValidationUtil.ValidateEnum(typeof(AudioSampleType), sampleType, nameof(sampleType)); SampleRate = sampleRate; Channel = channel; SampleType = sampleType; AudioIOUtil.ThrowIfError( AudioOutput.Create(SampleRate, (int)Channel, (int)SampleType, out _handle), $"Failed to create {nameof(AudioPlayback)}"); RegisterStreamCallback(); RegisterStateChangedCallback(); } ~AudioPlayback() { Dispose(false); } #region Dispose support private bool _isDisposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_isDisposed) { return; } if (_handle != IntPtr.Zero) { if (_state != AudioIOState.Idle) { try { Unprepare(); } catch (Exception) { } } AudioOutput.Destroy(_handle); _handle = IntPtr.Zero; _isDisposed = true; } } private void ValidateNotDisposed() { if (_isDisposed) { throw new ObjectDisposedException(GetType().Name); } } #endregion private void ValidateState(params AudioIOState[] desiredStates) { ValidateNotDisposed(); AudioIOUtil.ValidateState(_state, desiredStates); } /// /// Gets the sample rate of the audio output data stream, in Hertz (Hz). /// public int SampleRate { get; } /// /// Gets the channel type of the audio output data stream. /// public AudioChannel Channel { get; } /// /// Gets the sample type of the audio output data stream. /// public AudioSampleType SampleType { get; } /// /// Gets the sound type supported by the audio output device. /// /// The AudioPlayback has already been disposed of. public AudioStreamType StreamType { get { ValidateNotDisposed(); int audioType = 0; int ret = AudioOutput.GetSoundType(_handle, out audioType); MultimediaDebug.AssertNoError(ret); return (AudioStreamType)audioType; } } /// /// Gets the size allocated for the audio output buffer. /// /// The AudioPlayback has already been disposed of. public int GetBufferSize() { AudioIOUtil.ThrowIfError(AudioOutput.GetBufferSize(_handle, out var size)); return size; } /// /// Drains the buffered audio data from the output stream. /// It blocks the calling thread until the drain of the stream buffer is complete, for example, at the end of playback. /// /// The AudioPlayback has already been disposed of. /// The current state is . public void Drain() { ValidateState(AudioIOState.Running, AudioIOState.Paused); int ret = AudioOutput.Drain(_handle); MultimediaDebug.AssertNoError(ret); } /// /// Starts writing the audio data to the device. /// /// The buffer to write. /// The written size. /// is null. /// The length of is zero. /// The current state is not . /// The AudioPlayback has already been disposed of. public int Write(byte[] buffer) { ValidateState(AudioIOState.Running); if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } if (buffer.Length == 0) { throw new ArgumentException("buffer has no data.(the Length is zero.)", nameof(buffer)); } int ret = AudioOutput.Write(_handle, buffer, (uint)buffer.Length); AudioIOUtil.ThrowIfError(ret, "Failed to write buffer"); return ret; } /// /// Prepares the AudioPlayback. /// /// /// This must be called before . /// /// /// Operation failed due to an internal error.\n /// -or-\n /// The current state is not . /// /// The AudioPlayback has already been disposed of. /// public void Prepare() { ValidateState(AudioIOState.Idle); AudioIOUtil.ThrowIfError(AudioOutput.Prepare(_handle), $"Failed to prepare the {nameof(AudioPlayback)}"); } /// /// Unprepares the AudioPlayback. /// /// /// Operation failed due to an internal error.\n /// -or-\n /// The current state is . /// /// The AudioPlayback has already been disposed of. /// public void Unprepare() { ValidateState(AudioIOState.Running, AudioIOState.Paused); AudioIOUtil.ThrowIfError(AudioOutput.Unprepare(_handle), $"Failed to unprepare the {nameof(AudioPlayback)}"); } /// /// Pauses feeding of the audio data to the device. /// /// It has no effect if the current state is . /// /// The current state is .\n /// -or-\n /// The method is called in the event handler. /// /// The AudioPlayback has already been disposed of. /// public void Pause() { if (_state == AudioIOState.Paused) { return; } ValidateState(AudioIOState.Running); AudioIOUtil.ThrowIfError(AudioOutput.Pause(_handle)); } /// /// Resumes feeding of the audio data to the device. /// /// It has no effect if the current state is . /// /// The current state is .\n /// -or-\n /// The method is called in an event handler. /// /// The AudioPlayback has already been disposed of. /// public void Resume() { if (_state == AudioIOState.Running) { return; } ValidateState(AudioIOState.Paused); AudioIOUtil.ThrowIfError(AudioOutput.Resume(_handle)); } /// /// Flushes and discards the buffered audio data from the output stream. /// /// The current state is . /// The AudioPlayback has already been disposed of. public void Flush() { ValidateState(AudioIOState.Running, AudioIOState.Paused); int ret = AudioOutput.Flush(_handle); MultimediaDebug.AssertNoError(ret); } /// /// Applies the sound stream information to the AudioPlayback. /// /// The to apply for the AudioPlayback. /// is null. /// /// has already been disposed of.\n /// -or-\n /// The AudioPlayback has already been disposed of. /// /// is not supported. /// Not able to retrieve information from . public void ApplyStreamPolicy(AudioStreamPolicy streamPolicy) { if (streamPolicy == null) { throw new ArgumentNullException(nameof(streamPolicy)); } ValidateNotDisposed(); AudioIOUtil.ThrowIfError(AudioOutput.SetStreamInfo(_handle, streamPolicy.Handle)); } } }