Commit ad7237b5 by Oliver Woodman

Handle E-AC-3 audio in HLS.

parent 9fd575e1
......@@ -941,9 +941,9 @@ public final class AudioTrack {
| ((buffer.get(buffer.position() + 5) & 0xFC) >> 2);
return (nblks + 1) * 32;
} else if (encoding == C.ENCODING_AC3) {
return Ac3Util.getAc3SamplesPerSyncframe();
return Ac3Util.getAc3SyncframeAudioSampleCount();
} else if (encoding == C.ENCODING_E_AC3) {
return Ac3Util.parseEac3SamplesPerSyncframe(buffer);
return Ac3Util.parseEAc3SyncframeAudioSampleCount(buffer);
} else {
throw new IllegalStateException("Unexpected audio encoding: " + encoding);
}
......
......@@ -774,12 +774,12 @@ import java.util.List;
// TODO: Choose the right AC-3 track based on the contents of dac3/dec3.
// TODO: Add support for encryption (by setting out.trackEncryptionBoxes).
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
out.mediaFormat = Ac3Util.parseAnnexFAc3Format(parent, Integer.toString(trackId),
out.mediaFormat = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId),
durationUs, language);
return;
} else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
out.mediaFormat = Ac3Util.parseAnnexFEAc3Format(parent, Integer.toString(trackId),
out.mediaFormat = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId),
durationUs, language);
return;
} else if ((atomType == Atom.TYPE_dtsc || atomType == Atom.TYPE_dtse
......
......@@ -23,7 +23,7 @@ import com.google.android.exoplayer.util.ParsableBitArray;
import com.google.android.exoplayer.util.ParsableByteArray;
/**
* Parses a continuous AC-3 byte stream and extracts individual samples.
* Parses a continuous (E-)AC-3 byte stream and extracts individual samples.
*/
/* package */ final class Ac3Reader extends ElementaryStreamReader {
......@@ -33,6 +33,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
private static final int HEADER_SIZE = 8;
private final boolean isEac3;
private final ParsableBitArray headerScratchBits;
private final ParsableByteArray headerScratchBytes;
......@@ -43,16 +44,23 @@ import com.google.android.exoplayer.util.ParsableByteArray;
private boolean lastByteWas0B;
// Used when parsing the header.
private long frameDurationUs;
private long sampleDurationUs;
private MediaFormat mediaFormat;
private int sampleSize;
private int bitrate;
// Used when reading the samples.
private long timeUs;
public Ac3Reader(TrackOutput output) {
/**
* Constructs a new reader for (E-)AC-3 elementary streams.
*
* @param output Track output for extracted samples.
* @param isEac3 Whether the stream is E-AC-3 (ETSI TS 102 366 Annex E). Specify {@code false} to
* parse sample headers as AC-3.
*/
public Ac3Reader(TrackOutput output, boolean isEac3) {
super(output);
this.isEac3 = isEac3;
headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]);
headerScratchBytes = new ParsableByteArray(headerScratchBits.data);
state = STATE_FINDING_SYNC;
......@@ -94,7 +102,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
bytesRead += bytesToRead;
if (bytesRead == sampleSize) {
output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
timeUs += frameDurationUs;
timeUs += sampleDurationUs;
state = STATE_FINDING_SYNC;
}
break;
......@@ -124,11 +132,11 @@ import com.google.android.exoplayer.util.ParsableByteArray;
}
/**
* Locates the next sync word, advancing the position to the byte that immediately follows it.
* If a sync word was not located, the position is advanced to the limit.
* 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 True if a sync word position was found. False otherwise.
* @return True if a syncword position was found. False otherwise.
*/
private boolean skipToNextSync(ParsableByteArray pesBuffer) {
while (pesBuffer.bytesLeft() > 0) {
......@@ -151,15 +159,21 @@ import com.google.android.exoplayer.util.ParsableByteArray;
* Parses the sample header.
*/
private void parseHeader() {
headerScratchBits.setPosition(0);
sampleSize = Ac3Util.parseFrameSize(headerScratchBits);
if (mediaFormat == null) {
headerScratchBits.setPosition(0);
mediaFormat = Ac3Util.parseFrameAc3Format(headerScratchBits, null, C.UNKNOWN_TIME_US, null);
mediaFormat = isEac3
? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, C.UNKNOWN_TIME_US, null)
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, C.UNKNOWN_TIME_US, null);
output.format(mediaFormat);
bitrate = Ac3Util.getBitrate(sampleSize, mediaFormat.sampleRate);
}
frameDurationUs = (int) (1000L * 8 * sampleSize / bitrate);
sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data)
: Ac3Util.parseAc3SyncframeSize(headerScratchBits.data);
int audioSamplesPerSyncframe = isEac3
? Ac3Util.parseEAc3SyncframeAudioSampleCount(headerScratchBits.data)
: Ac3Util.getAc3SyncframeAudioSampleCount();
// In this class a sample is an access unit (syncframe in AC-3), but the MediaFormat sample rate
// specifies the number of PCM audio samples per second.
sampleDurationUs =
(int) (C.MICROS_PER_SECOND * audioSamplesPerSyncframe / mediaFormat.sampleRate);
}
}
......@@ -50,7 +50,7 @@ import java.util.Collections;
// Used when parsing the header.
private boolean hasOutputFormat;
private long frameDurationUs;
private long sampleDurationUs;
private int sampleSize;
// Used when reading the samples.
......@@ -96,7 +96,7 @@ import java.util.Collections;
bytesRead += bytesToRead;
if (bytesRead == sampleSize) {
output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
timeUs += frameDurationUs;
timeUs += sampleDurationUs;
bytesRead = 0;
state = STATE_FINDING_SYNC;
}
......@@ -173,7 +173,9 @@ import java.util.Collections;
MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, audioParams.second,
audioParams.first, Collections.singletonList(audioSpecificConfig), null);
frameDurationUs = (C.MICROS_PER_SECOND * 1024L) / mediaFormat.sampleRate;
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the
// number of PCM audio samples per second.
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / mediaFormat.sampleRate;
output.format(mediaFormat);
hasOutputFormat = true;
} else {
......
......@@ -310,9 +310,11 @@ public final class TsExtractor implements Extractor {
case TS_STREAM_TYPE_AAC:
pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC));
break;
case TS_STREAM_TYPE_E_AC3:
case TS_STREAM_TYPE_AC3:
pesPayloadReader = new Ac3Reader(output.track(streamType));
pesPayloadReader = new Ac3Reader(output.track(TS_STREAM_TYPE_AC3), false);
break;
case TS_STREAM_TYPE_E_AC3:
pesPayloadReader = new Ac3Reader(output.track(TS_STREAM_TYPE_E_AC3), true);
break;
case TS_STREAM_TYPE_H262:
pesPayloadReader = new H262Reader(output.track(TS_STREAM_TYPE_H262));
......
......@@ -20,61 +20,62 @@ import com.google.android.exoplayer.MediaFormat;
import java.nio.ByteBuffer;
/**
* Utility methods for parsing AC-3 headers.
* Utility methods for parsing (E-)AC-3 syncframes, which are access units in (E-)AC-3 bitstreams.
*/
public final class Ac3Util {
/**
* The number of new samples per (E-)AC-3 audio block.
*/
private static final int SAMPLES_PER_AUDIO_BLOCK = 256;
private static final int AUDIO_SAMPLES_PER_AUDIO_BLOCK = 256;
/**
* Each syncframe has 6 blocks that provide 256 new samples. See ETSI TS 102 366 subsection 4.1.
* Each syncframe has 6 blocks that provide 256 new audio samples. See ETSI TS 102 366 4.1.
*/
private static final int AC3_SAMPLES_PER_SYNCFRAME = 6 * SAMPLES_PER_AUDIO_BLOCK;
private static final int AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT = 6 * AUDIO_SAMPLES_PER_AUDIO_BLOCK;
/**
* Number of audio blocks per E-AC-3 sync frame, indexed by numblkscod.
* Number of audio blocks per E-AC-3 syncframe, indexed by numblkscod.
*/
private static final int[] AUDIO_BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD = new int[] {1, 2, 3, 6};
private static final int[] BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD = new int[] {1, 2, 3, 6};
/**
* Sample rates, indexed by fscod.
*/
private static final int[] SAMPLE_RATES = new int[] {48000, 44100, 32000};
private static final int[] SAMPLE_RATE_BY_FSCOD = new int[] {48000, 44100, 32000};
/**
* Sample rates, indexed by fscod2 (E-AC-3).
*/
private static final int[] SAMPLE_RATE_BY_FSCOD2 = new int[] {24000, 22050, 16000};
/**
* Channel counts, indexed by acmod.
*/
private static final int[] CHANNEL_COUNTS = new int[] {2, 1, 2, 3, 3, 4, 4, 5};
private static final int[] CHANNEL_COUNT_BY_ACMOD = new int[] {2, 1, 2, 3, 3, 4, 4, 5};
/**
* Nominal bitrates in kbps, indexed by bit_rate_code.
* Nominal bitrates in kbps, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.)
*/
private static final int[] BITRATES = new int[] {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192,
224, 256, 320, 384, 448, 512, 576, 640};
private static final int[] BITRATE_BY_HALF_FRMSIZECOD = new int[] {32, 40, 48, 56, 64, 80, 96,
112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640};
/**
* 16-bit words per sync frame, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.)
* 16-bit words per syncframe, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.)
*/
private static final int[] FRMSIZECOD_TO_FRAME_SIZE_44_1 = new int[] {69, 87, 104, 121, 139, 174,
208, 243, 278, 348, 417, 487, 557, 696, 835, 975, 1114, 1253, 1393};
private static final int[] SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1 = new int[] {69, 87, 104,
121, 139, 174, 208, 243, 278, 348, 417, 487, 557, 696, 835, 975, 1114, 1253, 1393};
/**
* Returns the AC-3 format given {@code data} containing the AC3SpecificBox according to
* ETSI TS 102 366 Annex F.
* ETSI TS 102 366 Annex F. The reading position of {@code data} will be modified.
*
* @param data The AC3SpecificBox.
* @param data The AC3SpecificBox to parse.
* @param trackId The track identifier to set on the format, or null.
* @param durationUs The duration to set on the format, in microseconds.
* @param language The language to set on the format.
* @return The AC-3 format parsed from data in the header.
*/
public static MediaFormat parseAnnexFAc3Format(ParsableByteArray data, String trackId,
public static MediaFormat parseAc3AnnexFFormat(ParsableByteArray data, String trackId,
long durationUs, String language) {
// fscod (sample rate code)
int fscod = (data.readUnsignedByte() & 0xC0) >> 6;
int sampleRate = SAMPLE_RATES[fscod];
int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
int nextByte = data.readUnsignedByte();
// Map acmod (audio coding mode) onto a channel count.
int channelCount = CHANNEL_COUNTS[(nextByte & 0x38) >> 3];
// lfeon (low frequency effects on)
if ((nextByte & 0x04) != 0) {
int channelCount = CHANNEL_COUNT_BY_ACMOD[(nextByte & 0x38) >> 3];
if ((nextByte & 0x04) != 0) { // lfeon
channelCount++;
}
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE,
......@@ -83,28 +84,25 @@ public final class Ac3Util {
/**
* Returns the E-AC-3 format given {@code data} containing the EC3SpecificBox according to
* ETSI TS 102 366 Annex F.
* ETSI TS 102 366 Annex F. The reading position of {@code data} will be modified.
*
* @param data The EC3SpecificBox.
* @param data The EC3SpecificBox to parse.
* @param trackId The track identifier to set on the format, or null.
* @param durationUs The duration to set on the format, in microseconds.
* @param language The language to set on the format.
* @return The E-AC-3 format parsed from data in the header.
*/
public static MediaFormat parseAnnexFEAc3Format(ParsableByteArray data, String trackId,
public static MediaFormat parseEAc3AnnexFFormat(ParsableByteArray data, String trackId,
long durationUs, String language) {
data.skipBytes(2); // Skip data_rate and num_ind_sub.
data.skipBytes(2); // data_rate, num_ind_sub
// Read only the first substream.
// TODO: Read later substreams?
// fscod (sample rate code)
int fscod = (data.readUnsignedByte() & 0xC0) >> 6;
int sampleRate = SAMPLE_RATES[fscod];
int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
int nextByte = data.readUnsignedByte();
// Map acmod (audio coding mode) onto a channel count.
int channelCount = CHANNEL_COUNTS[(nextByte & 0x0E) >> 1];
// lfeon (low frequency effects on)
if ((nextByte & 0x01) != 0) {
int channelCount = CHANNEL_COUNT_BY_ACMOD[(nextByte & 0x0E) >> 1];
if ((nextByte & 0x01) != 0) { // lfeon
channelCount++;
}
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_E_AC3, MediaFormat.NO_VALUE,
......@@ -112,22 +110,20 @@ public final class Ac3Util {
}
/**
* Returns the AC-3 format given {@code data} containing the frame header starting from the sync
* word.
* Returns the AC-3 format given {@code data} containing a syncframe. The reading position of
* {@code data} will be modified.
*
* @param data Data to parse, positioned at the start of the syncword.
* @param data The data to parse, positioned at the start of the syncframe.
* @param trackId The track identifier to set on the format, or null.
* @param durationUs The duration to set on the format, in microseconds.
* @param language The language to set on the format.
* @return The AC-3 format parsed from data in the header.
*/
public static MediaFormat parseFrameAc3Format(ParsableBitArray data, String trackId,
public static MediaFormat parseAc3SyncframeFormat(ParsableBitArray data, String trackId,
long durationUs, String language) {
// Skip syncword and crc1.
data.skipBits(4 * 8);
data.skipBits(16 + 16); // syncword, crc1
int fscod = data.readBits(2);
data.skipBits(14); // frmsizecod(6) + bsid (5 bits) + bsmod (3 bits)
data.skipBits(6 + 5 + 3); // frmsizecod, bsid, bsmod
int acmod = data.readBits(3);
if ((acmod & 0x01) != 0 && acmod != 1) {
data.skipBits(2); // cmixlev
......@@ -135,77 +131,108 @@ public final class Ac3Util {
if ((acmod & 0x04) != 0) {
data.skipBits(2); // surmixlev
}
if (acmod == 0x02) {
if (acmod == 2) {
data.skipBits(2); // dsurmod
}
boolean lfeon = data.readBit();
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, durationUs, CHANNEL_COUNTS[acmod] + (lfeon ? 1 : 0),
SAMPLE_RATES[fscod], null, language);
MediaFormat.NO_VALUE, durationUs, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0),
SAMPLE_RATE_BY_FSCOD[fscod], null, language);
}
/**
* Returns the AC-3 frame size in bytes given {@code data} containing the frame header starting
* from the sync word.
* Returns the E-AC-3 format given {@code data} containing a syncframe. The reading position of
* {@code data} will be modified.
*
* @param data Data to parse, positioned at the start of the syncword.
* @return The frame size parsed from data in the header.
* @param data The data to parse, positioned at the start of the syncframe.
* @param trackId The track identifier to set on the format, or null.
* @param durationUs The duration to set on the format, in microseconds.
* @param language The language to set on the format.
* @return The E-AC-3 format parsed from data in the header.
*/
public static int parseFrameSize(ParsableBitArray data) {
// Skip syncword and crc1.
data.skipBits(4 * 8);
public static MediaFormat parseEac3SyncframeFormat(ParsableBitArray data, String trackId,
long durationUs, String language) {
data.skipBits(16 + 2 + 11); // syncword, strmtype, frmsiz
int sampleRate;
int fscod = data.readBits(2);
int frmsizecod = data.readBits(6);
int sampleRate = SAMPLE_RATES[fscod];
int bitrate = BITRATES[frmsizecod / 2];
if (sampleRate == 32000) {
return 6 * bitrate;
} else if (sampleRate == 44100) {
return 2 * (FRMSIZECOD_TO_FRAME_SIZE_44_1[frmsizecod / 2] + (frmsizecod % 2));
} else { // sampleRate == 48000
return 4 * bitrate;
if (fscod == 3) {
sampleRate = SAMPLE_RATE_BY_FSCOD2[data.readBits(2)];
} else {
data.skipBits(2); // numblkscod
sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
}
int acmod = data.readBits(3);
boolean lfeon = data.readBit();
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_E_AC3, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, durationUs, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0),
sampleRate, null, language);
}
/**
* Returns the size in bytes of the given AC-3 syncframe.
*
* @param data The syncframe to parse.
* @return The syncframe size in bytes.
*/
public static int parseAc3SyncframeSize(byte[] data) {
int fscod = (data[4] & 0xC0) >> 6;
int frmsizecod = data[4] & 0x3F;
return getAc3SyncframeSize(fscod, frmsizecod);
}
/**
* Returns the bitrate of AC-3 audio given the size of a buffer and the sample rate.
* Returns the size in bytes of the given E-AC-3 syncframe.
*
* @param bufferSize Size in bytes of a full buffer of samples.
* @param sampleRate Sample rate in hz.
* @return Bitrate of the audio stream in kbit/s.
* @param data The syncframe to parse.
* @return The syncframe size in bytes.
*/
public static int getBitrate(int bufferSize, int sampleRate) {
// Each AC-3 buffer contains 1536 frames of audio, so the AudioTrack playback position
// advances by 1536 per buffer (32 ms at 48 kHz).
int unscaledBitrate = bufferSize * 8 * sampleRate;
int divisor = 1000 * 1536;
return (unscaledBitrate + divisor / 2) / divisor;
public static int parseEAc3SyncframeSize(byte[] data) {
return 2 * (((data[2] & 0x07) << 8) + (data[3] & 0xFF) + 1); // frmsiz
}
/**
* Returns the number of samples per AC-3 syncframe.
* Returns the number of audio samples in an AC-3 syncframe.
*/
public static int getAc3SamplesPerSyncframe() {
return AC3_SAMPLES_PER_SYNCFRAME;
public static int getAc3SyncframeAudioSampleCount() {
return AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT;
}
/**
* Returns the number of samples per syncframe for the E-AC-3 frame in {@code buffer}.
* Returns the number of audio samples represented by the given E-AC-3 syncframe.
*
* @param buffer The frame to parse.
* @return The number of samples per syncframe.
* @param data The syncframe to parse.
* @return The number of audio samples represented by the syncframe.
*/
public static int parseEac3SamplesPerSyncframe(ByteBuffer buffer) {
public static int parseEAc3SyncframeAudioSampleCount(byte[] data) {
// See ETSI TS 102 366 subsection E.1.2.2.
int audioBlocks;
if (((buffer.get(buffer.position() + 4) & 0xC0) >> 6) == 0x03) { // fscod
audioBlocks = 6;
} else {
int numblkscod = (buffer.get(buffer.position() + 4) & 0x30) >> 4;
audioBlocks = AUDIO_BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[numblkscod] * 256;
return AUDIO_SAMPLES_PER_AUDIO_BLOCK * (((data[4] & 0xC0) >> 6) == 0x03 ? 6 // fscod
: BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(data[4] & 0x30) >> 4]);
}
/**
* Like {@link #parseEAc3SyncframeAudioSampleCount(byte[])} but reads from a byte buffer. The
* buffer position is not modified.
*
* @see #parseEAc3SyncframeAudioSampleCount(byte[])
*/
public static int parseEAc3SyncframeAudioSampleCount(ByteBuffer buffer) {
// See ETSI TS 102 366 subsection E.1.2.2.
int fscod = (buffer.get(buffer.position() + 4) & 0xC0) >> 6;
return AUDIO_SAMPLES_PER_AUDIO_BLOCK * (fscod == 0x03 ? 6
: BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(buffer.get(buffer.position() + 4) & 0x30) >> 4]);
}
private static int getAc3SyncframeSize(int fscod, int frmsizecod) {
int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
if (sampleRate == 44100) {
return 2 * (SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1[frmsizecod / 2] + (frmsizecod % 2));
}
int bitrate = BITRATE_BY_HALF_FRMSIZECOD[frmsizecod / 2];
if (sampleRate == 32000) {
return 6 * bitrate;
} else { // sampleRate == 48000
return 4 * bitrate;
}
return audioBlocks * SAMPLES_PER_AUDIO_BLOCK;
}
private Ac3Util() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment