Android Android 開発: AudioRecord を使用して録音し、録音を wav ファイルとして保存し、AudioTrack を使用して録音を保存します

1. AudioRrecord を使用して録音する

1.1 宣言

まず、AudioRecord クラスのインスタンスを宣言する必要があります。事前に宣言する理由は、この場合、録音の開始と終了が 2 つの異なる方法でカプセル化されるためです。一般的に、ほとんどの場合、「記録の開始」と「記録の終了」を 2 つの異なるアクションに分割する必要があります。

private AudioRecord audioRecord;

AudioRecord のインスタンスを宣言することに加えて、いくつかのパラメーターも準備する必要があります。

// 采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。
public static final int SAMPLE_RATE_INHZ = 44100;

// 声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。
public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;

// 返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;

これら 3 つのパラメータは、インスタンス構築のために AudioRecord インスタンスに渡されます。

「サンプリングレート」パラメータは次のとおりであることに注意してください。

パブリック静的最終整数SAMPLE_RATE_INHZ = 44100;

この例では、サンプリング レートは「44100」であり、その後の「wav 形式への変換」と「再生」の間、このサンプリング レートを一定に保つ必要があります。

Android が記録にハードウェア デバイスを使用する場合、Android が記録したデータを独立してファイルに読み書きするために、独立したスレッドを使用する必要があります。

さらに、この独立したスレッドに記録をいつ終了するかを指示するフラグも必要です。

/**
 * 录音的工作线程
 */
private Thread recordingAudioThread;
private boolean isRecording = false;//mark if is recording

すべての準備が整いました。

1.2 初期化インスタンス

録音のバッファ サイズを取得します。

int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);

ここでは、以前に準備した 3 つのパラメータを使用しました。

次に、AudioRecord インスタンスを作成します。また、次の 3 つのパラメータを使用する必要があります。

this.audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize);

new AudioRecord() が実行時権限を取得する必要がある場合、ここで注意すべき点が 1 つあります。

1.3 権限の確認

try-catch を使用して上記の初期化コードをラップし、catch 内で許可なく例外をキャッチし、許可を再申請します。

これを行う利点は、アプリケーションに権限がない場合でも、現在のインターフェイスがクラッシュしないことです。

try{
    // 获取最小录音缓存大小,
    int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
    this.audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize);

    //后续的其它代码也将被包裹在这个try中
    //...
}
catch(IllegalStateException e){
    //需要检查运行时权限
}
catch(SecurityException e){
    //需要检查运行时权限
}

実行時の権限を確認する方法については、ここを参照してください。

実行時の権限を動的に取得する

1.4 録音を開始する

実際には、記録を開始するコードは 1 行だけです。

audioRecord.startRecording();

ただし、その前に、記録フラグも変更する必要があります。

// 开始录音
this.isRecording = true;
audioRecord.startRecording();

1.5 録画データを読み込んでファイルとして保存する

まずファイル パスを指定する必要があります。

String audioCacheFilePath = this.getExternalFilesDir(Environment.DIRECTORY_MUSIC).getAbsolutePath() + "/" + "jerboa_audio_cache.pcm";

Android 録音プログラムは、録音されたサウンドを PCM 形式で保存します。これはオリジナルのサウンド データであり、プレーヤーでは認識できません。したがって、この例の後続の手順では、wav 形式に変換します。ここでのファイル パスは実際には一時的な通過パスなので、固定のファイル名を付けることができます。

次に、このパスを使用してメモリ内にファイルを作成できます。

File file = new File(audioCacheFilePath);
Log.i(TAG, "audio cache pcm file path:" + audioCacheFilePath);

このファイルに対して簡単なトランザクションの準備を行い、出力ストリームを準備します。

/*
 *  以防万一,看一下这个文件是不是存在,如果存在的话,先删除掉
 */
if (file.exists()) {
    file.delete();
}

try {
    file.createNewFile();
} catch (IOException e) {
    e.printStackTrace();
}

FileOutputStream fos = null;
try {
    fos = new FileOutputStream(file);
} catch (FileNotFoundException e) {
    e.printStackTrace();
    Log.e(TAG, "临时缓存文件未找到");
}

if (fos == null) {
    return;
}

次に、Android 記録からデータを読み取るためのバイト配列を準備します。バイト配列の長さは、以前に取得したキャッシュ サイズです。

byte[] data = new byte[minBufferSize];

次に、AudioRecord.read() メソッドを呼び出して録音データを取得します。

int read;
if (fos != null) {
    while (isRecording && !recordingAudioThread.isInterrupted()) {
        read = audioRecord.read(data, 0, minBufferSize);
        if (AudioRecord.ERROR_INVALID_OPERATION != read) {
            try {
                fos.write(data);
                Log.i("audioRecordTest", "写录音数据->" + read);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

公式ドキュメントで説明されているように、AudioRecord.read() メソッドは int 値を返します。

読み取られたゼロまたは正のバイト数、または次のエラー コードのいずれか。バイト数は sizeInBytes を超えません。オブジェクトが適切に初期化されていない場合は
ERROR_INVALID_OPERATION
パラメータが有効なデータおよびインデックスに解決されない場合は
ERROR_BAD_VALUE、オブジェクトがもう有効ではないため再作成する必要がある場合は ERROR_DEAD_OBJECT。一部のデータが正常に転送された場合、デッド オブジェクト エラー コードは返されません。この場合、次の read() でエラーが返されます。他のエラーの場合は
ERROR

簡単に言うと、データが正常に読み取られた場合、int 値は読み取られたデータの長さになります。エラーが発生した場合、この int 値がエラー コードになります。

読み取り後に出力ストリームを閉じることを忘れないでください。

try {
    // 关闭数据流
    fos.close();
} catch (IOException e) {
    e.printStackTrace();
}

1.5 スレッドの使用

実際には、録音は進行中のプロセスです。AudioRecord クラスを使用すると、録音中にデータをキャプチャできます。したがって、データを読み取る上記のすべての手順を別のスレッドで実行する必要があります。

// 创建数据流,将缓存导入数据流
this.recordingAudioThread = new Thread(new Runnable() {
    @Override
    public void run() {
        //我们应该在线程内部获取录音数据
        //...
    }
}

スレッドは確実に起動される必要があります。

this.recordingAudioThread.start();

1.6 完全なパッケージ

上記のすべての手順を完全なメソッドにカプセル化しました。

/**
 * 开始录音,返回临时缓存文件(.pcm)的文件路径
 */
protected String startRecordAudio() {
    String audioCacheFilePath = this.getExternalFilesDir(Environment.DIRECTORY_MUSIC).getAbsolutePath() + "/" + "jerboa_audio_cache.pcm";
    try{
        // 获取最小录音缓存大小,
        int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
        this.audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize);


        // 开始录音
        this.isRecording = true;
        audioRecord.startRecording();

        // 创建数据流,将缓存导入数据流
        this.recordingAudioThread = new Thread(new Runnable() {
            @Override
            public void run() {
                File file = new File(audioCacheFilePath);
                Log.i(TAG, "audio cache pcm file path:" + audioCacheFilePath);

                /*
                 *  以防万一,看一下这个文件是不是存在,如果存在的话,先删除掉
                 */
                if (file.exists()) {
                    file.delete();
                }

                try {
                    file.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                FileOutputStream fos = null;
                try {
                    fos = new FileOutputStream(file);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    Log.e(TAG, "临时缓存文件未找到");
                }
                if (fos == null) {
                    return;
                }

                byte[] data = new byte[minBufferSize];
                int read;
                if (fos != null) {
                    while (isRecording && !recordingAudioThread.isInterrupted()) {
                        read = audioRecord.read(data, 0, minBufferSize);
                        if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                            try {
                                fos.write(data);
                                Log.i("audioRecordTest", "写录音数据->" + read);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }

                try {
                    // 关闭数据流
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

        this.recordingAudioThread.start();
    }
    catch(IllegalStateException e){
        Log.w(TAG,"需要获取录音权限!");
        this.checkIfNeedRequestRunningPermission();
    }
    catch(SecurityException e){
        Log.w(TAG,"需要获取录音权限!");
        this.checkIfNeedRequestRunningPermission();
    }

    return audioCacheFilePath;
}

1.7 録音の終了

録音の開始と比較して、録音の終了ははるかに簡単です。録音フラグを変更し、AudioRecord インスタンスを解放し、スレッドを中断してキャンセルするだけです。

/**
 * 停止录音
 */
protected void stopRecordAudio(){
    try {
        this.isRecording = false;
        if (this.audioRecord != null) {
            this.audioRecord.stop();
            this.audioRecord.release();
            this.audioRecord = null;
            this.recordingAudioThread.interrupt();
            this.recordingAudioThread = null;
        }
    }
    catch (Exception e){
        Log.w(TAG,e.getLocalizedMessage());
    }
}

2. 録音ファイルを wav ファイルとして保存します

AudioRecordクラスを利用して得られる録音ファイルは実際にはpcmファイルですが、pcmファイルは形式を指定していない単なる生データであるため、一般的なプレーヤーでは再生できません。

これらの元のオーディオ データを wav ファイルに変換する必要があります。

前の手順では、指定したファイル パスを通じて pcm ファイルを取得しましたが、生成される wav ファイルの保存パスも指定する必要があります。

//wav文件的路径放在系统的音频目录下
String wavFilePath = this.getExternalFilesDir(Environment.DIRECTORY_PODCASTS) + "/wav_" + System.currentTimeMillis() + ".wav";

次に、パッケージ化されたツール クラスを直接呼び出します。

PcmToWavUtil ptwUtil = new PcmToWavUtil();
ptwUtil.pcmToWav("Your ppm file path",wavFilePath,true);

ツール クラス PcmToWavUtil.java に直接移動します。

import android.media.AudioFormat;
import android.media.AudioRecord;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Created by lzt
 * time 2021/6/9 15:42
 *
 * @author lizhengting
 * 描述:pcm格式的音频转换为wav格式的工具类
 */
public class PcmToWavUtil {
    private int mBufferSize; //缓存的音频大小
    private int mSampleRate = 44100;// 此处的值必须与录音时的采样率一致
    private int mChannel = AudioFormat.CHANNEL_IN_STEREO; //立体声
    private int mEncoding = AudioFormat.ENCODING_PCM_16BIT;

    private static class SingleHolder {
        static PcmToWavUtil mInstance = new PcmToWavUtil();
    }

    public static PcmToWavUtil getInstance() {
        return SingleHolder.mInstance;
    }


    public PcmToWavUtil() {
        this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, mEncoding);
    }

    /**
     * @param sampleRate sample rate、采样率
     * @param channel    channel、声道
     * @param encoding   Audio data format、音频格式
     */
    public PcmToWavUtil(int sampleRate, int channel, int encoding) {
        this.mSampleRate = sampleRate;
        this.mChannel = channel;
        this.mEncoding = encoding;
        this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, mEncoding);
    }

    /**
     * pcm文件转wav文件
     *
     * @param inFilename  源文件路径
     * @param outFilename 目标文件路径
     * @param deleteOrg   是否删除源文件
     */
    public void pcmToWav(String inFilename, String outFilename, boolean deleteOrg) {
        FileInputStream in;
        FileOutputStream out;
        long totalAudioLen;
        long totalDataLen;
        long longSampleRate = mSampleRate;
        int channels = 2;
        long byteRate = 16 * mSampleRate * channels / 8;
        byte[] data = new byte[mBufferSize];
        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;

            writeWaveFileHeader(out, totalAudioLen, totalDataLen,
                    longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }
            in.close();
            out.close();
            if (deleteOrg) {
                new File(inFilename).delete();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void pcmToWav(String inFilename, String outFilename) {
        pcmToWav(inFilename, outFilename, false);
    }

    /**
     * 加入wav文件头
     */
    private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
                                     long totalDataLen, long longSampleRate, int channels, long byteRate)
            throws IOException {
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF/WAVE header
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W'; //WAVE
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f'; // 'fmt ' chunk
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16; // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 1; // format = 1
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        header[32] = (byte) (2 * 16 / 8); // block align
        header[33] = 0;
        header[34] = 16; // bits per sample
        header[35] = 0;
        header[36] = 'd'; //data
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 0, 44);
    }
}

3. AudioTrack を使用して wav ファイルを再生する

再生も録音よりもはるかに簡単で、パッケージ化されたコードが直接表示されます。

/**
 * 播放一个wav文件
 */
protected void playWav(String filePath){
    File wavFile = new File(filePath);
    try{
        FileInputStream fis=new FileInputStream(wavFile);
        byte[] buffer=new byte[1024*1024*2];//2M
        int len=fis.read(buffer);
        Log.i(TAG, "fis len="+len);
        Log.i(TAG, "0:"+(char)buffer[0]);
        int pcmlen=0;
        pcmlen+=buffer[0x2b];
        pcmlen=pcmlen*256+buffer[0x2a];
        pcmlen=pcmlen*256+buffer[0x29];
        pcmlen=pcmlen*256+buffer[0x28];

        int channel=buffer[0x17];
        channel=channel*256+buffer[0x16];

        int bits=buffer[0x23];
        bits=bits*256+buffer[0x22];
        Log.i(TAG, "pcmlen="+pcmlen+",channel="+channel+",bits="+bits);
        AudioTrack at = new AudioTrack(AudioManager.STREAM_MUSIC,         
                    SAMPLE_RATE_INHZ*2,
                    channel,
                    AudioFormat.ENCODING_PCM_16BIT,
                    pcmlen,
                    AudioTrack.MODE_STATIC);
        at.write(buffer, 0x2C, pcmlen);
        
        at.play();
        
    }
    catch(Exception e){

    }
    finally{
        wavFile = null;
    }
}

4 番目に、MediaPlayer を使用してオーディオを再生します。

MediaPlayer は AudioTrack よりも単純で、再生が完了したことを確認するためのリスナーの受け渡しをサポートしています。

/**
 * 使用MediaPlayer播放文件,并且指定一个当播放完成后会触发的监听器
 * @param filePath
 * @param onCompletionListener
 */
protected void playWavWithMediaPlayer(String filePath, MediaPlayer.OnCompletionListener onCompletionListener){
    //File wavFile = new File(filePath);
    try {
        MediaPlayer mediaPlayer = new MediaPlayer();
        mediaPlayer.setDataSource(filePath);
        mediaPlayer.setOnCompletionListener(onCompletionListener);
        mediaPlayer.prepare();
        mediaPlayer.start();
    }
    catch(Exception e){

    }
}

おすすめ

転載: blog.csdn.net/freezingxu/article/details/124978433