preface

In Android audio and video development, online knowledge is too fragmented, self-learning is very difficult, but audio and video cattle Jhuster put forward “Android audio and video from the entry to improve – task list”. This article is the second part of the Android audio and video task list. The corresponding content to learn is: Using AudioRecord and AudioTrack on the Android platform to complete the acquisition and playback of audio PCM data, and realize the reading and writing of audio WAV files


Audio and video task list

Audio and video task list: Click here to jump to view


A, directories,


(1) AudioRecord Complete the collection of audio PCM data

1. Preparation

1.1 AudioRecord API details

An AudioRecord is a function class provided by the Android system for recording, which can retrieve raw FRAMES of PCM audio data. The AndioRecord class’s main function is to allow various JAVA applications to manage audio resources that they collect through such sound-recording hardware. Pulling is done by reading the sound data from the AudioRecord object. During recording, all the application needs to do is get the recording data from the AudioRecord object in time through one of the following three class methods. The AudioRecord class provides three methods for retrieving sound data: read(byte[], int, int), read(short[], int, int), read(ByteBuffer, int). Whichever method you choose to use must be set up in advance to store the sound data in a user-friendly format. To start a recording, the AudioRecord initializes an associated sound buffer, which is used to store new sound data. The size of this buffer can be specified during object construction. It indicates how long an AudioRecord object can record (that is, the volume of sound that can be recorded at one time) before the sound data has been read (synchronized). The audio data is read from the audio hardware. The data size does not exceed the size of the entire recording data (it can be read multiple times), that is, the initial buffer capacity is read at a time.

1.2 Android recording process

  1. Construct an AudioRecord object in which the minimum size of the recording buffer required can be obtained by using the getMinBufferSize method. If the buffer size is too small, object construction will fail.
  2. Initialize a buffer that is greater than or equal to the size of the AudioRecord object’s buffer for writing sound data.
  3. Start the recording: audioRecord startRecording ();
  4. Create a data stream that reads the audio data from the AudioRecord into the initialized buffer and imports the buffer data into the data stream.
  5. Shutting down the data stream
  6. Stop the recording

1.3 Setting AudioRecord Parameters

Construct an AudioRecord object as follows:

audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, EncodingBitRate, recordBufSize);
Copy the code

Inside each parameter configuration is as follows: (1) the audioSource: collection of audio input source optional value in the form of a constant defined in MediaRecorder. AudioSource class, common values include:

  1. DEFAULT (DEFAULT)
  2. VOICE_RECOGNITION (for speech recognition, equivalent to DEFAULT)
  3. MIC (input from mobile phone microphone)
  4. VOICE_COMMUNICATION (for VoIP applications), etc

(2) sampleRateInHz: sampling rate currently 44100Hz is the only sampling rate that can guarantee compatibility with all Android phones.

The optional value is defined as a constant in the AudioFormat class, which is commonly used

  1. CHANNEL_IN_MONO (single channel)
  2. CHANNEL_IN_STEREO (double channel)

(4) audioFormat: Optional data bit width values are defined in the form of constants in the audioFormat class, commonly used is (1 is guaranteed to be compatible with all Android phones)

  1. ENCODING_PCM_16BIT (16 bit)
  2. ENCODING_PCM_8BIT (8 bit)

BufferSizeInBytes: size of the audio buffer inside the AudioRecord. The value of the buffer cannot be smaller than the size of a Frame: int size = Sampling rate x bit width x sampling time x number of channels


2. Use AudioRecord to record recordings

2.1 Creating an AudioRecord object

First we declare some global variable arguments:

private AudioRecord audioRecord = null;  // Declare an AudioRecord object
private int recordBufSize = 0; // Declare recoordBufffer's size field
Copy the code

Get the size of buffer and create AudioRecord:

public void createAudioRecord(a) {recordBufSize = AudioRecord. GetMinBufferSize (frequency, channelConfiguration, EncodingBitRate);// The smallest buffer size an audioRecord can accept
   audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, EncodingBitRate, recordBufSize);
}
Copy the code

2.2 Initializing a Buffer

byte data[] = new byte[recordBufSize];
Copy the code

2.3 Starting Recording

audioRecord.startRecording();
isRecording = true;
Copy the code

2.4 Create a data stream, read audio data from the AudioRecord into the initialized buffer, and import the buffer data into the data stream

FileOutputStream os = null;
try {
    os = new FileOutputStream(filename);
} catch (FileNotFoundException e) {    
    e.printStackTrace();
}

if (null! = os) {while (isRecording) {
        read = audioRecord.read(data, 0, recordBufSize);// If the audio data is read without error, the data is written to the file
        if(AudioRecord.ERROR_INVALID_OPERATION ! = read) {try {
                os.write(data);
            } catch(IOException e) { e.printStackTrace(); }}}try {
        os.close();
    } catch(IOException e) { e.printStackTrace(); }}Copy the code

2.5 Shutting down the Data stream

If isRecording is false, the while loop stops, the Stream stops, and the Stream is closed

isRecording = false;
Copy the code

2.6 Stopping Recording

After you stop recording, be careful to release resources

if (null != audioRecord) {
  audioRecord.stop();
   audioRecord.release();
  audioRecord = null;
   recordingThread = null;
}
Copy the code

Note: Permission requirements are WRITE_EXTERNAL_STORAGE and RECORD_AUDIO


3 summary

The Android SDK provides two sets of apis for audio collection: MediaRecorder and AudioRecord, the former is a bit more upper API, it can directly the phone microphone input audio data encoding compression (such as AMR, MP3, etc.) and coexist into a file, while the latter is closer to the bottom, can be more flexible control, You can get the raw FRAME OF PCM audio data. If you want to simply do a recorder, recording audio files, it is recommended to use MediaRecorder, and if you need to do further algorithm processing of audio, or the use of third-party coding library compression, as well as network transmission applications, it is recommended to use AudioRecord, In fact, MediaRecorder base is also called AudioRecord and Android Framework layer AudioFlinger interaction. Live in the real-time acquisition of audio is naturally to use AudioRecord.


(II) AudioTrack API plays audio PCM data

1. Basic use of AudioTrack

1.1 Constructing an AudioTrack instance

To construct an AudioTrack instance, the following two methods are briefly introduced:

The first construction method:

public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode);
Copy the code

StreamType: type of an audio stream sampleRateInHz: sampling rate channelConfig: channel audioFormat: format bufferSizeInBytes: minimum required recording cache mode: data loading mode

Example:

AudioTrack mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
        sample,
        channel,
        bits,
        minBufSize,
        AudioTrack.MODE_STREAM);
Copy the code

The second construction method:

public AudioTrack(AudioAttributes attributes,
                  AudioFormat format,
                  int bufferSizeInBytes,
                  int mode,
                  int sessionId);
Copy the code

Example:

audioTrack = new AudioTrack(
        new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build(),
        new AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ)
                .setEncoding(AUDIO_FORMAT)
                .setChannelMask(channelConfig)
                .build(),
        minBufferSize,
        AudioTrack.MODE_STREAM,
        AudioManager.AUDIO_SESSION_ID_GENERATE);
Copy the code

1.2 Constructing AudioTrack data loading mode

AudioTrack class can complete the audio data output tasks on the Android platform. AudioTrack has two data loading modes (MODE_STREAM and MODE_STATIC), corresponding to the data loading mode and the audio stream type, corresponding to two completely different usage scenarios. MODE_STREAM: In this mode, audio data is written to AudioTrack again and again by write. This is similar to writing data to a file using a write system call, but this works by copying the data from the user-provided Buffer to the Buffer inside AudioTrack each time, which causes a certain amount of latency. To solve this problem, AudioTrack introduced a second mode. MODE_STATIC: In this mode, all data needs to be passed to the internal buffer in the AudioTrack through a single write call before play, and no further data needs to be passed. This mode is suitable for files with small memory footprint and high latency requirements, such as ringtones. One drawback is that you can’t write too much data at a time, or the system won’t be able to allocate enough memory to store all the data. If STATIC mode is used, call Write first to write data, and then call Play.

Note: If STATIC mode is used, you must first call write to write data, and then call play.

/** * static mode * If static mode is used, call write first, then call play */
private void playInModeStatic(a) {
    // In static mode, the audio data needs to be written to the internal buffer of the AudioTrack once

    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground(Void... params) {
            try {
                // Read PCM data
                // File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");

                // Read waV data
                File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.wav");
                InputStream in = new FileInputStream(file);

                try {
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    for (intb; (b = in.read()) ! = -1;) { out.write(b); } audioData = out.toByteArray(); }finally{ in.close(); }}catch (IOException e) {
              
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void v) {
            audioTrack = new AudioTrack(
                    new AudioAttributes.Builder()
                            .setUsage(AudioAttributes.USAGE_MEDIA)
                            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                            .build(),
                    new AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ)
                            .setEncoding(AUDIO_FORMAT)
                            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                            .build(),
                    audioData.length,
                    AudioTrack.MODE_STATIC,
                    AudioManager.AUDIO_SESSION_ID_GENERATE);
          
            audioTrack.write(audioData, 0, audioData.length);

            audioTrack.play();
        }
    }.execute();
    
}
Copy the code

MODE_STREAM mode outputs audio as follows:

/** * play, use stream mode */
private void playInModeStream(a) {
    /* * SAMPLE_RATE_INHZ Indicates the sampling rate of PCM audio * channelConfig indicates the channel of PCM audio * * AUDIO_FORMAT Indicates the format of PCM audio * */

    int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
    final int minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_INHZ, channelConfig, AUDIO_FORMAT);

    audioTrack = new AudioTrack(
            new AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_MEDIA)
                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                    .build(),
            new AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ)
                    .setEncoding(AUDIO_FORMAT)
                    .setChannelMask(channelConfig)
                    .build(),
            minBufferSize,
            AudioTrack.MODE_STREAM,
            AudioManager.AUDIO_SESSION_ID_GENERATE);

    audioTrack.play();

    File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");
    try {
        fileInputStream = new FileInputStream(file);
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                try {
                    byte[] tempBuffer = new byte[minBufferSize];
                    while (fileInputStream.available() > 0) {
                        int readCount = fileInputStream.read(tempBuffer);
                        if (readCount == AudioTrack.ERROR_INVALID_OPERATION ||
                                readCount == AudioTrack.ERROR_BAD_VALUE) {
                            continue;
                        }
                        if(readCount ! =0&& readCount ! = -1) {
                            audioTrack.write(tempBuffer, 0, readCount); }}}catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    } catch(IOException e) { e.printStackTrace(); }}Copy the code

1.3 AudioTrack Type of audio streams

In the AudioTrack constructor, the audiomanager.stream_music parameter is touched. Its meaning has to do with how the Android system manages and classifies audio streams. Android classifies system sounds into several stream types, including STREAM_ALARM, STREAM_MUSIC, etc. STREAM_RING, STREAM_SYSTEM: Note: These types of categorizations have nothing to do with the audio data itself. For example, the MUSIC and RING types can both be an MP3 song. In addition, there is no set standard for the selection of sound stream type. For example, the ringtone in ringtone preview can be set to MUSIC type. The classification of Audio stream types is related to the Audio management strategy of the Audio system.


(iii) PCM data collected by AudioRecord is converted into WAV files

(1) According to the PCM data process collected by AudioRecord, the audio data is output to the file. After the recording is stopped, the file can not be played when the player opens it. What is the reason? Answer: according to the process covered, data is entered, but now the inside of the file content is only the original audio data, termed raw (Chinese explanation is that the “raw material” or “raw”), at this time, you let the player to open, it does not know what is the format of the saved, and don’t know how to decode operations. Of course not.

(2) How can I play my recorded content in the player? A: Add the WAVE HEAD data at the beginning of the file, which is the file header. Only by adding the data in the header of the file, can the player know exactly what the content is inside, and then can parse and play the content normally. Play a WAV File on an AudioTrack. Add the following code to the WAVE header:

public class PcmToWavUtil {
    /** * The audio size of the cache */
    private int mBufferSize;
    /** * sample rate */
    private int mSampleRate;
    /** * Number of channels */
    private int mChannel;

    / * * *@paramSampleRate sample rate, sample rate *@paramChannel *@paramEncoding Audio data format */
    PcmToWavUtil(int sampleRate, int channel, int encoding) {
        this.mSampleRate = sampleRate;
        this.mChannel = channel;
        this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
    }

    /** * PCM file to WAV file **@paramInFilename Source file path *@paramOutFilename Target file path */
    public void pcmToWav(String inFilename, String outFilename) {
        FileInputStream in;
        FileOutputStream out;
        long totalAudioLen;
        long totalDataLen;

        long longSampleRate = mSampleRate;
        int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 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;
            // Add header data to the target file
            writeWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
            // Add source file data to target file
            while(in.read(data) ! = -1) {
                out.write(data);
            }
            in.close();
            out.close();
        } catch(IOException e) { e.printStackTrace(); }}/**
     * 加入wav文件头
     */
    private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
                                     long totalDataLen, long longSampleRate, int channels, long byteRate)
        throws IOException {
        byte[] header = new byte[44];
        // RIFF/WAVE header
        header[0] = 'R';
        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);
        //WAVE
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        // 'fmt ' chunk
        header[12] = 'f';
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        // 4 bytes: size of 'fmt ' chunk
        header[16] = 16;
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        // format = 1
        header[20] = 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);
        // block align
        header[32] = (byte) (2 * 16 / 8);
        header[33] = 0;
        // bits per sample
        header[34] = 16;
        header[35] = 0;
        //data
        header[36] = 'd';
        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); }}Copy the code

(4) Read WAV files

AudioTrack plays audio WAV data

/** * static mode * If static mode is used, call write first, then call play */
private void playInModeStatic(a) {
    // In static mode, the audio data needs to be written to the internal buffer of the AudioTrack once
    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground(Void... params) {
            try {
                // Read waV data
                File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.wav");
                InputStream in = new FileInputStream(file);

                try {
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    for (intb; (b = in.read()) ! = -1;) { out.write(b); } audioData = out.toByteArray(); }finally{ in.close(); }}catch (IOException e) {
               
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void v) {
          
            audioTrack = new AudioTrack(
                    new AudioAttributes.Builder()
                            .setUsage(AudioAttributes.USAGE_MEDIA)
                            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                            .build(),
                    new AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ)
                            .setEncoding(AUDIO_FORMAT)
                            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                            .build(),
                    audioData.length,
                    AudioTrack.MODE_STATIC,
                    AudioManager.AUDIO_SESSION_ID_GENERATE);
         
            audioTrack.write(audioData, 0, audioData.length);
           
            audioTrack.play();
        }
    }.execute();

}
Copy the code

(V) Complete Code:

(1) Layout

<? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

    <Button
        android:id="@+id/btn_control"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="@string/start_record"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv" />

    <Button
        android:id="@+id/btn_play"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="@string/start_play"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_convert" />

    <Button
        android:id="@+id/btn_convert"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="Turn PCM wav"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_control" />

    <Button
        android:id="@+id/btn_play_wav"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="@string/start_play_wav"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_play" />

</LinearLayout>
Copy the code

strings.xml

<resources>
    <string name="app_name">audio demo</string>
    <string name="start_record"> Start recording </string> <string name="stop_record"> Stop recording </string> <string name="start_play"> Play PCM </string> <string name="start_play_wav"> Play WAV</string> <string name="stop_play"> Stop </string> </resources>Copy the code

Join the permissions

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
Copy the code

(2) the MainActivity

package com.lzacking.audiodemo;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import static com.lzacking.audiodemo.GlobalConfig.AUDIO_FORMAT;
import static com.lzacking.audiodemo.GlobalConfig.CHANNEL_CONFIG;
import static com.lzacking.audiodemo.GlobalConfig.SAMPLE_RATE_INHZ;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final int MY_PERMISSIONS_REQUEST = 1001;
    private static final String TAG = "MainActivity";

    private Button mBtnControl;
    private Button mBtnPlay;
    private Button mBtnPlayWav;

    /** * The runtime permission to apply for */
    private String[] permissions = new String[] {
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    /** * List of denied permissions */
    private List<String> mPermissionList = new ArrayList<>();
    private boolean isRecording;
    private AudioRecord audioRecord;
    private Button mBtnConvert;
    private AudioTrack audioTrack;
    private byte[] audioData;
    private FileInputStream fileInputStream;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // Record the PCM file
        mBtnControl = (Button) findViewById(R.id.btn_control);
        mBtnControl.setOnClickListener(this);

        / / play
        mBtnPlay = (Button) findViewById(R.id.btn_play);
        mBtnPlay.setOnClickListener(this);

        // Convert the PCM file to waV file
        mBtnConvert = (Button) findViewById(R.id.btn_convert);
        mBtnConvert.setOnClickListener(this);

        // Play the WAV file
        mBtnPlayWav = (Button) findViewById(R.id.btn_play_wav);
        mBtnPlayWav.setOnClickListener(this);

        checkPermissions();
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_control:
                Button button = (Button) view;
                if (button.getText().toString().equals(getString(R.string.start_record))) {
                    button.setText(getString(R.string.stop_record));
                    startRecord();
                } else {
                    button.setText(getString(R.string.start_record));
                    stopRecord();
                }
                break;

            case R.id.btn_play:
                Button btn = (Button) view;
                String string = btn.getText().toString();
                Log.i(TAG, "onClick: " + string);
                if (string.equals(getString(R.string.start_play))) {
                    btn.setText(getString(R.string.stop_play));
                    / / play PCM
                    playInModeStream();
                } else {
                    btn.setText(getString(R.string.start_play));
                    stopPlay();
                }
                break;

            case R.id.btn_convert:
                PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
                File pcmFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");
                File wavFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.wav");
                if(! wavFile.mkdirs()) { Log.e(TAG,"wavFile Directory not created");
                }

                if (wavFile.exists()) {
                    wavFile.delete();
                }
                pcmToWavUtil.pcmToWav(pcmFile.getAbsolutePath(), wavFile.getAbsolutePath());
                break;

            case R.id.btn_play_wav:
                Button btnWav = (Button) view;
                String stringWav = btnWav.getText().toString();
                Log.i(TAG, "onClick: " + stringWav);
                if (stringWav.equals(getString(R.string.start_play_wav))) {
                    btnWav.setText(getString(R.string.stop_play));
                    // Play the WAV file
                    playInModeStatic();
                } else {
                    btnWav.setText(getString(R.string.start_play_wav));
                    stopPlay();
                }
                break;

            default:
                break; }}@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == MY_PERMISSIONS_REQUEST) {
            for (int i = 0; i < grantResults.length; i++) {
                if(grantResults[i] ! = PackageManager.PERMISSION_GRANTED) { Log.i(TAG, permissions[i] +"Permission prohibited by user!"); }}// Permission application at runtime is not the focus of this demo, so no more processing will be done, please agree with permission application.}}/** * start recording */
    public void startRecord(a) {
        // Get the size of buffer
        final int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
        / / create the AudioRecord
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
                SAMPLE_RATE_INHZ,
                CHANNEL_CONFIG,
                AUDIO_FORMAT,
                minBufferSize);
        // Initialize a buffer
        final byte data[] = new byte[minBufferSize];

        final File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");
        if(! file.mkdirs()) { Log.e(TAG,"Directory not created");
        }

        if (file.exists()) {
            file.delete();
        }

        // Start recording
        audioRecord.startRecording();
        isRecording = true;

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                FileOutputStream os = null;
                try {
                    os = new FileOutputStream(file);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }

                if (null! = os) {while (isRecording) {
                        int read = audioRecord.read(data, 0, minBufferSize);
                        // If the audio data is read without error, the data is written to the file
                        if(AudioRecord.ERROR_INVALID_OPERATION ! = read) {try {
                                os.write(data);
                            } catch(IOException e) { e.printStackTrace(); }}}try {
                        Log.i(TAG, "run: close file output stream !");
                        os.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

    }


    /**
     * 停止录音
     */
    public void stopRecord(a) {
        isRecording = false;
        // Release resources
        if (null! = audioRecord) { audioRecord.stop(); audioRecord.release(); audioRecord =null; }}private void checkPermissions(a) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            for (int i = 0; i < permissions.length; i++) {
                if (ContextCompat.checkSelfPermission(this, permissions[i]) !=
                        PackageManager.PERMISSION_GRANTED) {
                    mPermissionList.add(permissions[i]);
                }
            }
            if(! mPermissionList.isEmpty()) { String[] permissions = mPermissionList.toArray(new String[mPermissionList.size()]);
                ActivityCompat.requestPermissions(this, permissions, MY_PERMISSIONS_REQUEST); }}}/** * play, use stream mode */
    private void playInModeStream(a) {
        /* * SAMPLE_RATE_INHZ Indicates the sampling rate of PCM audio * channelConfig indicates the channel of PCM audio * * AUDIO_FORMAT Indicates the format of PCM audio * */
        int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
        final int minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_INHZ, channelConfig, AUDIO_FORMAT);

        audioTrack = new AudioTrack(
                new AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                        .build(),
                new AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ)
                        .setEncoding(AUDIO_FORMAT)
                        .setChannelMask(channelConfig)
                        .build(),
                minBufferSize,
                AudioTrack.MODE_STREAM,
                AudioManager.AUDIO_SESSION_ID_GENERATE);

        audioTrack.play();

        File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");
        try {
            fileInputStream = new FileInputStream(file);
            new Thread(new Runnable() {
                @Override
                public void run(a) {
                    try {
                        byte[] tempBuffer = new byte[minBufferSize];
                        while (fileInputStream.available() > 0) {
                            int readCount = fileInputStream.read(tempBuffer);
                            if (readCount == AudioTrack.ERROR_INVALID_OPERATION ||
                                    readCount == AudioTrack.ERROR_BAD_VALUE) {
                                continue;
                            }
                            if(readCount ! =0&& readCount ! = -1) {
                                audioTrack.write(tempBuffer, 0, readCount); }}}catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

        } catch(IOException e) { e.printStackTrace(); }}/** * static mode * If static mode is used, call write first, then call play */
    private void playInModeStatic(a) {
        // In static mode, the audio data needs to be written to the internal buffer of the AudioTrack once
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                try {
                    // Read waV data
                    File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.wav");
                    InputStream in = new FileInputStream(file);

                    try {
                        ByteArrayOutputStream out = new ByteArrayOutputStream();
                        for (intb; (b = in.read()) ! = -1;) { out.write(b); } audioData = out.toByteArray(); }finally{ in.close(); }}catch (IOException e) {
                  
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void v) {
              
                audioTrack = new AudioTrack(
                        new AudioAttributes.Builder()
                                .setUsage(AudioAttributes.USAGE_MEDIA)
                                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                                .build(),
                        new AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ)
                                .setEncoding(AUDIO_FORMAT)
                                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                                .build(),
                        audioData.length,
                        AudioTrack.MODE_STATIC,
                        AudioManager.AUDIO_SESSION_ID_GENERATE);
             
                audioTrack.write(audioData, 0, audioData.length);
               
                audioTrack.play();
            }
        }.execute();
    }

    /** * stop playing */
    private void stopPlay(a) {
        if(audioTrack ! =null) { audioTrack.stop(); audioTrack.release(); }}}Copy the code

3) Create globalConfig.java

package com.lzacking.audiodemo;

import android.media.AudioFormat;


public class GlobalConfig {

    /** * the sampling rate is now guaranteed to be 44100Hz on all devices, but other sampling rates (22050, 16000, 11025) are also available on some devices. * /
    public static final int SAMPLE_RATE_INHZ = 44100;

    /** * Number of channels. CHANNEL_IN_MONO and CHANNEL_IN_STEREO. CHANNEL_IN_MONO can be used on all devices. * /
    public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;

    /** * The format of the returned audio data. ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT. */
    public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;

}
Copy the code

(4) Create pcMtowavUtil.java

package com.lzacking.audiodemo;

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

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

public class PcmToWavUtil {

    /** * The audio size of the cache */
    private int mBufferSize;
    /** * sample rate */
    private int mSampleRate;
    /** * Number of channels */
    private int mChannel;

    / * * *@paramSampleRate sample rate, sample rate *@paramChannel *@paramEncoding Audio data format */
    PcmToWavUtil(int sampleRate, int channel, int encoding) {
        this.mSampleRate = sampleRate;
        this.mChannel = channel;
        this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
    }

    /** * PCM file to WAV file **@paramInFilename Source file path *@paramOutFilename Target file path */
    public void pcmToWav(String inFilename, String outFilename) {
        FileInputStream in;
        FileOutputStream out;
        long totalAudioLen;
        long totalDataLen;

        long longSampleRate = mSampleRate;
        int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 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();
        } catch(IOException e) { e.printStackTrace(); }}/**
     * 加入wav文件头
     */
    private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
                                     long totalDataLen, long longSampleRate, int channels, long byteRate)
            throws IOException {
        byte[] header = new byte[44];
        // RIFF/WAVE header
        header[0] = 'R';
        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);
        // WAVE
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        // 'fmt ' chunk
        header[12] = 'f';
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        // 4 bytes: size of 'fmt ' chunk
        header[16] = 16;
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        // format = 1
        header[20] = 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);
        // block align
        header[32] = (byte) (2 * 16 / 8);
        header[33] = 0;
        // bits per sample
        header[34] = 16;
        header[35] = 0;
        //data
        header[36] = 'd';
        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); }}Copy the code

(5) Results


Source code: Android audio and video development foundation (two) : On the Android platform using AudioRecord and AudioTrack to complete the acquisition and playback of audio PCM data, and achieve reading and writing audio WAV files