Android オーディオ開発 (2): オーディオの録音 (WAV および MP3 形式)

1. Android オーディオ開発 (1): オーディオの基本
2. Android オーディオ開発 (2): オーディオの録音 (WAV および MP3 形式)
3. Android オーディオ開発 (3): ExoPlayer を使用したオーディオの再生
4. Android オーディオ開発 (4) :オーディオ再生モード
5.Androidオーディオ開発(5):誘導(画面オフ/明るい画面)管理

添付の GitHub ソース コード: MultimediaExplore


まず、オーディオの録音と再生の効果図を見てください。

 CSDN はローカル ビデオのアップロードをサポートしていないため、最初にスクリーンショットをアップロードしました。

上記は録音です:長押しで録音、音波アニメーションのサポート、右スワイプで削除など。pcm、wav、mp3 形式のオーディオの録音をサポートします。

再生は次のとおりです。左側のスピーカー アイコンをクリックして、録音したばかりのローカル オーディオ ファイルの再生を開始し [オンライン オーディオ再生にも対応]、再生の進行状況をサポートし、再生モードの切り替え (ハンドセット/スピーカー/ヘッドフォン) などをサポートします。

1. 録音許可:

機能を開発する前に、最初に関連する権限を追加して申請する必要があります。これにより、フォローアップ作業が正常に進行します。音声録音に必要なアクセス許可は次のとおりです。これらの機密性の高いアクセス許可は、コードに動的に適用する必要があり、同意後にのみ通常どおり録音できます。

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

次に、記録ファイルの構成:

最初のセクションで説明したオーディオの基本概念から、オーディオを録音する前に、録音に関連する構成を実行する必要があることがわかります。これにより、録音ファイルのオーディオ品質、ファイルサイズ、オーディオ形式などが直接決定されます。

    /**
     * 录音音频的相关配置
     */
    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. オーディオ録音:

オーディオ録音クラスには、主にAudioRecorderAudioRecordManager の 2 つのカプセル化クラスがあります。

AudioRecorder:主にシステムのAudioRecord を録音に使用します。そして、録音したオーディオ ファイルをマージしたり、トランスコードしたりして、必要なオーディオ ファイルを生成します。このファイルはグローバルなシングルトンであり、オーディオ録音クラスのインスタンスが 1 つだけであることを保証します。

AudioRecordManager: AudioRecorder のカプセル化と管理、および外部世界との対話は、録音のためのさまざまなライフサイクル制御呼び出しを含め、すべてこのクラスを通じて行われます。外部の世界と AudioRecorder の間の直接的なやり取りが減り、より優れたレコーディング クラスの管理が実現されました.このクラスもグローバル シングルトン クラスです.

1. 記録オブジェクトを初期化します。

ここで、 bufferSizeInBytes [バッファ バイト サイズ]audioRecordオブジェクトは、主に以前の録音設定に基づいて生成されます。

    /**
     * 创建默认的录音对象
     */
    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 ファイルの記録を開始します。

    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 ファイルをマージします。

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

 (3) マージされた pcm ファイルを wav ファイルに変換します。

    /**
     * 添加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 の 12 分の 1 にすぎませんが、音質はほとんど優れていません。大きな違いはありません。ファイルの録音など、音質に高い要件がない場合は、MP3 形式が最適です。

(1) オーディオ バッファの録音を開始します。

ここでは Mp3EncodeThread というスレッドを開き、録音によって生成されたバイト配列 byteBuffer を連続的にエンコード、デコードして MP3 ファイルを生成します。

    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 ファイルの直接生成をサポートしていません。これは、主にオープン ソースのlibmp3lame.so オーディオ コーデック ライブラリを使用する MP3 コーデックに対応します以下は不完全なエンコードとデコードのメソッド、および Mp3Encoder クラスです。

MP3 コーデック方式:

    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 クラス:


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 ファイルを変更してから、so ファイルを生成する必要があります。 ndkビルドを介して。

変更内容は主に、ファイル内の Mp3Encoder パスを自分のプロジェクト内の Mp3Encoder パスに変更することです。そうしないと、関連するネイティブ メソッドが見つかりません。

 また、CPUタイプはarmeabi-v7aとarm64-v8aが一般的ですが他にも対応したい場合はApplication.mkに追加してください。

 上記のファイルを変更した後、対応する libmp3lame.soファイルをndk-build コマンドでコンパイルおよび生成できます

さまざまな CPU タイプのファイルを、次のように jniLibs の下、または sourceSets によって構成されたパスの下に配置します。

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

MP3オーディオ形式で録音できます。

4. オーディオ録音管理 [AudioRecordManager]:

グローバル シングルトン モードの AudioRecorderManager はビジネスとやり取りします。これにより、オーディオ録音インスタンスの単一性が保証されるだけでなく、オーディオ ライフサイクルの管理も向上します。

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

おすすめ

転載: blog.csdn.net/u012440207/article/details/121719075
おすすめ