Merge pull request #5303 from DolbyLaboratories:dev-v2-ac4
PiperOrigin-RevId: 239957730
This commit is contained in:
commit
5da4c67c8a
@ -49,6 +49,8 @@
|
|||||||
* Fix an issue that caused audio to be truncated at the end of a period
|
* Fix an issue that caused audio to be truncated at the end of a period
|
||||||
when switching to a new period where gapless playback information was newly
|
when switching to a new period where gapless playback information was newly
|
||||||
present or newly absent.
|
present or newly absent.
|
||||||
|
* Add support for reading AC-4 streams
|
||||||
|
([#5303](https://github.com/google/ExoPlayer/pull/5303)).
|
||||||
* Add support for SHOUTcast ICY metadata
|
* Add support for SHOUTcast ICY metadata
|
||||||
([#3735](https://github.com/google/ExoPlayer/issues/3735)).
|
([#3735](https://github.com/google/ExoPlayer/issues/3735)).
|
||||||
* CEA-608: Improved conformance to the specification
|
* CEA-608: Improved conformance to the specification
|
||||||
|
@ -28,6 +28,7 @@ import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
|
|||||||
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
|
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ogg.OggExtractor;
|
import com.google.android.exoplayer2.extractor.ogg.OggExtractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.Ac4Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
||||||
@ -59,6 +60,7 @@ public final class DefaultExtractorsFactoryTest {
|
|||||||
Mp3Extractor.class,
|
Mp3Extractor.class,
|
||||||
AdtsExtractor.class,
|
AdtsExtractor.class,
|
||||||
Ac3Extractor.class,
|
Ac3Extractor.class,
|
||||||
|
Ac4Extractor.class,
|
||||||
TsExtractor.class,
|
TsExtractor.class,
|
||||||
FlvExtractor.class,
|
FlvExtractor.class,
|
||||||
OggExtractor.class,
|
OggExtractor.class,
|
||||||
|
@ -146,8 +146,8 @@ public final class C {
|
|||||||
* {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link
|
* {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link
|
||||||
* #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link
|
* #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link
|
||||||
* #ENCODING_PCM_MU_LAW}, {@link #ENCODING_PCM_A_LAW}, {@link #ENCODING_AC3}, {@link
|
* #ENCODING_PCM_MU_LAW}, {@link #ENCODING_PCM_A_LAW}, {@link #ENCODING_AC3}, {@link
|
||||||
* #ENCODING_E_AC3}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or {@link
|
* #ENCODING_E_AC3}, {@link #ENCODING_AC4}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or
|
||||||
* #ENCODING_DOLBY_TRUEHD}.
|
* {@link #ENCODING_DOLBY_TRUEHD}.
|
||||||
*/
|
*/
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@ -163,9 +163,10 @@ public final class C {
|
|||||||
ENCODING_PCM_A_LAW,
|
ENCODING_PCM_A_LAW,
|
||||||
ENCODING_AC3,
|
ENCODING_AC3,
|
||||||
ENCODING_E_AC3,
|
ENCODING_E_AC3,
|
||||||
|
ENCODING_AC4,
|
||||||
ENCODING_DTS,
|
ENCODING_DTS,
|
||||||
ENCODING_DTS_HD,
|
ENCODING_DTS_HD,
|
||||||
ENCODING_DOLBY_TRUEHD
|
ENCODING_DOLBY_TRUEHD,
|
||||||
})
|
})
|
||||||
public @interface Encoding {}
|
public @interface Encoding {}
|
||||||
|
|
||||||
@ -209,6 +210,8 @@ public final class C {
|
|||||||
public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
|
public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
|
||||||
/** @see AudioFormat#ENCODING_E_AC3 */
|
/** @see AudioFormat#ENCODING_E_AC3 */
|
||||||
public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
|
public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
|
||||||
|
/** @see AudioFormat#ENCODING_AC4 */
|
||||||
|
public static final int ENCODING_AC4 = AudioFormat.ENCODING_AC4;
|
||||||
/** @see AudioFormat#ENCODING_DTS */
|
/** @see AudioFormat#ENCODING_DTS */
|
||||||
public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS;
|
public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS;
|
||||||
/** @see AudioFormat#ENCODING_DTS_HD */
|
/** @see AudioFormat#ENCODING_DTS_HD */
|
||||||
|
@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 com.google.android.exoplayer2.audio;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||||
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||||
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/** Utility methods for parsing AC-4 frames, which are access units in AC-4 bitstreams. */
|
||||||
|
public final class Ac4Util {
|
||||||
|
|
||||||
|
/** Holds sample format information as presented by a syncframe header. */
|
||||||
|
public static final class SyncFrameInfo {
|
||||||
|
|
||||||
|
/** The bitstream version. */
|
||||||
|
public final int bitstreamVersion;
|
||||||
|
/** The audio sampling rate in Hz. */
|
||||||
|
public final int sampleRate;
|
||||||
|
/** The number of audio channels */
|
||||||
|
public final int channelCount;
|
||||||
|
/** The size of the frame. */
|
||||||
|
public final int frameSize;
|
||||||
|
/** Number of audio samples in the frame. */
|
||||||
|
public final int sampleCount;
|
||||||
|
|
||||||
|
private SyncFrameInfo(
|
||||||
|
int bitstreamVersion, int channelCount, int sampleRate, int frameSize, int sampleCount) {
|
||||||
|
this.bitstreamVersion = bitstreamVersion;
|
||||||
|
this.channelCount = channelCount;
|
||||||
|
this.sampleRate = sampleRate;
|
||||||
|
this.frameSize = frameSize;
|
||||||
|
this.sampleCount = sampleCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final int AC40_SYNCWORD = 0xAC40;
|
||||||
|
public static final int AC41_SYNCWORD = 0xAC41;
|
||||||
|
|
||||||
|
/** The channel count of AC-4 stream. */
|
||||||
|
// TODO: Parse AC-4 stream channel count.
|
||||||
|
private static final int CHANNEL_COUNT_2 = 2;
|
||||||
|
/**
|
||||||
|
* The header size for AC-4 parser. Only needs to be as big as we need to read, not the full
|
||||||
|
* header size.
|
||||||
|
*/
|
||||||
|
public static final int HEADER_SIZE_FOR_PARSER = 16;
|
||||||
|
/**
|
||||||
|
* Number of audio samples in the frame. Defined in IEC61937-14:2017 table 5 and 6. This table
|
||||||
|
* provides the number of samples per frame at the playback sampling frequency of 48 kHz. For 44.1
|
||||||
|
* kHz, only frame_rate_index(13) is valid and corresponding sample count is 2048.
|
||||||
|
*/
|
||||||
|
private static final int[] SAMPLE_COUNT =
|
||||||
|
new int[] {
|
||||||
|
/* [ 0] 23.976 fps */ 2002,
|
||||||
|
/* [ 1] 24 fps */ 2000,
|
||||||
|
/* [ 2] 25 fps */ 1920,
|
||||||
|
/* [ 3] 29.97 fps */ 1601, // 1601 | 1602 | 1601 | 1602 | 1602
|
||||||
|
/* [ 4] 30 fps */ 1600,
|
||||||
|
/* [ 5] 47.95 fps */ 1001,
|
||||||
|
/* [ 6] 48 fps */ 1000,
|
||||||
|
/* [ 7] 50 fps */ 960,
|
||||||
|
/* [ 8] 59.94 fps */ 800, // 800 | 801 | 801 | 801 | 801
|
||||||
|
/* [ 9] 60 fps */ 800,
|
||||||
|
/* [10] 100 fps */ 480,
|
||||||
|
/* [11] 119.88 fps */ 400, // 400 | 400 | 401 | 400 | 401
|
||||||
|
/* [12] 120 fps */ 400,
|
||||||
|
/* [13] 23.438 fps */ 2048
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the AC-4 format given {@code data} containing the AC4SpecificBox according to ETSI TS
|
||||||
|
* 103 190-1 Annex E. The reading position of {@code data} will be modified.
|
||||||
|
*
|
||||||
|
* @param data The AC4SpecificBox to parse.
|
||||||
|
* @param trackId The track identifier to set on the format.
|
||||||
|
* @param language The language to set on the format.
|
||||||
|
* @param drmInitData {@link DrmInitData} to be included in the format.
|
||||||
|
* @return The AC-4 format parsed from data in the header.
|
||||||
|
*/
|
||||||
|
public static Format parseAc4AnnexEFormat(
|
||||||
|
ParsableByteArray data, String trackId, String language, DrmInitData drmInitData) {
|
||||||
|
data.skipBytes(1); // ac4_dsi_version, bitstream_version[0:5]
|
||||||
|
int sampleRate = ((data.readUnsignedByte() & 0x20) >> 5 == 1) ? 48000 : 44100;
|
||||||
|
return Format.createAudioSampleFormat(
|
||||||
|
trackId,
|
||||||
|
MimeTypes.AUDIO_AC4,
|
||||||
|
/* codecs= */ null,
|
||||||
|
/* bitrate= */ Format.NO_VALUE,
|
||||||
|
/* maxInputSize= */ Format.NO_VALUE,
|
||||||
|
CHANNEL_COUNT_2,
|
||||||
|
sampleRate,
|
||||||
|
/* initializationData= */ null,
|
||||||
|
drmInitData,
|
||||||
|
/* selectionFlags= */ 0,
|
||||||
|
language);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns AC-4 format information given {@code data} containing a syncframe. The reading position
|
||||||
|
* of {@code data} will be modified.
|
||||||
|
*
|
||||||
|
* @param data The data to parse, positioned at the start of the syncframe.
|
||||||
|
* @return The AC-4 format data parsed from the header.
|
||||||
|
*/
|
||||||
|
public static SyncFrameInfo parseAc4SyncframeInfo(ParsableBitArray data) {
|
||||||
|
int headerSize = 0;
|
||||||
|
int syncWord = data.readBits(16);
|
||||||
|
headerSize += 2;
|
||||||
|
int frameSize = data.readBits(16);
|
||||||
|
headerSize += 2;
|
||||||
|
if (frameSize == 0xFFFF) {
|
||||||
|
frameSize = data.readBits(24);
|
||||||
|
headerSize += 3; // Extended frame_size
|
||||||
|
}
|
||||||
|
frameSize += headerSize;
|
||||||
|
if (syncWord == AC41_SYNCWORD) {
|
||||||
|
frameSize += 2; // crc_word
|
||||||
|
}
|
||||||
|
int bitstreamVersion = data.readBits(2);
|
||||||
|
if (bitstreamVersion == 3) {
|
||||||
|
bitstreamVersion += readVariableBits(data, /* bitsPerRead= */ 2);
|
||||||
|
}
|
||||||
|
int sequenceCounter = data.readBits(10);
|
||||||
|
if (data.readBit()) { // b_wait_frames
|
||||||
|
if (data.readBits(3) > 0) { // wait_frames
|
||||||
|
data.skipBits(2); // reserved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int sampleRate = data.readBit() ? 48000 : 44100;
|
||||||
|
int frameRateIndex = data.readBits(4);
|
||||||
|
int sampleCount = 0;
|
||||||
|
if (sampleRate == 44100 && frameRateIndex == 13) {
|
||||||
|
sampleCount = SAMPLE_COUNT[frameRateIndex];
|
||||||
|
} else if (sampleRate == 48000 && frameRateIndex < SAMPLE_COUNT.length) {
|
||||||
|
sampleCount = SAMPLE_COUNT[frameRateIndex];
|
||||||
|
switch (sequenceCounter % 5) {
|
||||||
|
case 1: // fall through
|
||||||
|
case 3:
|
||||||
|
if (frameRateIndex == 3 || frameRateIndex == 8) {
|
||||||
|
sampleCount++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (frameRateIndex == 8 || frameRateIndex == 11) {
|
||||||
|
sampleCount++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
if (frameRateIndex == 3 || frameRateIndex == 8 || frameRateIndex == 11) {
|
||||||
|
sampleCount++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new SyncFrameInfo(bitstreamVersion, CHANNEL_COUNT_2, sampleRate, frameSize, sampleCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size in bytes of the given AC-4 syncframe.
|
||||||
|
*
|
||||||
|
* @param data The syncframe to parse.
|
||||||
|
* @param syncword The syncword value for the syncframe.
|
||||||
|
* @return The syncframe size in bytes, or {@link C#LENGTH_UNSET} if the input is invalid.
|
||||||
|
*/
|
||||||
|
public static int parseAc4SyncframeSize(byte[] data, int syncword) {
|
||||||
|
if (data.length < 7) {
|
||||||
|
return C.LENGTH_UNSET;
|
||||||
|
}
|
||||||
|
int headerSize = 2; // syncword
|
||||||
|
int frameSize = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
|
||||||
|
headerSize += 2;
|
||||||
|
if (frameSize == 0xFFFF) {
|
||||||
|
frameSize = ((data[4] & 0xFF) << 16) | ((data[5] & 0xFF) << 8) | (data[6] & 0xFF);
|
||||||
|
headerSize += 3;
|
||||||
|
}
|
||||||
|
if (syncword == AC41_SYNCWORD) {
|
||||||
|
headerSize += 2;
|
||||||
|
}
|
||||||
|
frameSize += headerSize;
|
||||||
|
return frameSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the number of audio samples represented by the given AC-4 syncframe. The buffer's
|
||||||
|
* position is not modified.
|
||||||
|
*
|
||||||
|
* @param buffer The {@link ByteBuffer} from which to read the syncframe.
|
||||||
|
* @return The number of audio samples represented by the syncframe.
|
||||||
|
*/
|
||||||
|
public static int parseAc4SyncframeAudioSampleCount(ByteBuffer buffer) {
|
||||||
|
byte[] bufferBytes = new byte[HEADER_SIZE_FOR_PARSER];
|
||||||
|
int position = buffer.position();
|
||||||
|
buffer.get(bufferBytes);
|
||||||
|
buffer.position(position);
|
||||||
|
return parseAc4SyncframeInfo(new ParsableBitArray(bufferBytes)).sampleCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Populates {@code buffer} with an AC-4 sample header for a sample of the specified size. */
|
||||||
|
public static void getAc4SampleHeader(int size, ParsableByteArray buffer) {
|
||||||
|
// See ETSI TS 103 190-1 V1.3.1, Annex G.
|
||||||
|
buffer.reset(/* limit= */ 7);
|
||||||
|
buffer.data[0] = (byte) 0xAC;
|
||||||
|
buffer.data[1] = 0x40;
|
||||||
|
buffer.data[2] = (byte) 0xFF;
|
||||||
|
buffer.data[3] = (byte) 0xFF;
|
||||||
|
buffer.data[4] = (byte) ((size >> 16) & 0xFF);
|
||||||
|
buffer.data[5] = (byte) ((size >> 8) & 0xFF);
|
||||||
|
buffer.data[6] = (byte) (size & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int readVariableBits(ParsableBitArray data, int bitsPerRead) {
|
||||||
|
int value = 0;
|
||||||
|
while (true) {
|
||||||
|
value += data.readBits(bitsPerRead);
|
||||||
|
if (!data.readBit()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
value++;
|
||||||
|
value <<= bitsPerRead;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Ac4Util() {}
|
||||||
|
}
|
@ -1126,6 +1126,8 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
return 640 * 1000 / 8;
|
return 640 * 1000 / 8;
|
||||||
case C.ENCODING_E_AC3:
|
case C.ENCODING_E_AC3:
|
||||||
return 6144 * 1000 / 8;
|
return 6144 * 1000 / 8;
|
||||||
|
case C.ENCODING_AC4:
|
||||||
|
return 2688 * 1000 / 8;
|
||||||
case C.ENCODING_DTS:
|
case C.ENCODING_DTS:
|
||||||
// DTS allows an 'open' bitrate, but we assume the maximum listed value: 1536 kbit/s.
|
// DTS allows an 'open' bitrate, but we assume the maximum listed value: 1536 kbit/s.
|
||||||
return 1536 * 1000 / 8;
|
return 1536 * 1000 / 8;
|
||||||
@ -1154,6 +1156,8 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
return Ac3Util.getAc3SyncframeAudioSampleCount();
|
return Ac3Util.getAc3SyncframeAudioSampleCount();
|
||||||
} else if (encoding == C.ENCODING_E_AC3) {
|
} else if (encoding == C.ENCODING_E_AC3) {
|
||||||
return Ac3Util.parseEAc3SyncframeAudioSampleCount(buffer);
|
return Ac3Util.parseEAc3SyncframeAudioSampleCount(buffer);
|
||||||
|
} else if (encoding == C.ENCODING_AC4) {
|
||||||
|
return Ac4Util.parseAc4SyncframeAudioSampleCount(buffer);
|
||||||
} else if (encoding == C.ENCODING_DOLBY_TRUEHD) {
|
} else if (encoding == C.ENCODING_DOLBY_TRUEHD) {
|
||||||
int syncframeOffset = Ac3Util.findTrueHdSyncframeOffset(buffer);
|
int syncframeOffset = Ac3Util.findTrueHdSyncframeOffset(buffer);
|
||||||
return syncframeOffset == C.INDEX_UNSET
|
return syncframeOffset == C.INDEX_UNSET
|
||||||
|
@ -784,6 +784,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate);
|
mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Util.SDK_INT <= 28 && MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) {
|
||||||
|
// On some older builds, the AC-4 decoder expects to receive samples formatted as raw frames
|
||||||
|
// not sync frames. Set a format key to override this.
|
||||||
|
mediaFormat.setInteger("ac4-is-sync", 1);
|
||||||
|
}
|
||||||
return mediaFormat;
|
return mediaFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
|
|||||||
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
|
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ogg.OggExtractor;
|
import com.google.android.exoplayer2.extractor.ogg.OggExtractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.Ac4Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
|
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
|
||||||
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
|
||||||
@ -47,6 +48,7 @@ import java.lang.reflect.Constructor;
|
|||||||
* <li>FLV ({@link FlvExtractor})
|
* <li>FLV ({@link FlvExtractor})
|
||||||
* <li>WAV ({@link WavExtractor})
|
* <li>WAV ({@link WavExtractor})
|
||||||
* <li>AC3 ({@link Ac3Extractor})
|
* <li>AC3 ({@link Ac3Extractor})
|
||||||
|
* <li>AC4 ({@link Ac4Extractor})
|
||||||
* <li>AMR ({@link AmrExtractor})
|
* <li>AMR ({@link AmrExtractor})
|
||||||
* <li>FLAC (only available if the FLAC extension is built and included)
|
* <li>FLAC (only available if the FLAC extension is built and included)
|
||||||
* </ul>
|
* </ul>
|
||||||
@ -206,7 +208,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized Extractor[] createExtractors() {
|
public synchronized Extractor[] createExtractors() {
|
||||||
Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 12 : 13];
|
Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 13 : 14];
|
||||||
extractors[0] = new MatroskaExtractor(matroskaFlags);
|
extractors[0] = new MatroskaExtractor(matroskaFlags);
|
||||||
extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags);
|
extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags);
|
||||||
extractors[2] = new Mp4Extractor(mp4Flags);
|
extractors[2] = new Mp4Extractor(mp4Flags);
|
||||||
@ -235,9 +237,10 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
|||||||
| (constantBitrateSeekingEnabled
|
| (constantBitrateSeekingEnabled
|
||||||
? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
|
? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
|
||||||
: 0));
|
: 0));
|
||||||
|
extractors[12] = new Ac4Extractor();
|
||||||
if (FLAC_EXTRACTOR_CONSTRUCTOR != null) {
|
if (FLAC_EXTRACTOR_CONSTRUCTOR != null) {
|
||||||
try {
|
try {
|
||||||
extractors[12] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance();
|
extractors[13] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Should never happen.
|
// Should never happen.
|
||||||
throw new IllegalStateException("Unexpected error creating FLAC extractor", e);
|
throw new IllegalStateException("Unexpected error creating FLAC extractor", e);
|
||||||
|
@ -80,6 +80,8 @@ import java.util.List;
|
|||||||
public static final int TYPE_dac3 = Util.getIntegerCodeForString("dac3");
|
public static final int TYPE_dac3 = Util.getIntegerCodeForString("dac3");
|
||||||
public static final int TYPE_ec_3 = Util.getIntegerCodeForString("ec-3");
|
public static final int TYPE_ec_3 = Util.getIntegerCodeForString("ec-3");
|
||||||
public static final int TYPE_dec3 = Util.getIntegerCodeForString("dec3");
|
public static final int TYPE_dec3 = Util.getIntegerCodeForString("dec3");
|
||||||
|
public static final int TYPE_ac_4 = Util.getIntegerCodeForString("ac-4");
|
||||||
|
public static final int TYPE_dac4 = Util.getIntegerCodeForString("dac4");
|
||||||
public static final int TYPE_dtsc = Util.getIntegerCodeForString("dtsc");
|
public static final int TYPE_dtsc = Util.getIntegerCodeForString("dtsc");
|
||||||
public static final int TYPE_dtsh = Util.getIntegerCodeForString("dtsh");
|
public static final int TYPE_dtsh = Util.getIntegerCodeForString("dtsh");
|
||||||
public static final int TYPE_dtsl = Util.getIntegerCodeForString("dtsl");
|
public static final int TYPE_dtsl = Util.getIntegerCodeForString("dtsl");
|
||||||
|
@ -23,6 +23,7 @@ import com.google.android.exoplayer2.C;
|
|||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
import com.google.android.exoplayer2.audio.Ac3Util;
|
import com.google.android.exoplayer2.audio.Ac3Util;
|
||||||
|
import com.google.android.exoplayer2.audio.Ac4Util;
|
||||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer2.extractor.GaplessInfoHolder;
|
import com.google.android.exoplayer2.extractor.GaplessInfoHolder;
|
||||||
import com.google.android.exoplayer2.metadata.Metadata;
|
import com.google.android.exoplayer2.metadata.Metadata;
|
||||||
@ -756,6 +757,7 @@ import java.util.List;
|
|||||||
|| childAtomType == Atom.TYPE_enca
|
|| childAtomType == Atom.TYPE_enca
|
||||||
|| childAtomType == Atom.TYPE_ac_3
|
|| childAtomType == Atom.TYPE_ac_3
|
||||||
|| childAtomType == Atom.TYPE_ec_3
|
|| childAtomType == Atom.TYPE_ec_3
|
||||||
|
|| childAtomType == Atom.TYPE_ac_4
|
||||||
|| childAtomType == Atom.TYPE_dtsc
|
|| childAtomType == Atom.TYPE_dtsc
|
||||||
|| childAtomType == Atom.TYPE_dtse
|
|| childAtomType == Atom.TYPE_dtse
|
||||||
|| childAtomType == Atom.TYPE_dtsh
|
|| childAtomType == Atom.TYPE_dtsh
|
||||||
@ -1071,6 +1073,8 @@ import java.util.List;
|
|||||||
mimeType = MimeTypes.AUDIO_AC3;
|
mimeType = MimeTypes.AUDIO_AC3;
|
||||||
} else if (atomType == Atom.TYPE_ec_3) {
|
} else if (atomType == Atom.TYPE_ec_3) {
|
||||||
mimeType = MimeTypes.AUDIO_E_AC3;
|
mimeType = MimeTypes.AUDIO_E_AC3;
|
||||||
|
} else if (atomType == Atom.TYPE_ac_4) {
|
||||||
|
mimeType = MimeTypes.AUDIO_AC4;
|
||||||
} else if (atomType == Atom.TYPE_dtsc) {
|
} else if (atomType == Atom.TYPE_dtsc) {
|
||||||
mimeType = MimeTypes.AUDIO_DTS;
|
mimeType = MimeTypes.AUDIO_DTS;
|
||||||
} else if (atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl) {
|
} else if (atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl) {
|
||||||
@ -1128,6 +1132,10 @@ import java.util.List;
|
|||||||
parent.setPosition(Atom.HEADER_SIZE + childPosition);
|
parent.setPosition(Atom.HEADER_SIZE + childPosition);
|
||||||
out.format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language,
|
out.format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language,
|
||||||
drmInitData);
|
drmInitData);
|
||||||
|
} else if (childAtomType == Atom.TYPE_dac4) {
|
||||||
|
parent.setPosition(Atom.HEADER_SIZE + childPosition);
|
||||||
|
out.format =
|
||||||
|
Ac4Util.parseAc4AnnexEFormat(parent, Integer.toString(trackId), language, drmInitData);
|
||||||
} else if (childAtomType == Atom.TYPE_ddts) {
|
} else if (childAtomType == Atom.TYPE_ddts) {
|
||||||
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
|
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
|
||||||
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0,
|
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0,
|
||||||
|
@ -22,6 +22,7 @@ import android.util.SparseArray;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
|
import com.google.android.exoplayer2.audio.Ac4Util;
|
||||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
||||||
import com.google.android.exoplayer2.extractor.ChunkIndex;
|
import com.google.android.exoplayer2.extractor.ChunkIndex;
|
||||||
@ -133,13 +134,14 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
private final ParsableByteArray nalStartCode;
|
private final ParsableByteArray nalStartCode;
|
||||||
private final ParsableByteArray nalPrefix;
|
private final ParsableByteArray nalPrefix;
|
||||||
private final ParsableByteArray nalBuffer;
|
private final ParsableByteArray nalBuffer;
|
||||||
|
private final byte[] scratchBytes;
|
||||||
|
private final ParsableByteArray scratch;
|
||||||
|
|
||||||
// Adjusts sample timestamps.
|
// Adjusts sample timestamps.
|
||||||
private final @Nullable TimestampAdjuster timestampAdjuster;
|
private final @Nullable TimestampAdjuster timestampAdjuster;
|
||||||
|
|
||||||
// Parser state.
|
// Parser state.
|
||||||
private final ParsableByteArray atomHeader;
|
private final ParsableByteArray atomHeader;
|
||||||
private final byte[] extendedTypeScratch;
|
|
||||||
private final ArrayDeque<ContainerAtom> containerAtoms;
|
private final ArrayDeque<ContainerAtom> containerAtoms;
|
||||||
private final ArrayDeque<MetadataSampleInfo> pendingMetadataSampleInfos;
|
private final ArrayDeque<MetadataSampleInfo> pendingMetadataSampleInfos;
|
||||||
private final @Nullable TrackOutput additionalEmsgTrackOutput;
|
private final @Nullable TrackOutput additionalEmsgTrackOutput;
|
||||||
@ -160,6 +162,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
private int sampleBytesWritten;
|
private int sampleBytesWritten;
|
||||||
private int sampleCurrentNalBytesRemaining;
|
private int sampleCurrentNalBytesRemaining;
|
||||||
private boolean processSeiNalUnitPayload;
|
private boolean processSeiNalUnitPayload;
|
||||||
|
private boolean isAc4HeaderRequired;
|
||||||
|
|
||||||
// Extractor output.
|
// Extractor output.
|
||||||
private ExtractorOutput extractorOutput;
|
private ExtractorOutput extractorOutput;
|
||||||
@ -254,7 +257,8 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
|
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
|
||||||
nalPrefix = new ParsableByteArray(5);
|
nalPrefix = new ParsableByteArray(5);
|
||||||
nalBuffer = new ParsableByteArray();
|
nalBuffer = new ParsableByteArray();
|
||||||
extendedTypeScratch = new byte[16];
|
scratchBytes = new byte[16];
|
||||||
|
scratch = new ParsableByteArray(scratchBytes);
|
||||||
containerAtoms = new ArrayDeque<>();
|
containerAtoms = new ArrayDeque<>();
|
||||||
pendingMetadataSampleInfos = new ArrayDeque<>();
|
pendingMetadataSampleInfos = new ArrayDeque<>();
|
||||||
trackBundles = new SparseArray<>();
|
trackBundles = new SparseArray<>();
|
||||||
@ -291,6 +295,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
pendingMetadataSampleBytes = 0;
|
pendingMetadataSampleBytes = 0;
|
||||||
pendingSeekTimeUs = timeUs;
|
pendingSeekTimeUs = timeUs;
|
||||||
containerAtoms.clear();
|
containerAtoms.clear();
|
||||||
|
isAc4HeaderRequired = false;
|
||||||
enterReadingAtomHeaderState();
|
enterReadingAtomHeaderState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,7 +543,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
|
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
|
||||||
parseMoof(moof, trackBundles, flags, extendedTypeScratch);
|
parseMoof(moof, trackBundles, flags, scratchBytes);
|
||||||
// If drm init data is sideloaded, we ignore pssh boxes.
|
// If drm init data is sideloaded, we ignore pssh boxes.
|
||||||
DrmInitData drmInitData = sideloadedDrmInitData != null ? null
|
DrmInitData drmInitData = sideloadedDrmInitData != null ? null
|
||||||
: getDrmInitDataFromAtoms(moof.leafChildren);
|
: getDrmInitDataFromAtoms(moof.leafChildren);
|
||||||
@ -1224,6 +1229,8 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
sampleSize += sampleBytesWritten;
|
sampleSize += sampleBytesWritten;
|
||||||
parserState = STATE_READING_SAMPLE_CONTINUE;
|
parserState = STATE_READING_SAMPLE_CONTINUE;
|
||||||
sampleCurrentNalBytesRemaining = 0;
|
sampleCurrentNalBytesRemaining = 0;
|
||||||
|
isAc4HeaderRequired =
|
||||||
|
MimeTypes.AUDIO_AC4.equals(currentTrackBundle.track.format.sampleMimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackFragment fragment = currentTrackBundle.fragment;
|
TrackFragment fragment = currentTrackBundle.fragment;
|
||||||
@ -1288,6 +1295,14 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (isAc4HeaderRequired) {
|
||||||
|
Ac4Util.getAc4SampleHeader(sampleSize, scratch);
|
||||||
|
int length = scratch.limit();
|
||||||
|
output.sampleData(scratch, length);
|
||||||
|
sampleSize += length;
|
||||||
|
sampleBytesWritten += length;
|
||||||
|
isAc4HeaderRequired = false;
|
||||||
|
}
|
||||||
while (sampleBytesWritten < sampleSize) {
|
while (sampleBytesWritten < sampleSize) {
|
||||||
int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false);
|
int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false);
|
||||||
sampleBytesWritten += writtenBytes;
|
sampleBytesWritten += writtenBytes;
|
||||||
|
@ -19,6 +19,7 @@ import androidx.annotation.IntDef;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
|
import com.google.android.exoplayer2.audio.Ac4Util;
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
@ -31,6 +32,7 @@ import com.google.android.exoplayer2.extractor.TrackOutput;
|
|||||||
import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
|
import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
|
||||||
import com.google.android.exoplayer2.metadata.Metadata;
|
import com.google.android.exoplayer2.metadata.Metadata;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.NalUnitUtil;
|
import com.google.android.exoplayer2.util.NalUnitUtil;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
@ -95,6 +97,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||||||
// Temporary arrays.
|
// Temporary arrays.
|
||||||
private final ParsableByteArray nalStartCode;
|
private final ParsableByteArray nalStartCode;
|
||||||
private final ParsableByteArray nalLength;
|
private final ParsableByteArray nalLength;
|
||||||
|
private final ParsableByteArray scratch;
|
||||||
|
|
||||||
private final ParsableByteArray atomHeader;
|
private final ParsableByteArray atomHeader;
|
||||||
private final ArrayDeque<ContainerAtom> containerAtoms;
|
private final ArrayDeque<ContainerAtom> containerAtoms;
|
||||||
@ -108,6 +111,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||||||
private int sampleTrackIndex;
|
private int sampleTrackIndex;
|
||||||
private int sampleBytesWritten;
|
private int sampleBytesWritten;
|
||||||
private int sampleCurrentNalBytesRemaining;
|
private int sampleCurrentNalBytesRemaining;
|
||||||
|
private boolean isAc4HeaderRequired;
|
||||||
|
|
||||||
// Extractor outputs.
|
// Extractor outputs.
|
||||||
private ExtractorOutput extractorOutput;
|
private ExtractorOutput extractorOutput;
|
||||||
@ -136,6 +140,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||||||
containerAtoms = new ArrayDeque<>();
|
containerAtoms = new ArrayDeque<>();
|
||||||
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
|
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
|
||||||
nalLength = new ParsableByteArray(4);
|
nalLength = new ParsableByteArray(4);
|
||||||
|
scratch = new ParsableByteArray();
|
||||||
sampleTrackIndex = C.INDEX_UNSET;
|
sampleTrackIndex = C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +161,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||||||
sampleTrackIndex = C.INDEX_UNSET;
|
sampleTrackIndex = C.INDEX_UNSET;
|
||||||
sampleBytesWritten = 0;
|
sampleBytesWritten = 0;
|
||||||
sampleCurrentNalBytesRemaining = 0;
|
sampleCurrentNalBytesRemaining = 0;
|
||||||
|
isAc4HeaderRequired = false;
|
||||||
if (position == 0) {
|
if (position == 0) {
|
||||||
enterReadingAtomHeaderState();
|
enterReadingAtomHeaderState();
|
||||||
} else if (tracks != null) {
|
} else if (tracks != null) {
|
||||||
@ -493,6 +499,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||||||
if (sampleTrackIndex == C.INDEX_UNSET) {
|
if (sampleTrackIndex == C.INDEX_UNSET) {
|
||||||
return RESULT_END_OF_INPUT;
|
return RESULT_END_OF_INPUT;
|
||||||
}
|
}
|
||||||
|
isAc4HeaderRequired =
|
||||||
|
MimeTypes.AUDIO_AC4.equals(tracks[sampleTrackIndex].track.format.sampleMimeType);
|
||||||
}
|
}
|
||||||
Mp4Track track = tracks[sampleTrackIndex];
|
Mp4Track track = tracks[sampleTrackIndex];
|
||||||
TrackOutput trackOutput = track.trackOutput;
|
TrackOutput trackOutput = track.trackOutput;
|
||||||
@ -546,6 +554,14 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (isAc4HeaderRequired) {
|
||||||
|
Ac4Util.getAc4SampleHeader(sampleSize, scratch);
|
||||||
|
int length = scratch.limit();
|
||||||
|
trackOutput.sampleData(scratch, length);
|
||||||
|
sampleSize += length;
|
||||||
|
sampleBytesWritten += length;
|
||||||
|
isAc4HeaderRequired = false;
|
||||||
|
}
|
||||||
while (sampleBytesWritten < sampleSize) {
|
while (sampleBytesWritten < sampleSize) {
|
||||||
int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false);
|
int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false);
|
||||||
sampleBytesWritten += writtenBytes;
|
sampleBytesWritten += writtenBytes;
|
||||||
|
@ -76,7 +76,7 @@ public final class Ac3Extractor implements Extractor {
|
|||||||
if (scratch.readUnsignedInt24() != ID3_TAG) {
|
if (scratch.readUnsignedInt24() != ID3_TAG) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
scratch.skipBytes(3);
|
scratch.skipBytes(3); // version, flags
|
||||||
int length = scratch.readSynchSafeInt();
|
int length = scratch.readSynchSafeInt();
|
||||||
startPosition += 10 + length;
|
startPosition += 10 + length;
|
||||||
input.advancePeekPosition(length);
|
input.advancePeekPosition(length);
|
||||||
|
@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.audio.Ac4Util.AC40_SYNCWORD;
|
||||||
|
import static com.google.android.exoplayer2.audio.Ac4Util.AC41_SYNCWORD;
|
||||||
|
import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_DATA_ALIGNMENT_INDICATOR;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.audio.Ac4Util;
|
||||||
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||||
|
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||||
|
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/** Extracts data from AC-4 bitstreams. */
|
||||||
|
public final class Ac4Extractor implements Extractor {
|
||||||
|
|
||||||
|
/** Factory for {@link Ac4Extractor} instances. */
|
||||||
|
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new Ac4Extractor()};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of bytes to search when sniffing, excluding ID3 information, before giving
|
||||||
|
* up.
|
||||||
|
*/
|
||||||
|
private static final int MAX_SNIFF_BYTES = 8 * 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of the reading buffer, in bytes. This value is determined based on the maximum frame
|
||||||
|
* size used in broadcast applications.
|
||||||
|
*/
|
||||||
|
private static final int READ_BUFFER_SIZE = 16384;
|
||||||
|
|
||||||
|
/** The size of the frame header, in bytes. */
|
||||||
|
private static final int FRAME_HEADER_SIZE = 7;
|
||||||
|
|
||||||
|
private static final int ID3_TAG = Util.getIntegerCodeForString("ID3");
|
||||||
|
|
||||||
|
private final long firstSampleTimestampUs;
|
||||||
|
private final Ac4Reader reader;
|
||||||
|
private final ParsableByteArray sampleData;
|
||||||
|
|
||||||
|
private boolean startedPacket;
|
||||||
|
|
||||||
|
/** Creates a new extractor for AC-4 bitstreams. */
|
||||||
|
public Ac4Extractor() {
|
||||||
|
this(/* firstSampleTimestampUs= */ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a new extractor for AC-4 bitstreams, using the specified first sample timestamp. */
|
||||||
|
public Ac4Extractor(long firstSampleTimestampUs) {
|
||||||
|
this.firstSampleTimestampUs = firstSampleTimestampUs;
|
||||||
|
reader = new Ac4Reader();
|
||||||
|
sampleData = new ParsableByteArray(READ_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extractor implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
|
// Skip any ID3 headers.
|
||||||
|
ParsableByteArray scratch = new ParsableByteArray(10);
|
||||||
|
int startPosition = 0;
|
||||||
|
while (true) {
|
||||||
|
input.peekFully(scratch.data, /* offset= */ 0, /* length= */ 10);
|
||||||
|
scratch.setPosition(0);
|
||||||
|
if (scratch.readUnsignedInt24() != ID3_TAG) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
scratch.skipBytes(3); // version, flags
|
||||||
|
int length = scratch.readSynchSafeInt();
|
||||||
|
startPosition += 10 + length;
|
||||||
|
input.advancePeekPosition(length);
|
||||||
|
}
|
||||||
|
input.resetPeekPosition();
|
||||||
|
input.advancePeekPosition(startPosition);
|
||||||
|
|
||||||
|
int headerPosition = startPosition;
|
||||||
|
int validFramesCount = 0;
|
||||||
|
while (true) {
|
||||||
|
input.peekFully(scratch.data, /* offset= */ 0, /* length= */ FRAME_HEADER_SIZE);
|
||||||
|
scratch.setPosition(0);
|
||||||
|
int syncBytes = scratch.readUnsignedShort();
|
||||||
|
if (syncBytes != AC40_SYNCWORD && syncBytes != AC41_SYNCWORD) {
|
||||||
|
validFramesCount = 0;
|
||||||
|
input.resetPeekPosition();
|
||||||
|
if (++headerPosition - startPosition >= MAX_SNIFF_BYTES) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input.advancePeekPosition(headerPosition);
|
||||||
|
} else {
|
||||||
|
if (++validFramesCount >= 4) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
int frameSize = Ac4Util.parseAc4SyncframeSize(scratch.data, syncBytes);
|
||||||
|
if (frameSize == C.LENGTH_UNSET) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input.advancePeekPosition(frameSize - FRAME_HEADER_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(ExtractorOutput output) {
|
||||||
|
reader.createTracks(
|
||||||
|
output, new TrackIdGenerator(/* firstTrackId= */ 0, /* trackIdIncrement= */ 1));
|
||||||
|
output.endTracks();
|
||||||
|
output.seekMap(new SeekMap.Unseekable(/* durationUs= */ C.TIME_UNSET));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek(long position, long timeUs) {
|
||||||
|
startedPacket = false;
|
||||||
|
reader.seek();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(ExtractorInput input, PositionHolder seekPosition)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
int bytesRead = input.read(sampleData.data, /* offset= */ 0, /* length= */ READ_BUFFER_SIZE);
|
||||||
|
if (bytesRead == C.RESULT_END_OF_INPUT) {
|
||||||
|
return RESULT_END_OF_INPUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed whatever data we have to the reader, regardless of whether the read finished or not.
|
||||||
|
sampleData.setPosition(0);
|
||||||
|
sampleData.setLimit(bytesRead);
|
||||||
|
|
||||||
|
if (!startedPacket) {
|
||||||
|
// Pass data to the reader as though it's contained within a single infinitely long packet.
|
||||||
|
reader.packetStarted(firstSampleTimestampUs, FLAG_DATA_ALIGNMENT_INDICATOR);
|
||||||
|
startedPacket = true;
|
||||||
|
}
|
||||||
|
// TODO: Make it possible for the reader to consume the dataSource directly, so that it becomes
|
||||||
|
// unnecessary to copy the data through packetBuffer.
|
||||||
|
reader.consume(sampleData);
|
||||||
|
return RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.audio.Ac4Util;
|
||||||
|
import com.google.android.exoplayer2.audio.Ac4Util.SyncFrameInfo;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||||
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
/** Parses a continuous AC-4 byte stream and extracts individual samples. */
|
||||||
|
public final class Ac4Reader implements ElementaryStreamReader {
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({STATE_FINDING_SYNC, STATE_READING_HEADER, STATE_READING_SAMPLE})
|
||||||
|
private @interface State {}
|
||||||
|
|
||||||
|
private static final int STATE_FINDING_SYNC = 0;
|
||||||
|
private static final int STATE_READING_HEADER = 1;
|
||||||
|
private static final int STATE_READING_SAMPLE = 2;
|
||||||
|
|
||||||
|
private final ParsableBitArray headerScratchBits;
|
||||||
|
private final ParsableByteArray headerScratchBytes;
|
||||||
|
private final String language;
|
||||||
|
|
||||||
|
private String trackFormatId;
|
||||||
|
private TrackOutput output;
|
||||||
|
|
||||||
|
@State private int state;
|
||||||
|
private int bytesRead;
|
||||||
|
|
||||||
|
// Used to find the header.
|
||||||
|
private boolean lastByteWasAC;
|
||||||
|
private boolean hasCRC;
|
||||||
|
|
||||||
|
// Used when parsing the header.
|
||||||
|
private long sampleDurationUs;
|
||||||
|
private Format format;
|
||||||
|
private int sampleSize;
|
||||||
|
|
||||||
|
// Used when reading the samples.
|
||||||
|
private long timeUs;
|
||||||
|
|
||||||
|
/** Constructs a new reader for AC-4 elementary streams. */
|
||||||
|
public Ac4Reader() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new reader for AC-4 elementary streams.
|
||||||
|
*
|
||||||
|
* @param language Track language.
|
||||||
|
*/
|
||||||
|
public Ac4Reader(String language) {
|
||||||
|
headerScratchBits = new ParsableBitArray(new byte[Ac4Util.HEADER_SIZE_FOR_PARSER]);
|
||||||
|
headerScratchBytes = new ParsableByteArray(headerScratchBits.data);
|
||||||
|
state = STATE_FINDING_SYNC;
|
||||||
|
bytesRead = 0;
|
||||||
|
lastByteWasAC = false;
|
||||||
|
hasCRC = false;
|
||||||
|
this.language = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek() {
|
||||||
|
state = STATE_FINDING_SYNC;
|
||||||
|
bytesRead = 0;
|
||||||
|
lastByteWasAC = false;
|
||||||
|
hasCRC = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
|
||||||
|
generator.generateNewId();
|
||||||
|
trackFormatId = generator.getFormatId();
|
||||||
|
output = extractorOutput.track(generator.getTrackId(), C.TRACK_TYPE_AUDIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
|
||||||
|
timeUs = pesTimeUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void consume(ParsableByteArray data) {
|
||||||
|
while (data.bytesLeft() > 0) {
|
||||||
|
switch (state) {
|
||||||
|
case STATE_FINDING_SYNC:
|
||||||
|
if (skipToNextSync(data)) {
|
||||||
|
state = STATE_READING_HEADER;
|
||||||
|
headerScratchBytes.data[0] = (byte) 0xAC;
|
||||||
|
headerScratchBytes.data[1] = (byte) (hasCRC ? 0x41 : 0x40);
|
||||||
|
bytesRead = 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STATE_READING_HEADER:
|
||||||
|
if (continueRead(data, headerScratchBytes.data, Ac4Util.HEADER_SIZE_FOR_PARSER)) {
|
||||||
|
parseHeader();
|
||||||
|
headerScratchBytes.setPosition(0);
|
||||||
|
output.sampleData(headerScratchBytes, Ac4Util.HEADER_SIZE_FOR_PARSER);
|
||||||
|
state = STATE_READING_SAMPLE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STATE_READING_SAMPLE:
|
||||||
|
int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
|
||||||
|
output.sampleData(data, bytesToRead);
|
||||||
|
bytesRead += bytesToRead;
|
||||||
|
if (bytesRead == sampleSize) {
|
||||||
|
output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null);
|
||||||
|
timeUs += sampleDurationUs;
|
||||||
|
state = STATE_FINDING_SYNC;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void packetFinished() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continues a read from the provided {@code source} into a given {@code target}. It's assumed
|
||||||
|
* that the data should be written into {@code target} starting from an offset of zero.
|
||||||
|
*
|
||||||
|
* @param source The source from which to read.
|
||||||
|
* @param target The target into which data is to be read.
|
||||||
|
* @param targetLength The target length of the read.
|
||||||
|
* @return Whether the target length was reached.
|
||||||
|
*/
|
||||||
|
private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) {
|
||||||
|
int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead);
|
||||||
|
source.readBytes(target, bytesRead, bytesToRead);
|
||||||
|
bytesRead += bytesToRead;
|
||||||
|
return bytesRead == targetLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locates the next syncword, advancing the position to the byte that immediately follows it. If a
|
||||||
|
* syncword was not located, the position is advanced to the limit.
|
||||||
|
*
|
||||||
|
* @param pesBuffer The buffer whose position should be advanced.
|
||||||
|
* @return Whether a syncword position was found.
|
||||||
|
*/
|
||||||
|
private boolean skipToNextSync(ParsableByteArray pesBuffer) {
|
||||||
|
while (pesBuffer.bytesLeft() > 0) {
|
||||||
|
if (!lastByteWasAC) {
|
||||||
|
lastByteWasAC = (pesBuffer.readUnsignedByte() == 0xAC);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int secondByte = pesBuffer.readUnsignedByte();
|
||||||
|
lastByteWasAC = secondByte == 0xAC;
|
||||||
|
if (secondByte == 0x40 || secondByte == 0x41) {
|
||||||
|
hasCRC = secondByte == 0x41;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses the sample header. */
|
||||||
|
@SuppressWarnings("ReferenceEquality")
|
||||||
|
private void parseHeader() {
|
||||||
|
headerScratchBits.setPosition(0);
|
||||||
|
SyncFrameInfo frameInfo = Ac4Util.parseAc4SyncframeInfo(headerScratchBits);
|
||||||
|
if (format == null
|
||||||
|
|| frameInfo.channelCount != format.channelCount
|
||||||
|
|| frameInfo.sampleRate != format.sampleRate
|
||||||
|
|| !MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) {
|
||||||
|
format =
|
||||||
|
Format.createAudioSampleFormat(
|
||||||
|
trackFormatId,
|
||||||
|
MimeTypes.AUDIO_AC4,
|
||||||
|
/* codecs= */ null,
|
||||||
|
/* bitrate= */ Format.NO_VALUE,
|
||||||
|
/* maxInputSize= */ Format.NO_VALUE,
|
||||||
|
frameInfo.channelCount,
|
||||||
|
frameInfo.sampleRate,
|
||||||
|
/* initializationData= */ null,
|
||||||
|
/* drmInitData= */ null,
|
||||||
|
/* selectionFlags= */ 0,
|
||||||
|
language);
|
||||||
|
output.format(format);
|
||||||
|
}
|
||||||
|
sampleSize = frameInfo.frameSize;
|
||||||
|
// In this class a sample is an AC-4 sync frame, but Format#sampleRate specifies the number of
|
||||||
|
// PCM audio samples per second.
|
||||||
|
sampleDurationUs = C.MICROS_PER_SECOND * frameInfo.sampleCount / format.sampleRate;
|
||||||
|
}
|
||||||
|
}
|
@ -149,6 +149,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
|
|||||||
case TsExtractor.TS_STREAM_TYPE_AC3:
|
case TsExtractor.TS_STREAM_TYPE_AC3:
|
||||||
case TsExtractor.TS_STREAM_TYPE_E_AC3:
|
case TsExtractor.TS_STREAM_TYPE_E_AC3:
|
||||||
return new PesReader(new Ac3Reader(esInfo.language));
|
return new PesReader(new Ac3Reader(esInfo.language));
|
||||||
|
case TsExtractor.TS_STREAM_TYPE_AC4:
|
||||||
|
return new PesReader(new Ac4Reader(esInfo.language));
|
||||||
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
|
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
|
||||||
if (isSet(FLAG_IGNORE_HDMV_DTS_STREAM)) {
|
if (isSet(FLAG_IGNORE_HDMV_DTS_STREAM)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -87,6 +87,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
public static final int TS_STREAM_TYPE_DTS = 0x8A;
|
public static final int TS_STREAM_TYPE_DTS = 0x8A;
|
||||||
public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82;
|
public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82;
|
||||||
public static final int TS_STREAM_TYPE_E_AC3 = 0x87;
|
public static final int TS_STREAM_TYPE_E_AC3 = 0x87;
|
||||||
|
public static final int TS_STREAM_TYPE_AC4 = 0xAC; // DVB/ATSC AC-4 Descriptor
|
||||||
public static final int TS_STREAM_TYPE_H262 = 0x02;
|
public static final int TS_STREAM_TYPE_H262 = 0x02;
|
||||||
public static final int TS_STREAM_TYPE_H264 = 0x1B;
|
public static final int TS_STREAM_TYPE_H264 = 0x1B;
|
||||||
public static final int TS_STREAM_TYPE_H265 = 0x24;
|
public static final int TS_STREAM_TYPE_H265 = 0x24;
|
||||||
@ -102,6 +103,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
|
|
||||||
private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3");
|
private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3");
|
||||||
private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3");
|
private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3");
|
||||||
|
private static final long AC4_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-4");
|
||||||
private static final long HEVC_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("HEVC");
|
private static final long HEVC_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("HEVC");
|
||||||
|
|
||||||
private static final int BUFFER_SIZE = TS_PACKET_SIZE * 50;
|
private static final int BUFFER_SIZE = TS_PACKET_SIZE * 50;
|
||||||
@ -494,8 +496,11 @@ public final class TsExtractor implements Extractor {
|
|||||||
private static final int TS_PMT_DESC_AC3 = 0x6A;
|
private static final int TS_PMT_DESC_AC3 = 0x6A;
|
||||||
private static final int TS_PMT_DESC_EAC3 = 0x7A;
|
private static final int TS_PMT_DESC_EAC3 = 0x7A;
|
||||||
private static final int TS_PMT_DESC_DTS = 0x7B;
|
private static final int TS_PMT_DESC_DTS = 0x7B;
|
||||||
|
private static final int TS_PMT_DESC_DVB_EXT = 0x7F;
|
||||||
private static final int TS_PMT_DESC_DVBSUBS = 0x59;
|
private static final int TS_PMT_DESC_DVBSUBS = 0x59;
|
||||||
|
|
||||||
|
private static final int TS_PMT_DESC_DVB_EXT_AC4 = 0x15;
|
||||||
|
|
||||||
private final ParsableBitArray pmtScratch;
|
private final ParsableBitArray pmtScratch;
|
||||||
private final SparseArray<TsPayloadReader> trackIdToReaderScratch;
|
private final SparseArray<TsPayloadReader> trackIdToReaderScratch;
|
||||||
private final SparseIntArray trackIdToPidScratch;
|
private final SparseIntArray trackIdToPidScratch;
|
||||||
@ -648,6 +653,8 @@ public final class TsExtractor implements Extractor {
|
|||||||
streamType = TS_STREAM_TYPE_AC3;
|
streamType = TS_STREAM_TYPE_AC3;
|
||||||
} else if (formatIdentifier == E_AC3_FORMAT_IDENTIFIER) {
|
} else if (formatIdentifier == E_AC3_FORMAT_IDENTIFIER) {
|
||||||
streamType = TS_STREAM_TYPE_E_AC3;
|
streamType = TS_STREAM_TYPE_E_AC3;
|
||||||
|
} else if (formatIdentifier == AC4_FORMAT_IDENTIFIER) {
|
||||||
|
streamType = TS_STREAM_TYPE_AC4;
|
||||||
} else if (formatIdentifier == HEVC_FORMAT_IDENTIFIER) {
|
} else if (formatIdentifier == HEVC_FORMAT_IDENTIFIER) {
|
||||||
streamType = TS_STREAM_TYPE_H265;
|
streamType = TS_STREAM_TYPE_H265;
|
||||||
}
|
}
|
||||||
@ -655,6 +662,13 @@ public final class TsExtractor implements Extractor {
|
|||||||
streamType = TS_STREAM_TYPE_AC3;
|
streamType = TS_STREAM_TYPE_AC3;
|
||||||
} else if (descriptorTag == TS_PMT_DESC_EAC3) { // enhanced_AC-3_descriptor
|
} else if (descriptorTag == TS_PMT_DESC_EAC3) { // enhanced_AC-3_descriptor
|
||||||
streamType = TS_STREAM_TYPE_E_AC3;
|
streamType = TS_STREAM_TYPE_E_AC3;
|
||||||
|
} else if (descriptorTag == TS_PMT_DESC_DVB_EXT) {
|
||||||
|
// Extension descriptor in DVB (ETSI EN 300 468).
|
||||||
|
int descriptorTagExt = data.readUnsignedByte();
|
||||||
|
if (descriptorTagExt == TS_PMT_DESC_DVB_EXT_AC4) {
|
||||||
|
// AC-4_descriptor in DVB (ETSI EN 300 468).
|
||||||
|
streamType = TS_STREAM_TYPE_AC4;
|
||||||
|
}
|
||||||
} else if (descriptorTag == TS_PMT_DESC_DTS) { // DTS_descriptor
|
} else if (descriptorTag == TS_PMT_DESC_DTS) { // DTS_descriptor
|
||||||
streamType = TS_STREAM_TYPE_DTS;
|
streamType = TS_STREAM_TYPE_DTS;
|
||||||
} else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) {
|
} else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) {
|
||||||
|
@ -58,6 +58,7 @@ public final class MimeTypes {
|
|||||||
public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3";
|
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_E_AC3 = BASE_TYPE_AUDIO + "/eac3";
|
||||||
public static final String AUDIO_E_AC3_JOC = BASE_TYPE_AUDIO + "/eac3-joc";
|
public static final String AUDIO_E_AC3_JOC = BASE_TYPE_AUDIO + "/eac3-joc";
|
||||||
|
public static final String AUDIO_AC4 = BASE_TYPE_AUDIO + "/ac4";
|
||||||
public static final String AUDIO_TRUEHD = BASE_TYPE_AUDIO + "/true-hd";
|
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 = BASE_TYPE_AUDIO + "/vnd.dts";
|
||||||
public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd";
|
public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd";
|
||||||
@ -228,6 +229,8 @@ public final class MimeTypes {
|
|||||||
return MimeTypes.AUDIO_E_AC3;
|
return MimeTypes.AUDIO_E_AC3;
|
||||||
} else if (codec.startsWith("ec+3")) {
|
} else if (codec.startsWith("ec+3")) {
|
||||||
return MimeTypes.AUDIO_E_AC3_JOC;
|
return MimeTypes.AUDIO_E_AC3_JOC;
|
||||||
|
} else if (codec.startsWith("ac-4") || codec.startsWith("dac4")) {
|
||||||
|
return MimeTypes.AUDIO_AC4;
|
||||||
} else if (codec.startsWith("dtsc") || codec.startsWith("dtse")) {
|
} else if (codec.startsWith("dtsc") || codec.startsWith("dtse")) {
|
||||||
return MimeTypes.AUDIO_DTS;
|
return MimeTypes.AUDIO_DTS;
|
||||||
} else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) {
|
} else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) {
|
||||||
@ -292,6 +295,8 @@ public final class MimeTypes {
|
|||||||
return MimeTypes.AUDIO_DTS_HD;
|
return MimeTypes.AUDIO_DTS_HD;
|
||||||
case 0xAD:
|
case 0xAD:
|
||||||
return MimeTypes.AUDIO_OPUS;
|
return MimeTypes.AUDIO_OPUS;
|
||||||
|
case 0xAE:
|
||||||
|
return MimeTypes.AUDIO_AC4;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -345,6 +350,8 @@ public final class MimeTypes {
|
|||||||
case MimeTypes.AUDIO_E_AC3:
|
case MimeTypes.AUDIO_E_AC3:
|
||||||
case MimeTypes.AUDIO_E_AC3_JOC:
|
case MimeTypes.AUDIO_E_AC3_JOC:
|
||||||
return C.ENCODING_E_AC3;
|
return C.ENCODING_E_AC3;
|
||||||
|
case MimeTypes.AUDIO_AC4:
|
||||||
|
return C.ENCODING_AC4;
|
||||||
case MimeTypes.AUDIO_DTS:
|
case MimeTypes.AUDIO_DTS:
|
||||||
return C.ENCODING_DTS;
|
return C.ENCODING_DTS;
|
||||||
case MimeTypes.AUDIO_DTS_HD:
|
case MimeTypes.AUDIO_DTS_HD:
|
||||||
|
BIN
library/core/src/test/assets/ts/sample.ac4
Normal file
BIN
library/core/src/test/assets/ts/sample.ac4
Normal file
Binary file not shown.
106
library/core/src/test/assets/ts/sample.ac4.0.dump
Normal file
106
library/core/src/test/assets/ts/sample.ac4.0.dump
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
seekMap:
|
||||||
|
isSeekable = false
|
||||||
|
duration = UNSET TIME
|
||||||
|
getPosition(0) = [[timeUs=0, position=0]]
|
||||||
|
numberOfTracks = 1
|
||||||
|
track 0:
|
||||||
|
format:
|
||||||
|
bitrate = -1
|
||||||
|
id = 0
|
||||||
|
containerMimeType = null
|
||||||
|
sampleMimeType = audio/ac4
|
||||||
|
maxInputSize = -1
|
||||||
|
width = -1
|
||||||
|
height = -1
|
||||||
|
frameRate = -1.0
|
||||||
|
rotationDegrees = 0
|
||||||
|
pixelWidthHeightRatio = 1.0
|
||||||
|
channelCount = 2
|
||||||
|
sampleRate = 48000
|
||||||
|
pcmEncoding = -1
|
||||||
|
encoderDelay = 0
|
||||||
|
encoderPadding = 0
|
||||||
|
subsampleOffsetUs = 9223372036854775807
|
||||||
|
selectionFlags = 0
|
||||||
|
language = null
|
||||||
|
drmInitData = -
|
||||||
|
initializationData:
|
||||||
|
total output bytes = 7594
|
||||||
|
sample count = 19
|
||||||
|
sample 0:
|
||||||
|
time = 0
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash B4277F9E
|
||||||
|
sample 1:
|
||||||
|
time = 40000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash E8E0A142
|
||||||
|
sample 2:
|
||||||
|
time = 80000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash 2E5073D0
|
||||||
|
sample 3:
|
||||||
|
time = 120000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash 850E71D8
|
||||||
|
sample 4:
|
||||||
|
time = 160000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash 69CD444E
|
||||||
|
sample 5:
|
||||||
|
time = 200000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash BD24F36D
|
||||||
|
sample 6:
|
||||||
|
time = 240000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash E24F2490
|
||||||
|
sample 7:
|
||||||
|
time = 280000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash EE6F1F06
|
||||||
|
sample 8:
|
||||||
|
time = 320000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash 2DAB000F
|
||||||
|
sample 9:
|
||||||
|
time = 360000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash 8102B7EC
|
||||||
|
sample 10:
|
||||||
|
time = 400000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash 55BF59AC
|
||||||
|
sample 11:
|
||||||
|
time = 440000
|
||||||
|
flags = 1
|
||||||
|
data = length 494, hash CBC2E09F
|
||||||
|
sample 12:
|
||||||
|
time = 480000
|
||||||
|
flags = 1
|
||||||
|
data = length 519, hash 9DAF56E9
|
||||||
|
sample 13:
|
||||||
|
time = 520000
|
||||||
|
flags = 1
|
||||||
|
data = length 598, hash 8169EE2
|
||||||
|
sample 14:
|
||||||
|
time = 560000
|
||||||
|
flags = 1
|
||||||
|
data = length 435, hash 28C21246
|
||||||
|
sample 15:
|
||||||
|
time = 600000
|
||||||
|
flags = 1
|
||||||
|
data = length 365, hash FF14716D
|
||||||
|
sample 16:
|
||||||
|
time = 640000
|
||||||
|
flags = 1
|
||||||
|
data = length 392, hash 4CC96B29
|
||||||
|
sample 17:
|
||||||
|
time = 680000
|
||||||
|
flags = 1
|
||||||
|
data = length 373, hash D7AC6D4E
|
||||||
|
sample 18:
|
||||||
|
time = 720000
|
||||||
|
flags = 1
|
||||||
|
data = length 392, hash 99F2511F
|
||||||
|
tracksEnded = true
|
@ -26,6 +26,7 @@ import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
|
|||||||
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
|
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ogg.OggExtractor;
|
import com.google.android.exoplayer2.extractor.ogg.OggExtractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.Ac4Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
||||||
@ -62,7 +63,8 @@ public final class DefaultExtractorsFactoryTest {
|
|||||||
OggExtractor.class,
|
OggExtractor.class,
|
||||||
PsExtractor.class,
|
PsExtractor.class,
|
||||||
WavExtractor.class,
|
WavExtractor.class,
|
||||||
AmrExtractor.class
|
AmrExtractor.class,
|
||||||
|
Ac4Extractor.class
|
||||||
};
|
};
|
||||||
|
|
||||||
assertThat(listCreatedExtractorClasses).containsNoDuplicates();
|
assertThat(listCreatedExtractorClasses).containsNoDuplicates();
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.android.exoplayer2.testutil.ExtractorAsserts;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Unit test for {@link Ac4Extractor}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public final class Ac4ExtractorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAc4Sample() throws Exception {
|
||||||
|
ExtractorAsserts.assertBehavior(Ac4Extractor::new, "ts/sample.ac4");
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ import com.google.android.exoplayer2.extractor.ExtractorInput;
|
|||||||
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
|
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
|
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.Ac4Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
|
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
|
||||||
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
||||||
@ -43,6 +44,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
public static final String AAC_FILE_EXTENSION = ".aac";
|
public static final String AAC_FILE_EXTENSION = ".aac";
|
||||||
public static final String AC3_FILE_EXTENSION = ".ac3";
|
public static final String AC3_FILE_EXTENSION = ".ac3";
|
||||||
public static final String EC3_FILE_EXTENSION = ".ec3";
|
public static final String EC3_FILE_EXTENSION = ".ec3";
|
||||||
|
public static final String AC4_FILE_EXTENSION = ".ac4";
|
||||||
public static final String MP3_FILE_EXTENSION = ".mp3";
|
public static final String MP3_FILE_EXTENSION = ".mp3";
|
||||||
public static final String MP4_FILE_EXTENSION = ".mp4";
|
public static final String MP4_FILE_EXTENSION = ".mp4";
|
||||||
public static final String M4_FILE_EXTENSION_PREFIX = ".m4";
|
public static final String M4_FILE_EXTENSION_PREFIX = ".m4";
|
||||||
@ -128,6 +130,13 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(extractorByFileExtension instanceof Ac4Extractor)) {
|
||||||
|
Ac4Extractor ac4Extractor = new Ac4Extractor();
|
||||||
|
if (sniffQuietly(ac4Extractor, extractorInput)) {
|
||||||
|
return buildResult(ac4Extractor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!(extractorByFileExtension instanceof Mp3Extractor)) {
|
if (!(extractorByFileExtension instanceof Mp3Extractor)) {
|
||||||
Mp3Extractor mp3Extractor =
|
Mp3Extractor mp3Extractor =
|
||||||
new Mp3Extractor(/* flags= */ 0, /* forcedFirstSampleTimestampUs= */ 0);
|
new Mp3Extractor(/* flags= */ 0, /* forcedFirstSampleTimestampUs= */ 0);
|
||||||
@ -181,6 +190,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
} else if (lastPathSegment.endsWith(AC3_FILE_EXTENSION)
|
} else if (lastPathSegment.endsWith(AC3_FILE_EXTENSION)
|
||||||
|| lastPathSegment.endsWith(EC3_FILE_EXTENSION)) {
|
|| lastPathSegment.endsWith(EC3_FILE_EXTENSION)) {
|
||||||
return new Ac3Extractor();
|
return new Ac3Extractor();
|
||||||
|
} else if (lastPathSegment.endsWith(AC4_FILE_EXTENSION)) {
|
||||||
|
return new Ac4Extractor();
|
||||||
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
|
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
|
||||||
return new Mp3Extractor(/* flags= */ 0, /* forcedFirstSampleTimestampUs= */ 0);
|
return new Mp3Extractor(/* flags= */ 0, /* forcedFirstSampleTimestampUs= */ 0);
|
||||||
} else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)
|
} else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)
|
||||||
@ -250,6 +261,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
return buildResult(new AdtsExtractor());
|
return buildResult(new AdtsExtractor());
|
||||||
} else if (previousExtractor instanceof Ac3Extractor) {
|
} else if (previousExtractor instanceof Ac3Extractor) {
|
||||||
return buildResult(new Ac3Extractor());
|
return buildResult(new Ac3Extractor());
|
||||||
|
} else if (previousExtractor instanceof Ac4Extractor) {
|
||||||
|
return buildResult(new Ac4Extractor());
|
||||||
} else if (previousExtractor instanceof Mp3Extractor) {
|
} else if (previousExtractor instanceof Mp3Extractor) {
|
||||||
return buildResult(new Mp3Extractor());
|
return buildResult(new Mp3Extractor());
|
||||||
} else {
|
} else {
|
||||||
@ -262,6 +275,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
extractor,
|
extractor,
|
||||||
extractor instanceof AdtsExtractor
|
extractor instanceof AdtsExtractor
|
||||||
|| extractor instanceof Ac3Extractor
|
|| extractor instanceof Ac3Extractor
|
||||||
|
|| extractor instanceof Ac4Extractor
|
||||||
|| extractor instanceof Mp3Extractor,
|
|| extractor instanceof Mp3Extractor,
|
||||||
isReusable(extractor));
|
isReusable(extractor));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user