AudioRecord:以float[]格式录制,然后转换为byte[] Xamarin。

huangapple go评论70阅读模式
英文:

AudioRecord: Record in float[] then convert to byte[] Xamarin

问题

你目前已经为Android制作了自己的NAudio音频记录驱动程序,对于任何BufferMilliseconds下的PCM 16位音频都可以正常工作,但是一旦切换到需要录制到float[],然后在调用DataAvailable事件之前转换为byte[]的PCM Float,音频在20ms BufferMilliseconds下是流畅和良好的,但如果增加到40ms或50ms,音频会变得非常杂乱。您已经寻找了一段时间,但似乎没有提到或提供解决这个问题的方法。

这是我正在记录到float并将其转换的方式:

else if (WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat)
{
   float[] floatBuffer = new float[bufferSize / 4];
   byte[] byteBuffer = new byte[bufferSize];
   var floatsRead = audioRecord.Read(floatBuffer, 0, floatBuffer.Length, 1);
   Buffer.BlockCopy(floatBuffer, 0, byteBuffer, 0, byteBuffer.Length);
   if (floatsRead > 0)
   {
      DataAvailable?.Invoke(this, new WaveInEventArgs(byteBuffer, bufferSize));
   }
}

最初在RecordingLogic()方法中使用了WaveBuffer,但结果也是相同的问题。

这是我完整的脚本。

英文:

Currently I have made my own NAudio audio record driver for android which works fine for PCM 16 bit audio at any BufferMilliseconds however as soon as I record in PCM Float which requires me to record into a float[] then convert to byte[] before invoking the DataAvailable event the audio is smooth and decent at 20ms BufferMilliseconds but if I increase to say 40ms or 50ms the audio becomes very choppy. I have also been looking around for quite some time, but none seem to reference or provide some resolution to the issue I am facing here.

This is how I am recording into the float and converting it.

else if (WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat)
{
   float[] floatBuffer = new float[bufferSize / 4];
   byte[] byteBuffer = new byte[bufferSize];
   var floatsRead = audioRecord.Read(floatBuffer, 0, floatBuffer.Length, 1);
   Buffer.BlockCopy(floatBuffer, 0, byteBuffer, 0, byteBuffer.Length);
   if (floatsRead > 0)
   {
      DataAvailable?.Invoke(this, new WaveInEventArgs(byteBuffer, bufferSize));
   }
}

Originally was using a WaveBuffer that you can see I am using below in the RecordingLogic() method but that also resulted in the same issue.

This is the entire script I have.

using Android.Media;
using NAudio.CoreAudioApi;
using System;
using System.Threading;

namespace NAudio.Wave
{
    public class AudioRecorder : IWaveIn
    {
        #region Private Fields
        private AudioRecord audioRecord;
        private SynchronizationContext synchronizationContext;
        private CaptureState captureState;
        private bool disposed;
        #endregion

        #region Public Fields
        public WaveFormat WaveFormat { get; set; }
        public int BufferMilliseconds { get; set; }
        public AudioSource audioSource { get; set; }
        #endregion

        public event EventHandler<WaveInEventArgs> DataAvailable;
        public event EventHandler<StoppedEventArgs> RecordingStopped;

        #region Constructor
        public AudioRecorder()
        {
            audioRecord = null;
            synchronizationContext = SynchronizationContext.Current;
            WaveFormat = new WaveFormat(8000, 16, 1);
            BufferMilliseconds = 100;
            captureState = CaptureState.Stopped;
            disposed = false;
            audioSource = AudioSource.Mic;

        }
        #endregion

        #region Private Methods
        private void OpenRecorder()
        {
            //We want to make sure the recorder is definitely closed.
            CloseRecorder();
            Encoding encoding;
            ChannelIn channelMask;

            //Set the encoding
            if (WaveFormat.Encoding == WaveFormatEncoding.Pcm || WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat)
            {
                encoding = WaveFormat.BitsPerSample switch
                {
                    8 => Encoding.Pcm8bit,
                    16 => Encoding.Pcm16bit,
                    32 => Encoding.PcmFloat,
                    _ => throw new ArgumentException("Input wave provider must be 8-bit, 16-bit or 32bit", nameof(WaveFormat))
                };
            }
            else
            {
                throw new ArgumentException("Input wave provider must be PCM or IEEE Float", nameof(WaveFormat));
            }

            //Set the channel type. Only accepts Mono or Stereo
            channelMask = WaveFormat.Channels switch
            {
                1 => ChannelIn.Mono,
                2 => ChannelIn.Stereo,
                _ => throw new ArgumentException("Input wave provider must be mono or stereo", nameof(WaveFormat))
            };

            //Determine the buffer size
            int minBufferSize = AudioRecord.GetMinBufferSize(WaveFormat.SampleRate, channelMask, encoding);
            int bufferSize = WaveFormat.ConvertLatencyToByteSize(BufferMilliseconds);
            if (bufferSize < minBufferSize)
            {
                bufferSize = minBufferSize;
            }

            //Create the AudioRecord Object.
            audioRecord = new AudioRecord(audioSource, WaveFormat.SampleRate, channelMask, encoding, bufferSize);
        }

        private void CloseRecorder()
        {
            //Make sure that the recorder was opened
            if (audioRecord != null)
            {
                //Make sure that the recorder is stopped.
                if (audioRecord.RecordingState != RecordState.Stopped)
                    audioRecord.Stop();

                //Release and dispose of everything.
                audioRecord.Release();
                audioRecord.Dispose();
                audioRecord = null;
            }
        }

        private void RecordThread()
        {
            Exception exception = null;
            try
            {
                RecordingLogic();
            }
            catch (Exception ex)
            {
                exception = ex;
            }
            finally
            {
                captureState = CaptureState.Stopped;
                RaiseRecordingStoppedEvent(exception);
            }
        }

        private void RecordingLogic()
        {
            //Initialize the wave buffer
            int bufferSize = BufferMilliseconds * WaveFormat.AverageBytesPerSecond / 1000;
            if (bufferSize % WaveFormat.BlockAlign != 0)
            {
                bufferSize -= bufferSize % WaveFormat.BlockAlign;
            }

            WaveBuffer waveBuffer = new WaveBuffer(bufferSize);
            captureState = CaptureState.Capturing;

            //Run the record loop
            while (captureState != CaptureState.Stopped)
            {
                if (captureState != CaptureState.Capturing)
                {
                    Thread.Sleep(10);
                    continue;
                }

                if (WaveFormat.Encoding == WaveFormatEncoding.Pcm)
                {
                    var bytesRead = audioRecord.Read(waveBuffer.ByteBuffer, 0, bufferSize);
                    if (bytesRead > 0)
                    {
                        DataAvailable?.Invoke(this, new WaveInEventArgs(waveBuffer.ByteBuffer, bytesRead));
                    }
                }
                else if (WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat)
                {
                    try
                    {
                        float[] floatBuffer = new float[bufferSize / 4];
                        byte[] byteBuffer = new byte[bufferSize];
                        var floatsRead = audioRecord.Read(floatBuffer, 0, floatBuffer.Length, 1);
                        Buffer.BlockCopy(floatBuffer, 0, byteBuffer, 0, byteBuffer.Length);
                        if (floatsRead > 0)
                        {
                            DataAvailable?.Invoke(this, new WaveInEventArgs(byteBuffer, bufferSize));
                        }
                    }
                    catch(Exception ex)
                    {
                        Console.WriteLine(ex);
                    }
                }
            }
        }

        private void RaiseRecordingStoppedEvent(Exception e)
        {
            var handler = RecordingStopped;
            if (handler != null)
            {
                if (synchronizationContext == null)
                {
                    handler(this, new StoppedEventArgs(e));
                }
                else
                {
                    synchronizationContext.Post(state => handler(this, new StoppedEventArgs(e)), null);
                }
            }
        }

        private void ThrowIfDisposed()
        {
            if (disposed)
                throw new ObjectDisposedException(GetType().FullName);
        }
        #endregion

        #region Public Methods
        public void StartRecording()
        {
            //Check if we haven't disposed.
            ThrowIfDisposed();
            //Check if we are already recording.
            if (captureState == CaptureState.Capturing)
            {
                return;
            }

            //Make sure that we have some format to use.
            if (WaveFormat == null)
            {
                throw new ArgumentNullException(nameof(WaveFormat));
            }

            //Starting capture procedure
            OpenRecorder();
            captureState = CaptureState.Starting;
            audioRecord.StartRecording();
            ThreadPool.QueueUserWorkItem((state) => RecordThread(), null);
        }

        public void StopRecording()
        {
            ThrowIfDisposed();

            //Check if it has already been stopped
            if (captureState != CaptureState.Stopped)
            {
                captureState = CaptureState.Stopped;
                audioRecord.Stop();
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

        protected virtual void Dispose(bool disposing)
        {
            //Clean up any managed and unmanaged resources
            if (!disposed)
            {
                if (disposing)
                {
                    if (captureState != CaptureState.Stopped)
                    {
                        StopRecording();
                    }
                    audioRecord?.Release();
                    audioRecord?.Dispose();
                }
                disposed = true;
            }
        }
    }
}

答案1

得分: 0

I was able to figure out the issue. The issue was I was setting the wrong bufferSizeInBytes variable when creating the AudioRecord object. All I had to do was change this.

//Determine the buffer size
int minBufferSize = AudioRecord.GetMinBufferSize(WaveFormat.SampleRate, channelMask, encoding);
int bufferSize = WaveFormat.ConvertLatencyToByteSize(BufferMilliseconds);

if (bufferSize < minBufferSize)
{
   bufferSize = minBufferSize;
}

//Create the AudioRecord Object.
audioRecord = new AudioRecord(audioSource, WaveFormat.SampleRate, channelMask, encoding, bufferSize);

To This

//Determine the buffer size
int minBufferSize = AudioRecord.GetMinBufferSize(WaveFormat.SampleRate, channelMask, encoding);
int bufferSize = WaveFormat.ConvertLatencyToByteSize(BufferMilliseconds);

if (WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat) bufferSize = bufferSize / 4;
if (bufferSize < minBufferSize)
{
   bufferSize = minBufferSize;
}

//Create the AudioRecord Object.
audioRecord = new AudioRecord(audioSource, WaveFormat.SampleRate, channelMask, encoding, bufferSize);

I wasn't calculating the correct amount of float[] length for the sample size defined in BufferMilliseconds as it is asking for how big the float[] length should be instead of byte[] length which is 4x the size of the length of float[]. Audio now sounds smooth with whatever BufferMilliseconds I set it to.

Edit
I also changed
DataAvailable?.Invoke(this, new WaveInEventArgs(byteBuffer, bufferSize));

To
DataAvailable?.Invoke(this, new WaveInEventArgs(byteBuffer, floatsRead * 4));

So I could properly return exactly how many bytes were read.

英文:

I was able to figure out the issue. The issue was I was setting the wrong bufferSizeInBytes variable when creating the AudioRecord object. All I had to do was change this.

//Determine the buffer size
int minBufferSize = AudioRecord.GetMinBufferSize(WaveFormat.SampleRate, channelMask, encoding);
int bufferSize = WaveFormat.ConvertLatencyToByteSize(BufferMilliseconds);

if (bufferSize < minBufferSize)
{
   bufferSize = minBufferSize;
}

//Create the AudioRecord Object.
audioRecord = new AudioRecord(audioSource, WaveFormat.SampleRate, channelMask, encoding, bufferSize);

To This

//Determine the buffer size
int minBufferSize = AudioRecord.GetMinBufferSize(WaveFormat.SampleRate, channelMask, encoding);
int bufferSize = WaveFormat.ConvertLatencyToByteSize(BufferMilliseconds);

if (WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat) bufferSize = bufferSize / 4;
if (bufferSize < minBufferSize)
{
   bufferSize = minBufferSize;
}

//Create the AudioRecord Object.
audioRecord = new AudioRecord(audioSource, WaveFormat.SampleRate, channelMask, encoding, bufferSize);

I wasn't calculating the correct amount of float[] length for the sample size defined in BufferMilliseconds as it is asking for how big the float[] length should be instead of byte[] length which is 4x the size of the length of float[]. Audio now sounds smooth with whatever BufferMilliseconds I set it to.

Edit

I also changed
DataAvailable?.Invoke(this, new WaveInEventArgs(byteBuffer, bufferSize));

To
DataAvailable?.Invoke(this, new WaveInEventArgs(byteBuffer, floatsRead * 4));

So I could properly return exactly how many bytes were read.

huangapple
  • 本文由 发表于 2023年5月7日 13:56:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76192398.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定