Android audio and video topics (2) Use AudioRecord and AudioTrack API to collect and play audio PCM data on the Android platform, and realize reading and writing audio wav files

1. Basic use of AudioTrack

The AudioTrack class can complete the task of outputting audio data on the Android platform. AudioTrack has two data loading modes (MODE_STREAM and MODE_STATIC), corresponding to the data loading mode and audio stream type, corresponding to two completely different usage scenarios.

  • MODE_STREAM: In this mode, the audio data is written to AudioTrack through write again and again. This is usually similar to writing data to a file through the write system call, but this way of working requires copying the data from the Buffer provided by the user to the Buffer inside the AudioTrack every time, which will introduce a delay to a certain extent. To solve this problem, AudioTrack introduced the second mode.
  • MODE_STATIC: In this mode, you only need to transfer all the data to the internal buffer in AudioTrack through a write call before playing, and you don't need to transfer data later. This mode is suitable for files with small memory footprint and high latency requirements like ringtones. But it also has a disadvantage, that is, you cannot write too much data at a time, otherwise the system cannot allocate enough memory to store all the data.

1.1 MODE_STATIC mode

The way to output audio in MODE_STATIC mode is as follows ( Note: If you use STATIC mode, you must first call write to write data, and then call play. ):

Copy code

public class AudioTrackPlayerDemoActivity extends Activity implements
        OnClickListener {

    private static final String TAG = "AudioTrackPlayerDemoActivity";
    private Button button;
    private byte[] audioData;
    private AudioTrack audioTrack;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.setContentView(R.layout.main);
        this.button = (Button) super.findViewById(R.id.play);
        this.button.setOnClickListener(this);
        this.button.setEnabled(false);
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                try {
                    InputStream in = getResources().openRawResource(R.raw.ding);
                    try {
                        ByteArrayOutputStream out = new ByteArrayOutputStream(
                                264848);
                        for (int b; (b = in.read()) != -1;) {
                            out.write(b);
                        }
                        Log.d(TAG, "Got the data");
                        audioData = out.toByteArray();
                    } finally {
                        in.close();
                    }
                } catch (IOException e) {
                    Log.wtf(TAG, "Failed to read", e);
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void v) {
                Log.d(TAG, "Creating track...");
                button.setEnabled(true);
                Log.d(TAG, "Enabled button");
            }
        }.execute();
    }

    public void onClick(View view) {
        this.button.setEnabled(false);
        this.releaseAudioTrack();
        this.audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
                AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT,
                audioData.length, AudioTrack.MODE_STATIC);
        Log.d(TAG, "Writing audio data...");
        this.audioTrack.write(audioData, 0, audioData.length);
        Log.d(TAG, "Starting playback");
        audioTrack.play();
        Log.d(TAG, "Playing");
        this.button.setEnabled(true);
    }

    private void releaseAudioTrack() {
        if (this.audioTrack != null) {
            Log.d(TAG, "Stopping");
            audioTrack.stop();
            Log.d(TAG, "Releasing");
            audioTrack.release();
            Log.d(TAG, "Nulling");
        }
    }

    public void onPause() {
        super.onPause();
        this.releaseAudioTrack();
    }
}

Copy code

1.2 MODE_STREAM mode

The way to output audio in MODE_STREAM mode is as follows:

Copy code

byte[] tempBuffer = new byte[bufferSize];
int readCount = 0;
while (dis.available() > 0) {
    readCount = dis.read(tempBuffer);
    if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {
        continue;
    }
    if (readCount != 0 && readCount != -1) {
        audioTrack.play();
        audioTrack.write(tempBuffer, 0, readCount);
    }
} 

Copy code

Second, AudioTrack detailed

 2.1 Types of audio streams

In the AudioTrack constructor, the parameter AudioManager.STREAM_MUSIC will be exposed. Its meaning is related to the management and classification of audio streams by the Android system.

Android divides the system sound into several stream types, the following are a few common ones:

· STREAM_ALARM: warning sound

· STREAM_MUSIC: Music sounds, such as music, etc.

· STREAM_RING: ringtone

· STREAM_SYSTEM: system sounds, such as low battery prompt, lock screen sound, etc.

· STREAM_VOCIE_CALL: call sound

Note: The above types of divisions have nothing to do with the audio data itself. For example, both MUSIC and RING types can be a certain MP3 song. In addition, there is no fixed standard for the selection of the sound stream type. For example, the ringtone in the ringtone preview can be set to the MUSIC type. The division of audio stream types is related to the audio management strategy of the Audio system.

 

2.2 Buffer allocation and the concept of Frame

When calculating the size of the Buffer allocation, a method we often use is: getMinBufferSize. This function determines how much data buffer the application layer allocates.

AudioTrack.getMinBufferSize(8000,//8K sampling points per second                               
        AudioFormat.CHANNEL_CONFIGURATION_STEREO,//Dual-channel                   
        AudioFormat.ENCODING_PCM_16BIT);

Starting from AudioTrack.getMinBufferSize to trace the code, you can find that there is a very important concept in the underlying code: Frame. Frame is a unit used to describe the amount of data. A unit of Frame is equal to the number of bytes of a sample point × the number of channels (for example, PCM16, a frame of two channels is equal to 2×2=4 bytes). One sampling point is only for one channel, but in fact there may be one or more channels. Since an independent unit cannot be used to represent the amount of data sampled at one time for all channels, the concept of Frame is introduced. The size of the frame is the number of bytes of a sampling point × the number of channels. In addition, in the current sound card driver, its internal buffer is also allocated and managed using Frame as a unit.

Here is the method traced back to the native layer:

Copy code

// minBufCount represents the minimum number of buffers, it uses Frame as the unit 
   uint32_t minBufCount = afLatency / ((1000 *afFrameCount)/afSamplingRate); 
    if(minBufCount <2) minBufCount = 2;//At least two buffers are required 
 
   // Calculate the minimum number of frames 
   uint32_tminFrameCount =                
         (afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate; 
  //The following calculates the minimum buffer size based on the smallest FrameCount    
   intminBuffSize = minFrameCount //The calculation method is fully in line with our previous introduction to Frame 
           * (audioFormat == javaAudioTrackFields .PCM16? 2: 1) 
           * nbChannels; 
 
    returnminBuffSize;

Copy code

getMinBufSize will comprehensively consider the hardware situation (such as whether the sampling rate is supported, the delay of the hardware itself, etc.) to arrive at a minimum buffer size. Generally, the buffer size we allocate will be an integer multiple of it.

2.3 AudioTrack construction process

Each audio stream corresponds to an instance of the AudioTrack class. Each AudioTrack will be registered in AudioFlinger when it is created, and AudioFlinger will mix all the AudioTracks (Mixer), and then send them to AudioHardware for playback. At present, Android can at most at the same time. Create 32 audio streams, that is, Mixer will process up to 32 AudioTrack data streams at the same time. 

3. Comparison between AudioTrack and MediaPlayer

To play sounds, you can use MediaPlayer and AudioTrack, both of which provide Java API for application developers to use. Although both can play sounds, there is still a big difference between the two.

3.1 Difference

The biggest difference is that MediaPlayer can play sound files in multiple formats, such as MP3, AAC, WAV, OGG, MIDI, etc. MediaPlayer will create the corresponding audio decoder in the framework layer. AudioTrack can only play decoded PCM streams. If you compare the supported file formats, AudioTrack only supports wav format audio files, because most of the wav format audio files are PCM streams. AudioTrack does not create a decoder, so it can only play wav files that do not need to be decoded.

3.2 Contact

MediaPlayer will still create AudioTrack in the framework layer, pass the decoded PCM stream to AudioTrack, and then pass it to AudioFlinger for mixing, and then pass it to the hardware for playback, so MediaPlayer includes AudioTrack.

3.3 SoundPool

When contacting the Android audio playback API, I found that SoundPool can also be used to play audio. The following are the usage scenarios of the three: MediaPlayer is more suitable for playing local music files or online streaming resources for a long time in the background; SoundPool is suitable for playing relatively short audio clips, such as game sounds, button sounds, ringtones, etc., it can Play multiple audios at the same time; AudioTrack is closer to the bottom layer, provides very powerful control capabilities, supports low-latency playback, and is suitable for scenarios such as streaming media and VoIP voice calls.

Fourth, the source code 

https://github.com/renhui/AudioDemo 

Guess you like

Origin blog.csdn.net/xfb1989/article/details/113348396