英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论