blob: 86bab413458e815f9ca4b4e213fa3b622e47bdc7 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media.cts;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.AudioAttributes;
import android.util.Log;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.LinkedList;
/**
* Class for playing audio by using audio track.
* {@link #write(byte[], int, int)} and {@link #write(short[], int, int)} methods will
* block until all data has been written to system. In order to avoid blocking, this class
* caculates available buffer size first then writes to audio sink.
*/
public class NonBlockingAudioTrack {
private static final String TAG = NonBlockingAudioTrack.class.getSimpleName();
class QueueElement {
ByteBuffer data;
int size;
}
private AudioTrack mAudioTrack;
private int mSampleRate;
private int mNumBytesQueued = 0;
private LinkedList<QueueElement> mQueue = new LinkedList<QueueElement>();
public NonBlockingAudioTrack(int sampleRate, int channelCount, boolean hwAvSync,
int audioSessionId) {
int channelConfig;
switch (channelCount) {
case 1:
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
break;
case 2:
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break;
case 6:
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
break;
default:
throw new IllegalArgumentException();
}
int minBufferSize =
AudioTrack.getMinBufferSize(
sampleRate,
channelConfig,
AudioFormat.ENCODING_PCM_16BIT);
int bufferSize = 2 * minBufferSize;
if (!hwAvSync) {
mAudioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC,
sampleRate,
channelConfig,
AudioFormat.ENCODING_PCM_16BIT,
bufferSize,
AudioTrack.MODE_STREAM);
}
else {
// build AudioTrack using Audio Attributes and FLAG_HW_AV_SYNC
AudioAttributes audioAttributes = (new AudioAttributes.Builder())
.setLegacyStreamType(AudioManager.STREAM_MUSIC)
.setFlags(AudioAttributes.FLAG_HW_AV_SYNC)
.build();
AudioFormat audioFormat = (new AudioFormat.Builder())
.setChannelMask(channelConfig)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(sampleRate)
.build();
mAudioTrack = new AudioTrack(audioAttributes, audioFormat, bufferSize,
AudioTrack.MODE_STREAM, audioSessionId);
}
mSampleRate = sampleRate;
}
public long getAudioTimeUs() {
int numFramesPlayed = mAudioTrack.getPlaybackHeadPosition();
return (numFramesPlayed * 1000000L) / mSampleRate;
}
public int getNumBytesQueued() {
return mNumBytesQueued;
}
public void play() {
mAudioTrack.play();
}
public void stop() {
mAudioTrack.stop();
mQueue.clear();
mNumBytesQueued = 0;
}
public void pause() {
mAudioTrack.pause();
}
public void flush() {
if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
return;
}
mAudioTrack.flush();
mQueue.clear();
mNumBytesQueued = 0;
}
public void release() {
mQueue.clear();
mNumBytesQueued = 0;
mAudioTrack.release();
mAudioTrack = null;
}
public void process() {
while (!mQueue.isEmpty()) {
QueueElement element = mQueue.peekFirst();
int written = mAudioTrack.write(element.data, element.size,
AudioTrack.WRITE_NON_BLOCKING);
if (written < 0) {
throw new RuntimeException("Audiotrack.write() failed.");
}
mNumBytesQueued -= written;
element.size -= written;
if (element.size != 0) {
break;
}
mQueue.removeFirst();
}
}
public int getPlayState() {
return mAudioTrack.getPlayState();
}
public void write(ByteBuffer data, int size, long pts) {
// create timestamp header
final int headerSize = 16;
ByteBuffer avSyncHeader;
avSyncHeader = ByteBuffer.allocate(headerSize);
avSyncHeader.order(ByteOrder.BIG_ENDIAN);
avSyncHeader.putInt(0x55550001);
avSyncHeader.putInt(size);
avSyncHeader.putLong(pts);
avSyncHeader.position(0);
QueueElement headerElement = new QueueElement();
headerElement.data = avSyncHeader;
headerElement.size = headerSize;
// accumulate size written to queue
mNumBytesQueued += headerSize;
mQueue.add(headerElement);
// create payload element
QueueElement element = new QueueElement();
element.data = data;
element.size = size;
data.position(0);
// accumulate size written to queue
mNumBytesQueued += size;
mQueue.add(element);
}
}