/*
* 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;
///
/// Releases all resources used by the object.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases the resources used by the object.
///
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
///
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));
}
}
}