Merge "AudioTrack: support for offloaded playback"
This commit is contained in:
committed by
Android (Google) Code Review
commit
b5ebe3003a
@@ -33,6 +33,8 @@
|
||||
#define ENCODING_AAC_HE_V2 12
|
||||
#define ENCODING_IEC61937 13
|
||||
#define ENCODING_DOLBY_TRUEHD 14
|
||||
#define ENCODING_AAC_ELD 15
|
||||
#define ENCODING_AAC_XHE 16
|
||||
|
||||
#define ENCODING_INVALID 0
|
||||
#define ENCODING_DEFAULT 1
|
||||
@@ -71,6 +73,10 @@ static inline audio_format_t audioFormatToNative(int audioFormat)
|
||||
return AUDIO_FORMAT_DOLBY_TRUEHD;
|
||||
case ENCODING_IEC61937:
|
||||
return AUDIO_FORMAT_IEC61937;
|
||||
case ENCODING_AAC_ELD:
|
||||
return AUDIO_FORMAT_AAC_ELD;
|
||||
case ENCODING_AAC_XHE:
|
||||
return AUDIO_FORMAT_AAC; // FIXME temporary value, needs addition of xHE-AAC
|
||||
case ENCODING_DEFAULT:
|
||||
return AUDIO_FORMAT_DEFAULT;
|
||||
default:
|
||||
@@ -114,6 +120,11 @@ static inline int audioFormatFromNative(audio_format_t nativeFormat)
|
||||
return ENCODING_IEC61937;
|
||||
case AUDIO_FORMAT_DOLBY_TRUEHD:
|
||||
return ENCODING_DOLBY_TRUEHD;
|
||||
case AUDIO_FORMAT_AAC_ELD:
|
||||
return ENCODING_AAC_ELD;
|
||||
// FIXME needs addition of AUDIO_FORMAT_AAC_XHE
|
||||
//case AUDIO_FORMAT_AAC_XHE:
|
||||
// return ENCODING_AAC_XHE;
|
||||
case AUDIO_FORMAT_DEFAULT:
|
||||
return ENCODING_DEFAULT;
|
||||
default:
|
||||
@@ -121,6 +132,25 @@ static inline int audioFormatFromNative(audio_format_t nativeFormat)
|
||||
}
|
||||
}
|
||||
|
||||
// This function converts Java channel masks to a native channel mask.
|
||||
// validity should be checked with audio_is_output_channel().
|
||||
static inline audio_channel_mask_t nativeChannelMaskFromJavaChannelMasks(
|
||||
jint channelPositionMask, jint channelIndexMask)
|
||||
{
|
||||
// 0 is the java android.media.AudioFormat.CHANNEL_INVALID value
|
||||
if (channelIndexMask != 0) { // channel index mask takes priority
|
||||
// To convert to a native channel mask, the Java channel index mask
|
||||
// requires adding the index representation.
|
||||
return audio_channel_mask_from_representation_and_bits(
|
||||
AUDIO_CHANNEL_REPRESENTATION_INDEX,
|
||||
channelIndexMask);
|
||||
}
|
||||
// To convert to a native channel mask, the Java channel position mask
|
||||
// requires a shift by 2 to skip the two deprecated channel
|
||||
// configurations "default" and "mono".
|
||||
return (audio_channel_mask_t)((uint32_t)channelPositionMask >> 2);
|
||||
}
|
||||
|
||||
static inline audio_channel_mask_t outChannelMaskToNative(int channelMask)
|
||||
{
|
||||
switch (channelMask) {
|
||||
|
||||
@@ -1770,6 +1770,24 @@ android_media_AudioSystem_getStreamVolumeDB(JNIEnv *env, jobject thiz,
|
||||
(audio_devices_t)device);
|
||||
}
|
||||
|
||||
static jboolean
|
||||
android_media_AudioSystem_isOffloadSupported(JNIEnv *env, jobject thiz,
|
||||
jint encoding, jint sampleRate, jint channelMask, jint channelIndexMask)
|
||||
{
|
||||
audio_offload_info_t format = AUDIO_INFO_INITIALIZER;
|
||||
format.format = (audio_format_t) audioFormatToNative(encoding);
|
||||
format.sample_rate = (uint32_t) sampleRate;
|
||||
format.channel_mask = nativeChannelMaskFromJavaChannelMasks(channelMask, channelIndexMask);
|
||||
format.stream_type = AUDIO_STREAM_MUSIC;
|
||||
format.has_video = false;
|
||||
format.is_streaming = false;
|
||||
// offload duration unknown at this point:
|
||||
// client side code cannot access "audio.offload.min.duration.secs" property to make a query
|
||||
// agnostic of duration, so using acceptable estimate of 2mn
|
||||
format.duration_us = 120 * 1000000;
|
||||
return AudioSystem::isOffloadSupported(format);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
static const JNINativeMethod gMethods[] = {
|
||||
@@ -1823,6 +1841,7 @@ static const JNINativeMethod gMethods[] = {
|
||||
(void *)android_media_AudioSystem_registerRecordingCallback},
|
||||
{"systemReady", "()I", (void *)android_media_AudioSystem_systemReady},
|
||||
{"getStreamVolumeDB", "(III)F", (void *)android_media_AudioSystem_getStreamVolumeDB},
|
||||
{"native_is_offload_supported", "(IIII)Z", (void *)android_media_AudioSystem_isOffloadSupported},
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ struct audiotrack_callback_cookie {
|
||||
jobject audioTrack_ref;
|
||||
bool busy;
|
||||
Condition cond;
|
||||
bool isOffload;
|
||||
};
|
||||
|
||||
// keep these values in sync with AudioTrack.java
|
||||
@@ -90,6 +91,7 @@ class AudioTrackJniStorage {
|
||||
AudioTrackJniStorage() {
|
||||
mCallbackData.audioTrack_class = 0;
|
||||
mCallbackData.audioTrack_ref = 0;
|
||||
mCallbackData.isOffload = false;
|
||||
}
|
||||
|
||||
~AudioTrackJniStorage() {
|
||||
@@ -132,27 +134,34 @@ static void audioCallback(int event, void* user, void *info) {
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case AudioTrack::EVENT_MARKER: {
|
||||
JNIEnv *env = AndroidRuntime::getJNIEnv();
|
||||
if (user != NULL && env != NULL) {
|
||||
env->CallStaticVoidMethod(
|
||||
callbackInfo->audioTrack_class,
|
||||
javaAudioTrackFields.postNativeEventInJava,
|
||||
callbackInfo->audioTrack_ref, event, 0,0, NULL);
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
// Offload only events
|
||||
case AudioTrack::EVENT_STREAM_END:
|
||||
case AudioTrack::EVENT_MORE_DATA:
|
||||
// a.k.a. tear down
|
||||
case AudioTrack::EVENT_NEW_IAUDIOTRACK:
|
||||
if (callbackInfo->isOffload) {
|
||||
JNIEnv *env = AndroidRuntime::getJNIEnv();
|
||||
if (user != NULL && env != NULL) {
|
||||
env->CallStaticVoidMethod(
|
||||
callbackInfo->audioTrack_class,
|
||||
javaAudioTrackFields.postNativeEventInJava,
|
||||
callbackInfo->audioTrack_ref, event, 0,0, NULL);
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
// PCM and offload events
|
||||
case AudioTrack::EVENT_MARKER:
|
||||
case AudioTrack::EVENT_NEW_POS: {
|
||||
JNIEnv *env = AndroidRuntime::getJNIEnv();
|
||||
if (user != NULL && env != NULL) {
|
||||
env->CallStaticVoidMethod(
|
||||
callbackInfo->audioTrack_class,
|
||||
javaAudioTrackFields.postNativeEventInJava,
|
||||
callbackInfo->audioTrack_ref, event, 0,0, NULL);
|
||||
callbackInfo->audioTrack_class,
|
||||
javaAudioTrackFields.postNativeEventInJava,
|
||||
callbackInfo->audioTrack_ref, event, 0,0, NULL);
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
@@ -198,30 +207,12 @@ sp<AudioTrack> android_media_AudioTrack_getAudioTrack(JNIEnv* env, jobject audio
|
||||
return getAudioTrack(env, audioTrackObj);
|
||||
}
|
||||
|
||||
// This function converts Java channel masks to a native channel mask.
|
||||
// validity should be checked with audio_is_output_channel().
|
||||
static inline audio_channel_mask_t nativeChannelMaskFromJavaChannelMasks(
|
||||
jint channelPositionMask, jint channelIndexMask)
|
||||
{
|
||||
if (channelIndexMask != 0) { // channel index mask takes priority
|
||||
// To convert to a native channel mask, the Java channel index mask
|
||||
// requires adding the index representation.
|
||||
return audio_channel_mask_from_representation_and_bits(
|
||||
AUDIO_CHANNEL_REPRESENTATION_INDEX,
|
||||
channelIndexMask);
|
||||
}
|
||||
// To convert to a native channel mask, the Java channel position mask
|
||||
// requires a shift by 2 to skip the two deprecated channel
|
||||
// configurations "default" and "mono".
|
||||
return (audio_channel_mask_t)(channelPositionMask >> 2);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
static jint
|
||||
android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, jobject jaa,
|
||||
jintArray jSampleRate, jint channelPositionMask, jint channelIndexMask,
|
||||
jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession,
|
||||
jlong nativeAudioTrack) {
|
||||
jlong nativeAudioTrack, jboolean offload) {
|
||||
|
||||
ALOGV("sampleRates=%p, channel mask=%x, index mask=%x, audioFormat(Java)=%d, buffSize=%d"
|
||||
"nativeAudioTrack=0x%" PRIX64,
|
||||
@@ -322,8 +313,19 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, job
|
||||
lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz);
|
||||
// we use a weak reference so the AudioTrack object can be garbage collected.
|
||||
lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this);
|
||||
lpJniStorage->mCallbackData.isOffload = offload;
|
||||
lpJniStorage->mCallbackData.busy = false;
|
||||
|
||||
audio_offload_info_t offloadInfo;
|
||||
if (offload) {
|
||||
offloadInfo = AUDIO_INFO_INITIALIZER;
|
||||
offloadInfo.format = format;
|
||||
offloadInfo.sample_rate = sampleRateInHertz;
|
||||
offloadInfo.channel_mask = nativeChannelMask;
|
||||
offloadInfo.has_video = false;
|
||||
offloadInfo.stream_type = AUDIO_STREAM_MUSIC; //required for offload
|
||||
}
|
||||
|
||||
// initialize the native AudioTrack object
|
||||
status_t status = NO_ERROR;
|
||||
switch (memoryMode) {
|
||||
@@ -342,7 +344,7 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, job
|
||||
true,// thread can call Java
|
||||
sessionId,// audio session ID
|
||||
AudioTrack::TRANSFER_SYNC,
|
||||
NULL, // default offloadInfo
|
||||
offload ? &offloadInfo : NULL,
|
||||
-1, -1, // default uid, pid values
|
||||
paa);
|
||||
break;
|
||||
@@ -1234,7 +1236,7 @@ static const JNINativeMethod gMethods[] = {
|
||||
{"native_stop", "()V", (void *)android_media_AudioTrack_stop},
|
||||
{"native_pause", "()V", (void *)android_media_AudioTrack_pause},
|
||||
{"native_flush", "()V", (void *)android_media_AudioTrack_flush},
|
||||
{"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJ)I",
|
||||
{"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJZ)I",
|
||||
(void *)android_media_AudioTrack_setup},
|
||||
{"native_finalize", "()V", (void *)android_media_AudioTrack_finalize},
|
||||
{"native_release", "()V", (void *)android_media_AudioTrack_release},
|
||||
|
||||
@@ -240,20 +240,25 @@ public final class AudioFormat implements Parcelable {
|
||||
public static final int ENCODING_DTS_HD = 8;
|
||||
/** Audio data format: MP3 compressed
|
||||
* @hide
|
||||
* TODO unhide and add to @Encoding (intentional white space
|
||||
* */
|
||||
public static final int ENCODING_MP3 = 9;
|
||||
/** Audio data format: AAC LC compressed
|
||||
* @hide
|
||||
* TODO unhide and add to @Encoding (intentional white space
|
||||
* */
|
||||
public static final int ENCODING_AAC_LC = 10;
|
||||
/** Audio data format: AAC HE V1 compressed
|
||||
* @hide
|
||||
* TODO unhide and add to @Encoding (intentional white space
|
||||
* */
|
||||
public static final int ENCODING_AAC_HE_V1 = 11;
|
||||
/** Audio data format: AAC HE V2 compressed
|
||||
* @hide
|
||||
* TODO unhide and add to @Encoding (intentional white space
|
||||
* */
|
||||
public static final int ENCODING_AAC_HE_V2 = 12;
|
||||
|
||||
/** Audio data format: compressed audio wrapped in PCM for HDMI
|
||||
* or S/PDIF passthrough.
|
||||
* IEC61937 uses a stereo stream of 16-bit samples as the wrapper.
|
||||
@@ -266,6 +271,16 @@ public final class AudioFormat implements Parcelable {
|
||||
/** Audio data format: DOLBY TRUEHD compressed
|
||||
**/
|
||||
public static final int ENCODING_DOLBY_TRUEHD = 14;
|
||||
/** Audio data format: AAC ELD compressed
|
||||
* @hide
|
||||
* TODO unhide and add to @Encoding (intentional white space
|
||||
* */
|
||||
public static final int ENCODING_AAC_ELD = 15;
|
||||
/** Audio data format: AAC xHE compressed
|
||||
* @hide
|
||||
* TODO unhide and add to @Encoding (intentional white space
|
||||
* */
|
||||
public static final int ENCODING_AAC_XHE = 16;
|
||||
|
||||
/** @hide */
|
||||
public static String toLogFriendlyEncoding(int enc) {
|
||||
@@ -298,6 +313,10 @@ public final class AudioFormat implements Parcelable {
|
||||
return "ENCODING_IEC61937";
|
||||
case ENCODING_DOLBY_TRUEHD:
|
||||
return "ENCODING_DOLBY_TRUEHD";
|
||||
case ENCODING_AAC_ELD:
|
||||
return "ENCODING_AAC_ELD";
|
||||
case ENCODING_AAC_XHE:
|
||||
return "ENCODING_AAC_XHE";
|
||||
default :
|
||||
return "invalid encoding " + enc;
|
||||
}
|
||||
@@ -514,6 +533,8 @@ public final class AudioFormat implements Parcelable {
|
||||
case ENCODING_AAC_HE_V1:
|
||||
case ENCODING_AAC_HE_V2:
|
||||
case ENCODING_IEC61937:
|
||||
case ENCODING_AAC_ELD:
|
||||
case ENCODING_AAC_XHE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -532,6 +553,13 @@ public final class AudioFormat implements Parcelable {
|
||||
case ENCODING_DTS:
|
||||
case ENCODING_DTS_HD:
|
||||
case ENCODING_IEC61937:
|
||||
//TODO not true yet (intended white space
|
||||
case ENCODING_MP3:
|
||||
case ENCODING_AAC_LC:
|
||||
case ENCODING_AAC_HE_V1:
|
||||
case ENCODING_AAC_HE_V2:
|
||||
case ENCODING_AAC_ELD:
|
||||
case ENCODING_AAC_XHE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -556,6 +584,8 @@ public final class AudioFormat implements Parcelable {
|
||||
case ENCODING_AAC_HE_V1:
|
||||
case ENCODING_AAC_HE_V2:
|
||||
case ENCODING_IEC61937: // wrapped in PCM but compressed
|
||||
case ENCODING_AAC_ELD:
|
||||
case ENCODING_AAC_XHE:
|
||||
return false;
|
||||
case ENCODING_INVALID:
|
||||
default:
|
||||
@@ -581,6 +611,8 @@ public final class AudioFormat implements Parcelable {
|
||||
case ENCODING_AAC_LC:
|
||||
case ENCODING_AAC_HE_V1:
|
||||
case ENCODING_AAC_HE_V2:
|
||||
case ENCODING_AAC_ELD:
|
||||
case ENCODING_AAC_XHE:
|
||||
return false;
|
||||
case ENCODING_INVALID:
|
||||
default:
|
||||
@@ -794,14 +826,7 @@ public final class AudioFormat implements Parcelable {
|
||||
|
||||
/**
|
||||
* Sets the data encoding format.
|
||||
* @param encoding one of {@link AudioFormat#ENCODING_DEFAULT},
|
||||
* {@link AudioFormat#ENCODING_PCM_8BIT},
|
||||
* {@link AudioFormat#ENCODING_PCM_16BIT},
|
||||
* {@link AudioFormat#ENCODING_PCM_FLOAT},
|
||||
* {@link AudioFormat#ENCODING_AC3},
|
||||
* {@link AudioFormat#ENCODING_E_AC3}.
|
||||
* {@link AudioFormat#ENCODING_DTS},
|
||||
* {@link AudioFormat#ENCODING_DTS_HD}.
|
||||
* @param encoding the specified encoding or default.
|
||||
* @return the same Builder instance.
|
||||
* @throws java.lang.IllegalArgumentException
|
||||
*/
|
||||
@@ -818,6 +843,12 @@ public final class AudioFormat implements Parcelable {
|
||||
case ENCODING_DTS:
|
||||
case ENCODING_DTS_HD:
|
||||
case ENCODING_IEC61937:
|
||||
case ENCODING_MP3:
|
||||
case ENCODING_AAC_LC:
|
||||
case ENCODING_AAC_HE_V1:
|
||||
case ENCODING_AAC_HE_V2:
|
||||
case ENCODING_AAC_ELD:
|
||||
case ENCODING_AAC_XHE:
|
||||
mEncoding = encoding;
|
||||
break;
|
||||
case ENCODING_INVALID:
|
||||
@@ -1016,7 +1047,7 @@ public final class AudioFormat implements Parcelable {
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@IntDef({
|
||||
@IntDef(flag = false, prefix = "ENCODING", value = {
|
||||
ENCODING_DEFAULT,
|
||||
ENCODING_PCM_8BIT,
|
||||
ENCODING_PCM_16BIT,
|
||||
@@ -1025,8 +1056,8 @@ public final class AudioFormat implements Parcelable {
|
||||
ENCODING_E_AC3,
|
||||
ENCODING_DTS,
|
||||
ENCODING_DTS_HD,
|
||||
ENCODING_IEC61937
|
||||
})
|
||||
ENCODING_IEC61937 }
|
||||
)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Encoding {}
|
||||
|
||||
|
||||
@@ -1328,6 +1328,21 @@ public class AudioManager {
|
||||
}
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
// Offload query
|
||||
/**
|
||||
* @hide
|
||||
* TODO unhide (intentional white space to attract attention:
|
||||
* Returns whether offloaded playback of an audio format is supported on the device.
|
||||
* Offloaded playback is where the decoding of an audio stream is not competing with other
|
||||
* software resources. In general, it is supported by dedicated hardware, such as audio DSPs.
|
||||
* @param format the audio format (codec, sample rate, channels) being checked.
|
||||
* @return true if the given audio format can be offloaded.
|
||||
*/
|
||||
public static boolean isOffloadedPlaybackSupported(@NonNull AudioFormat format) {
|
||||
return AudioSystem.isOffloadSupported(format);
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
// Bluetooth SCO control
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.media;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.audiopolicy.AudioMix;
|
||||
@@ -818,6 +819,14 @@ public class AudioSystem
|
||||
|
||||
public static native float getStreamVolumeDB(int stream, int index, int device);
|
||||
|
||||
static boolean isOffloadSupported(@NonNull AudioFormat format) {
|
||||
return native_is_offload_supported(format.getEncoding(), format.getSampleRate(),
|
||||
format.getChannelMask(), format.getChannelIndexMask());
|
||||
}
|
||||
|
||||
private static native boolean native_is_offload_supported(int encoding, int sampleRate,
|
||||
int channelMask, int channelIndexMask);
|
||||
|
||||
// Items shared with audio service
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,9 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.NioUtils;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
@@ -185,6 +187,22 @@ public class AudioTrack extends PlayerBase
|
||||
* Event id denotes when previously set update period has elapsed during playback.
|
||||
*/
|
||||
private static final int NATIVE_EVENT_NEW_POS = 4;
|
||||
/**
|
||||
* Callback for more data
|
||||
* TODO only for offload
|
||||
*/
|
||||
private static final int NATIVE_EVENT_MORE_DATA = 0;
|
||||
/**
|
||||
* IAudioTrack tear down for offloaded tracks
|
||||
* TODO: when received, java AudioTrack must be released
|
||||
*/
|
||||
private static final int NATIVE_EVENT_NEW_IAUDIOTRACK = 6;
|
||||
/**
|
||||
* Event id denotes when all the buffers queued in AF and HW are played
|
||||
* back (after stop is called) for an offloaded track.
|
||||
* TODO: not just for offload
|
||||
*/
|
||||
private static final int NATIVE_EVENT_STREAM_END = 7;
|
||||
|
||||
private final static String TAG = "android.media.AudioTrack";
|
||||
|
||||
@@ -540,6 +558,12 @@ public class AudioTrack extends PlayerBase
|
||||
public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
|
||||
int mode, int sessionId)
|
||||
throws IllegalArgumentException {
|
||||
this(attributes, format, bufferSizeInBytes, mode, sessionId, false /*offload*/);
|
||||
}
|
||||
|
||||
private AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
|
||||
int mode, int sessionId, boolean offload)
|
||||
throws IllegalArgumentException {
|
||||
super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
|
||||
// mState already == STATE_UNINITIALIZED
|
||||
|
||||
@@ -601,7 +625,8 @@ public class AudioTrack extends PlayerBase
|
||||
// native initialization
|
||||
int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
|
||||
sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
|
||||
mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/);
|
||||
mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/,
|
||||
offload);
|
||||
if (initResult != SUCCESS) {
|
||||
loge("Error code "+initResult+" when initializing AudioTrack.");
|
||||
return; // with mState == STATE_UNINITIALIZED
|
||||
@@ -681,7 +706,8 @@ public class AudioTrack extends PlayerBase
|
||||
0 /*mNativeBufferSizeInBytes - NA*/,
|
||||
0 /*mDataLoadMode - NA*/,
|
||||
session,
|
||||
nativeTrackInJavaObj);
|
||||
nativeTrackInJavaObj,
|
||||
false /*offload*/);
|
||||
if (initResult != SUCCESS) {
|
||||
loge("Error code "+initResult+" when initializing AudioTrack.");
|
||||
return; // with mState == STATE_UNINITIALIZED
|
||||
@@ -730,6 +756,7 @@ public class AudioTrack extends PlayerBase
|
||||
* <br>If the session ID is not specified with {@link #setSessionId(int)}, a new one will
|
||||
* be generated.
|
||||
*/
|
||||
// TODO add that offload is false by default (intended white space
|
||||
public static class Builder {
|
||||
private AudioAttributes mAttributes;
|
||||
private AudioFormat mFormat;
|
||||
@@ -737,6 +764,7 @@ public class AudioTrack extends PlayerBase
|
||||
private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
|
||||
private int mMode = MODE_STREAM;
|
||||
private int mPerformanceMode = PERFORMANCE_MODE_NONE;
|
||||
private boolean mOffload = false;
|
||||
|
||||
/**
|
||||
* Constructs a new Builder with the default values as described above.
|
||||
@@ -866,6 +894,24 @@ public class AudioTrack extends PlayerBase
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* TODO unhide (intentional whitespace
|
||||
* TODO should offload require POWER_SAVING?
|
||||
* Sets whether this track will play through the offloaded audio path.
|
||||
* When set to true, at build time, the audio format will be checked against
|
||||
* {@link AudioManager#isOffloadedPlaybackSupported(AudioFormat)} to verify the audio format
|
||||
* used by this track is supported on the device's offload path (if any).
|
||||
* <br>Offload is only supported for media audio data, and therefore require that
|
||||
* the usage be {@link AudioAttributes#USAGE_MEDIA}.
|
||||
* @param offload true to require the offload path for playback.
|
||||
* @return the same Builder instance.
|
||||
*/
|
||||
public @NonNull Builder setOffloadedPlayback(boolean offload) {
|
||||
mOffload = offload;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an {@link AudioTrack} instance initialized with all the parameters set
|
||||
* on this <code>Builder</code>.
|
||||
@@ -909,6 +955,19 @@ public class AudioTrack extends PlayerBase
|
||||
.setEncoding(AudioFormat.ENCODING_DEFAULT)
|
||||
.build();
|
||||
}
|
||||
|
||||
//TODO tie offload to PERFORMANCE_MODE_POWER_SAVING?
|
||||
if (mOffload) {
|
||||
if (mAttributes.getUsage() != AudioAttributes.USAGE_MEDIA) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Cannot create AudioTrack, offload requires USAGE_MEDIA");
|
||||
}
|
||||
if (!AudioManager.isOffloadedPlaybackSupported(mFormat)) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Cannot create AudioTrack, offload format not supported");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// If the buffer size is not specified in streaming mode,
|
||||
// use a single frame for the buffer size and let the
|
||||
@@ -918,7 +977,7 @@ public class AudioTrack extends PlayerBase
|
||||
* mFormat.getBytesPerSample(mFormat.getEncoding());
|
||||
}
|
||||
final AudioTrack track = new AudioTrack(
|
||||
mAttributes, mFormat, mBufferSizeInBytes, mMode, mSessionId);
|
||||
mAttributes, mFormat, mBufferSizeInBytes, mMode, mSessionId, mOffload);
|
||||
if (track.getState() == STATE_UNINITIALIZED) {
|
||||
// release is not necessary
|
||||
throw new UnsupportedOperationException("Cannot create AudioTrack");
|
||||
@@ -2882,6 +2941,55 @@ public class AudioTrack extends PlayerBase
|
||||
void onPeriodicNotification(AudioTrack track);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* TODO unhide (intentional white space to attract attention:
|
||||
* Abstract class to receive event notification about the stream playback.
|
||||
*/
|
||||
public abstract static class StreamEventCallback {
|
||||
// TODO rename if supported for non offload tracks
|
||||
public void onTearDown(AudioTrack track) { }
|
||||
public void onStreamPresentationEnd(AudioTrack track) { }
|
||||
public void onStreamDataRequest(AudioTrack track) { }
|
||||
}
|
||||
|
||||
private Executor mStreamEventExec;
|
||||
private StreamEventCallback mStreamEventCb;
|
||||
private final Object mStreamEventCbLock = new Object();
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* TODO unhide (intentional white space to attract attention:
|
||||
* Registers a callback for notification of stream events.
|
||||
* @param executor {@link Executor} to handle the callbacks
|
||||
* @param eventCallback the callback to receive the stream events
|
||||
*/
|
||||
public void setStreamEventCallback(@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull StreamEventCallback eventCallback) {
|
||||
if (eventCallback == null) {
|
||||
throw new IllegalArgumentException("Illegal null StreamEventCallback");
|
||||
}
|
||||
if (executor == null) {
|
||||
throw new IllegalArgumentException("Illegal null Executor for the StreamEventCallback");
|
||||
}
|
||||
synchronized (mStreamEventCbLock) {
|
||||
mStreamEventExec = executor;
|
||||
mStreamEventCb = eventCallback;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Unregisters the callback for notification of stream events, previously set
|
||||
* by {@link #setStreamEventCallback(StreamEventCallback, Executor)}.
|
||||
*/
|
||||
public void removeStreamEventCallback() {
|
||||
synchronized (mStreamEventCbLock) {
|
||||
mStreamEventExec = null;
|
||||
mStreamEventCb = null;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// Inner classes
|
||||
//--------------------
|
||||
@@ -2965,7 +3073,7 @@ public class AudioTrack extends PlayerBase
|
||||
private static void postEventFromNative(Object audiotrack_ref,
|
||||
int what, int arg1, int arg2, Object obj) {
|
||||
//logd("Event posted from the native side: event="+ what + " args="+ arg1+" "+arg2);
|
||||
AudioTrack track = (AudioTrack)((WeakReference)audiotrack_ref).get();
|
||||
final AudioTrack track = (AudioTrack)((WeakReference)audiotrack_ref).get();
|
||||
if (track == null) {
|
||||
return;
|
||||
}
|
||||
@@ -2974,6 +3082,32 @@ public class AudioTrack extends PlayerBase
|
||||
track.broadcastRoutingChange();
|
||||
return;
|
||||
}
|
||||
|
||||
if (what == NATIVE_EVENT_MORE_DATA || what == NATIVE_EVENT_NEW_IAUDIOTRACK
|
||||
|| what == NATIVE_EVENT_STREAM_END) {
|
||||
final Executor exec;
|
||||
final StreamEventCallback cb;
|
||||
synchronized (track.mStreamEventCbLock) {
|
||||
exec = track.mStreamEventExec;
|
||||
cb = track.mStreamEventCb;
|
||||
}
|
||||
if ((exec == null) || (cb == null)) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
case NATIVE_EVENT_MORE_DATA:
|
||||
exec.execute(() -> cb.onStreamDataRequest(track));
|
||||
return;
|
||||
case NATIVE_EVENT_NEW_IAUDIOTRACK:
|
||||
// TODO also release track as it's not longer usable
|
||||
exec.execute(() -> cb.onTearDown(track));
|
||||
return;
|
||||
case NATIVE_EVENT_STREAM_END:
|
||||
exec.execute(() -> cb.onStreamPresentationEnd(track));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NativePositionEventHandlerDelegate delegate = track.mEventHandlerDelegate;
|
||||
if (delegate != null) {
|
||||
Handler handler = delegate.getHandler();
|
||||
@@ -2995,7 +3129,8 @@ public class AudioTrack extends PlayerBase
|
||||
private native final int native_setup(Object /*WeakReference<AudioTrack>*/ audiotrack_this,
|
||||
Object /*AudioAttributes*/ attributes,
|
||||
int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
|
||||
int buffSizeInBytes, int mode, int[] sessionId, long nativeAudioTrack);
|
||||
int buffSizeInBytes, int mode, int[] sessionId, long nativeAudioTrack,
|
||||
boolean offload);
|
||||
|
||||
private native final void native_finalize();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user