场景说明:
在现在有安卓手机中AudioRecord录制出的音频是通过扬声器或表克风录制出来的。对于外录即扬声器录制的视频而言,音质十分渣。对于想要录制现场的声音如开会时,两人对话时声音证据保存的声音录制,完全不能满足需求。鉴于此,研究安卓平台下高清音频录制的解决方案。
原理:本人实现原理很简单,分为两部分:1, 原始音频采集,2 音频编码 。
1,原始音频采集:
还是通过安卓的API AudioRecord 去采集声音,这个API录制声音不行,但在安卓上采集音频通常只能通过这个API,因为它直接封装了底层audio_device设备。
想要自己另外实现一套采集手段,不大实现且费力。我们可以通过设定一定的音频采样率,通道类型,码率,缓冲区大小来初始化AudioRecord,让它来为我们
采集原始音频,即 PCM 格式音频。
2 , 音频转码:
采集到PCM音频后,将PCM格式转码为MP3格式音频。这里我用的是LAME编码器,它是一款出色的音频编码器,摘自网上的一段介绍,LAME(mitiok.ma.cx)编码出来的MP3音色纯厚、空间宽广、低音清晰、细节表现良好,它独创的心理音响模型技术保证了CD音频还原的真实性。也不知是真是假。呵呵,反正本人用的还不错,保留了MP3音频的高清质量。
具体实现步骤:
1,音频采集:
这里使用设定的采样率,码率,缓冲区大小初始化AudioRecord
并且设制处理音频的间隔时间及回调监听。
/**
* Initialize audio recorder
*/
private void initAudioRecorder() throws IOException {
int bytesPerFrame = audioFormat.getBytesPerFrame();
/* Get number of samples. Calculate the buffer size (round up to the
factor of given frame size) */
int frameSize = AudioRecord.getMinBufferSize(samplingRate,
channelConfig, audioFormat.getAudioFormat()) / bytesPerFrame;
if (frameSize % FRAME_COUNT != 0) {
frameSize = frameSize + (FRAME_COUNT - frameSize % FRAME_COUNT);
Log.d(TAG, "Frame size: " + frameSize);
}
bufferSize = frameSize * bytesPerFrame;
/* Setup audio recorder */
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
samplingRate, channelConfig, audioFormat.getAudioFormat(),
bufferSize);
// Setup RingBuffer. Currently is 10 times size of hardware buffer
// Initialize buffer to hold data
ringBuffer = new RingBuffer(10 * bufferSize);
buffer = new byte[bufferSize];
// Initialize lame buffer
// mp3 sampling rate is the same as the recorded pcm sampling rate
// The bit rate is 32kbps
RecordLib.init(samplingRate, 1, samplingRate, BIT_RATE);
// Initialize the place to put mp3 file
// String externalPath = Environment.getExternalStorageDirectory()
// .getAbsolutePath();
// File directory = new File(externalPath + "/" + "AudioRecorder");
// if (!directory.exists()) {
// directory.mkdirs();
// Log.d(TAG, "Created directory");
// }
mp3File = new File(mFilePath);
os = new FileOutputStream(mp3File);
// Create and run thread used to encode data
// The thread will
encodeThread = new DataEncodeThread(ringBuffer, os, bufferSize);
encodeThread.start();
audioRecord.setRecordPositionUpdateListener(encodeThread, encodeThread.getHandler());
audioRecord.setPositionNotificationPeriod(FRAME_COUNT);
threads = new AcquireAudioPower();
}
2,音频转码
通过AudioRecord的回调函数来进行实时音频转码
@Override
public void onPeriodicNotification(AudioRecord recorder) {
processData();
}
/**
* Get data from ring buffer
* Encode it to mp3 frames using lame encoder
* @return Number of bytes read from ring buffer
* 0 in case there is no data left
*/
private int processData() {
int bytes = ringBuffer.read(buffer, bufferSize);
//Log.d(TAG, "Read size: " + bytes);
if (bytes > 0) {
short[] innerBuf = new short[bytes / 2];
ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(innerBuf);
int encodedSize = RecordLib.encode(innerBuf, innerBuf, bytes / 2, mp3Buffer);
if (encodedSize < 0) {
Log.e(TAG, "Lame encoded size: " + encodedSize);
}
try {
os.write(mp3Buffer, 0, encodedSize);
} catch (IOException e) {
Log.e(TAG, "Unable to write to file");
}
return bytes;
}
return 0;
}
3,lame编码技术实现:
JNIEXPORT void JNICALL Java_com_cunnar_lame_RecordLib_init(
JNIEnv *env, jclass cls, jint inSamplerate, jint outChannel,
jint outSamplerate, jint outBitrate, jint quality) {
if (glf != NULL) {
lame_close(glf);
glf = NULL;
}
glf = lame_init();
lame_set_in_samplerate(glf, inSamplerate);
lame_set_num_channels(glf, outChannel);
lame_set_out_samplerate(glf, outSamplerate);
lame_set_brate(glf, outBitrate);
lame_set_quality(glf, quality);
lame_init_params(glf);
}
JNIEXPORT jint JNICALL Java_com_cunnar_lame_RecordLib_encode(
JNIEnv *env, jclass cls, jshortArray buffer_l, jshortArray buffer_r,
jint samples, jbyteArray mp3buf) {
jshort* j_buffer_l = (*env)->GetShortArrayElements(env, buffer_l, NULL);
jshort* j_buffer_r = (*env)->GetShortArrayElements(env, buffer_r, NULL);
const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);
int result = lame_encode_buffer(glf, j_buffer_l, j_buffer_r,
samples, j_mp3buf, mp3buf_size);
(*env)->ReleaseShortArrayElements(env, buffer_l, j_buffer_l, 0);
(*env)->ReleaseShortArrayElements(env, buffer_r, j_buffer_r, 0);
(*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);
return result;
}
JNIEXPORT jint JNICALL Java_com_cunnar_lame_RecordLib_flush(
JNIEnv *env, jclass cls, jbyteArray mp3buf) {
const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);
int result = lame_encode_flush(glf, j_mp3buf, mp3buf_size);
(*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);
return result;
}
JNIEXPORT void JNICALL Java_com_cunnar_lame_RecordLib_close(
JNIEnv *env, jclass cls) {
lame_close(glf);
glf = NULL;
}
总结:将安卓采集到的原始音频做一次到MP3格式的编码,即可达到高清录制的效果。这里可以再进行声音变声的效果,有兴趣的可以自己研究一下。
一般都是通过控制采集率,码率来实现变声的。