AudioRecord 有时会跳过音频数据
AudioRecord sometimes skips audio data
我使用 AudioRecord 在 wav 文件中录制音频。我在辅助线程的循环中执行 read() 操作,数据在另一秒内传递到队列中。写入文件的线程。
问题如下:
当我选择将音频数据保存在外部SD卡中时,音频包含一些skips/distortions的毫秒数(只是有时)。其他用户在内部 SD 卡上保存时也有此问题
这是我实例化 AudioRecord 对象的方式:
initBufferSize = assureMinBufferSize();
if (minBufferSize == AudioRecord.ERROR_BAD_VALUE) {
throw new Exception("" + ErrorCodes.ERROR_CODE_0);
}
else if (minBufferSize == AudioRecord.ERROR) {
throw new Exception("" + ErrorCodes.ERROR_CODE_0);
}
buffer = new byte[initBufferSize];
aRecorder = new AudioRecord(mSource, sRate, nChannel, AudioFormat.ENCODING_PCM_16BIT, initBufferSize);
这就是我合并 AudioRecorder 对象的方式:
private class AudioReaderRunnable implements Runnable {
private volatile boolean stopped;
@Override
public void run() {
while (!stopped) {
while(mState == State.RECORDING){
read();
}
}
Log.i(getClass().getName(), "AudioReade thread stopped!");
}
private void read(){
if(aRecorder == null)
return;
// "dirty" patch for some null crash for some users
if(buffer == null)
buffer = new byte[initBufferSize];
//int x = aRecorder.read(buffer, 0, buffer.length);
int x = readFully(buffer, 0, buffer.length);
if(x <= 0)
return;
payloadSize += x;
mWavData.arrayCopy(buffer);
mWavData.setGain(rGain);
mWavData.setBitsPerSamples(bitsPerSample);
mWavData.setNrChannels(nChannelsNumber);
// send audio to another thread for writing
mAudioWritter.add(audioData);
}
private int readFully(byte[] data, int off, int length) {
int read = 0;
int result = 0;
int requestedSize = length;
if(aRecorder == null){
return read;
}
try {
result = aRecorder.read(data, off, requestedSize);
} catch (Exception e) {
Log.e(getClass().getName(), e.getMessage(), e);
if(aRecorder == null)
return read;
}
read = result;
while (requestedSize != result && !stopped) {
if(aRecorder == null)
return read;
requestedSize -= result;
try {
result = aRecorder.read(data, result - 1, requestedSize);
} catch (Exception e) {
Log.e(getClass().getName(), e.getMessage(), e);
if(aRecorder == null)
return read;
}
read += result;
}
return read;
}
public void stopThread() {
stopped = true;
}
}
Try This Code:-
//This Variable are define in class
private int recordingcounter;
private static final int RECORDER_BPP = 16;
private static final String AUDIO_RECORDER_FILE_EXT_WAV = ".wav";
private static final String AUDIO_RECORDER_FOLDER = "AudioRecorder";
private static final String AUDIO_RECORDER_TEMP_FILE = "record_temp.raw";
private static final int RECORDER_SAMPLERATE = 16000;// /44100; //High voice
// recording result
// for use sample
// rate is 44100
private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_STEREO;
private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
private AudioRecord recorder = null;
private int bufferSize = 0;
private Thread recordingThread = null;
private boolean isRecording = false;
//--------------------- start button click event in add-----------------------------------//
bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE, RECORDER_CHANNELS,RECORDER_AUDIO_ENCODING);
try {
startRecording();
} catch (Exception e) {
// TODO: handle exception
// Toast.makeText(RecordingActivity.this,
// e.getMessage(), Toast.LENGTH_SHORT)
// .show();
}
//-------------------- stop button click event in add---------------------------//
try {
stopRecording();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
//---------this methods are add in your class-----------------------------//
// --------------------------------------------------------Sound Recording
// Code----------------------------------------------------//
private String getFilename() {
String filepath = Environment.getExternalStorageDirectory().getPath();
File file = new File(filepath, AUDIO_RECORDER_FOLDER);
if (!file.exists()) {
file.mkdirs();
}
return (file.getAbsolutePath() + "/" + "voiceFile" + AUDIO_RECORDER_FILE_EXT_WAV);
}
private String getTempFilename() {
String filepath = Environment.getExternalStorageDirectory().getPath();
File file = new File(filepath, AUDIO_RECORDER_FOLDER);
if (!file.exists()) {
file.mkdirs();
}
File tempFile = new File(filepath, AUDIO_RECORDER_TEMP_FILE);
if (tempFile.exists())
tempFile.delete();
return (file.getAbsolutePath() + "/" + AUDIO_RECORDER_TEMP_FILE);
}
@SuppressLint("NewApi")
private void startRecording() {
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
RECORDER_SAMPLERATE, RECORDER_CHANNELS,
RECORDER_AUDIO_ENCODING, bufferSize);
recorder.startRecording();
// Log.e("Audio session", ""+recorder.getAudioSessionId());
isRecording = true;
recordingThread = new Thread(new Runnable() {
@Override
public void run() {
writeAudioDataToFile();
}
}, "AudioRecorder Thread");
recordingThread.start();
}
private void writeAudioDataToFile() {
byte data[] = new byte[bufferSize];
String filename = getTempFilename();
FileOutputStream os = null;
try {
os = new FileOutputStream(filename);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int read = 0;
if (null != os) {
while (isRecording) {
read = recorder.read(data, 0, bufferSize);
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
try {
os.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void stopRecording() {
if (null != recorder) {
isRecording = false;
recorder.stop();
recorder.release();
// Utility.Log("", "" + recorder.getRecordingState());
// AppLog.logString(""+recorder.getRecordingState());
recorder = null;
recordingThread = null;
}
copyWaveFile(getTempFilename(), getFilename());
}
private void copyWaveFile(String inFilename, String outFilename) {
FileInputStream in = null;
FileOutputStream out = null;
long totalAudioLen = 0;
long totalDataLen = totalAudioLen + 36;
long longSampleRate = RECORDER_SAMPLERATE;
int channels = 2;
long byteRate = RECORDER_BPP * RECORDER_SAMPLERATE * channels / 8;
byte[] data = new byte[bufferSize];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
// Utility.Log("", "File size: " + totalDataLen);
// AppLog.logString("File size: " + totalDataLen);
WriteWaveFileHeader(out, totalAudioLen, totalDataLen,
longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, long longSampleRate, int channels, long byteRate)
throws IOException {
byte[] header = new byte[44];
header[0] = 'R'; // RIFF/WAVE header
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f'; // 'fmt ' chunk
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1; // format = 1
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
header[32] = (byte) (2 * 16 / 8); // block align
header[33] = 0;
header[34] = RECORDER_BPP; // bits per sample
header[35] = 0;
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
//------------和清单文件这一行------------//
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
我在 Xamarin.Android 上遇到了类似的问题,我是这样解决的:
AudioRecord 缓冲区就像一个 window - 当缓冲区被填满时,它会从头开始重写。所以我们需要确保缓冲区足够大,以便我们在它被重写之前从中读取。
大问题来了 - 当垃圾收集器启动时,它会停止所有应用程序线程大约一秒钟 - 这足以让记录器写入大约 100KB 的数据到缓冲区(当使用 44100Hz 单声道 Pcm16 位时),但我们在此期间不读取,因为应用程序线程已停止!因此,如果缓冲区不够大,数据会在我们有机会读取和写入文件之前被重写。因此,音频文件缺少垃圾收集期间录制的音频。
因此,您需要使用足够大的 bufferSize 来初始化 AudioRecord
,以便在垃圾回收事件之后继续存在。对于不同的音频配置,这将是不同的大小。对我来说(使用 44100Hz,单声道,PCM16 位),缓冲区大小 512,000 就可以了。
我不确定 GC 在本机上的工作方式 Android,但我相信它可能是相似的。
我使用 AudioRecord 在 wav 文件中录制音频。我在辅助线程的循环中执行 read() 操作,数据在另一秒内传递到队列中。写入文件的线程。
问题如下: 当我选择将音频数据保存在外部SD卡中时,音频包含一些skips/distortions的毫秒数(只是有时)。其他用户在内部 SD 卡上保存时也有此问题
这是我实例化 AudioRecord 对象的方式:
initBufferSize = assureMinBufferSize();
if (minBufferSize == AudioRecord.ERROR_BAD_VALUE) {
throw new Exception("" + ErrorCodes.ERROR_CODE_0);
}
else if (minBufferSize == AudioRecord.ERROR) {
throw new Exception("" + ErrorCodes.ERROR_CODE_0);
}
buffer = new byte[initBufferSize];
aRecorder = new AudioRecord(mSource, sRate, nChannel, AudioFormat.ENCODING_PCM_16BIT, initBufferSize);
这就是我合并 AudioRecorder 对象的方式:
private class AudioReaderRunnable implements Runnable {
private volatile boolean stopped;
@Override
public void run() {
while (!stopped) {
while(mState == State.RECORDING){
read();
}
}
Log.i(getClass().getName(), "AudioReade thread stopped!");
}
private void read(){
if(aRecorder == null)
return;
// "dirty" patch for some null crash for some users
if(buffer == null)
buffer = new byte[initBufferSize];
//int x = aRecorder.read(buffer, 0, buffer.length);
int x = readFully(buffer, 0, buffer.length);
if(x <= 0)
return;
payloadSize += x;
mWavData.arrayCopy(buffer);
mWavData.setGain(rGain);
mWavData.setBitsPerSamples(bitsPerSample);
mWavData.setNrChannels(nChannelsNumber);
// send audio to another thread for writing
mAudioWritter.add(audioData);
}
private int readFully(byte[] data, int off, int length) {
int read = 0;
int result = 0;
int requestedSize = length;
if(aRecorder == null){
return read;
}
try {
result = aRecorder.read(data, off, requestedSize);
} catch (Exception e) {
Log.e(getClass().getName(), e.getMessage(), e);
if(aRecorder == null)
return read;
}
read = result;
while (requestedSize != result && !stopped) {
if(aRecorder == null)
return read;
requestedSize -= result;
try {
result = aRecorder.read(data, result - 1, requestedSize);
} catch (Exception e) {
Log.e(getClass().getName(), e.getMessage(), e);
if(aRecorder == null)
return read;
}
read += result;
}
return read;
}
public void stopThread() {
stopped = true;
}
}
Try This Code:-
//This Variable are define in class
private int recordingcounter;
private static final int RECORDER_BPP = 16;
private static final String AUDIO_RECORDER_FILE_EXT_WAV = ".wav";
private static final String AUDIO_RECORDER_FOLDER = "AudioRecorder";
private static final String AUDIO_RECORDER_TEMP_FILE = "record_temp.raw";
private static final int RECORDER_SAMPLERATE = 16000;// /44100; //High voice
// recording result
// for use sample
// rate is 44100
private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_STEREO;
private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
private AudioRecord recorder = null;
private int bufferSize = 0;
private Thread recordingThread = null;
private boolean isRecording = false;
//--------------------- start button click event in add-----------------------------------//
bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE, RECORDER_CHANNELS,RECORDER_AUDIO_ENCODING);
try {
startRecording();
} catch (Exception e) {
// TODO: handle exception
// Toast.makeText(RecordingActivity.this,
// e.getMessage(), Toast.LENGTH_SHORT)
// .show();
}
//-------------------- stop button click event in add---------------------------//
try {
stopRecording();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
//---------this methods are add in your class-----------------------------//
// --------------------------------------------------------Sound Recording
// Code----------------------------------------------------//
private String getFilename() {
String filepath = Environment.getExternalStorageDirectory().getPath();
File file = new File(filepath, AUDIO_RECORDER_FOLDER);
if (!file.exists()) {
file.mkdirs();
}
return (file.getAbsolutePath() + "/" + "voiceFile" + AUDIO_RECORDER_FILE_EXT_WAV);
}
private String getTempFilename() {
String filepath = Environment.getExternalStorageDirectory().getPath();
File file = new File(filepath, AUDIO_RECORDER_FOLDER);
if (!file.exists()) {
file.mkdirs();
}
File tempFile = new File(filepath, AUDIO_RECORDER_TEMP_FILE);
if (tempFile.exists())
tempFile.delete();
return (file.getAbsolutePath() + "/" + AUDIO_RECORDER_TEMP_FILE);
}
@SuppressLint("NewApi")
private void startRecording() {
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
RECORDER_SAMPLERATE, RECORDER_CHANNELS,
RECORDER_AUDIO_ENCODING, bufferSize);
recorder.startRecording();
// Log.e("Audio session", ""+recorder.getAudioSessionId());
isRecording = true;
recordingThread = new Thread(new Runnable() {
@Override
public void run() {
writeAudioDataToFile();
}
}, "AudioRecorder Thread");
recordingThread.start();
}
private void writeAudioDataToFile() {
byte data[] = new byte[bufferSize];
String filename = getTempFilename();
FileOutputStream os = null;
try {
os = new FileOutputStream(filename);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int read = 0;
if (null != os) {
while (isRecording) {
read = recorder.read(data, 0, bufferSize);
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
try {
os.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void stopRecording() {
if (null != recorder) {
isRecording = false;
recorder.stop();
recorder.release();
// Utility.Log("", "" + recorder.getRecordingState());
// AppLog.logString(""+recorder.getRecordingState());
recorder = null;
recordingThread = null;
}
copyWaveFile(getTempFilename(), getFilename());
}
private void copyWaveFile(String inFilename, String outFilename) {
FileInputStream in = null;
FileOutputStream out = null;
long totalAudioLen = 0;
long totalDataLen = totalAudioLen + 36;
long longSampleRate = RECORDER_SAMPLERATE;
int channels = 2;
long byteRate = RECORDER_BPP * RECORDER_SAMPLERATE * channels / 8;
byte[] data = new byte[bufferSize];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
// Utility.Log("", "File size: " + totalDataLen);
// AppLog.logString("File size: " + totalDataLen);
WriteWaveFileHeader(out, totalAudioLen, totalDataLen,
longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, long longSampleRate, int channels, long byteRate)
throws IOException {
byte[] header = new byte[44];
header[0] = 'R'; // RIFF/WAVE header
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f'; // 'fmt ' chunk
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1; // format = 1
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
header[32] = (byte) (2 * 16 / 8); // block align
header[33] = 0;
header[34] = RECORDER_BPP; // bits per sample
header[35] = 0;
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
//------------和清单文件这一行------------//
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
我在 Xamarin.Android 上遇到了类似的问题,我是这样解决的:
AudioRecord 缓冲区就像一个 window - 当缓冲区被填满时,它会从头开始重写。所以我们需要确保缓冲区足够大,以便我们在它被重写之前从中读取。
大问题来了 - 当垃圾收集器启动时,它会停止所有应用程序线程大约一秒钟 - 这足以让记录器写入大约 100KB 的数据到缓冲区(当使用 44100Hz 单声道 Pcm16 位时),但我们在此期间不读取,因为应用程序线程已停止!因此,如果缓冲区不够大,数据会在我们有机会读取和写入文件之前被重写。因此,音频文件缺少垃圾收集期间录制的音频。
因此,您需要使用足够大的 bufferSize 来初始化 AudioRecord
,以便在垃圾回收事件之后继续存在。对于不同的音频配置,这将是不同的大小。对我来说(使用 44100Hz,单声道,PCM16 位),缓冲区大小 512,000 就可以了。
我不确定 GC 在本机上的工作方式 Android,但我相信它可能是相似的。