1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.icu.util.ULocale;
22 import android.os.Bundle;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 import java.util.HashMap;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.Objects;
32 
33 
34 /**
35  * The AudioPresentation class encapsulates the information that describes an audio presentation
36  * which is available in next generation audio content.
37  *
38  * Used by {@link MediaExtractor} {@link MediaExtractor#getAudioPresentations(int)} and
39  * {@link AudioTrack} {@link AudioTrack#setPresentation(AudioPresentation)} to query available
40  * presentations and to select one, respectively.
41  *
42  * A list of available audio presentations in a media source can be queried using
43  * {@link MediaExtractor#getAudioPresentations(int)}. This list can be presented to a user for
44  * selection.
45  * An AudioPresentation can be passed to an offloaded audio decoder via
46  * {@link AudioTrack#setPresentation(AudioPresentation)} to request decoding of the selected
47  * presentation. An audio stream may contain multiple presentations that differ by language,
48  * accessibility, end point mastering and dialogue enhancement. An audio presentation may also have
49  * a set of description labels in different languages to help the user to make an informed
50  * selection.
51  *
52  * Applications that parse media streams and extract presentation information on their own
53  * can create instances of AudioPresentation by using {@link AudioPresentation.Builder} class.
54  */
55 public final class AudioPresentation implements Parcelable {
56     private final int mPresentationId;
57     private final int mProgramId;
58     private final ULocale mLanguage;
59 
60     /**
61      * The ContentClassifier int definitions represent the AudioPresentation content
62      * classifier (as per TS 103 190-1 v1.2.1 4.3.3.8.1)
63      * @hide
64      */
65     @IntDef(
66         value = {
67         CONTENT_UNKNOWN,
68         CONTENT_MAIN,
69         CONTENT_MUSIC_AND_EFFECTS,
70         CONTENT_VISUALLY_IMPAIRED,
71         CONTENT_HEARING_IMPAIRED,
72         CONTENT_DIALOG,
73         CONTENT_COMMENTARY,
74         CONTENT_EMERGENCY,
75         CONTENT_VOICEOVER,
76     })
77     @Retention(RetentionPolicy.SOURCE)
78     public @interface ContentClassifier {}
79 
80     /**
81      * Audio presentation classifier: Unknown.
82      */
83     public static final int CONTENT_UNKNOWN                 = -1;
84     /**
85      * Audio presentation classifier: Complete main.
86      */
87     public static final int CONTENT_MAIN                    = 0;
88     /**
89      * Audio presentation content classifier: Music and effects.
90      */
91     public static final int CONTENT_MUSIC_AND_EFFECTS       = 1;
92     /**
93      * Audio presentation content classifier: Visually impaired.
94      */
95     public static final int CONTENT_VISUALLY_IMPAIRED       = 2;
96     /**
97      * Audio presentation content classifier: Hearing impaired.
98      */
99     public static final int CONTENT_HEARING_IMPAIRED        = 3;
100     /**
101      * Audio presentation content classifier: Dialog.
102      */
103     public static final int CONTENT_DIALOG                  = 4;
104     /**
105      * Audio presentation content classifier: Commentary.
106      */
107     public static final int CONTENT_COMMENTARY              = 5;
108     /**
109      * Audio presentation content classifier: Emergency.
110      */
111     public static final int CONTENT_EMERGENCY               = 6;
112     /**
113      * Audio presentation content classifier: Voice over.
114      */
115     public static final int CONTENT_VOICEOVER               = 7;
116 
117     /** @hide */
118     @IntDef(
119         value = {
120             MASTERING_NOT_INDICATED,
121             MASTERED_FOR_STEREO,
122             MASTERED_FOR_SURROUND,
123             MASTERED_FOR_3D,
124             MASTERED_FOR_HEADPHONE,
125     })
126     @Retention(RetentionPolicy.SOURCE)
127     public @interface MasteringIndicationType {}
128     private final @MasteringIndicationType int mMasteringIndication;
129     private final boolean mAudioDescriptionAvailable;
130     private final boolean mSpokenSubtitlesAvailable;
131     private final boolean mDialogueEnhancementAvailable;
132     private final HashMap<ULocale, String> mLabels;
133 
134     /**
135      * No preferred reproduction channel layout.
136      *
137      * @see Builder#setMasteringIndication(int)
138      */
139     public static final int MASTERING_NOT_INDICATED         = 0;
140     /**
141      * Stereo speaker layout.
142      *
143      * @see Builder#setMasteringIndication(int)
144      */
145     public static final int MASTERED_FOR_STEREO             = 1;
146     /**
147      * Two-dimensional (e.g. 5.1) speaker layout.
148      *
149      * @see Builder#setMasteringIndication(int)
150      */
151     public static final int MASTERED_FOR_SURROUND           = 2;
152     /**
153      * Three-dimensional (e.g. 5.1.2) speaker layout.
154      *
155      * @see Builder#setMasteringIndication(int)
156      */
157     public static final int MASTERED_FOR_3D                 = 3;
158     /**
159      * Prerendered for headphone playback.
160      *
161      * @see Builder#setMasteringIndication(int)
162      */
163     public static final int MASTERED_FOR_HEADPHONE          = 4;
164 
165     /**
166      * Unknown audio presentation ID, this indicates audio presentation ID is not selected.
167      */
168     public static final int PRESENTATION_ID_UNKNOWN = -1;
169 
170     /**
171      * Unknown audio program ID, this indicates audio program ID is not selected.
172      */
173     public static final int PROGRAM_ID_UNKNOWN = -1;
174 
175     /**
176      * This allows an application developer to construct an AudioPresentation object with all the
177      * parameters.
178      * The IDs are all that is required for an
179      * {@link AudioTrack#setPresentation(AudioPresentation)} to be successful.
180      * The rest of the metadata is informative only so as to distinguish features
181      * of different presentations.
182      * @param presentationId Presentation ID to be decoded by a next generation audio decoder.
183      * @param programId Program ID to be decoded by a next generation audio decoder.
184      * @param language Locale corresponding to ISO 639-1/639-2 language code.
185      * @param masteringIndication One of {@link AudioPresentation#MASTERING_NOT_INDICATED},
186      *     {@link AudioPresentation#MASTERED_FOR_STEREO},
187      *     {@link AudioPresentation#MASTERED_FOR_SURROUND},
188      *     {@link AudioPresentation#MASTERED_FOR_3D},
189      *     {@link AudioPresentation#MASTERED_FOR_HEADPHONE}.
190      * @param audioDescriptionAvailable Audio description for the visually impaired.
191      * @param spokenSubtitlesAvailable Spoken subtitles for the visually impaired.
192      * @param dialogueEnhancementAvailable Dialogue enhancement.
193      * @param labels Text label indexed by its locale corresponding to the language code.
194      */
AudioPresentation(int presentationId, int programId, @NonNull ULocale language, @MasteringIndicationType int masteringIndication, boolean audioDescriptionAvailable, boolean spokenSubtitlesAvailable, boolean dialogueEnhancementAvailable, @NonNull Map<ULocale, String> labels)195     private AudioPresentation(int presentationId,
196                              int programId,
197                              @NonNull ULocale language,
198                              @MasteringIndicationType int masteringIndication,
199                              boolean audioDescriptionAvailable,
200                              boolean spokenSubtitlesAvailable,
201                              boolean dialogueEnhancementAvailable,
202                              @NonNull Map<ULocale, String> labels) {
203         mPresentationId = presentationId;
204         mProgramId = programId;
205         mLanguage = language;
206         mMasteringIndication = masteringIndication;
207         mAudioDescriptionAvailable = audioDescriptionAvailable;
208         mSpokenSubtitlesAvailable = spokenSubtitlesAvailable;
209         mDialogueEnhancementAvailable = dialogueEnhancementAvailable;
210         mLabels = new HashMap<ULocale, String>(labels);
211     }
212 
AudioPresentation(@onNull Parcel in)213     private AudioPresentation(@NonNull Parcel in) {
214         mPresentationId = in.readInt();
215         mProgramId = in.readInt();
216         mLanguage = in.readSerializable(ULocale.class.getClassLoader(), ULocale.class);
217         mMasteringIndication = in.readInt();
218         mAudioDescriptionAvailable = in.readBoolean();
219         mSpokenSubtitlesAvailable = in.readBoolean();
220         mDialogueEnhancementAvailable = in.readBoolean();
221         mLabels = in.readSerializable(HashMap.class.getClassLoader(), HashMap.class);
222     }
223 
224     /**
225      * Returns presentation ID used by the framework to select an audio presentation rendered by a
226      * decoder. Presentation ID is typically sequential, but does not have to be.
227      */
getPresentationId()228     public int getPresentationId() {
229         return mPresentationId;
230     }
231 
232     /**
233      * Returns program ID used by the framework to select an audio presentation rendered by a
234      * decoder. Program ID can be used to further uniquely identify the presentation to a decoder.
235      */
getProgramId()236     public int getProgramId() {
237         return mProgramId;
238     }
239 
240     /**
241      * @return a map of available text labels for this presentation. Each label is indexed by its
242      * locale corresponding to the language code as specified by ISO 639-2. Either ISO 639-2/B
243      * or ISO 639-2/T could be used.
244      */
getLabels()245     public Map<Locale, String> getLabels() {
246         Map<Locale, String> localeLabels = new HashMap<Locale, String>(mLabels.size());
247         for (Map.Entry<ULocale, String> entry : mLabels.entrySet()) {
248             localeLabels.put(entry.getKey().toLocale(), entry.getValue());
249         }
250         return localeLabels;
251     }
252 
getULabels()253     private Map<ULocale, String> getULabels() {
254         return mLabels;
255     }
256 
257     /**
258      * @return the locale corresponding to audio presentation's ISO 639-1/639-2 language code.
259      */
getLocale()260     public Locale getLocale() {
261         return mLanguage.toLocale();
262     }
263 
getULocale()264     private ULocale getULocale() {
265         return mLanguage;
266     }
267 
268     /**
269      * @return the mastering indication of the audio presentation.
270      * See {@link AudioPresentation#MASTERING_NOT_INDICATED},
271      *     {@link AudioPresentation#MASTERED_FOR_STEREO},
272      *     {@link AudioPresentation#MASTERED_FOR_SURROUND},
273      *     {@link AudioPresentation#MASTERED_FOR_3D},
274      *     {@link AudioPresentation#MASTERED_FOR_HEADPHONE}
275      */
276     @MasteringIndicationType
getMasteringIndication()277     public int getMasteringIndication() {
278         return mMasteringIndication;
279     }
280 
281     /**
282      * Indicates whether an audio description for the visually impaired is available.
283      * @return {@code true} if audio description is available.
284      */
hasAudioDescription()285     public boolean hasAudioDescription() {
286         return mAudioDescriptionAvailable;
287     }
288 
289     /**
290      * Indicates whether spoken subtitles for the visually impaired are available.
291      * @return {@code true} if spoken subtitles are available.
292      */
hasSpokenSubtitles()293     public boolean hasSpokenSubtitles() {
294         return mSpokenSubtitlesAvailable;
295     }
296 
297     /**
298      * Indicates whether dialogue enhancement is available.
299      * @return {@code true} if dialogue enhancement is available.
300      */
hasDialogueEnhancement()301     public boolean hasDialogueEnhancement() {
302         return mDialogueEnhancementAvailable;
303     }
304 
305     @Override
equals(Object o)306     public boolean equals(Object o) {
307         if (this == o) {
308             return true;
309         }
310         if (!(o instanceof AudioPresentation)) {
311             return false;
312         }
313         AudioPresentation obj = (AudioPresentation) o;
314         return mPresentationId == obj.getPresentationId()
315                 && mProgramId == obj.getProgramId()
316                 && mLanguage.equals(obj.getULocale())
317                 && mMasteringIndication == obj.getMasteringIndication()
318                 && mAudioDescriptionAvailable == obj.hasAudioDescription()
319                 && mSpokenSubtitlesAvailable == obj.hasSpokenSubtitles()
320                 && mDialogueEnhancementAvailable == obj.hasDialogueEnhancement()
321                 && mLabels.equals(obj.getULabels());
322     }
323 
324     @Override
hashCode()325     public int hashCode() {
326         return Objects.hash(mPresentationId,
327                 mProgramId,
328                 mLanguage.hashCode(),
329                 mMasteringIndication,
330                 mAudioDescriptionAvailable,
331                 mSpokenSubtitlesAvailable,
332                 mDialogueEnhancementAvailable,
333                 mLabels.hashCode());
334     }
335 
336     @Override
toString()337     public String toString() {
338         StringBuilder sb = new StringBuilder();
339         sb.append(getClass().getSimpleName() + " ");
340         sb.append("{ presentation id=" + mPresentationId);
341         sb.append(", program id=" + mProgramId);
342         sb.append(", language=" + mLanguage);
343         sb.append(", labels=" + mLabels);
344         sb.append(", mastering indication=" + mMasteringIndication);
345         sb.append(", audio description=" + mAudioDescriptionAvailable);
346         sb.append(", spoken subtitles=" + mSpokenSubtitlesAvailable);
347         sb.append(", dialogue enhancement=" + mDialogueEnhancementAvailable);
348         sb.append(" }");
349         return sb.toString();
350     }
351 
352     /**
353      * A builder class for creating {@link AudioPresentation} objects.
354      */
355     public static final class Builder {
356         private final int mPresentationId;
357         private int mProgramId = PROGRAM_ID_UNKNOWN;
358         private ULocale mLanguage = new ULocale("");
359         private int mMasteringIndication = MASTERING_NOT_INDICATED;
360         private boolean mAudioDescriptionAvailable = false;
361         private boolean mSpokenSubtitlesAvailable = false;
362         private boolean mDialogueEnhancementAvailable = false;
363         private HashMap<ULocale, String> mLabels = new HashMap<ULocale, String>();
364 
365         /**
366          * Create a {@link Builder}. Any field that should be included in the
367          * {@link AudioPresentation} must be added.
368          *
369          * @param presentationId The presentation ID of this audio presentation.
370          */
Builder(int presentationId)371         public Builder(int presentationId) {
372             mPresentationId = presentationId;
373         }
374         /**
375          * Sets the ProgramId to which this audio presentation refers.
376          *
377          * @param programId The program ID to be decoded.
378          */
setProgramId(int programId)379         public @NonNull Builder setProgramId(int programId) {
380             mProgramId = programId;
381             return this;
382         }
383         /**
384          * Sets the language information of the audio presentation.
385          *
386          * @param language Locale corresponding to ISO 639-1/639-2 language code.
387          */
setLocale(@onNull ULocale language)388         public @NonNull Builder setLocale(@NonNull ULocale language) {
389             mLanguage = language;
390             return this;
391         }
392 
393         /**
394          * Sets the mastering indication.
395          *
396          * @param masteringIndication Input to set mastering indication.
397          * @throws IllegalArgumentException if the mastering indication is not any of
398          * {@link AudioPresentation#MASTERING_NOT_INDICATED},
399          * {@link AudioPresentation#MASTERED_FOR_STEREO},
400          * {@link AudioPresentation#MASTERED_FOR_SURROUND},
401          * {@link AudioPresentation#MASTERED_FOR_3D},
402          * and {@link AudioPresentation#MASTERED_FOR_HEADPHONE}
403          */
setMasteringIndication( @asteringIndicationType int masteringIndication)404         public @NonNull Builder setMasteringIndication(
405                 @MasteringIndicationType int masteringIndication) {
406             if (masteringIndication != MASTERING_NOT_INDICATED
407                     && masteringIndication != MASTERED_FOR_STEREO
408                     && masteringIndication != MASTERED_FOR_SURROUND
409                     && masteringIndication != MASTERED_FOR_3D
410                     && masteringIndication != MASTERED_FOR_HEADPHONE) {
411                 throw new IllegalArgumentException("Unknown mastering indication: "
412                                                         + masteringIndication);
413             }
414             mMasteringIndication = masteringIndication;
415             return this;
416         }
417 
418         /**
419          * Sets locale / text label pairs describing the presentation.
420          *
421          * @param labels Text label indexed by its locale corresponding to the language code.
422          */
setLabels(@onNull Map<ULocale, CharSequence> labels)423         public @NonNull Builder setLabels(@NonNull Map<ULocale, CharSequence> labels) {
424             mLabels.clear();
425             for (Map.Entry<ULocale, CharSequence> entry : labels.entrySet()) {
426                 mLabels.put(entry.getKey(), entry.getValue().toString());
427             }
428             return this;
429         }
430 
431         /**
432          * Indicate whether the presentation contains audio description for the visually impaired.
433          *
434          * @param audioDescriptionAvailable Audio description for the visually impaired.
435          */
setHasAudioDescription(boolean audioDescriptionAvailable)436         public @NonNull Builder setHasAudioDescription(boolean audioDescriptionAvailable) {
437             mAudioDescriptionAvailable = audioDescriptionAvailable;
438             return this;
439         }
440 
441         /**
442          * Indicate whether the presentation contains spoken subtitles for the visually impaired.
443          *
444          * @param spokenSubtitlesAvailable Spoken subtitles for the visually impaired.
445          */
setHasSpokenSubtitles(boolean spokenSubtitlesAvailable)446         public @NonNull Builder setHasSpokenSubtitles(boolean spokenSubtitlesAvailable) {
447             mSpokenSubtitlesAvailable = spokenSubtitlesAvailable;
448             return this;
449         }
450 
451         /**
452          * Indicate whether the presentation supports dialogue enhancement.
453          *
454          * @param dialogueEnhancementAvailable Dialogue enhancement.
455          */
setHasDialogueEnhancement(boolean dialogueEnhancementAvailable)456         public @NonNull Builder setHasDialogueEnhancement(boolean dialogueEnhancementAvailable) {
457             mDialogueEnhancementAvailable = dialogueEnhancementAvailable;
458             return this;
459         }
460 
461         /**
462          * Creates a {@link AudioPresentation} instance with the specified fields.
463          *
464          * @return The new {@link AudioPresentation} instance
465          */
build()466         public @NonNull AudioPresentation build() {
467             return new AudioPresentation(mPresentationId, mProgramId,
468                                            mLanguage, mMasteringIndication,
469                                            mAudioDescriptionAvailable, mSpokenSubtitlesAvailable,
470                                            mDialogueEnhancementAvailable, mLabels);
471         }
472     }
473 
474     @Override
describeContents()475     public int describeContents() {
476         return 0;
477     }
478 
479     @Override
writeToParcel(@onNull Parcel dest, int flags)480     public void writeToParcel(@NonNull Parcel dest, int flags) {
481         dest.writeInt(getPresentationId());
482         dest.writeInt(getProgramId());
483         dest.writeSerializable(getULocale());
484         dest.writeInt(getMasteringIndication());
485         dest.writeBoolean(hasAudioDescription());
486         dest.writeBoolean(hasSpokenSubtitles());
487         dest.writeBoolean(hasDialogueEnhancement());
488         dest.writeSerializable(mLabels);
489     }
490 
491     @NonNull
492     public static final Parcelable.Creator<AudioPresentation> CREATOR =
493             new Parcelable.Creator<AudioPresentation>() {
494             @Override
495             public AudioPresentation createFromParcel(@NonNull Parcel in) {
496                 return new AudioPresentation(in);
497             }
498 
499             @Override
500             public AudioPresentation[] newArray(int size) {
501                 return new AudioPresentation[size];
502             }
503     };
504 }
505