您的位置:首页 > 移动开发 > Android开发

android实现之高清音频录制编码

2015-12-16 10:51 525 查看
场景说明:

在现在有安卓手机中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格式的编码,即可达到高清录制的效果。这里可以再进行声音变声的效果,有兴趣的可以自己研究一下。

一般都是通过控制采集率,码率来实现变声的。

下面附上本人的实现源码。下载源码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: