Android音视频开发 — AudioTrack的使用

AudioTrack的使用

简介

AudioTrack类为Java应用程序管理和播放单个音频资源。它允许PCM音频缓冲区流到音频接收器进行播放。

AudioTrack有两个模式

模式 解释 作用范围
静态模式(MODE_STATIC) 这种模式下,在play之前只需要把所有数据通过一次write调用传递到AudioTrack中的内部缓冲区 在处理适合内存的短声音以及需要以尽可能小的延迟播放时,应选择静态模式
流模式(MODE_STREAM) 以流的形式持续把音频数据写到AudioTrack内部的Buffer中 由于音频数据的特性(高采样率、每采样位数…)太大而无法放入内存的 等等

使用

1、加上读取文件的权限

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

2、因用到系统的文件做耗时操作所以需要用到AsyncTask

3、最主要的是public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes)方法
第一个参数是 保存要播放的数据的数组

第二个参数是 在音频数据中,以字节表示的偏移量,其中要写入的数据 (我也不是清楚,希望有大佬能解释一下,建议写0就好)

第三个参数是 写入音频数据的字节数

4、具体解释请看下面的代码

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

测试

界面只有两个简单的按钮进行测试,均能播放。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44758662/article/details/114187110