我正在尝试从原始 pcm 数据生成具有 ulaw 编码的 wav 文件。我已经通过在谷歌和这里找到了一些解决方案,但我似乎无法让音频很好地播放。它的嘈杂和筛选的声音。可能是音频播放太快,不确定。
所以,这是我迄今为止尝试过的代码。
我有我的 AudioRecorder.java,它记录了数据并将其保存为原始 pcm 文件,将原始 pcm 数据转换为 ulaw 编码数据,创建 wav 标头,然后将数据流合并并保存为一个 .wav 文件
public class AudioRecorder {
private static final String TAG = "AudioRecorder";
private int audioInput = MediaRecorder.AudioSource.MIC;
private int audioSampleRate = 8000; //frequency which ranges from 8K Hz to 44.1K Hz
private int audioChannel = AudioFormat.CHANNEL_IN_MONO;
private int audioEncode = AudioFormat.ENCODING_PCM_16BIT;
private int bufferSizeInBytes = 0;
private AudioRecord audioRecord;
private Status status = Status.STATUS_NO_READY;
protected String pcmFileName;
private int currentPosition = 0;
private int lastVolumn = 0;
private FileOutputStream fosPcm = null;
public AudioRecorder() {
pcmFileName = AudioFileUtils.getPcmFileAbsolutePath(RECORDED_FILE_NAME);
status = Status.STATUS_READY;
}
public void setAudioInput(int audioInput) {
this.audioInput = audioInput;
}
public void setAudioSampleRate(int audioSampleRate) {
this.audioSampleRate = audioSampleRate;
}
public void setAudioChannel(int audioChannel) {
this.audioChannel = audioChannel;
}
/**
* This method is to start recording using AudioRecord, also has NoiseSuppressor and AutomaticGainControl enabled
*/
public void startRecord() {
bufferSizeInBytes = AudioRecord.getMinBufferSize(audioSampleRate,
audioChannel, audioEncode);
audioRecord = new AudioRecord(audioInput, audioSampleRate, audioChannel, audioEncode, bufferSizeInBytes);
if (status == Status.STATUS_NO_READY) {
throw new IllegalStateException("not init");
}
if (status == Status.STATUS_START) {
throw new IllegalStateException("is recording ");
}
Log.d("AudioRecorder", "===startRecord===" + audioRecord.getState());
audioRecord.startRecording();
new Thread(new Runnable() {
@Override
public void run() {
NoiseSuppressor noiseSuppressor = NoiseSuppressor.create(audioRecord.getAudioSessionId());
if (noiseSuppressor != null) {
noiseSuppressor.setEnabled(true);
}
AutomaticGainControl automaticGainControl = AutomaticGainControl.create(audioRecord.getAudioSessionId());
if (automaticGainControl != null) {
automaticGainControl.setEnabled(true);
}
recordToFile();
}
}).start();
}
public void stop() {
if (status != Status.STATUS_START) {
throw new IllegalStateException("not recording");
} else {
stopRecorder();
// convertPCmFile();
makeDestFile();
status = Status.STATUS_READY;
}
}
private void convertPCmFile() {
File file = new File(AudioFileUtils.getPcmFileAbsolutePath(RECORDED_GREETING_FILE_NAME)); // for ex. path= "/sdcard/samplesound.pcm" or "/sdcard/samplesound.wav"
byte[] byteData = new byte[(int) file.length()];
try {
FileInputStream in = new FileInputStream(file);
in.read(byteData);
in.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// Set and push to audio track..
int intSize = android.media.AudioTrack.getMinBufferSize(audioSampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT);
AudioTrack at = new AudioTrack(AudioManager.STREAM_MUSIC, audioSampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT, intSize, AudioTrack.MODE_STREAM);
if (at != null) {
at.play();
// Write the byte array to the track
at.write(byteData, 0, byteData.length);
at.stop();
at.release();
} else
Log.d("TCAudio", "audio track is not initialised ");
}
private void makeDestFile() {
new Thread() {
@Override
public void run() {
File file = new File(pcmFileName);
try {
//Step 1: create input stream from generated pcm audio data
byte[] pcmData = new byte[(int) file.length()];
FileInputStream inputStream1 = new FileInputStream(file);
int readBytes = inputStream1.read(pcmData);
inputStream1.close();
//Step 2: calculate the size that has to be sent to constructor which is half the size of actual pcm audio data
int size = UlawEncoderInputStream.maxAbsPcm(pcmData, 0, pcmData.length / 2);
//Step 3: send the input stream as well as the size to the constructor
FileInputStream inputStream2 = new FileInputStream(file);
UlawEncoderInputStream ulawEncoderInputStream = new UlawEncoderInputStream(inputStream2, size);
//Step 4: create byte[] with size of half of bytes of pcm audio data
byte[] ulawData = new byte[pcmData.length / 2];
//Step 5: call read from UlawEncoderInputStream with above pcmData which is newly created
int nRead;
nRead = ulawEncoderInputStream.read(ulawData);
//Step 6: create wav header
byte[] wavHeader = wavFileHeader(ulawData.length, ulawData.length + 36, audioSampleRate, audioChannel, audioSampleRate);
//Step 7: combine wav header and encodedUlawBuffer in one byte[]
byte[] allByteArray = new byte[wavHeader.length + ulawData.length];
ByteBuffer buff = ByteBuffer.wrap(allByteArray);
buff.put(wavHeader);
buff.put(ulawData);
//Step 8 : writing the combined data into a new file
OutputStream outputStream = new FileOutputStream(new File(AudioFileUtils.getWavFileAbsolutePath(RECORDED_FILE_NAME)));
outputStream.write(allByteArray);
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
releaseRecorder();
}
}.run();
}
private byte[] wavFileHeader(long totalAudioLen, long totalDataLen, long longSampleRate,
int channels, long byteRate) {
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] = 7; // format = 7, for ulaw
header[21] = 0;
header[22] = (byte) (channels & 0xff);
header[23] = (byte) ((channels >> 8) & 0xFF);
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) ((channels * 8) / 8);//
// block align
header[33] = 0;
header[34] = 8; // 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);
return header;
}
public void release() {
stopRecorder();
releaseRecorder();
status = Status.STATUS_READY;
}
private void releaseRecorder() {
if (audioRecord != null) {
audioRecord.release();
audioRecord = null;
}
}
private void stopRecorder() {
if (audioRecord != null) {
try {
audioRecord.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* linear PCM data recorded to file
*/
private void recordToFile() {
byte[] audiodata = new byte[bufferSizeInBytes];
int readsize = 0;
try {
fosPcm = new FileOutputStream(pcmFileName, true);
} catch (FileNotFoundException e) {
Log.e("AudioRecorder", e.getMessage());
}
status = Status.STATUS_START;
while (status == Status.STATUS_START && audioRecord != null) {
readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
if (AudioRecord.ERROR_INVALID_OPERATION != readsize && fosPcm != null) {
try {
//get the volumn 1--10
int sum = 0;
for (int i = 0; i < readsize; i++) {
sum += Math.abs(audiodata[i]);
}
if (readsize > 0) {
int raw = sum / readsize;
lastVolumn = raw > 32 ? raw - 32 : 0;
Log.i(TAG, "writeDataTOFile: volumn -- " + raw + " / lastvolumn -- " + lastVolumn);
}
if (readsize > 0 && readsize <= audiodata.length)
fosPcm.write(audiodata, 0, readsize);
} catch (IOException e) {
Log.e("AudioRecorder", e.getMessage());
}
}
}
try {
if (fosPcm != null) {
fosPcm.close();
}
} catch (IOException e) {
Log.e("AudioRecorder", e.getMessage());
}
}
public Status getStatus() {
return status;
}
public enum Status {
STATUS_NO_READY,
STATUS_READY,
STATUS_START,
STATUS_PAUSE,
STATUS_STOP
}
}
我有 AudioFileUtils.java,它只提供要保存的扩展名的路径和附加
public class AudioFileUtils {
private static String rootPath = "audiorecord";
private final static String AUDIO_PCM_BASEPATH = "/" + rootPath + "/pcm/";
private final static String AUDIO_WAV_BASEPATH = "/" + rootPath + "/wav/";
private static void setRootPath(String rootPath) {
AudioFileUtils.rootPath = rootPath;
}
public static String getPcmFileAbsolutePath(String fileName) {
if (TextUtils.isEmpty(fileName)) {
throw new NullPointerException("fileName isEmpty");
}
String mAudioRawPath = "";
if (!fileName.endsWith(".pcm")) {
fileName = fileName + ".pcm";
}
String fileBasePath = SampleApp.getInstance().getApplicationContext().getExternalFilesDir(null) + AUDIO_PCM_BASEPATH;
File file = new File(fileBasePath);
if (!file.exists()) {
file.mkdirs();
}
mAudioRawPath = fileBasePath + fileName;
return mAudioRawPath;
}
public static String getWavFileAbsolutePath(String fileName) {
if (fileName == null) {
throw new NullPointerException("fileName can't be null");
}
String mAudioWavPath = "";
if (!fileName.endsWith(".wav")) {
fileName = fileName + ".wav";
}
String fileBasePath = SampleApp.getInstance().getApplicationContext().getExternalFilesDir(null) + AUDIO_WAV_BASEPATH;
CoxApplication.getInstance().getAccountManager().setMessageFilePath(fileBasePath);
File file = new File(fileBasePath);
if (!file.exists()) {
file.mkdirs();
}
mAudioWavPath = fileBasePath + fileName;
return mAudioWavPath;
}
}
我正在使用 UlawEncoderInputStream.java,我在这里找到了它并对其进行了一些更改,可以在下面找到
public class UlawEncoderInputStream extends InputStream {
private final static String TAG = "UlawEncoderInputStream";
private final static int MAX_ULAW = 8192;
private final static int SCALE_BITS = 16;
private InputStream mIn;
private int mMax = 0;
// this buffer needs to be LARGER than the largest possible file size for
// a 30 second PCM file recorded at either 8000 Hz or 44.1 KHz.
// If it is smaller, the file will be cut off.
private final byte[] mBuf = new byte[1048576];
private int mBufCount = 0; // should be 0 or 1
private final byte[] mOneByte = new byte[1];
public static void encode(byte[] pcmBuf, int pcmOffset,
byte[] ulawBuf, int ulawOffset, int length, int max) {
// from 'ulaw' in wikipedia
// +8191 to +8159 0x80
// +8158 to +4063 in 16 intervals of 256 0x80 + interval number
// +4062 to +2015 in 16 intervals of 128 0x90 + interval number
// +2014 to +991 in 16 intervals of 64 0xA0 + interval number
// +990 to +479 in 16 intervals of 32 0xB0 + interval number
// +478 to +223 in 16 intervals of 16 0xC0 + interval number
// +222 to +95 in 16 intervals of 8 0xD0 + interval number
// +94 to +31 in 16 intervals of 4 0xE0 + interval number
// +30 to +1 in 15 intervals of 2 0xF0 + interval number
// 0 0xFF
// -1 0x7F
// -31 to -2 in 15 intervals of 2 0x70 + interval number
// -95 to -32 in 16 intervals of 4 0x60 + interval number
// -223 to -96 in 16 intervals of 8 0x50 + interval number
// -479 to -224 in 16 intervals of 16 0x40 + interval number
// -991 to -480 in 16 intervals of 32 0x30 + interval number
// -2015 to -992 in 16 intervals of 64 0x20 + interval number
// -4063 to -2016 in 16 intervals of 128 0x10 + interval number
// -8159 to -4064 in 16 intervals of 256 0x00 + interval number
// -8192 to -8160 0x00
// set scale factors
if (max <= 0) max = MAX_ULAW;
int coef = MAX_ULAW * (1 << SCALE_BITS) / max;
for (int i = 0; i < length; i++) {
int pcm = (0xff & pcmBuf[pcmOffset++]) + (pcmBuf[pcmOffset++] << 8);
pcm = (pcm * coef) >> SCALE_BITS;
int ulaw;
if (pcm >= 0) {
ulaw = pcm <= 0 ? 0xff :
pcm <= 30 ? 0xf0 + (( 30 - pcm) >> 1) :
pcm <= 94 ? 0xe0 + (( 94 - pcm) >> 2) :
pcm <= 222 ? 0xd0 + (( 222 - pcm) >> 3) :
pcm <= 478 ? 0xc0 + (( 478 - pcm) >> 4) :
pcm <= 990 ? 0xb0 + (( 990 - pcm) >> 5) :
pcm <= 2014 ? 0xa0 + ((2014 - pcm) >> 6) :
pcm <= 4062 ? 0x90 + ((4062 - pcm) >> 7) :
pcm <= 8158 ? 0x80 + ((8158 - pcm) >> 8) :
0x80;
} else {
ulaw = -1 <= pcm ? 0x7f :
-31 <= pcm ? 0x70 + ((pcm - -31) >> 1) :
-95 <= pcm ? 0x60 + ((pcm - -95) >> 2) :
-223 <= pcm ? 0x50 + ((pcm - -223) >> 3) :
-479 <= pcm ? 0x40 + ((pcm - -479) >> 4) :
-991 <= pcm ? 0x30 + ((pcm - -991) >> 5) :
-2015 <= pcm ? 0x20 + ((pcm - -2015) >> 6) :
-4063 <= pcm ? 0x10 + ((pcm - -4063) >> 7) :
-8159 <= pcm ? 0x00 + ((pcm - -8159) >> 8) :
0x00;
}
ulawBuf[ulawOffset++] = (byte)ulaw;
}
}
/**
* Compute the maximum of the absolute value of the pcm samples.
* The return value can be used to set ulaw encoder scaling.
* @param pcmBuf array containing 16 bit pcm data.
* @param offset offset of start of 16 bit pcm data.
* @param length number of pcm samples (not number of input bytes)
* @return maximum abs of pcm data values
*/
public static int maxAbsPcm(byte[] pcmBuf, int offset, int length) {
int max = 0;
for (int i = 0; i < length; i++) {
int pcm = (0xff & pcmBuf[offset++]) + (pcmBuf[offset++] << 8);
if (pcm < 0) pcm = -pcm;
if (pcm > max) max = pcm;
}
return max;
}
/**
* Create an InputStream which takes 16 bit pcm data and produces ulaw data.
* @param in InputStream containing 16 bit pcm data.
* @param max pcm value corresponding to maximum ulaw value.
*/
public UlawEncoderInputStream(InputStream in, int max) {
mIn = in;
mMax = max;
}
@Override
public int read(byte[] buf, int offset, int length) throws IOException {
if (mIn == null) throw new IllegalStateException("not open");
// return at least one byte, but try to fill 'length'
while (mBufCount < 2) {
int n = mIn.read(mBuf, mBufCount, Math.min(length * 2, mBuf.length - mBufCount));
if (n == -1) return -1;
mBufCount += n;
}
// compand data
int n = Math.min(mBufCount / 2, length);
encode(mBuf, 0, buf, offset, n, mMax);
// move data to bottom of mBuf
mBufCount -= n * 2;
for (int i = 0; i < mBufCount; i++) mBuf[i] = mBuf[i + n * 2];
return n;
}
/*public byte[] getUpdatedBuffer(){
return mBuf;
}*/
@Override
public int read(byte[] buf) throws IOException {
return read(buf, 0, buf.length);
}
@Override
public int read() throws IOException {
int n = read(mOneByte, 0, 1);
if (n == -1) return -1;
return 0xff & (int)mOneByte[0];
}
@Override
public void close() throws IOException {
if (mIn != null) {
InputStream in = mIn;
mIn = null;
in.close();
}
}
@Override
public int available() throws IOException {
return (mIn.available() + mBufCount) / 2;
}
}
在做完这一切之后,正如我所说的那样,声音太刺耳和嘈杂。不知道我错在哪里。
任何帮助,不胜感激!先感谢您。