diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h index 092aaf62bbc0a..51cefb9accb8e 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 @@ 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) { diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 7ec68edddf52e..2be947158fc5a 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -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}, }; diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 556ac27dfe9ed..11011b1d7c164 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -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 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}, diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index 93fc3da545504..46fe89a74ce57 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -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 {} diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 913b5e8411128..3b77aa1e1bd45 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -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 /** diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 2cd764df2c547..b4316ba9b1df4 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 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 /** diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index e535fdf53d018..6add381fc8855 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -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(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 *
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). + *
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 Builder. @@ -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_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();