From 280cc545dd5c5a8e6af86fdacdb2a4944e02ba5f Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 5 Apr 2018 11:52:28 -0700 Subject: [PATCH] Search for TrueHD syncframes In MatroskaExtractor TrueHD audio samples are joined into larger chunks. For some streams the resulting chunked samples seem never to start with a syncframe. This could result in playback of TrueHD streams getting stuck after seeking because we could never read a syncframe at the start of a sample to determine the sample size. Instead of expecting to find a syncframe at the start of a sample, search for it within the sample, to fix this issue. Note: this means that we may search a few thousand bytes to find the sample size, but the cost is only incurred for the first audio sample read. Issue: #3845 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=191775779 --- RELEASENOTES.md | 3 ++ .../android/exoplayer2/audio/Ac3Util.java | 45 +++++++++++++------ .../exoplayer2/audio/DefaultAudioSink.java | 7 ++- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f56a511f6ae..1cc4d658c97 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -36,6 +36,9 @@ advancing ([#3841](https://github.com/google/ExoPlayer/issues/3841)). * Add an option to skip silent audio in `PlaybackParameters` ((#2635)[https://github.com/google/ExoPlayer/issues/2635]). + * Fix an issue where playback of TrueHD streams would get stuck after seeking + due to not finding a syncframe + ((#3845)[https://github.com/google/ExoPlayer/issues/3845]). * Caching: * Add release method to Cache interface. * Prevent multiple instances of SimpleCache in the same folder. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index 0263bb90f8f..c61b8ff24c0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -99,7 +99,7 @@ private SyncFrameInfo( /** * 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; + public static final int TRUEHD_SYNCFRAME_PREFIX_LENGTH = 10; /** * The number of new samples per (E-)AC-3 audio block. @@ -463,6 +463,26 @@ public static int parseEAc3SyncframeAudioSampleCount(ByteBuffer buffer) { : BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(buffer.get(buffer.position() + 4) & 0x30) >> 4]); } + /** + * Returns the offset relative to the buffer's position of the start of a TrueHD syncframe, or + * {@link C#INDEX_UNSET} if no syncframe was found. The buffer's position is not modified. + * + * @param buffer The {@link ByteBuffer} within which to find a syncframe. + * @return The offset relative to the buffer's position of the start of a TrueHD syncframe, or + * {@link C#INDEX_UNSET} if no syncframe was found. + */ + public static int findTrueHdSyncframeOffset(ByteBuffer buffer) { + int startIndex = buffer.position(); + int endIndex = buffer.limit() - TRUEHD_SYNCFRAME_PREFIX_LENGTH; + for (int i = startIndex; i <= endIndex; i++) { + // The syncword ends 0xBA for TrueHD or 0xBB for MLP. + if ((buffer.getInt(i + 4) & 0xFEFFFFFF) == 0xBA6F72F8) { + return i - startIndex; + } + } + return C.INDEX_UNSET; + } + /** * Returns the number of audio samples represented by the given TrueHD syncframe, or 0 if the * buffer is not the start of a syncframe. @@ -481,25 +501,22 @@ public static int parseTrueHdSyncframeAudioSampleCount(byte[] syncframe) { || (syncframe[7] & 0xFE) != 0xBA) { return 0; } - return 40 << (syncframe[8] & 7); + boolean isMlp = (syncframe[7] & 0xFF) == 0xBB; + return 40 << ((syncframe[isMlp ? 9 : 8] >> 4) & 0x07); } /** - * 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. + * Reads the number of audio samples represented by a TrueHD 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. + * @param buffer The {@link ByteBuffer} from which to read the syncframe. + * @param offset The offset of the start of the syncframe relative to the buffer's position. + * @return The number of audio samples represented by the syncframe. */ - public static int parseTrueHdSyncframeAudioSampleCount(ByteBuffer buffer) { + public static int parseTrueHdSyncframeAudioSampleCount(ByteBuffer buffer, int offset) { // TODO: Link to specification if available. - // The syncword ends 0xBA for TrueHD or 0xBB for MLP. - if ((buffer.getInt(buffer.position() + 4) & 0xFEFFFFFF) != 0xBA6F72F8) { - return 0; - } - return 40 << (buffer.get(buffer.position() + 8) & 0x07); + boolean isMlp = (buffer.get(buffer.position() + offset + 7) & 0xFF) == 0xBB; + return 40 << ((buffer.get(buffer.position() + offset + (isMlp ? 9 : 8)) >> 4) & 0x07); } private static int getAc3SyncframeSize(int fscod, int frmsizecod) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 5f363cdaf19..371fe50b216 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -1076,8 +1076,11 @@ private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffe } else if (encoding == C.ENCODING_E_AC3) { return Ac3Util.parseEAc3SyncframeAudioSampleCount(buffer); } else if (encoding == C.ENCODING_DOLBY_TRUEHD) { - return Ac3Util.parseTrueHdSyncframeAudioSampleCount(buffer) - * Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT; + int syncframeOffset = Ac3Util.findTrueHdSyncframeOffset(buffer); + return syncframeOffset == C.INDEX_UNSET + ? 0 + : (Ac3Util.parseTrueHdSyncframeAudioSampleCount(buffer, syncframeOffset) + * Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT); } else { throw new IllegalStateException("Unexpected audio encoding: " + encoding); }