Add support for Dolby Atmos

Issue: #2465

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=176341309
This commit is contained in:
andrewlewis 2017-11-20 04:32:56 -08:00 committed by Oliver Woodman
parent 75acfc7957
commit c4fe0e6482
6 changed files with 237 additions and 23 deletions

View File

@ -12,6 +12,8 @@
* New Cast extension: Simplifies toggling between local and Cast playbacks.
* Support 32-bit PCM float output from `DefaultAudioSink`, and add an option to
use this with `FfmpegAudioRenderer`.
* Support extraction and decoding of Dolby Atmos
([#2465](https://github.com/google/ExoPlayer/issues/2465)).
### 2.6.0 ###

View File

@ -15,6 +15,10 @@
*/
package com.google.android.exoplayer2.audio;
import static com.google.android.exoplayer2.audio.Ac3Util.Ac3SyncFrameInfo.STREAM_TYPE_TYPE0;
import static com.google.android.exoplayer2.audio.Ac3Util.Ac3SyncFrameInfo.STREAM_TYPE_TYPE1;
import static com.google.android.exoplayer2.audio.Ac3Util.Ac3SyncFrameInfo.STREAM_TYPE_UNDEFINED;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData;
@ -181,7 +185,14 @@ public final class Ac3Util {
channelCount += 2;
}
}
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, null, Format.NO_VALUE,
String mimeType = MimeTypes.AUDIO_E_AC3;
if (data.bytesLeft() > 0) {
nextByte = data.readUnsignedByte();
if ((nextByte & 0x01) != 0) { // flag_ec3_extension_type_a
mimeType = MimeTypes.AUDIO_ATMOS;
}
}
return Format.createAudioSampleFormat(trackId, mimeType, null, Format.NO_VALUE,
Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, language);
}
@ -198,29 +209,176 @@ public final class Ac3Util {
boolean isEac3 = data.readBits(5) == 16;
data.setPosition(initialPosition);
String mimeType;
int streamType = Ac3SyncFrameInfo.STREAM_TYPE_UNDEFINED;
int streamType = STREAM_TYPE_UNDEFINED;
int sampleRate;
int acmod;
int frameSize;
int sampleCount;
boolean lfeon;
int channelCount;
if (isEac3) {
mimeType = MimeTypes.AUDIO_E_AC3;
// Syntax from ETSI TS 102 366 V1.2.1 subsections E.1.2.1 and E.1.2.2.
data.skipBits(16); // syncword
streamType = data.readBits(2);
data.skipBits(3); // substreamid
frameSize = (data.readBits(11) + 1) * 2;
int fscod = data.readBits(2);
int audioBlocks;
int numblkscod;
if (fscod == 3) {
numblkscod = 3;
sampleRate = SAMPLE_RATE_BY_FSCOD2[data.readBits(2)];
audioBlocks = 6;
} else {
int numblkscod = data.readBits(2);
numblkscod = data.readBits(2);
audioBlocks = BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[numblkscod];
sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
}
sampleCount = AUDIO_SAMPLES_PER_AUDIO_BLOCK * audioBlocks;
acmod = data.readBits(3);
lfeon = data.readBit();
channelCount = CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0);
data.skipBits(5 + 5); // bsid, dialnorm
if (data.readBit()) { // compre
data.skipBits(8); // compr
}
if (acmod == 0) {
data.skipBits(5); // dialnorm2
if (data.readBit()) { // compr2e
data.skipBits(8); // compr2
}
}
if (streamType == STREAM_TYPE_TYPE1 && data.readBit()) { // chanmape
data.skipBits(16); // chanmap
}
if (data.readBit()) { // mixmdate
if (acmod > 2) {
data.skipBits(2); // dmixmod
}
if ((acmod & 0x01) != 0 && acmod > 2) {
data.skipBits(3 + 3); // ltrtcmixlev, lorocmixlev
}
if ((acmod & 0x04) != 0) {
data.skipBits(6); // ltrtsurmixlev, lorosurmixlev
}
if (lfeon && data.readBit()) { // lfemixlevcode
data.skipBits(5); // lfemixlevcod
}
if (streamType == STREAM_TYPE_TYPE0) {
if (data.readBit()) { // pgmscle
data.skipBits(6); //pgmscl
}
if (acmod == 0 && data.readBit()) { // pgmscl2e
data.skipBits(6); // pgmscl2
}
if (data.readBit()) { // extpgmscle
data.skipBits(6); // extpgmscl
}
int mixdef = data.readBits(2);
if (mixdef == 1) {
data.skipBits(1 + 1 + 3); // premixcmpsel, drcsrc, premixcmpscl
} else if (mixdef == 2) {
data.skipBits(12); // mixdata
} else if (mixdef == 3) {
int mixdeflen = data.readBits(5);
if (data.readBit()) { // mixdata2e
data.skipBits(1 + 1 + 3); // premixcmpsel, drcsrc, premixcmpscl
if (data.readBit()) { // extpgmlscle
data.skipBits(4); // extpgmlscl
}
if (data.readBit()) { // extpgmcscle
data.skipBits(4); // extpgmcscl
}
if (data.readBit()) { // extpgmrscle
data.skipBits(4); // extpgmrscl
}
if (data.readBit()) { // extpgmlsscle
data.skipBits(4); // extpgmlsscl
}
if (data.readBit()) { // extpgmrsscle
data.skipBits(4); // extpgmrsscl
}
if (data.readBit()) { // extpgmlfescle
data.skipBits(4); // extpgmlfescl
}
if (data.readBit()) { // dmixscle
data.skipBits(4); // dmixscl
}
if (data.readBit()) { // addche
if (data.readBit()) { // extpgmaux1scle
data.skipBits(4); // extpgmaux1scl
}
if (data.readBit()) { // extpgmaux2scle
data.skipBits(4); // extpgmaux2scl
}
}
}
if (data.readBit()) { // mixdata3e
data.skipBits(5); // spchdat
if (data.readBit()) { // addspchdate
data.skipBits(5 + 2); // spchdat1, spchan1att
if (data.readBit()) { // addspdat1e
data.skipBits(5 + 3); // spchdat2, spchan2att
}
}
}
data.skipBits(8 * (mixdeflen + 2)); // mixdata
data.byteAlign(); // mixdatafill
}
if (acmod < 2) {
if (data.readBit()) { // paninfoe
data.skipBits(8 + 6); // panmean, paninfo
}
if (acmod == 0) {
if (data.readBit()) { // paninfo2e
data.skipBits(8 + 6); // panmean2, paninfo2
}
}
}
if (data.readBit()) { // frmmixcfginfoe
if (numblkscod == 0) {
data.skipBits(5); // blkmixcfginfo[0]
} else {
for (int blk = 0; blk < audioBlocks; blk++) {
if (data.readBit()) { // blkmixcfginfoe
data.skipBits(5); // blkmixcfginfo[blk]
}
}
}
}
}
}
if (data.readBit()) { // infomdate
data.skipBits(3 + 1 + 1); // bsmod, copyrightb, origbs
if (acmod == 2) {
data.skipBits(2 + 2); // dsurmod, dheadphonmod
}
if (acmod >= 6) {
data.skipBits(2); // dsurexmod
}
if (data.readBit()) { // audioprodie
data.skipBits(5 + 2 + 1); // mixlevel, roomtyp, adconvtyp
}
if (acmod == 0 && data.readBit()) { // audioprodi2e
data.skipBits(5 + 2 + 1); // mixlevel2, roomtyp2, adconvtyp2
}
if (fscod < 3) {
data.skipBit(); // sourcefscod
}
}
if (streamType == 0 && numblkscod != 3) {
data.skipBit(); // convsync
}
if (streamType == 2 && (numblkscod == 3 || data.readBit())) { // blkid
data.skipBits(6); // frmsizecod
}
mimeType = MimeTypes.AUDIO_E_AC3;
if (data.readBit()) { // addbsie
int addbsil = data.readBits(6);
if (addbsil == 1 && data.readBits(8) == 1) { // addbsi
mimeType = MimeTypes.AUDIO_ATMOS;
}
}
} else /* is AC-3 */ {
mimeType = MimeTypes.AUDIO_AC3;
data.skipBits(16 + 16); // syncword, crc1
@ -240,9 +398,9 @@ public final class Ac3Util {
}
sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
sampleCount = AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT;
lfeon = data.readBit();
channelCount = CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0);
}
boolean lfeon = data.readBit();
int channelCount = CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0);
return new Ac3SyncFrameInfo(mimeType, streamType, channelCount, sampleRate, frameSize,
sampleCount);
}

View File

@ -39,7 +39,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
private static final int STATE_READING_HEADER = 1;
private static final int STATE_READING_SAMPLE = 2;
private static final int HEADER_SIZE = 8;
private static final int HEADER_SIZE = 128;
private final ParsableBitArray headerScratchBits;
private final ParsableByteArray headerScratchBytes;

View File

@ -20,6 +20,7 @@ import android.annotation.TargetApi;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecList;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@ -120,7 +121,7 @@ public final class MediaCodecUtil {
* exists.
* @throws DecoderQueryException If there was an error querying the available decoders.
*/
public static MediaCodecInfo getDecoderInfo(String mimeType, boolean secure)
public static @Nullable MediaCodecInfo getDecoderInfo(String mimeType, boolean secure)
throws DecoderQueryException {
List<MediaCodecInfo> decoderInfos = getDecoderInfos(mimeType, secure);
return decoderInfos.isEmpty() ? null : decoderInfos.get(0);
@ -140,27 +141,34 @@ public final class MediaCodecUtil {
public static synchronized List<MediaCodecInfo> getDecoderInfos(String mimeType,
boolean secure) throws DecoderQueryException {
CodecKey key = new CodecKey(mimeType, secure);
List<MediaCodecInfo> decoderInfos = decoderInfosCache.get(key);
if (decoderInfos != null) {
return decoderInfos;
List<MediaCodecInfo> cachedDecoderInfos = decoderInfosCache.get(key);
if (cachedDecoderInfos != null) {
return cachedDecoderInfos;
}
MediaCodecListCompat mediaCodecList = Util.SDK_INT >= 21
? new MediaCodecListCompatV21(secure) : new MediaCodecListCompatV16();
decoderInfos = getDecoderInfosInternal(key, mediaCodecList);
ArrayList<MediaCodecInfo> decoderInfos = getDecoderInfosInternal(key, mediaCodecList, mimeType);
if (secure && decoderInfos.isEmpty() && 21 <= Util.SDK_INT && Util.SDK_INT <= 23) {
// Some devices don't list secure decoders on API level 21 [Internal: b/18678462]. Try the
// legacy path. We also try this path on API levels 22 and 23 as a defensive measure.
mediaCodecList = new MediaCodecListCompatV16();
decoderInfos = getDecoderInfosInternal(key, mediaCodecList);
decoderInfos = getDecoderInfosInternal(key, mediaCodecList, mimeType);
if (!decoderInfos.isEmpty()) {
Log.w(TAG, "MediaCodecList API didn't list secure decoder for: " + mimeType
+ ". Assuming: " + decoderInfos.get(0).name);
}
}
if (MimeTypes.AUDIO_ATMOS.equals(mimeType)) {
// E-AC3 decoders can decode Atmos streams, but in 2-D rather than 3-D.
CodecKey eac3Key = new CodecKey(MimeTypes.AUDIO_E_AC3, key.secure);
ArrayList<MediaCodecInfo> eac3DecoderInfos =
getDecoderInfosInternal(eac3Key, mediaCodecList, mimeType);
decoderInfos.addAll(eac3DecoderInfos);
}
applyWorkarounds(decoderInfos);
decoderInfos = Collections.unmodifiableList(decoderInfos);
decoderInfosCache.put(key, decoderInfos);
return decoderInfos;
List<MediaCodecInfo> unmodifiableDecoderInfos = Collections.unmodifiableList(decoderInfos);
decoderInfosCache.put(key, unmodifiableDecoderInfos);
return unmodifiableDecoderInfos;
}
/**
@ -212,10 +220,21 @@ public final class MediaCodecUtil {
// Internal methods.
private static List<MediaCodecInfo> getDecoderInfosInternal(
CodecKey key, MediaCodecListCompat mediaCodecList) throws DecoderQueryException {
/**
* Returns {@link MediaCodecInfo}s for the given codec {@code key} in the order given by
* {@code mediaCodecList}.
*
* @param key The codec key.
* @param mediaCodecList The codec list.
* @param requestedMimeType The originally requested MIME type, which may differ from the codec
* key MIME type if the codec key is being considered as a fallback.
* @return The codec information for usable codecs matching the specified key.
* @throws DecoderQueryException If there was an error querying the available decoders.
*/
private static ArrayList<MediaCodecInfo> getDecoderInfosInternal(CodecKey key,
MediaCodecListCompat mediaCodecList, String requestedMimeType) throws DecoderQueryException {
try {
List<MediaCodecInfo> decoderInfos = new ArrayList<>();
ArrayList<MediaCodecInfo> decoderInfos = new ArrayList<>();
String mimeType = key.mimeType;
int numberOfCodecs = mediaCodecList.getCodecCount();
boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit();
@ -223,7 +242,7 @@ public final class MediaCodecUtil {
for (int i = 0; i < numberOfCodecs; i++) {
android.media.MediaCodecInfo codecInfo = mediaCodecList.getCodecInfoAt(i);
String codecName = codecInfo.getName();
if (isCodecUsableDecoder(codecInfo, codecName, secureDecodersExplicit)) {
if (isCodecUsableDecoder(codecInfo, codecName, secureDecodersExplicit, requestedMimeType)) {
for (String supportedType : codecInfo.getSupportedTypes()) {
if (supportedType.equalsIgnoreCase(mimeType)) {
try {
@ -265,9 +284,16 @@ public final class MediaCodecUtil {
/**
* Returns whether the specified codec is usable for decoding on the current device.
*
* @param info The codec information.
* @param name The name of the codec
* @param secureDecodersExplicit Whether secure decoders were explicitly listed, if present.
* @param requestedMimeType The originally requested MIME type, which may differ from the codec
* key MIME type if the codec key is being considered as a fallback.
* @return Whether the specified codec is usable for decoding on the current device.
*/
private static boolean isCodecUsableDecoder(android.media.MediaCodecInfo info, String name,
boolean secureDecodersExplicit) {
boolean secureDecodersExplicit, String requestedMimeType) {
if (info.isEncoder() || (!secureDecodersExplicit && name.endsWith(".secure"))) {
return false;
}
@ -356,6 +382,12 @@ public final class MediaCodecUtil {
return false;
}
// MTK E-AC3 decoder doesn't support decoding Atmos streams in 2-D. See [Internal: b/69400041].
if (MimeTypes.AUDIO_ATMOS.equals(requestedMimeType)
&& "OMX.MTK.AUDIO.DECODER.DSPAC3".equals(name)) {
return false;
}
return true;
}

View File

@ -51,6 +51,7 @@ public final class MimeTypes {
public static final String AUDIO_MLAW = BASE_TYPE_AUDIO + "/g711-mlaw";
public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3";
public static final String AUDIO_E_AC3 = BASE_TYPE_AUDIO + "/eac3";
public static final String AUDIO_ATMOS = BASE_TYPE_AUDIO + "/eac3-joc";
public static final String AUDIO_TRUEHD = BASE_TYPE_AUDIO + "/true-hd";
public static final String AUDIO_DTS = BASE_TYPE_AUDIO + "/vnd.dts";
public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd";
@ -195,6 +196,8 @@ public final class MimeTypes {
return MimeTypes.AUDIO_AC3;
} else if (codec.startsWith("ec-3") || codec.startsWith("dec3")) {
return MimeTypes.AUDIO_E_AC3;
} else if (codec.startsWith("ec+3")) {
return MimeTypes.AUDIO_ATMOS;
} else if (codec.startsWith("dtsc") || codec.startsWith("dtse")) {
return MimeTypes.AUDIO_DTS;
} else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) {
@ -252,6 +255,7 @@ public final class MimeTypes {
case MimeTypes.AUDIO_AC3:
return C.ENCODING_AC3;
case MimeTypes.AUDIO_E_AC3:
case MimeTypes.AUDIO_ATMOS:
return C.ENCODING_E_AC3;
case MimeTypes.AUDIO_DTS:
return C.ENCODING_DTS;

View File

@ -460,6 +460,7 @@ public class DashManifestParser extends DefaultHandler
String drmSchemeType = null;
ArrayList<SchemeData> drmSchemeDatas = new ArrayList<>();
ArrayList<Descriptor> inbandEventStreams = new ArrayList<>();
ArrayList<Descriptor> supplementalProperties = new ArrayList<>();
boolean seenFirstBaseUrl = false;
do {
@ -487,12 +488,14 @@ public class DashManifestParser extends DefaultHandler
}
} else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) {
inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream"));
} else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) {
supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty"));
}
} while (!XmlPullParserUtil.isEndTag(xpp, "Representation"));
Format format = buildFormat(id, mimeType, width, height, frameRate, audioChannels,
audioSamplingRate, bandwidth, adaptationSetLanguage, adaptationSetSelectionFlags,
adaptationSetAccessibilityDescriptors, codecs);
adaptationSetAccessibilityDescriptors, codecs, supplementalProperties);
segmentBase = segmentBase != null ? segmentBase : new SingleSegmentBase();
return new RepresentationInfo(format, baseUrl, segmentBase, drmSchemeType, drmSchemeDatas,
@ -502,9 +505,12 @@ public class DashManifestParser extends DefaultHandler
protected Format buildFormat(String id, String containerMimeType, int width, int height,
float frameRate, int audioChannels, int audioSamplingRate, int bitrate, String language,
@C.SelectionFlags int selectionFlags, List<Descriptor> accessibilityDescriptors,
String codecs) {
String codecs, List<Descriptor> supplementalProperties) {
String sampleMimeType = getSampleMimeType(containerMimeType, codecs);
if (sampleMimeType != null) {
if (MimeTypes.AUDIO_E_AC3.equals(sampleMimeType)) {
sampleMimeType = parseEac3SupplementalProperties(supplementalProperties);
}
if (MimeTypes.isVideo(sampleMimeType)) {
return Format.createVideoContainerFormat(id, containerMimeType, sampleMimeType, codecs,
bitrate, width, height, frameRate, null, selectionFlags);
@ -1045,6 +1051,18 @@ public class DashManifestParser extends DefaultHandler
return Format.NO_VALUE;
}
protected static String parseEac3SupplementalProperties(List<Descriptor> supplementalProperties) {
for (int i = 0; i < supplementalProperties.size(); i++) {
Descriptor descriptor = supplementalProperties.get(i);
String schemeIdUri = descriptor.schemeIdUri;
if ("tag:dolby.com,2014:dash:DolbyDigitalPlusExtensionType:2014".equals(schemeIdUri)
&& "ec+3".equals(descriptor.value)) {
return MimeTypes.AUDIO_ATMOS;
}
}
return MimeTypes.AUDIO_E_AC3;
}
protected static float parseFrameRate(XmlPullParser xpp, float defaultValue) {
float frameRate = defaultValue;
String frameRateAttribute = xpp.getAttributeValue(null, "frameRate");