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 cacheBufferList = Collections.synchronizedList(new LinkedList());    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, "暂停");            }        }    }}

更多相关文章

  1. Android中数据存储的5种方法
  2. Android数据加密之Des加密
  3. Android 数据库之 SQLiteConnectionPool 源码分析
  4. Android 数据库技术
  5. 老罗Android开发视频教程( android解析json数据 )4集集合
  6. Android音频开发(5):Mp3的录制 - 编译Lame源码
  7. Android音频开发(2):使用AudioRecord录制pcm格式音频

随机推荐

  1. ubuntu 使用android studio 查看android(
  2. Android开发的小细节
  3. MTK 平台(MTK6573)马达驱动
  4. android解决坚屏拍照和保存图片旋转90度
  5. 自定义android preference组件
  6. arm-eabi-addr2line工具
  7. Android自定义对话框(Dialog)
  8. Android 实现蓝牙客户端与服务器端通信
  9. 解决EditText不显示光标的三种方法(总结)
  10. Android深入探究-- 实现即时拍照并上传