Add AC-4 format support

* Add AC-4 MIME type definition
 * Add AC-4 format support in Mp4Extractor and TsExtractor
 * Add AC-4 Extractor
 * Add AC-4 playback support in MPEG-4, MPEG-DASH, TS and HLS
This commit is contained in:
ybai001 2018-12-24 09:38:52 +08:00 committed by ybai
parent 246d464466
commit ae40208d92
15 changed files with 729 additions and 5 deletions

View File

@ -143,8 +143,8 @@ public final class C {
* {@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_MU_LAW}, {@link #ENCODING_PCM_A_LAW}, {@link #ENCODING_AC3}, {@link
* #ENCODING_E_AC3}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or {@link
* #ENCODING_DOLBY_TRUEHD}.
* #ENCODING_E_AC3}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD}, {@link
* #ENCODING_DOLBY_TRUEHD} or {@link #ENCODING_AC4}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@ -162,7 +162,8 @@ public final class C {
ENCODING_E_AC3,
ENCODING_DTS,
ENCODING_DTS_HD,
ENCODING_DOLBY_TRUEHD
ENCODING_DOLBY_TRUEHD,
ENCODING_AC4
})
public @interface Encoding {}
@ -212,6 +213,8 @@ public final class C {
public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD;
/** @see AudioFormat#ENCODING_DOLBY_TRUEHD */
public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD;
/** @see AudioFormat#ENCODING_AC4 */
public static final int ENCODING_AC4 = AudioFormat.ENCODING_AC4;
/**
* Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link

View File

@ -0,0 +1,269 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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 sample mime type of the bitstream is {@link MimeTypes#AUDIO_AC4}.
*/
public final String mimeType;
/**
* 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(
String mimeType,
int bitstreamVersion,
int channelCount,
int sampleRate,
int frameSize,
int sampleCount) {
this.mimeType = mimeType;
this.bitstreamVersion = bitstreamVersion;
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.frameSize = frameSize;
this.sampleCount = sampleCount;
}
}
/**
* The channel count of AC-4 stream.
*/
// TODO: Parse AC-4 stream channel count.
public 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 48kHz. For
* 44.1kHz, 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);
int sampleRate = ((data.readUnsignedByte() & 0x20) >> 5 == 1) ? 48000 : 44100;
return Format.createAudioSampleFormat(
trackId,
MimeTypes.AUDIO_AC4,
null,
Format.NO_VALUE,
Format.NO_VALUE,
CHANNEL_COUNT_2,
sampleRate,
null,
drmInitData,
0,
language);
}
private static int readVariableBits(ParsableBitArray data, int nbits) {
int value = 0;
while (true) {
int moreBits;
value += data.readBits(nbits);
moreBits = data.readBits(1);
if (moreBits == 0)
break;
value++;
value <<= nbits;
}
return value;
}
/**
* 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;
}
frameSize += headerSize;
if (syncWord == 0xAC41) {
frameSize += 2;
}
int bitstreamVersion = data.readBits(2);
if (bitstreamVersion == 3){
bitstreamVersion += readVariableBits(data, 2);
}
int sequenceCounter = data.readBits(10);
if (data.readBits(1) == 1) {
if (data.readBits(3) > 0){
data.skipBits(2);
}
}
int sampleRate = (data.readBits(1) == 1) ? 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:
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(
MimeTypes.AUDIO_AC4, bitstreamVersion, CHANNEL_COUNT_2, sampleRate, frameSize, sampleCount);
}
/**
* Returns the size in bytes of the given AC-4 syncframe.
*
* @param data The syncframe to parse.
* @return The syncframe size in bytes. {@link C#LENGTH_UNSET} if the input is invalid.
*/
public static int parseAc4SyncframeSize(byte[] data, int syncBytes) {
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 (syncBytes == 0xAC41) {
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];
buffer.get(bufferBytes);
ParsableBitArray data = new ParsableBitArray(bufferBytes);
SyncFrameInfo ac4SyncframeInfo = parseAc4SyncframeInfo(data);
return ac4SyncframeInfo.sampleCount;
}
/**
* Create AC-4 sample header, which includes AC-4 syncword and frame size.
*
* @param sampleSize The size of AC-4 sample.
* @return The AC-4 sample header byte array.
*/
public static ParsableByteArray getAc4SampleHeader(int sampleSize) {
// Add AC-4 Syncword 0xAC40 and frame size to create AC-4 syncframe
// ETSI TS 103 190-1 V1.3.1, Annex G
byte[] ac4SampleHeader = new byte[] {(byte)0xAC, 0x40, (byte)0xFF, (byte)0xFF,
(byte)(sampleSize >> 16 & 0xFF), (byte)(sampleSize >> 8 & 0xFF), (byte)(sampleSize & 0xFF)};
return new ParsableByteArray(ac4SampleHeader);
}
private Ac4Util() {}
}

View File

@ -1202,6 +1202,8 @@ public final class DefaultAudioSink implements AudioSink {
return 18000 * 1000 / 8;
case C.ENCODING_DOLBY_TRUEHD:
return 24500 * 1000 / 8;
case C.ENCODING_AC4:
return 2688 * 1000 / 8;
case C.ENCODING_INVALID:
case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_24BIT:
@ -1229,6 +1231,8 @@ public final class DefaultAudioSink implements AudioSink {
? 0
: (Ac3Util.parseTrueHdSyncframeAudioSampleCount(buffer, syncframeOffset)
* Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT);
} else if (encoding == C.ENCODING_AC4) {
return Ac4Util.parseAc4SyncframeAudioSampleCount(buffer);
} else {
throw new IllegalStateException("Unexpected audio encoding: " + encoding);
}

View File

@ -787,6 +787,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate);
}
}
if (MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) {
// Some devices handle AC-4 raw frame by default.
// Need to notify the codec to handle AC-4 sync frame.
mediaFormat.setInteger("ac4-is-sync", 1);
}
return mediaFormat;
}

View File

@ -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.ogg.OggExtractor;
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.DefaultTsPayloadReaderFactory;
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
@ -206,7 +207,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
@Override
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[1] = new FragmentedMp4Extractor(fragmentedMp4Flags);
extractors[2] = new Mp4Extractor(mp4Flags);
@ -235,9 +236,10 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
| (constantBitrateSeekingEnabled
? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
: 0));
extractors[12] = new Ac4Extractor();
if (FLAC_EXTRACTOR_CONSTRUCTOR != null) {
try {
extractors[12] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance();
extractors[13] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance();
} catch (Exception e) {
// Should never happen.
throw new IllegalStateException("Unexpected error creating FLAC extractor", e);

View File

@ -67,6 +67,8 @@ import java.util.List;
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_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_dtsh = Util.getIntegerCodeForString("dtsh");
public static final int TYPE_dtsl = Util.getIntegerCodeForString("dtsl");

View File

@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
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.extractor.GaplessInfoHolder;
import com.google.android.exoplayer2.metadata.Metadata;
@ -684,6 +685,7 @@ import java.util.List;
|| childAtomType == Atom.TYPE_enca
|| childAtomType == Atom.TYPE_ac_3
|| childAtomType == Atom.TYPE_ec_3
|| childAtomType == Atom.TYPE_ac_4
|| childAtomType == Atom.TYPE_dtsc
|| childAtomType == Atom.TYPE_dtse
|| childAtomType == Atom.TYPE_dtsh
@ -974,6 +976,8 @@ import java.util.List;
mimeType = MimeTypes.AUDIO_AC3;
} else if (atomType == Atom.TYPE_ec_3) {
mimeType = MimeTypes.AUDIO_E_AC3;
} else if (atomType == Atom.TYPE_ac_4) {
mimeType = MimeTypes.AUDIO_AC4;
} else if (atomType == Atom.TYPE_dtsc) {
mimeType = MimeTypes.AUDIO_DTS;
} else if (atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl) {
@ -1031,6 +1035,10 @@ import java.util.List;
parent.setPosition(Atom.HEADER_SIZE + childPosition);
out.format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language,
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) {
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0,

View File

@ -22,6 +22,7 @@ import android.util.SparseArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
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.SchemeData;
import com.google.android.exoplayer2.extractor.ChunkIndex;
@ -1276,10 +1277,17 @@ public class FragmentedMp4Extractor implements Extractor {
}
}
} else {
int sampleHeaderSize = 0;
if (MimeTypes.AUDIO_AC4.equals(track.format.sampleMimeType)) {
ParsableByteArray ac4SampleHeaderData = Ac4Util.getAc4SampleHeader(sampleSize);
output.sampleData(ac4SampleHeaderData, ac4SampleHeaderData.capacity());
sampleHeaderSize = ac4SampleHeaderData.capacity();
}
while (sampleBytesWritten < sampleSize) {
int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false);
sampleBytesWritten += writtenBytes;
}
sampleSize += sampleHeaderSize;
}
@C.BufferFlags int sampleFlags = fragment.sampleIsSyncFrameTable[sampleIndex]

View File

@ -19,6 +19,7 @@ import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
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.ExtractorInput;
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.metadata.Metadata;
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.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
@ -536,11 +538,18 @@ public final class Mp4Extractor implements Extractor, SeekMap {
}
}
} else {
int sampleHeaderSize = 0;
if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType)) {
ParsableByteArray ac4SampleHeaderData = Ac4Util.getAc4SampleHeader(sampleSize);
trackOutput.sampleData(ac4SampleHeaderData, ac4SampleHeaderData.capacity());
sampleHeaderSize = ac4SampleHeaderData.capacity();
}
while (sampleBytesWritten < sampleSize) {
int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false);
sampleBytesWritten += writtenBytes;
sampleCurrentNalBytesRemaining -= writtenBytes;
}
sampleSize += sampleHeaderSize;
}
trackOutput.sampleMetadata(track.sampleTable.timestampsUs[sampleIndex],
track.sampleTable.flags[sampleIndex], sampleSize, 0, null);

View File

@ -0,0 +1,162 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.extractor.ts;
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;
private static final int AC40_SYNC_WORD = 0xAC40;
private static final int AC41_SYNC_WORD = 0xAC41;
/**
* Typical AC-4 frame size (in bytes)
* The value 16 kB is determined by the maximum frame size used in broadcast applications.
*/
private static final int FRAMELEN_TYPICAL = 16384; /* in bytes (16 kB) */
private static final int ID3_TAG = Util.getIntegerCodeForString("ID3");
private final long firstSampleTimestampUs;
private final Ac4Reader reader;
private final ParsableByteArray sampleData;
private boolean startedPacket;
public Ac4Extractor() {
this(0);
}
public Ac4Extractor(long firstSampleTimestampUs) {
this.firstSampleTimestampUs = firstSampleTimestampUs;
reader = new Ac4Reader();
sampleData = new ParsableByteArray(FRAMELEN_TYPICAL);
}
// 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, 0, 10);
scratch.setPosition(0);
if (scratch.readUnsignedInt24() != ID3_TAG) {
break;
}
scratch.skipBytes(3);
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, 0, 7);
scratch.setPosition(0);
int syncBytes = scratch.readUnsignedShort();
if (syncBytes != AC40_SYNC_WORD && syncBytes != AC41_SYNC_WORD) {
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 - 7);
}
}
}
@Override
public void init(ExtractorOutput output) {
reader.createTracks(output, new TrackIdGenerator(0, 1));
output.endTracks();
output.seekMap(new SeekMap.Unseekable(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, 0, FRAMELEN_TYPICAL);
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;
}
}

View File

@ -0,0 +1,215 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.extractor.ts;
import android.support.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.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] = 0x40;
if (hasCRC)
headerScratchBytes.data[1] = 0x41;
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();
if (secondByte == 0x40 || secondByte == 0x41) {
lastByteWasAC = false;
hasCRC = (secondByte == 0x41);
return true;
} else {
lastByteWasAC = (secondByte == 0xAC);
}
}
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
|| frameInfo.mimeType != format.sampleMimeType) {
format = Format.createAudioSampleFormat(trackFormatId, frameInfo.mimeType, null,
Format.NO_VALUE, Format.NO_VALUE, frameInfo.channelCount, frameInfo.sampleRate, null,
null, 0, language);
output.format(format);
}
sampleSize = frameInfo.frameSize;
// In this class a sample is an AC-4 sync frame, but the MediaFormat sample rate specifies the
// number of PCM audio samples per second.
sampleDurationUs = C.MICROS_PER_SECOND * frameInfo.sampleCount / format.sampleRate;
}
}

View File

@ -142,6 +142,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
case TsExtractor.TS_STREAM_TYPE_AC3:
case TsExtractor.TS_STREAM_TYPE_E_AC3:
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_DTS:
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
return new PesReader(new DtsReader(esInfo.language));

View File

@ -85,6 +85,7 @@ public final class TsExtractor implements Extractor {
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_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_H264 = 0x1B;
public static final int TS_STREAM_TYPE_H265 = 0x24;
@ -100,6 +101,7 @@ public final class TsExtractor implements Extractor {
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 AC4_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-4");
private static final long HEVC_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("HEVC");
private static final int BUFFER_SIZE = TS_PACKET_SIZE * 50;
@ -484,8 +486,11 @@ public final class TsExtractor implements Extractor {
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_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_DVB_EXT_AC4 = 0x15;
private final ParsableBitArray pmtScratch;
private final SparseArray<TsPayloadReader> trackIdToReaderScratch;
private final SparseIntArray trackIdToPidScratch;
@ -638,6 +643,8 @@ public final class TsExtractor implements Extractor {
streamType = TS_STREAM_TYPE_AC3;
} else if (formatIdentifier == E_AC3_FORMAT_IDENTIFIER) {
streamType = TS_STREAM_TYPE_E_AC3;
} else if (formatIdentifier == AC4_FORMAT_IDENTIFIER) {
streamType = TS_STREAM_TYPE_AC4;
} else if (formatIdentifier == HEVC_FORMAT_IDENTIFIER) {
streamType = TS_STREAM_TYPE_H265;
}
@ -645,6 +652,13 @@ public final class TsExtractor implements Extractor {
streamType = TS_STREAM_TYPE_AC3;
} else if (descriptorTag == TS_PMT_DESC_EAC3) { // enhanced_AC-3_descriptor
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
streamType = TS_STREAM_TYPE_DTS;
} else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) {

View File

@ -55,6 +55,7 @@ public final class MimeTypes {
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_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_DTS = BASE_TYPE_AUDIO + "/vnd.dts";
public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd";
@ -237,6 +238,8 @@ public final class MimeTypes {
return MimeTypes.AUDIO_E_AC3;
} else if (codec.startsWith("ec+3")) {
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")) {
return MimeTypes.AUDIO_DTS;
} else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) {
@ -301,6 +304,8 @@ public final class MimeTypes {
return MimeTypes.AUDIO_DTS_HD;
case 0xAD:
return MimeTypes.AUDIO_OPUS;
case 0xAE:
return MimeTypes.AUDIO_AC4;
default:
return null;
}
@ -354,6 +359,8 @@ public final class MimeTypes {
case MimeTypes.AUDIO_E_AC3:
case MimeTypes.AUDIO_E_AC3_JOC:
return C.ENCODING_E_AC3;
case MimeTypes.AUDIO_AC4:
return C.ENCODING_AC4;
case MimeTypes.AUDIO_DTS:
return C.ENCODING_DTS;
case MimeTypes.AUDIO_DTS_HD:

View File

@ -25,6 +25,7 @@ import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
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.DefaultTsPayloadReaderFactory;
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 AC3_FILE_EXTENSION = ".ac3";
public static final String AC4_FILE_EXTENSION = ".ac4";
public static final String EC3_FILE_EXTENSION = ".ec3";
public static final String MP3_FILE_EXTENSION = ".mp3";
public static final String MP4_FILE_EXTENSION = ".mp4";
@ -94,6 +96,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
return buildResult(new AdtsExtractor());
} else if (previousExtractor instanceof Ac3Extractor) {
return buildResult(new Ac3Extractor());
} else if (previousExtractor instanceof Ac4Extractor) {
return buildResult(new Ac4Extractor());
} else if (previousExtractor instanceof Mp3Extractor) {
return buildResult(new Mp3Extractor());
} else {
@ -135,6 +139,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)) {
Mp3Extractor mp3Extractor =
new Mp3Extractor(/* flags= */ 0, /* forcedFirstSampleTimestampUs= */ 0);
@ -188,6 +199,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
} else if (lastPathSegment.endsWith(AC3_FILE_EXTENSION)
|| lastPathSegment.endsWith(EC3_FILE_EXTENSION)) {
return new Ac3Extractor();
} else if (lastPathSegment.endsWith(AC4_FILE_EXTENSION)) {
return new Ac4Extractor();
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
return new Mp3Extractor(/* flags= */ 0, /* forcedFirstSampleTimestampUs= */ 0);
} else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)
@ -254,6 +267,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
extractor,
extractor instanceof AdtsExtractor
|| extractor instanceof Ac3Extractor
|| extractor instanceof Ac4Extractor
|| extractor instanceof Mp3Extractor);
}