目录
音频流式播放器AudioTrack配合WebSocket的完整案例
本文理论涉及较少,偏向应用。文章涉及如下几个模块功能:
- AudioTrack播放器
- WebSocket的使用
- 阻塞队列BlockingDeque的使用
- 额外的录音AudioRecord
一、 为什么选择AudioTrack
支持音频播放的播放器有很多,比如Android自带的MediaPlayer,第三方ExoPlayer等等。但要是支持流式播放的还得是AudioTrack,MediaPlayer(底层也是AudioTrack)虽然也可以实现流式播放,但会有很多问题:
- 他们需要读取文件或者URL,这就需要先把音频数据保存本地,然后把file地址设置到播放器。
- 服务器如果返回数据比较慢,或者网络延迟,WebSocket每一帧数据间隔较长的时候,会导致上一帧数据播放完,MediaPlayer就默认数据播放完了,再有数据也不再播放了,所以需要自己维护这个状态。
当然,AudioTrack由于是更偏向底层,所以很多功能需要用户自己实现:
- 播放器是否准备好播放的回调;
- 监听是否播放完毕;
二、AudioTrack播放器
先看实现逻辑:
import android.annotation.SuppressLint;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
public class AudioTrackImpl implements AudioPlayerInterface {
private static final String TAG = "AudioTrackImpl";
private AudioTrack audioTrack;
private volatile boolean isPlaying = true;
/**
* 二进制数据流统计,当大于1时,通知View层可以播放数据。
*/
private int mBytdDataCount = 0;
/**
* 服务端是否全部返回数据
*/
private volatile boolean isWriteAudioFinish = false;
private final BlockingDeque<byte[]> audioDataQueue = new LinkedBlockingDeque<>();
/**
* 重新播放需要的数据
*/
private final LinkedList<byte[]> mReplayData = new LinkedList<>();
private final PlayerListener mListener;
public AudioTrackImpl(PlayerListener listener) {
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
AudioFormat audioFormat = new AudioFormat.Builder()
.setSampleRate(AudioConfig.sampleRateInHz)
.setEncoding(AudioConfig.audioFormat)
.setChannelMask(AudioConfig.channelOutConfig)
.build();
audioTrack = new AudioTrack(audioAttributes, audioFormat,AudioConfig.BUFFER_SIZE_IN_BYTES, AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE);
mListener = listener;
}
@Override
public void writeAudioChunk(byte[] audioData) {
try {
if (mBytdDataCount == 0) {
ThreadDispatcher.getInstance().postToMainThread(new Runnable() {
@Override
public void run() {
mListener.onPlaybackStateChanged(PlayerListener.PLAYBACK_READY);
}
});
}
mBytdDataCount++;
audioDataQueue.put(audioData);
mReplayData.add(audioData);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}