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));
......
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