Androidのオーディオとビデオの開発-AudioTrackの使用

AudioTrackの使用

前書き

AudioTrackクラスは、Javaアプリケーションの単一のオーディオリソースを管理および再生します。これにより、PCMオーディオバッファをオーディオレシーバーにストリーミングして再生できます。

AudioTrackには2つのモードがあります

モード 説明 行動範囲
静的モード(MODE_STATIC) このモードでは、再生する前に、書き込み呼び出しを介してすべてのデータをAudioTrackの内部バッファーに渡すだけで済みます。 メモリに適した短いサウンドを処理し、可能な限り最小の遅延で再生する場合は、静的モードを選択する必要があります
ストリームモード(MODE_STREAM) オーディオデータをストリームの形式でAudioTrack内のバッファに継続的に書き込みます オーディオデータの特性(高いサンプリングレート、サンプルあたりのビット数など)により、メモリに収まらないなどの理由があります。

使用する

1.ファイルを読み取る権限を追加します

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

2.システムファイルを使用するため、耗时操作AsyncTaskが必要です

3.最も重要なことは、public int write(@ NonNull byte [] audioData、int offsetInBytes、int sizeInBytes)メソッド
です。最初のパラメーターは、再生されるデータを保持する配列です。

2番目のパラメーターは、オーディオデータのバイト単位のオフセットとそれに書き込まれるデータです(明確ではありません。誰かが説明してくれるといいのですが、0を書き込むことをお勧めします)

3番目のパラメーターは、書き込まれたオーディオデータのバイト数です。

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

テスト

インターフェイスにはテスト用の2つのシンプルなボタンしかなく、両方を再生できます。

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/weixin_44758662/article/details/114187110