/* * 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); } } } }