Commit ad7237b5 by Oliver Woodman

Handle E-AC-3 audio in HLS.

parent 9fd575e1
...@@ -941,9 +941,9 @@ public final class AudioTrack { ...@@ -941,9 +941,9 @@ public final class AudioTrack {
| ((buffer.get(buffer.position() + 5) & 0xFC) >> 2); | ((buffer.get(buffer.position() + 5) & 0xFC) >> 2);
return (nblks + 1) * 32; return (nblks + 1) * 32;
} else if (encoding == C.ENCODING_AC3) { } else if (encoding == C.ENCODING_AC3) {
return Ac3Util.getAc3SamplesPerSyncframe(); return Ac3Util.getAc3SyncframeAudioSampleCount();
} else if (encoding == C.ENCODING_E_AC3) { } else if (encoding == C.ENCODING_E_AC3) {
return Ac3Util.parseEac3SamplesPerSyncframe(buffer); return Ac3Util.parseEAc3SyncframeAudioSampleCount(buffer);
} else { } else {
throw new IllegalStateException("Unexpected audio encoding: " + encoding); throw new IllegalStateException("Unexpected audio encoding: " + encoding);
} }
......
...@@ -774,12 +774,12 @@ import java.util.List; ...@@ -774,12 +774,12 @@ import java.util.List;
// TODO: Choose the right AC-3 track based on the contents of dac3/dec3. // TODO: Choose the right AC-3 track based on the contents of dac3/dec3.
// TODO: Add support for encryption (by setting out.trackEncryptionBoxes). // TODO: Add support for encryption (by setting out.trackEncryptionBoxes).
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition); parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
out.mediaFormat = Ac3Util.parseAnnexFAc3Format(parent, Integer.toString(trackId), out.mediaFormat = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId),
durationUs, language); durationUs, language);
return; return;
} else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) { } else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition); parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
out.mediaFormat = Ac3Util.parseAnnexFEAc3Format(parent, Integer.toString(trackId), out.mediaFormat = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId),
durationUs, language); durationUs, language);
return; return;
} else if ((atomType == Atom.TYPE_dtsc || atomType == Atom.TYPE_dtse } else if ((atomType == Atom.TYPE_dtsc || atomType == Atom.TYPE_dtse
......
...@@ -23,7 +23,7 @@ import com.google.android.exoplayer.util.ParsableBitArray; ...@@ -23,7 +23,7 @@ import com.google.android.exoplayer.util.ParsableBitArray;
import com.google.android.exoplayer.util.ParsableByteArray; 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 { /* package */ final class Ac3Reader extends ElementaryStreamReader {
...@@ -33,6 +33,7 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -33,6 +33,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
private static final int HEADER_SIZE = 8; private static final int HEADER_SIZE = 8;
private final boolean isEac3;
private final ParsableBitArray headerScratchBits; private final ParsableBitArray headerScratchBits;
private final ParsableByteArray headerScratchBytes; private final ParsableByteArray headerScratchBytes;
...@@ -43,16 +44,23 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -43,16 +44,23 @@ import com.google.android.exoplayer.util.ParsableByteArray;
private boolean lastByteWas0B; private boolean lastByteWas0B;
// Used when parsing the header. // Used when parsing the header.
private long frameDurationUs; private long sampleDurationUs;
private MediaFormat mediaFormat; private MediaFormat mediaFormat;
private int sampleSize; private int sampleSize;
private int bitrate;
// Used when reading the samples. // Used when reading the samples.
private long timeUs; 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); super(output);
this.isEac3 = isEac3;
headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]); headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]);
headerScratchBytes = new ParsableByteArray(headerScratchBits.data); headerScratchBytes = new ParsableByteArray(headerScratchBits.data);
state = STATE_FINDING_SYNC; state = STATE_FINDING_SYNC;
...@@ -94,7 +102,7 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -94,7 +102,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
bytesRead += bytesToRead; bytesRead += bytesToRead;
if (bytesRead == sampleSize) { if (bytesRead == sampleSize) {
output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
timeUs += frameDurationUs; timeUs += sampleDurationUs;
state = STATE_FINDING_SYNC; state = STATE_FINDING_SYNC;
} }
break; break;
...@@ -124,11 +132,11 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -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. * Locates the next syncword, advancing the position to the byte that immediately follows it. If a
* If a sync word was not located, the position is advanced to the limit. * syncword was not located, the position is advanced to the limit.
* *
* @param pesBuffer The buffer whose position should be advanced. * @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) { private boolean skipToNextSync(ParsableByteArray pesBuffer) {
while (pesBuffer.bytesLeft() > 0) { while (pesBuffer.bytesLeft() > 0) {
...@@ -151,15 +159,21 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -151,15 +159,21 @@ import com.google.android.exoplayer.util.ParsableByteArray;
* Parses the sample header. * Parses the sample header.
*/ */
private void parseHeader() { private void parseHeader() {
headerScratchBits.setPosition(0);
sampleSize = Ac3Util.parseFrameSize(headerScratchBits);
if (mediaFormat == null) { if (mediaFormat == null) {
headerScratchBits.setPosition(0); mediaFormat = isEac3
mediaFormat = Ac3Util.parseFrameAc3Format(headerScratchBits, null, C.UNKNOWN_TIME_US, null); ? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, C.UNKNOWN_TIME_US, null)
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, C.UNKNOWN_TIME_US, null);
output.format(mediaFormat); 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; ...@@ -50,7 +50,7 @@ import java.util.Collections;
// Used when parsing the header. // Used when parsing the header.
private boolean hasOutputFormat; private boolean hasOutputFormat;
private long frameDurationUs; private long sampleDurationUs;
private int sampleSize; private int sampleSize;
// Used when reading the samples. // Used when reading the samples.
...@@ -96,7 +96,7 @@ import java.util.Collections; ...@@ -96,7 +96,7 @@ import java.util.Collections;
bytesRead += bytesToRead; bytesRead += bytesToRead;
if (bytesRead == sampleSize) { if (bytesRead == sampleSize) {
output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
timeUs += frameDurationUs; timeUs += sampleDurationUs;
bytesRead = 0; bytesRead = 0;
state = STATE_FINDING_SYNC; state = STATE_FINDING_SYNC;
} }
...@@ -173,7 +173,9 @@ import java.util.Collections; ...@@ -173,7 +173,9 @@ import java.util.Collections;
MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC, MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, audioParams.second, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, audioParams.second,
audioParams.first, Collections.singletonList(audioSpecificConfig), null); 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); output.format(mediaFormat);
hasOutputFormat = true; hasOutputFormat = true;
} else { } else {
......
...@@ -310,9 +310,11 @@ public final class TsExtractor implements Extractor { ...@@ -310,9 +310,11 @@ public final class TsExtractor implements Extractor {
case TS_STREAM_TYPE_AAC: case TS_STREAM_TYPE_AAC:
pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC)); pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC));
break; break;
case TS_STREAM_TYPE_E_AC3:
case TS_STREAM_TYPE_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; break;
case TS_STREAM_TYPE_H262: case TS_STREAM_TYPE_H262:
pesPayloadReader = new H262Reader(output.track(TS_STREAM_TYPE_H262)); pesPayloadReader = new H262Reader(output.track(TS_STREAM_TYPE_H262));
......
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