blob: b804ac2fe258ea7eabeea3cce573ff1f7e73e3e0 [file] [log] [blame]
/*
**
** Copyright 2018, 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.annotation.RawRes;
import android.content.res.AssetFileDescriptor;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
import com.android.compatibility.common.util.CtsAndroidTestCase;
import javax.annotation.concurrent.GuardedBy;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Executor;
@NonMediaMainlineTest
public class AudioTrackOffloadTest extends CtsAndroidTestCase {
private static final String TAG = "AudioTrackOffloadTest";
private static final int BUFFER_SIZE_SEC = 3;
private static final int PRESENTATION_END_TIMEOUT_MS = 8 * 1000; // 8s
private static final AudioAttributes DEFAULT_ATTR = new AudioAttributes.Builder().build();
public void testIsOffloadSupportedNullFormat() throws Exception {
try {
final boolean offloadableFormat = AudioManager.isOffloadedPlaybackSupported(null,
DEFAULT_ATTR);
fail("Shouldn't be able to use null AudioFormat in isOffloadedPlaybackSupported()");
} catch (NullPointerException e) {
// ok, NPE is expected here
}
}
public void testIsOffloadSupportedNullAttributes() throws Exception {
try {
final boolean offloadableFormat = AudioManager.isOffloadedPlaybackSupported(
getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), null);
fail("Shouldn't be able to use null AudioAttributes in isOffloadedPlaybackSupported()");
} catch (NullPointerException e) {
// ok, NPE is expected here
}
}
public void testExerciseIsOffloadSupported() throws Exception {
final boolean offloadableFormat = AudioManager.isOffloadedPlaybackSupported(
getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), DEFAULT_ATTR);
}
public void testMP3AudioTrackOffload() throws Exception {
testAudioTrackOffload(R.raw.sine1khzs40dblong,
/* bitRateInkbps= */ 192,
getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
}
public void testOpusAudioTrackOffload() throws Exception {
testAudioTrackOffload(R.raw.testopus,
/* bitRateInkbps= */ 118, // Average
getAudioFormatWithEncoding(AudioFormat.ENCODING_OPUS));
}
/** Test offload of an audio resource that MUST be at least 3sec long. */
private void testAudioTrackOffload(@RawRes int audioRes, int bitRateInkbps,
AudioFormat audioFormat) throws Exception {
AudioTrack track = null;
int bufferSizeInBytes3sec = bitRateInkbps * 1024 * 3 / 8;
try (AssetFileDescriptor audioToOffload = getContext().getResources()
.openRawResourceFd(audioRes);
InputStream audioInputStream = audioToOffload.createInputStream()) {
if (!AudioManager.isOffloadedPlaybackSupported(audioFormat, DEFAULT_ATTR)) {
Log.i(TAG, "skipping testAudioTrackOffload as offload encoding "
+ audioFormat.getEncoding() + " is not supported");
// cannot test if offloading is not supported
return;
}
// format is offloadable, test playback head is progressing
track = new AudioTrack.Builder()
.setAudioAttributes(DEFAULT_ATTR)
.setAudioFormat(audioFormat)
.setTransferMode(AudioTrack.MODE_STREAM)
.setBufferSizeInBytes(bufferSizeInBytes3sec)
.setOffloadedPlayback(true).build();
assertNotNull("Couldn't create offloaded AudioTrack", track);
assertEquals("Unexpected track sample rate", 44100, track.getSampleRate());
assertEquals("Unexpected track channel config", AudioFormat.CHANNEL_OUT_STEREO,
track.getChannelConfiguration());
try {
track.registerStreamEventCallback(mExec, null);
fail("Shouldn't be able to register null StreamEventCallback");
} catch (Exception e) { }
track.registerStreamEventCallback(mExec, mCallback);
final byte[] data = new byte[bufferSizeInBytes3sec];
final int read = audioInputStream.read(data);
assertEquals("Could not read enough audio from the resource file",
bufferSizeInBytes3sec, read);
track.play();
int written = 0;
while (written < read) {
int wrote = track.write(data, written, read - written,
AudioTrack.WRITE_BLOCKING);
if (wrote < 0) {
fail("Unable to write all read data, wrote " + written + " bytes");
}
written += wrote;
}
try {
Thread.sleep(1 * 1000);
synchronized(mPresEndLock) {
track.stop();
mPresEndLock.safeWait(PRESENTATION_END_TIMEOUT_MS);
}
} catch (InterruptedException e) { fail("Error while sleeping"); }
synchronized (mEventCallbackLock) {
assertTrue("onDataRequest not called", mCallback.mDataRequestCount > 0);
}
synchronized (mPresEndLock) {
// we are at most PRESENTATION_END_TIMEOUT_MS + 1s after about 3s of data was
// supplied, presentation should have ended
assertEquals("onPresentationEnded not called one time",
1, mCallback.mPresentationEndedCount);
}
} finally {
if (track != null) {
Log.i(TAG, "pause");
track.pause();
track.unregisterStreamEventCallback(mCallback);
track.release();
}
}
}
private static AudioFormat getAudioFormatWithEncoding(int encoding) {
return new AudioFormat.Builder()
.setEncoding(encoding)
.setSampleRate(44100)
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.build();
}
private Executor mExec = new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
};
private final Object mEventCallbackLock = new Object();
private final SafeWaitObject mPresEndLock = new SafeWaitObject();
private EventCallback mCallback = new EventCallback();
private class EventCallback extends AudioTrack.StreamEventCallback {
@GuardedBy("mEventCallbackLock")
int mTearDownCount;
@GuardedBy("mPresEndLock")
int mPresentationEndedCount;
@GuardedBy("mEventCallbackLock")
int mDataRequestCount;
@Override
public void onTearDown(AudioTrack track) {
synchronized (mEventCallbackLock) {
Log.i(TAG, "onTearDown");
mTearDownCount++;
}
}
@Override
public void onPresentationEnded(AudioTrack track) {
synchronized (mPresEndLock) {
Log.i(TAG, "onPresentationEnded");
mPresentationEndedCount++;
mPresEndLock.safeNotify();
}
}
@Override
public void onDataRequest(AudioTrack track, int size) {
synchronized (mEventCallbackLock) {
Log.i(TAG, "onDataRequest size:"+size);
mDataRequestCount++;
}
}
}
}