mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Add support for Dolby TrueHD passthrough
Issue: #2147 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=180678595
This commit is contained in:
parent
7314e9bddc
commit
8e8e53c42d
6 changed files with 211 additions and 54 deletions
|
|
@ -39,6 +39,8 @@
|
||||||
* DefaultTrackSelector: Support disabling of individual text track selection
|
* DefaultTrackSelector: Support disabling of individual text track selection
|
||||||
flags.
|
flags.
|
||||||
* New Cast extension: Simplifies toggling between local and Cast playbacks.
|
* New Cast extension: Simplifies toggling between local and Cast playbacks.
|
||||||
|
* Audio: Support TrueHD passthrough for rechunked samples in Matroska files
|
||||||
|
([#2147](https://github.com/google/ExoPlayer/issues/2147)).
|
||||||
|
|
||||||
### 2.6.1 ###
|
### 2.6.1 ###
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,13 +122,22 @@ public final class C {
|
||||||
*/
|
*/
|
||||||
public static final int AUDIO_SESSION_ID_UNSET = AudioManager.AUDIO_SESSION_ID_GENERATE;
|
public static final int AUDIO_SESSION_ID_UNSET = AudioManager.AUDIO_SESSION_ID_GENERATE;
|
||||||
|
|
||||||
/**
|
/** Represents an audio encoding, or an invalid or unset value. */
|
||||||
* Represents an audio encoding, or an invalid or unset value.
|
|
||||||
*/
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT,
|
@IntDef({
|
||||||
ENCODING_PCM_24BIT, ENCODING_PCM_32BIT, ENCODING_PCM_FLOAT, ENCODING_AC3, ENCODING_E_AC3,
|
Format.NO_VALUE,
|
||||||
ENCODING_DTS, ENCODING_DTS_HD})
|
ENCODING_INVALID,
|
||||||
|
ENCODING_PCM_8BIT,
|
||||||
|
ENCODING_PCM_16BIT,
|
||||||
|
ENCODING_PCM_24BIT,
|
||||||
|
ENCODING_PCM_32BIT,
|
||||||
|
ENCODING_PCM_FLOAT,
|
||||||
|
ENCODING_AC3,
|
||||||
|
ENCODING_E_AC3,
|
||||||
|
ENCODING_DTS,
|
||||||
|
ENCODING_DTS_HD,
|
||||||
|
ENCODING_DOLBY_TRUEHD
|
||||||
|
})
|
||||||
public @interface Encoding {}
|
public @interface Encoding {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -138,46 +147,28 @@ public final class C {
|
||||||
@IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT,
|
@IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT,
|
||||||
ENCODING_PCM_24BIT, ENCODING_PCM_32BIT, ENCODING_PCM_FLOAT})
|
ENCODING_PCM_24BIT, ENCODING_PCM_32BIT, ENCODING_PCM_FLOAT})
|
||||||
public @interface PcmEncoding {}
|
public @interface PcmEncoding {}
|
||||||
/**
|
/** @see AudioFormat#ENCODING_INVALID */
|
||||||
* @see AudioFormat#ENCODING_INVALID
|
|
||||||
*/
|
|
||||||
public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
|
public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
|
||||||
/**
|
/** @see AudioFormat#ENCODING_PCM_8BIT */
|
||||||
* @see AudioFormat#ENCODING_PCM_8BIT
|
|
||||||
*/
|
|
||||||
public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
|
public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
|
||||||
/**
|
/** @see AudioFormat#ENCODING_PCM_16BIT */
|
||||||
* @see AudioFormat#ENCODING_PCM_16BIT
|
|
||||||
*/
|
|
||||||
public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
|
public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
|
||||||
/**
|
/** PCM encoding with 24 bits per sample. */
|
||||||
* PCM encoding with 24 bits per sample.
|
|
||||||
*/
|
|
||||||
public static final int ENCODING_PCM_24BIT = 0x80000000;
|
public static final int ENCODING_PCM_24BIT = 0x80000000;
|
||||||
/**
|
/** PCM encoding with 32 bits per sample. */
|
||||||
* PCM encoding with 32 bits per sample.
|
|
||||||
*/
|
|
||||||
public static final int ENCODING_PCM_32BIT = 0x40000000;
|
public static final int ENCODING_PCM_32BIT = 0x40000000;
|
||||||
/**
|
/** @see AudioFormat#ENCODING_PCM_FLOAT */
|
||||||
* @see AudioFormat#ENCODING_PCM_FLOAT
|
|
||||||
*/
|
|
||||||
public static final int ENCODING_PCM_FLOAT = AudioFormat.ENCODING_PCM_FLOAT;
|
public static final int ENCODING_PCM_FLOAT = AudioFormat.ENCODING_PCM_FLOAT;
|
||||||
/**
|
/** @see AudioFormat#ENCODING_AC3 */
|
||||||
* @see AudioFormat#ENCODING_AC3
|
|
||||||
*/
|
|
||||||
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_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
|
|
||||||
*/
|
|
||||||
public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD;
|
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#CHANNEL_OUT_7POINT1_SURROUND
|
* @see AudioFormat#CHANNEL_OUT_7POINT1_SURROUND
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,7 @@ import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/** Utility methods for parsing Dolby TrueHD and (E-)AC3 syncframes. */
|
||||||
* Utility methods for parsing (E-)AC-3 syncframes, which are access units in (E-)AC-3 bitstreams.
|
|
||||||
*/
|
|
||||||
public final class Ac3Util {
|
public final class Ac3Util {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -93,6 +91,17 @@ public final class Ac3Util {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of samples to store in each output chunk when rechunking TrueHD streams. The number
|
||||||
|
* of samples extracted from the container corresponding to one syncframe must be an integer
|
||||||
|
* multiple of this value.
|
||||||
|
*/
|
||||||
|
public static final int TRUEHD_RECHUNK_SAMPLE_COUNT = 8;
|
||||||
|
/**
|
||||||
|
* The number of bytes that must be parsed from a TrueHD syncframe to calculate the sample count.
|
||||||
|
*/
|
||||||
|
public static final int TRUEHD_SYNCFRAME_PREFIX_LENGTH = 12;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of new samples per (E-)AC-3 audio block.
|
* The number of new samples per (E-)AC-3 audio block.
|
||||||
*/
|
*/
|
||||||
|
|
@ -441,6 +450,43 @@ public final class Ac3Util {
|
||||||
: BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(buffer.get(buffer.position() + 4) & 0x30) >> 4]);
|
: BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(buffer.get(buffer.position() + 4) & 0x30) >> 4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of audio samples represented by the given TrueHD syncframe, or 0 if the
|
||||||
|
* buffer is not the start of a syncframe.
|
||||||
|
*
|
||||||
|
* @param syncframe The bytes from which to read the syncframe. Must be at least {@link
|
||||||
|
* #TRUEHD_SYNCFRAME_PREFIX_LENGTH} bytes long.
|
||||||
|
* @return The number of audio samples represented by the syncframe, or 0 if the buffer doesn't
|
||||||
|
* contain the start of a syncframe.
|
||||||
|
*/
|
||||||
|
public static int parseTrueHdSyncframeAudioSampleCount(byte[] syncframe) {
|
||||||
|
// TODO: Link to specification if available.
|
||||||
|
if (syncframe[4] != (byte) 0xF8
|
||||||
|
|| syncframe[5] != (byte) 0x72
|
||||||
|
|| syncframe[6] != (byte) 0x6F
|
||||||
|
|| syncframe[7] != (byte) 0xBA) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 40 << (syncframe[8] & 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the number of audio samples represented by the given TrueHD syncframe, or 0 if the buffer
|
||||||
|
* is not the start of a syncframe. The buffer's position is not modified.
|
||||||
|
*
|
||||||
|
* @param buffer The {@link ByteBuffer} from which to read the syncframe. Must have at least
|
||||||
|
* {@link #TRUEHD_SYNCFRAME_PREFIX_LENGTH} bytes remaining.
|
||||||
|
* @return The number of audio samples represented by the syncframe, or 0 if the buffer is not the
|
||||||
|
* start of a syncframe.
|
||||||
|
*/
|
||||||
|
public static int parseTrueHdSyncframeAudioSampleCount(ByteBuffer buffer) {
|
||||||
|
// TODO: Link to specification if available.
|
||||||
|
if (buffer.getInt(buffer.position() + 4) != 0xBA6F72F8) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 40 << (buffer.get(buffer.position() + 8) & 0x07);
|
||||||
|
}
|
||||||
|
|
||||||
private static int getAc3SyncframeSize(int fscod, int frmsizecod) {
|
private static int getAc3SyncframeSize(int fscod, int frmsizecod) {
|
||||||
int halfFrmsizecod = frmsizecod / 2;
|
int halfFrmsizecod = frmsizecod / 2;
|
||||||
if (fscod < 0 || fscod >= SAMPLE_RATE_BY_FSCOD.length || frmsizecod < 0
|
if (fscod < 0 || fscod >= SAMPLE_RATE_BY_FSCOD.length || frmsizecod < 0
|
||||||
|
|
|
||||||
|
|
@ -446,9 +446,12 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
if (outputEncoding == C.ENCODING_AC3 || outputEncoding == C.ENCODING_E_AC3) {
|
if (outputEncoding == C.ENCODING_AC3 || outputEncoding == C.ENCODING_E_AC3) {
|
||||||
// AC-3 allows bitrates up to 640 kbit/s.
|
// AC-3 allows bitrates up to 640 kbit/s.
|
||||||
bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 80 * 1024 / C.MICROS_PER_SECOND);
|
bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 80 * 1024 / C.MICROS_PER_SECOND);
|
||||||
} else /* (outputEncoding == C.ENCODING_DTS || outputEncoding == C.ENCODING_DTS_HD */ {
|
} else if (outputEncoding == 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.
|
||||||
bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 192 * 1024 / C.MICROS_PER_SECOND);
|
bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 192 * 1024 / C.MICROS_PER_SECOND);
|
||||||
|
} else /* outputEncoding == C.ENCODING_DTS_HD || outputEncoding == C.ENCODING_DOLBY_TRUEHD*/ {
|
||||||
|
// HD passthrough requires a larger buffer to avoid underrun.
|
||||||
|
bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 192 * 6 * 1024 / C.MICROS_PER_SECOND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bufferSizeUs =
|
bufferSizeUs =
|
||||||
|
|
@ -580,6 +583,13 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
if (!isInputPcm && framesPerEncodedSample == 0) {
|
if (!isInputPcm && framesPerEncodedSample == 0) {
|
||||||
// If this is the first encoded sample, calculate the sample size in frames.
|
// If this is the first encoded sample, calculate the sample size in frames.
|
||||||
framesPerEncodedSample = getFramesPerEncodedSample(outputEncoding, buffer);
|
framesPerEncodedSample = getFramesPerEncodedSample(outputEncoding, buffer);
|
||||||
|
if (framesPerEncodedSample == 0) {
|
||||||
|
// We still don't know the number of frames per sample, so drop the buffer.
|
||||||
|
// For TrueHD this can occur after some seek operations, as not every sample starts with
|
||||||
|
// a syncframe header. If we chunked samples together so the extracted samples always
|
||||||
|
// started with a syncframe header, the chunks would be too large.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drainingPlaybackParameters != null) {
|
if (drainingPlaybackParameters != null) {
|
||||||
|
|
@ -1225,6 +1235,9 @@ 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_DOLBY_TRUEHD) {
|
||||||
|
return Ac3Util.parseTrueHdSyncframeAudioSampleCount(buffer)
|
||||||
|
* Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Unexpected audio encoding: " + encoding);
|
throw new IllegalStateException("Unexpected audio encoding: " + encoding);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,13 @@
|
||||||
package com.google.android.exoplayer2.extractor.mkv;
|
package com.google.android.exoplayer2.extractor.mkv;
|
||||||
|
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
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.Ac3Util;
|
||||||
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;
|
||||||
|
|
@ -32,6 +34,7 @@ import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||||
import com.google.android.exoplayer2.extractor.PositionHolder;
|
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.LongArray;
|
import com.google.android.exoplayer2.util.LongArray;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.NalUnitUtil;
|
import com.google.android.exoplayer2.util.NalUnitUtil;
|
||||||
|
|
@ -413,6 +416,9 @@ public final class MatroskaExtractor implements Extractor {
|
||||||
reader.reset();
|
reader.reset();
|
||||||
varintReader.reset();
|
varintReader.reset();
|
||||||
resetSample();
|
resetSample();
|
||||||
|
for (int i = 0; i < tracks.size(); i++) {
|
||||||
|
tracks.valueAt(i).reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -431,7 +437,13 @@ public final class MatroskaExtractor implements Extractor {
|
||||||
return Extractor.RESULT_SEEK;
|
return Extractor.RESULT_SEEK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return continueReading ? Extractor.RESULT_CONTINUE : Extractor.RESULT_END_OF_INPUT;
|
if (!continueReading) {
|
||||||
|
for (int i = 0; i < tracks.size(); i++) {
|
||||||
|
tracks.valueAt(i).outputPendingSampleMetadata();
|
||||||
|
}
|
||||||
|
return Extractor.RESULT_END_OF_INPUT;
|
||||||
|
}
|
||||||
|
return Extractor.RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ int getElementType(int id) {
|
/* package */ int getElementType(int id) {
|
||||||
|
|
@ -1077,14 +1089,26 @@ public final class MatroskaExtractor implements Extractor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commitSampleToOutput(Track track, long timeUs) {
|
private void commitSampleToOutput(Track track, long timeUs) {
|
||||||
if (CODEC_ID_SUBRIP.equals(track.codecId)) {
|
if (track.trueHdSampleRechunker != null) {
|
||||||
commitSubtitleSample(track, SUBRIP_TIMECODE_FORMAT, SUBRIP_PREFIX_END_TIMECODE_OFFSET,
|
track.trueHdSampleRechunker.sampleMetadata(track, timeUs);
|
||||||
SUBRIP_TIMECODE_LAST_VALUE_SCALING_FACTOR, SUBRIP_TIMECODE_EMPTY);
|
} else {
|
||||||
} else if (CODEC_ID_ASS.equals(track.codecId)) {
|
if (CODEC_ID_SUBRIP.equals(track.codecId)) {
|
||||||
commitSubtitleSample(track, SSA_TIMECODE_FORMAT, SSA_PREFIX_END_TIMECODE_OFFSET,
|
commitSubtitleSample(
|
||||||
SSA_TIMECODE_LAST_VALUE_SCALING_FACTOR, SSA_TIMECODE_EMPTY);
|
track,
|
||||||
|
SUBRIP_TIMECODE_FORMAT,
|
||||||
|
SUBRIP_PREFIX_END_TIMECODE_OFFSET,
|
||||||
|
SUBRIP_TIMECODE_LAST_VALUE_SCALING_FACTOR,
|
||||||
|
SUBRIP_TIMECODE_EMPTY);
|
||||||
|
} else if (CODEC_ID_ASS.equals(track.codecId)) {
|
||||||
|
commitSubtitleSample(
|
||||||
|
track,
|
||||||
|
SSA_TIMECODE_FORMAT,
|
||||||
|
SSA_PREFIX_END_TIMECODE_OFFSET,
|
||||||
|
SSA_TIMECODE_LAST_VALUE_SCALING_FACTOR,
|
||||||
|
SSA_TIMECODE_EMPTY);
|
||||||
|
}
|
||||||
|
track.output.sampleMetadata(timeUs, blockFlags, sampleBytesWritten, 0, track.cryptoData);
|
||||||
}
|
}
|
||||||
track.output.sampleMetadata(timeUs, blockFlags, sampleBytesWritten, 0, track.cryptoData);
|
|
||||||
sampleRead = true;
|
sampleRead = true;
|
||||||
resetSample();
|
resetSample();
|
||||||
}
|
}
|
||||||
|
|
@ -1251,6 +1275,10 @@ public final class MatroskaExtractor implements Extractor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (track.trueHdSampleRechunker != null) {
|
||||||
|
Assertions.checkState(sampleStrippedBytes.limit() == 0);
|
||||||
|
track.trueHdSampleRechunker.startSample(input, blockFlags, size);
|
||||||
|
}
|
||||||
while (sampleBytesRead < size) {
|
while (sampleBytesRead < size) {
|
||||||
readToOutput(input, output, size - sampleBytesRead);
|
readToOutput(input, output, size - sampleBytesRead);
|
||||||
}
|
}
|
||||||
|
|
@ -1510,7 +1538,70 @@ public final class MatroskaExtractor implements Extractor {
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
MatroskaExtractor.this.binaryElement(id, contentsSize, input);
|
MatroskaExtractor.this.binaryElement(id, contentsSize, input);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rechunks TrueHD sample data into groups of {@link Ac3Util#TRUEHD_RECHUNK_SAMPLE_COUNT} samples.
|
||||||
|
*/
|
||||||
|
private static final class TrueHdSampleRechunker {
|
||||||
|
|
||||||
|
private final byte[] syncframePrefix;
|
||||||
|
|
||||||
|
private boolean foundSyncframe;
|
||||||
|
private int sampleCount;
|
||||||
|
private int chunkSize;
|
||||||
|
private long timeUs;
|
||||||
|
private @C.BufferFlags int blockFlags;
|
||||||
|
|
||||||
|
public TrueHdSampleRechunker() {
|
||||||
|
syncframePrefix = new byte[Ac3Util.TRUEHD_SYNCFRAME_PREFIX_LENGTH];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
foundSyncframe = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startSample(ExtractorInput input, @C.BufferFlags int blockFlags, int size)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
if (!foundSyncframe) {
|
||||||
|
input.peekFully(syncframePrefix, 0, Ac3Util.TRUEHD_SYNCFRAME_PREFIX_LENGTH);
|
||||||
|
input.resetPeekPosition();
|
||||||
|
if ((Ac3Util.parseTrueHdSyncframeAudioSampleCount(syncframePrefix) == C.INDEX_UNSET)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foundSyncframe = true;
|
||||||
|
sampleCount = 0;
|
||||||
|
}
|
||||||
|
if (sampleCount == 0) {
|
||||||
|
// This is the first sample in the chunk, so reset the block flags and chunk size.
|
||||||
|
this.blockFlags = blockFlags;
|
||||||
|
chunkSize = 0;
|
||||||
|
}
|
||||||
|
chunkSize += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sampleMetadata(Track track, long timeUs) {
|
||||||
|
if (!foundSyncframe) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sampleCount++ == 0) {
|
||||||
|
// This is the first sample in the chunk, so update the timestamp.
|
||||||
|
this.timeUs = timeUs;
|
||||||
|
}
|
||||||
|
if (sampleCount < Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT) {
|
||||||
|
// We haven't read enough samples to output a chunk.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
track.output.sampleMetadata(this.timeUs, blockFlags, chunkSize, 0, track.cryptoData);
|
||||||
|
sampleCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void outputPendingSampleMetadata(Track track) {
|
||||||
|
if (foundSyncframe && sampleCount > 0) {
|
||||||
|
track.output.sampleMetadata(this.timeUs, blockFlags, chunkSize, 0, track.cryptoData);
|
||||||
|
sampleCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class Track {
|
private static final class Track {
|
||||||
|
|
@ -1573,6 +1664,7 @@ public final class MatroskaExtractor implements Extractor {
|
||||||
public int sampleRate = 8000;
|
public int sampleRate = 8000;
|
||||||
public long codecDelayNs = 0;
|
public long codecDelayNs = 0;
|
||||||
public long seekPreRollNs = 0;
|
public long seekPreRollNs = 0;
|
||||||
|
@Nullable public TrueHdSampleRechunker trueHdSampleRechunker;
|
||||||
|
|
||||||
// Text elements.
|
// Text elements.
|
||||||
public boolean flagForced;
|
public boolean flagForced;
|
||||||
|
|
@ -1583,9 +1675,7 @@ public final class MatroskaExtractor implements Extractor {
|
||||||
public TrackOutput output;
|
public TrackOutput output;
|
||||||
public int nalUnitLengthFieldLength;
|
public int nalUnitLengthFieldLength;
|
||||||
|
|
||||||
/**
|
/** Initializes the track with an output. */
|
||||||
* Initializes the track with an output.
|
|
||||||
*/
|
|
||||||
public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException {
|
public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException {
|
||||||
String mimeType;
|
String mimeType;
|
||||||
int maxInputSize = Format.NO_VALUE;
|
int maxInputSize = Format.NO_VALUE;
|
||||||
|
|
@ -1669,6 +1759,7 @@ public final class MatroskaExtractor implements Extractor {
|
||||||
break;
|
break;
|
||||||
case CODEC_ID_TRUEHD:
|
case CODEC_ID_TRUEHD:
|
||||||
mimeType = MimeTypes.AUDIO_TRUEHD;
|
mimeType = MimeTypes.AUDIO_TRUEHD;
|
||||||
|
trueHdSampleRechunker = new TrueHdSampleRechunker();
|
||||||
break;
|
break;
|
||||||
case CODEC_ID_DTS:
|
case CODEC_ID_DTS:
|
||||||
case CODEC_ID_DTS_EXPRESS:
|
case CODEC_ID_DTS_EXPRESS:
|
||||||
|
|
@ -1786,9 +1877,21 @@ public final class MatroskaExtractor implements Extractor {
|
||||||
this.output.format(format);
|
this.output.format(format);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Forces any pending sample metadata to be flushed to the output. */
|
||||||
* Returns the HDR Static Info as defined in CTA-861.3.
|
public void outputPendingSampleMetadata() {
|
||||||
*/
|
if (trueHdSampleRechunker != null) {
|
||||||
|
trueHdSampleRechunker.outputPendingSampleMetadata(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resets any state stored in the track in response to a seek. */
|
||||||
|
public void reset() {
|
||||||
|
if (trueHdSampleRechunker != null) {
|
||||||
|
trueHdSampleRechunker.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the HDR Static Info as defined in CTA-861.3. */
|
||||||
private byte[] getHdrStaticInfo() {
|
private byte[] getHdrStaticInfo() {
|
||||||
// Are all fields present.
|
// Are all fields present.
|
||||||
if (primaryRChromaticityX == Format.NO_VALUE || primaryRChromaticityY == Format.NO_VALUE
|
if (primaryRChromaticityX == Format.NO_VALUE || primaryRChromaticityY == Format.NO_VALUE
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,8 @@ public final class MimeTypes {
|
||||||
return C.ENCODING_DTS;
|
return C.ENCODING_DTS;
|
||||||
case MimeTypes.AUDIO_DTS_HD:
|
case MimeTypes.AUDIO_DTS_HD:
|
||||||
return C.ENCODING_DTS_HD;
|
return C.ENCODING_DTS_HD;
|
||||||
|
case MimeTypes.AUDIO_TRUEHD:
|
||||||
|
return C.ENCODING_DOLBY_TRUEHD;
|
||||||
default:
|
default:
|
||||||
return C.ENCODING_INVALID;
|
return C.ENCODING_INVALID;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue