Merge "Add audio presentation interface to Java API"
diff --git a/api/current.txt b/api/current.txt
index bbe3c49..d415f52 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22150,6 +22150,20 @@
field public static final android.os.Parcelable.Creator<android.media.AudioPlaybackConfiguration> CREATOR;
}
+ public final class AudioPresentation {
+ method public java.util.Map<java.util.Locale, java.lang.String> getLabels();
+ method public java.util.Locale getLocale();
+ method public int getMasteringIndication();
+ method public boolean hasAudioDescription();
+ method public boolean hasDialogueEnhancement();
+ method public boolean hasSpokenSubtitles();
+ field public static final int MASTERED_FOR_3D = 3; // 0x3
+ field public static final int MASTERED_FOR_HEADPHONE = 4; // 0x4
+ field public static final int MASTERED_FOR_STEREO = 1; // 0x1
+ field public static final int MASTERED_FOR_SURROUND = 2; // 0x2
+ field public static final int MASTERING_NOT_INDICATED = 0; // 0x0
+ }
+
public class AudioRecord implements android.media.AudioRouting {
ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
@@ -22316,6 +22330,7 @@
method public int setPlaybackRate(int);
method public int setPositionNotificationPeriod(int);
method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
+ method public int setPresentation(android.media.AudioPresentation);
method protected deprecated void setState(int);
method public deprecated int setStereoVolume(float, float);
method public void setStreamEventCallback(java.util.concurrent.Executor, android.media.AudioTrack.StreamEventCallback);
@@ -23472,6 +23487,7 @@
ctor public MediaExtractor();
method public boolean advance();
method protected void finalize();
+ method public java.util.List<android.media.AudioPresentation> getAudioPresentations(int);
method public long getCachedDuration();
method public android.media.MediaExtractor.CasInfo getCasInfo(int);
method public android.media.DrmInitData getDrmInitData();
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index afbc579..61a22c1 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -1262,6 +1262,18 @@
return VolumeShaperHelper::convertStateToJobject(env, gVolumeShaperFields, state);
}
+static int android_media_AudioTrack_setPresentation(
+ JNIEnv *env, jobject thiz, jint presentationId, jint programId) {
+ sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+ if (lpTrack == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "AudioTrack not initialized");
+ return (jint)AUDIO_JAVA_ERROR;
+ }
+
+ return (jint)lpTrack->selectPresentation((int)presentationId, (int)programId);
+}
+
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
@@ -1333,6 +1345,7 @@
{"native_getVolumeShaperState",
"(I)Landroid/media/VolumeShaper$State;",
(void *)android_media_AudioTrack_get_volume_shaper_state},
+ {"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation},
};
diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java
new file mode 100644
index 0000000..4652c18
--- /dev/null
+++ b/media/java/android/media/AudioPresentation.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 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;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+
+/**
+ * The AudioPresentation class encapsulates the information that describes an audio presentation
+ * which is available in next generation audio content.
+ *
+ * Used by {@link MediaExtractor} {@link MediaExtractor#getAudioPresentations(int)} and
+ * {@link AudioTrack} {@link AudioTrack#setPresentation(AudioPresentation)} to query available
+ * presentations and to select one.
+ *
+ * A list of available audio presentations in a media source can be queried using
+ * {@link MediaExtractor#getAudioPresentations(int)}. This list can be presented to a user for
+ * selection.
+ * An AudioPresentation can be passed to an offloaded audio decoder via
+ * {@link AudioTrack#setPresentation(AudioPresentation)} to request decoding of the selected
+ * presentation. An audio stream may contain multiple presentations that differ by language,
+ * accessibility, end point mastering and dialogue enhancement. An audio presentation may also have
+ * a set of description labels in different languages to help the user to make an informed
+ * selection.
+ */
+public final class AudioPresentation {
+ private final int mPresentationId;
+ private final int mProgramId;
+ private final Map<String, String> mLabels;
+ private final String mLanguage;
+
+ /** @hide */
+ @IntDef(
+ value = {
+ MASTERING_NOT_INDICATED,
+ MASTERED_FOR_STEREO,
+ MASTERED_FOR_SURROUND,
+ MASTERED_FOR_3D,
+ MASTERED_FOR_HEADPHONE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MasteringIndicationType {}
+
+ private final @MasteringIndicationType int mMasteringIndication;
+ private final boolean mAudioDescriptionAvailable;
+ private final boolean mSpokenSubtitlesAvailable;
+ private final boolean mDialogueEnhancementAvailable;
+
+ /**
+ * No preferred reproduction channel layout.
+ */
+ public static final int MASTERING_NOT_INDICATED = 0;
+ /**
+ * Stereo speaker layout.
+ */
+ public static final int MASTERED_FOR_STEREO = 1;
+ /**
+ * Two-dimensional (e.g. 5.1) speaker layout.
+ */
+ public static final int MASTERED_FOR_SURROUND = 2;
+ /**
+ * Three-dimensional (e.g. 5.1.2) speaker layout.
+ */
+ public static final int MASTERED_FOR_3D = 3;
+ /**
+ * Prerendered for headphone playback.
+ */
+ public static final int MASTERED_FOR_HEADPHONE = 4;
+
+ AudioPresentation(int presentationId,
+ int programId,
+ Map<String, String> labels,
+ String language,
+ @MasteringIndicationType int masteringIndication,
+ boolean audioDescriptionAvailable,
+ boolean spokenSubtitlesAvailable,
+ boolean dialogueEnhancementAvailable) {
+ this.mPresentationId = presentationId;
+ this.mProgramId = programId;
+ this.mLanguage = language;
+ this.mMasteringIndication = masteringIndication;
+ this.mAudioDescriptionAvailable = audioDescriptionAvailable;
+ this.mSpokenSubtitlesAvailable = spokenSubtitlesAvailable;
+ this.mDialogueEnhancementAvailable = dialogueEnhancementAvailable;
+
+ this.mLabels = new HashMap<String, String>(labels);
+ }
+
+ /**
+ * The framework uses this presentation id to select an audio presentation rendered by a
+ * decoder. Presentation id is typically sequential, but does not have to be.
+ * @hide
+ */
+ public int getPresentationId() {
+ return mPresentationId;
+ }
+
+ /**
+ * The framework uses this program id to select an audio presentation rendered by a decoder.
+ * Program id can be used to further uniquely identify the presentation to a decoder.
+ * @hide
+ */
+ public int getProgramId() {
+ return mProgramId;
+ }
+
+ /**
+ * @return a map of available text labels for this presentation. Each label is indexed by its
+ * locale corresponding to the language code as specified by ISO 639-2 [42]. Either ISO 639-2/B
+ * or ISO 639-2/T could be used.
+ */
+ public Map<Locale, String> getLabels() {
+ Map<Locale, String> localeLabels = new HashMap<>();
+ for (Map.Entry<String, String> entry : mLabels.entrySet()) {
+ localeLabels.put(new Locale(entry.getKey()), entry.getValue());
+ }
+ return localeLabels;
+ }
+
+ /**
+ * @return the locale corresponding to audio presentation's ISO 639-1/639-2 language code.
+ */
+ public Locale getLocale() {
+ return new Locale(mLanguage);
+ }
+
+ /**
+ * @return the mastering indication of the audio presentation.
+ * See {@link #MASTERING_NOT_INDICATED}, {@link #MASTERED_FOR_STEREO},
+ * {@link #MASTERED_FOR_SURROUND}, {@link #MASTERED_FOR_3D}, {@link #MASTERED_FOR_HEADPHONE}
+ */
+ @MasteringIndicationType
+ public int getMasteringIndication() {
+ return mMasteringIndication;
+ }
+
+ /**
+ * Indicates whether an audio description for the visually impaired is available.
+ * @return {@code true} if audio description is available.
+ */
+ public boolean hasAudioDescription() {
+ return mAudioDescriptionAvailable;
+ }
+
+ /**
+ * Indicates whether spoken subtitles for the visually impaired are available.
+ * @return {@code true} if spoken subtitles are available.
+ */
+ public boolean hasSpokenSubtitles() {
+ return mSpokenSubtitlesAvailable;
+ }
+
+ /**
+ * Indicates whether dialogue enhancement is available.
+ * @return {@code true} if dialogue enhancement is available.
+ */
+ public boolean hasDialogueEnhancement() {
+ return mDialogueEnhancementAvailable;
+ }
+}
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 4e9ce8e..8e822a5 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -2008,6 +2008,25 @@
}
/**
+ * Sets the audio presentation.
+ * If the audio presentation is invalid then {@link #ERROR_BAD_VALUE} will be returned.
+ * If a multi-stream decoder (MSD) is not present, or the format does not support
+ * multiple presentations, then {@link #ERROR_INVALID_OPERATION} will be returned.
+ * @param presentation see {@link AudioPresentation}. In particular, id should be set.
+ * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
+ * {@link #ERROR_INVALID_OPERATION}
+ * @throws IllegalArgumentException if the audio presentation is null.
+ * @throws IllegalStateException if track is not initialized.
+ */
+ public int setPresentation(@NonNull AudioPresentation presentation) {
+ if (presentation == null) {
+ throw new IllegalArgumentException("audio presentation is null");
+ }
+ return native_setPresentation(presentation.getPresentationId(),
+ presentation.getProgramId());
+ }
+
+ /**
* Sets the initialization state of the instance. This method was originally intended to be used
* in an AudioTrack subclass constructor to set a subclass-specific post-initialization state.
* However, subclasses of AudioTrack are no longer recommended, so this method is obsolete.
@@ -3245,6 +3264,7 @@
@NonNull VolumeShaper.Operation operation);
private native @Nullable VolumeShaper.State native_getVolumeShaperState(int id);
+ private native final int native_setPresentation(int presentationId, int programId);
//---------------------------------------------------------
// Utility methods
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 174d6a3..4919eeb 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -22,6 +22,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
+import android.media.AudioPresentation;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.media.MediaHTTPService;
@@ -40,6 +41,7 @@
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -396,6 +398,17 @@
}
/**
+ * Get the list of available audio presentations for the track.
+ * @param trackIndex index of the track.
+ * @return a list of available audio presentations for a given valid audio track index.
+ * The list will be empty if the source does not contain any audio presentations.
+ */
+ @NonNull
+ public List<AudioPresentation> getAudioPresentations(int trackIndex) {
+ return new ArrayList<AudioPresentation>();
+ }
+
+ /**
* Get the PSSH info if present.
* @return a map of uuid-to-bytes, with the uuid specifying
* the crypto scheme, and the bytes being the data specific to that scheme.
diff --git a/media/jni/android_media_AudioPresentation.h b/media/jni/android_media_AudioPresentation.h
new file mode 100644
index 0000000..71b8dac
--- /dev/null
+++ b/media/jni/android_media_AudioPresentation.h
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_MEDIA_AUDIO_PRESENTATION_H_
+#define _ANDROID_MEDIA_AUDIO_PRESENTATION_H_
+
+#include "jni.h"
+
+#include <media/AudioPresentationInfo.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include <nativehelper/ScopedLocalRef.h>
+
+namespace android {
+
+struct JAudioPresentationInfo {
+ struct fields_t {
+ jclass clazz;
+ jmethodID constructID;
+
+ // list parameters
+ jclass listclazz;
+ jmethodID listConstructId;
+ jmethodID listAddId;
+
+ void init(JNIEnv *env) {
+ jclass lclazz = env->FindClass("android/media/AudioPresentation");
+ if (lclazz == NULL) {
+ return;
+ }
+
+ clazz = (jclass)env->NewGlobalRef(lclazz);
+ if (clazz == NULL) {
+ return;
+ }
+
+ constructID = env->GetMethodID(clazz, "<init>",
+ "(IILjava/util/Map;Ljava/lang/String;IZZZ)V");
+ env->DeleteLocalRef(lclazz);
+
+ // list objects
+ jclass llistclazz = env->FindClass("java/util/ArrayList");
+ CHECK(llistclazz != NULL);
+ listclazz = static_cast<jclass>(env->NewGlobalRef(llistclazz));
+ CHECK(listclazz != NULL);
+ listConstructId = env->GetMethodID(listclazz, "<init>", "()V");
+ CHECK(listConstructId != NULL);
+ listAddId = env->GetMethodID(listclazz, "add", "(Ljava/lang/Object;)Z");
+ CHECK(listAddId != NULL);
+ env->DeleteLocalRef(llistclazz);
+ }
+
+ void exit(JNIEnv *env) {
+ env->DeleteGlobalRef(clazz);
+ clazz = NULL;
+ env->DeleteGlobalRef(listclazz);
+ listclazz = NULL;
+ }
+ };
+
+ static status_t ConvertMessageToMap(JNIEnv *env, const sp<AMessage> &msg, jobject *map) {
+ ScopedLocalRef<jclass> hashMapClazz(env, env->FindClass("java/util/HashMap"));
+
+ if (hashMapClazz.get() == NULL) {
+ return -EINVAL;
+ }
+ jmethodID hashMapConstructID =
+ env->GetMethodID(hashMapClazz.get(), "<init>", "()V");
+
+ if (hashMapConstructID == NULL) {
+ return -EINVAL;
+ }
+ jmethodID hashMapPutID =
+ env->GetMethodID(
+ hashMapClazz.get(),
+ "put",
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+
+ if (hashMapPutID == NULL) {
+ return -EINVAL;
+ }
+
+ jobject hashMap = env->NewObject(hashMapClazz.get(), hashMapConstructID);
+
+ for (size_t i = 0; i < msg->countEntries(); ++i) {
+ AMessage::Type valueType;
+ const char *key = msg->getEntryNameAt(i, &valueType);
+
+ if (!strncmp(key, "android._", 9)) {
+ // don't expose private keys (starting with android._)
+ continue;
+ }
+
+ jobject valueObj = NULL;
+
+ AString val;
+ CHECK(msg->findString(key, &val));
+
+ valueObj = env->NewStringUTF(val.c_str());
+
+ if (valueObj != NULL) {
+ jstring keyObj = env->NewStringUTF(key);
+
+ env->CallObjectMethod(hashMap, hashMapPutID, keyObj, valueObj);
+
+ env->DeleteLocalRef(keyObj); keyObj = NULL;
+ env->DeleteLocalRef(valueObj); valueObj = NULL;
+ }
+ }
+
+ *map = hashMap;
+
+ return OK;
+ }
+
+ jobject asJobject(JNIEnv *env, const fields_t& fields, const AudioPresentationInfo &info) {
+ jobject list = env->NewObject(fields.listclazz, fields.listConstructId);
+
+ for (size_t i = 0; i < info.countPresentations(); ++i) {
+ const sp<AudioPresentation> &ap = info.getPresentation(i);
+ jobject jLabelObject;
+
+ sp<AMessage> labelMessage = new AMessage();
+ for (size_t i = 0; i < ap->mLabels.size(); ++i) {
+ labelMessage->setString(ap->mLabels.keyAt(i).string(),
+ ap->mLabels.valueAt(i).string());
+ }
+ if (ConvertMessageToMap(env, labelMessage, &jLabelObject) != OK) {
+ return NULL;
+ }
+ jstring jLanguage = env->NewStringUTF(ap->mLanguage.string());
+
+ jobject jValueObj = env->NewObject(fields.clazz, fields.constructID,
+ static_cast<jint>(ap->mPresentationId),
+ static_cast<jint>(ap->mProgramId),
+ jLabelObject,
+ jLanguage,
+ static_cast<jint>(ap->mMasteringIndication),
+ static_cast<jboolean>((ap->mAudioDescriptionAvailable == 1) ?
+ 1 : 0),
+ static_cast<jboolean>((ap->mSpokenSubtitlesAvailable == 1) ?
+ 1 : 0),
+ static_cast<jboolean>((ap->mDialogueEnhancementAvailable == 1) ?
+ 1 : 0));
+ if (jValueObj == NULL) {
+ env->DeleteLocalRef(jLanguage); jLanguage = NULL;
+ return NULL;
+ }
+
+ env->CallBooleanMethod(list, fields.listAddId, jValueObj);
+ env->DeleteLocalRef(jValueObj); jValueObj = NULL;
+ env->DeleteLocalRef(jLanguage); jLanguage = NULL;
+ }
+ return list;
+ }
+};
+} // namespace android
+
+#endif // _ANDROID_MEDIA_AUDIO_PRESENTATION_H_