Android audio and video development-the use of AudioTrack

Use of AudioTrack

Introduction

The AudioTrack class manages and plays a single audio resource for a Java application. It allows the PCM audio buffer to stream to the audio receiver for playback.

AudioTrack has two modes

mode Explanation Scope of action
Static mode (MODE_STATIC) In this mode, you only need to pass all data to the internal buffer in AudioTrack through a write call before playing. When dealing with short sounds suitable for memory and playing with the smallest possible delay, static mode should be selected
Stream mode (MODE_STREAM) Continuously write audio data to the Buffer inside AudioTrack in the form of a stream Due to the characteristics of audio data (high sampling rate, bits per sample...) is too large to fit into the memory, etc.

use

1. Add the permission to read the file

 <uses-permissionandroid:name="android.permission.READ_EXTERNAL_STORAGE" />

2. 耗时操作AsyncTask is needed because of the use of system files

3. The most important thing is the public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) method
. The first parameter is the array that holds the data to be played.

The second parameter is the offset in bytes in the audio data, and the data to be written in it (I am not clear, I hope someone can explain it, and it is recommended to write 0)

The third parameter is the number of bytes of audio data written

4. For specific explanation, please see the code below

package com.audioandvideo.two.Activity;

import androidx.appcompat.app.AppCompatActivity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.audioandvideo.R;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * MODE_STATIC:这种模式下,在play之前只需要把所有数据通过一次write调用传递到AudioTrack中的内部缓冲区
 * MODE_STREAM:以流的形式持续把音频数据写到AudioTrack内部的Buffer中
 */
public class AudioTrackActivity extends AppCompatActivity implements View.OnClickListener{
    
    
    private Button btnStatic;
    private Button btnStream;
    private AudioTrack audioTrack;
    private byte[] audioData;

    // 采样率:音频的采样频率,每秒钟能够采样的次数,采样率越高,音质越高
    // 44100是目前的标准,但是某些设备仍然支持22050,16000,11025
    // 采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级
    private final int AUDIO_SAMPLE_RATE = 44100;

    // 声道设置:android支持双声道立体声和单声道。MONO单声道,STEREO立体声
    private final  int AUDIO_CHANNEL = AudioFormat.CHANNEL_OUT_STEREO;

    // 编码制式和采样大小:采集来的数据当然使用PCM编码
    // (脉冲代码调制编码,即PCM编码。PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。)
    // android支持的采样大小16bit 或者8bit。当然采样大小越大,那么信息量越多,音质也越高,现在主流的采样
    // 大小都是16bit,在低质量的语音传输的时候8bit 足够了。
    private final  int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
    //要播放的pcm文件的储存位置及文件名
    private final String pcmFileName = Environment.getExternalStorageDirectory() + "/Download/record.pcm";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_audio_track);
        btnStatic = findViewById(R.id.btn_static);
        btnStream = findViewById(R.id.btn_stream);
        btnStatic.setOnClickListener(this);
        btnStream.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
    
    
        switch (v.getId()){
    
    
            case R.id.btn_static:
                new AsyncStatic().execute();
                break;
            case R.id.btn_stream:
                new AsyncStream().execute();
                break;
        }
    }

    /**
     *MODE_STATIC:这种模式下,在play之前只需要把所有数据通过一次write调用传递到AudioTrack中的内部缓冲区
     */
   private class AsyncStatic extends AsyncTask{
    
    
       /**
        * 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。
        * 此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。
        * @param objects
        * @return
        */
       @Override
       protected Object doInBackground(Object[] objects) {
    
    
           try {
    
    
               InputStream in = new FileInputStream(new File(pcmFileName));
               try {
    
    
         //字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。
                   ByteArrayOutputStream out = new ByteArrayOutputStream();
                   for (int b; (b = in.read()) != -1; ) {
    
    
                       out.write(b);
                   }
                   //创建一个新分配的字节数组。数组的大小和当前输出流的大小,内容是当前输出流的拷贝。
                   audioData = out.toByteArray();
               } finally {
    
    
                   //最后关闭文件
                   in.close();
               }
           } catch (IOException e) {
    
    
           }
           return null;
       }

       /**
        * 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground
        * 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回
        * @param o
        */
       @Override
       protected void onPostExecute(Object o) {
    
    

           audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, AUDIO_SAMPLE_RATE,
                   AUDIO_CHANNEL, AUDIO_ENCODING, audioData.length,AudioTrack.MODE_STATIC);
           audioTrack.write(audioData, 0, audioData.length); //将音频数据写入音频接收器以便播放
           audioTrack.play();//开始播放
       }
   }

    /**
     * MODE_STREAM:以流的形式持续把音频数据写到AudioTrack内部的Buffer中
     */
   private class AsyncStream extends AsyncTask{
    
    

       @Override
       protected Object doInBackground(Object[] objects) {
    
    
           final int minBufferSize = AudioTrack.getMinBufferSize(AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING);
           audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, AUDIO_SAMPLE_RATE,
                  AUDIO_CHANNEL, AUDIO_ENCODING, minBufferSize, AudioTrack.MODE_STREAM);//最后一个参数为audioTrack的模式
           audioTrack.play();

           File file = new File(pcmFileName);
           //从文件系统中的某个文件中获得输入字节
           FileInputStream fileInputStream = null;
           try {
    
    
               fileInputStream = new FileInputStream(file);
           } catch (FileNotFoundException e) {
    
    
               e.printStackTrace();
           }
           byte[] tempBuffer = new byte[minBufferSize];
           while (true) {
    
    
               try {
    
    
                   if (!(fileInputStream.available() > 0)) {
    
    
                       stop();            //停止播放
                       break;
                   }
//  fileInputStream.read(tempBuffer)返回读入缓冲区的总字节数;如果因为已经到达流末尾而不再有数据可用,则返回 -1。
                   int readCount = fileInputStream.read(tempBuffer);
                   Log.d("TAG", "doInBackground:"+String.valueOf(readCount));
                   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();
               }

           }
           return null;
       }
   }
//停止播放
   private void stop(){
    
    
       if(audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED){
    
    
           audioTrack.stop();
           audioTrack.release();
       }
   }
}

test

The interface has only two simple buttons for testing, and both can be played.

Insert picture description here

Guess you like

Origin blog.csdn.net/weixin_44758662/article/details/114187110