Android音頻開發(6):Mp3的錄製 - 使用Lame實時錄製MP3格式音頻

Android 音頻開發 目錄

  1. Android音頻開發(1):音頻相關知識
  2. Android音頻開發(2):使用AudioRecord錄製pcm格式音頻
  3. Android音頻開發(3):使用AudioRecord實現錄音的暫停和恢復
  4. Android音頻開發(4):PCM轉WAV格式音頻
  5. Android音頻開發(5):Mp3的錄製 - 編譯Lame源碼
  6. Android音頻開發(6):Mp3的錄製 - 使用Lame實時錄製MP3格式音頻
  7. Android音頻開發(7):音樂可視化-FFT頻譜圖

項目地址

https://github.com/zhaolewei/ZlwAudioRecorder


前言

上一篇介紹瞭如何去編譯so文件,這一篇主要介紹下如何實時將pcm數據轉換爲MP3數據。

一、實現過程:

AudioRecorder在開啓錄音後,通過read方法不斷獲取pcm的採樣數據,每次獲取到數據後交給lame去處理,處理完成後存入文件中。
這一篇相對之前代碼,增加了兩個類:Mp3Encoder.java 和 Mp3EncoderThread.java

  • Mp3Encoder: 通過Jni調用so文件的c代碼,將pcm轉換成mp3格式數據
  • Mp3EncodeThread: 將pcm轉換成mp3時需要開啓子線程進行統一管理,以及全部轉碼完成的回調

代碼實現

  1. Mp3Encoder.java
public class Mp3Encoder {

    static {
        System.loadLibrary("mp3lame");
    }

    public native static void close();

    public native static int encode(short[] buffer_l, short[] buffer_r, int samples, byte[] mp3buf);

    public native static int flush(byte[] mp3buf);

    public native static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate, int quality);

    public static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate) {
        init(inSampleRate, outChannel, outSampleRate, outBitrate, 7);
    }
}
  1. Mp3EncodeThread.java
  • 每次有新的pcm數據後將數據打包成ChangeBuffer 類型,通過addChangeBuffer()存放到線程隊列當中,線程開啓後會不斷輪詢隊列內容,當有內容後開始轉碼,無內容時進入阻塞,直到數據全部處理完成後,關閉輪詢。
public class Mp3EncodeThread extends Thread {
    private static final String TAG = Mp3EncodeThread.class.getSimpleName();

    /**
     * mp3文件的碼率 32kbit/s = 4kb/s
     */
    private static final int OUT_BITRATE = 32;

    private List<ChangeBuffer> cacheBufferList = Collections.synchronizedList(new LinkedList<ChangeBuffer>());
    private File file;
    private FileOutputStream os;
    private byte[] mp3Buffer;
    private EncordFinishListener encordFinishListener;

    /**
     * 是否已停止錄音
     */
    private volatile boolean isOver = false;

    /**
     * 是否繼續輪詢數據隊列
     */
    private volatile boolean start = true;

    public Mp3EncodeThread(File file, int bufferSize) {
        this.file = file;
        mp3Buffer = new byte[(int) (7200 + (bufferSize * 2 * 1.25))];
        RecordConfig currentConfig = RecordService.getCurrentConfig();
        int sampleRate = currentConfig.getSampleRate();
        Mp3Encoder.init(sampleRate, currentConfig.getChannelCount(), sampleRate, OUT_BITRATE);
    }

    @Override
    public void run() {
        try {
            this.os = new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            Logger.e(e, TAG, e.getMessage());
            return;
        }

        while (start) {
            ChangeBuffer next = next();
            Logger.v(TAG, "處理數據:%s", next == null ? "null" : next.getReadSize());
            lameData(next);
        }
    }

    public void addChangeBuffer(ChangeBuffer changeBuffer) {
        if (changeBuffer != null) {
            cacheBufferList.add(changeBuffer);
            synchronized (this) {
                notify();
            }
        }
    }

    public void stopSafe(EncordFinishListener encordFinishListener) {
        this.encordFinishListener = encordFinishListener;
        isOver = true;
        synchronized (this) {
            notify();
        }
    }

    private ChangeBuffer next() {
        for (; ; ) {
            if (cacheBufferList == null || cacheBufferList.size() == 0) {
                try {
                    if (isOver) {
                        finish();
                    }
                    synchronized (this) {
                        wait();
                    }
                } catch (Exception e) {
                    Logger.e(e, TAG, e.getMessage());
                }
            } else {
                return cacheBufferList.remove(0);
            }
        }
    }

    private void lameData(ChangeBuffer changeBuffer) {
        if (changeBuffer == null) {
            return;
        }
        short[] buffer = changeBuffer.getData();
        int readSize = changeBuffer.getReadSize();
        if (readSize > 0) {
            int encodedSize = Mp3Encoder.encode(buffer, buffer, readSize, mp3Buffer);
            if (encodedSize < 0) {
                Logger.e(TAG, "Lame encoded size: " + encodedSize);
            }
            try {
                os.write(mp3Buffer, 0, encodedSize);
            } catch (IOException e) {
                Logger.e(e, TAG, "Unable to write to file");
            }
        }
    }

    private void finish() {
        start = false;
        final int flushResult = Mp3Encoder.flush(mp3Buffer);
        if (flushResult > 0) {
            try {
                os.write(mp3Buffer, 0, flushResult);
                os.close();
            } catch (final IOException e) {
                Logger.e(TAG, e.getMessage());
            }
        }
        Logger.d(TAG, "轉換結束 :%s", file.length());
        if (encordFinishListener != null) {
            encordFinishListener.onFinish();
        }
    }

    public static class ChangeBuffer {
        private short[] rawData;
        private int readSize;

        public ChangeBuffer(short[] rawData, int readSize) {
            this.rawData = rawData.clone();
            this.readSize = readSize;
        }

        short[] getData() {
            return rawData;
        }

        int getReadSize() {
            return readSize;
        }
    }

    public interface EncordFinishListener {
        /**
         * 格式轉換完畢
         */
        void onFinish();
    }
}

使用

 private class AudioRecordThread extends Thread {
        private AudioRecord audioRecord;
        private int bufferSize;

        AudioRecordThread() {
            bufferSize = AudioRecord.getMinBufferSize(currentConfig.getSampleRate(),
                    currentConfig.getChannelConfig(), currentConfig.getEncodingConfig()) * RECORD_AUDIO_BUFFER_TIMES;
            audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, currentConfig.getSampleRate(),
                    currentConfig.getChannelConfig(), currentConfig.getEncodingConfig(), bufferSize);
            if (currentConfig.getFormat() == RecordConfig.RecordFormat.MP3 && mp3EncodeThread == null) {
                initMp3EncoderThread(bufferSize);
            }
        }

        @Override
        public void run() {
            super.run();
            startMp3Recorder();
        }

         private void initMp3EncoderThread(int bufferSize) {
            try {
                mp3EncodeThread = new Mp3EncodeThread(resultFile, bufferSize);
                mp3EncodeThread.start();
            } catch (Exception e) {
                Logger.e(e, TAG, e.getMessage());
            }
         }
         
        private void startMp3Recorder() {
            state = RecordState.RECORDING;
            notifyState();

            try {
                audioRecord.startRecording();
                short[] byteBuffer = new short[bufferSize];

                while (state == RecordState.RECORDING) {
                    int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);
                    if (mp3EncodeThread != null) {
                        mp3EncodeThread.addChangeBuffer(new Mp3EncodeThread.ChangeBuffer(byteBuffer, end));
                    }
                    notifyData(ByteUtils.toBytes(byteBuffer));
                }
                audioRecord.stop();
            } catch (Exception e) {
                Logger.e(e, TAG, e.getMessage());
                notifyError("錄音失敗");
            }
            if (state != RecordState.PAUSE) {
                state = RecordState.IDLE;
                notifyState();
                if (mp3EncodeThread != null) {
                    mp3EncodeThread.stopSafe(new Mp3EncodeThread.EncordFinishListener() {
                        @Override
                        public void onFinish() {
                            notifyFinish();
                        }
                    });
                } else {
                    notifyFinish();
                }
            } else {
                Logger.d(TAG, "暫停");
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章