阅读 472

Android音频开发之录制音频(WAV及MP3格式)

这篇文章主要为大家介绍了Android如何实现音频文件的录制(WAV及MP3格式),文中代码具有一定的参考价值,感兴趣的小伙伴们可以参考一下

目录
  • 一、音频录制权限:

  • 二、录音文件的配置:

  • 三、音频录制:

    • 1、录音对象初始化:

    • 2、录制wav音频文件:

    • 3、录制MP3音频文件

  • 四、音频录制管理【AudioRecordManager】:

    附GitHub源码:MultimediaExplore

    首先看下音频录制跟播放效果简图:

    上面是录音:长按即可录音,支持声波动画,右滑删除等。支持录制pcm、wav、mp3格式音频。

    下面是播放:点击左边扬声器icon,开始播放刚录制的本地音频文件【也支持在线音频播放】,支持播放进度,支持切换播放模式(听筒/扬声器/耳机)等。

    一、音频录制权限:

    无论在做开发任何功能之前,总得先添加及申请相关权限,后续的工作才能正常进行下去。音频录制所需权限如下,而且要在代码中动态申请这些敏感权限,同意后才能正常录制:

    1
    2
    3
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    二、录音文件的配置:

    通过第一节讲到音频的基础概念可知,在录制音频前应先进行录制的相关配置,它直接决定了录音文件的音频质量、文件大小、音频格式等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /**
     * 录音音频的相关配置
     */
    private void initConfig() {
        recordConfig = new RecordConfig();
        //采样位宽
        recordConfig.setEncodingConfig(AudioFormat.ENCODING_PCM_16BIT);
        //录音格式
        recordConfig.setFormat(RecordConfig.RecordFormat.MP3);
        // recordConfig.setFormat(RecordConfig.RecordFormat.WAV);
        //采样频率
        recordConfig.setSampleRate(16000);
        String recordDir = String.format(Locale.getDefault(), "%s/Record/zhongyao/",
                Environment.getExternalStorageDirectory().getAbsolutePath());
        //存储目录
        recordConfig.setRecordDir(recordDir);
        AudioRecordManager.getInstance().setCurrentConfig(recordConfig);
    }

    三、音频录制:

    音频录制类主要有两个封装类:分别是AudioRecorder 、AudioRecordManager。

    AudioRecorder:主要是使用系统的AudioRecord来进行录音。并把录制好的音频文件进行合并,转码等,生成我们所需的音频文件。该文件是全局单例的,保证音频录制类只有一个实例。

    AudioRecordManager:对AudioRecorder的封装管理,与外界交互均通过此类来完成,包括录音的各种生命周期控制调用等。减少了外界与AudioRecorder的直接交互,已达到对录音类的更好的管理,此类也是一个全局单例类。

    1、录音对象初始化:

    这里主要根据之前的录音配置,生成 bufferSizeInBytes【缓冲区字节大小】,和audioRecord对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
     * 创建默认的录音对象
     */
    public void prepareRecord() {
        // 获得缓冲区字节大小
        if (bufferSizeInBytes == 0) {
            bufferSizeInBytes = AudioRecord.getMinBufferSize(currentConfig.getSampleRate(),
                    currentConfig.getChannelConfig(), currentConfig.getEncodingConfig());
        }
        if (audioRecord == null) {
            audioRecord = new AudioRecord(AUDIO_INPUT, currentConfig.getSampleRate(),
                    currentConfig.getChannelConfig(), currentConfig.getEncodingConfig(), bufferSizeInBytes);
        }
     
        audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_PREPARE;
    }

    2、录制wav音频文件:

    wav音频文件是无损的,所以音质会接近原生,但也正是因为是无损的,所以wav音频文件几乎没有压缩,相对来说会比较大。

    录制wav音频得先进行录制采用,获得pcm文件,然后把pcm文件合并,最后再转成wav音频文件。

    (1)开始录制pcm文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    private void startPcmRecorder() {
        audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_START;
        notifyState();
        Logger.d(TAG, "开始录制 Pcm");
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(tmpFile);
            audioRecord.startRecording();
            byte[] byteBuffer = new byte[bufferSizeInBytes];
     
            while (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_START) {
                int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);
                notifyData(byteBuffer);
                fos.write(byteBuffer, 0, end);
                fos.flush();
            }
            audioRecord.stop();
            files.add(tmpFile);
            if (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_STOP) {
                makeFile();
            } else {
                Logger.d(TAG, "取消录制...");
            }
        } catch (Exception e) {
            Logger.e(e, TAG, e.getMessage());
            notifyError("录音失败");
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (audioRecordStatus != AudioRecordStatus.AUDIO_RECORD_PAUSE) {
            audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_IDLE;
            notifyState();
            Logger.d(TAG, "录音结束");
        }
    }

    (2)合并生成的多个pcm文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
     * 合并pcm文件
     */
    private void mergePcmFile() {
        boolean mergeSuccess = mergePcmFiles(resultFile, files);
        if (!mergeSuccess) {
            notifyError("合并失败");
        }
    }

    (3)将合并好的pcm文件转成wav文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
     * 添加Wav头文件
     */
    private void makeWav() {
        if (!FileUtil.isFile(resultFile) || resultFile.length() == 0) {
            return;
        }
        byte[] header = WavUtils.generateWavFileHeader((int) resultFile.length(), currentConfig.getSampleRate(), currentConfig.getChannelCount(), currentConfig.getEncoding());
        WavUtils.writeHeader(resultFile, header);
    }

    3、录制MP3音频文件

    相比WAV音频文件而言,MP3音频文件,就更加常见,商业上使用的也比较多,就是因为MP3音频时经过压缩的,文件大小只有WAV的十二分之一,但是音质上几乎没有较大的差异性。当对音质没有极高要求的情况下,如录音文件,MP3格式是极好的选择。

    (1)开始录制音频缓存:

    这里有开启一个线程Mp3EncodeThread,将录音产生的字节数组byteBuffer不断的进行编解码生成MP3文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    private void startMp3Recorder() {
        audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_START;
        notifyState();
     
        try {
            audioRecord.startRecording();
            short[] byteBuffer = new short[bufferSizeInBytes];
     
            while (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_START) {
                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 (audioRecordStatus != AudioRecordStatus.AUDIO_RECORD_PAUSE) {
            if (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_CANCEL) {
                deleteMp3Encoded();
            } else {
                stopMp3Encoded();
            }
        } else {
            Logger.d(TAG, "暂停");
        }
    }

    (2)MP3音频编解码:

    Android原生的音频录制,支持直接生成WAV文件,但其实是不支持直接生成MP3文件的。这里对应MP3编解码,主要用到了开源的 libmp3lame.so 这个音频编解码库。以下是lame编解码方法及Mp3Encoder类:

    MP3编解码方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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");
            }
        }
    }

    Mp3Encoder类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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);
        }
    }

    (3)生成libmp3lame.so:

    本项目提供的源码中有lame的jni源码,如果想生成libmp3lame.so文件,供自己的项目使用,那么需要对Mp3Encoder.c 和Mp3Encoder.h文件进行修改,然后通过ndk build 生成该so文件。

    修改的内容主要是把文件中的Mp3Encoder路径改成自己项目中的Mp3Encoder的路径,否则会找不到相关的native方法。

    另外一般情况下,cpu类型支持 armeabi-v7a 、arm64-v8a 两种即可,如果想支持其他的可在Application.mk中添加。

    上面文件修改完毕,通过ndk-build指令即可编译生成对应的 libmp3lame.so 文件。

    将so不同CPU类型的文件放置 jniLibs 下,或者通过sourceSets配置的路径下,如:

    1
    2
    3
    4
    sourceSets.main {
        jni.srcDirs = []//disable automatic ndk-build call
        jniLibs.srcDirs = ['libs']
    }

    即可进行MP3音频格式的录制。

    四、音频录制管理【AudioRecordManager】:

    通过全局单例模式的AudioRecorderManager与业务进行交互,即保证了音频录制实例的单一性,又能较好的对音频生命周期等进行较好的管理。 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    /**
     * Create by zhongyao on 2021/8/18
     * Description:音频录制管理类
     */
    public class AudioRecordManager {
      
        private static final String TAG = "AudioRecordManager";
      
        private AudioRecordManager() {
        }
      
        public static AudioRecordManager getInstance() {
            return AudioRecordManagerHolder.instance;
        }
      
        public static class AudioRecordManagerHolder {
            public static AudioRecordManager instance = new AudioRecordManager();
        }
      
        public void setCurrentConfig(RecordConfig recordConfig) {
            AudioRecorder.getInstance().setCurrentConfig(recordConfig);
        }
      
        public RecordConfig getCurrentConfig() {
            return AudioRecorder.getInstance().getCurrentConfig();
        }
      
        /**
         * 录音状态监听回调
         */
        public void setRecordStateListener(RecordStateListener listener) {
            AudioRecorder.getInstance().setRecordStateListener(listener);
        }
      
        /**
         * 录音数据监听回调
         */
        public void setRecordDataListener(RecordDataListener listener) {
            AudioRecorder.getInstance().setRecordDataListener(listener);
        }
      
        /**
         * 录音可视化数据回调,傅里叶转换后的频域数据
         */
        public void setRecordFftDataListener(RecordFftDataListener recordFftDataListener) {
            AudioRecorder.getInstance().setRecordFftDataListener(recordFftDataListener);
        }
      
        /**
         * 录音文件转换结束回调
         */
        public void setRecordResultListener(RecordResultListener listener) {
            AudioRecorder.getInstance().setRecordResultListener(listener);
        }
      
        /**
         * 录音音量监听回调
         */
        public void setRecordSoundSizeListener(RecordSoundSizeListener listener) {
            AudioRecorder.getInstance().setRecordSoundSizeListener(listener);
        }
      
        public void setStatus(AudioRecordStatus curAudioRecordStatus) {
            switch (curAudioRecordStatus) {
                case AUDIO_RECORD_IDLE:
      
                    break;
      
                case AUDIO_RECORD_PREPARE:
                    AudioRecorder.getInstance().prepareRecord();
                    break;
      
                case AUDIO_RECORD_START:
                    AudioRecorder.getInstance().startRecord();
                    break;
      
                case AUDIO_RECORD_PAUSE:
                    AudioRecorder.getInstance().pauseRecord();
                    break;
      
                case AUDIO_RECORD_STOP:
                    AudioRecorder.getInstance().stopRecord();
                    break;
      
                case AUDIO_RECORD_CANCEL:
                    AudioRecorder.getInstance().cancelRecord();
                    break;
      
                case AUDIO_RECORD_RELEASE:
                    AudioRecorder.getInstance().releaseRecord();
                    break;
      
                default:
                    break;
            }
        }
      
        public AudioRecordStatus getStatus() {
            return AudioRecorder.getInstance().getAudioRecordStatus();
        }
      
        public String getAudioPath() {
            return AudioRecorder.getInstance().getAudioPath();
        }
    }

    以上就是Android音频开发之录制音频(WAV及MP3格式)的详细内容

    原文链接:https://blog.csdn.net/u012440207/article/details/121719075


    文章分类
    代码人生
    版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
    相关推荐