AudioTrack用来播放音频的原始数据PCM。MediaPlayer把音频解码后的数据交给AudioTrack来播放。
我们看下AudioTrack的构造函数
public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int mode, int sessionId)
throws IllegalArgumentException {
this(attributes, format, bufferSizeInBytes, mode, sessionId, false /*offload*/,
ENCAPSULATION_MODE_NONE, null /* tunerConfiguration */);
}
AudioAttributes attributes
AudioAttributes audioAttributes = (new AudioAttributes.Builder()).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build();
设置当前要播放什么内容的数据
我们看源码包括下面一下信息
public final class AudioAttributes implements Parcelable {
private final static String TAG = "AudioAttributes";
/**
* Content type value to use when the content type is unknown, or other than the ones defined.
*/
public final static int CONTENT_TYPE_UNKNOWN = 0;
/**
* Content type value to use when the content type is speech.
*/
public final static int CONTENT_TYPE_SPEECH = 1;
/**
* Content type value to use when the content type is music.
*/
public final static int CONTENT_TYPE_MUSIC = 2;
/**
* Content type value to use when the content type is a soundtrack, typically accompanying
* a movie or TV program.
*/
public final static int CONTENT_TYPE_MOVIE = 3;
/**
* Content type value to use when the content type is a sound used to accompany a user
* action, such as a beep or sound effect expressing a key click, or event, such as the
* type of a sound for a bonus being received in a game. These sounds are mostly synthesized
* or short Foley sounds.
*/
public final static int CONTENT_TYPE_SONIFICATION = 4;
包括了 演讲 电影 音乐等类型。我们这里选择
CONTENT_TYPE_MUSIC
AudioFormat format
AudioFormat.Builder builder = new AudioFormat.Builder();
builder.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(44100);
AudioFormat audioFormat = builder.build();
设置声道掩码,比如采集音视频我们设置了AudioFormat.CHANNEL_IN_STEREO,那么播放的时候要设置成AudioFormat.CHANNEL_OUT_STEREO
设置编码格式AudioFormat.ENCODING_PCM_16BIT 每个采样大小是2个字节
设置采样率 44100
builder还提供了setChannelIndexMask方法,setChannelIndexMask和setChannelMask是互斥的。
setChannelIndexMask
Added in API level 23
AudioFormat.Builder setChannelIndexMask (int channelIndexMask)
设置频道索引掩码。 通道索引掩码指定帧中的音频采样与编号的端点通道的关联。 信道索引掩码中的第i位对应于第i个端点信道。 例如,具有四个通道的端点被表示为索引掩码位0至3.这个位置掩码解释的这个setChannelMask(int) 。
AudioTrack和AudioRecord支持通道索引掩码。 如果指定了通道索引掩码,则使用该掩码,否则使用由setChannelMask指定的通道位置掩码。 对于AudioTrack和AudioRecord ,如果指定了通道索引掩码,则不需要通道位置掩码。
int bufferSizeInBytes
* See {@link #getMinBufferSize(int, int, int)} to determine the estimated minimum buffer size
* for an AudioTrack instance in streaming mode.
注释中写道对于流模式AudioTrack getMinBufferSize
int mode
@param mode streaming or static buffer. See {@link #MODE_STATIC} and {@link #MODE_STREAM}.
MODE_STATIC 一次把数据传进来
MODE_STREAM 传很多次数据
int sessionId 会话id
AudioManager.AUDIO_SESSION_ID_GENERATE
AudioManager.generateAudioSessionId()
开始播放
Runnable recordRunnable = new Runnable() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void run() {
try {
//设置线程的优先级
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
byte[] tempBuffer = new byte[buffersize];
int readCount = 0;
while (inputStream.available() > 0) {
readCount= inputStream.read(tempBuffer);
if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {
continue;
}
if (readCount != 0 && readCount != -1) {//一边播放一边写入语音数据
//判断AudioTrack未初始化,停止播放的时候释放了,状态就为STATE_UNINITIALIZED
if(mAudioTrack.getState() == mAudioTrack.STATE_UNINITIALIZED){
init();
}
mAudioTrack.play();
mAudioTrack.write(tempBuffer, 0, readCount);
}
}
stopPlay();//播放完就停止播放
} catch (Exception e) {
e.printStackTrace();
}
}
};
停止播放
public void stopPlay() {
try {
if (mAudioTrack != null) {
if (mAudioTrack.getState() == AudioRecord.STATE_INITIALIZED) {//初始化成功
mAudioTrack.stop();//停止播放
}
if (mAudioTrack != null) {
mAudioTrack.release();//释放audioTrack资源
}
}
if (inputStream != null) {
inputStream.close();//关闭数据输入流
}
} catch (Exception e) {
e.printStackTrace();
}
}
完整代码
package com.yuanxuzhen.androidmedia.audio;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.os.Build;
import android.provider.MediaStore;
import android.util.Log;
import androidx.annotation.RequiresApi;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AudioTrackManager {
private static volatile AudioTrackManager instance;
private AudioTrack mAudioTrack;
private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
private int buffersize;
private ExecutorService executorService;
private DataInputStream inputStream;
private AudioTrackManager() {
init();
}
public static AudioTrackManager getInstance() {
if (instance == null) {
synchronized (AudioTrackManager.class) {
if (instance == null) {
instance = new AudioTrackManager();
}
}
}
return instance;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void init() {
executorService = Executors.newCachedThreadPool();
AudioFormat.Builder builder = new AudioFormat.Builder();
builder.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(44100);
AudioFormat audioFormat = builder.build();
buffersize = AudioTrack.getMinBufferSize(44100,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT);
Log.e("yuanAudioTrack", "init buffersize=" + buffersize);
AudioAttributes audioAttributes = (new AudioAttributes.Builder()).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build();
mAudioTrack = new AudioTrack(
audioAttributes,
audioFormat,
buffersize,
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE
);
}
/**
* 播放线程
*/
Runnable recordRunnable = new Runnable() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void run() {
try {
//设置线程的优先级
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
byte[] tempBuffer = new byte[buffersize];
int readCount = 0;
while (inputStream.available() > 0) {
readCount= inputStream.read(tempBuffer);
if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {
continue;
}
if (readCount != 0 && readCount != -1) {//一边播放一边写入语音数据
//判断AudioTrack未初始化,停止播放的时候释放了,状态就为STATE_UNINITIALIZED
if(mAudioTrack.getState() == mAudioTrack.STATE_UNINITIALIZED){
init();
}
mAudioTrack.play();
mAudioTrack.write(tempBuffer, 0, readCount);
}
}
stopPlay();//播放完就停止播放
} catch (Exception e) {
e.printStackTrace();
}
}
};
private void setPath(String path) throws Exception {
File file = new File(path);
inputStream = new DataInputStream(new FileInputStream(file));
}
public void stopPlay() {
try {
if (mAudioTrack != null) {
if (mAudioTrack.getState() == AudioRecord.STATE_INITIALIZED) {//初始化成功
mAudioTrack.stop();//停止播放
}
if (mAudioTrack != null) {
mAudioTrack.release();//释放audioTrack资源
}
}
if (inputStream != null) {
inputStream.close();//关闭数据输入流
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void startPlay(String path) {
try {
setPath(path);
executorService.execute(recordRunnable);
} catch (Exception e) {
e.printStackTrace();
}
}
}
gitee地址