场景说明:

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

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