Android audio development (2): recording audio (WAV and MP3 format)

1. Android audio development (1): audio basics
2. Android audio development (2): recording audio (WAV and MP3 format)
3. Android audio development (3): using ExoPlayer to play audio
4. Android audio development (4) : Audio playback mode
5. Android audio development (5): Induction (screen off/bright screen) management

Attached GitHub source code: MultimediaExplore


First look at the audio recording and playback effect diagram:

 CSDN does not support local video upload, so I uploaded a screenshot first:

The above is recording : long press to record, support sound wave animation, right swipe to delete, etc. Support recording pcm, wav, mp3 format audio.

The following is playback : click the speaker icon on the left to start playing the local audio file just recorded [also supports online audio playback], supports playback progress, and supports switching playback modes (handset/speaker/headphone), etc.

1. Audio recording permission:

No matter before developing any functions, you must first add and apply for relevant permissions, so that the follow-up work can proceed normally. The permissions required for audio recording are as follows, and these sensitive permissions must be dynamically applied in the code, and can only be recorded normally after consent:

    <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" />

Second, the configuration of the recording file:

From the basic concept of audio mentioned in the first section, we can know that the relevant configuration of recording should be performed before recording audio, which directly determines the audio quality, file size, audio format, etc. of the recording file.

    /**
     * 录音音频的相关配置
     */
    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);
    }

3. Audio recording:

The audio recording class mainly has two encapsulation classes: AudioRecorder and AudioRecordManager.

AudioRecorder: Mainly use the system's AudioRecord for recording. And merge the recorded audio files, transcode, etc., to generate the audio files we need. This file is a global singleton, ensuring that there is only one instance of the audio recording class.

AudioRecordManager: The encapsulation and management of AudioRecorder, and the interaction with the outside world are all done through this class, including various life cycle control calls for recording. The direct interaction between the outside world and AudioRecorder has been reduced, and better management of the recording class has been achieved. This class is also a global singleton class.

1. Initialize the recording object:

Here, bufferSizeInBytes [buffer byte size] and audioRecord objects are generated mainly based on the previous recording configuration .

    /**
     * 创建默认的录音对象
     */
    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. Record wav audio files:

The wav audio file is lossless, so the sound quality will be close to the original, but it is precisely because it is lossless, so the wav audio file is almost uncompressed, and relatively speaking, it will be relatively large.

To record wav audio, you must first record and use to obtain pcm files, then merge the pcm files, and finally convert them into wav audio files.

(1) Start recording pcm files:

    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) Merge multiple generated pcm files:

    /**
     * 合并pcm文件
     */
    private void mergePcmFile() {
        boolean mergeSuccess = mergePcmFiles(resultFile, files);
        if (!mergeSuccess) {
            notifyError("合并失败");
        }
    }

 (3) Convert the merged pcm file into a wav file:

    /**
     * 添加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. Record MP3 audio files

Compared with WAV audio files, MP3 audio files are more common and are used more commercially, because MP3 audio is compressed, and the file size is only one-twelfth of WAV, but there is almost no better sound quality. Big difference. When there is no high requirement for sound quality, such as recording files, MP3 format is an excellent choice.

(1) Start recording audio buffer:

Here is to open a thread Mp3EncodeThread, the byte array byteBuffer generated by recording is continuously encoded and decoded to generate MP3 files.

    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 audio codec:

Android's native audio recording supports the direct generation of WAV files, but in fact it does not support the direct generation of MP3 files. This corresponds to the MP3 codec, which mainly uses the open source libmp3lame.so  audio codec library. The following are lame encoding and decoding methods and the Mp3Encoder class:

MP3 codec method:

    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 class:


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) Generate libmp3lame.so :

The source code provided by this project includes the jni source code of lame. If you want to generate the libmp3lame.so file for your own project, you need to modify the Mp3Encoder.c and Mp3Encoder.h files, and then generate the so file through ndk build.

The modified content is mainly to change the Mp3Encoder path in the file to the Mp3Encoder path in your own project , otherwise the relevant native method will not be found.

 In addition, in general, the cpu type supports armeabi-v7a and arm64-v8a . If you want to support others, you can add them in Application.mk.

 After the above file is modified, the corresponding  libmp3lame.so file can be compiled and generated through the ndk-build command .

Place the files of so different CPU types under jniLibs, or under the path configured by sourceSets, such as:

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

You can record in MP3 audio format.

4. Audio recording management [AudioRecordManager]:

The AudioRecorderManager in the global singleton mode interacts with the business, which not only ensures the singleness of the audio recording instance, but also better manages the audio lifecycle.

/**
 * 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();
    }
}

Guess you like

Origin blog.csdn.net/u012440207/article/details/121719075