/*
* 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 System.Runtime.InteropServices;
using static Interop.AudioIO;
namespace Tizen.Multimedia
{
///
/// Provides the ability to directly manage the system audio input devices.
///
/// http://tizen.org/privilege/recorder
public abstract class AudioCaptureBase : 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;
internal IntPtr _handle = IntPtr.Zero;
private AudioIOState _state = AudioIOState.Idle;
internal AudioCaptureBase(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(
AudioInput.Create(SampleRate, (int)Channel, (int)SampleType, out _handle));
RegisterStateChangedCallback();
}
~AudioCaptureBase()
{
Dispose(false);
}
///
/// Occurs when the state of the AudioCapture 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(
AudioInput.SetStateChangedCallback(_handle, _stateChangedCallback, IntPtr.Zero));
}
#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)
{
}
}
AudioInput.Destroy(_handle);
_handle = IntPtr.Zero;
_isDisposed = true;
}
}
internal void ValidateNotDisposed()
{
if (_isDisposed)
{
throw new ObjectDisposedException(GetType().Name);
}
}
#endregion
internal void ValidateState(params AudioIOState[] desiredStates)
{
ValidateNotDisposed();
AudioIOUtil.ValidateState(_state, desiredStates);
}
///
/// Gets the sample rate of the audio input data stream, in Hertz (Hz).
///
public int SampleRate { get; }
///
/// Gets the channel type of the audio input data stream.
///
public AudioChannel Channel { get; }
///
/// Gets the sample type of the audio input data stream.
///
public AudioSampleType SampleType { get; }
///
/// Gets the size allocated for the audio input buffer.
///
/// The AudioPlayback has already been disposed of.
public int GetBufferSize()
{
AudioIOUtil.ThrowIfError(AudioInput.GetBufferSize(_handle, out var size));
return size;
}
///
/// Prepares the AudioCapture for reading audio data by starting buffering of audio data from the device.
///
///
/// Operation failed due to an internal error.\n
/// -or-\n
/// The current state is not .
///
///
public void Prepare()
{
ValidateState(AudioIOState.Idle);
AudioIOUtil.ThrowIfError(AudioInput.Prepare(_handle),
"Failed to prepare the AudioCapture");
}
///
/// Unprepares the AudioCapture.
///
///
/// Operation failed due to an internal error.\n
/// -or-\n
/// The current state is .
///
///
public void Unprepare()
{
ValidateState(AudioIOState.Running, AudioIOState.Paused);
AudioIOUtil.ThrowIfError(AudioInput.Unprepare(_handle),
"Failed to unprepare the AudioCapture");
}
///
/// Pauses buffering of audio data from the device.
///
///
/// The current state is .\n
/// -or-\n
/// The method is called in the event handler.
///
///
public void Pause()
{
if (_state == AudioIOState.Paused)
{
return;
}
ValidateState(AudioIOState.Running);
AudioIOUtil.ThrowIfError(AudioInput.Pause(_handle));
}
///
/// Resumes buffering audio data from the device.
///
///
/// The current state is .\n
/// -or-\n
/// The method is called in the event handler.
///
///
public void Resume()
{
if (_state == AudioIOState.Running)
{
return;
}
ValidateState(AudioIOState.Paused);
AudioIOUtil.ThrowIfError(AudioInput.Resume(_handle));
}
///
/// Flushes and discards buffered audio data from the input stream.
///
/// The current state is .
public void Flush()
{
ValidateState(AudioIOState.Running, AudioIOState.Paused);
int ret = AudioInput.Flush(_handle);
MultimediaDebug.AssertNoError(ret);
}
///
/// Sets the sound stream information to the audio input.
///
/// The to apply for the AudioCapture.
/// is null.
/// 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(AudioInput.SetStreamInfo(_handle, streamPolicy.Handle));
}
}
///
/// Provides the ability to record audio from system audio input devices in a synchronous way.
///
/// http://tizen.org/privilege/recorder
public class AudioCapture : AudioCaptureBase
{
///
/// Initializes a new instance of the AudioCapture class with the specified sample rate, channel, and sampleType.
///
/// 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.
///
/// The required privilege is not specified.
/// The system does not support microphone.
public AudioCapture(int sampleRate, AudioChannel channel, AudioSampleType sampleType)
: base(sampleRate, channel, sampleType)
{
}
///
/// Reads audio data from the audio input buffer.
///
/// The number of bytes to be read.
/// The buffer of audio data captured.
/// The current state is not .
/// is equal to or less than zero.
public byte[] Read(int count)
{
if (count <= 0)
{
throw new ArgumentOutOfRangeException(nameof(count), count,
$"{ nameof(count) } can't be equal to or less than zero.");
}
ValidateState(AudioIOState.Running);
byte[] buffer = new byte[count];
AudioIOUtil.ThrowIfError(AudioInput.Read(_handle, buffer, count),
"Failed to read");
return buffer;
}
}
///
/// Provides the ability to record audio from system audio input devices in an asynchronous way.
///
/// http://tizen.org/privilege/recorder
public class AsyncAudioCapture : AudioCaptureBase
{
///
/// Occurs when audio data is available.
///
public event EventHandler DataAvailable;
///
/// Initializes a new instance of the AsyncAudioCapture class with the specified sample rate, channel and sampleType.
///
/// 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.
///
/// The required privilege is not specified.
/// The system does not support microphone.
public AsyncAudioCapture(int sampleRate, AudioChannel channel, AudioSampleType sampleType)
: base(sampleRate, channel, sampleType)
{
_streamCallback = (IntPtr handle, uint length, IntPtr _) => { OnInputDataAvailable(handle, length); };
AudioIOUtil.ThrowIfError(
AudioInput.SetStreamCallback(_handle, _streamCallback, IntPtr.Zero),
$"Failed to initialize a { nameof(AsyncAudioCapture) }");
}
private AudioStreamCallback _streamCallback;
private void OnInputDataAvailable(IntPtr handle, uint length)
{
if (length == 0U)
{
return;
}
IntPtr ptr = IntPtr.Zero;
try
{
AudioIOUtil.ThrowIfError(AudioInput.Peek(_handle, out ptr, ref length));
byte[] buffer = new byte[length];
Marshal.Copy(ptr, buffer, 0, (int)length);
AudioInput.Drop(_handle);
DataAvailable?.Invoke(this, new AudioDataAvailableEventArgs(buffer));
}
catch (Exception e)
{
Log.Error(nameof(AsyncAudioCapture), e.Message);
}
}
}
}