Merge "AudioTrack: support for offloaded playback"
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index 092aaf6..51cefb9 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -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 @@
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 @@
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 @@
}
}
+// 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) {
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 7ec68ed..2be9471 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -1770,6 +1770,24 @@
(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 @@
(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},
};
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 556ac27..11011b1 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -73,6 +73,7 @@
jobject audioTrack_ref;
bool busy;
Condition cond;
+ bool isOffload;
};
// keep these values in sync with AudioTrack.java
@@ -90,6 +91,7 @@
AudioTrackJniStorage() {
mCallbackData.audioTrack_class = 0;
mCallbackData.audioTrack_ref = 0;
+ mCallbackData.isOffload = false;
}
~AudioTrackJniStorage() {
@@ -132,27 +134,34 @@
}
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 @@
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 @@
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 @@
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 @@
{"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},
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 93fc3da..46fe89a 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -240,20 +240,25 @@
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 @@
/** 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
/**
* 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 @@
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 @@
}
/** @hide */
- @IntDef({
+ @IntDef(flag = false, prefix = "ENCODING", value = {
ENCODING_DEFAULT,
ENCODING_PCM_8BIT,
ENCODING_PCM_16BIT,
@@ -1025,8 +1056,8 @@
ENCODING_E_AC3,
ENCODING_DTS,
ENCODING_DTS_HD,
- ENCODING_IEC61937
- })
+ ENCODING_IEC61937 }
+ )
@Retention(RetentionPolicy.SOURCE)
public @interface Encoding {}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 913b5e8..3b77aa1 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1329,6 +1329,21 @@
}
//====================================================================
+ // 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
/**
* Sticky broadcast intent action indicating that the Bluetooth SCO audio
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 2cd764d..b4316ba 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -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 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
/**
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index e535fdf..6add381 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -24,7 +24,9 @@
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 @@
* 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 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 @@
// 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 @@
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 @@
* <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 @@
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.
@@ -867,6 +895,24 @@
}
/**
+ * @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>.
* @return a new successfully initialized {@link AudioTrack} instance.
@@ -909,6 +955,19 @@
.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 @@
* 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 @@
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 @@
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 @@
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 @@
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();