为了账号安全,请及时绑定邮箱和手机立即绑定

Android音频开发(6):Mp3的录制 - 使用Lame实时录制MP3格式音频

标签:
Android

前言

上一篇介绍了如何去编译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, "暂停");
            }
        }
    }
}



作者:android_赵乐玮
链接:https://www.jianshu.com/p/2722533e5a8a


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消